pretty_print/tree/mod.rs
1use crate::{helpers::PrettySequence, render, FmtWrite, PrettyBuilder, RenderAnnotated};
2use alloc::{borrow::Cow, rc::Rc, string::String};
3use color_ansi::AnsiStyle;
4use core::{
5 fmt::{Debug, Formatter},
6 ops::{Add, AddAssign},
7};
8use std::io::Write;
9use unicode_segmentation::UnicodeSegmentation;
10
11mod display;
12mod into;
13
14/// The concrete document type. This type is not meant to be used directly. Instead use the static
15/// functions on `Doc` or the methods on an `DocAllocator`.
16///
17/// The `T` parameter is used to abstract over pointers to `Doc`. See `RefDoc` and `BoxDoc` for how
18/// it is used
19pub enum PrettyTree {
20 /// Nothing to show
21 Nil,
22 /// A hard line break
23 Hardline,
24 /// A dynamic text document, all newlines are hard line breaks
25 Text(Rc<str>),
26 /// A static text document, all newlines are hard line breaks
27 StaticText(&'static str),
28 /// A document with ansi styles
29 Annotated {
30 /// The style to use for the text
31 style: Rc<AnsiStyle>,
32 /// The text to display
33 body: Rc<Self>,
34 },
35 /// Concatenates two documents
36 Append {
37 /// The first document
38 lhs: Rc<Self>,
39 /// The second document
40 rhs: Rc<Self>,
41 },
42 /// Concatenates two documents with a space in between
43 Group {
44 /// The first document
45 items: Rc<Self>,
46 },
47 /// Concatenates two documents with a line in between
48 MaybeInline {
49 /// The first document
50 block: Rc<Self>,
51 /// The second document
52 inline: Rc<Self>,
53 },
54 /// Concatenates two documents with a line in between
55 Nest {
56 /// The first document
57 space: isize,
58 /// The second document
59 doc: Rc<Self>,
60 },
61 /// Stores the length of a string document that is not just ascii
62 RenderLength {
63 /// The length of the string
64 length: usize,
65 /// The document
66 body: Rc<Self>,
67 },
68 /// Concatenates two documents with a line in between
69 Union {
70 /// The first document
71 lhs: Rc<Self>,
72 /// The second document
73 rhs: Rc<Self>,
74 },
75 /// Concatenates two documents with a line in between
76 Column {
77 /// The first document
78 invoke: Rc<dyn Fn(usize) -> Self>,
79 },
80 /// Concatenates two documents with a line in between
81 Nesting {
82 /// The first document
83 invoke: Rc<dyn Fn(usize) -> Self>,
84 },
85 /// Concatenates two documents with a line in between
86 Fail,
87}
88
89#[allow(non_upper_case_globals)]
90impl PrettyTree {
91 /// A hard line break
92 pub const Space: Self = PrettyTree::StaticText(" ");
93 /// A line acts like a `\n` but behaves like `space` if it is grouped on a single line.
94 #[inline]
95 pub fn line_or_space() -> Self {
96 Self::Hardline.flat_alt(Self::Space).into()
97 }
98 /// A line acts like `\n` but behaves like `nil` if it is grouped on a single line.
99 #[inline]
100 pub fn line_or_comma() -> Self {
101 Self::Hardline.flat_alt(PrettyTree::StaticText(", ")).into()
102 }
103 /// Acts like `line` but behaves like `nil` if grouped on a single line
104 #[inline]
105 pub fn line_or_nil() -> Self {
106 Self::Hardline.flat_alt(Self::Nil).into()
107 }
108}
109
110impl PrettyTree {
111 /// The given text, which must not contain line breaks.
112 #[inline]
113 pub fn text<U: Into<Cow<'static, str>>>(data: U) -> Self {
114 match data.into() {
115 Cow::Borrowed(s) => PrettyTree::StaticText(s),
116 Cow::Owned(s) => PrettyTree::Text(Rc::from(s)),
117 }
118 .with_utf8_len()
119 }
120}
121
122impl PrettyTree {
123 /// Writes a rendered document to a `std::io::Write` object.
124 #[inline]
125 #[cfg(feature = "std")]
126 pub fn render<W>(&self, width: usize, out: &mut W) -> std::io::Result<()>
127 where
128 W: ?Sized + std::io::Write,
129 {
130 self.render_raw(width, &mut crate::IoWrite::new(out))
131 }
132
133 /// Writes a rendered document to a `std::fmt::Write` object.
134 #[inline]
135 pub fn render_fmt<W>(&self, width: usize, out: &mut W) -> core::fmt::Result
136 where
137 W: ?Sized + core::fmt::Write,
138 {
139 self.render_raw(width, &mut FmtWrite::new(out))
140 }
141
142 /// Writes a rendered document to a `RenderAnnotated<A>` object.
143 #[inline]
144 pub fn render_raw<W>(&self, width: usize, out: &mut W) -> Result<(), W::Error>
145 where
146 W: RenderAnnotated,
147 W: ?Sized,
148 {
149 render::best(Rc::new(self.clone()), width, out)
150 }
151}
152
153impl PrettyTree {
154 /// The given text, which must not contain line breaks.
155 #[inline]
156 #[cfg(feature = "std")]
157 pub fn render_colored<W: Write>(&self, width: usize, out: W) -> std::io::Result<()> {
158 render::best(Rc::new(self.clone()), width, &mut crate::TerminalWriter::new(out))
159 }
160}
161
162impl PrettyBuilder for PrettyTree {
163 /// Acts as `self` when laid out on multiple lines and acts as `that` when laid out on a single line.
164 ///
165 /// ```
166 /// use pretty::{Arena, DocAllocator};
167 ///
168 /// let arena = Arena::<()>::new();
169 /// let body = arena.line().append("x");
170 /// let doc = arena
171 /// .text("let")
172 /// .append(arena.line())
173 /// .append("x")
174 /// .group()
175 /// .append(body.clone().flat_alt(arena.line().append("in").append(body)))
176 /// .group();
177 ///
178 /// assert_eq!(doc.1.pretty(100).to_string(), "let x in x");
179 /// assert_eq!(doc.1.pretty(8).to_string(), "let x\nx");
180 /// ```
181 #[inline]
182 fn flat_alt<E>(self, flat: E) -> Self
183 where
184 E: Into<PrettyTree>,
185 {
186 Self::MaybeInline { block: Rc::new(self), inline: Rc::new(flat.into()) }
187 }
188 /// Indents `self` by `adjust` spaces from the current cursor position
189 ///
190 /// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr
191 /// like `RefDoc` or `RcDoc`
192 ///
193 /// ```rust
194 /// use pretty::DocAllocator;
195 ///
196 /// let arena = pretty::Arena::<()>::new();
197 /// let doc = arena
198 /// .text("prefix")
199 /// .append(arena.text(" "))
200 /// .append(arena.reflow("The indent function indents these words!").indent(4));
201 /// assert_eq!(
202 /// doc.1.pretty(24).to_string(),
203 /// "
204 /// prefix The indent
205 /// function
206 /// indents these
207 /// words!"
208 /// .trim_start(),
209 /// );
210 /// ```
211 #[inline]
212 fn indent(self, adjust: usize) -> Self {
213 let spaces = {
214 use crate::render::SPACES;
215 let mut doc = PrettyTree::Nil;
216 let mut remaining = adjust;
217 while remaining != 0 {
218 let i = SPACES.len().min(remaining);
219 remaining -= i;
220 doc = doc.append(PrettyTree::text(&SPACES[..i]))
221 }
222 doc
223 };
224 spaces.append(self).hang(adjust.try_into().unwrap())
225 }
226
227 /// Increase the indentation level of this document.
228 #[inline]
229 fn nest(self, offset: isize) -> Self {
230 if let Self::Nil = self {
231 return self;
232 }
233 if offset == 0 {
234 return self;
235 }
236 Self::Nest { space: offset, doc: Rc::new(self) }
237 }
238}
239
240impl PrettyTree {
241 fn with_utf8_len(self) -> Self {
242 let s = match &self {
243 Self::Text(s) => s.as_ref(),
244 Self::StaticText(s) => s,
245 // Doc::SmallText(s) => s,
246 _ => return self,
247 };
248 if s.is_ascii() {
249 self
250 }
251 else {
252 let grapheme_len = s.graphemes(true).count();
253 Self::RenderLength { length: grapheme_len, body: Rc::new(self) }
254 }
255 }
256
257 /// Append the given document after this document.
258 #[inline]
259 pub fn append<E>(self, follow: E) -> Self
260 where
261 E: Into<PrettyTree>,
262 {
263 let rhs = follow.into();
264 match (&self, &rhs) {
265 (Self::Nil, _) => rhs,
266 (_, Self::Nil) => self,
267 _ => Self::Append { lhs: Rc::new(self), rhs: Rc::new(rhs) },
268 }
269 }
270 /// Allocate a document that intersperses the given separator `S` between the given documents
271 /// `[A, B, C, ..., Z]`, yielding `[A, S, B, S, C, S, ..., S, Z]`.
272 ///
273 /// Compare [the `intersperse` method from the `itertools` crate](https://docs.rs/itertools/0.5.9/itertools/trait.Itertools.html#method.intersperse).
274 ///
275 /// NOTE: The separator type, `S` may need to be cloned. Consider using cheaply cloneable ptr
276 /// like `RefDoc` or `RcDoc`
277 #[inline]
278 pub fn join<I, T1, T2>(terms: I, joint: T2) -> PrettyTree
279 where
280 I: IntoIterator<Item = T1>,
281 T1: Into<PrettyTree>,
282 T2: Into<PrettyTree>,
283 {
284 let joint = joint.into();
285 let mut iter = terms.into_iter().map(|s| s.into());
286 let mut terms = PrettySequence::new(0);
287 terms += iter.next().unwrap_or(PrettyTree::Nil);
288 for term in iter {
289 terms += joint.clone();
290 terms += term;
291 }
292 terms.into()
293 }
294 /// Allocate a document that intersperses the given separator `S` between the given documents
295 pub fn concat<I>(docs: I) -> Self
296 where
297 I: IntoIterator,
298 I::Item: Into<PrettyTree>,
299 {
300 let mut head = Self::Nil;
301 for item in docs.into_iter() {
302 head += item.into();
303 }
304 head
305 }
306
307 /// Mark this document as a group.
308 ///
309 /// Groups are layed out on a single line if possible. Within a group, all basic documents with
310 /// several possible layouts are assigned the same layout, that is, they are all layed out
311 /// horizontally and combined into a one single line, or they are each layed out on their own
312 /// line.
313 #[inline]
314 pub fn group(self) -> Self {
315 match self {
316 Self::Group { .. } | Self::Text(_) | Self::StaticText(_) | Self::Nil => self,
317 _ => Self::Group { items: Rc::new(self) },
318 }
319 }
320
321 /// Mark this document as a comment.
322 #[inline]
323 pub fn annotate(self, style: Rc<AnsiStyle>) -> Self {
324 Self::Annotated { style: style, body: Rc::new(self) }
325 }
326 /// Mark this document as a hard line break.
327 #[inline]
328 pub fn union<E>(self, other: E) -> Self
329 where
330 E: Into<PrettyTree>,
331 {
332 Self::Union { lhs: Rc::new(self), rhs: Rc::new(other.into()) }
333 }
334
335 /// Lays out `self` so with the nesting level set to the current column
336 ///
337 /// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr
338 /// like `RefDoc` or `RcDoc`
339 ///
340 /// ```rust
341 /// use pretty::{docs, DocAllocator};
342 ///
343 /// let arena = &pretty::Arena::<()>::new();
344 /// let doc = docs![
345 /// arena,
346 /// "lorem",
347 /// " ",
348 /// arena.intersperse(["ipsum", "dolor"].iter().cloned(), arena.line_()).align(),
349 /// arena.hardline(),
350 /// "next",
351 /// ];
352 /// assert_eq!(doc.1.pretty(80).to_string(), "lorem ipsum\n dolor\nnext");
353 /// ```
354 #[inline]
355 pub fn align(self) -> Self {
356 Self::Column {
357 invoke: Rc::new(move |col| {
358 let self_ = self.clone();
359 Self::Nesting { invoke: Rc::new(move |nest| self_.clone().nest(col as isize - nest as isize)) }
360 }),
361 }
362 }
363
364 /// Lays out `self` with a nesting level set to the current level plus `adjust`.
365 ///
366 /// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr
367 /// like `RefDoc` or `RcDoc`
368 ///
369 /// ```rust
370 /// use pretty::DocAllocator;
371 ///
372 /// let arena = pretty::Arena::<()>::new();
373 /// let doc = arena
374 /// .text("prefix")
375 /// .append(arena.text(" "))
376 /// .append(arena.reflow("Indenting these words with nest").hang(4));
377 /// assert_eq!(
378 /// doc.1.pretty(24).to_string(),
379 /// "prefix Indenting these\n words with\n nest",
380 /// );
381 /// ```
382 #[inline]
383 pub fn hang(self, adjust: isize) -> Self {
384 self.nest(adjust).align()
385 }
386
387 /// Lays out `self` and provides the column width of it available to `f`
388 ///
389 /// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr
390 /// like `RefDoc` or `RcDoc`
391 ///
392 /// ```rust
393 /// use pretty::DocAllocator;
394 ///
395 /// let arena = pretty::Arena::<()>::new();
396 /// let doc = arena
397 /// .text("prefix ")
398 /// .append(arena.column(|l| arena.text("| <- column ").append(arena.as_string(l)).into_doc()));
399 /// assert_eq!(doc.1.pretty(80).to_string(), "prefix | <- column 7");
400 /// ```
401 #[inline]
402 pub fn width<F>(self, f: F) -> Self
403 where
404 F: Fn(isize) -> Self + Clone + 'static,
405 {
406 Self::Column {
407 invoke: Rc::new(move |start| {
408 let f = f.clone();
409 self.clone() + Self::Column { invoke: Rc::new(move |end| f(end as isize - start as isize)) }
410 }),
411 }
412 }
413
414 /// Puts `self` between `before` and `after`
415 #[inline]
416 pub fn enclose<E, F>(self, before: E, after: F) -> Self
417 where
418 E: Into<Self>,
419 F: Into<Self>,
420 {
421 before.into().append(self).append(after.into())
422 }
423
424 /// Puts `self` between `before` and `after` if `cond` is true
425 pub fn single_quotes(self) -> Self {
426 self.enclose("'", "'")
427 }
428
429 /// Puts `self` between `before` and `after` if `cond` is true
430 pub fn double_quotes(self) -> Self {
431 self.enclose("\"", "\"")
432 }
433 /// Puts `self` between `before` and `after` if `cond` is true
434 pub fn parens(self) -> Self {
435 self.enclose("(", ")")
436 }
437 /// Puts `self` between `before` and `after` if `cond` is true
438 pub fn angles(self) -> Self {
439 self.enclose("<", ">")
440 }
441 /// Puts `self` between `before` and `after` if `cond` is true
442 pub fn braces(self) -> Self {
443 self.enclose("{", "}")
444 }
445 /// Puts `self` between `before` and `after` if `cond` is true
446 pub fn brackets(self) -> Self {
447 self.enclose("[", "]")
448 }
449}