Fork of daniellemaywood.uk/gleam — Wasm codegen work
37 kB
919 lines
1//! This module implements the functionality described in
2//! ["Strictly Pretty" (2000) by Christian Lindig][0], with a few
3//! extensions.
4//!
5//! This module is heavily influenced by Elixir's Inspect.Algebra and
6//! JavaScript's Prettier.
7//!
8//! [0]: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34.2200
9//!
10//! ## Extensions
11//!
12//! - `ForcedBreak` from Elixir.
13//! - `FlexBreak` from Elixir.
14//!
15//! The way this module works is fairly simple conceptually, however the actual
16//! behaviour in practice can be hard to wrap one's head around.
17//!
18//! The basic premise is the `Document` type, which is a tree structure,
19//! containing some text as well as information on how it can be formatted.
20//! Once the document is constructed, it can be printed using the
21//! `to_pretty_string` function.
22//!
23//! It will then traverse the tree, and construct
24//! a string, attempting to wrap lines to that they do not exceed the line length
25//! limit specified. Where and when it wraps lines is determined by the structure
26//! of the `Document` itself.
27//!
28#![allow(clippy::wrong_self_convention)]
29
30#[cfg(test)]
31mod tests;
32
33use std::{cell::RefCell, rc::Rc};
34
35use ecow::{EcoString, eco_format};
36use itertools::Itertools;
37use num_bigint::BigInt;
38use unicode_segmentation::UnicodeSegmentation;
39
40use crate::{Result, io::Utf8Writer};
41
42/// Join multiple documents together in a vector. This macro calls the `to_doc`
43/// method on each element, providing a concise way to write a document sequence.
44/// For example:
45///
46/// ```rust:norun
47/// docvec!["Hello", line(), "world!"]
48/// ```
49///
50/// Note: each document in a docvec is not separated in any way: the formatter
51/// will never break a line unless a `Document::Break` or `Document::Line`
52/// is used. Therefore, `docvec!["a", "b", "c"]` is equivalent to
53/// `"abc".to_doc()`.
54///
55#[macro_export]
56macro_rules! docvec {
57 () => {
58 Document::Vec(Vec::new())
59 };
60
61 // A docvec![] with a single element
62 ($first:expr $(,)?) => {
63 Document::Vec(vec![$first.to_doc()])
64 };
65
66 // A docvec![] with multiple elements.
67 ($first:expr, $($rest:expr),+ $(,)?) => {
68 // A document that looks like this: `Vec[Vec[..rest], ..other_rest]`
69 // is exactly the same as a flat: `Vec[..rest, ..other_rest]`.
70 // So in case a `docvec!` starts with a `Vec` we flatten it out to avoid
71 // having deeply nested documents.
72 match $first.to_doc() {
73 Document::Vec(mut vec) => {
74 $(
75 vec.push($rest.to_doc());
76 )*
77 Document::Vec(vec)
78 },
79 first => Document::Vec(vec![first, $($rest.to_doc()),+])
80 }
81 };
82}
83
84/// Coerce a value into a Document.
85/// Note we do not implement this for String as a slight pressure to favour str
86/// over String.
87pub trait Documentable<'a> {
88 fn to_doc(self) -> Document<'a>;
89}
90
91impl<'a> Documentable<'a> for char {
92 fn to_doc(self) -> Document<'a> {
93 Document::eco_string(eco_format!("{self}"))
94 }
95}
96
97impl<'a> Documentable<'a> for &'a str {
98 fn to_doc(self) -> Document<'a> {
99 Document::str(self)
100 }
101}
102
103impl<'a> Documentable<'a> for EcoString {
104 fn to_doc(self) -> Document<'a> {
105 Document::eco_string(self)
106 }
107}
108
109impl<'a> Documentable<'a> for &EcoString {
110 fn to_doc(self) -> Document<'a> {
111 Document::eco_string(self.clone())
112 }
113}
114
115impl<'a> Documentable<'a> for isize {
116 fn to_doc(self) -> Document<'a> {
117 Document::eco_string(eco_format!("{self}"))
118 }
119}
120
121impl<'a> Documentable<'a> for i64 {
122 fn to_doc(self) -> Document<'a> {
123 Document::eco_string(eco_format!("{self}"))
124 }
125}
126
127impl<'a> Documentable<'a> for usize {
128 fn to_doc(self) -> Document<'a> {
129 Document::eco_string(eco_format!("{self}"))
130 }
131}
132
133impl<'a> Documentable<'a> for f64 {
134 fn to_doc(self) -> Document<'a> {
135 Document::eco_string(eco_format!("{self:?}"))
136 }
137}
138
139impl<'a> Documentable<'a> for u64 {
140 fn to_doc(self) -> Document<'a> {
141 Document::eco_string(eco_format!("{self:?}"))
142 }
143}
144
145impl<'a> Documentable<'a> for u32 {
146 fn to_doc(self) -> Document<'a> {
147 Document::eco_string(eco_format!("{self}"))
148 }
149}
150
151impl<'a> Documentable<'a> for u16 {
152 fn to_doc(self) -> Document<'a> {
153 Document::eco_string(eco_format!("{self}"))
154 }
155}
156
157impl<'a> Documentable<'a> for u8 {
158 fn to_doc(self) -> Document<'a> {
159 Document::eco_string(eco_format!("{self}"))
160 }
161}
162
163impl<'a> Documentable<'a> for BigInt {
164 fn to_doc(self) -> Document<'a> {
165 Document::eco_string(eco_format!("{self}"))
166 }
167}
168
169impl<'a> Documentable<'a> for Document<'a> {
170 fn to_doc(self) -> Document<'a> {
171 self
172 }
173}
174
175impl<'a> Documentable<'a> for Vec<Document<'a>> {
176 fn to_doc(self) -> Document<'a> {
177 Document::Vec(self)
178 }
179}
180
181impl<'a, D: Documentable<'a>> Documentable<'a> for Option<D> {
182 fn to_doc(self) -> Document<'a> {
183 self.map(Documentable::to_doc).unwrap_or_else(nil)
184 }
185}
186
187/// Joins an iterator into a single document, in the same way as `docvec!`.
188pub fn concat<'a>(docs: impl IntoIterator<Item = Document<'a>>) -> Document<'a> {
189 Document::Vec(docs.into_iter().collect())
190}
191
192/// Joins an iterator into a single document, interspersing each element with
193/// another document. This is useful for example in argument lists, where a
194/// list of arguments must all be separated with a comma.
195pub fn join<'a>(
196 docs: impl IntoIterator<Item = Document<'a>>,
197 separator: Document<'a>,
198) -> Document<'a> {
199 concat(Itertools::intersperse(docs.into_iter(), separator))
200}
201
202/// A trait that allows for objects to observe the cursor position as it is being formatted.
203/// This is useful for any operations that need to track the exact position a document is
204/// being written to in a buffer such as for source mapping.
205pub trait CursorPositionObserver: std::fmt::Debug {
206 fn observe_cursor_position(&mut self, line: isize, width: isize);
207}
208
209/// A pretty printable document. A tree structure, made up of text and other
210/// elements which determine how it can be formatted.
211///
212/// The variants of this enum should probably not be constructed directly,
213/// rather use the helper functions of the same names to construct them.
214/// For example, use `line()` instead of `Document::Line(1)`.
215///
216#[derive(Debug, Clone)]
217pub enum Document<'a> {
218 /// A mandatory linebreak. This is always printed as a string of newlines,
219 /// equal in length to the number specified.
220 Line(usize),
221
222 /// Forces the breaks of the wrapped document to be considered as not
223 /// fitting on a single line. Used in combination with a `Group` it can be
224 /// used to force its `Break`s to always break.
225 ForceBroken(Box<Self>),
226
227 /// Ignore the next break, forcing it to render as unbroken.
228 NextBreakFits(Box<Self>, NextBreakFitsMode),
229
230 /// A document after which the formatter can insert a newline. This determines
231 /// where line breaks can occur, outside of hardcoded `Line`s.
232 /// See `break_` and `flex_break` for usage.
233 Break {
234 broken: &'a str,
235 unbroken: &'a str,
236 kind: BreakKind,
237 },
238
239 /// Join multiple documents together. The documents are not separated in any
240 /// way: the formatter will only print newlines if `Document::Break` or
241 /// `Document::Line` is used.
242 Vec(Vec<Self>),
243
244 /// Nests the given document by the given indent, depending on the specified
245 /// condition. See `Document::nest`, `Document::set_nesting` and
246 /// `Document::nest_if_broken` for usages.
247 Nest(isize, NestMode, NestCondition, Box<Self>),
248
249 /// Groups a document. When pretty printing a group, the formatter will
250 /// first attempt to fit the entire group on one line. If it fails, all
251 /// `break_` documents in the group will render broken.
252 ///
253 /// Nested groups are handled separately to their parents, so if the
254 /// outermost group is broken, any sub-groups might be rendered broken
255 /// or unbroken, depending on whether they fit on a single line.
256 Group(Box<Self>),
257
258 /// Renders a string slice. This will always render the string verbatim,
259 /// without any line breaks or other modifications to it.
260 Str {
261 string: &'a str,
262 /// The number of extended grapheme clusters in the string.
263 /// This is what the pretty printer uses as the width of the string as it
264 /// is closes to what a human would consider the "length" of a string.
265 ///
266 /// Since computing the number of grapheme clusters requires walking over
267 /// the string we precompute it to avoid iterating through a string over
268 /// and over again in the pretty printing algorithm.
269 ///
270 graphemes: isize,
271 },
272
273 /// Renders an `EcoString`. This will always render the string verbatim,
274 /// without any line breaks or other modifications to it.
275 EcoString {
276 string: EcoString,
277 /// The number of extended grapheme clusters in the string.
278 /// This is what the pretty printer uses as the width of the string as it
279 /// is closes to what a human would consider the "length" of a string.
280 ///
281 /// Since computing the number of grapheme clusters requires walking over
282 /// the string we precompute it to avoid iterating through a string over
283 /// and over again in the pretty printing algorithm.
284 ///
285 graphemes: isize,
286 },
287
288 /// A string that is not taken into account when determining line length.
289 /// This is useful for additional formatting text which won't be rendered
290 /// in the final output, such as ANSI codes or HTML elements.
291 ZeroWidthString { string: EcoString },
292
293 /// A node that gets notified of the cursor position as it is being formatted.
294 /// This allows for processes outside of the final output to be notified of
295 /// the cursor position and perform actions based on it, such as recording
296 /// the span of the node in the generated source code for a source mapping.
297 CursorPositionObserver {
298 observer: Rc<RefCell<dyn CursorPositionObserver>>,
299 },
300}
301
302#[derive(Debug, Clone, Copy, PartialEq, Eq)]
303enum Mode {
304 /// The mode used when a group doesn't fit on a single line: when `Broken`
305 /// the `Break`s inside it will be rendered as newlines, splitting the
306 /// group.
307 Broken,
308
309 /// The default mode used when a group can fit on a single line: all its
310 /// `Break`s will be rendered as their unbroken string and kept on a single
311 /// line.
312 Unbroken,
313
314 /// This mode is used by the `NextBreakFit` document to force a break to be
315 /// considered as broken.
316 ForcedBroken,
317
318 /// This mode is used to disable a `NextBreakFit` document.
319 ForcedUnbroken,
320}
321
322/// A flag that can be used to enable or disable a `NextBreakFit` document.
323#[derive(Debug, Clone, Copy, PartialEq, Eq)]
324pub enum NextBreakFitsMode {
325 Enabled,
326 Disabled,
327}
328
329/// A flag that can be used to conditionally disable a `Nest` document.
330#[derive(Debug, Clone, Copy, PartialEq, Eq)]
331pub enum NestCondition {
332 /// This always applies the nesting. This is a sensible default that will
333 /// work for most of the cases.
334 Always,
335 /// Only applies the nesting if the wrapping `Group` couldn't fit on a
336 /// single line and has been broken.
337 IfBroken,
338}
339
340/// Used to change the way nesting of documents work.
341#[derive(Debug, Clone, Copy, PartialEq, Eq)]
342pub enum NestMode {
343 /// If the nesting mode is `Increase`, the current indentation will be
344 /// increased by the specified value.
345 Increase,
346 /// If the nesting mode is `Set`, the current indentation is going to be set
347 /// to exactly the specified value.
348 ///
349 /// `doc.nest(2).set_nesting(0)`
350 /// "wibble
351 /// wobble <- no indentation is added!
352 /// wubble"
353 Set,
354}
355
356fn fits(
357 limit: isize,
358 mut current_width: isize,
359 mut docs: im::Vector<(isize, Mode, &Document<'_>)>,
360) -> bool {
361 // The `fits` function is going to take each document from the `docs` queue
362 // and check if those can fit on a single line. In order to do so documents
363 // are going to be pushed in front of this queue and have to be accompanied
364 // by additional information:
365 // - the document indentation, that can be increased by the `Nest` block.
366 // - the current mode; this is needed to know if a group is being broken or
367 // not and treat `Break`s differently as a consequence. You can see how
368 // the behaviour changes in [ref:break-fit].
369 //
370 // The loop might be broken earlier without checking all documents under one
371 // of two conditions:
372 // - the documents exceed the line `limit` and surely won't fit
373 // [ref:document-unfit].
374 // - the documents are sure to fit the line - for example, if we meet a
375 // broken `Break` [ref:break-fit] or a newline [ref:newline-fit].
376 loop {
377 // [tag:document-unfit] If we've exceeded the maximum width allowed for
378 // a line, it means that the document won't fit on a single line, we can
379 // break the loop.
380 if current_width > limit {
381 return false;
382 };
383
384 // We start by checking the first document of the queue. If there's no
385 // documents then we can safely say that it fits (if reached this point
386 // it means that the limit wasn't exceeded).
387 let (indent, mode, document) = match docs.pop_front() {
388 Some(x) => x,
389 None => return true,
390 };
391
392 match document {
393 // If a document is marked as `ForceBroken` we can immediately say
394 // that it doesn't fit, so that every break is going to be
395 // forcefully broken.
396 Document::ForceBroken(doc) => match mode {
397 // If the mode is `ForcedBroken` it means that we have to ignore
398 // this break [ref:forced-broken], so we go check the inner
399 // document ignoring the effects of this one.
400 Mode::ForcedBroken => docs.push_front((indent, mode, doc)),
401 Mode::Broken | Mode::Unbroken | Mode::ForcedUnbroken => return false,
402 },
403
404 // [tag:newline-fit] When we run into a line we know that the
405 // document has a bit that fits in the current line; if it didn't
406 // fit (that is, it exceeded the maximum allowed width) the loop
407 // would have been broken by one of the earlier checks.
408 Document::Line(_) => return true,
409
410 // If the nesting level is increased we go on checking the wrapped
411 // document and increase its indentation level based on the nesting
412 // condition.
413 Document::Nest(i, nest_mode, condition, doc) => match condition {
414 NestCondition::IfBroken => docs.push_front((indent, mode, doc)),
415 NestCondition::Always => {
416 let new_indent = match nest_mode {
417 NestMode::Increase => indent + i,
418 NestMode::Set => *i,
419 };
420 docs.push_front((new_indent, mode, doc))
421 }
422 },
423
424 // As a general rule, a group fits if it can stay on a single line
425 // without its breaks being broken down.
426 Document::Group(doc) => match mode {
427 // If an outer group was broken, we still try to fit the inner
428 // group on a single line, that's why for the inner document
429 // we change the mode back to `Unbroken`.
430 Mode::Broken => docs.push_front((indent, Mode::Unbroken, doc)),
431 // Any other mode is preserved as-is: if the mode is forced it
432 // has to be left unchanged, and if the mode is already unbroken
433 // there's no need to change it.
434 Mode::Unbroken | Mode::ForcedBroken | Mode::ForcedUnbroken => {
435 docs.push_front((indent, mode, doc))
436 }
437 },
438
439 // When we run into a string we increase the current_width; looping
440 // back we will check if we've exceeded the maximum allowed width.
441 Document::Str { graphemes, .. } | Document::EcoString { graphemes, .. } => {
442 current_width += graphemes
443 }
444
445 // Zero width strings do nothing: they do not contribute to line length
446 Document::ZeroWidthString { .. } | Document::CursorPositionObserver { .. } => {}
447
448 // If we get to a break we need to first see if it has to be
449 // rendered as its unbroken or broken string, depending on the mode.
450 Document::Break { unbroken, .. } => match mode {
451 // [tag:break-fit] If the break has to be broken we're done!
452 // We haven't exceeded the maximum length (otherwise the loop
453 // iteration would have stopped with one of the earlier checks),
454 // and - since it needs to be broken - we'll have to go on a new
455 // line anyway.
456 // This means that the document inspected so far will fit on a
457 // single line, thus we return true.
458 Mode::Broken | Mode::ForcedBroken => return true,
459 // If the break is not broken then it will be rendered inline as
460 // its unbroken string, so we treat it exactly as if it were a
461 // normal string.
462 Mode::Unbroken | Mode::ForcedUnbroken => current_width += unbroken.len() as isize,
463 },
464
465 // The `NextBreakFits` can alter the current mode to `ForcedBroken`
466 // or `ForcedUnbroken` based on its enabled flag.
467 Document::NextBreakFits(doc, enabled) => match enabled {
468 // [tag:disable-next-break] If it is disabled then we check the
469 // wrapped document changing the mode to `ForcedUnbroken`.
470 NextBreakFitsMode::Disabled => docs.push_front((indent, Mode::ForcedUnbroken, doc)),
471 NextBreakFitsMode::Enabled => match mode {
472 // If we're in `ForcedUnbroken` mode it means that the check
473 // was disabled by a document wrapping this one
474 // [ref:disable-next-break]; that's why we do nothing and
475 // check the wrapped document as if it were a normal one.
476 Mode::ForcedUnbroken => docs.push_front((indent, mode, doc)),
477 // [tag:forced-broken] Any other mode is turned into
478 // `ForcedBroken` so that when we run into a break, the
479 // response to the question "Does the document fit?" will be
480 // yes [ref:break-fit].
481 // This is why this is called `NextBreakFit` I think.
482 Mode::Broken | Mode::Unbroken | Mode::ForcedBroken => {
483 docs.push_front((indent, Mode::ForcedBroken, doc))
484 }
485 },
486 },
487
488 // If there's a sequence of documents we will check each one, one
489 // after the other to see if - as a whole - they can fit on a single
490 // line.
491 Document::Vec(vec) => {
492 // The array needs to be reversed to preserve the order of the
493 // documents since each one is pushed _to the front_ of the
494 // queue of documents to check.
495 for doc in vec.iter().rev() {
496 docs.push_front((indent, mode, doc));
497 }
498 }
499 }
500 }
501}
502
503/// The kind of line break this `Document::Break` is.
504#[derive(Debug, Clone, Copy, PartialEq, Eq)]
505pub enum BreakKind {
506 /// A `flex_break`.
507 Flex,
508 /// A `break_`.
509 Strict,
510}
511
512fn format(
513 writer: &mut impl Utf8Writer,
514 limit: isize,
515 mut docs: im::Vector<(isize, Mode, &Document<'_>)>,
516) -> Result<()> {
517 let mut line: isize = 0;
518 let mut width: isize = 0;
519 // As long as there are documents to print we'll take each one by one and
520 // output the corresponding string to the given writer.
521 //
522 // Each document in the `docs` queue also has an accompanying indentation
523 // and mode:
524 // - the indentation is used to keep track of the current indentation,
525 // you might notice in [ref:format-nest] that it adds documents to the
526 // queue increasing their current indentation.
527 // - the mode is used to keep track of the state of the documents inside a
528 // group. For example, if a group doesn't fit on a single line its
529 // documents will be split into multiple lines and the mode set to
530 // `Broken` to keep track of this.
531 while let Some((indent, mode, document)) = docs.pop_front() {
532 match document {
533 // When we run into a line we print the given number of newlines and
534 // add the indentation required by the given document.
535 Document::Line(i) => {
536 for _ in 0..*i {
537 writer.str_write("\n")?;
538 }
539 // i will always be positive, so we can safely cast it to isize
540 line += *i as isize;
541 for _ in 0..indent {
542 writer.str_write(" ")?;
543 }
544 width = indent;
545 }
546
547 // Flex breaks are NOT conditional to the mode: if the mode is
548 // already `Unbroken`, then the break is left unbroken (like strict
549 // breaks); any other mode is ignored.
550 // A flexible break will only be split if the following documents
551 // can't fit on the same line; otherwise, it is just displayed as an
552 // unbroken `Break`.
553 Document::Break {
554 broken,
555 unbroken,
556 kind: BreakKind::Flex,
557 } => {
558 let unbroken_width = width + unbroken.len() as isize;
559 // Every time we need to check again if the remaining piece can
560 // fit. If it does, the flexible break is not broken.
561 if mode == Mode::Unbroken || fits(limit, unbroken_width, docs.clone()) {
562 writer.str_write(unbroken)?;
563 width = unbroken_width;
564 } else {
565 writer.str_write(broken)?;
566 writer.str_write("\n")?;
567 line += 1;
568 for _ in 0..indent {
569 writer.str_write(" ")?;
570 }
571 width = indent;
572 }
573 }
574
575 // Strict breaks are conditional to the mode. They differ from
576 // flexible break because, if a group gets split - that is the mode
577 // is `Broken` or `ForceBroken` - ALL of the breaks in that group
578 // will be split. You can notice the difference with flexible breaks
579 // because here we only check the mode and then take action; before
580 // we would try and see if the remaining documents fit on a single
581 // line before deciding if the (flexible) break can be split or not.
582 Document::Break {
583 broken,
584 unbroken,
585 kind: BreakKind::Strict,
586 } => match mode {
587 // If the mode requires the break to be broken, then its broken
588 // string is printed, then we start a newline and indent it
589 // according to the current indentation level.
590 Mode::Broken | Mode::ForcedBroken => {
591 writer.str_write(broken)?;
592 writer.str_write("\n")?;
593 line += 1;
594 for _ in 0..indent {
595 writer.str_write(" ")?;
596 }
597 width = indent;
598 }
599 // If the mode doesn't require the break to be broken, then its
600 // unbroken string is printed as if it were a normal string;
601 // also updating the width of the current line.
602 Mode::Unbroken | Mode::ForcedUnbroken => {
603 writer.str_write(unbroken)?;
604 width += unbroken.len() as isize
605 }
606 },
607
608 // Strings are printed as they are and the current width is
609 // increased accordingly.
610 Document::EcoString { string, graphemes } => {
611 width += graphemes;
612 writer.str_write(string)?;
613 }
614
615 Document::Str { string, graphemes } => {
616 width += graphemes;
617 writer.str_write(string)?;
618 }
619
620 Document::ZeroWidthString { string } => {
621 // We write the string, but do not increment the length
622 writer.str_write(string)?;
623 }
624
625 // If multiple documents need to be printed, then they are all
626 // pushed to the front of the queue and will be printed one by one.
627 Document::Vec(vec) => {
628 // Just like `fits`, the elements will be pushed _on the front_
629 // of the queue. In order to keep their original order they need
630 // to be pushed in reverse order.
631 for doc in vec.iter().rev() {
632 docs.push_front((indent, mode, doc));
633 }
634 }
635
636 // A `Nest` document doesn't result in anything being printed, its
637 // only effect is to increase the current nesting level for the
638 // wrapped document [tag:format-nest].
639 Document::Nest(i, nest_mode, condition, doc) => match (condition, mode) {
640 // The nesting is only applied under two conditions:
641 // - either the nesting condition is `Always`.
642 // - or the condition is `IfBroken` and the group was actually
643 // broken (that is, the current mode is `Broken`).
644 (NestCondition::Always, _) | (NestCondition::IfBroken, Mode::Broken) => {
645 let new_indent = match nest_mode {
646 NestMode::Increase => indent + i,
647 NestMode::Set => *i,
648 };
649 docs.push_front((new_indent, mode, doc))
650 }
651 // If none of the above conditions is met, then the nesting is
652 // not applied.
653 _ => docs.push_front((indent, mode, doc)),
654 },
655
656 Document::Group(doc) => {
657 // When we see a group we first try and see if it can fit on a
658 // single line without breaking any break; that is why we use
659 // the `Unbroken` mode here: we want to try to fit everything on
660 // a single line.
661 let group_docs = im::vector![(indent, Mode::Unbroken, doc.as_ref())];
662 if fits(limit, width, group_docs) {
663 // If everything can stay on a single line we print the
664 // wrapped document with the `Unbroken` mode, leaving all
665 // the group's break as unbroken.
666 docs.push_front((indent, Mode::Unbroken, doc));
667 } else {
668 // Otherwise, we need to break the group. We print the
669 // wrapped document changing its mode to `Broken` so that
670 // all its breaks will be split on newlines.
671 docs.push_front((indent, Mode::Broken, doc));
672 }
673 }
674
675 // `ForceBroken` and `NextBreakFits` only change the way the `fit`
676 // function works but do not actually change the formatting of a
677 // document by themselves. That's why when we run into those we
678 // just go on printing the wrapped document without altering the
679 // current mode.
680 Document::ForceBroken(document) | Document::NextBreakFits(document, _) => {
681 docs.push_front((indent, mode, document));
682 }
683
684 Document::CursorPositionObserver { observer } => {
685 // Notify the observer of the current cursor position
686 observer.borrow_mut().observe_cursor_position(line, width);
687 }
688 }
689 }
690 Ok(())
691}
692
693/// Renders an empty document.
694pub fn nil<'a>() -> Document<'a> {
695 Document::Vec(vec![])
696}
697
698/// Renders a single newline.
699pub fn line<'a>() -> Document<'a> {
700 Document::Line(1)
701}
702
703/// Renders a string of newlines, equal in length to the number provided.
704pub fn lines<'a>(i: usize) -> Document<'a> {
705 Document::Line(i)
706}
707
708/// A document after which the formatter can insert a newline. This determines
709/// where line breaks can occur, outside of hardcoded `Line`s.
710///
711/// If the formatter determines that a group cannot fit on a single line,
712/// all breaks in the group will be rendered as broken. Otherwise, they
713/// will be rendered as unbroken.
714///
715/// A broken `Break` renders the `broken` string, followed by a newline.
716/// An unbroken `Break` renders the `unbroken` string by itself.
717///
718/// For example:
719/// ```rust:norun
720/// let document = docvec!["Hello", break_("", ", "), "world!"];
721/// assert_eq!(document.to_pretty_string(20), "Hello, world!");
722/// assert_eq!(document.to_pretty_string(10), "Hello\nworld!");
723/// ```
724///
725pub fn break_<'a>(broken: &'a str, unbroken: &'a str) -> Document<'a> {
726 Document::Break {
727 broken,
728 unbroken,
729 kind: BreakKind::Strict,
730 }
731}
732
733/// A document after which the formatter can insert a newline, similar to
734/// `break_()`. The difference is that when a group is rendered broken, all
735/// breaks are rendered broken. However, `flex_break` decides whether to
736/// break or not for every individual `flex_break`.
737///
738/// For example:
739/// ```rust:norun
740/// let with_breaks = docvec!["Hello", break_("", ", "), "pretty", break_("", ", "), "printed!"];
741/// assert_eq!(with_breaks.to_pretty_string(20), "Hello\npretty\nprinted!");
742///
743/// let with_flex_breaks = docvec!["Hello", flex_break("", ", "), "pretty", flex_break("", ", "), "printed!"];
744/// assert_eq!(with_flex_breaks.to_pretty_string(20), "Hello, pretty\nprinted!");
745/// ```
746///
747pub fn flex_break<'a>(broken: &'a str, unbroken: &'a str) -> Document<'a> {
748 Document::Break {
749 broken,
750 unbroken,
751 kind: BreakKind::Flex,
752 }
753}
754
755/// A string that is not taken into account when determining line length.
756/// This is useful for additional formatting text which won't be rendered
757/// in the final output, such as ANSI codes or HTML elements.
758///
759/// For example:
760/// ```rust:norun
761/// let document = docvec!["Hello", zero_width_string("This is a very long string"), break_("", ""), "world"];
762/// assert_eq!(document.to_pretty_string(20), "HelloThis is a very long stringworld");
763/// ```
764///
765pub fn zero_width_string<'a>(string: EcoString) -> Document<'a> {
766 Document::ZeroWidthString { string }
767}
768
769impl<'a> Document<'a> {
770 /// Creates a document from a string slice.
771 pub fn str(string: &'a str) -> Self {
772 Document::Str {
773 graphemes: string.graphemes(true).count() as isize,
774 string,
775 }
776 }
777
778 /// Creates a document from an owned `EcoString`.
779 pub fn eco_string(string: EcoString) -> Self {
780 Document::EcoString {
781 graphemes: string.graphemes(true).count() as isize,
782 string,
783 }
784 }
785
786 /// Groups a document. When pretty printing a group, the formatter will
787 /// first attempt to fit the entire group on one line. If it fails, all
788 /// `break_` documents in the group will render broken.
789 ///
790 /// Nested groups are handled separately to their parents, so if the
791 /// outermost group is broken, any sub-groups might be rendered broken
792 /// or unbroken, depending on whether they fit on a single line.
793 pub fn group(self) -> Self {
794 match self {
795 // Grouping a group doesn't change how it will be formatted so we
796 // can avoid boxing it.
797 Document::Group(_)
798 // Grouping a literal string will never change how it's formatted,
799 // we can avoid boxing it.
800 | Document::Str { .. }
801 | Document::EcoString { .. }
802 | Document::ZeroWidthString { .. } => self,
803
804 Document::Line(_)
805 | Document::ForceBroken(_)
806 | Document::NextBreakFits(..)
807 | Document::Break { .. }
808 | Document::Vec(_)
809 | Document::Nest(..) => Self::Group(Box::new(self)),
810 }
811 }
812
813 /// Sets the indentation level of a document.
814 pub fn set_nesting(self, indent: isize) -> Self {
815 Self::Nest(indent, NestMode::Set, NestCondition::Always, Box::new(self))
816 }
817
818 /// Nests a document by a certain indentation. When rending linebreaks, the
819 /// formatter will print a new line followed by the current indentation.
820 pub fn nest(self, indent: isize) -> Self {
821 Self::Nest(
822 indent,
823 NestMode::Increase,
824 NestCondition::Always,
825 Box::new(self),
826 )
827 }
828
829 /// Nests a document by a certain indentation, but only if the current
830 /// group is broken.
831 pub fn nest_if_broken(self, indent: isize) -> Self {
832 Self::Nest(
833 indent,
834 NestMode::Increase,
835 NestCondition::IfBroken,
836 Box::new(self),
837 )
838 }
839
840 /// Forces all `break_` and `flex_break` documents in the current group
841 /// to render broken.
842 pub fn force_break(self) -> Self {
843 Self::ForceBroken(Box::new(self))
844 }
845
846 /// Force the next `Break` to render unbroken, regardless of whether it
847 /// fits on the line or not.
848 pub fn next_break_fits(self, mode: NextBreakFitsMode) -> Self {
849 Self::NextBreakFits(Box::new(self), mode)
850 }
851
852 /// Appends one document to another. Equivalent to `docvec![self, second]`,
853 /// except that it `self` is already a `Document::Vec`, it will append
854 /// directly to it instead of allocating a new vector.
855 ///
856 /// Useful when chaining multiple documents together in a fashion where
857 /// they cannot be put all into one `docvec!` macro.
858 pub fn append(self, second: impl Documentable<'a>) -> Self {
859 match self {
860 Self::Vec(mut vec) => {
861 vec.push(second.to_doc());
862 Self::Vec(vec)
863 }
864 Self::Line(..)
865 | Self::ForceBroken(..)
866 | Self::NextBreakFits(..)
867 | Self::Break { .. }
868 | Self::Nest(..)
869 | Self::Group(..)
870 | Self::Str { .. }
871 | Self::EcoString { .. }
872 | Self::ZeroWidthString { .. }
873 | Self::CursorPositionObserver { .. } => Self::Vec(vec![self, second.to_doc()]),
874 }
875 }
876
877 /// Prints a document into a `String`, attempting to limit lines to `limit`
878 /// characters in length.
879 pub fn to_pretty_string(self, limit: isize) -> String {
880 let mut buffer = String::new();
881 self.pretty_print(limit, &mut buffer)
882 .expect("Writing to string buffer failed");
883 buffer
884 }
885
886 /// Surrounds a document in two delimiters. Equivalent to
887 /// `docvec![option, self, closed]`.
888 pub fn surround(self, open: impl Documentable<'a>, closed: impl Documentable<'a>) -> Self {
889 open.to_doc().append(self).append(closed)
890 }
891
892 /// Prints a document into `writer`, attempting to limit lines to `limit`
893 /// characters in length.
894 pub fn pretty_print(&self, limit: isize, writer: &mut impl Utf8Writer) -> Result<()> {
895 let docs = im::vector![(0, Mode::Unbroken, self)];
896 format(writer, limit, docs)?;
897 Ok(())
898 }
899
900 /// Returns true when the document contains no printable characters
901 /// (whitespace and newlines are considered printable characters).
902 pub fn is_empty(&self) -> bool {
903 use Document::*;
904 match self {
905 Line(n) => *n == 0,
906 EcoString { string, .. } => string.is_empty(),
907 Str { string, .. } => string.is_empty(),
908 // assuming `broken` and `unbroken` are equivalent
909 Break { broken, .. } => broken.is_empty(),
910 ForceBroken(d) | Nest(_, _, _, d) | Group(d) | NextBreakFits(d, _) => d.is_empty(),
911 Vec(docs) => docs.iter().all(|d| d.is_empty()),
912 // Zero-width strings don't count towards line length, but they are
913 // still printed and so are not empty. (Unless their string contents
914 // is also empty)
915 ZeroWidthString { string } => string.is_empty(),
916 CursorPositionObserver { .. } => true,
917 }
918 }
919}