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}