Track first line across calls and add tests

Fix logic issue where `self.first` was not tracked across calls, causing incorrect indentation.

Replaced `idx` with `is_first_line` flag.

Added `test_indent_chunked` to verify correct behavior.
This commit is contained in:
strickczq 2025-05-30 00:29:54 +08:00 committed by René Kijewski
parent 6653662d9e
commit 77b0763679

View File

@ -108,6 +108,7 @@ struct IndentWriter<'a, W> {
first: bool, first: bool,
blank: bool, blank: bool,
is_new_line: bool, is_new_line: bool,
is_first_line: bool,
} }
impl<'a, W: fmt::Write> IndentWriter<'a, W> { impl<'a, W: fmt::Write> IndentWriter<'a, W> {
@ -118,6 +119,7 @@ impl<'a, W: fmt::Write> IndentWriter<'a, W> {
first, first,
blank, blank,
is_new_line: true, is_new_line: true,
is_first_line: true,
} }
} }
} }
@ -128,11 +130,18 @@ impl<W: fmt::Write> fmt::Write for IndentWriter<'_, W> {
return self.dest.write_str(s); return self.dest.write_str(s);
} }
for (idx, line) in s.split_inclusive('\n').enumerate() { for line in s.split_inclusive('\n') {
if (self.first || idx > 0 || !self.is_new_line) if self.is_new_line {
&& (self.blank || !matches!(line, "\n" | "\r\n")) if self.is_first_line {
{ if self.first && (self.blank || !matches!(line, "\n" | "\r\n")) {
self.dest.write_str(self.indent)?; self.dest.write_str(self.indent)?;
}
self.is_first_line = false;
} else {
if self.blank || !matches!(line, "\n" | "\r\n") {
self.dest.write_str(self.indent)?;
}
}
} }
self.dest.write_str(line)?; self.dest.write_str(line)?;
self.is_new_line = line.ends_with('\n'); self.is_new_line = line.ends_with('\n');
@ -309,6 +318,65 @@ mod tests {
); );
} }
#[test]
fn test_indent_chunked() {
#[derive(Clone, Copy)]
struct Chunked<'a>(&'a str);
impl<'a> fmt::Display for Chunked<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for chunk in self.0.chars() {
write!(f, "{}", chunk)?;
}
Ok(())
}
}
assert_eq!(
indent(Chunked("hello"), 2, false, false).unwrap().to_string(),
"hello"
);
assert_eq!(
indent(Chunked("hello\n"), 2, false, false).unwrap().to_string(),
"hello\n"
);
assert_eq!(
indent(Chunked("hello\nfoo"), 2, false, false).unwrap().to_string(),
"hello\n foo"
);
assert_eq!(
indent(Chunked("hello\nfoo\n bar"), 4, false, false)
.unwrap()
.to_string(),
"hello\n foo\n bar"
);
assert_eq!(
indent(Chunked("hello"), 267_332_238_858, false, false)
.unwrap()
.to_string(),
"hello"
);
assert_eq!(
indent(Chunked("hello\n\n bar"), 4, false, false)
.unwrap()
.to_string(),
"hello\n\n bar"
);
assert_eq!(
indent(Chunked("hello\n\n bar"), 4, false, true).unwrap().to_string(),
"hello\n \n bar"
);
assert_eq!(
indent(Chunked("hello\n\n bar"), 4, true, false).unwrap().to_string(),
" hello\n\n bar"
);
assert_eq!(
indent(Chunked("hello\n\n bar"), 4, true, true).unwrap().to_string(),
" hello\n \n bar"
);
}
#[test] #[test]
#[allow(clippy::arc_with_non_send_sync)] // it's only a test, it does not have to make sense #[allow(clippy::arc_with_non_send_sync)] // it's only a test, it does not have to make sense
#[allow(clippy::type_complexity)] // it's only a test, it does not have to be pretty #[allow(clippy::type_complexity)] // it's only a test, it does not have to be pretty