markdown_fmt/
paragraph.rs

1use super::*;
2
3const MARKDOWN_HARD_BREAK: &str = "  \n";
4
5/// A formatter buffer we write paragraph text into.
6pub trait ParagraphFormatter: Write {
7    /// Make a new instance based on the given maximum width and buffer
8    /// capacity.
9    fn new(max_width: Option<usize>, capacity: usize) -> Self;
10
11    /// Check if the internal buffer is empty.
12    fn is_empty(&self) -> bool;
13
14    /// Consume Self and return the formatted buffer.
15    fn into_buffer(self) -> String;
16}
17
18/// A buffer where we write text
19pub struct Paragraph {
20    buffer: String,
21    max_width: Option<usize>,
22}
23
24impl Write for Paragraph {
25    fn write_str(&mut self, s: &str) -> std::fmt::Result {
26        let is_hard_break = |s: &str| -> bool {
27            // Hard breaks can have any amount of leading whitesace followed by a newline
28            s.strip_prefix("  ")
29                .is_some_and(|maybe_hard_break| maybe_hard_break.trim_start_matches(' ').eq("\n"))
30        };
31
32        if self.max_width.is_some() && is_hard_break(s) {
33            self.buffer.push_str(MARKDOWN_HARD_BREAK);
34            return Ok(());
35        }
36
37        if self.max_width.is_some() && s.trim().is_empty() {
38            // If the user configured the max_width then push a space so we can reflow text
39            self.buffer.push(' ');
40        } else {
41            self.buffer.push_str(s);
42        }
43
44        Ok(())
45    }
46}
47
48impl ParagraphFormatter for Paragraph {
49    fn new(max_width: Option<usize>, capacity: usize) -> Self {
50        Self {
51            max_width,
52            buffer: String::with_capacity(capacity),
53        }
54    }
55
56    fn is_empty(&self) -> bool {
57        self.buffer.is_empty()
58    }
59
60    fn into_buffer(mut self) -> String {
61        let rewrite_buffer = std::mem::take(&mut self.buffer);
62
63        let Some(max_width) = self.max_width else {
64            // We didn't configure a max_width, so just return the buffer
65            return rewrite_buffer;
66        };
67
68        let all_lines_with_max_width = rewrite_buffer.lines().all(|l| l.len() <= max_width);
69
70        if all_lines_with_max_width {
71            // Don't need to wrap any lines
72            return rewrite_buffer;
73        }
74
75        let mut output_buffer = String::with_capacity(rewrite_buffer.capacity());
76
77        let wrap_options = TextWrapOptions::new(max_width)
78            .break_words(false)
79            .word_separator(textwrap::WordSeparator::AsciiSpace)
80            .wrap_algorithm(textwrap::WrapAlgorithm::FirstFit);
81
82        let mut split_on_hard_breaks = rewrite_buffer.split(MARKDOWN_HARD_BREAK).peekable();
83
84        while let Some(text) = split_on_hard_breaks.next() {
85            let has_next = split_on_hard_breaks.peek().is_some();
86            let wrapped_text = textwrap::fill(text, wrap_options.clone());
87            output_buffer.push_str(&wrapped_text);
88            if has_next {
89                output_buffer.push_str(MARKDOWN_HARD_BREAK);
90            }
91        }
92
93        output_buffer
94    }
95}