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