···8686pub mod parse;
8787pub mod paths;
8888pub mod pretty;
8989+pub mod pretty_ref_arena;
8990pub mod requirement;
9091pub mod strings;
9192pub mod type_;
···11+//! This module implements the functionality described in
22+//! ["Strictly Pretty" (2000) by Christian Lindig][0], with a few
33+//! extensions.
44+//!
55+//! This module is heavily influenced by Elixir's Inspect.Algebra and
66+//! JavaScript's Prettier.
77+//!
88+//! [0]: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34.2200
99+//!
1010+//! ## Extensions
1111+//!
1212+//! - `ForcedBreak` from Elixir.
1313+//! - `FlexBreak` from Elixir.
1414+//!
1515+//! The way this module works is fairly simple conceptually, however the actual
1616+//! behaviour in practice can be hard to wrap one's head around.
1717+//!
1818+//! The basic premise is the `Document` type, which is a tree structure,
1919+//! containing some text as well as information on how it can be formatted.
2020+//! Once the document is constructed, it can be printed using the
2121+//! `to_pretty_string` function.
2222+//!
2323+//! It will then traverse the tree, and construct
2424+//! a string, attempting to wrap lines to that they do not exceed the line length
2525+//! limit specified. Where and when it wraps lines is determined by the structure
2626+//! of the `Document` itself.
2727+//!
2828+#![allow(clippy::wrong_self_convention)]
2929+3030+use std::{cell::RefCell, rc::Rc};
3131+3232+use ecow::{EcoString, eco_format};
3333+use num_bigint::BigInt;
3434+use typed_arena::Arena;
3535+use unicode_segmentation::UnicodeSegmentation;
3636+3737+use crate::{Result, io::Utf8Writer};
3838+3939+/// Join multiple documents together in a vector. This macro calls the `to_doc`
4040+/// method on each element, providing a concise way to write a document sequence.
4141+/// For example:
4242+///
4343+/// ```rust:norun
4444+/// docvec!["Hello", line(), "world!"]
4545+/// ```
4646+///
4747+/// Note: each document in a docvec is not separated in any way: the formatter
4848+/// will never break a line unless a `Document::Break` or `Document::Line`
4949+/// is used. Therefore, `docvec!["a", "b", "c"]` is equivalent to
5050+/// `"abc".to_doc()`.
5151+///
5252+#[macro_export]
5353+macro_rules! docvec_ref_arena {
5454+ () => {
5555+ Document::Vec(Vec::new())
5656+ };
5757+5858+ // A docvec![] with multiple elements.
5959+ ($arena:expr, $first:expr, $($rest:expr),+ $(,)?) => {
6060+ // A document that looks like this: `Vec[Vec[..rest], ..other_rest]`
6161+ // is exactly the same as a flat: `Vec[..rest, ..other_rest]`.
6262+ // So in case a `docvec!` starts with a `Vec` we flatten it out to avoid
6363+ // having deeply nested documents.
6464+ {
6565+ $first.to_doc(&$arena)
6666+ $(.append(&$arena, $rest))+
6767+ }
6868+ };
6969+}
7070+7171+/// A trait that allows for objects to observe the cursor position as it is being formatted.
7272+/// This is useful for any operations that need to track the exact position a document is
7373+/// being written to in a buffer such as for source mapping.
7474+pub trait CursorPositionObserver: std::fmt::Debug {
7575+ fn observe_cursor_position(&mut self, line: isize, width: isize);
7676+}
7777+7878+// To the outside world a document is an opaque data structure. It just wraps an
7979+// id for an arena used to allocate all the `PrintableDocument`s.
8080+#[derive(Debug, Clone, Copy)]
8181+pub struct Document<'string, 'doc>(&'doc PrintableDocument<'string, 'doc>);
8282+8383+/// Coerce a value into a Document.
8484+/// Note we do not implement this for String as a slight pressure to favour str
8585+/// over String.
8686+pub trait Documentable<'string, 'doc> {
8787+ fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc>;
8888+}
8989+9090+impl<'string, 'doc> Documentable<'string, 'doc> for char {
9191+ fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> {
9292+ eco_format!("{self}").to_doc(arena)
9393+ }
9494+}
9595+9696+impl<'string, 'doc> Documentable<'string, 'doc> for &'string str {
9797+ fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> {
9898+ Document(arena.documents.alloc(PrintableDocument::str(self)))
9999+ }
100100+}
101101+102102+impl<'string, 'doc> Documentable<'string, 'doc> for EcoString {
103103+ fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> {
104104+ Document(arena.documents.alloc(PrintableDocument::EcoString {
105105+ graphemes: self.graphemes(true).count() as isize,
106106+ string: self,
107107+ }))
108108+ }
109109+}
110110+111111+impl<'string, 'doc> Documentable<'string, 'doc> for &EcoString {
112112+ fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> {
113113+ self.clone().to_doc(arena)
114114+ }
115115+}
116116+117117+impl<'string, 'doc> Documentable<'string, 'doc> for isize {
118118+ fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> {
119119+ eco_format!("{self}").to_doc(arena)
120120+ }
121121+}
122122+123123+impl<'string, 'doc> Documentable<'string, 'doc> for i64 {
124124+ fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> {
125125+ eco_format!("{self}").to_doc(arena)
126126+ }
127127+}
128128+129129+impl<'string, 'doc> Documentable<'string, 'doc> for usize {
130130+ fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> {
131131+ eco_format!("{self}").to_doc(arena)
132132+ }
133133+}
134134+135135+impl<'string, 'doc> Documentable<'string, 'doc> for f64 {
136136+ fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> {
137137+ eco_format!("{self:?}").to_doc(arena)
138138+ }
139139+}
140140+141141+impl<'string, 'doc> Documentable<'string, 'doc> for u64 {
142142+ fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> {
143143+ eco_format!("{self:?}").to_doc(arena)
144144+ }
145145+}
146146+147147+impl<'string, 'doc> Documentable<'string, 'doc> for u32 {
148148+ fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> {
149149+ eco_format!("{self}").to_doc(arena)
150150+ }
151151+}
152152+153153+impl<'string, 'doc> Documentable<'string, 'doc> for u16 {
154154+ fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> {
155155+ eco_format!("{self}").to_doc(arena)
156156+ }
157157+}
158158+159159+impl<'string, 'doc> Documentable<'string, 'doc> for u8 {
160160+ fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> {
161161+ eco_format!("{self}").to_doc(arena)
162162+ }
163163+}
164164+165165+impl<'string, 'doc> Documentable<'string, 'doc> for BigInt {
166166+ fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> {
167167+ eco_format!("{self}").to_doc(arena)
168168+ }
169169+}
170170+171171+impl<'string, 'doc> Documentable<'string, 'doc> for Document<'string, 'doc> {
172172+ fn to_doc(self, _arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> {
173173+ self
174174+ }
175175+}
176176+177177+impl<'string, 'doc> Documentable<'string, 'doc> for Vec<Document<'string, 'doc>> {
178178+ fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> {
179179+ arena.concat(self)
180180+ }
181181+}
182182+183183+impl<'string, 'doc, D: Documentable<'string, 'doc>> Documentable<'string, 'doc> for Option<D> {
184184+ fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> {
185185+ self.map(|documentable| documentable.to_doc(arena))
186186+ .unwrap_or(arena.nil())
187187+ }
188188+}
189189+190190+impl<'string, 'doc> Document<'string, 'doc> {
191191+ /// Groups a document. When pretty printing a group, the formatter will
192192+ /// first attempt to fit the entire group on one line. If it fails, all
193193+ /// `break_` documents in the group will render broken.
194194+ ///
195195+ /// Nested groups are handled separately to their parents, so if the
196196+ /// outermost group is broken, any sub-groups might be rendered broken
197197+ /// or unbroken, depending on whether they fit on a single line.
198198+ pub fn group(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> {
199199+ match self.0 {
200200+ // Grouping a group doesn't change how it will be formatted so we
201201+ // can avoid boxing it.
202202+ PrintableDocument::Group(_)
203203+ // Same for an empty document.
204204+ | PrintableDocument::Empty
205205+ // Grouping a literal string will never change how it's formatted,
206206+ // we can avoid boxing it.
207207+ | PrintableDocument::Str { .. }
208208+ | PrintableDocument::EcoString { .. }
209209+ | PrintableDocument::ZeroWidthString { .. }
210210+ | PrintableDocument::CursorPositionObserver { .. } => self,
211211+212212+ PrintableDocument::Line(_)
213213+ | PrintableDocument::ForceBroken(_)
214214+ | PrintableDocument::NextBreakFits(..)
215215+ | PrintableDocument::Break { .. }
216216+ | PrintableDocument::Join(..)
217217+ | PrintableDocument::Nest(..) => Document(arena.documents.alloc(PrintableDocument::Group(self))),
218218+ }
219219+ }
220220+221221+ /// Sets the indentation level of a document.
222222+ pub fn set_nesting(
223223+ self,
224224+ arena: &'doc DocumentArena<'string, 'doc>,
225225+ indent: isize,
226226+ ) -> Document<'string, 'doc> {
227227+ Document(arena.documents.alloc(PrintableDocument::Nest(
228228+ indent,
229229+ NestMode::Set,
230230+ NestCondition::Always,
231231+ self,
232232+ )))
233233+ }
234234+235235+ /// Nests a document by a certain indentation. When rending linebreaks, the
236236+ /// formatter will print a new line followed by the current indentation.
237237+ pub fn nest(
238238+ self,
239239+ arena: &'doc DocumentArena<'string, 'doc>,
240240+ indent: isize,
241241+ ) -> Document<'string, 'doc> {
242242+ Document(arena.documents.alloc(PrintableDocument::Nest(
243243+ indent,
244244+ NestMode::Increase,
245245+ NestCondition::Always,
246246+ self,
247247+ )))
248248+ }
249249+250250+ /// Nests a document by a certain indentation, but only if the current
251251+ /// group is broken.
252252+ pub fn nest_if_broken(
253253+ self,
254254+ arena: &'doc DocumentArena<'string, 'doc>,
255255+ indent: isize,
256256+ ) -> Document<'string, 'doc> {
257257+ Document(arena.documents.alloc(PrintableDocument::Nest(
258258+ indent,
259259+ NestMode::Increase,
260260+ NestCondition::IfBroken,
261261+ self,
262262+ )))
263263+ }
264264+265265+ /// Forces all `break_` and `flex_break` documents in the current group
266266+ /// to render broken.
267267+ pub fn force_break(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> {
268268+ Document(arena.documents.alloc(PrintableDocument::ForceBroken(self)))
269269+ }
270270+271271+ /// Force the next `Break` to render unbroken, regardless of whether it
272272+ /// fits on the line or not.
273273+ pub fn next_break_fits(
274274+ self,
275275+ arena: &'doc DocumentArena<'string, 'doc>,
276276+ mode: NextBreakFitsMode,
277277+ ) -> Document<'string, 'doc> {
278278+ Document(
279279+ arena
280280+ .documents
281281+ .alloc(PrintableDocument::NextBreakFits(self, mode)),
282282+ )
283283+ }
284284+285285+ /// Appends one document to another. Equivalent to `docvec![self, second]`,
286286+ /// except that it `self` is already a `Document::Vec`, it will append
287287+ /// directly to it instead of allocating a new vector.
288288+ ///
289289+ /// Useful when chaining multiple documents together in a fashion where
290290+ /// they cannot be put all into one `docvec!` macro.
291291+ pub fn append(
292292+ self,
293293+ arena: &'doc DocumentArena<'string, 'doc>,
294294+ second: impl Documentable<'string, 'doc>,
295295+ ) -> Document<'string, 'doc> {
296296+ match self.0 {
297297+ PrintableDocument::Empty => second.to_doc(arena),
298298+ PrintableDocument::Line(..)
299299+ | PrintableDocument::Join(..)
300300+ | PrintableDocument::ForceBroken(..)
301301+ | PrintableDocument::NextBreakFits(..)
302302+ | PrintableDocument::Break { .. }
303303+ | PrintableDocument::Nest(..)
304304+ | PrintableDocument::Group(..)
305305+ | PrintableDocument::Str { .. }
306306+ | PrintableDocument::EcoString { .. }
307307+ | PrintableDocument::ZeroWidthString { .. }
308308+ | PrintableDocument::CursorPositionObserver { .. } => {
309309+ let second = second.to_doc(arena);
310310+ if let PrintableDocument::Empty = second.0 {
311311+ self
312312+ } else {
313313+ Document(arena.documents.alloc(PrintableDocument::Join(self, second)))
314314+ }
315315+ }
316316+ }
317317+ }
318318+319319+ /// Surrounds a document in two delimiters. Equivalent to
320320+ /// `docvec![option, self, closed]`.
321321+ pub fn surround(
322322+ self,
323323+ arena: &'doc DocumentArena<'string, 'doc>,
324324+ open: impl Documentable<'string, 'doc>,
325325+ closed: impl Documentable<'string, 'doc>,
326326+ ) -> Document<'string, 'doc> {
327327+ open.to_doc(arena)
328328+ .append(arena, self)
329329+ .append(arena, closed.to_doc(arena))
330330+ }
331331+332332+ /// Returns true when the document contains no printable characters
333333+ /// (whitespace and newlines are considered printable characters).
334334+ pub fn is_empty(&self, arena: &DocumentArena<'string, 'doc>) -> bool {
335335+ match self.0 {
336336+ PrintableDocument::Empty => true,
337337+ PrintableDocument::Line(n) => *n == 0,
338338+ PrintableDocument::EcoString { string, .. } => string.is_empty(),
339339+ PrintableDocument::Str { string, .. } => string.is_empty(),
340340+ // assuming `broken` and `unbroken` are equivalent
341341+ PrintableDocument::Break { broken, .. } => broken.is_empty(),
342342+ PrintableDocument::ForceBroken(document)
343343+ | PrintableDocument::Nest(_, _, _, document)
344344+ | PrintableDocument::Group(document)
345345+ | PrintableDocument::NextBreakFits(document, _) => document.is_empty(arena),
346346+ PrintableDocument::Join(first, second) => {
347347+ first.is_empty(arena) && second.is_empty(arena)
348348+ }
349349+ // Zero-width strings don't count towards line length, but they are
350350+ // still printed and so are not empty. (Unless their string contents
351351+ // is also empty)
352352+ PrintableDocument::ZeroWidthString { string } => string.is_empty(),
353353+ PrintableDocument::CursorPositionObserver { .. } => true,
354354+ }
355355+ }
356356+357357+ /// Prints a document into a `String`, attempting to limit lines to `limit`
358358+ /// characters in length.
359359+ pub fn to_pretty_string(self, limit: isize) -> String {
360360+ let mut buffer = String::new();
361361+ self.pretty_print(limit, &mut buffer)
362362+ .expect("Writing to string buffer failed");
363363+ buffer
364364+ }
365365+366366+ /// Prints a document into `writer`, attempting to limit lines to `limit`
367367+ /// characters in length.
368368+ pub fn pretty_print(self, limit: isize, writer: &mut impl Utf8Writer) -> Result<()> {
369369+ let docs = im::vector![(0, Mode::Unbroken, self)];
370370+ format(writer, limit, docs)?;
371371+ Ok(())
372372+ }
373373+}
374374+375375+/// A pretty printable document. A tree structure, made up of text and other
376376+/// elements which determine how it can be formatted.
377377+///
378378+/// The variants of this enum should probably not be constructed directly,
379379+/// rather use the helper functions of the same names to construct them.
380380+/// For example, use `line()` instead of `Document::Line(1)`.
381381+///
382382+#[derive(Debug, Clone)]
383383+enum PrintableDocument<'string, 'doc> {
384384+ /// A mandatory linebreak. This is always printed as a string of newlines,
385385+ /// equal in length to the number specified.
386386+ Line(usize),
387387+388388+ /// Forces the breaks of the wrapped document to be considered as not
389389+ /// fitting on a single line. Used in combination with a `Group` it can be
390390+ /// used to force its `Break`s to always break.
391391+ ForceBroken(Document<'string, 'doc>),
392392+393393+ /// Ignore the next break, forcing it to render as unbroken.
394394+ NextBreakFits(Document<'string, 'doc>, NextBreakFitsMode),
395395+396396+ /// A document after which the formatter can insert a newline. This determines
397397+ /// where line breaks can occur, outside of hardcoded `Line`s.
398398+ /// See `break_` and `flex_break` for usage.
399399+ Break {
400400+ broken: &'string str,
401401+ unbroken: &'string str,
402402+ kind: BreakKind,
403403+ },
404404+405405+ /// Join multiple documents together. The documents are not separated in any
406406+ /// way: the formatter will only print newlines if `Document::Break` or
407407+ /// `Document::Line` is used.
408408+ // maybe experiment with Slice(&'doc [Document<'string, 'doc>]),
409409+ Join(Document<'string, 'doc>, Document<'string, 'doc>),
410410+411411+ /// Nests the given document by the given indent, depending on the specified
412412+ /// condition. See `Document::nest`, `Document::set_nesting` and
413413+ /// `Document::nest_if_broken` for usages.
414414+ Nest(isize, NestMode, NestCondition, Document<'string, 'doc>),
415415+416416+ /// Groups a document. When pretty printing a group, the formatter will
417417+ /// first attempt to fit the entire group on one line. If it fails, all
418418+ /// `break_` documents in the group will render broken.
419419+ ///
420420+ /// Nested groups are handled separately to their parents, so if the
421421+ /// outermost group is broken, any sub-groups might be rendered broken
422422+ /// or unbroken, depending on whether they fit on a single line.
423423+ Group(Document<'string, 'doc>),
424424+425425+ /// Renders a string slice. This will always render the string verbatim,
426426+ /// without any line breaks or other modifications to it.
427427+ Str {
428428+ string: &'string str,
429429+ /// The number of extended grapheme clusters in the string.
430430+ /// This is what the pretty printer uses as the width of the string as it
431431+ /// is closes to what a human would consider the "length" of a string.
432432+ ///
433433+ /// Since computing the number of grapheme clusters requires walking over
434434+ /// the string we precompute it to avoid iterating through a string over
435435+ /// and over again in the pretty printing algorithm.
436436+ ///
437437+ graphemes: isize,
438438+ },
439439+440440+ /// Renders an `EcoString`. This will always render the string verbatim,
441441+ /// without any line breaks or other modifications to it.
442442+ EcoString {
443443+ string: EcoString,
444444+ /// The number of extended grapheme clusters in the string.
445445+ /// This is what the pretty printer uses as the width of the string as it
446446+ /// is closes to what a human would consider the "length" of a string.
447447+ ///
448448+ /// Since computing the number of grapheme clusters requires walking over
449449+ /// the string we precompute it to avoid iterating through a string over
450450+ /// and over again in the pretty printing algorithm.
451451+ ///
452452+ graphemes: isize,
453453+ },
454454+455455+ /// A string that is not taken into account when determining line length.
456456+ /// This is useful for additional formatting text which won't be rendered
457457+ /// in the final output, such as ANSI codes or HTML elements.
458458+ ZeroWidthString {
459459+ string: EcoString,
460460+ },
461461+462462+ /// A node that gets notified of the cursor position as it is being formatted.
463463+ /// This allows for processes outside of the final output to be notified of
464464+ /// the cursor position and perform actions based on it, such as recording
465465+ /// the span of the node in the generated source code for a source mapping.
466466+ CursorPositionObserver {
467467+ observer: Rc<RefCell<dyn CursorPositionObserver>>,
468468+ },
469469+ Empty,
470470+}
471471+472472+impl<'a, 'doc> PrintableDocument<'a, 'doc> {
473473+ fn str(str: &'a str) -> Self {
474474+ PrintableDocument::Str {
475475+ graphemes: str.graphemes(true).count() as isize,
476476+ string: str,
477477+ }
478478+ }
479479+}
480480+481481+/// The kind of line break this `Document::Break` is.
482482+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
483483+pub enum BreakKind {
484484+ /// A `flex_break`.
485485+ Flex,
486486+ /// A `break_`.
487487+ Strict,
488488+}
489489+490490+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
491491+enum Mode {
492492+ /// The mode used when a group doesn't fit on a single line: when `Broken`
493493+ /// the `Break`s inside it will be rendered as newlines, splitting the
494494+ /// group.
495495+ Broken,
496496+497497+ /// The default mode used when a group can fit on a single line: all its
498498+ /// `Break`s will be rendered as their unbroken string and kept on a single
499499+ /// line.
500500+ Unbroken,
501501+502502+ /// This mode is used by the `NextBreakFit` document to force a break to be
503503+ /// considered as broken.
504504+ ForcedBroken,
505505+506506+ /// This mode is used to disable a `NextBreakFit` document.
507507+ ForcedUnbroken,
508508+}
509509+510510+/// A flag that can be used to enable or disable a `NextBreakFit` document.
511511+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
512512+pub enum NextBreakFitsMode {
513513+ Enabled,
514514+ Disabled,
515515+}
516516+517517+/// A flag that can be used to conditionally disable a `Nest` document.
518518+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
519519+pub enum NestCondition {
520520+ /// This always applies the nesting. This is a sensible default that will
521521+ /// work for most of the cases.
522522+ Always,
523523+ /// Only applies the nesting if the wrapping `Group` couldn't fit on a
524524+ /// single line and has been broken.
525525+ IfBroken,
526526+}
527527+528528+/// Used to change the way nesting of documents work.
529529+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
530530+pub enum NestMode {
531531+ /// If the nesting mode is `Increase`, the current indentation will be
532532+ /// increased by the specified value.
533533+ Increase,
534534+ /// If the nesting mode is `Set`, the current indentation is going to be set
535535+ /// to exactly the specified value.
536536+ ///
537537+ /// `doc.nest(2).set_nesting(0)`
538538+ /// "wibble
539539+ /// wobble <- no indentation is added!
540540+ /// wubble"
541541+ Set,
542542+}
543543+544544+/// A structure used to allocate and manipulate documents.
545545+/// All documents are allocated on an arena.
546546+/// Some documents are only allocated once and can be used accessed as fields
547547+/// of this structure to avoid wasting loads of allocations on them.
548548+pub struct DocumentArena<'string, 'doc> {
549549+ documents: Arena<PrintableDocument<'string, 'doc>>,
550550+}
551551+552552+impl<'string, 'doc> std::fmt::Debug for DocumentArena<'string, 'doc> {
553553+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
554554+ f.debug_struct("DocumentArena").finish()
555555+ }
556556+}
557557+558558+impl<'string, 'doc> DocumentArena<'string, 'doc> {
559559+ pub fn new() -> Self {
560560+ let documents = Arena::new();
561561+ Self { documents }
562562+ }
563563+564564+ pub fn nil(&'doc self) -> Document<'string, 'doc> {
565565+ Document(self.documents.alloc(PrintableDocument::Empty))
566566+ }
567567+568568+ pub fn line(&'doc self) -> Document<'string, 'doc> {
569569+ self.lines(1)
570570+ }
571571+572572+ pub fn position_observer(
573573+ &'doc self,
574574+ observer: Rc<RefCell<dyn CursorPositionObserver>>,
575575+ ) -> Document<'string, 'doc> {
576576+ Document(
577577+ self.documents
578578+ .alloc(PrintableDocument::CursorPositionObserver { observer }),
579579+ )
580580+ }
581581+582582+ /// Renders a string of newlines, equal in length to the number provided.
583583+ #[inline]
584584+ pub fn lines(&'doc self, i: usize) -> Document<'string, 'doc> {
585585+ Document(self.documents.alloc(PrintableDocument::Line(i)))
586586+ }
587587+588588+ /// A document after which the formatter can insert a newline. This determines
589589+ /// where line breaks can occur, outside of hardcoded `Line`s.
590590+ ///
591591+ /// If the formatter determines that a group cannot fit on a single line,
592592+ /// all breaks in the group will be rendered as broken. Otherwise, they
593593+ /// will be rendered as unbroken.
594594+ ///
595595+ /// A broken `Break` renders the `broken` string, followed by a newline.
596596+ /// An unbroken `Break` renders the `unbroken` string by itself.
597597+ ///
598598+ /// For example:
599599+ /// ```rust:norun
600600+ /// let document = docvec!["Hello", break_("", ", "), "world!"];
601601+ /// assert_eq!(document.to_pretty_string(20), "Hello, world!");
602602+ /// assert_eq!(document.to_pretty_string(10), "Hello\nworld!");
603603+ /// ```
604604+ ///
605605+ pub fn break_(
606606+ &'doc self,
607607+ broken: &'string str,
608608+ unbroken: &'string str,
609609+ ) -> Document<'string, 'doc> {
610610+ Document(self.documents.alloc(PrintableDocument::Break {
611611+ broken,
612612+ unbroken,
613613+ kind: BreakKind::Strict,
614614+ }))
615615+ }
616616+617617+ /// A document after which the formatter can insert a newline, similar to
618618+ /// `break_()`. The difference is that when a group is rendered broken, all
619619+ /// breaks are rendered broken. However, `flex_break` decides whether to
620620+ /// break or not for every individual `flex_break`.
621621+ ///
622622+ /// For example:
623623+ /// ```rust:norun
624624+ /// let with_breaks = docvec!["Hello", break_("", ", "), "pretty", break_("", ", "), "printed!"];
625625+ /// assert_eq!(with_breaks.to_pretty_string(20), "Hello\npretty\nprinted!");
626626+ ///
627627+ /// let with_flex_breaks = docvec!["Hello", flex_break("", ", "), "pretty", flex_break("", ", "), "printed!"];
628628+ /// assert_eq!(with_flex_breaks.to_pretty_string(20), "Hello, pretty\nprinted!");
629629+ /// ```
630630+ ///
631631+ pub fn flex_break(
632632+ &'doc self,
633633+ broken: &'string str,
634634+ unbroken: &'string str,
635635+ ) -> Document<'string, 'doc> {
636636+ Document(self.documents.alloc(PrintableDocument::Break {
637637+ broken,
638638+ unbroken,
639639+ kind: BreakKind::Flex,
640640+ }))
641641+ }
642642+643643+ /// A string that is not taken into account when determining line length.
644644+ /// This is useful for additional formatting text which won't be rendered
645645+ /// in the final output, such as ANSI codes or HTML elements.
646646+ ///
647647+ /// For example:
648648+ /// ```rust:norun
649649+ /// let document = docvec!["Hello", zero_width_string("This is a very long string"), break_("", ""), "world"];
650650+ /// assert_eq!(document.to_pretty_string(20), "HelloThis is a very long stringworld");
651651+ /// ```
652652+ ///
653653+ pub fn zero_width_string(&'doc self, string: EcoString) -> Document<'string, 'doc> {
654654+ Document(
655655+ self.documents
656656+ .alloc(PrintableDocument::ZeroWidthString { string }),
657657+ )
658658+ }
659659+660660+ pub fn concat(
661661+ &'doc self,
662662+ documents: impl IntoIterator<Item = Document<'string, 'doc>>,
663663+ ) -> Document<'string, 'doc> {
664664+ let mut documents = documents.into_iter().peekable();
665665+ let Some(mut previous) = documents.next() else {
666666+ return self.nil();
667667+ };
668668+669669+ while let Some(next) = documents.next() {
670670+ previous = Document(
671671+ self.documents
672672+ .alloc(PrintableDocument::Join(previous, next)),
673673+ );
674674+ }
675675+676676+ previous
677677+ }
678678+679679+ /// Joins an iterator into a single document, interspersing each element with
680680+ /// another document. This is useful for example in argument lists, where a
681681+ /// list of arguments must all be separated with a comma.
682682+ pub fn join(
683683+ &'doc self,
684684+ elements: impl IntoIterator<Item = Document<'string, 'doc>>,
685685+ separator: Document<'string, 'doc>,
686686+ ) -> Document<'string, 'doc> {
687687+ let mut elements = elements.into_iter().peekable();
688688+ let Some(mut previous) = elements.next() else {
689689+ return self.nil();
690690+ };
691691+692692+ while let Some(next) = elements.next() {
693693+ let doc = Document(
694694+ self.documents
695695+ .alloc(PrintableDocument::Join(previous, separator)),
696696+ );
697697+ previous = Document(self.documents.alloc(PrintableDocument::Join(doc, next)));
698698+ }
699699+700700+ previous
701701+ }
702702+}
703703+704704+fn format<'string, 'doc>(
705705+ writer: &mut impl Utf8Writer,
706706+ limit: isize,
707707+ mut docs: im::Vector<(isize, Mode, Document<'string, 'doc>)>,
708708+) -> Result<()> {
709709+ let mut line: isize = 0;
710710+ let mut width: isize = 0;
711711+ // As long as there are documents to print we'll take each one by one and
712712+ // output the corresponding string to the given writer.
713713+ //
714714+ // Each document in the `docs` queue also has an accompanying indentation
715715+ // and mode:
716716+ // - the indentation is used to keep track of the current indentation,
717717+ // you might notice in [ref:format-nest] that it adds documents to the
718718+ // queue increasing their current indentation.
719719+ // - the mode is used to keep track of the state of the documents inside a
720720+ // group. For example, if a group doesn't fit on a single line its
721721+ // documents will be split into multiple lines and the mode set to
722722+ // `Broken` to keep track of this.
723723+ while let Some((indent, mode, document)) = docs.pop_front() {
724724+ match document.0 {
725725+ // When we run into a line we print the given number of newlines and
726726+ // add the indentation required by the given document.
727727+ PrintableDocument::Line(count) => {
728728+ for _ in 0..*count {
729729+ writer.str_write("\n")?;
730730+ }
731731+ line += *count as isize;
732732+ for _ in 0..indent {
733733+ writer.str_write(" ")?;
734734+ }
735735+ width = indent;
736736+ }
737737+738738+ // Flex breaks are NOT conditional to the mode: if the mode is
739739+ // already `Unbroken`, then the break is left unbroken (like strict
740740+ // breaks); any other mode is ignored.
741741+ // A flexible break will only be split if the following documents
742742+ // can't fit on the same line; otherwise, it is just displayed as an
743743+ // unbroken `Break`.
744744+ PrintableDocument::Break {
745745+ broken,
746746+ unbroken,
747747+ kind: BreakKind::Flex,
748748+ } => {
749749+ let unbroken_width = width + unbroken.len() as isize;
750750+ // Every time we need to check again if the remaining piece can
751751+ // fit. If it does, the flexible break is not broken.
752752+ if mode == Mode::Unbroken || fits(limit, unbroken_width, docs.clone()) {
753753+ writer.str_write(unbroken)?;
754754+ width = unbroken_width;
755755+ } else {
756756+ writer.str_write(broken)?;
757757+ writer.str_write("\n")?;
758758+ line += 1;
759759+ for _ in 0..indent {
760760+ writer.str_write(" ")?;
761761+ }
762762+ width = indent;
763763+ }
764764+ }
765765+766766+ // Strict breaks are conditional to the mode. They differ from
767767+ // flexible break because, if a group gets split - that is the mode
768768+ // is `Broken` or `ForceBroken` - ALL of the breaks in that group
769769+ // will be split. You can notice the difference with flexible breaks
770770+ // because here we only check the mode and then take action; before
771771+ // we would try and see if the remaining documents fit on a single
772772+ // line before deciding if the (flexible) break can be split or not.
773773+ PrintableDocument::Break {
774774+ broken,
775775+ unbroken,
776776+ kind: BreakKind::Strict,
777777+ } => match mode {
778778+ // If the mode requires the break to be broken, then its broken
779779+ // string is printed, then we start a newline and indent it
780780+ // according to the current indentation level.
781781+ Mode::Broken | Mode::ForcedBroken => {
782782+ writer.str_write(broken)?;
783783+ writer.str_write("\n")?;
784784+ line += 1;
785785+ for _ in 0..indent {
786786+ writer.str_write(" ")?;
787787+ }
788788+ width = indent;
789789+ }
790790+ // If the mode doesn't require the break to be broken, then its
791791+ // unbroken string is printed as if it were a normal string;
792792+ // also updating the width of the current line.
793793+ Mode::Unbroken | Mode::ForcedUnbroken => {
794794+ writer.str_write(unbroken)?;
795795+ width += unbroken.len() as isize
796796+ }
797797+ },
798798+799799+ // Strings are printed as they are and the current width is
800800+ // increased accordingly.
801801+ PrintableDocument::EcoString { string, graphemes } => {
802802+ width += graphemes;
803803+ writer.str_write(&string)?;
804804+ }
805805+806806+ PrintableDocument::Str { string, graphemes } => {
807807+ width += graphemes;
808808+ writer.str_write(string)?;
809809+ }
810810+811811+ PrintableDocument::ZeroWidthString { string } => {
812812+ // We write the string, but do not increment the length
813813+ writer.str_write(&string)?;
814814+ }
815815+816816+ // If multiple documents need to be printed, then they are all
817817+ // pushed to the front of the queue and will be printed one by one.
818818+ PrintableDocument::Join(first, second) => {
819819+ // Just like `fits`, the elements will be pushed _on the front_
820820+ // of the queue. In order to keep their original order they need
821821+ // to be pushed in reverse order.
822822+ docs.push_front((indent, mode, *second));
823823+ docs.push_front((indent, mode, *first));
824824+ }
825825+826826+ // A `Nest` document doesn't result in anything being printed, its
827827+ // only effect is to increase the current nesting level for the
828828+ // wrapped document [tag:format-nest].
829829+ PrintableDocument::Nest(i, nest_mode, condition, doc) => match (condition, mode) {
830830+ // The nesting is only applied under two conditions:
831831+ // - either the nesting condition is `Always`.
832832+ // - or the condition is `IfBroken` and the group was actually
833833+ // broken (that is, the current mode is `Broken`).
834834+ (NestCondition::Always, _) | (NestCondition::IfBroken, Mode::Broken) => {
835835+ let new_indent = match nest_mode {
836836+ NestMode::Increase => indent + i,
837837+ NestMode::Set => *i,
838838+ };
839839+ docs.push_front((new_indent, mode, *doc))
840840+ }
841841+ // If none of the above conditions is met, then the nesting is
842842+ // not applied.
843843+ _ => docs.push_front((indent, mode, *doc)),
844844+ },
845845+846846+ PrintableDocument::Group(doc) => {
847847+ // When we see a group we first try and see if it can fit on a
848848+ // single line without breaking any break; that is why we use
849849+ // the `Unbroken` mode here: we want to try to fit everything on
850850+ // a single line.
851851+ let group_docs = im::vector![(indent, Mode::Unbroken, *doc)];
852852+ if fits(limit, width, group_docs) {
853853+ // If everything can stay on a single line we print the
854854+ // wrapped document with the `Unbroken` mode, leaving all
855855+ // the group's break as unbroken.
856856+ docs.push_front((indent, Mode::Unbroken, *doc));
857857+ } else {
858858+ // Otherwise, we need to break the group. We print the
859859+ // wrapped document changing its mode to `Broken` so that
860860+ // all its breaks will be split on newlines.
861861+ docs.push_front((indent, Mode::Broken, *doc));
862862+ }
863863+ }
864864+865865+ // `ForceBroken` and `NextBreakFits` only change the way the `fit`
866866+ // function works but do not actually change the formatting of a
867867+ // document by themselves. That's why when we run into those we
868868+ // just go on printing the wrapped document without altering the
869869+ // current mode.
870870+ PrintableDocument::ForceBroken(document)
871871+ | PrintableDocument::NextBreakFits(document, _) => {
872872+ docs.push_front((indent, mode, *document));
873873+ }
874874+875875+ PrintableDocument::CursorPositionObserver { observer } => {
876876+ // Notify the observer of the current cursor position
877877+ observer.borrow_mut().observe_cursor_position(line, width);
878878+ }
879879+880880+ PrintableDocument::Empty => (),
881881+ }
882882+ }
883883+ Ok(())
884884+}
885885+886886+fn fits<'string, 'doc>(
887887+ limit: isize,
888888+ mut current_width: isize,
889889+ mut docs: im::Vector<(isize, Mode, Document<'string, 'doc>)>,
890890+) -> bool {
891891+ // The `fits` function is going to take each document from the `docs` queue
892892+ // and check if those can fit on a single line. In order to do so documents
893893+ // are going to be pushed in front of this queue and have to be accompanied
894894+ // by additional information:
895895+ // - the document indentation, that can be increased by the `Nest` block.
896896+ // - the current mode; this is needed to know if a group is being broken or
897897+ // not and treat `Break`s differently as a consequence. You can see how
898898+ // the behaviour changes in [ref:break-fit].
899899+ //
900900+ // The loop might be broken earlier without checking all documents under one
901901+ // of two conditions:
902902+ // - the documents exceed the line `limit` and surely won't fit
903903+ // [ref:document-unfit].
904904+ // - the documents are sure to fit the line - for example, if we meet a
905905+ // broken `Break` [ref:break-fit] or a newline [ref:newline-fit].
906906+ loop {
907907+ // [tag:document-unfit] If we've exceeded the maximum width allowed for
908908+ // a line, it means that the document won't fit on a single line, we can
909909+ // break the loop.
910910+ if current_width > limit {
911911+ return false;
912912+ };
913913+914914+ // We start by checking the first document of the queue. If there's no
915915+ // documents then we can safely say that it fits (if reached this point
916916+ // it means that the limit wasn't exceeded).
917917+ let (indent, mode, document) = match docs.pop_front() {
918918+ Some(x) => x,
919919+ None => return true,
920920+ };
921921+922922+ match document.0 {
923923+ // If a document is marked as `ForceBroken` we can immediately say
924924+ // that it doesn't fit, so that every break is going to be
925925+ // forcefully broken.
926926+ PrintableDocument::ForceBroken(doc) => match mode {
927927+ // If the mode is `ForcedBroken` it means that we have to ignore
928928+ // this break [ref:forced-broken], so we go check the inner
929929+ // document ignoring the effects of this one.
930930+ Mode::ForcedBroken => docs.push_front((indent, mode, *doc)),
931931+ Mode::Broken | Mode::Unbroken | Mode::ForcedUnbroken => return false,
932932+ },
933933+934934+ // [tag:newline-fit] When we run into a line we know that the
935935+ // document has a bit that fits in the current line; if it didn't
936936+ // fit (that is, it exceeded the maximum allowed width) the loop
937937+ // would have been broken by one of the earlier checks.
938938+ PrintableDocument::Line(_) => return true,
939939+940940+ // If the nesting level is increased we go on checking the wrapped
941941+ // document and increase its indentation level based on the nesting
942942+ // condition.
943943+ PrintableDocument::Nest(i, nest_mode, condition, doc) => match condition {
944944+ NestCondition::IfBroken => docs.push_front((indent, mode, *doc)),
945945+ NestCondition::Always => {
946946+ let new_indent = match nest_mode {
947947+ NestMode::Increase => indent + i,
948948+ NestMode::Set => *i,
949949+ };
950950+ docs.push_front((new_indent, mode, *doc))
951951+ }
952952+ },
953953+954954+ // As a general rule, a group fits if it can stay on a single line
955955+ // without its breaks being broken down.
956956+ PrintableDocument::Group(doc) => match mode {
957957+ // If an outer group was broken, we still try to fit the inner
958958+ // group on a single line, that's why for the inner document
959959+ // we change the mode back to `Unbroken`.
960960+ Mode::Broken => docs.push_front((indent, Mode::Unbroken, *doc)),
961961+ // Any other mode is preserved as-is: if the mode is forced it
962962+ // has to be left unchanged, and if the mode is already unbroken
963963+ // there's no need to change it.
964964+ Mode::Unbroken | Mode::ForcedBroken | Mode::ForcedUnbroken => {
965965+ docs.push_front((indent, mode, *doc))
966966+ }
967967+ },
968968+969969+ // When we run into a string we increase the current_width; looping
970970+ // back we will check if we've exceeded the maximum allowed width.
971971+ PrintableDocument::Str { graphemes, .. }
972972+ | PrintableDocument::EcoString { graphemes, .. } => current_width += graphemes,
973973+974974+ // Zero width strings do nothing: they do not contribute to line length
975975+ PrintableDocument::ZeroWidthString { .. }
976976+ | PrintableDocument::CursorPositionObserver { .. } => {}
977977+978978+ // If we get to a break we need to first see if it has to be
979979+ // rendered as its unbroken or broken string, depending on the mode.
980980+ PrintableDocument::Break { unbroken, .. } => match mode {
981981+ // [tag:break-fit] If the break has to be broken we're done!
982982+ // We haven't exceeded the maximum length (otherwise the loop
983983+ // iteration would have stopped with one of the earlier checks),
984984+ // and - since it needs to be broken - we'll have to go on a new
985985+ // line anyway.
986986+ // This means that the document inspected so far will fit on a
987987+ // single line, thus we return true.
988988+ Mode::Broken | Mode::ForcedBroken => return true,
989989+ // If the break is not broken then it will be rendered inline as
990990+ // its unbroken string, so we treat it exactly as if it were a
991991+ // normal string.
992992+ Mode::Unbroken | Mode::ForcedUnbroken => current_width += unbroken.len() as isize,
993993+ },
994994+995995+ // The `NextBreakFits` can alter the current mode to `ForcedBroken`
996996+ // or `ForcedUnbroken` based on its enabled flag.
997997+ PrintableDocument::NextBreakFits(doc, enabled) => match enabled {
998998+ // [tag:disable-next-break] If it is disabled then we check the
999999+ // wrapped document changing the mode to `ForcedUnbroken`.
10001000+ NextBreakFitsMode::Disabled => {
10011001+ docs.push_front((indent, Mode::ForcedUnbroken, *doc))
10021002+ }
10031003+ NextBreakFitsMode::Enabled => match mode {
10041004+ // If we're in `ForcedUnbroken` mode it means that the check
10051005+ // was disabled by a document wrapping this one
10061006+ // [ref:disable-next-break]; that's why we do nothing and
10071007+ // check the wrapped document as if it were a normal one.
10081008+ Mode::ForcedUnbroken => docs.push_front((indent, mode, *doc)),
10091009+ // [tag:forced-broken] Any other mode is turned into
10101010+ // `ForcedBroken` so that when we run into a break, the
10111011+ // response to the question "Does the document fit?" will be
10121012+ // yes [ref:break-fit].
10131013+ // This is why this is called `NextBreakFit` I think.
10141014+ Mode::Broken | Mode::Unbroken | Mode::ForcedBroken => {
10151015+ docs.push_front((indent, Mode::ForcedBroken, *doc))
10161016+ }
10171017+ },
10181018+ },
10191019+10201020+ // If there's a sequence of documents we will check each one, one
10211021+ // after the other to see if - as a whole - they can fit on a single
10221022+ // line.
10231023+ PrintableDocument::Join(first, second) => {
10241024+ // The array needs to be reversed to preserve the order of the
10251025+ // documents since each one is pushed _to the front_ of the
10261026+ // queue of documents to check.
10271027+ docs.push_front((indent, mode, *second));
10281028+ docs.push_front((indent, mode, *first));
10291029+ }
10301030+10311031+ PrintableDocument::Empty => (),
10321032+ }
10331033+ }
10341034+}