pub struct Doc(/* private fields */);Expand description
An immutable, cheaply-clonable description of a document’s layout.
A Doc records intent — “these pieces belong together”, “break here if the
line is too long”, “indent the inside by four” — and leaves the choice of
concrete line breaks to render, which fits the document to a
target width. The same Doc renders differently at width 40 and width 120
with no change to how it was built.
§Cloning
Doc is a thin handle around a reference-counted node (Rc), so
Clone is a pointer-count bump, not a deep copy. Sharing a sub-document in
several places costs one Rc clone each. Doc is single-threaded by design
(it is not Send/Sync); a formatter builds and renders on one thread.
§Examples
use pretty_lang::Doc;
// `[1, 2, 3]` — flat because it fits.
let list = Doc::text("[")
.append(Doc::join(
Doc::text(",").append(Doc::line()),
["1", "2", "3"].map(Doc::text),
))
.append(Doc::text("]"))
.group();
assert_eq!(list.render(80), "[1, 2, 3]");Implementations§
Source§impl Doc
impl Doc
Sourcepub fn text(s: impl Into<Cow<'static, str>>) -> Doc
pub fn text(s: impl Into<Cow<'static, str>>) -> Doc
A literal piece of text.
The argument is anything that converts into a Cow<'static, str>, so a
string literal is stored without allocating and an owned String is
moved in. The display width is measured once, here, as the number of
Unicode scalar values.
§Panics
Never panics. The text is treated as a single unbreakable unit; it MUST
NOT contain a '\n' (embed line breaks with line,
softline, or hardline so the layout
engine can account for them). A newline inside text is rendered
verbatim but throws the width accounting off.
§Examples
use pretty_lang::Doc;
// A static literal — no allocation.
assert_eq!(Doc::text("let x").render(80), "let x");
// An owned, computed string.
let name = format!("v{}", 42);
assert_eq!(Doc::text(name).render(80), "v42");Sourcepub fn line() -> Doc
pub fn line() -> Doc
A flexible break that is a single space when its group is laid out flat and a newline (plus the current indentation) when the group breaks.
This is the workhorse separator: put it between items that should sit on one line when they fit and stack one-per-line when they do not.
§Examples
use pretty_lang::Doc;
let doc = Doc::text("a").append(Doc::line()).append(Doc::text("b")).group();
assert_eq!(doc.render(80), "a b"); // fits: space
assert_eq!(doc.render(1), "a\nb"); // too narrow: newlineSourcepub fn softline() -> Doc
pub fn softline() -> Doc
A flexible break that is nothing when its group is flat and a newline (plus indentation) when the group breaks. Use it where a broken layout wants a line break but a flat layout wants no space at all — for example right after an opening bracket.
§Examples
use pretty_lang::Doc;
let doc = Doc::text("(")
.append(Doc::softline())
.append(Doc::text("x"))
.group();
assert_eq!(doc.render(80), "(x"); // flat: no gapSourcepub fn hardline() -> Doc
pub fn hardline() -> Doc
A break that is always a newline, and forces every group that contains it to break. Use it for constructs that must never be collapsed onto one line, such as line comments or statement separators in block bodies.
§Examples
use pretty_lang::Doc;
let doc = Doc::text("a").append(Doc::hardline()).append(Doc::text("b")).group();
// Even though "a b" would fit at width 80, the hardline forces a break.
assert_eq!(doc.render(80), "a\nb");Sourcepub fn append(self, other: Doc) -> Doc
pub fn append(self, other: Doc) -> Doc
Concatenate self with other, laid out left then right. This is the
fundamental way to build a document up from parts.
nil is the identity: a.append(Doc::nil()) and
Doc::nil().append(a) both render exactly as a.
§Examples
use pretty_lang::Doc;
let doc = Doc::text("fn ").append(Doc::text("main")).append(Doc::text("()"));
assert_eq!(doc.render(80), "fn main()");Sourcepub fn nest(self, indent: isize) -> Doc
pub fn nest(self, indent: isize) -> Doc
Increase the indentation applied to every line break inside self by
indent columns. Indentation is relative and nests: an inner nest(4)
inside an outer nest(4) indents broken lines by eight.
indent is an isize; a negative value dedents. The effective
indentation never goes below zero (it is clamped at the point a newline
is emitted).
Only line breaks that actually happen are affected — nest on a document
that stays flat has no visible effect.
§Examples
use pretty_lang::Doc;
let body = Doc::text("{")
.append(
Doc::line()
.append(Doc::text("stmt;"))
.nest(4),
)
.append(Doc::line())
.append(Doc::text("}"))
.group();
assert_eq!(body.render(4), "{\n stmt;\n}");Sourcepub fn group(self) -> Doc
pub fn group(self) -> Doc
Mark self as a layout choice point.
When the renderer reaches a group it first asks whether the group’s
contents fit, laid out flat, in the width remaining on the current line.
If they do, every flexible break inside becomes its flat form (a space or
nothing). If they do not — or the group contains a
hardline — every flexible break inside becomes a
newline. The decision is all-or-nothing for the breaks directly owned
by this group; nested groups are decided independently.
Grouping is what turns one document into “one line if it fits, otherwise stacked”. A document with no groups always uses the broken form of every break.
§Examples
use pretty_lang::Doc;
let call = Doc::text("f(")
.append(
Doc::softline()
.append(Doc::join(
Doc::text(",").append(Doc::line()),
["alpha", "beta", "gamma"].map(Doc::text),
))
.nest(4),
)
.append(Doc::softline())
.append(Doc::text(")"))
.group();
assert_eq!(call.render(80), "f(alpha, beta, gamma)");
assert_eq!(
call.render(10),
"f(\n alpha,\n beta,\n gamma\n)"
);Sourcepub fn concat(docs: impl IntoIterator<Item = Doc>) -> Doc
pub fn concat(docs: impl IntoIterator<Item = Doc>) -> Doc
Concatenate every document produced by docs, in order. Returns
nil for an empty iterator.
This is a left fold of append and allocates one internal
node per item.
§Examples
use pretty_lang::Doc;
let doc = Doc::concat(["a", "b", "c"].map(Doc::text));
assert_eq!(doc.render(80), "abc");
assert_eq!(Doc::concat(core::iter::empty()).render(80), "");Sourcepub fn join(sep: Doc, docs: impl IntoIterator<Item = Doc>) -> Doc
pub fn join(sep: Doc, docs: impl IntoIterator<Item = Doc>) -> Doc
Concatenate every document produced by docs, placing a clone of sep
between consecutive items (but not before the first or after the last).
Returns nil for an empty iterator.
This is the idiomatic way to render comma-separated lists, &&-joined
conditions, ::-joined paths, and the like — pair it with
group so the whole list collapses onto one line when it
fits.
§Examples
use pretty_lang::Doc;
let path = Doc::join(Doc::text("::"), ["std", "collections", "HashMap"].map(Doc::text));
assert_eq!(path.render(80), "std::collections::HashMap");
// With a flexible separator, the list reflows under a group.
let args = Doc::join(
Doc::text(",").append(Doc::line()),
["x", "y"].map(Doc::text),
)
.group();
assert_eq!(args.render(80), "x, y");Sourcepub fn render(&self, width: usize) -> String
pub fn render(&self, width: usize) -> String
Render this document to an owned String, choosing line breaks so that
no line exceeds width columns where the document allows a choice.
width is the target line length in Unicode scalars. Lines can still
exceed it when a single unbreakable text is wider than
width, or where the document offers no break — the renderer never
invents break points that were not described.
§Examples
use pretty_lang::Doc;
let doc = Doc::text("a").append(Doc::line()).append(Doc::text("b")).group();
assert_eq!(doc.render(80), "a b");
assert_eq!(doc.render(1), "a\nb");Sourcepub fn render_into<W: Write>(&self, width: usize, out: &mut W) -> Result
pub fn render_into<W: Write>(&self, width: usize, out: &mut W) -> Result
Render this document into any core::fmt::Write sink, choosing line
breaks for the target width. Use this to stream directly into a caller
-owned buffer and avoid the intermediate String that
render allocates.
§Errors
Returns core::fmt::Error if and only if the underlying out returns
an error while being written to.
§Examples
use core::fmt::Write;
use pretty_lang::Doc;
let doc = Doc::text("hello").append(Doc::text(" world"));
let mut buf = String::new();
doc.render_into(80, &mut buf).unwrap();
assert_eq!(buf, "hello world");Sourcepub fn render_writer<W: Write>(&self, width: usize, out: &mut W) -> Result<()>
Available on crate feature std only.
pub fn render_writer<W: Write>(&self, width: usize, out: &mut W) -> Result<()>
std only.Render this document into a std::io::Write sink, choosing line breaks
for the target width. This is the streaming counterpart to
render for files, sockets, and stdout.
§Errors
Propagates the first std::io::Error returned by out.
§Examples
use pretty_lang::Doc;
let doc = Doc::text("written to stdout");
let mut buf: Vec<u8> = Vec::new();
doc.render_writer(80, &mut buf).unwrap();
assert_eq!(buf, b"written to stdout");Trait Implementations§
Source§impl Drop for Doc
impl Drop for Doc
Source§fn drop(&mut self)
fn drop(&mut self)
Dismantle the document iteratively when this is its last owner.
The document is a tree of reference-counted nodes, so the derived drop glue would recurse one call frame per level and overflow the stack on a deeply nested document (a long chain of binary expressions, say). This impl walks a uniquely-owned spine with an explicit heap work list instead, keeping the actual node drops shallow. Leaves and shared nodes take a branch-only fast path that allocates nothing.