A Wadler/Leijen-style pretty printer. Build documents with combinators, then render them to fit a page width.
Renders a document to a string, fitting it within the given column width.
The algorithm makes greedy left-to-right decisions: when a group fits on the
remaining line, it stays flat; otherwise it breaks.
doc = group (string "aaa" |> a line |> a (string "bbb"))
pretty 10 doc == "aaa bbb" -- fits in 10 columns
pretty 5 doc == "aaa\nbbb" -- too wide, breaksRenders a document with both page width and ribbon width constraints.
The ribbon width limits non-indentation characters per line. At indentation
level i, the effective width is min(pageWidth, i + ribbonWidth).
prettyWithRibbon { pageWidth = 80, ribbonWidth = 40 } docThis keeps deeply nested code compact. At indent 50 with ribbon 40, lines break at column 80 (not 90), preventing rightward drift.
If ribbonWidth >= pageWidth, behaves identically to pretty pageWidth.
An empty document that produces no output.
Unlike string "", empties are filtered out by join, lines, words, etc.
This prevents double separators when some items are conditionally absent:
words [ string "a", empty, string "b" ] -- "a b", not "a b"
-- Conditional content:
words [ string "hello", if showName then string name else empty ]A single space character. Equivalent to char ' '.
string "hello" |> a space |> a (string "world") -- "hello world"Creates a document from a string. This is the primary way to add text content.
pretty 80 (string "hello") == "hello"
string "let" |> a space |> a (string "x = 1") -- "let x = 1"Creates a tagged string for syntax highlighting. Use with Pretty.Renderer
to produce HTML or other formatted output.
type Tag = Keyword | Literal
taggedString "let" Keyword |> a space |> a (taggedString "42" Literal)Creates a document from a single character.
char '(' |> a (string "x") |> a (char ')') -- "(x)"Concatenates two documents. The first argument comes before the second.
append (string "hello") (string "world") -- "helloworld"Usually it's more convenient to use a for pipelines, or join/words/lines
for lists.
Flip of append for use in pipelines. Read as "and":
string "if"
|> a space
|> a (string "x > 0")
|> a space
|> a (string "then")
-- "if x > 0 then"Joins documents with a separator between each pair. Empty documents are filtered out to prevent double separators.
join (string ", ") [ string "a", string "b", string "c" ] -- "a, b, c"
join (string ", ") [ string "a", empty, string "c" ] -- "a, c" (not "a, , c")See also words (join with space), lines (join with line), separators.
Joins documents with line between each. Equivalent to join line.
lines [ string "one", string "two", string "three" ]
-- "one\ntwo\nthree"Wrap in group for flexible breaking:
group (lines [ string "a", string "b" ])
-- Wide: "a b" | Narrow: "a\nb"Joins documents with a separator that appears both when flat and when broken. Useful for leading-comma or leading-pipe style:
separators ", " [ string "a", string "b", string "c" ] |> group
-- Wide: "a, b, c"
-- Narrow: "a\n, b\n, c"
separators "| " [ string "A", string "B" ] |> group
-- Wide: "A| B"
-- Narrow: "A\n| B"Joins documents with softline (each break decides independently).
softlines [ string "one", string "two", string "three" ]
-- Each softline independently becomes space or newline based on fitUnlike group (lines [...]) where all breaks happen together, here each
break point makes its own decision.
Joins documents with spaces. Equivalent to join space.
words [ string "hello", string "world" ] -- "hello world"Empty docs are filtered out, preventing double spaces.
Left fold over a list, starting from empty.
fold (\item acc -> acc |> a (string item)) [ "a", "b", "c" ]
-- "abc"Try to lay out a document on one line; if it doesn't fit, break it.
This is the key combinator for flexible layouts. It creates a choice between the flattened version (line breaks become spaces) and the original.
items = string "one" |> a line |> a (string "two") |> a line |> a (string "three")
pretty 20 (group items) == "one two three" -- fits
pretty 10 (group items) == "one\ntwo\nthree" -- doesn't fit, breaksNested groups break outside-in: the outer group breaks first, inner groups may still fit on their lines.
A line break that becomes a space when flattened by group.
-- Without group: always breaks
string "a" |> a line |> a (string "b") -- "a\nb"
-- With group: breaks only if needed
group (string "a" |> a line |> a (string "b"))
-- Wide: "a b" (line becomes space)
-- Narrow: "a\nb" (line breaks)Use tightline if you want no space when flattened.
A line break that becomes nothing when flattened by group.
Use this before closing delimiters to avoid unwanted spaces:
-- Array formatting
group (
char '[' |> a tightline
|> a (string "items")
|> a tightline |> a (char ']')
)
-- Wide: "[items]" (no spaces)
-- Narrow: "[\nitems\n]"Compare with line which would produce "[ items ]" when flat.
A line break that tries to be a space. Equivalent to group line.
Unlike line inside a group, each softline makes its own independent
break decision:
string "a" |> a softline |> a (string "b") |> a softline |> a (string "c")
-- Might render as: "a b\nc" if "a b" fits but "a b c" doesn'tChoose between two documents based on flat vs broken mode.
group (flatAlt (string "flat") (string "broken"))
-- Wide: "flat" | Narrow: "broken"Without a surrounding group, always uses the broken (second) argument.
Unlike line/tightline which vary a single token, flatAlt allows entirely
different document structures in each mode:
-- Different paren spacing: "(x)" when flat, "( x\n)" when broken
group (flatAlt
(char '(' |> a x |> a (char ')'))
(string "( " |> a x |> a line |> a (char ')'))
)Includes a document only in flat mode; becomes empty when broken.
group (string "items" |> a (ifFlat (string " (inline)")))
-- Wide: "items (inline)"
-- Narrow: "items"Equivalent to flatAlt doc empty.
Includes a document only when broken; becomes empty in flat mode.
-- Trailing comma only in multi-line mode
group (lines [ string "a" |> a (ifBreak (char ',')), string "b" ])
-- Wide: "a b"
-- Narrow: "a,\nb"Equivalent to flatAlt empty doc.
Aligns subsequent lines to the current column position. Unlike nest which
adds a fixed indent, align indents relative to where you are now.
string "let " |> a (align (lines [ string "x = 1", string "y = 2" ]))
-- "let x = 1\n y = 2"
-- ^--- aligned to column 4 (after "let ")Useful for hanging indentation that adapts to context.
Adds indentation to all line breaks inside a document. The first line is NOT indented—only lines after breaks.
string "{" |> a (nest 4 (line |> a (string "body"))) |> a line |> a (string "}")
-- "{\n body\n}"Common mistake—nest around content without a line break inside:
nest 4 (string "no effect") -- just "no effect", no indentationThe line break must be INSIDE the nest for indentation to apply.
Combines align with additional nesting. Equivalent to align (nest n doc).
string "fn(" |> a (hang 2 (lines [ string "arg1", string "arg2" ])) |> a (string ")")
-- "fn(arg1\n arg2)"
-- ^--- aligned to column 3, plus 2 more = column 5Indents the first line AND all subsequent lines by the given amount.
indent 4 (lines [ string "one", string "two" ])
-- " one\n two"Compare with nest which only affects lines after breaks, not the first line.
Wraps a document with left and right delimiters.
surround (string "<<") (string ">>") (string "content") -- "<<content>>"See also parens, braces, brackets for common cases.
Wraps a document in parentheses.
parens (string "x + y") -- "(x + y)"Wraps a document in curly braces.
braces (string "x: 1") -- "{x: 1}"Wraps a document in square brackets.
brackets (string "1, 2, 3") -- "[1, 2, 3]"Tags all strings in a document for syntax highlighting.
type Tag = Error
setTag Error (words [ string "undefined", string "variable" ])
-- Both strings will be tagged as ErrorConditionally update tags based on string content and current tag.
-- Tag numbers as literals
updateTag (\s _ ->
if String.all Char.isDigit s then Just Literal else Nothing
) doc
-- Remove all tags
updateTag (\_ _ -> Nothing) doc
A document represents possible layouts. The
tparameter allows tagging strings for syntax highlighting (seePretty.Renderer).