indent_write/
fmt.rs

1use core::fmt;
2/// Adapter for writers to indent each line
3///
4/// An `IndentWriter` adapts a [`fmt::Write`] object to insert an indent before
5/// each non-empty line. Specifically, this means it will insert an indent
6/// between each newline when followed by a non-newline.
7///
8/// These writers can be nested to provide increasing levels of indentation.
9///
10/// # Example
11///
12/// ```
13/// # use std::fmt::Write;
14/// use indent_write::fmt::IndentWriter;
15///
16/// let output = String::new();
17///
18/// let mut indented = IndentWriter::new("\t", output);
19///
20/// // Lines will be indented
21/// write!(indented, "Line 1\nLine 2\n");
22///
23/// // Empty lines will not be indented
24/// write!(indented, "\n\nLine 3\n\n");
25///
26/// assert_eq!(indented.get_ref(), "\tLine 1\n\tLine 2\n\n\n\tLine 3\n\n");
27/// ```
28#[derive(Debug, Clone)]
29pub struct IndentWriter<'i, W> {
30    writer: W,
31    indent: &'i str,
32    need_indent: bool,
33}
34
35impl<'i, W: fmt::Write> IndentWriter<'i, W> {
36    /// Create a new [`IndentWriter`].
37    #[inline]
38    pub fn new(indent: &'i str, writer: W) -> Self {
39        Self {
40            writer,
41            indent,
42            need_indent: true,
43        }
44    }
45
46    /// Create a new [`IndentWriter`] which will not add an indent to the first
47    /// written line.
48    ///
49    /// # Example
50    ///
51    /// ```
52    /// # use std::fmt::Write;
53    /// use indent_write::fmt::IndentWriter;
54    ///
55    /// let mut buffer = String::new();
56    /// let mut writer = IndentWriter::new_skip_initial("    ", &mut buffer);
57    ///
58    /// writeln!(writer, "Line 1").unwrap();
59    /// writeln!(writer, "Line 2").unwrap();
60    /// writeln!(writer, "Line 3").unwrap();
61    ///
62    /// assert_eq!(buffer, "Line 1\n    Line 2\n    Line 3\n")
63    /// ```
64    #[inline]
65    pub fn new_skip_initial(indent: &'i str, writer: W) -> Self {
66        Self {
67            writer,
68            indent,
69            need_indent: false,
70        }
71    }
72
73    /// Extract the writer from the `IndentWriter`, discarding any in-progress
74    /// indent state.
75    #[inline]
76    pub fn into_inner(self) -> W {
77        self.writer
78    }
79
80    /// Get a reference to the wrapped writer
81    #[inline]
82    pub fn get_ref(&self) -> &W {
83        &self.writer
84    }
85
86    /// Get the string being used as an indent for each line
87    #[inline]
88    pub fn indent(&self) -> &'i str {
89        self.indent
90    }
91}
92
93impl<'i, W: fmt::Write> fmt::Write for IndentWriter<'i, W> {
94    fn write_str(&mut self, mut s: &str) -> fmt::Result {
95        loop {
96            match self.need_indent {
97                // We don't need an indent. Scan for the end of the line
98                false => match s.as_bytes().iter().position(|&b| b == b'\n') {
99                    // No end of line in the input; write the entire string
100                    None => break self.writer.write_str(s),
101
102                    // We can see the end of the line. Write up to and including
103                    // that newline, then request an indent
104                    Some(len) => {
105                        let (head, tail) = s.split_at(len + 1);
106                        self.writer.write_str(head)?;
107                        self.need_indent = true;
108                        s = tail;
109                    }
110                },
111                // We need an indent. Scan for the beginning of the next
112                // non-empty line.
113                true => match s.as_bytes().iter().position(|&b| b != b'\n') {
114                    // No non-empty lines in input, write the entire string
115                    None => break self.writer.write_str(s),
116
117                    // We can see the next non-empty line. Write up to the
118                    // beginning of that line, then insert an indent, then
119                    // continue.
120                    Some(len) => {
121                        let (head, tail) = s.split_at(len);
122                        self.writer.write_str(head)?;
123                        self.writer.write_str(self.indent)?;
124                        self.need_indent = false;
125                        s = tail;
126                    }
127                },
128            }
129        }
130    }
131
132    fn write_char(&mut self, c: char) -> fmt::Result {
133        // We need an indent, and this is the start of a non-empty line.
134        // Insert the indent.
135        if self.need_indent && c != '\n' {
136            self.writer.write_str(self.indent)?;
137            self.need_indent = false;
138        }
139
140        // This is the end of a non-empty line. Request an indent.
141        if !self.need_indent && c == '\n' {
142            self.need_indent = true;
143        }
144
145        self.writer.write_char(c)
146    }
147}