From c3075dfe9afc788a1a0a2b965f0eb4f9a4ba77b4 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 27 Sep 2024 11:14:28 -0700 Subject: [PATCH] Fix bugs in diff hunk highlighting (#18454) Fixes https://github.com/zed-industries/zed/issues/18405 In https://github.com/zed-industries/zed/pull/18313, we introduced a problem where git addition highlights might spuriously return when undoing certain changes. It turned out, there were already some cases where git hunk highlighting was incorrect when editing at the boundaries of expanded diff hunks. In this PR, I've introduced a test helper method for more rigorously (and readably) testing the editor's git state. You can assert about the entire state of an editor's diff decorations using a formatted diff: ```rust cx.assert_diff_hunks( r#" - use some::mod1; use some::mod2; const A: u32 = 42; - const B: u32 = 42; const C: u32 = 42; fn main() { - println!("hello"); + //println!("hello"); println!("world"); + // + // } fn another() { println!("another"); + println!("another"); } - fn another2() { println!("another2"); } "# .unindent(), ); ``` This will assert about the editor's actual row highlights, not just the editor's internal hunk-tracking state. I rewrote all of our editor diff tests to use these more high-level assertions, and it caught the new bug, as well as some pre-existing bugs in the highlighting of added content. The problem was how we *remove* highlighted rows. Previously, it relied on supplying exactly the same range as one that we had previously highlighted. I've added a `remove_highlighted_rows(ranges)` APIs which is much simpler - it clears out any row ranges that intersect the given ranges (which is all that we need for the Git diff use case). Release Notes: - N/A --- Cargo.lock | 1 + crates/assistant/src/inline_assistant.rs | 4 +- crates/editor/Cargo.toml | 5 +- crates/editor/src/editor.rs | 157 +- crates/editor/src/editor_tests.rs | 1682 +++++------------ crates/editor/src/hunk_diff.rs | 45 +- crates/editor/src/test.rs | 113 -- crates/editor/src/test/editor_test_context.rs | 129 +- crates/go_to_line/src/go_to_line.rs | 2 +- crates/outline/src/outline.rs | 8 +- 10 files changed, 710 insertions(+), 1436 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85a62c9519e01..123141d188e0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3729,6 +3729,7 @@ dependencies = [ "multi_buffer", "ordered-float 2.10.1", "parking_lot", + "pretty_assertions", "project", "rand 0.8.5", "release_channel", diff --git a/crates/assistant/src/inline_assistant.rs b/crates/assistant/src/inline_assistant.rs index 9c117e66653e9..e2f2fa190d397 100644 --- a/crates/assistant/src/inline_assistant.rs +++ b/crates/assistant/src/inline_assistant.rs @@ -1142,7 +1142,7 @@ impl InlineAssistant { for row_range in inserted_row_ranges { editor.highlight_rows::( row_range, - Some(cx.theme().status().info_background), + cx.theme().status().info_background, false, cx, ); @@ -1209,7 +1209,7 @@ impl InlineAssistant { editor.set_show_inline_completions(Some(false), cx); editor.highlight_rows::( Anchor::min()..=Anchor::max(), - Some(cx.theme().status().deleted_background), + cx.theme().status().deleted_background, false, cx, ); diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index b6b22ef64d33f..cfd9284f80765 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -24,7 +24,8 @@ test-support = [ "workspace/test-support", "tree-sitter-rust", "tree-sitter-typescript", - "tree-sitter-html" + "tree-sitter-html", + "unindent", ] [dependencies] @@ -54,6 +55,7 @@ markdown.workspace = true multi_buffer.workspace = true ordered-float.workspace = true parking_lot.workspace = true +pretty_assertions.workspace = true project.workspace = true rand.workspace = true rpc.workspace = true @@ -74,6 +76,7 @@ theme.workspace = true tree-sitter-html = { workspace = true, optional = true } tree-sitter-rust = { workspace = true, optional = true } tree-sitter-typescript = { workspace = true, optional = true } +unindent = { workspace = true, optional = true } ui.workspace = true url.workspace = true util.workspace = true diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cfffa584b6c21..48785dbaa55cf 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -822,7 +822,7 @@ impl SelectionHistory { struct RowHighlight { index: usize, range: RangeInclusive, - color: Option, + color: Hsla, should_autoscroll: bool, } @@ -11500,41 +11500,125 @@ impl Editor { } } - /// Adds or removes (on `None` color) a highlight for the rows corresponding to the anchor range given. - /// On matching anchor range, replaces the old highlight; does not clear the other existing highlights. - /// If multiple anchor ranges will produce highlights for the same row, the last range added will be used. + /// Adds a row highlight for the given range. If a row has multiple highlights, the + /// last highlight added will be used. pub fn highlight_rows( &mut self, - rows: RangeInclusive, - color: Option, + range: RangeInclusive, + color: Hsla, should_autoscroll: bool, cx: &mut ViewContext, ) { let snapshot = self.buffer().read(cx).snapshot(cx); let row_highlights = self.highlighted_rows.entry(TypeId::of::()).or_default(); - let existing_highlight_index = row_highlights.binary_search_by(|highlight| { - highlight - .range - .start() - .cmp(rows.start(), &snapshot) - .then(highlight.range.end().cmp(rows.end(), &snapshot)) + let ix = row_highlights.binary_search_by(|highlight| { + Ordering::Equal + .then_with(|| highlight.range.start().cmp(&range.start(), &snapshot)) + .then_with(|| highlight.range.end().cmp(&range.end(), &snapshot)) }); - match (color, existing_highlight_index) { - (Some(_), Ok(ix)) | (_, Err(ix)) => row_highlights.insert( - ix, - RowHighlight { - index: post_inc(&mut self.highlight_order), - range: rows, - should_autoscroll, - color, - }, - ), - (None, Ok(i)) => { - row_highlights.remove(i); + + if let Err(mut ix) = ix { + let index = post_inc(&mut self.highlight_order); + + // If this range intersects with the preceding highlight, then merge it with + // the preceding highlight. Otherwise insert a new highlight. + let mut merged = false; + if ix > 0 { + let prev_highlight = &mut row_highlights[ix - 1]; + if prev_highlight + .range + .end() + .cmp(&range.start(), &snapshot) + .is_ge() + { + ix -= 1; + if prev_highlight + .range + .end() + .cmp(&range.end(), &snapshot) + .is_lt() + { + prev_highlight.range = *prev_highlight.range.start()..=*range.end(); + } + merged = true; + prev_highlight.index = index; + prev_highlight.color = color; + prev_highlight.should_autoscroll = should_autoscroll; + } + } + + if !merged { + row_highlights.insert( + ix, + RowHighlight { + range: range.clone(), + index, + color, + should_autoscroll, + }, + ); + } + + // If any of the following highlights intersect with this one, merge them. + while let Some(next_highlight) = row_highlights.get(ix + 1) { + let highlight = &row_highlights[ix]; + if next_highlight + .range + .start() + .cmp(&highlight.range.end(), &snapshot) + .is_le() + { + if next_highlight + .range + .end() + .cmp(&highlight.range.end(), &snapshot) + .is_gt() + { + row_highlights[ix].range = + *highlight.range.start()..=*next_highlight.range.end(); + } + row_highlights.remove(ix + 1); + } else { + break; + } } } } + /// Remove any highlighted row ranges of the given type that intersect the + /// given ranges. + pub fn remove_highlighted_rows( + &mut self, + ranges_to_remove: Vec>, + cx: &mut ViewContext, + ) { + let snapshot = self.buffer().read(cx).snapshot(cx); + let row_highlights = self.highlighted_rows.entry(TypeId::of::()).or_default(); + let mut ranges_to_remove = ranges_to_remove.iter().peekable(); + row_highlights.retain(|highlight| { + while let Some(range_to_remove) = ranges_to_remove.peek() { + match range_to_remove.end.cmp(&highlight.range.start(), &snapshot) { + Ordering::Less => { + ranges_to_remove.next(); + } + Ordering::Equal => { + return false; + } + Ordering::Greater => { + match range_to_remove.start.cmp(&highlight.range.end(), &snapshot) { + Ordering::Less | Ordering::Equal => { + return false; + } + Ordering::Greater => break, + } + } + } + } + + true + }) + } + /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted. pub fn clear_row_highlights(&mut self) { self.highlighted_rows.remove(&TypeId::of::()); @@ -11543,13 +11627,12 @@ impl Editor { /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting. pub fn highlighted_rows( &self, - ) -> Option, Option<&Hsla>)>> { - Some( - self.highlighted_rows - .get(&TypeId::of::())? - .iter() - .map(|highlight| (&highlight.range, highlight.color.as_ref())), - ) + ) -> impl '_ + Iterator, Hsla)> { + self.highlighted_rows + .get(&TypeId::of::()) + .map_or(&[] as &[_], |vec| vec.as_slice()) + .iter() + .map(|highlight| (highlight.range.clone(), highlight.color)) } /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict. @@ -11574,10 +11657,7 @@ impl Editor { used_highlight_orders.entry(row).or_insert(highlight.index); if highlight.index >= *used_index { *used_index = highlight.index; - match highlight.color { - Some(hsla) => unique_rows.insert(DisplayRow(row), hsla), - None => unique_rows.remove(&DisplayRow(row)), - }; + unique_rows.insert(DisplayRow(row), highlight.color); } } unique_rows @@ -11593,10 +11673,11 @@ impl Editor { .values() .flat_map(|highlighted_rows| highlighted_rows.iter()) .filter_map(|highlight| { - if highlight.color.is_none() || !highlight.should_autoscroll { - return None; + if highlight.should_autoscroll { + Some(highlight.range.start().to_display_point(snapshot).row()) + } else { + None } - Some(highlight.range.start().to_display_point(snapshot).row()) }) .min() } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 31a69918026f7..b17d94a5eb0f0 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -2,9 +2,8 @@ use super::*; use crate::{ scroll::scroll_amount::ScrollAmount, test::{ - assert_text_with_selections, build_editor, editor_hunks, - editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext, - expanded_hunks, expanded_hunks_background_highlights, select_ranges, + assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext, + editor_test_context::EditorTestContext, select_ranges, }, JoinLines, }; @@ -11196,36 +11195,30 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test cx.set_diff_base(Some(&diff_base)); executor.run_until_parked(); - let unexpanded_hunks = vec![ - ( - "use some::mod;\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(0)..DisplayRow(1), - ), - ( - "const A: u32 = 42;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(2)..DisplayRow(2), - ), - ( - " println!(\"hello\");\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(4)..DisplayRow(5), - ), - ( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(6)..DisplayRow(7), - ), - ]; + cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - assert_eq!(all_hunks, unexpanded_hunks); + editor.go_to_next_hunk(&GoToHunk, cx); + editor.toggle_hunk_diff(&ToggleHunkDiff, cx); }); + executor.run_until_parked(); + cx.assert_diff_hunks( + r#" + use some::modified; + + + fn main() { + - println!("hello"); + + println!("hello there"); + + println!("around the"); + println!("world"); + } + "# + .unindent(), + ); cx.update_editor(|editor, cx| { - for _ in 0..4 { + for _ in 0..3 { editor.go_to_next_hunk(&GoToHunk, cx); editor.toggle_hunk_diff(&ToggleHunkDiff, cx); } @@ -11245,57 +11238,47 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test "# .unindent(), ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - all_hunks, - vec![ - ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(2)..DisplayRow(3)), - ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(6)..DisplayRow(6)), - (" println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(10)..DisplayRow(11)), - ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(14)), - ], - "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \ - (from modified and removed hunks)" - ); - assert_eq!( - all_hunks, all_expanded_hunks, - "Editor hunks should not change and all be expanded" - ); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(2)..=DisplayRow(2), DisplayRow(10)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(13)], - "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks" - ); - }); + + cx.assert_diff_hunks( + r#" + - use some::mod; + + use some::modified; + + - const A: u32 = 42; + + fn main() { + - println!("hello"); + + println!("hello there"); + + + println!("around the"); + println!("world"); + } + "# + .unindent(), + ); cx.update_editor(|editor, cx| { editor.cancel(&Cancel, cx); - - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - Vec::new(), - "After cancelling in editor, no git highlights should be left" - ); - assert_eq!( - all_expanded_hunks, - Vec::new(), - "After cancelling in editor, no hunks should be expanded" - ); - assert_eq!( - all_hunks, unexpanded_hunks, - "After cancelling in editor, regular hunks' coordinates should get back to normal" - ); }); + + cx.assert_diff_hunks( + r#" + use some::modified; + + + fn main() { + println!("hello there"); + + println!("around the"); + println!("world"); + } + "# + .unindent(), + ); } #[gpui::test] -async fn test_toggled_diff_base_change( +async fn test_diff_base_change_with_expanded_diff_hunks( executor: BackgroundExecutor, cx: &mut gpui::TestAppContext, ) { @@ -11339,115 +11322,78 @@ async fn test_toggled_diff_base_change( cx.set_diff_base(Some(&diff_base)); executor.run_until_parked(); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - assert_eq!( - all_hunks, - vec![ - ( - "use some::mod1;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(0)..DisplayRow(0) - ), - ( - "const B: u32 = 42;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(3)..DisplayRow(3) - ), - ( - " println!(\"hello\");\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(6)..DisplayRow(7) - ), - ( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(9)..DisplayRow(11) - ), - ] - ); - }); cx.update_editor(|editor, cx| { editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx); }); executor.run_until_parked(); - cx.assert_editor_state( - &r#" - use some::mod2; + cx.assert_diff_hunks( + r#" + - use some::mod1; + use some::mod2; + + const A: u32 = 42; + - const B: u32 = 42; + const C: u32 = 42; + + fn main() { + - println!("hello"); + + //println!("hello"); + + println!("world"); + + // + + // + } + "# + .unindent(), + ); - const A: u32 = 42; - const C: u32 = 42; + cx.set_diff_base(Some("new diff base!")); + executor.run_until_parked(); + cx.assert_diff_hunks( + r#" + use some::mod2; - fn main(ˇ) { - //println!("hello"); + const A: u32 = 42; + const C: u32 = 42; - println!("world"); - // - // - } + fn main() { + //println!("hello"); + + println!("world"); + // + // + } "# .unindent(), ); cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - all_hunks, - vec![ - ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(2)..DisplayRow(2)), - ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(7)..DisplayRow(7)), - (" println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(12)..DisplayRow(13)), - ("".to_string(), DiffHunkStatus::Added, DisplayRow(16)..DisplayRow(18)), - ], - "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \ - (from modified and removed hunks)" - ); - assert_eq!( - all_hunks, all_expanded_hunks, - "Editor hunks should not change and all be expanded" - ); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(12)..=DisplayRow(12), DisplayRow(16)..=DisplayRow(17)], - "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks" - ); + editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx); }); - - cx.set_diff_base(Some("new diff base!")); executor.run_until_parked(); - - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - Vec::new(), - "After diff base is changed, old git highlights should be removed" - ); - assert_eq!( - all_expanded_hunks, - Vec::new(), - "After diff base is changed, old git hunk expansions should be removed" - ); - assert_eq!( - all_hunks, - vec![( - "new diff base!".to_string(), - DiffHunkStatus::Modified, - DisplayRow(0)..snapshot.display_snapshot.max_point().row() - )], - "After diff base is changed, hunks should update" - ); - }); + cx.assert_diff_hunks( + r#" + - new diff base! + + use some::mod2; + + + + const A: u32 = 42; + + const C: u32 = 42; + + + + fn main() { + + //println!("hello"); + + + + println!("world"); + + // + + // + + } + "# + .unindent(), + ); } #[gpui::test] -async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) { +async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx).await; @@ -11504,337 +11450,138 @@ async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::Test cx.set_diff_base(Some(&diff_base)); executor.run_until_parked(); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - assert_eq!( - all_hunks, - vec![ - ( - "use some::mod1;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(0)..DisplayRow(0) - ), - ( - "const B: u32 = 42;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(3)..DisplayRow(3) - ), - ( - " println!(\"hello\");\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(6)..DisplayRow(7) - ), - ( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(9)..DisplayRow(11) - ), - ( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(15)..DisplayRow(16) - ), - ( - "fn another2() {\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(18)..DisplayRow(18) - ), - ] - ); - }); cx.update_editor(|editor, cx| { editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx); }); executor.run_until_parked(); - cx.assert_editor_state( - &r#" - «use some::mod2; - const A: u32 = 42; - const C: u32 = 42; + cx.assert_diff_hunks( + r#" + - use some::mod1; + use some::mod2; - fn main() { - //println!("hello"); + const A: u32 = 42; + - const B: u32 = 42; + const C: u32 = 42; - println!("world"); - // - //ˇ» - } + fn main() { + - println!("hello"); + + //println!("hello"); - fn another() { - println!("another"); - println!("another"); - } + println!("world"); + + // + + // + } - println!("another2"); - } + fn another() { + println!("another"); + + println!("another"); + } + + - fn another2() { + println!("another2"); + } "# .unindent(), ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - all_hunks, - vec![ - ( - "use some::mod1;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(2)..DisplayRow(2) - ), - ( - "const B: u32 = 42;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(7)..DisplayRow(7) - ), - ( - " println!(\"hello\");\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(12)..DisplayRow(13) - ), - ( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(16)..DisplayRow(18) - ), - ( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(23)..DisplayRow(24) - ), - ( - "fn another2() {\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(28)..DisplayRow(28) - ), - ], - ); - assert_eq!(all_hunks, all_expanded_hunks); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![ - DisplayRow(12)..=DisplayRow(12), - DisplayRow(16)..=DisplayRow(17), - DisplayRow(23)..=DisplayRow(23) - ] - ); - }); + // Fold across some of the diff hunks. They should no longer appear expanded. cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx)); cx.executor().run_until_parked(); - cx.assert_editor_state( - &r#" - «use some::mod2; - const A: u32 = 42; - const C: u32 = 42; + // Hunks are not shown if their position is within a fold + cx.assert_diff_hunks( + r#" + use some::mod2; - fn main() { - //println!("hello"); + const A: u32 = 42; + const C: u32 = 42; - println!("world"); - // - //ˇ» - } + fn main() { + //println!("hello"); - fn another() { - println!("another"); - println!("another"); - } + println!("world"); + // + // + } - println!("another2"); - } + fn another() { + println!("another"); + + println!("another"); + } + + - fn another2() { + println!("another2"); + } "# .unindent(), ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - all_hunks, - vec![ - ( - "use some::mod1;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(0)..DisplayRow(0) - ), - ( - "const B: u32 = 42;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(0)..DisplayRow(0) - ), - ( - " println!(\"hello\");\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(0)..DisplayRow(0) - ), - ( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(0)..DisplayRow(1) - ), - ( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(6)..DisplayRow(7) - ), - ( - "fn another2() {\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(11)..DisplayRow(11) - ), - ], - "Hunk list should still return shifted folded hunks" - ); - assert_eq!( - all_expanded_hunks, - vec![ - ( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(6)..DisplayRow(7) - ), - ( - "fn another2() {\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(11)..DisplayRow(11) - ), - ], - "Only non-folded hunks should be left expanded" - ); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(0)..=DisplayRow(0), DisplayRow(6)..=DisplayRow(6)], - "Only one hunk is left not folded, its highlight should be visible" - ); - }); cx.update_editor(|editor, cx| { editor.select_all(&SelectAll, cx); editor.unfold_lines(&UnfoldLines, cx); }); cx.executor().run_until_parked(); - cx.assert_editor_state( - &r#" - «use some::mod2; - const A: u32 = 42; - const C: u32 = 42; + // The deletions reappear when unfolding. + cx.assert_diff_hunks( + r#" + - use some::mod1; + use some::mod2; + + const A: u32 = 42; + - const B: u32 = 42; + const C: u32 = 42; + + fn main() { + - println!("hello"); + + //println!("hello"); + + println!("world"); + + // + + // + } + + fn another() { + println!("another"); + + println!("another"); + } + + - fn another2() { + println!("another2"); + } + "# + .unindent(), + ); +} - fn main() { - //println!("hello"); +#[gpui::test] +async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); - println!("world"); - // - // - } + let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj"; + let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj"; + let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu"; + let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu"; + let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!"; + let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!"; - fn another() { - println!("another"); - println!("another"); - } - - println!("another2"); - } - ˇ»"# - .unindent(), - ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - all_hunks, - vec![ - ( - "use some::mod1;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(2)..DisplayRow(2) - ), - ( - "const B: u32 = 42;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(7)..DisplayRow(7) - ), - ( - " println!(\"hello\");\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(12)..DisplayRow(13) - ), - ( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(16)..DisplayRow(18) - ), - ( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(23)..DisplayRow(24) - ), - ( - "fn another2() {\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(28)..DisplayRow(28) - ), - ], - ); - assert_eq!(all_hunks, all_expanded_hunks); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![ - DisplayRow(12)..=DisplayRow(12), - DisplayRow(16)..=DisplayRow(17), - DisplayRow(23)..=DisplayRow(23) - ], - "After unfolding, all hunk diffs should be visible again" - ); - }); -} - -#[gpui::test] -async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) { - init_test(cx, |_| {}); - - let cols = 4; - let rows = 10; - let sample_text_1 = sample_text(rows, cols, 'a'); - assert_eq!( - sample_text_1, - "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj" - ); - let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"; - let sample_text_2 = sample_text(rows, cols, 'l'); - assert_eq!( - sample_text_2, - "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu" - ); - let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"; - let sample_text_3 = sample_text(rows, cols, 'v'); - assert_eq!( - sample_text_3, - "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}" - ); - let modified_sample_text_3 = - "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"; - let buffer_1 = cx.new_model(|cx| { - let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx); - buffer.set_diff_base(Some(sample_text_1.clone()), cx); - buffer - }); - let buffer_2 = cx.new_model(|cx| { - let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx); - buffer.set_diff_base(Some(sample_text_2.clone()), cx); - buffer - }); - let buffer_3 = cx.new_model(|cx| { - let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx); - buffer.set_diff_base(Some(sample_text_3.clone()), cx); - buffer - }); + let buffer_1 = cx.new_model(|cx| { + let mut buffer = Buffer::local(file_1_new.to_string(), cx); + buffer.set_diff_base(Some(file_1_old.into()), cx); + buffer + }); + let buffer_2 = cx.new_model(|cx| { + let mut buffer = Buffer::local(file_2_new.to_string(), cx); + buffer.set_diff_base(Some(file_2_old.into()), cx); + buffer + }); + let buffer_3 = cx.new_model(|cx| { + let mut buffer = Buffer::local(file_3_new.to_string(), cx); + buffer.set_diff_base(Some(file_3_old.into()), cx); + buffer + }); let multi_buffer = cx.new_model(|cx| { let mut multibuffer = MultiBuffer::new(ReadWrite); @@ -11850,7 +11597,7 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) primary: None, }, ExcerptRange { - context: Point::new(9, 0)..Point::new(10, 4), + context: Point::new(9, 0)..Point::new(10, 3), primary: None, }, ], @@ -11868,7 +11615,7 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) primary: None, }, ExcerptRange { - context: Point::new(9, 0)..Point::new(10, 4), + context: Point::new(9, 0)..Point::new(10, 3), primary: None, }, ], @@ -11886,7 +11633,7 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) primary: None, }, ExcerptRange { - context: Point::new(9, 0)..Point::new(10, 4), + context: Point::new(9, 0)..Point::new(10, 3), primary: None, }, ], @@ -11895,143 +11642,81 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) multibuffer }); - let fs = FakeFs::new(cx.executor()); - fs.insert_tree( - "/a", - json!({ - "main.rs": modified_sample_text_1, - "other.rs": modified_sample_text_2, - "lib.rs": modified_sample_text_3, - }), - ) - .await; + let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx)); + let mut cx = EditorTestContext::for_editor(editor, cx).await; + cx.run_until_parked(); - let project = Project::test(fs, ["/a".as_ref()], cx).await; - let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); - let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx); - let multi_buffer_editor = cx.new_view(|cx| { - Editor::new( - EditorMode::Full, - multi_buffer, - Some(project.clone()), - true, - cx, - ) - }); - cx.executor().run_until_parked(); + cx.assert_editor_state( + &" + ˇaaa + ccc + ddd - let expected_all_hunks = vec![ - ( - "bbbb\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(4)..DisplayRow(4), - ), - ( - "nnnn\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(21)..DisplayRow(22), - ), - ( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(41)..DisplayRow(42), - ), - ]; - let expected_all_hunks_shifted = vec![ - ( - "bbbb\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(6)..DisplayRow(6), - ), - ( - "nnnn\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(25)..DisplayRow(26), - ), - ( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(46)..DisplayRow(47), - ), - ]; + ggg + hhh - multi_buffer_editor.update(cx, |editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new()); - assert_eq!(all_hunks, expected_all_hunks); - assert_eq!(all_expanded_hunks, Vec::new()); - }); - multi_buffer_editor.update(cx, |editor, cx| { + lll + mmm + NNN + + qqq + rrr + + uuu + 111 + 222 + 333 + + 666 + 777 + + 000 + !!!" + .unindent(), + ); + + cx.update_editor(|editor, cx| { editor.select_all(&SelectAll, cx); editor.toggle_hunk_diff(&ToggleHunkDiff, cx); }); cx.executor().run_until_parked(); - multi_buffer_editor.update(cx, |editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![ - DisplayRow(25)..=DisplayRow(25), - DisplayRow(46)..=DisplayRow(46) - ], - ); - assert_eq!(all_hunks, expected_all_hunks_shifted); - assert_eq!(all_hunks, all_expanded_hunks); - }); - multi_buffer_editor.update(cx, |editor, cx| { - editor.toggle_hunk_diff(&ToggleHunkDiff, cx); - }); - cx.executor().run_until_parked(); - multi_buffer_editor.update(cx, |editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new()); - assert_eq!(all_hunks, expected_all_hunks); - assert_eq!(all_expanded_hunks, Vec::new()); - }); + cx.assert_diff_hunks( + " + aaa + - bbb + ccc + ddd - multi_buffer_editor.update(cx, |editor, cx| { - editor.toggle_hunk_diff(&ToggleHunkDiff, cx); - }); - cx.executor().run_until_parked(); - multi_buffer_editor.update(cx, |editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![ - DisplayRow(25)..=DisplayRow(25), - DisplayRow(46)..=DisplayRow(46) - ], - ); - assert_eq!(all_hunks, expected_all_hunks_shifted); - assert_eq!(all_hunks, all_expanded_hunks); - }); + ggg + hhh - multi_buffer_editor.update(cx, |editor, cx| { - editor.toggle_hunk_diff(&ToggleHunkDiff, cx); - }); - cx.executor().run_until_parked(); - multi_buffer_editor.update(cx, |editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new()); - assert_eq!(all_hunks, expected_all_hunks); - assert_eq!(all_expanded_hunks, Vec::new()); - }); + + lll + mmm + - nnn + + NNN + + qqq + rrr + + uuu + 111 + 222 + 333 + + + 666 + 777 + + 000 + !!!" + .unindent(), + ); } #[gpui::test] -async fn test_edits_around_toggled_additions( +async fn test_edits_around_expanded_insertion_hunks( executor: BackgroundExecutor, cx: &mut gpui::TestAppContext, ) { @@ -12074,71 +11759,21 @@ async fn test_edits_around_toggled_additions( cx.set_diff_base(Some(&diff_base)); executor.run_until_parked(); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - assert_eq!( - all_hunks, - vec![( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(4)..DisplayRow(7) - )] - ); - }); + cx.update_editor(|editor, cx| { editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx); }); executor.run_until_parked(); - cx.assert_editor_state( - &r#" - use some::mod1; - use some::mod2; - - const A: u32 = 42; - const B: u32 = 42; - const C: u32 = 42; - ˇ - - fn main() { - println!("hello"); - println!("world"); - } - "# - .unindent(), - ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - all_hunks, - vec![( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(5)..DisplayRow(8) - )] - ); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(5)..=DisplayRow(7)] - ); - assert_eq!(all_hunks, all_expanded_hunks); - }); - - cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx)); - executor.run_until_parked(); - cx.assert_editor_state( - &r#" + cx.assert_diff_hunks( + r#" use some::mod1; use some::mod2; const A: u32 = 42; - const B: u32 = 42; - const C: u32 = 42; - const D: u32 = 42; - ˇ + + const B: u32 = 42; + + const C: u32 = 42; + + fn main() { println!("hello"); @@ -12148,134 +11783,20 @@ async fn test_edits_around_toggled_additions( "# .unindent(), ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - all_hunks, - vec![( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(5)..DisplayRow(9) - )] - ); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(5)..=DisplayRow(7)], - "Edited hunk should have one more line added" - ); - assert_eq!( - all_hunks, all_expanded_hunks, - "Expanded hunk should also grow with the addition" - ); - }); - cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx)); + cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx)); executor.run_until_parked(); - cx.assert_editor_state( - &r#" - use some::mod1; - use some::mod2; - - const A: u32 = 42; - const B: u32 = 42; - const C: u32 = 42; - const D: u32 = 42; - const E: u32 = 42; - ˇ - - fn main() { - println!("hello"); - println!("world"); - } - "# - .unindent(), - ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - all_hunks, - vec![( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(5)..DisplayRow(10) - )] - ); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(5)..=DisplayRow(7)], - "Edited hunk should have one more line added" - ); - assert_eq!(all_hunks, all_expanded_hunks); - }); - - cx.update_editor(|editor, cx| { - editor.move_up(&MoveUp, cx); - editor.delete_line(&DeleteLine, cx); - }); - executor.run_until_parked(); - cx.assert_editor_state( - &r#" + cx.assert_diff_hunks( + r#" use some::mod1; use some::mod2; const A: u32 = 42; - const B: u32 = 42; - const C: u32 = 42; - const D: u32 = 42; - ˇ - - fn main() { - println!("hello"); - - println!("world"); - } - "# - .unindent(), - ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - all_hunks, - vec![( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(5)..DisplayRow(9) - )] - ); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(5)..=DisplayRow(7)], - "Deleting a line should shrint the hunk" - ); - assert_eq!( - all_hunks, all_expanded_hunks, - "Expanded hunk should also shrink with the addition" - ); - }); - - cx.update_editor(|editor, cx| { - editor.move_up(&MoveUp, cx); - editor.delete_line(&DeleteLine, cx); - editor.move_up(&MoveUp, cx); - editor.delete_line(&DeleteLine, cx); - editor.move_up(&MoveUp, cx); - editor.delete_line(&DeleteLine, cx); - }); - executor.run_until_parked(); - cx.assert_editor_state( - &r#" - use some::mod1; - use some::mod2; - - const A: u32 = 42; - ˇ + + const B: u32 = 42; + + const C: u32 = 42; + + const D: u32 = 42; + + fn main() { println!("hello"); @@ -12285,148 +11806,21 @@ async fn test_edits_around_toggled_additions( "# .unindent(), ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - all_hunks, - vec![( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(6)..DisplayRow(7) - )] - ); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(6)..=DisplayRow(6)] - ); - assert_eq!(all_hunks, all_expanded_hunks); - }); - cx.update_editor(|editor, cx| { - editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx); - editor.delete_line(&DeleteLine, cx); - }); - executor.run_until_parked(); - cx.assert_editor_state( - &r#" - ˇ - - fn main() { - println!("hello"); - - println!("world"); - } - "# - .unindent(), - ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - all_hunks, - vec![ - ( - "use some::mod1;\nuse some::mod2;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(0)..DisplayRow(0) - ), - ( - "const A: u32 = 42;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(3)..DisplayRow(3) - ) - ] - ); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - Vec::new(), - "Should close all stale expanded addition hunks" - ); - assert_eq!( - all_expanded_hunks, - vec![( - "const A: u32 = 42;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(3)..DisplayRow(3) - )], - "Should open hunks that were adjacent to the stale addition one" - ); - }); -} - -#[gpui::test] -async fn test_edits_around_toggled_deletions( - executor: BackgroundExecutor, - cx: &mut gpui::TestAppContext, -) { - init_test(cx, |_| {}); - - let mut cx = EditorTestContext::new(cx).await; - - let diff_base = r#" - use some::mod1; - use some::mod2; - - const A: u32 = 42; - const B: u32 = 42; - const C: u32 = 42; - - - fn main() { - println!("hello"); - - println!("world"); - } - "# - .unindent(); + cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx)); executor.run_until_parked(); - cx.set_state( - &r#" - use some::mod1; - use some::mod2; - - ˇconst B: u32 = 42; - const C: u32 = 42; - - - fn main() { - println!("hello"); - - println!("world"); - } - "# - .unindent(), - ); - cx.set_diff_base(Some(&diff_base)); - executor.run_until_parked(); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - assert_eq!( - all_hunks, - vec![( - "const A: u32 = 42;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(3)..DisplayRow(3) - )] - ); - }); - cx.update_editor(|editor, cx| { - editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx); - }); - executor.run_until_parked(); - cx.assert_editor_state( - &r#" + cx.assert_diff_hunks( + r#" use some::mod1; - use some::mod2; - - ˇconst B: u32 = 42; - const C: u32 = 42; + use some::mod2; + const A: u32 = 42; + + const B: u32 = 42; + + const C: u32 = 42; + + const D: u32 = 42; + + const E: u32 = 42; + + fn main() { println!("hello"); @@ -12436,33 +11830,23 @@ async fn test_edits_around_toggled_deletions( "# .unindent(), ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new()); - assert_eq!( - all_hunks, - vec![( - "const A: u32 = 42;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(5)..DisplayRow(5) - )] - ); - assert_eq!(all_hunks, all_expanded_hunks); - }); cx.update_editor(|editor, cx| { + editor.move_up(&MoveUp, cx); editor.delete_line(&DeleteLine, cx); }); executor.run_until_parked(); - cx.assert_editor_state( - &r#" + + cx.assert_diff_hunks( + r#" use some::mod1; use some::mod2; - ˇconst C: u32 = 42; - + const A: u32 = 42; + + const B: u32 = 42; + + const C: u32 = 42; + + const D: u32 = 42; + + fn main() { println!("hello"); @@ -12472,27 +11856,13 @@ async fn test_edits_around_toggled_deletions( "# .unindent(), ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - Vec::new(), - "Deleted hunks do not highlight current editor's background" - ); - assert_eq!( - all_hunks, - vec![( - "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(6)..DisplayRow(6) - )] - ); - assert_eq!(all_hunks, all_expanded_hunks); - }); cx.update_editor(|editor, cx| { + editor.move_up(&MoveUp, cx); + editor.delete_line(&DeleteLine, cx); + editor.move_up(&MoveUp, cx); + editor.delete_line(&DeleteLine, cx); + editor.move_up(&MoveUp, cx); editor.delete_line(&DeleteLine, cx); }); executor.run_until_parked(); @@ -12501,6 +11871,7 @@ async fn test_edits_around_toggled_deletions( use some::mod1; use some::mod2; + const A: u32 = 42; ˇ fn main() { @@ -12511,33 +11882,15 @@ async fn test_edits_around_toggled_deletions( "# .unindent(), ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new()); - assert_eq!( - all_hunks, - vec![( - "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(7)..DisplayRow(7) - )] - ); - assert_eq!(all_hunks, all_expanded_hunks); - }); - cx.update_editor(|editor, cx| { - editor.handle_input("replacement", cx); - }); - executor.run_until_parked(); - cx.assert_editor_state( - &r#" + cx.assert_diff_hunks( + r#" use some::mod1; use some::mod2; - replacementˇ + const A: u32 = 42; + + fn main() { println!("hello"); @@ -12546,29 +11899,29 @@ async fn test_edits_around_toggled_deletions( "# .unindent(), ); + cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - all_hunks, - vec![( - "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(8)..DisplayRow(9) - )] - ); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(8)..=DisplayRow(8)], - "Modified expanded hunks should display additions and highlight their background" - ); - assert_eq!(all_hunks, all_expanded_hunks); + editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx); + editor.delete_line(&DeleteLine, cx); }); + executor.run_until_parked(); + cx.assert_diff_hunks( + r#" + + - const A: u32 = 42; + + fn main() { + println!("hello"); + + println!("world"); + } + "# + .unindent(), + ); } #[gpui::test] -async fn test_edits_around_toggled_modifications( +async fn test_edits_around_expanded_deletion_hunks( executor: BackgroundExecutor, cx: &mut gpui::TestAppContext, ) { @@ -12583,14 +11936,14 @@ async fn test_edits_around_toggled_modifications( const A: u32 = 42; const B: u32 = 42; const C: u32 = 42; - const D: u32 = 42; fn main() { println!("hello"); println!("world"); - }"# + } + "# .unindent(); executor.run_until_parked(); cx.set_state( @@ -12598,298 +11951,165 @@ async fn test_edits_around_toggled_modifications( use some::mod1; use some::mod2; - const A: u32 = 42; - const B: u32 = 42; - const C: u32 = 43ˇ - const D: u32 = 42; + ˇconst B: u32 = 42; + const C: u32 = 42; fn main() { println!("hello"); println!("world"); - }"# + } + "# .unindent(), ); cx.set_diff_base(Some(&diff_base)); executor.run_until_parked(); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - assert_eq!( - all_hunks, - vec![( - "const C: u32 = 42;\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(5)..DisplayRow(6) - )] - ); - }); + cx.update_editor(|editor, cx| { editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx); }); executor.run_until_parked(); - cx.assert_editor_state( - &r#" + + cx.assert_diff_hunks( + r#" use some::mod1; use some::mod2; - const A: u32 = 42; + - const A: u32 = 42; const B: u32 = 42; - const C: u32 = 43ˇ - const D: u32 = 42; + const C: u32 = 42; fn main() { println!("hello"); println!("world"); - }"# + } + "# .unindent(), ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(7)..=DisplayRow(7)], - ); - assert_eq!( - all_hunks, - vec![( - "const C: u32 = 42;\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(7)..DisplayRow(8) - )] - ); - assert_eq!(all_hunks, all_expanded_hunks); - }); cx.update_editor(|editor, cx| { - editor.handle_input("\nnew_line\n", cx); + editor.delete_line(&DeleteLine, cx); }); executor.run_until_parked(); cx.assert_editor_state( &r#" - use some::mod1; - use some::mod2; + use some::mod1; + use some::mod2; - const A: u32 = 42; - const B: u32 = 42; - const C: u32 = 43 - new_line - ˇ - const D: u32 = 42; + ˇconst C: u32 = 42; - fn main() { - println!("hello"); + fn main() { + println!("hello"); - println!("world"); - }"# + println!("world"); + } + "# .unindent(), ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(7)..=DisplayRow(7)], - "Modified hunk should grow highlighted lines on more text additions" - ); - assert_eq!( - all_hunks, - vec![( - "const C: u32 = 42;\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(7)..DisplayRow(10) - )] - ); - assert_eq!(all_hunks, all_expanded_hunks); - }); - - cx.update_editor(|editor, cx| { - editor.move_up(&MoveUp, cx); - editor.move_up(&MoveUp, cx); - editor.move_up(&MoveUp, cx); - editor.delete_line(&DeleteLine, cx); - }); - executor.run_until_parked(); - cx.assert_editor_state( - &r#" - use some::mod1; - use some::mod2; - - const A: u32 = 42; - ˇconst C: u32 = 43 - new_line + cx.assert_diff_hunks( + r#" + use some::mod1; + use some::mod2; - const D: u32 = 42; + - const A: u32 = 42; + - const B: u32 = 42; + const C: u32 = 42; - fn main() { - println!("hello"); + fn main() { + println!("hello"); - println!("world"); - }"# + println!("world"); + } + "# .unindent(), ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(7)..=DisplayRow(9)], - ); - assert_eq!( - all_hunks, - vec![( - "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(7)..DisplayRow(10) - )], - "Modified hunk should grow deleted lines on text deletions above" - ); - assert_eq!(all_hunks, all_expanded_hunks); - }); cx.update_editor(|editor, cx| { - editor.move_up(&MoveUp, cx); - editor.handle_input("v", cx); + editor.delete_line(&DeleteLine, cx); }); executor.run_until_parked(); cx.assert_editor_state( &r#" - use some::mod1; - use some::mod2; + use some::mod1; + use some::mod2; + + ˇ - vˇconst A: u32 = 42; - const C: u32 = 43 - new_line + fn main() { + println!("hello"); + + println!("world"); + } + "# + .unindent(), + ); + cx.assert_diff_hunks( + r#" + use some::mod1; + use some::mod2; - const D: u32 = 42; + - const A: u32 = 42; + - const B: u32 = 42; + - const C: u32 = 42; - fn main() { - println!("hello"); + fn main() { + println!("hello"); - println!("world"); - }"# + println!("world"); + } + "# .unindent(), ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(7)..=DisplayRow(10)], - "Modified hunk should grow deleted lines on text modifications above" - ); - assert_eq!( - all_hunks, - vec![( - "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(7)..DisplayRow(11) - )] - ); - assert_eq!(all_hunks, all_expanded_hunks); - }); cx.update_editor(|editor, cx| { - editor.move_down(&MoveDown, cx); - editor.move_down(&MoveDown, cx); - editor.delete_line(&DeleteLine, cx) + editor.handle_input("replacement", cx); }); executor.run_until_parked(); cx.assert_editor_state( &r#" - use some::mod1; - use some::mod2; - - vconst A: u32 = 42; - const C: u32 = 43 - ˇ - const D: u32 = 42; + use some::mod1; + use some::mod2; + replacementˇ - fn main() { - println!("hello"); + fn main() { + println!("hello"); - println!("world"); - }"# + println!("world"); + } + "# .unindent(), ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(7)..=DisplayRow(9)], - "Modified hunk should grow shrink lines on modification lines removal" - ); - assert_eq!( - all_hunks, - vec![( - "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(7)..DisplayRow(10) - )] - ); - assert_eq!(all_hunks, all_expanded_hunks); - }); - - cx.update_editor(|editor, cx| { - editor.move_up(&MoveUp, cx); - editor.move_up(&MoveUp, cx); - editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx); - editor.delete_line(&DeleteLine, cx) - }); - executor.run_until_parked(); - cx.assert_editor_state( - &r#" - use some::mod1; - use some::mod2; + cx.assert_diff_hunks( + r#" + use some::mod1; + use some::mod2; - ˇ + - const A: u32 = 42; + - const B: u32 = 42; + - const C: u32 = 42; + - + + replacement - fn main() { - println!("hello"); + fn main() { + println!("hello"); - println!("world"); - }"# + println!("world"); + } + "# .unindent(), ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - Vec::new(), - "Modified hunk should turn into a removed one on all modified lines removal" - ); - assert_eq!( - all_hunks, - vec![( - "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n" - .to_string(), - DiffHunkStatus::Removed, - DisplayRow(8)..DisplayRow(8) - )] - ); - assert_eq!(all_hunks, all_expanded_hunks); - }); } #[gpui::test] -async fn test_multiple_expanded_hunks_merge( +async fn test_edit_after_expanded_modification_hunk( executor: BackgroundExecutor, cx: &mut gpui::TestAppContext, ) { @@ -12913,7 +12133,7 @@ async fn test_multiple_expanded_hunks_merge( println!("world"); }"# .unindent(); - executor.run_until_parked(); + cx.set_state( &r#" use some::mod1; @@ -12935,30 +12155,20 @@ async fn test_multiple_expanded_hunks_merge( cx.set_diff_base(Some(&diff_base)); executor.run_until_parked(); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - assert_eq!( - all_hunks, - vec![( - "const C: u32 = 42;\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(5)..DisplayRow(6) - )] - ); - }); cx.update_editor(|editor, cx| { editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx); }); executor.run_until_parked(); - cx.assert_editor_state( - &r#" + + cx.assert_diff_hunks( + r#" use some::mod1; use some::mod2; const A: u32 = 42; const B: u32 = 42; - const C: u32 = 43ˇ + - const C: u32 = 42; + + const C: u32 = 43 const D: u32 = 42; @@ -12969,47 +12179,31 @@ async fn test_multiple_expanded_hunks_merge( }"# .unindent(), ); - cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(7)..=DisplayRow(7)], - ); - assert_eq!( - all_hunks, - vec![( - "const C: u32 = 42;\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(7)..DisplayRow(8) - )] - ); - assert_eq!(all_hunks, all_expanded_hunks); - }); cx.update_editor(|editor, cx| { editor.handle_input("\nnew_line\n", cx); }); executor.run_until_parked(); - cx.assert_editor_state( - &r#" - use some::mod1; - use some::mod2; - const A: u32 = 42; - const B: u32 = 42; - const C: u32 = 43 - new_line - ˇ - const D: u32 = 42; + cx.assert_diff_hunks( + r#" + use some::mod1; + use some::mod2; + + const A: u32 = 42; + const B: u32 = 42; + - const C: u32 = 42; + + const C: u32 = 43 + + new_line + + + const D: u32 = 42; - fn main() { - println!("hello"); + fn main() { + println!("hello"); - println!("world"); - }"# + println!("world"); + }"# .unindent(), ); } diff --git a/crates/editor/src/hunk_diff.rs b/crates/editor/src/hunk_diff.rs index 4fa1f10a8a17c..e819032471f44 100644 --- a/crates/editor/src/hunk_diff.rs +++ b/crates/editor/src/hunk_diff.rs @@ -19,8 +19,8 @@ use util::RangeExt; use crate::{ editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, BlockDisposition, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight, DisplayRow, DisplaySnapshot, - Editor, EditorElement, EditorSnapshot, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk, - RangeToAnchorExt, RevertFile, RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff, + Editor, EditorElement, EditorSnapshot, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk, RevertFile, + RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff, }; #[derive(Debug, Clone)] @@ -219,14 +219,7 @@ impl Editor { }); } - for removed_rows in highlights_to_remove { - editor.highlight_rows::( - to_inclusive_row_range(removed_rows, &snapshot), - None, - false, - cx, - ); - } + editor.remove_highlighted_rows::(highlights_to_remove, cx); editor.remove_blocks(blocks_to_remove, None, cx); for hunk in hunks_to_expand { editor.expand_diff_hunk(None, &hunk, cx); @@ -306,7 +299,7 @@ impl Editor { DiffHunkStatus::Added => { self.highlight_rows::( to_inclusive_row_range(hunk_start..hunk_end, &snapshot), - Some(added_hunk_color(cx)), + added_hunk_color(cx), false, cx, ); @@ -315,7 +308,7 @@ impl Editor { DiffHunkStatus::Modified => { self.highlight_rows::( to_inclusive_row_range(hunk_start..hunk_end, &snapshot), - Some(added_hunk_color(cx)), + added_hunk_color(cx), false, cx, ); @@ -850,14 +843,7 @@ impl Editor { retain }); - for removed_rows in highlights_to_remove { - editor.highlight_rows::( - to_inclusive_row_range(removed_rows, &snapshot), - None, - false, - cx, - ); - } + editor.remove_highlighted_rows::(highlights_to_remove, cx); editor.remove_blocks(blocks_to_remove, None, cx); if let Some(diff_base_buffer) = &diff_base_buffer { @@ -978,7 +964,7 @@ fn editor_with_deleted_text( editor.set_show_inline_completions(Some(false), cx); editor.highlight_rows::( Anchor::min()..=Anchor::max(), - Some(deleted_color), + deleted_color, false, cx, ); @@ -1060,15 +1046,16 @@ fn to_inclusive_row_range( row_range: Range, snapshot: &EditorSnapshot, ) -> RangeInclusive { - let mut display_row_range = - row_range.start.to_display_point(snapshot)..row_range.end.to_display_point(snapshot); - if display_row_range.end.row() > display_row_range.start.row() { - *display_row_range.end.row_mut() -= 1; + let mut end = row_range.end.to_point(&snapshot.buffer_snapshot); + if end.column == 0 && end.row > 0 { + end = Point::new( + end.row - 1, + snapshot + .buffer_snapshot + .line_len(MultiBufferRow(end.row - 1)), + ); } - let point_range = display_row_range.start.to_point(&snapshot.display_snapshot) - ..display_row_range.end.to_point(&snapshot.display_snapshot); - let new_range = point_range.to_anchors(&snapshot.buffer_snapshot); - new_range.start..=new_range.end + row_range.start..=snapshot.buffer_snapshot.anchor_after(end) } impl DisplayDiffHunk { diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 50214cd723ee3..d04b266e61802 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -88,116 +88,3 @@ pub(crate) fn build_editor_with_project( ) -> Editor { Editor::new(EditorMode::Full, buffer, Some(project), true, cx) } - -#[cfg(any(test, feature = "test-support"))] -pub fn editor_hunks( - editor: &Editor, - snapshot: &DisplaySnapshot, - cx: &mut ViewContext<'_, Editor>, -) -> Vec<( - String, - git::diff::DiffHunkStatus, - std::ops::Range, -)> { - use multi_buffer::MultiBufferRow; - use text::Point; - - use crate::hunk_status; - - snapshot - .buffer_snapshot - .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX) - .map(|hunk| { - let display_range = Point::new(hunk.row_range.start.0, 0) - .to_display_point(snapshot) - .row() - ..Point::new(hunk.row_range.end.0, 0) - .to_display_point(snapshot) - .row(); - let (_, buffer, _) = editor - .buffer() - .read(cx) - .excerpt_containing(Point::new(hunk.row_range.start.0, 0), cx) - .expect("no excerpt for expanded buffer's hunk start"); - let diff_base = buffer - .read(cx) - .diff_base() - .expect("should have a diff base for expanded hunk") - .slice(hunk.diff_base_byte_range.clone()) - .to_string(); - (diff_base, hunk_status(&hunk), display_range) - }) - .collect() -} - -#[cfg(any(test, feature = "test-support"))] -pub fn expanded_hunks( - editor: &Editor, - snapshot: &DisplaySnapshot, - cx: &mut ViewContext<'_, Editor>, -) -> Vec<( - String, - git::diff::DiffHunkStatus, - std::ops::Range, -)> { - editor - .expanded_hunks - .hunks(false) - .map(|expanded_hunk| { - let hunk_display_range = expanded_hunk - .hunk_range - .start - .to_display_point(snapshot) - .row() - ..expanded_hunk - .hunk_range - .end - .to_display_point(snapshot) - .row(); - let (_, buffer, _) = editor - .buffer() - .read(cx) - .excerpt_containing(expanded_hunk.hunk_range.start, cx) - .expect("no excerpt for expanded buffer's hunk start"); - let diff_base = buffer - .read(cx) - .diff_base() - .expect("should have a diff base for expanded hunk") - .slice(expanded_hunk.diff_base_byte_range.clone()) - .to_string(); - (diff_base, expanded_hunk.status, hunk_display_range) - }) - .collect() -} - -#[cfg(any(test, feature = "test-support"))] -pub fn expanded_hunks_background_highlights( - editor: &mut Editor, - cx: &mut gpui::WindowContext, -) -> Vec> { - use crate::DisplayRow; - - let mut highlights = Vec::new(); - - let mut range_start = 0; - let mut previous_highlighted_row = None; - for (highlighted_row, _) in editor.highlighted_display_rows(cx) { - match previous_highlighted_row { - Some(previous_row) => { - if previous_row + 1 != highlighted_row.0 { - highlights.push(DisplayRow(range_start)..=DisplayRow(previous_row)); - range_start = highlighted_row.0; - } - } - None => { - range_start = highlighted_row.0; - } - } - previous_highlighted_row = Some(highlighted_row.0); - } - if let Some(previous_row) = previous_highlighted_row { - highlights.push(DisplayRow(range_start)..=DisplayRow(previous_row)); - } - - highlights -} diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index 3e4ef174d422a..2ec4f4a3b7b7b 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -1,17 +1,18 @@ use crate::{ - display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer, - RowExt, + display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DiffRowHighlight, DisplayPoint, + Editor, MultiBuffer, RowExt, }; use collections::BTreeMap; use futures::Future; +use git::diff::DiffHunkStatus; use gpui::{ AnyWindowHandle, AppContext, Keystroke, ModelContext, Pixels, Point, View, ViewContext, - VisualTestContext, + VisualTestContext, WindowHandle, }; use indoc::indoc; use itertools::Itertools; use language::{Buffer, BufferSnapshot, LanguageRegistry}; -use multi_buffer::ExcerptRange; +use multi_buffer::{ExcerptRange, ToPoint}; use parking_lot::RwLock; use project::{FakeFs, Project}; use std::{ @@ -71,6 +72,16 @@ impl EditorTestContext { } } + pub async fn for_editor(editor: WindowHandle, cx: &mut gpui::TestAppContext) -> Self { + let editor_view = editor.root_view(cx).unwrap(); + Self { + cx: VisualTestContext::from_window(*editor.deref(), cx), + window: editor.into(), + editor: editor_view, + assertion_cx: AssertionContextManager::new(), + } + } + pub fn new_multibuffer( cx: &mut gpui::TestAppContext, excerpts: [&str; COUNT], @@ -297,6 +308,76 @@ impl EditorTestContext { state_context } + #[track_caller] + pub fn assert_diff_hunks(&mut self, expected_diff: String) { + // Normalize the expected diff. If it has no diff markers, then insert blank markers + // before each line. Strip any whitespace-only lines. + let has_diff_markers = expected_diff + .lines() + .any(|line| line.starts_with("+") || line.starts_with("-")); + let expected_diff_text = expected_diff + .split('\n') + .map(|line| { + let trimmed = line.trim(); + if trimmed.is_empty() { + String::new() + } else if has_diff_markers { + line.to_string() + } else { + format!(" {line}") + } + }) + .join("\n"); + + // Read the actual diff from the editor's row highlights and block + // decorations. + let actual_diff = self.editor.update(&mut self.cx, |editor, cx| { + let snapshot = editor.snapshot(cx); + let text = editor.text(cx); + let insertions = editor + .highlighted_rows::() + .map(|(range, _)| { + range.start().to_point(&snapshot.buffer_snapshot).row + ..range.end().to_point(&snapshot.buffer_snapshot).row + 1 + }) + .collect::>(); + let deletions = editor + .expanded_hunks + .hunks + .iter() + .filter_map(|hunk| { + if hunk.blocks.is_empty() { + return None; + } + let row = hunk + .hunk_range + .start + .to_point(&snapshot.buffer_snapshot) + .row; + let (_, buffer, _) = editor + .buffer() + .read(cx) + .excerpt_containing(hunk.hunk_range.start, cx) + .expect("no excerpt for expanded buffer's hunk start"); + let deleted_text = buffer + .read(cx) + .diff_base() + .expect("should have a diff base for expanded hunk") + .slice(hunk.diff_base_byte_range.clone()) + .to_string(); + if let DiffHunkStatus::Modified | DiffHunkStatus::Removed = hunk.status { + Some((row, deleted_text)) + } else { + None + } + }) + .collect::>(); + format_diff(text, deletions, insertions) + }); + + pretty_assertions::assert_eq!(actual_diff, expected_diff_text, "unexpected diff state"); + } + /// Make an assertion about the editor's text and the ranges and directions /// of its selections using a string containing embedded range markers. /// @@ -401,6 +482,46 @@ impl EditorTestContext { } } +fn format_diff( + text: String, + actual_deletions: Vec<(u32, String)>, + actual_insertions: Vec>, +) -> String { + let mut diff = String::new(); + for (row, line) in text.split('\n').enumerate() { + let row = row as u32; + if row > 0 { + diff.push('\n'); + } + if let Some(text) = actual_deletions + .iter() + .find_map(|(deletion_row, deleted_text)| { + if *deletion_row == row { + Some(deleted_text) + } else { + None + } + }) + { + for line in text.lines() { + diff.push('-'); + if !line.is_empty() { + diff.push(' '); + diff.push_str(line); + } + diff.push('\n'); + } + } + let marker = if actual_insertions.iter().any(|range| range.contains(&row)) { + "+ " + } else { + " " + }; + diff.push_str(format!("{marker}{line}").trim_end()); + } + diff +} + impl Deref for EditorTestContext { type Target = gpui::VisualTestContext; diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index 4f3e6194a022e..fd631648c2c75 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -121,7 +121,7 @@ impl GoToLine { active_editor.clear_row_highlights::(); active_editor.highlight_rows::( anchor..=anchor, - Some(cx.theme().colors().editor_highlighted_line_background), + cx.theme().colors().editor_highlighted_line_background, true, cx, ); diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index cd641636349e3..520311b6f3c62 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -144,7 +144,7 @@ impl OutlineViewDelegate { active_editor.clear_row_highlights::(); active_editor.highlight_rows::( outline_item.range.start..=outline_item.range.end, - Some(cx.theme().colors().editor_highlighted_line_background), + cx.theme().colors().editor_highlighted_line_background, true, cx, ); @@ -240,10 +240,10 @@ impl PickerDelegate for OutlineViewDelegate { self.prev_scroll_position.take(); self.active_editor.update(cx, |active_editor, cx| { - if let Some(rows) = active_editor + let highlight = active_editor .highlighted_rows::() - .and_then(|highlights| highlights.into_iter().next().map(|(rows, _)| rows.clone())) - { + .next(); + if let Some((rows, _)) = highlight { active_editor.change_selections(Some(Autoscroll::center()), cx, |s| { s.select_ranges([*rows.start()..*rows.start()]) });