1use crate::{
4 comments::{CommentState, CommentStringExt},
5 string::{QuoteState, QuotedStringExt},
6};
7use std::fmt::Write;
8
9#[derive(Clone, Debug, Default)]
11struct IndentGroup {
12 skip_line: bool,
13}
14
15#[derive(Clone, Copy, Debug)]
16enum WriteState {
17 LineStart(CommentState),
18 WriteTokens(CommentState),
19 WriteString(char),
20}
21
22impl WriteState {
23 fn comment_state(&self) -> CommentState {
24 match self {
25 Self::LineStart(state) => *state,
26 Self::WriteTokens(state) => *state,
27 Self::WriteString(_) => CommentState::None,
28 }
29 }
30}
31
32impl Default for WriteState {
33 fn default() -> Self {
34 Self::LineStart(CommentState::default())
35 }
36}
37
38#[derive(Clone, Debug)]
42pub struct FormatBuffer<W> {
43 pub w: W,
44 indents: Vec<IndentGroup>,
45 base_indent_len: usize,
46 tab_width: usize,
47 last_char: Option<char>,
48 current_line_len: usize,
49 restrict_to_single_line: bool,
50 state: WriteState,
51}
52
53impl<W> FormatBuffer<W> {
54 pub fn new(w: W, tab_width: usize) -> Self {
55 Self {
56 w,
57 tab_width,
58 base_indent_len: 0,
59 indents: vec![],
60 current_line_len: 0,
61 last_char: None,
62 restrict_to_single_line: false,
63 state: WriteState::default(),
64 }
65 }
66
67 pub fn create_temp_buf(&self) -> FormatBuffer<String> {
70 let mut new = FormatBuffer::new(String::new(), self.tab_width);
71 new.base_indent_len = self.total_indent_len();
72 new.current_line_len = self.current_line_len();
73 new.last_char = self.last_char;
74 new.restrict_to_single_line = self.restrict_to_single_line;
75 new.state = match self.state {
76 WriteState::WriteTokens(state) | WriteState::LineStart(state) => {
77 WriteState::LineStart(state)
78 }
79 WriteState::WriteString(ch) => WriteState::WriteString(ch),
80 };
81 new
82 }
83
84 pub fn restrict_to_single_line(&mut self, restricted: bool) {
86 self.restrict_to_single_line = restricted;
87 }
88
89 pub fn indent(&mut self, delta: usize) {
91 self.indents.extend(std::iter::repeat_n(IndentGroup::default(), delta));
92 }
93
94 pub fn dedent(&mut self, delta: usize) {
96 self.indents.truncate(self.indents.len() - delta);
97 }
98
99 fn level(&self) -> usize {
102 self.indents.iter().filter(|i| !i.skip_line).count()
103 }
104
105 pub fn last_indent_group_skipped(&self) -> bool {
107 self.indents.last().map(|i| i.skip_line).unwrap_or(false)
108 }
109
110 pub fn set_last_indent_group_skipped(&mut self, skip_line: bool) {
112 if let Some(i) = self.indents.last_mut() {
113 i.skip_line = skip_line
114 }
115 }
116
117 pub fn current_indent_len(&self) -> usize {
119 self.level() * self.tab_width
120 }
121
122 pub fn total_indent_len(&self) -> usize {
124 self.current_indent_len() + self.base_indent_len
125 }
126
127 pub fn current_line_len(&self) -> usize {
129 self.current_line_len
130 }
131
132 pub fn is_beginning_of_line(&self) -> bool {
134 matches!(self.state, WriteState::LineStart(_))
135 }
136
137 pub fn start_group(&mut self) {
139 self.indents.push(IndentGroup { skip_line: true });
140 }
141
142 pub fn end_group(&mut self) {
144 self.indents.pop();
145 }
146
147 pub fn last_char(&self) -> Option<char> {
149 self.last_char
150 }
151
152 fn handle_newline(&mut self, mut comment_state: CommentState) {
154 if comment_state == CommentState::Line {
155 comment_state = CommentState::None;
156 }
157 self.current_line_len = 0;
158 self.set_last_indent_group_skipped(false);
159 self.last_char = Some('\n');
160 self.state = WriteState::LineStart(comment_state);
161 }
162}
163
164impl<W: Write> FormatBuffer<W> {
165 pub fn write_raw(&mut self, s: impl AsRef<str>) -> std::fmt::Result {
168 self._write_raw(s.as_ref())
169 }
170
171 fn _write_raw(&mut self, s: &str) -> std::fmt::Result {
172 let mut lines = s.lines().peekable();
173 let mut comment_state = self.state.comment_state();
174 while let Some(line) = lines.next() {
175 let (new_comment_state, line_start) = line
179 .comment_state_char_indices()
180 .with_state(comment_state)
181 .take(self.base_indent_len)
182 .take_while(|(_, _, ch)| ch.is_whitespace())
183 .last()
184 .map(|(state, idx, _)| (state, idx + 1))
185 .unwrap_or((comment_state, 0));
186 comment_state = new_comment_state;
187 let trimmed_line = &line[line_start..];
188 if !trimmed_line.is_empty() {
189 self.w.write_str(trimmed_line)?;
190 self.current_line_len += trimmed_line.len();
191 self.last_char = trimmed_line.chars().next_back();
192 self.state = WriteState::WriteTokens(comment_state);
193 }
194 if lines.peek().is_some() || s.ends_with('\n') {
195 if self.restrict_to_single_line {
196 return Err(std::fmt::Error)
197 }
198 self.w.write_char('\n')?;
199 self.handle_newline(comment_state);
200 }
201 }
202 Ok(())
203 }
204}
205
206impl<W: Write> Write for FormatBuffer<W> {
207 fn write_str(&mut self, mut s: &str) -> std::fmt::Result {
208 if s.is_empty() {
209 return Ok(())
210 }
211
212 let mut indent = " ".repeat(self.current_indent_len());
213
214 loop {
215 match self.state {
216 WriteState::LineStart(mut comment_state) => {
217 match s.find(|b| b != '\n') {
218 None => {
220 if !s.is_empty() {
221 self.w.write_str(s)?;
222 self.handle_newline(comment_state);
223 }
224 break
225 }
226
227 Some(len) => {
231 let (head, tail) = s.split_at(len);
232 self.w.write_str(head)?;
233 self.w.write_str(&indent)?;
234 self.current_line_len = 0;
235 self.last_char = Some(' ');
236 if len > 0 {
238 if self.last_indent_group_skipped() {
239 indent = " ".repeat(self.current_indent_len() + self.tab_width);
240 self.set_last_indent_group_skipped(false);
241 }
242 if comment_state == CommentState::Line {
243 comment_state = CommentState::None;
244 }
245 }
246 s = tail;
247 self.state = WriteState::WriteTokens(comment_state);
248 }
249 }
250 }
251 WriteState::WriteTokens(comment_state) => {
252 if s.is_empty() {
253 break
254 }
255
256 let mut len = 0;
258 let mut new_state = WriteState::WriteTokens(comment_state);
259 for (state, idx, ch) in s.comment_state_char_indices().with_state(comment_state)
260 {
261 len = idx;
262 if ch == '\n' {
263 if self.restrict_to_single_line {
264 return Err(std::fmt::Error)
265 }
266 new_state = WriteState::LineStart(state);
267 break
268 } else if state == CommentState::None && (ch == '\'' || ch == '"') {
269 new_state = WriteState::WriteString(ch);
270 break
271 } else {
272 new_state = WriteState::WriteTokens(state);
273 }
274 }
275
276 if matches!(new_state, WriteState::WriteTokens(_)) {
277 self.w.write_str(s)?;
279 self.current_line_len += s.len();
280 self.last_char = s.chars().next_back();
281 self.state = new_state;
282 break
283 } else {
284 let (head, tail) = s.split_at(len + 1);
287 self.w.write_str(head)?;
288 s = tail;
289 match new_state {
290 WriteState::LineStart(comment_state) => {
291 self.handle_newline(comment_state)
292 }
293 new_state => {
294 self.current_line_len += head.len();
295 self.last_char = head.chars().next_back();
296 self.state = new_state;
297 }
298 }
299 }
300 }
301 WriteState::WriteString(quote) => {
302 match s.quoted_ranges().with_state(QuoteState::String(quote)).next() {
303 None => {
305 self.w.write_str(s)?;
306 self.current_line_len += s.len();
307 self.last_char = s.chars().next_back();
308 break
309 }
310 Some((_, _, len)) => {
312 let (head, tail) = s.split_at(len + 1);
313 self.w.write_str(head)?;
314 if let Some((_, last)) = head.rsplit_once('\n') {
315 self.set_last_indent_group_skipped(false);
316 self.current_line_len = last.len();
317 } else {
318 self.current_line_len += head.len();
319 }
320 self.last_char = Some(quote);
321 s = tail;
322 self.state = WriteState::WriteTokens(CommentState::None);
323 }
324 }
325 }
326 }
327 }
328
329 Ok(())
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 const TAB_WIDTH: usize = 4;
338
339 #[test]
340 fn test_buffer_indents() -> std::fmt::Result {
341 let delta = 1;
342
343 let mut buf = FormatBuffer::new(String::new(), TAB_WIDTH);
344 assert_eq!(buf.indents.len(), 0);
345 assert_eq!(buf.level(), 0);
346 assert_eq!(buf.current_indent_len(), 0);
347
348 buf.indent(delta);
349 assert_eq!(buf.indents.len(), delta);
350 assert_eq!(buf.level(), delta);
351 assert_eq!(buf.current_indent_len(), delta * TAB_WIDTH);
352
353 buf.indent(delta);
354 buf.set_last_indent_group_skipped(true);
355 assert!(buf.last_indent_group_skipped());
356 assert_eq!(buf.indents.len(), delta * 2);
357 assert_eq!(buf.level(), delta);
358 assert_eq!(buf.current_indent_len(), delta * TAB_WIDTH);
359 buf.dedent(delta);
360
361 buf.dedent(delta);
362 assert_eq!(buf.indents.len(), 0);
363 assert_eq!(buf.level(), 0);
364 assert_eq!(buf.current_indent_len(), 0);
365
366 let res = std::panic::catch_unwind(|| buf.clone().dedent(delta));
368 assert!(res.is_err());
369
370 Ok(())
371 }
372
373 #[test]
374 fn test_identical_temp_buf() -> std::fmt::Result {
375 let content = "test string";
376 let multiline_content = "test\nmultiline\nmultiple";
377 let mut buf = FormatBuffer::new(String::new(), TAB_WIDTH);
378
379 let mut temp = buf.create_temp_buf();
381 writeln!(buf, "{content}")?;
382 writeln!(temp, "{content}")?;
383 assert_eq!(buf.w, format!("{content}\n"));
384 assert_eq!(temp.w, buf.w);
385 assert_eq!(temp.current_line_len, buf.current_line_len);
386 assert_eq!(temp.base_indent_len, buf.total_indent_len());
387
388 let delta = 1;
389 buf.indent(delta);
390
391 let mut temp_indented = buf.create_temp_buf();
392 assert!(temp_indented.w.is_empty());
393 assert_eq!(temp_indented.base_indent_len, buf.total_indent_len());
394 assert_eq!(temp_indented.level() + delta, buf.level());
395
396 let indent = " ".repeat(delta * TAB_WIDTH);
397
398 let mut original_buf = buf.clone();
399 write!(buf, "{multiline_content}")?;
400 let expected_content = format!(
401 "{}\n{}{}",
402 content,
403 indent,
404 multiline_content.lines().collect::<Vec<_>>().join(&format!("\n{indent}"))
405 );
406 assert_eq!(buf.w, expected_content);
407
408 write!(temp_indented, "{multiline_content}")?;
409
410 write!(original_buf, "{}", temp_indented.w)?;
412 assert_eq!(buf.w, original_buf.w);
413
414 Ok(())
415 }
416
417 #[test]
418 fn test_preserves_original_content_with_default_settings() -> std::fmt::Result {
419 let contents = [
420 "simple line",
421 r"
422 some
423 multiline
424 content",
425 "// comment",
426 "/* comment */",
427 r"mutliline
428 content
429 // comment1
430 with comments
431 /* comment2 */ ",
432 ];
433
434 for content in &contents {
435 let mut buf = FormatBuffer::new(String::new(), TAB_WIDTH);
436 write!(buf, "{content}")?;
437 assert_eq!(&buf.w, content);
438 }
439
440 Ok(())
441 }
442}