Api reference

A comprehensive reference of all methods that kotlin-pretty provides.

This has been mostly adapted from https://hackage.haskell.org/package/prettyprinter-1.5.1/docs/Data-Text-Prettyprint-Doc.html.

Functions on Doc

Lists functions that operator on and/or return a Doc<A>

Basic

Primitives and simple combinators to create and alter documents:

fun nil(): Doc<Nothing>

Creates an empty document. The resulting document still has a height of 1. So in combination with e.g. vCat it can still render as a newline.

listOf("hello".text(), nil(), "world".text()).vCat()
// hello
// 
// world

fun String.text(): Doc<Nothing>

Create a document which contains the string as text.

The string should never contain newlines as that would lead to unwanted behaviour. If it can contain newlines, use doc instead.


fun String.doc(): Doc<Nothing>

Create a document which contains the string as text and also handles newlines by replacing them with hardLine()


fun line(): Doc<Nothing>

This document advances the layout to the next line and sets the indentation to the current nesting level.

"This is rendered in".text() + line() + "two lines".text()
// This is rendered in
// two lines

If inside a group the newline can be undone and replaced by a space.


fun lineBreak(): Doc<Nothing>

This document advances the layout to the next line and sets the indentation to the current nesting level.

"This is rendered in".text() + lineBreak() + "two lines".text()
// This is rendered in
// two lines

If inside a group the newline can be undone and replaced by nil.


fun softLine(): Doc<Nothing>

softLine behaves like a space if the layout fits, otherwise like a newline.

("This is rendered in".text() + softLine() + "one line".text()).pretty(maxWidth = 80)
// This is rendered in one line
("This is rendered in".text() + softLine() + "two lines".text()).pretty(maxWidth = 20)
// This is rendered in
// two lines
softLine() == line().group()

fun softLineBreak(): Doc<Nothing>

softLineBreak behavious like nil if the layout fits, otherwise like a newline.

("This is rendered in".text() + softLineBreak() + "one line".text()).pretty(maxWidth = 80)
// This is rendered inone line
("This is rendered in".text() + softLineBreak() + "two lines".text()).pretty(maxWidth = 20)
// This is rendered in
// two lines
softLineBreak() == lineBreak().group()

fun hardLine(): Doc<Nothing>

Insert a new line which cannot be flattened by group.


fun <A> Doc<A>.nest(i: Int): Doc<A>

Change the layout of a document by increasing the indentation level (of the following lines) by i. Negative values are allowed and will decrease the nesting level.

listOf(listOf("Hello".text(), "World".text()).vCat().nest(4), "!".text()).vCat()
// Hello
//     World
// !

See also:

  • hang nest relative to the current cursor position, instead of the current nesting level
  • align set nesting level to the current cursor position
  • indent directly increase indentation padded with spaces

fun <A> Doc<A>.group(): Doc<A>

