Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / pretty.rs
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}