Doc
The abstract data type Doc<A> represents prettified documents that have been annotated with data of type A.
More specifically, a value of type Doc represents a non-empty set of possible layouts for a given document. The layout algorithms select one of these possibilities, taking into account variables such as the width of the document.
The annotation is an arbitrary piece of data associated with (part of) a document. Annotations may be used by rendering algorithms to display documents differently by providing information such as: - color information (e.g., when rendering to the terminal) - mouseover text (e.g., when rendering to rich HTML) - whether to show something or not (to allow simple or detailed versions)
Alignment
Signature
declare const align: <A>(self: Doc<A>) => Doc<A>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import * as String from "effect/String"
// As an example, the documents below will be placed one above the other
// regardless of the current nesting level
// Without `align`ment, the second line is simply placed below everything
// that has been laid out so far
const unaligned = Doc.hsep([Doc.text("lorem"), Doc.vsep([Doc.text("ipsum"), Doc.text("dolor")])])
assert.strictEqual(
Doc.render(unaligned, { style: "pretty" }),
String.stripMargin(
`|lorem ipsum
|dolor`,
),
)
// With `align`ment, the `vsep`ed documents all start at the same column
const aligned = Doc.hsep([
Doc.text("lorem"),
Doc.align(Doc.vsep([Doc.text("ipsum"), Doc.text("dolor")])),
])
assert.strictEqual(
Doc.render(aligned, { style: "pretty" }),
String.stripMargin(
`|lorem ipsum
| dolor`,
),
)encloseSep
The encloseSep combinator concatenates a collection of documents, separating each document in the collection using the specified sep document. After concatenation, the resulting document is enclosed by the specified left and right documents.
To place the sep document at the end of each entry, see the punctuate combinator.
Signature
declare const encloseSep: {
<A, B, C>(
left: Doc<A>,
right: Doc<B>,
sep: Doc<C>,
): <D>(docs: Iterable<Doc<D>>) => Doc<A | B | C | D>;
<A, B, C, D>(
docs: Iterable<Doc<D>>,
left: Doc<A>,
right: Doc<B>,
sep: Doc<C>,
): Doc<A | B | C | D>;
};Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import { pipe } from "effect/Function"
import * as String from "effect/String"
const doc = Doc.hsep([
Doc.text("list"),
Doc.align(
pipe(
["1", "20", "300", "4000"].map((n) => (n.length === 1 ? Doc.char(n) : Doc.text(n))),
Doc.encloseSep(Doc.lbracket, Doc.rbracket, Doc.comma),
),
),
])
// The documents are laid out horizontally if the document fits the page
assert.strictEqual(Doc.render(doc, { style: "pretty" }), "list [1,20,300,4000]")
// Otherwise they are laid out vertically, with separators put in the front
assert.strictEqual(
Doc.render(doc, {
style: "pretty",
options: { lineWidth: 10 },
}),
String.stripMargin(
`|list [1
| ,20
| ,300
| ,4000]`,
),
)The hang combinator lays out a document with the nesting level set to the *current column* plus the specified indent. Negative values for indent are allowed and decrease the nesting level accordingly.
This differs from the nest combinator, which is based on the *current nesting level* plus the specified indent. When you"re not sure, try the more efficient combinator (nest) first.
Signature
declare const hang: {
(indent: number): <A>(self: Doc<A>) => Doc<A>;
<A>(self: Doc<A>, indent: number): Doc<A>;
};Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import { pipe } from "effect/Function"
import * as String from "effect/String"
const doc = Doc.hsep([
Doc.text("prefix"),
pipe(Doc.reflow("Indenting these words with hang"), Doc.hang(4)),
])
assert.strictEqual(
Doc.render(doc, {
style: "pretty",
options: { lineWidth: 24 },
}),
String.stripMargin(
`|prefix Indenting these
| words with
| hang`,
),
)The indent combinator indents a document by the specified indent beginning from the current cursor position.
Signature
declare const indent: {
(indent: number): <A>(self: Doc<A>) => Doc<A>;
<A>(self: Doc<A>, indent: number): Doc<A>;
};Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import { pipe } from "effect/Function"
import * as String from "effect/String"
const doc = Doc.hcat([
Doc.text("prefix"),
pipe(Doc.reflow("The indent function indents these words!"), Doc.indent(4)),
])
assert.strictEqual(
Doc.render(doc, {
style: "pretty",
options: { lineWidth: 24 },
}),
String.stripMargin(
`|prefix The indent
| function
| indents these
| words!`,
),
)A Haskell-inspired variant of encloseSep that uses a comma as the separator and braces as the enclosure for a collection of documents.
Signature
declare const list: <A>(docs: Iterable<Doc<A>>) => Doc<A>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
const doc = Doc.list(
["1", "20", "300", "4000"].map((n) => (n.length === 1 ? Doc.char(n) : Doc.text(n))),
)
assert.strictEqual(Doc.render(doc, { style: "pretty" }), "[1, 20, 300, 4000]")Lays out a document with the current nesting level (indentation of the following lines) increased by the specified indent. Negative values are allowed and will decrease the nesting level accordingly.
See also: * hang: nest a document relative to the current cursor position instead of the current nesting level * align: set the nesting level to the current cursor position * indent: increase the indentation on the spot, padding any empty space with spaces
Signature
declare const nest: {
(indent: number): <A>(self: Doc<A>) => Doc<A>;
<A>(self: Doc<A>, indent: number): Doc<A>;
};Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import { pipe } from "effect/Function"
import * as String from "effect/String"
const doc = Doc.vsep([
pipe(Doc.vsep(Doc.words("lorem ipsum dolor")), Doc.nest(4)),
Doc.text("sit"),
Doc.text("amet"),
])
assert.strictEqual(
Doc.render(doc, { style: "pretty" }),
String.stripMargin(
`|lorem
| ipsum
| dolor
|sit
|amet`,
),
)A Haskell-inspired variant of encloseSep that uses a comma as the separator and parentheses as the enclosure for a collection of documents.
Signature
declare const tupled: <A>(docs: Iterable<Doc<A>>) => Doc<A>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
const doc = Doc.tupled(
["1", "20", "300", "4000"].map((n) => (n.length === 1 ? Doc.char(n) : Doc.text(n))),
)
assert.strictEqual(Doc.render(doc, { style: "pretty" }), "(1, 20, 300, 4000)")Alternative Layouts
The flatAlt document will render left by default. However, when grouped, y will be preferred with left as the fallback for cases where y does not fit onto the page.
NOTE: Users should be careful to ensure that left is less wide than right. Otherwise, if right ends up not fitting the page, then the layout algorithms will fall back to an even wider layout.
Signature
declare const flatAlt: {
<B>(that: Doc<B>): <A>(self: Doc<A>) => Doc<B | A>;
<A, B>(self: Doc<A>, that: Doc<B>): Doc<A | B>;
};Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import { pipe } from "effect/Function"
import * as String from "effect/String"
const open = pipe(Doc.empty, Doc.flatAlt(Doc.text("{ ")))
const close = pipe(Doc.empty, Doc.flatAlt(Doc.text(" }")))
const separator = pipe(Doc.empty, Doc.flatAlt(Doc.text("; ")))
const prettyDo = <A>(documents: Array<Doc.Doc<A>>): Doc.Doc<A> => {
return pipe(
Doc.hsep([Doc.text("do"), pipe(documents, Doc.encloseSep(open, close, separator), Doc.align)]),
Doc.group,
)
}
const statements = [
Doc.text("name:_ <- getArgs"),
Doc.text('let greet = "Hello, " <> name'),
Doc.text("putStrLn greet"),
]
// If it fits, then the content is put onto a single line with the `{;}` style
assert.strictEqual(
pipe(
prettyDo(statements),
Doc.render({
style: "pretty",
options: { lineWidth: 80 },
}),
),
'do { name:_ <- getArgs; let greet = "Hello, " <> name; putStrLn greet }',
)
// When there is not enough space, the content is broken up onto multiple lines
assert.strictEqual(
pipe(
prettyDo(statements),
Doc.render({
style: "pretty",
options: { lineWidth: 10 },
}),
),
String.stripMargin(
`|do name:_ <- getArgs
| let greet = "Hello, " <> name
| putStrLn greet`,
),
)The group combinator attempts to lay out a document onto a single line by removing the contained line breaks. If the result does not fit the page, or if a hardLine prevents flattening the document, x is laid out without any changes.
The group function is key to layouts that adapt to available space nicely.
Signature
declare const group: <A>(self: Doc<A>) => Doc<A>;Signature
declare const union: {
<B>(that: Doc<B>): <A>(self: Doc<A>) => Doc<B | A>;
<A, B>(self: Doc<A>, that: Doc<B>): Doc<A | B>;
};Annotations
alterAnnotations
Change the annotations of a document. Individual annotations can be removed, changed, or replaced by multiple ones.
This is a general function that combines unAnnotate and reAnnotate, and is useful for mapping semantic annotations (such as ยปthis is a keywordยซ) to display annotations (such as ยปthis is red and underlinedยซ) because some backends may not care about certain annotations while others may.
Annotations earlier in the new list will be applied earlier, so returning [Bold, Green] will result in a bold document that contains green text, and not vice versa.
Since this traverses the entire document tree, including the parts that are not rendered (due to other layouts having better fit), it is preferable to reannotate a document after producing the layout by using alterAnnotations from the SimpleDocStream module.
Signature
declare const alterAnnotations: {
<A, B>(f: (a: A) => Iterable<B>): (self: Doc<A>) => Doc<B>;
<A, B>(self: Doc<A>, f: (a: A) => Iterable<B>): Doc<B>;
};Adds an annotation to a Doc. The annotation can then be used by the rendering algorithm to, for example, add color to certain parts of the output.
Note This function is relevant only for custom formats with their own annotations, and is not relevant for basic pretty printing.
Signature
declare const annotate: {
<A>(annotation: A): (self: Doc<A>) => Doc<A>;
<A>(self: Doc<A>, annotation: A): Doc<A>;
};reAnnotate
Changes the annotation of a document. Useful for modifying documents embedded with one form of annotation with a more general annotation.
Note that with each invocation, the entire document tree is traversed. If possible, it is preferable to reannotate a document after producing the layout using reAnnotateS.
Signature
declare const reAnnotate: {
<A, B>(f: (a: A) => B): (self: Doc<A>) => Doc<B>;
<A, B>(self: Doc<A>, f: (a: A) => B): Doc<B>;
};unAnnotate
Removes all annotations from a document.
Note: with each invocation, the entire document tree is traversed. If possible, it is preferable to unannotate a document after producing the layout using unAnnotateS.
Signature
declare const unAnnotate: <A>(self: Doc<A>) => Doc<never>;Combinators
Concatenation
The cat combinator lays out two documents separated by nothing.
Signature
declare const cat: {
<B>(that: Doc<B>): <A>(self: Doc<A>) => Doc<B | A>;
<A, B>(self: Doc<A>, that: Doc<B>): Doc<A | B>;
};The cats combinator will attempt to lay out a collection of documents separated by nothing. If the output does not fit the page, then the documents will be separated by newlines. This is what differentiates it from vcat, which always lays out documents beneath one another.
Signature
declare const cats: <A>(docs: Iterable<Doc<A>>) => Doc<A>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import * as String from "effect/String"
const doc: Doc.Doc<never> = Doc.hsep([Doc.text("Docs:"), Doc.cats(Doc.words("lorem ipsum dolor"))])
assert.strictEqual(Doc.render(doc, { style: "pretty" }), "Docs: loremipsumdolor")
// If the document exceeds the width of the page, the documents are rendered
// one above another
assert.strictEqual(
Doc.render(doc, {
style: "pretty",
options: { lineWidth: 10 },
}),
String.stripMargin(
`|Docs: lorem
|ipsum
|dolor`,
),
)catWithLine
The catWithLine combinator concatenates two documents by placing a line document between them.
Signature
declare const catWithLine: {
<B>(that: Doc<B>): <A>(self: Doc<A>) => Doc<B | A>;
<A, B>(self: Doc<A>, that: Doc<B>): Doc<A | B>;
};Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import { pipe } from "effect/Function"
import * as String from "effect/String"
const doc: Doc.Doc<never> = pipe(Doc.char("a"), Doc.catWithLine(Doc.char("b")))
assert.strictEqual(
Doc.render(doc, { style: "pretty" }),
String.stripMargin(
`|a
|b`,
),
)catWithLineBreak
The catWithLineBreak combinator concatenates two documents by placing a lineBreak document between them.
Signature
declare const catWithLineBreak: {
<B>(that: Doc<B>): <A>(self: Doc<A>) => Doc<B | A>;
<A, B>(self: Doc<A>, that: Doc<B>): Doc<A | B>;
};Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import { pipe } from "effect/Function"
import * as String from "effect/String"
const doc: Doc.Doc<never> = pipe(Doc.char("a"), Doc.catWithLineBreak(Doc.char("b")))
assert.strictEqual(
Doc.render(doc, { style: "pretty" }),
String.stripMargin(
`|a
|b`,
),
)
assert.strictEqual(Doc.render(Doc.group(doc), { style: "pretty" }), "ab")catWithSoftLine
The catWithSoftLine combinator concatenates two documents by placing a softLine document between them.
Signature
declare const catWithSoftLine: {
<B>(that: Doc<B>): <A>(self: Doc<A>) => Doc<B | A>;
<A, B>(self: Doc<A>, that: Doc<B>): Doc<A | B>;
};Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import { pipe } from "effect/Function"
import * as String from "effect/String"
const doc: Doc.Doc<never> = pipe(Doc.char("a"), Doc.catWithSoftLine(Doc.char("b")))
assert.strictEqual(Doc.render(doc, { style: "pretty" }), "a b")
assert.strictEqual(
Doc.render(doc, {
style: "pretty",
options: { lineWidth: 1 },
}),
String.stripMargin(
`|a
|b`,
),
)catWithSoftLineBreak
The catWithSoftLineBreak combinator concatenates two documents by placing a softLineBreak document between them.
Signature
declare const catWithSoftLineBreak: {
<B>(that: Doc<B>): <A>(self: Doc<A>) => Doc<B | A>;
<A, B>(self: Doc<A>, that: Doc<B>): Doc<A | B>;
};Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import { pipe } from "effect/Function"
import * as String from "effect/String"
const doc: Doc.Doc<never> = pipe(Doc.char("a"), Doc.catWithSoftLineBreak(Doc.char("b")))
assert.strictEqual(Doc.render(doc, { style: "pretty" }), "ab")
assert.strictEqual(
Doc.render(doc, {
style: "pretty",
options: { lineWidth: 1 },
}),
String.stripMargin(
`|a
|b`,
),
)catWithSpace
The catWithSpace combinator concatenates two documents by placing a space document between them.
Signature
declare const catWithSpace: {
<B>(that: Doc<B>): <A>(self: Doc<A>) => Doc<B | A>;
<A, B>(self: Doc<A>, that: Doc<B>): Doc<A | B>;
};Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import { pipe } from "effect/Function"
const doc: Doc.Doc<never> = pipe(Doc.char("a"), Doc.catWithSpace(Doc.char("b")))
assert.strictEqual(Doc.render(doc, { style: "pretty" }), "a b")concatWith
The concatWith combinator concatenates all documents in a collection element-wise with the specified binary function.
Signature
declare const concatWith: {
<A>(f: (left: Doc<A>, right: Doc<A>) => Doc<A>): (docs: Iterable<Doc<A>>) => Doc<A>;
<A>(docs: Iterable<Doc<A>>, f: (left: Doc<A>, right: Doc<A>) => Doc<A>): Doc<A>;
};Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import { pipe } from "effect/Function"
const doc: Doc.Doc<never> = pipe(
[Doc.char("a"), Doc.char("b")],
Doc.concatWith((x, y) => Doc.catWithSpace(y)(x)),
)
assert.strictEqual(Doc.render(doc, { style: "pretty" }), "a b")The fillCat combinator concatenates all documents in a collection horizontally by placing a empty between each pair of documents as long as they fit the page. Once the page width is exceeded, a lineBreak is inserted and the process is repeated for all documents in the collection.
Note: the use of lineBreak means that if grouped, the documents will be separated with empty instead of newlines. See fillSep if you want a space instead.
Signature
declare const fillCat: <A>(docs: Iterable<Doc<A>>) => Doc<A>;The hcat combinator concatenates all documents in a collection horizontally without any spacing.
Signature
declare const hcat: <A>(docs: Iterable<Doc<A>>) => Doc<A>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import * as String from "effect/String"
const doc: Doc.Doc<never> = Doc.hcat(Doc.words("lorem ipsum dolor"))
assert.strictEqual(Doc.render(doc, { style: "pretty" }), "loremipsumdolor")The vcat combinator concatenates all documents in a collection vertically. If the output is grouped then the line breaks are removed.
In other words vcat is like vsep, with newlines removed instead of replaced by spaces.
Signature
declare const vcat: <A>(docs: Iterable<Doc<A>>) => Doc<A>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import * as String from "effect/String"
const doc: Doc.Doc<never> = Doc.vcat(Doc.words("lorem ipsum dolor"))
assert.strictEqual(
Doc.render(doc, { style: "pretty" }),
String.stripMargin(
`|lorem
|ipsum
|dolor`,
),
)Constructors
A document containing a single character.
Invariants - Cannot be the newline ("\n") character
Control characters are preserved verbatim. Apply sanitize before rendering a document containing untrusted text to a terminal.
Signature
declare const char: (char: string) => Doc<never>;Lays out a document according to the document"sPageWidth.
Signature
declare const pageWidth: <A>(react: (pageWidth: PageWidth) => Doc<A>) => Doc<A>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import * as String from "effect/String"
const doc = Doc.hsep([
Doc.text("prefix"),
Doc.pageWidth((pageWidth) => {
switch (pageWidth._tag) {
case "AvailablePerLine": {
const { lineWidth, ribbonFraction } = pageWidth
return Doc.squareBracketed(
Doc.text(`Width: ${lineWidth}, Ribbon Fraction: ${ribbonFraction}`),
)
}
case "Unbounded": {
return Doc.empty
}
}
}),
])
const example = Doc.vsep([0, 4, 8].map((n) => Doc.indent(n)(doc)))
assert.strictEqual(
Doc.render(example, {
style: "pretty",
options: { lineWidth: 32 },
}),
String.stripMargin(
`|prefix [Width: 32, Ribbon Fraction: 1]
| prefix [Width: 32, Ribbon Fraction: 1]
| prefix [Width: 32, Ribbon Fraction: 1]`,
),
)Constructs a document containing a string of text.
Note: newline characters (\n) contained in the provided string will be disregarded (i.e. not rendered) in the output document.
Control characters are preserved verbatim. Apply sanitize before rendering a document containing untrusted text to a terminal.
Signature
declare const string: (str: string) => Doc<never>;A document containing a string of text.
Invariants - Text cannot be less than two characters long - Text cannot contain a newline ("\n") character
Control characters are preserved verbatim. Apply sanitize before rendering a document containing untrusted text to a terminal.
Signature
declare const text: (text: string) => Doc<never>;Filling
The fill combinator first lays out the document x and then appends spaces until the width of the document is equal to the specified width. If the width of x is already larger than the specified width, nothing is appended.
Signature
declare const fill: {
(w: number): <A>(self: Doc<A>) => Doc<A>;
<A>(self: Doc<A>, w: number): Doc<A>;
};Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import { pipe } from "effect/Function"
import * as String from "effect/String"
type Signature = [name: string, type: string]
const signatures: Array<Signature> = [
["empty", "Doc"],
["nest", "Int -> Doc -> Doc"],
["fillSep", "[Doc] -> Doc"],
]
const prettySignature = <A>([name, type]: Signature): Doc.Doc<A> =>
Doc.hsep([pipe(Doc.text(name), Doc.fill(5)), Doc.text("::"), Doc.text(type)])
const doc = Doc.hsep([Doc.text("let"), Doc.align(Doc.vcat(signatures.map(prettySignature)))])
assert.strictEqual(
Doc.render(doc, { style: "pretty" }),
String.stripMargin(
`|let empty :: Doc
| nest :: Int -> Doc -> Doc
| fillSep :: [Doc] -> Doc`,
),
)The fillBreak combinator first lays out the document x and then appends spaces until the width of the document is equal to the specified width. If the width of x is already larger than the specified width, the nesting level is increased by the specified width and a line is appended.
Signature
declare const fillBreak: {
(w: number): <A>(self: Doc<A>) => Doc<A>;
<A>(self: Doc<A>, w: number): Doc<A>;
};Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import { pipe } from "effect/Function"
import * as String from "effect/String"
type Signature = [name: string, type: string]
const signatures: Array<Signature> = [
["empty", "Doc"],
["nest", "Int -> Doc -> Doc"],
["fillSep", "[Doc] -> Doc"],
]
const prettySignature = <A>([name, type]: Signature): Doc.Doc<A> =>
Doc.hsep([pipe(Doc.text(name), Doc.fillBreak(5)), Doc.text("::"), Doc.text(type)])
const doc = Doc.hsep([Doc.text("let"), Doc.align(Doc.vcat(signatures.map(prettySignature)))])
assert.strictEqual(
Doc.render(doc, { style: "pretty" }),
String.stripMargin(
`|let empty :: Doc
| nest :: Int -> Doc -> Doc
| fillSep
| :: [Doc] -> Doc`,
),
)Flattening
changesUponFlattening
Select the first element of each Union and discard the first element of each FlatAlt to produce a "flattened" version of the input document.
The result is Flattened if the element might change depending on the chosen layout algorithm (i.e., the resulting document contains sub-documents that may be rendered differently).
The result is AlreadyFlat if the document is static (i.e., the resulting document contains only a plain Empty node).
NeverFlat is returned when the document cannot be flattened because it contains either a hard Line or a Fail.
Signature
declare const changesUponFlattening: <A>(self: Doc<A>) => Flatten<Doc<A>>;Flattens a document but does not report changes.
Signature
declare const flatten: <A>(self: Doc<A>) => Doc<A>;Folding
Signature
declare const match: {
<A, R>(patterns: {
readonly Annotated: (annotation: A, doc: Doc<A>) => R;
readonly Cat: (x: Doc<A>, y: Doc<A>) => R;
readonly Char: (char: string) => R;
readonly Column: (react: (position: number) => Doc<A>) => R;
readonly Empty: () => R;
readonly Fail: () => R;
readonly FlatAlt: (x: Doc<A>, y: Doc<A>) => R;
readonly Line: () => R;
readonly Nest: (indent: number, doc: Doc<A>) => R;
readonly Nesting: (react: (level: number) => Doc<A>) => R;
readonly Text: (text: string) => R;
readonly Union: (x: Doc<A>, y: Doc<A>) => R;
readonly WithPageWidth: (react: (pageWidth: PageWidth) => Doc<A>) => R;
}): (self: Doc<A>) => R;
<A, R>(
self: Doc<A>,
patterns: {
readonly Annotated: (annotation: A, doc: Doc<A>) => R;
readonly Cat: (x: Doc<A>, y: Doc<A>) => R;
readonly Char: (char: string) => R;
readonly Column: (react: (position: number) => Doc<A>) => R;
readonly Empty: () => R;
readonly Fail: () => R;
readonly FlatAlt: (x: Doc<A>, y: Doc<A>) => R;
readonly Line: () => R;
readonly Nest: (indent: number, doc: Doc<A>) => R;
readonly Nesting: (react: (level: number) => Doc<A>) => R;
readonly Text: (text: string) => R;
readonly Union: (x: Doc<A>, y: Doc<A>) => R;
readonly WithPageWidth: (react: (pageWidth: PageWidth) => Doc<A>) => R;
},
): R;
};Instances
Signature
declare const Covariant: covariant.Covariant<Doc.TypeLambda>;Signature
declare const getMonoid: <A>(_: void) => Monoid<Doc<A>>;getSemigroup
Signature
declare const getSemigroup: <A>(_: void) => Semigroup<Doc<A>>;Signature
declare const Invariant: invariant.Invariant<Doc.TypeLambda>;Model
Represents a document with an associated annotation.
Signature
interface Annotated<A> extends Variance<A> {
readonly _tag: "Annotated";
readonly annotation: A;
readonly doc: Doc<A>;
}Represents the concatenation of two documents.
Signature
interface Cat<A> extends Variance<A> {
readonly _tag: "Cat";
readonly left: Doc<A>;
readonly right: Doc<A>;
}Represents a document containing a single character.
Invariants - Cannot be the newline ("\n") character
Signature
interface Char<A> extends Variance<A> {
readonly _tag: "Char";
readonly char: string;
}Represents a document that reacts to the current cursor position.
Signature
interface Column<A> extends Variance<A> {
readonly _tag: "Column";
readonly react: (position: number) => Doc<A>;
}Represents a prettified document that has been annotated with data of type A.
Signature
type Doc<A> =
| Fail<A>
| Empty<A>
| Char<A>
| Text<A>
| Line<A>
| FlatAlt<A>
| Cat<A>
| Nest<A>
| Union<A>
| Column<A>
| WithPageWidth<A>
| Nesting<A>
| Annotated<A>;DocTypeLambda interface
Signature
interface DocTypeLambda extends TypeLambda {
readonly type: Doc<unknown>;
}Represents the empty document.
Conceptually, the Empty document can be thought of as the unit of Cat.
Signature
interface Empty<A> extends Variance<A> {
readonly _tag: "Empty";
}Represents a document that cannot be rendered. Generally occurs when flattening a line. The layout algorithms will reject this document and choose a more suitable rendering.
Signature
interface Fail<A> extends Variance<A> {
readonly _tag: "Fail";
}Represents a flattened alternative of two documents. The layout algorithms will choose the first document, but when flattened (via group) the second document will be preferreinternal.
The layout algorithms operate under the assumption that the first alternative is less wide than the flattened second alternative.
Signature
interface FlatAlt<A> extends Variance<A> {
readonly _tag: "FlatAlt";
readonly left: Doc<A>;
readonly right: Doc<A>;
}Represents a document that contains a hard line break.
Signature
interface Line<A> extends Variance<A> {
readonly _tag: "Line";
}Represents a document that is indented by a certain number of columns.
Signature
interface Nest<A> extends Variance<A> {
readonly _tag: "Nest";
readonly doc: Doc<A>;
readonly indent: number;
}Represents a document that reacts to the current nesting level.
Signature
interface Nesting<A> extends Variance<A> {
readonly _tag: "Nesting";
readonly react: (level: number) => Doc<A>;
}Represents a document containing a string of text.
Invariants - Text cannot be less than two characters long - Text cannot contain a newline ("\n") character
Signature
interface Text<A> extends Variance<A> {
readonly _tag: "Text";
readonly text: string;
}Represents the union of two documents. Used to implement layout alternatives for group.
Invariants - The first lines of the first document should be longer than the first lines of the second document so that the layout algorithm can pick the document with the best fit.
Signature
interface Union<A> extends Variance<A> {
readonly _tag: "Union";
readonly left: Doc<A>;
readonly right: Doc<A>;
}WithPageWidth interface
Represents a document that reacts to the current page width.
Signature
interface WithPageWidth<A> extends Variance<A> {
readonly _tag: "WithPageWidth";
readonly react: (pageWidth: PageWidth) => Doc<A>;
}Other
Primitives
A document containing a single `` character.
Signature
declare const backslash: Doc<never>;A document containing a single : character.
Signature
declare const colon: Doc<never>;A document containing a single , character.
Signature
declare const comma: Doc<never>;A document containing a single . character.
Signature
declare const dot: Doc<never>;A document containing a single " character.
Signature
declare const dquote: Doc<never>;The empty document behaves like a document containing the empty string (""), so it has a height of 1.
This may lead to surprising behavior if the empty document is expected to bear no weight inside certain layout functions, such asvcat, where it will render an empty line of output.
Signature
declare const empty: Doc<never>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import * as String from "effect/String"
const doc = Doc.vsep([
Doc.text("hello"),
// `parentheses` for visibility purposes only
Doc.parenthesized(Doc.empty),
Doc.text("world"),
])
const expected = `|hello
|()
|world`
assert.strictEqual(Doc.render(doc, { style: "pretty" }), String.stripMargin(expected))A document containing a single = character.
Signature
declare const equalSign: Doc<never>;The fail document is a document that cannot be rendered.
Generally occurs when flattening a line. The layout algorithms will reject this document and choose a more suitable rendering.
Signature
declare const fail: Doc<never>;The hardLine document is always laid out as a line break, regardless of space or whether or not the document was group"ed.
Signature
declare const hardLine: Doc<never>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import * as String from "effect/String"
const doc: Doc.Doc<never> = Doc.hcat([
Doc.text("lorem ipsum"),
Doc.hardLine,
Doc.text("dolor sit amet"),
])
// Even with enough space, a line break is introduced
assert.strictEqual(
Doc.render(doc, {
style: "pretty",
options: { lineWidth: 1000 },
}),
String.stripMargin(
`|lorem ipsum
|dolor sit amet`,
),
)A document containing a single < character.
Signature
declare const langle: Doc<never>;A document containing a single { character.
Signature
declare const lbrace: Doc<never>;A document containing a single [ character.
Signature
declare const lbracket: Doc<never>;The line document advances to the next line and indents to the current nesting level. However, line will behave like space if the line break is undone by group.
Signature
declare const line: Doc<never>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import * as String from "effect/String"
const doc: Doc.Doc<never> = Doc.hcat([
Doc.text("lorem ipsum"),
Doc.line,
Doc.text("dolor sit amet"),
])
assert.strictEqual(
Doc.render(doc, { style: "pretty" }),
String.stripMargin(
`|lorem ipsum
|dolor sit amet`,
),
)
assert.strictEqual(Doc.render(Doc.group(doc), { style: "pretty" }), "lorem ipsum dolor sit amet")The lineBreak document is like line but behaves like empty if the line break is undone by group (instead of space).
Signature
declare const lineBreak: Doc<never>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import * as String from "effect/String"
const doc: Doc.Doc<never> = Doc.hcat([
Doc.text("lorem ipsum"),
Doc.lineBreak,
Doc.text("dolor sit amet"),
])
assert.strictEqual(
Doc.render(doc, { style: "pretty" }),
String.stripMargin(
`|lorem ipsum
|dolor sit amet`,
),
)
assert.strictEqual(Doc.render(Doc.group(doc), { style: "pretty" }), "lorem ipsumdolor sit amet")A document containing a single ( character.
Signature
declare const lparen: Doc<never>;A document containing a single > character.
Signature
declare const rangle: Doc<never>;A document containing a single } character.
Signature
declare const rbrace: Doc<never>;A document containing a single ] character.
Signature
declare const rbracket: Doc<never>;A document containing a single ) character.
Signature
declare const rparen: Doc<never>;A document containing a single ; character.
Signature
declare const semi: Doc<never>;A document containing a single / character.
Signature
declare const slash: Doc<never>;The softLine document behaves like space if the resulting output fits onto the page, otherwise it behaves like line.
Signature
declare const softLine: Doc<never>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import * as String from "effect/String"
const doc: Doc.Doc<never> = Doc.hcat([
Doc.text("lorem ipsum"),
Doc.softLine,
Doc.text("dolor sit amet"),
])
// Here we have enough space to put everything onto one line
assert.strictEqual(
Doc.render(doc, {
style: "pretty",
options: { lineWidth: 80 },
}),
"lorem ipsum dolor sit amet",
)
// If the page width is narrowed to `10`, the layout algorithm will
// introduce a line break
assert.strictEqual(
Doc.render(Doc.group(doc), {
style: "pretty",
options: { lineWidth: 10 },
}),
String.stripMargin(
`|lorem ipsum
|dolor sit amet`,
),
)softLineBreak
The softLineBreak document is similar to softLine, but behaves like empty if the resulting output does not fit onto the page (instead of space).
Signature
declare const softLineBreak: Doc<never>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import * as String from "effect/String"
const doc: Doc.Doc<never> = Doc.hcat([
Doc.text("ThisText"),
Doc.softLineBreak,
Doc.text("IsWayTooLong"),
])
// With enough space, we get direct concatenation of documents:
assert.strictEqual(
Doc.render(doc, {
style: "pretty",
options: { lineWidth: 80 },
}),
"ThisTextIsWayTooLong",
)
// If the page width is narrowed to `10`, the layout algorithm will
// introduce a line break
assert.strictEqual(
Doc.render(Doc.group(doc), {
style: "pretty",
options: { lineWidth: 10 },
}),
String.stripMargin(
`|ThisText
|IsWayTooLong`,
),
)A document containing a single character.
Signature
declare const space: Doc<never>;A document containing a single " character.
Signature
declare const squote: Doc<never>;A document containing a single | character.
Signature
declare const vbar: Doc<never>;Reactive Layouts
Lays out a document depending upon the column at which the document starts.
Signature
declare const column: <A>(react: (position: number) => Doc<A>) => Doc<A>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import * as String from "effect/String"
// Example 1:
const example1 = Doc.column((l) => Doc.hsep([Doc.text("Columns are"), Doc.text(`${l}-based`)]))
assert.strictEqual(Doc.render(example1, { style: "pretty" }), "Columns are 0-based")
// Example 2:
const doc = Doc.hsep([Doc.text("prefix"), Doc.column((l) => Doc.text(`| <- column ${l}`))])
const example2 = Doc.vsep([0, 4, 8].map((n) => Doc.indent(n)(doc)))
assert.strictEqual(
Doc.render(example2, { style: "pretty" }),
String.stripMargin(
`|prefix | <- column 7
| prefix | <- column 11
| prefix | <- column 15`,
),
)Lays out a document depending upon the current nesting level (i.e., the current indentation of the document).
Signature
declare const nesting: <A>(react: (level: number) => Doc<A>) => Doc<A>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import * as String from "effect/String"
const doc = Doc.hsep([
Doc.text("prefix"),
Doc.nesting((l) => Doc.squareBracketed(Doc.text(`Nested: ${l}`))),
])
const example = Doc.vsep([0, 4, 8].map((n) => Doc.indent(n)(doc)))
assert.strictEqual(
Doc.render(example, { style: "pretty" }),
String.stripMargin(
`|prefix [Nested: 0]
| prefix [Nested: 4]
| prefix [Nested: 8]`,
),
)The width combinator makes the column width of a document available to the document while rendering.
Signature
declare const width: {
<A, B>(react: (width: number) => Doc<B>): (self: Doc<A>) => Doc<A | B>;
<A, B>(self: Doc<A>, react: (width: number) => Doc<B>): Doc<A | B>;
};Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import { pipe } from "effect/Function"
import * as String from "effect/String"
const annotate = <A>(doc: Doc.Doc<A>): Doc.Doc<A> =>
pipe(
Doc.squareBracketed(doc),
Doc.width((w) => Doc.text(` <- width: ${w}`)),
)
const docs = [
Doc.text("---"),
Doc.text("------"),
Doc.indent(Doc.text("---"), 3),
Doc.vsep([Doc.text("---"), Doc.indent(Doc.text("---"), 4)]),
]
const doc = Doc.align(Doc.vsep(docs.map(annotate)))
assert.strictEqual(
Doc.render(doc, { style: "pretty" }),
String.stripMargin(
`|[---] <- width: 5
|[------] <- width: 8
|[ ---] <- width: 8
|[---
| ---] <- width: 8`,
),
)Refinements
isAnnotated
Returns true if the specified Doc is a Annotated, false otherwise.
Signature
declare const isAnnotated: <A>(self: Doc<A>) => self is Annotated<A>;Returns true if the specified Doc is a Cat, false otherwise.
Signature
declare const isCat: <A>(self: Doc<A>) => self is Cat<A>;Returns true if the specified Doc is a Char, false otherwise.
Signature
declare const isChar: <A>(self: Doc<A>) => self is Char<A>;Returns true if the specified Doc is a Column, false otherwise.
Signature
declare const isColumn: <A>(self: Doc<A>) => self is Column<A>;Returns true if the specified value is a Doc, false otherwise.
Signature
declare const isDoc: (u: unknown) => u is Doc<unknown>;Returns true if the specified Doc is an Empty, false otherwise.
Signature
declare const isEmpty: <A>(self: Doc<A>) => self is Empty<A>;Returns true if the specified Doc is a Fail, false otherwise.
Signature
declare const isFail: <A>(self: Doc<A>) => self is Fail<A>;Returns true if the specified Doc is a FlatAlt, false otherwise.
Signature
declare const isFlatAlt: <A>(self: Doc<A>) => self is FlatAlt<A>;Returns true if the specified Doc is a Line, false otherwise.
Signature
declare const isLine: <A>(self: Doc<A>) => self is Line<A>;Returns true if the specified Doc is a Nest, false otherwise.
Signature
declare const isNest: <A>(self: Doc<A>) => self is Nest<A>;Returns true if the specified Doc is a Nesting, false otherwise.
Signature
declare const isNesting: <A>(self: Doc<A>) => self is Nesting<A>;Returns true if the specified Doc is a Text, false otherwise.
Signature
declare const isText: <A>(self: Doc<A>) => self is Text<A>;Returns true if the specified Doc is a Union, false otherwise.
Signature
declare const isUnion: <A>(self: Doc<A>) => self is Union<A>;isWithPageWidth
Returns true if the specified Doc is a WithPageWidth, false otherwise.
Signature
declare const isWithPageWidth: {};Rendering
Signature
declare const render: {
(config: RenderConfig): <A>(self: Doc<A>) => string;
<A>(self: Doc<A>, config: RenderConfig): string;
};renderStream
Signature
declare const renderStream: <A>(self: DocStream<A>) => string;Separation
The fillSep combinator concatenates all documents in a collection horizontally by placing a space between each pair of documents as long as they fit the page. Once the page width is exceeded, a line is inserted and the process is repeated for all documents in the collection.
Note: the use of line means that if grouped, the documents will be separated with a space instead of newlines. See fillCat if you do not want a space.
Signature
declare const fillSep: <A>(docs: Iterable<Doc<A>>) => Doc<A>;The hsep combinator concatenates all documents in a collection horizontally by placing a space between each pair of documents.
For automatic line breaks, consider using fillSep.
Signature
declare const hsep: <A>(docs: Iterable<Doc<A>>) => Doc<A>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
const doc: Doc.Doc<never> = Doc.hsep(Doc.words("lorem ipsum dolor sit amet"))
assert.strictEqual(
Doc.render(doc, {
style: "pretty",
options: { lineWidth: 80 },
}),
"lorem ipsum dolor sit amet",
)
// The `hsep` combinator will not introduce line breaks on its own, even when
// the page is too narrow
assert.strictEqual(
Doc.render(doc, {
style: "pretty",
options: { lineWidth: 5 },
}),
"lorem ipsum dolor sit amet",
)The seps combinator will attempt to lay out a collection of documents separated by spaces. If the output does not fit the page, then the documents will be separated by newlines. This is what differentiates it from vsep, which always lays out documents beneath one another.
Signature
declare const seps: <A>(docs: Iterable<Doc<A>>) => Doc<A>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import * as String from "effect/String"
const doc: Doc.Doc<never> = Doc.hsep([Doc.text("prefix"), Doc.seps(Doc.words("text to lay out"))])
assert.strictEqual(Doc.render(doc, { style: "pretty" }), "prefix text to lay out")
// If the page width is too narrow, documents are separated by newlines
assert.strictEqual(
Doc.render(doc, {
style: "pretty",
options: { lineWidth: 20 },
}),
String.stripMargin(
`|prefix text
|to
|lay
|out`,
),
)The vsep combinator concatenates all documents in a collection vertically. If a group undoes the line breaks inserted by vsep, the documents are separated with a space instead.
When a vsep is grouped, the documents are separated with a space if the layoutfits the page, otherwise nothing is done. See the sep convenience function for this use case.
Signature
declare const vsep: <A>(docs: Iterable<Doc<A>>) => Doc<A>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import * as String from "effect/String"
const unaligned = Doc.hsep([Doc.text("prefix"), Doc.vsep(Doc.words("text to lay out"))])
assert.strictEqual(
Doc.render(unaligned, { style: "pretty" }),
String.stripMargin(
`|prefix text
|to
|lay
|out`,
),
)
// The `align` function can be used to align the documents under their first
// element
const aligned = Doc.hsep([Doc.text("prefix"), Doc.align(Doc.vsep(Doc.words("text to lay out")))])
assert.strictEqual(
Doc.render(aligned, { style: "pretty" }),
String.stripMargin(
`|prefix text
| to
| lay
| out`,
),
)Symbol
Utilities
angleBracketed
Encloses the input document in angle brackets (<>).
Signature
declare const angleBracketed: <A>(self: Doc<A>) => Doc<A>;curlyBraced
Encloses the input document in curly braces ({}).
Signature
declare const curlyBraced: <A>(self: Doc<A>) => Doc<A>;doubleQuoted
Encloses the input document in double quotes ("").
Signature
declare const doubleQuoted: <A>(self: Doc<A>) => Doc<A>;parenthesized
Encloses the input document in parentheses (()).
Signature
declare const parenthesized: <A>(self: Doc<A>) => Doc<A>;The punctuate combinator appends the punctuator document to all but the last document in a collection of documents. The separators are places after the document entries, which can be observed if the result is oriented vertically.
Signature
declare const punctuate: {
<A, B>(punctuator: Doc<A>): (docs: Iterable<Doc<B>>) => readonly Array<Doc<A | B>>;
<A, B>(docs: Iterable<Doc<B>>, punctuator: Doc<A>): readonly Array<Doc<A | B>>;
}Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import { pipe } from "effect/Function"
import * as String from "effect/String"
const docs = pipe(Doc.words("lorem ipsum dolor sit amet"), Doc.punctuate(Doc.comma))
assert.strictEqual(
Doc.render(Doc.hsep(docs), { style: "pretty" }),
"lorem, ipsum, dolor, sit, amet",
)
// The separators are put at the end of the entries, which can be better
// visualzied if the documents are rendered vertically
assert.strictEqual(
Doc.render(Doc.vsep(docs), { style: "pretty" }),
String.stripMargin(
`|lorem,
|ipsum,
|dolor,
|sit,
|amet`,
),
)Splits a string of words into individual Text documents using the specified char to split on (defaults to " "). In addition, a softLine is inserted in between each word so that if the text exceeds the available width it will be broken into multiple lines.
Signature
declare const reflow: (s: string, char?: string) => Doc<never>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import * as String from "effect/String"
const doc = Doc.reflow(
"Lorem ipsum dolor sit amet, consectetur adipisicing elit, " +
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
)
assert.strictEqual(
Doc.render(doc, {
style: "pretty",
options: { lineWidth: 32 },
}),
String.stripMargin(
`|Lorem ipsum dolor sit amet,
|consectetur adipisicing elit,
|sed do eiusmod tempor incididunt
|ut labore et dolore magna
|aliqua.`,
),
)Removes control characters from the text leaves of a document while preserving its structure and annotations.
Horizontal tabs (U+0009) and line feeds (U+000A) are preserved. Other C0 controls, DEL (U+007F), and C1 controls (U+0080 through U+009F) are removed.
Use this at the boundary where untrusted text enters a document that will be rendered to a terminal. Existing constructors and renderers remain suitable for trusted text containing intentional terminal sequences, such as OSC 8 hyperlinks.
Signature
declare const sanitize: <A>(self: Doc<A>) => Doc<A>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
const doc = Doc.sanitize(Doc.text("hello\u001bworld"))
assert.strictEqual(Doc.render(doc, { style: "pretty" }), "helloworld")singleQuoted
Encloses the input document in single quotes ("").
Signature
declare const singleQuoted: <A>(self: Doc<A>) => Doc<A>;The spaces combinator lays out a document containing n spaces. Negative values for n count as 0 spaces.
Signature
declare const spaces: (n: number) => Doc<never>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
const doc = Doc.squareBracketed(Doc.doubleQuoted(Doc.spaces(5)))
assert.strictEqual(Doc.render(doc, { style: "pretty" }), '[" "]')squareBracketed
Encloses the input document in square brackets ([]).
Signature
declare const squareBracketed: <A>(self: Doc<A>) => Doc<A>;The surround combinator encloses a document in between left and right documents.
Signature
declare const surround: {
<A, B, C>(left: Doc<A>, right: Doc<B>): (self: Doc<C>) => Doc<A | B | C>;
<A, B, C>(self: Doc<C>, left: Doc<A>, right: Doc<B>): Doc<A | B | C>;
};Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
import { pipe } from "effect/Function"
const doc = pipe(Doc.char("-"), Doc.surround(Doc.char("A"), Doc.char("Z")))
assert.strictEqual(Doc.render(doc, { style: "pretty" }), "A-Z")textSpaces
Signature
declare const textSpaces: (n: number) => string;Splits a string of words into individual Text documents using the specified char to split on (defaults to " ").
Signature
declare const words: (s: string, char?: string) => ReadonlyArray<Doc<never>>;Example
import * as assert from "node:assert"
import * as Doc from "@effect/printer/Doc"
const doc = Doc.tupled(Doc.words("lorem ipsum dolor"))
assert.strictEqual(Doc.render(doc, { style: "pretty" }), "(lorem, ipsum, dolor)")
The
aligncombinator lays out a document with the nesting level set to the current column.