Group tries to flatten a document to a single line. If this fails (on hardLine's) or does not fit, the document will be layed out unchanged. This function is the key to building adapting layouts.

See vCat, line, flatAlt for examples.


fun <A> Doc<A>.flatAlt(fallback: Doc<A>): Doc<A>

Renders a document as is by default, but when inside a group it will render the fallback. TODO note on how fallbacks affect the rendering and what invariants it should hold.

line() == hardLine().flatAlt(space())
lineBreak() == hardLine().flatAlt(nil())

Alignment

This section describes functions that can align their output relative to the current cursor position as opposed to nest which always aligns to the current nesting level. This means that in terms of the Wadler-Leijen algorithm (which is what is used to layout the document), these functions do not produce an optimal result. They are however immensly useful in practice. Some of these functions are also more expensive to use at the top level, but should be fine in most cases.

fun <A> Doc<A>.align(): Doc<A>

Align will lay out the document with the current nesting level set to the current column.

"Hello".text() spaced listOf("World".text(), "there".text()).vSep()
// Hello World
// there
"Hello".text() spaced listOf("World".text(), "there".text()).vSep().align()
// Hello World
//       there

fun <A> Doc<A>.hang(i: Int): Doc<A>

Hang lays out the document x with a nesting level set to the current column plus i. Negative values are allowed, and decrease the nesting level.

val doc = "Indenting these words with hang".reflow()
("prefix".text() spaced doc.hang(4)).pretty(maxWidth = 24)
// prefix Indenting
//            these
//            words with
//            hang
val doc = "Indenting these words with nest".reflow()
("prefix".text() spaced doc.nest(4)).pretty(maxWidth = 24)
// prefix Indenting
//     these
//     words with
//     nest
hang(i) == nest(i).align()

fun <A> Doc<A>.indent(i: Int): Doc<A>

Indents the document with i spaces, starting from the current column.

val doc = "Indent these words using indent".reflow()
("prefix".text() spaced doc.indent(4)).pretty(maxWidth = 24)
// prefix     Indent
//            these
//            words
//            using
//            indent

fun <A> List<Doc<A>>.encloseSep(left: Doc<A>, right: Doc<A>, sep: Doc<A>): Doc<A>

Concatenate the documents between left and right and add sep in between.

val encloseSep = "list".text() spaced listOf(1.doc(), 2.doc(), 10.doc(), 1698.doc())
    .encloseSep(lBracket(), rBracket(), comma())
    .align()
encloseSep.pretty(maxWidth = 80)
// list [1,2,10,1698]
encloseSep.pretty(maxWidth = 10)
// list [1
//      ,2
//      ,10
//      ,1698]

If you want to put the separator as a suffix, take a look at puncuate.


fun <A> List<Doc<A>>.list(): Doc<A>

Variant of encloseSep for list-like output.

val listDoc = listOf(1.doc(), 10.doc(), 100.doc(), 1000.doc(), 10000.doc()).list()
listDoc.pretty(maxWidth = 80)
// [1, 10, 100, 1000, 10000]
listDoc.pretty(maxWidth = 10)
// [ 1
// , 10
// , 100
// , 1000
// , 10000] 

fun <A> List<Doc<A>>.tupled(): Doc<A>

Variant of encloseSep for tuple-like output.

val tupleDoc = listOf(1.doc(), 10.doc(), 100.doc(), 1000.doc(), 10000.doc()).tupled()
tupleDoc.pretty(maxWidth = 80)
// (1, 10, 100, 1000, 10000)
tupleDoc.pretty(maxWidth = 10)
// ( 1
// , 10
// , 100
// , 1000
// , 10000) 

fun <A> List<Doc<A>>.semiBraces(): Doc<A>

Variant of encloseSep for braced output.

val bracedDoc = listOf(1.doc(), 10.doc(), 100.doc(), 1000.doc(), 10000.doc()).semiBraces()
bracedDoc.pretty(maxWidth = 80)
// {1, 10, 100, 1000, 10000}
bracedDoc.pretty(maxWidth = 10)
// { 1
// , 10
// , 100
// , 1000
// , 10000} 

Binary and infix functions

fun <A> Doc<A>.plus(other: Doc<A>): Doc<A>

Concatenate two documents.

"Hello".text() + space() + "world".text()
// Hello world

fun <A> Doc<A>.spaced(other: Doc<A>): Doc<A>

Concatenate two documents with a space in between.

"Hello".text() spaced "world".text()
// Hello world

fun <A> Doc<A>.line(other: Doc<A>): Doc<A>

Concatenate two documents with a line in between.

"Hello".text() line "world".text()
// Hello
// world

fun <A> Doc<A>.lineBreak(other: Doc<A>): Doc<A>

Concatenate two documents with a lineBreak in between.

"Hello".text() lineBreak "world".text()
// Hello
// world

fun <A> Doc<A>.softLine(other: Doc<A>): Doc<A>

Concatenate two documents with a softLine in between.

"Hello".text() softLine "world".text()
// Hello world

fun <A> Doc<A>.softLineBreak(other: Doc<A>): Doc<A>

Concatenate two documents with a softLineBreak in between.

"Hello ".text() softLineBreak "world".text()
// Hello world

List

These functions generalize working over lists of documents. They are separated into two families: sep and cat where sep uses line and cat uses lineBreak to separate content.

fun <A> List<Doc<A>>.foldDoc(f: (Doc<A>, Doc<A>) -> Doc<A>): Doc<A>

Concatenate the documents in a list given a binary function f. Almost all other operators on lists are implemented using this function.


fun <F, A> Kind<F, Doc<A>>.foldDoc(FF: Foldable<F>, f: (Doc<A>, Doc<A>) -> Doc<A>): Doc<A>

Kind polymorphic version of foldDoc that allows folding any foldable structure.


Sep

Functions from this list will use line to insert linebreaks. This means it when used with group they will be replaced by spaces.

fun <A> List<Doc<A>>.hSep(): Doc<A>

Concatenate all documents using spaced. This never introduces newlines on its own.

val hSepDoc = listOf("Hello".text(), "there!".text(), "- Kenobi".text()).hSep()
hSepDoc.pretty(maxWidth = 80)
// Hello there! - Kenobi
hSepDoc.pretty(maxWidth = 10)
// Hello there! - Kenobi

For a layout that automatically adds linebreaks, consider fillSep instead.


fun <A> List<Doc<A>>.vSep(): Doc<A>

Concatenate all documents vertically using line.

val vSepDoc = listOf("Text".text(), "to".text(), "lay".text(), "out".text()).vSep()
"prefix".text() spaced vSepDoc
// prefix Text
// to
// lay
// out
"prefix".text() spaced vSepDoc.align()
// prefix Text
//        to
//        lay
//        out

Because later grouping a vSep is so common, sep is a built-in which does that.


fun <A> List<Doc<A>>.fillSep(): Doc<A>

Concatenate all documents with softLine. The resulting document will be as wide as possible, but introduce newlines if it no longer fits.

val fillSepDoc = listOf(
    "Very".text(), "long".text(), "text".text(),
    "to".text(), "lay".text(), "out.".text(),
    "Hello".text(), "world".text(), "example!".text()
).fillSep()
fillSepDoc.pretty(maxWidth = 80)
// Very long text to lay out. Hello
// world example!
fillSepDoc.pretty(maxWidth = 40)
// Very long text
// to lay out.
// Hello world
// example!

fun <A> List<Doc<A>>.sep(): Doc<A>

Concatenate all documents with softLine, but calls group on the resulting document to flatten it. This is different from [vSep] because it tries to lay out horizontally first, instead of just vertically. This is also different from fillSep, because it will also flatten the individual documents instead of just the separator.

val sepDoc = listOf("Text".text() line "that".text(), "spans".text(), "multiple".text(), "lines".text()).sep()
sepDoc.pretty(maxWidth = 80)
// Text that spans multiple lines
sepDoc.pretty(maxWidth = 20)
// Text
// that
// spans
// multiple
// lines

Cat

Functions from this list will use lineBreak to insert linebreaks. This means it when used with group they will be replaced by nil.

fun <A> List<Doc<A>>.hCat(): Doc<A>

Concatenate all documents using plus. This never introduces newlines on its own.

val hCatDoc = listOf("Hello".text(), "there!".text(), "- Kenobi".text()).hCat()
hCatDoc.pretty(maxWidth = 80)
// Hellothere!- Kenobi
hCatDoc.pretty(maxWidth = 10)
// Hellothere!- Kenobi

For a layout that automatically adds linebreaks, consider fillCat instead.


fun <A> List<Doc<A>>.vCat(): Doc<A>

Concatenate all documents vertically using lineBreak.

val vCatDoc = listOf("Text".text(), "to".text(), "lay".text(), "out".text()).vCat()
"prefix".text() spaced vCatDoc
// prefix Text
// to
// lay
// out
"prefix".text() spaced vCatDoc.align()
// prefix Text
//        to
//        lay
//        out

Because later grouping a vCat is so common, cat is a built-in which does that.


fun <A> List<Doc<A>>.fillCat(): Doc<A>

Concatenate all documents with softLineBreak. The resulting document will be as wide as possible, but introduce newlines if it no longer fits.

val fillCatDoc = listOf(
    "Very".text(), "long".text(), "text".text(),
    "to".text(), "lay".text(), "out.".text(),
    "Hello".text(), "world".text(), "example!".text()
).fillCat()
fillCatDoc.pretty(maxWidth = 80)
// Verylongtexttolayout.Helloworld
// example!
fillCatDoc.pretty(maxWidth = 40)
// Verylongtextto
// layout.Hello
// worldexample!

fun <A> List<Doc<A>>.cat(): Doc<A>

Concatenate all documents with softLineBreak, but calls group on the resulting document to flatten it. This is different from vCat because it tries to lay out horizontally first, instead of just vertically. This is also different from fillCat, because it will also flatten the individual documents instead of just the separator.

val catDoc = listOf("Text".text() line "that".text(), "spans".text(), "multiple".text(), "lines".text()).cat()
catDoc.pretty(maxWidth = 80)
// Text thatspansmultiplelines
catDoc.pretty(maxWidth = 20)
// Text
// that
// spans
// multiple
// lines

Others

fun <A> List<Doc<A>>.punctuate(p: Doc<A>): List<Doc<A>>

Append p to all but the last document.

val punctuateDoc = listOf("Hello".text(), "world".text(), "example".text())
    .punctuate(comma())
punctuateDoc.hSep().pretty(maxWidth = 80)
// Hello, world, example
punctuateDoc.vSep().pretty(maxWidth = 20)
// Hello,
// world,
// example

If you want to but elements infront of the documents you should use encloseSep.


Reactive/conditional layouts

Layout documents with access to context such as the current position, the available page width or the current nesting.

fun <A> column(f: (Int) -> Doc<A>): Doc<A>

Layout a document with information on what the current column is. Used to implement align.

column { c -> "Columns are".text() spaced c.doc() + "-based".text() }
// Columns are 0-based
val colDoc = "prefix".text() spaced column { pipe() spaced " <- column".text() spaced it.doc()  }
listOf(0, 4, 8).map { colDoc.indent(it) }.vSep()
// prefix |  <- column 7
//     prefix |  <- column 11
//         prefix |  <- column 15

fun <A> nesting(f: (Int) -> Doc<A>): Doc<A>

Layout a document with information on what the current nesting is. Used to implement align.

val nestingDoc = "prefix".text() spaced nesting { ("Nested:".text() spaced it.doc()).brackets() }
listOf(0, 4, 8).map { nestingDoc.indent(it) }.vSep()
// prefix [Nested: 0]
//     prefix [Nested: 4]
//         prefix [Nested: 8]

fun <A> Doc<A>.width(f: (Int) -> Doc<A>): Doc<A>

Layout a document with information on what the current column width is.

fun <A> annotate(d: Doc<A>): Doc<A> = d.brackets().width { w -> " <- width:".text() spaced w.doc() }
listOf(
    "---".text(), "------".text(), "---".text().indent(3),
    listOf("---".text(), "---".text().indent(4)).vSep()
).map(::annotate).vSep().align()
// [---] <- width: 5
// [------] <- width: 8
// [   ---] <- width: 8
// [---
//     ---] <- width: 8

fun <A> pageWidth(f: (PageWidth) -> Doc<A>): Doc<A>

Layout a document with information on what the pagewidth settings are.

fun PageWidth.doc(): Doc<Nothing> = when (this) {
    PageWidth.Unbounded -> "Unbounded".text()
    is PageWidth.Available ->
        "Max width:".text() spaced maxWidth.doc() spaced ", ribbonFrac:".text() spaced ribbonFract.doc()
}
val _pwDoc = "prefix".text() spaced pageWidth { pw -> pw.doc().brackets() }
val pwDoc = listOf(0, 4, 8).map { _pwDoc.indent(it) }.vSep()
pwDoc.pretty(maxWidth = 32)
// prefix [Max width: 32 , ribbonFrac: 0.4]
//     prefix [Max width: 32 , ribbonFrac: 0.4]
//         prefix [Max width: 32 , ribbonFrac: 0.4]
pwDoc.layoutPretty(PageWidth.Unbounded).renderString()
// prefix [Unbounded]
//     prefix [Unbounded]
//         prefix [Unbounded]

Fillers

Fill up available space.

fun <A> Doc<A>.fill(i: Int): Doc<A>

Lay out a document and append spaces until it has a width of i.

val types = listOf("empty" to "Doc", "nest" to "Int -> Doc -> Doc", "fillSep" to "[Doc] -> Doc")
"let".text() spaced types.map { (n, tp) -> n.text().fill(5) spaced "::".text() spaced tp.text() }
    .vCat().align()
// let empty :: Doc
//     nest  :: Int -> Doc -> Doc
//     fillSep :: [Doc] -> Doc

fun <A> Doc<A>.fillBreak(i: Int): Doc<A>

Lay out a document and append spaces until it has a width of i and insert a lineBreak if it exceeds the desired width.

val types = listOf("empty" to "Doc", "nest" to "Int -> Doc -> Doc", "fillSep" to "[Doc] -> Doc")
"let".text() spaced types.map { (n, tp) -> n.text().fillBreak(5) spaced "::".text() spaced tp.text() }
    .vCat().align()
// let empty :: Doc
//     nest  :: Int -> Doc -> Doc
//     fillSep
//           :: [Doc] -> Doc

Convenience

fun <A> Doc<A>.enclose(l: Doc<A>, r: Doc<A>): Doc<A>

Surround the document with l and r.

"Hello".text().enclose("_".text(), "_".text())
// _Hello_

Bracketing

Common functions to enclose documents.

ASCII

fun <A> Doc<A>.sQuotes(): Doc<A>
"·".text().sQuotes()
// '·'

fun <A> Doc<A>.dQuotes(): Doc<A>
"·".text().dQuotes()
// "·"

fun <A> Doc<A>.parens(): Doc<A>
"·".text().parens()
// (·)

fun <A> Doc<A>.angles(): Doc<A>
"·".text().angles()
// <·>

fun <A> Doc<A>.braces(): Doc<A>
"·".text().braces()
// {·}

fun <A> Doc<A>.brackets(): Doc<A>
"·".text().brackets()
// [·]

Unicode

fun <A> Doc<A>.d9966quotes(): Doc<A>
"·".text().d9966quotes()
// „·“

fun <A> Doc<A>.d6699quotes(): Doc<A>
"·".text().d6699quotes()
// “·”

fun <A> Doc<A>.s96quotes(): Doc<A>
"·".text().s96quotes()
// ‚·‘

fun <A> Doc<A>.s69quotes(): Doc<A>
"·".text().s69quotes()
// ‘·’

fun <A> Doc<A>.dGuillemetsOut(): Doc<A>
"·".text().dGuillemetsOut()
// «·»

fun <A> Doc<A>.dGuillemetsIn(): Doc<A>
"·".text().dGuillemetsIn()
// »·«

fun <A> Doc<A>.sGuillemetsOut(): Doc<A>
"·".text().sGuillemetsOut()
// ‹·›

fun <A> Doc<A>.sGuillemetsIn(): Doc<A>
"·".text().sGuillemetsIn()
// ›·‹

Named characters

Some constants for ascii and unicode characters

ASCII

Constants for ASCII characters can be found here.

Unicode

Constants for uncode characters can be found here.

Annotations

Combinators that deal with annotations. Learn more about annotations here.

fun <A> Doc<A>.annotate(a: A): Doc<A>

Add an annotation to a document. This can be used to supply additional context to the renderer, e.g. color. This is only relevant for documents that are rendered with a custom renderer. renderString and derivates will ignore annotations.


fun <A> Doc<A>.unAnnotate(): Doc<Nothing>

Remove annotations from a document.


fun <A, B> Doc<A>.reAnnotate(f: (A) -> B): Doc<B>

Alter annotations of a document.


fun <A, B> Doc<A>.alterAnnotations(f: (A) -> List<B>): Doc<B>

Alter annotations of a document with the ability to either remove the annotation or add one or more annotations to the document.

Utiltity

fun String.words(): List<Doc<Nothing>>

Split a string into single words and convert them to Doc using doc.

"Hello world string".words().vSep()
// Hello
// world
// string

fun String.reflow(): Doc<Nothing>

Replace spaces in a string with softLine. This attempts to lay out the string as wide as possible, but introduces newlines when it exceeds max-width.

"Hello world string that is slightly longer than normal".reflow()
// Hello world string that is
// slightly longer than normal

Optimization

fun <A> Doc<A>.fuse(shallow: Boolean): Doc<A>

Fuse text nodes in a document if possible. If you document consists of many "H".text() + "E".text() ... operations, this will attempt to fuse the text together to reduce the overhead for the layout algorithm. If you use deep fusion shallow => false, this will also try to fuse documents returned by [column], [nesting] and [pageWidth]. A fused document is always equal to an unfused on when rendered. Benchmark your code to see if this benefits you or not!


Operations on SimpleDoc

Operations that use SimpleDoc instead of Doc. To learn more about SimpleDoc go here.

Layout

fun Doc<A>.layoutPretty(pw: PageWidth): SimpleDoc<A>

The default layout algorithm which calculates a layout for a document.


fun Doc<A>.layoutSmart(pw: PageWidth): SimpleDoc<A>

A layout algorithm which uses more lookahead than layoutPretty. It is slightly slower, but can produce nicer results.

fun <A> Doc<A>.f(): Doc<A> = (("fun(".text() softLineBreak this) + rParen()).hang(2)
val lSmartDoc = listOf("exempli".text(), "gratia".text()).list().align().f().f().f().f().f()
val hr = pipe() + "------------------------".text() + pipe()
listOf(hr, lSmartDoc, hr).vSep().layoutPretty(PageWidth.Available(26, 1F)).renderString()
// |------------------------|
// fun(fun(fun(fun(fun(
//                   [ exempli
//                   , gratia] )))))
// |------------------------|
listOf(hr, lSmartDoc, hr).vSep().layoutSmart(PageWidth.Available(26, 1F)).renderString()
// |------------------------|
// fun(
//   fun(
//     fun(
//       fun(
//         fun(
//           [ exempli
//           , gratia] )))))
// |------------------------|

fun Doc<A>.layoutCompact(): SimpleDoc<A>

A very simple layout algorithm that lays out a document with no extra newlines or indentation. This is useful to render into machine-readable formats.


Render

fun SimpleDoc<A>.renderString(): String

Render a simple doc as a string. Ignores annotations.


fun <A, B> SimpleDoc<A>.renderDecorated(empty: B, combine: (B, B) -> B, fromString: (String) -> B, annotate: (A, B) -> B): B

Helper to implement basic renders that handle annotations.

For example renderString is defined as:

fun <A> SimpleDoc<A>.renderString(): String =
    renderDecorated(
        empty = "",
        combine = { a, b -> a + b },
        fromString = { it },
        annotate = { ann, str -> str }
    )

Last modified January 14, 2020