dprint_core/formatting/
print.rs

1use std::cell::RefCell;
2
3use self::thread_state::BumpAllocator;
4
5use super::*;
6
7/// Options for printing the print items.
8pub struct PrintOptions {
9  /// The width the printer will attempt to keep the line under.
10  pub max_width: u32,
11  /// The number of columns to count when indenting or using a tab.
12  pub indent_width: u8,
13  /// Whether to use tabs for indenting.
14  pub use_tabs: bool,
15  /// The newline character to use when doing a new line.
16  pub new_line_text: &'static str,
17}
18
19impl PrintOptions {
20  pub(super) fn to_printer_options(&self) -> PrinterOptions {
21    PrinterOptions {
22      indent_width: self.indent_width,
23      max_width: self.max_width,
24      #[cfg(feature = "tracing")]
25      enable_tracing: false,
26    }
27  }
28}
29
30/// Function to create the provided print items and print them out as a string.
31///
32/// Note: It is unsafe to use the print items created within `get_print_items`
33/// outside of the closure, since they are created with a thread local allocator
34/// that is reset once this function returns.
35pub fn format(get_print_items: impl FnOnce() -> PrintItems, options: PrintOptions) -> String {
36  increment_formatting_count();
37  let old_counts = thread_state::take_counts();
38  let print_items = get_print_items();
39
40  let result = thread_state::with_bump_allocator(|bump| {
41    let result = print_with_allocator(bump, &print_items, &options);
42    if decrement_formatting_count() {
43      bump.reset();
44    }
45    result
46  });
47  thread_state::set_counts(old_counts);
48  result
49}
50
51/// Prints out the print items using the provided options.
52///
53/// Note: This should only be used in rare scenarios. In most cases,
54/// use only `dprint_core::formatting::format`.
55pub fn print(print_items: PrintItems, options: PrintOptions) -> String {
56  // This shouldn't be called without calling `format` because it doesn't
57  // reset the allocator.
58  panic_if_not_formatting();
59
60  let old_counts = thread_state::take_counts();
61  let result = thread_state::with_bump_allocator(|bump| print_with_allocator(bump, &print_items, &options));
62  thread_state::set_counts(old_counts);
63  result
64}
65
66fn print_with_allocator(bump: &mut BumpAllocator, print_items: &PrintItems, options: &PrintOptions) -> String {
67  match Printer::new(bump, print_items.first_node, options.to_printer_options()).print() {
68    Some(write_items) => WriteItemsPrinter::from(options).print(write_items),
69    None => String::new(),
70  }
71}
72
73#[cfg(feature = "tracing")]
74#[derive(serde::Serialize)]
75#[serde(rename_all = "camelCase")]
76pub struct TracingResult {
77  pub traces: Vec<Trace>,
78  pub writer_nodes: Vec<TraceWriterNode>,
79  pub print_nodes: Vec<TracePrintNode>,
80}
81
82/// Gets trace information for analysis purposes.
83#[cfg(feature = "tracing")]
84pub fn trace_printing(get_print_items: impl FnOnce() -> PrintItems, options: PrintOptions) -> TracingResult {
85  use std::iter;
86
87  increment_formatting_count();
88  let print_items = get_print_items();
89
90  thread_state::with_bump_allocator(|bump| {
91    let tracing_result = Printer::new(bump, print_items.first_node, {
92      let mut printer_options = options.to_printer_options();
93      printer_options.enable_tracing = true;
94      printer_options
95    })
96    .print_for_tracing();
97    let writer_items_printer = WriteItemsPrinter::from(&options);
98
99    let result = TracingResult {
100      traces: tracing_result.traces,
101      writer_nodes: tracing_result
102        .writer_nodes
103        .into_iter()
104        .map(|node| {
105          let text = writer_items_printer.print(iter::once(node.item));
106          TraceWriterNode {
107            writer_node_id: node.graph_node_id,
108            previous_node_id: node.previous.map(|n| n.graph_node_id),
109            text,
110          }
111        })
112        .collect(),
113      print_nodes: super::get_trace_print_nodes(print_items.first_node),
114    };
115
116    if decrement_formatting_count() {
117      bump.reset();
118    }
119    result
120  })
121}
122
123thread_local! {
124  static FORMATTING_COUNT: RefCell<u32> = const { RefCell::new(0) };
125}
126
127fn increment_formatting_count() {
128  FORMATTING_COUNT.with(|formatting_count_cell| {
129    let mut formatting_count = formatting_count_cell.borrow_mut();
130    *formatting_count += 1;
131  })
132}
133
134fn decrement_formatting_count() -> bool {
135  FORMATTING_COUNT.with(|formatting_count_cell| {
136    let mut formatting_count = formatting_count_cell.borrow_mut();
137    *formatting_count -= 1;
138    *formatting_count == 0
139  })
140}
141
142fn panic_if_not_formatting() {
143  FORMATTING_COUNT.with(|formatting_count_cell| {
144    if *formatting_count_cell.borrow() == 0 {
145      panic!("dprint_core::formatting::print cannot be called except within the provided closure to dprint_core::formatting::format");
146    }
147  })
148}
149
150#[cfg(test)]
151mod test {
152  use crate::formatting::LineNumber;
153
154  use super::super::PrintItems;
155  use super::format;
156  use super::PrintOptions;
157
158  #[test]
159  fn test_format_in_format() {
160    assert_eq!(
161      format(
162        || {
163          let mut items = PrintItems::new();
164          assert_eq!(LineNumber::new("").unique_id(), 0);
165          assert_eq!(LineNumber::new("").unique_id(), 1);
166          assert_eq!(LineNumber::new("").unique_id(), 2);
167          items.push_str_runtime_width_computed("test");
168          items.push_string(format(
169            || {
170              // It's important that these start incrementing from
171              // 0 when formatting within a format because these
172              // are stored as resolved within the printer using
173              // a vector and the id is the index
174              assert_eq!(LineNumber::new("").unique_id(), 0);
175              assert_eq!(LineNumber::new("").unique_id(), 1);
176              "test".into()
177            },
178            get_print_options(),
179          ));
180          // now ensure it goes back to where it left off
181          assert_eq!(LineNumber::new("").unique_id(), 3);
182          items
183        },
184        get_print_options(),
185      ),
186      "testtest"
187    );
188  }
189
190  fn get_print_options() -> PrintOptions {
191    PrintOptions {
192      max_width: 40,
193      indent_width: 2,
194      use_tabs: false,
195      new_line_text: "\n",
196    }
197  }
198}