From 56080771e61f95b387b5f737bab158ca28fa05f8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Jan 2023 17:02:38 -0800 Subject: [PATCH] Add test for avoiding indent adjustment inside newly-created errors --- crates/language/src/buffer.rs | 10 ++ crates/language/src/buffer_tests.rs | 207 ++++++++++++++++++++-------- crates/language/src/syntax_map.rs | 48 +------ crates/text/src/text.rs | 51 +++++++ 4 files changed, 212 insertions(+), 104 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 5c966bbc72..110e10564c 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1666,6 +1666,16 @@ impl Buffer { #[cfg(any(test, feature = "test-support"))] impl Buffer { + pub fn edit_via_marked_text( + &mut self, + marked_string: &str, + autoindent_mode: Option, + cx: &mut ModelContext, + ) { + let edits = self.edits_for_marked_text(marked_string); + self.edit(edits, autoindent_mode, cx); + } + pub fn set_group_interval(&mut self, group_interval: Duration) { self.text.set_group_interval(group_interval); } diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 382311e204..0b2ef1d7a7 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -800,23 +800,29 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta cx.set_global(settings); cx.add_model(|cx| { - let text = " + let mut buffer = Buffer::new( + 0, + " fn a() { c; d; } - " - .unindent(); - - let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + " + .unindent(), + cx, + ) + .with_language(Arc::new(rust_lang()), cx); // Lines 2 and 3 don't match the indentation suggestion. When editing these lines, // their indentation is not adjusted. - buffer.edit( - [ - (empty(Point::new(1, 1)), "()"), - (empty(Point::new(2, 1)), "()"), - ], + buffer.edit_via_marked_text( + &" + fn a() { + c«()»; + d«()»; + } + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); @@ -833,14 +839,22 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta // When appending new content after these lines, the indentation is based on the // preceding lines' actual indentation. - buffer.edit( - [ - (empty(Point::new(1, 1)), "\n.f\n.g"), - (empty(Point::new(2, 1)), "\n.f\n.g"), - ], + buffer.edit_via_marked_text( + &" + fn a() { + c« + .f + .g()»; + d« + .f + .g()»; + } + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); + assert_eq!( buffer.text(), " @@ -859,20 +873,27 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta }); cx.add_model(|cx| { - let text = " + let mut buffer = Buffer::new( + 0, + " fn a() { - { - b()? - } - Ok(()) - } - " - .unindent(); - let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + b(); + | + " + .replace("|", "") // marker to preserve trailing whitespace + .unindent(), + cx, + ) + .with_language(Arc::new(rust_lang()), cx); - // Delete a closing curly brace changes the suggested indent for the line. - buffer.edit( - [(Point::new(3, 4)..Point::new(3, 5), "")], + // Insert a closing brace. It is outdented. + buffer.edit_via_marked_text( + &" + fn a() { + b(); + «}» + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); @@ -880,19 +901,20 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta buffer.text(), " fn a() { - { - b()? - | - Ok(()) + b(); } " - .replace('|', "") // included in the string to preserve trailing whites .unindent() ); - // Manually editing the leading whitespace - buffer.edit( - [(Point::new(3, 0)..Point::new(3, 12), "")], + // Manually edit the leading whitespace. The edit is preserved. + buffer.edit_via_marked_text( + &" + fn a() { + b(); + « »} + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); @@ -900,11 +922,8 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta buffer.text(), " fn a() { - { - b()? - - Ok(()) - } + b(); + } " .unindent() ); @@ -913,30 +932,108 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta } #[gpui::test] -fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppContext) { - cx.set_global(Settings::test(cx)); +fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut MutableAppContext) { + let settings = Settings::test(cx); + cx.set_global(settings); + cx.add_model(|cx| { - let text = " - fn a() {} - " - .unindent(); + let mut buffer = Buffer::new( + 0, + " + fn a() { + i + } + " + .unindent(), + cx, + ) + .with_language(Arc::new(rust_lang()), cx); - let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); - - buffer.edit([(5..5, "\nb")], Some(AutoindentMode::EachLine), cx); + // Regression test: line does not get outdented due to syntax error + buffer.edit_via_marked_text( + &" + fn a() { + i«f let Some(x) = y» + } + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); assert_eq!( buffer.text(), " - fn a( - b) {} + fn a() { + if let Some(x) = y + } + " + .unindent() + ); + + buffer.edit_via_marked_text( + &" + fn a() { + if let Some(x) = y« {» + } + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " + fn a() { + if let Some(x) = y { + } + " + .unindent() + ); + + buffer + }); +} + +#[gpui::test] +fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppContext) { + cx.set_global(Settings::test(cx)); + cx.add_model(|cx| { + let mut buffer = Buffer::new( + 0, + " + fn a() {} + " + .unindent(), + cx, + ) + .with_language(Arc::new(rust_lang()), cx); + + buffer.edit_via_marked_text( + &" + fn a(« + b») {} + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " + fn a( + b) {} " .unindent() ); // The indentation suggestion changed because `@end` node (a close paren) // is now at the beginning of the line. - buffer.edit( - [(Point::new(1, 4)..Point::new(1, 5), "")], + buffer.edit_via_marked_text( + &" + fn a( + ˇ) {} + " + .unindent(), Some(AutoindentMode::EachLine), cx, ); @@ -1894,7 +1991,3 @@ fn get_tree_sexp(buffer: &ModelHandle, cx: &gpui::TestAppContext) -> Str layers[0].node.to_sexp() }) } - -fn empty(point: Point) -> Range { - point..point -} diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 458ffd8bc2..8d66730854 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -2262,7 +2262,7 @@ mod tests { mutated_syntax_map.reparse(language.clone(), &buffer); for (i, marked_string) in steps.into_iter().enumerate() { - edit_buffer(&mut buffer, &marked_string.unindent()); + buffer.edit_via_marked_text(&marked_string.unindent()); // Reparse the syntax map mutated_syntax_map.interpolate(&buffer); @@ -2452,52 +2452,6 @@ mod tests { assert_eq!(actual_ranges, expected_ranges); } - fn edit_buffer(buffer: &mut Buffer, marked_string: &str) { - let old_text = buffer.text(); - let (new_text, mut ranges) = marked_text_ranges(marked_string, false); - if ranges.is_empty() { - ranges.push(0..new_text.len()); - } - - assert_eq!( - old_text[..ranges[0].start], - new_text[..ranges[0].start], - "invalid edit" - ); - - let mut delta = 0; - let mut edits = Vec::new(); - let mut ranges = ranges.into_iter().peekable(); - - while let Some(inserted_range) = ranges.next() { - let new_start = inserted_range.start; - let old_start = (new_start as isize - delta) as usize; - - let following_text = if let Some(next_range) = ranges.peek() { - &new_text[inserted_range.end..next_range.start] - } else { - &new_text[inserted_range.end..] - }; - - let inserted_len = inserted_range.len(); - let deleted_len = old_text[old_start..] - .find(following_text) - .expect("invalid edit"); - - let old_range = old_start..old_start + deleted_len; - edits.push((old_range, new_text[inserted_range].to_string())); - delta += inserted_len as isize - deleted_len as isize; - } - - assert_eq!( - old_text.len() as isize + delta, - new_text.len() as isize, - "invalid edit" - ); - - buffer.edit(edits); - } - pub fn string_contains_sequence(text: &str, parts: &[&str]) -> bool { let mut last_part_end = 0; for part in parts { diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 914023f305..c7d36e29de 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1372,6 +1372,57 @@ impl Buffer { #[cfg(any(test, feature = "test-support"))] impl Buffer { + pub fn edit_via_marked_text(&mut self, marked_string: &str) { + let edits = self.edits_for_marked_text(marked_string); + self.edit(edits); + } + + pub fn edits_for_marked_text(&self, marked_string: &str) -> Vec<(Range, String)> { + let old_text = self.text(); + let (new_text, mut ranges) = util::test::marked_text_ranges(marked_string, false); + if ranges.is_empty() { + ranges.push(0..new_text.len()); + } + + assert_eq!( + old_text[..ranges[0].start], + new_text[..ranges[0].start], + "invalid edit" + ); + + let mut delta = 0; + let mut edits = Vec::new(); + let mut ranges = ranges.into_iter().peekable(); + + while let Some(inserted_range) = ranges.next() { + let new_start = inserted_range.start; + let old_start = (new_start as isize - delta) as usize; + + let following_text = if let Some(next_range) = ranges.peek() { + &new_text[inserted_range.end..next_range.start] + } else { + &new_text[inserted_range.end..] + }; + + let inserted_len = inserted_range.len(); + let deleted_len = old_text[old_start..] + .find(following_text) + .expect("invalid edit"); + + let old_range = old_start..old_start + deleted_len; + edits.push((old_range, new_text[inserted_range].to_string())); + delta += inserted_len as isize - deleted_len as isize; + } + + assert_eq!( + old_text.len() as isize + delta, + new_text.len() as isize, + "invalid edit" + ); + + edits + } + pub fn check_invariants(&self) { // Ensure every fragment is ordered by locator in the fragment tree and corresponds // to an insertion fragment in the insertions tree.