diff --git a/Cargo.lock b/Cargo.lock index 86431d42e91ca4..6123c3a0db60db 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 9c117e66653e93..e2f2fa190d3977 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 b6b22ef64d33f6..cfd9284f807650 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 cfffa584b6c214..48785dbaa55cfc 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 31a69918026f72..b17d94a5eb0f03 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 4fa1f10a8a17c9..e819032471f442 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 50214cd723ee31..d04b266e61802b 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 3e4ef174d422ae..2ec4f4a3b7b7bc 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 4f3e6194a022eb..fd631648c2c759 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 cd641636349e39..520311b6f3c625 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()]) });