From 7d0f2739f20612f4d49a334bf6c17db39694d924 Mon Sep 17 00:00:00 2001 From: Terr Date: Mon, 11 Nov 2024 11:50:40 +0100 Subject: [PATCH] Correctly indent lists directly after header on first format --- src/formatting.rs | 4 +++- src/lib.rs | 13 ++++++++-- src/main.rs | 6 ++--- src/parsing.rs | 25 +++++++++++--------- tests/bullet_points.expected | 5 ++++ tests/bullet_points.input | 4 ++++ tests/{1.expected => full_document.expected} | 0 tests/{1.input => full_document.input} | 0 8 files changed, 40 insertions(+), 17 deletions(-) rename tests/{1.expected => full_document.expected} (100%) rename tests/{1.input => full_document.input} (100%) diff --git a/src/formatting.rs b/src/formatting.rs index d9bef11..b42d79c 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -45,7 +45,9 @@ pub fn wrap_long_lines(formatted_lines: &mut Vec, max_line_length } // Find a word boundary to split the string at - let Some(split_pos) = find_word_boundary(current_line, max_line_length) else { continue; }; + let Some(split_pos) = find_word_boundary(current_line, max_line_length) else { + continue; + }; // This FormattedLine will be placed below (line index + 1) the `current_line` in // the document diff --git a/src/lib.rs b/src/lib.rs index 0e623e4..c6f0fba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,13 +70,17 @@ impl Default for Document { pub struct Block { contents: Vec, header: FormattedLine, + is_before_first_header: bool, } impl Block { pub fn new(header: FormattedLine) -> Self { + let is_before_first_header = header.is_empty(); + Block { header, contents: Vec::new(), + is_before_first_header, } } @@ -84,8 +88,13 @@ impl Block { self.contents.push(line); } - fn indent_level(&self) -> usize { - self.header.indent_level + /// Returns the indentation level of the text contents the `Block` and sibling headers + fn contents_indent_level(&self) -> usize { + if self.is_before_first_header { + 0 + } else { + self.header.indent_level + 1 + } } fn has_header(&self) -> bool { diff --git a/src/main.rs b/src/main.rs index 3baa89e..a219819 100644 --- a/src/main.rs +++ b/src/main.rs @@ -76,8 +76,8 @@ mod tests { #[test] fn format_full_document() { - let actual = format_file(Path::new("tests/1.input")); - let expected = read_file(Path::new("tests/1.expected")).unwrap(); + let actual = format_file(Path::new("tests/full_document.input")); + let expected = read_file(Path::new("tests/full_document.expected")).unwrap(); assert_equal(&actual, &expected); } @@ -85,7 +85,7 @@ mod tests { /// Formatting a text for a second time should not result in a different output #[test] fn format_full_document_twice() { - let first_format = format_file(Path::new("tests/1.input")); + let first_format = format_file(Path::new("tests/full_document.input")); let second_format = format(&first_format); assert_equal(&second_format, &first_format); diff --git a/src/parsing.rs b/src/parsing.rs index a416ad7..eff4503 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -60,7 +60,7 @@ pub fn parse_document(contents: &str) -> Document { // Preserve the existing indenting of the text/code in these lines that would // otherwise be trimmed off. FormattedLine { - indent_level: current_block.indent_level() + 1, + indent_level: current_block.contents_indent_level(), line_type: LineType::Preformatted, contents: format!( "{preformat_indent}{text}", @@ -89,16 +89,16 @@ fn determine_new_header_indent(document: &Document, raw_line: &RawLine) -> usize match previous_block.raw_header_indent().cmp(&raw_line.num_indent) { // New header is a sibling (at the same level) of the previous header - Ordering::Equal => previous_block.indent_level(), + Ordering::Equal => previous_block.contents_indent_level().saturating_sub(1), // New header is a parent of *a* previous header Ordering::Greater => document .find_latest_block_with_raw_indent(raw_line.num_indent) - .map(|block| block.indent_level()) + .map(|block| block.contents_indent_level().saturating_sub(1)) .unwrap_or(0), // New header is a child of the previous header - Ordering::Less => previous_block.indent_level() + 1, + Ordering::Less => previous_block.contents_indent_level(), } } @@ -120,12 +120,15 @@ fn determine_new_bullet_point_indent(current_block: &Block, raw_line: &RawLine) Ordering::Equal => previous_list_item.indent_level, // List item is shifted one or more levels to the left compared to the previous - // bullet point in the list. Find the first line (starting from the last line) - // that had the same indenting in the original raw file. + // bullet point in the list. This can mean the previous list was interrupted by some + // text, signalling the end of that list and meaning that this is a new list. + // + // Find the first line (starting from the last line) that had the same indenting in the + // original raw file. Ordering::Greater => current_block .find_latest_line_with_raw_indent(raw_line.num_indent) .map(|line| line.indent_level) - .unwrap_or(current_block.indent_level() + 1), + .unwrap_or(current_block.contents_indent_level()), // List item is shifted right compared to the previous bullet point. Only one // level of indenting per line can be added per line. @@ -134,7 +137,7 @@ fn determine_new_bullet_point_indent(current_block: &Block, raw_line: &RawLine) } else if let Some(previous_text) = current_block.find_previous_of(LineType::Text) { previous_text.indent_level } else { - 0 + current_block.contents_indent_level() } } @@ -153,16 +156,16 @@ fn parse_text_line(current_block: &mut Block, raw_line: RawLine) -> FormattedLin } } else if current_block.has_header() { // Non-bullet list Contents of a block follow the block's indent level plus one - FormattedLine::from_raw(raw_line, current_block.indent_level() + 1) + FormattedLine::from_raw(raw_line, current_block.contents_indent_level()) } else { // This applies to empty lines and to lines of text that are placed before the // very first header of the document. - FormattedLine::from_raw(raw_line, current_block.indent_level()) + FormattedLine::from_raw(raw_line, current_block.contents_indent_level()) } } else { // This applies to the first line after a header. - FormattedLine::from_raw(raw_line, current_block.indent_level() + 1) + FormattedLine::from_raw(raw_line, current_block.contents_indent_level()) } } diff --git a/tests/bullet_points.expected b/tests/bullet_points.expected index d3d500a..4c8ad23 100644 --- a/tests/bullet_points.expected +++ b/tests/bullet_points.expected @@ -25,3 +25,8 @@ Text. * Bullet point 4.1 * Bullet point 4.1.1 * Bullet point 5 + +=== Header 2 + + * Bullet point 1 + * Bullet point 2 diff --git a/tests/bullet_points.input b/tests/bullet_points.input index 44d042b..58803d0 100644 --- a/tests/bullet_points.input +++ b/tests/bullet_points.input @@ -25,3 +25,7 @@ Text. * Bullet point 4.1 * Bullet point 4.1.1 * Bullet point 5 + +=== Header 2 + * Bullet point 1 + * Bullet point 2 diff --git a/tests/1.expected b/tests/full_document.expected similarity index 100% rename from tests/1.expected rename to tests/full_document.expected diff --git a/tests/1.input b/tests/full_document.input similarity index 100% rename from tests/1.input rename to tests/full_document.input