diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 121c936563..d2835edc61 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -7,18 +7,12 @@ use collections::HashMap; use editor::{ actions::{ ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, Redo, Rename, - RevertSelectedHunks, ToggleCodeActions, Undo, - }, - display_map::DisplayRow, - test::{ - editor_hunks, - editor_test_context::{AssertionContextManager, EditorTestContext}, - expanded_hunks, expanded_hunks_background_highlights, + ToggleCodeActions, Undo, }, + test::editor_test_context::{AssertionContextManager, EditorTestContext}, Editor, }; use futures::StreamExt; -use git::diff::DiffHunkStatus; use gpui::{TestAppContext, UpdateGlobal, VisualContext, VisualTestContext}; use indoc::indoc; use language::{ @@ -1970,285 +1964,6 @@ async fn test_inlay_hint_refresh_is_forwarded( }); } -#[gpui::test] -async fn test_multiple_hunk_types_revert(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { - let mut server = TestServer::start(cx_a.executor()).await; - let client_a = server.create_client(cx_a, "user_a").await; - let client_b = server.create_client(cx_b, "user_b").await; - server - .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) - .await; - let active_call_a = cx_a.read(ActiveCall::global); - let active_call_b = cx_b.read(ActiveCall::global); - - cx_a.update(editor::init); - cx_b.update(editor::init); - - client_a.language_registry().add(rust_lang()); - client_b.language_registry().add(rust_lang()); - - let base_text = indoc! {r#"struct Row; -struct Row1; -struct Row2; - -struct Row4; -struct Row5; -struct Row6; - -struct Row8; -struct Row9; -struct Row10;"#}; - - client_a - .fs() - .insert_tree( - "/a", - json!({ - "main.rs": base_text, - }), - ) - .await; - let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; - active_call_a - .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) - .await - .unwrap(); - let project_id = active_call_a - .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) - .await - .unwrap(); - - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; - active_call_b - .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) - .await - .unwrap(); - - let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); - let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); - - let editor_a = workspace_a - .update(cx_a, |workspace, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, cx) - }) - .await - .unwrap() - .downcast::() - .unwrap(); - - let editor_b = workspace_b - .update(cx_b, |workspace, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, cx) - }) - .await - .unwrap() - .downcast::() - .unwrap(); - - let mut editor_cx_a = EditorTestContext { - cx: cx_a.clone(), - window: cx_a.handle(), - editor: editor_a, - assertion_cx: AssertionContextManager::new(), - }; - let mut editor_cx_b = EditorTestContext { - cx: cx_b.clone(), - window: cx_b.handle(), - editor: editor_b, - assertion_cx: AssertionContextManager::new(), - }; - - // host edits the file, that differs from the base text, producing diff hunks - editor_cx_a.set_state(indoc! {r#"struct Row; - struct Row0.1; - struct Row0.2; - struct Row1; - - struct Row4; - struct Row5444; - struct Row6; - - struct Row9; - struct Row1220;ˇ"#}); - editor_cx_a.update_editor(|editor, cx| { - editor - .buffer() - .read(cx) - .as_singleton() - .unwrap() - .update(cx, |buffer, cx| { - buffer.set_diff_base(Some(base_text.into()), cx); - }); - }); - editor_cx_b.update_editor(|editor, cx| { - editor - .buffer() - .read(cx) - .as_singleton() - .unwrap() - .update(cx, |buffer, cx| { - buffer.set_diff_base(Some(base_text.into()), cx); - }); - }); - cx_a.executor().run_until_parked(); - cx_b.executor().run_until_parked(); - - // the client selects a range in the updated buffer, expands it to see the diff for each hunk in the selection - // the host does not see the diffs toggled - editor_cx_b.set_selections_state(indoc! {r#"«ˇstruct Row; - struct Row0.1; - struct Row0.2; - struct Row1; - - struct Row4; - struct Row5444; - struct Row6; - - struct R»ow9; - struct Row1220;"#}); - editor_cx_b - .update_editor(|editor, cx| editor.toggle_hunk_diff(&editor::actions::ToggleHunkDiff, cx)); - cx_a.executor().run_until_parked(); - cx_b.executor().run_until_parked(); - editor_cx_a.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![ - ( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(1)..DisplayRow(3) - ), - ( - "struct Row2;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(4)..DisplayRow(4) - ), - ( - "struct Row5;\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(6)..DisplayRow(7) - ), - ( - "struct Row8;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(9)..DisplayRow(9) - ), - ( - "struct Row10;".to_string(), - DiffHunkStatus::Modified, - DisplayRow(10)..DisplayRow(10), - ), - ] - ); - assert_eq!(all_expanded_hunks, Vec::new()); - }); - editor_cx_b.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(1)..=DisplayRow(2), DisplayRow(8)..=DisplayRow(8)], - ); - assert_eq!( - all_hunks, - vec![ - ( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(1)..DisplayRow(3) - ), - ( - "struct Row2;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(5)..DisplayRow(5) - ), - ( - "struct Row5;\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(8)..DisplayRow(9) - ), - ( - "struct Row8;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(12)..DisplayRow(12) - ), - ( - "struct Row10;".to_string(), - DiffHunkStatus::Modified, - DisplayRow(13)..DisplayRow(13), - ), - ] - ); - assert_eq!(all_expanded_hunks, &all_hunks[..all_hunks.len() - 1]); - }); - - // the client reverts the hunks, removing the expanded diffs too - // both host and the client observe the reverted state (with one hunk left, not covered by client's selection) - editor_cx_b.update_editor(|editor, cx| { - editor.revert_selected_hunks(&RevertSelectedHunks, cx); - }); - cx_a.executor().run_until_parked(); - cx_b.executor().run_until_parked(); - editor_cx_a.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![( - "struct Row10;".to_string(), - DiffHunkStatus::Modified, - DisplayRow(10)..DisplayRow(10), - )] - ); - assert_eq!(all_expanded_hunks, Vec::new()); - }); - editor_cx_b.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![( - "struct Row10;".to_string(), - DiffHunkStatus::Modified, - DisplayRow(10)..DisplayRow(10), - )] - ); - assert_eq!(all_expanded_hunks, Vec::new()); - }); - editor_cx_a.assert_editor_state(indoc! {r#"struct Row; - struct Row1; - struct Row2; - - struct Row4; - struct Row5; - struct Row6; - - struct Row8; - struct Row9; - struct Row1220;ˇ"#}); - editor_cx_b.assert_editor_state(indoc! {r#"«ˇstruct Row; - struct Row1; - struct Row2; - - struct Row4; - struct Row5; - struct Row6; - - struct Row8; - struct R»ow9; - struct Row1220;"#}); -} - #[gpui::test(iterations = 10)] async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { let mut server = TestServer::start(cx_a.executor()).await; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ad5cd24d73..78c8ba6920 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -154,7 +154,7 @@ use theme::{ }; use ui::{ h_flex, prelude::*, ButtonSize, ButtonStyle, Disclosure, IconButton, IconName, IconSize, - ListItem, Popover, Tooltip, + ListItem, Popover, PopoverMenuHandle, Tooltip, }; use util::{defer, maybe, post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::item::{ItemHandle, PreviewTabsSettings}; @@ -562,6 +562,7 @@ pub struct Editor { nav_history: Option, context_menu: RwLock>, mouse_context_menu: Option, + hunk_controls_menu_handle: PopoverMenuHandle, completion_tasks: Vec<(CompletionId, Task>)>, signature_help_state: SignatureHelpState, auto_signature_help: Option, @@ -1938,6 +1939,7 @@ impl Editor { nav_history: None, context_menu: RwLock::new(None), mouse_context_menu: None, + hunk_controls_menu_handle: PopoverMenuHandle::default(), completion_tasks: Default::default(), signature_help_state: SignatureHelpState::default(), auto_signature_help: None, @@ -5383,23 +5385,6 @@ impl Editor { })) } - fn close_hunk_diff_button( - &self, - hunk: HoveredHunk, - row: DisplayRow, - cx: &mut ViewContext, - ) -> IconButton { - IconButton::new( - ("close_hunk_diff_indicator", row.0 as usize), - ui::IconName::Close, - ) - .shape(ui::IconButtonShape::Square) - .icon_size(IconSize::XSmall) - .icon_color(Color::Muted) - .tooltip(|cx| Tooltip::for_action("Close hunk diff", &ToggleHunkDiff, cx)) - .on_click(cx.listener(move |editor, _e, cx| editor.toggle_hovered_hunk(&hunk, cx))) - } - pub fn context_menu_visible(&self) -> bool { self.context_menu .read() @@ -9335,32 +9320,42 @@ impl Editor { } } - fn go_to_hunk(&mut self, _: &GoToHunk, cx: &mut ViewContext) { + fn go_to_next_hunk(&mut self, _: &GoToHunk, cx: &mut ViewContext) { let snapshot = self .display_map .update(cx, |display_map, cx| display_map.snapshot(cx)); let selection = self.selections.newest::(cx); + self.go_to_hunk_after_position(&snapshot, selection.head(), cx); + } - if !self.seek_in_direction( - &snapshot, - selection.head(), + fn go_to_hunk_after_position( + &mut self, + snapshot: &DisplaySnapshot, + position: Point, + cx: &mut ViewContext<'_, Editor>, + ) -> Option { + if let Some(hunk) = self.go_to_next_hunk_in_direction( + snapshot, + position, false, - snapshot.buffer_snapshot.git_diff_hunks_in_range( - MultiBufferRow(selection.head().row + 1)..MultiBufferRow::MAX, - ), + snapshot + .buffer_snapshot + .git_diff_hunks_in_range(MultiBufferRow(position.row + 1)..MultiBufferRow::MAX), cx, ) { - let wrapped_point = Point::zero(); - self.seek_in_direction( - &snapshot, - wrapped_point, - true, - snapshot.buffer_snapshot.git_diff_hunks_in_range( - MultiBufferRow(wrapped_point.row + 1)..MultiBufferRow::MAX, - ), - cx, - ); + return Some(hunk); } + + let wrapped_point = Point::zero(); + self.go_to_next_hunk_in_direction( + snapshot, + wrapped_point, + true, + snapshot.buffer_snapshot.git_diff_hunks_in_range( + MultiBufferRow(wrapped_point.row + 1)..MultiBufferRow::MAX, + ), + cx, + ) } fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext) { @@ -9369,52 +9364,65 @@ impl Editor { .update(cx, |display_map, cx| display_map.snapshot(cx)); let selection = self.selections.newest::(cx); - if !self.seek_in_direction( - &snapshot, - selection.head(), - false, - snapshot.buffer_snapshot.git_diff_hunks_in_range_rev( - MultiBufferRow(0)..MultiBufferRow(selection.head().row), - ), - cx, - ) { - let wrapped_point = snapshot.buffer_snapshot.max_point(); - self.seek_in_direction( - &snapshot, - wrapped_point, - true, - snapshot.buffer_snapshot.git_diff_hunks_in_range_rev( - MultiBufferRow(0)..MultiBufferRow(wrapped_point.row), - ), - cx, - ); - } + self.go_to_hunk_before_position(&snapshot, selection.head(), cx); } - fn seek_in_direction( + fn go_to_hunk_before_position( + &mut self, + snapshot: &DisplaySnapshot, + position: Point, + cx: &mut ViewContext<'_, Editor>, + ) -> Option { + if let Some(hunk) = self.go_to_next_hunk_in_direction( + snapshot, + position, + false, + snapshot + .buffer_snapshot + .git_diff_hunks_in_range_rev(MultiBufferRow(0)..MultiBufferRow(position.row)), + cx, + ) { + return Some(hunk); + } + + let wrapped_point = snapshot.buffer_snapshot.max_point(); + self.go_to_next_hunk_in_direction( + snapshot, + wrapped_point, + true, + snapshot + .buffer_snapshot + .git_diff_hunks_in_range_rev(MultiBufferRow(0)..MultiBufferRow(wrapped_point.row)), + cx, + ) + } + + fn go_to_next_hunk_in_direction( &mut self, snapshot: &DisplaySnapshot, initial_point: Point, is_wrapped: bool, hunks: impl Iterator, cx: &mut ViewContext, - ) -> bool { + ) -> Option { let display_point = initial_point.to_display_point(snapshot); let mut hunks = hunks - .map(|hunk| diff_hunk_to_display(&hunk, snapshot)) - .filter(|hunk| is_wrapped || !hunk.contains_display_row(display_point.row())) + .map(|hunk| (diff_hunk_to_display(&hunk, snapshot), hunk)) + .filter(|(display_hunk, _)| { + is_wrapped || !display_hunk.contains_display_row(display_point.row()) + }) .dedup(); - if let Some(hunk) = hunks.next() { + if let Some((display_hunk, hunk)) = hunks.next() { self.change_selections(Some(Autoscroll::fit()), cx, |s| { - let row = hunk.start_display_row(); + let row = display_hunk.start_display_row(); let point = DisplayPoint::new(row, 0); s.select_display_ranges([point..point]); }); - true + Some(hunk) } else { - false + None } } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 5927c22cb0..de1b12abe0 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -9623,7 +9623,7 @@ async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) cx.update_editor(|editor, cx| { //Wrap around the bottom of the buffer for _ in 0..3 { - editor.go_to_hunk(&GoToHunk, cx); + editor.go_to_next_hunk(&GoToHunk, cx); } }); @@ -9709,7 +9709,7 @@ async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) //Make sure that the fold only gets one hunk for _ in 0..4 { - editor.go_to_hunk(&GoToHunk, cx); + editor.go_to_next_hunk(&GoToHunk, cx); } }); @@ -11226,7 +11226,7 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test cx.update_editor(|editor, cx| { for _ in 0..4 { - editor.go_to_hunk(&GoToHunk, cx); + editor.go_to_next_hunk(&GoToHunk, cx); editor.toggle_hunk_diff(&ToggleHunkDiff, cx); } }); @@ -11249,18 +11249,13 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test 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(1)..=DisplayRow(1), DisplayRow(7)..=DisplayRow(7), DisplayRow(9)..=DisplayRow(9)], - "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks" - ); assert_eq!( all_hunks, vec![ - ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(1)..DisplayRow(2)), - ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(4)..DisplayRow(4)), - (" println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(7)..DisplayRow(8)), - ("".to_string(), DiffHunkStatus::Added, DisplayRow(9)..DisplayRow(10)), + ("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)" @@ -11269,6 +11264,11 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test 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.update_editor(|editor, cx| { @@ -11311,7 +11311,7 @@ async fn test_toggled_diff_base_change( const B: u32 = 42; const C: u32 = 42; - fn main(ˇ) { + fn main() { println!("hello"); println!("world"); @@ -11356,9 +11356,9 @@ async fn test_toggled_diff_base_change( DisplayRow(3)..DisplayRow(3) ), ( - "fn main(ˇ) {\n println!(\"hello\");\n".to_string(), + " println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, - DisplayRow(5)..DisplayRow(7) + DisplayRow(6)..DisplayRow(7) ), ( "".to_string(), @@ -11390,22 +11390,18 @@ async fn test_toggled_diff_base_change( "# .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(9)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(14)], - "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks" - ); assert_eq!( all_hunks, vec![ - ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(1)..DisplayRow(1)), - ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(5)..DisplayRow(5)), - ("fn main(ˇ) {\n println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(9)..DisplayRow(11)), - ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(15)), + ("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)" @@ -11414,6 +11410,11 @@ async fn test_toggled_diff_base_change( 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" + ); }); cx.set_diff_base(Some("new diff base!")); @@ -11459,7 +11460,7 @@ async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::Test const B: u32 = 42; const C: u32 = 42; - fn main(ˇ) { + fn main() { println!("hello"); println!("world"); @@ -11520,9 +11521,9 @@ async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::Test DisplayRow(3)..DisplayRow(3) ), ( - "fn main(ˇ) {\n println!(\"hello\");\n".to_string(), + " println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, - DisplayRow(5)..DisplayRow(7) + DisplayRow(6)..DisplayRow(7) ), ( "".to_string(), @@ -11576,50 +11577,50 @@ async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::Test 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(9)..=DisplayRow(10), - DisplayRow(13)..=DisplayRow(14), - DisplayRow(19)..=DisplayRow(19) - ] - ); assert_eq!( all_hunks, vec![ ( "use some::mod1;\n".to_string(), DiffHunkStatus::Removed, - DisplayRow(1)..DisplayRow(1) + DisplayRow(2)..DisplayRow(2) ), ( "const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, - DisplayRow(5)..DisplayRow(5) + DisplayRow(7)..DisplayRow(7) ), ( - "fn main(ˇ) {\n println!(\"hello\");\n".to_string(), + " println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, - DisplayRow(9)..DisplayRow(11) + DisplayRow(12)..DisplayRow(13) ), ( "".to_string(), DiffHunkStatus::Added, - DisplayRow(13)..DisplayRow(15) + DisplayRow(16)..DisplayRow(18) ), ( "".to_string(), DiffHunkStatus::Added, - DisplayRow(19)..DisplayRow(20) + DisplayRow(23)..DisplayRow(24) ), ( "fn another2() {\n".to_string(), DiffHunkStatus::Removed, - DisplayRow(23)..DisplayRow(23) + 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) + ] + ); }); cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx)); @@ -11653,11 +11654,6 @@ async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::Test 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(0)..=DisplayRow(0), DisplayRow(5)..=DisplayRow(5)], - "Only one hunk is left not folded, its highlight should be visible" - ); assert_eq!( all_hunks, vec![ @@ -11672,7 +11668,7 @@ async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::Test DisplayRow(0)..DisplayRow(0) ), ( - "fn main(ˇ) {\n println!(\"hello\");\n".to_string(), + " println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(0)..DisplayRow(0) ), @@ -11684,12 +11680,12 @@ async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::Test ( "".to_string(), DiffHunkStatus::Added, - DisplayRow(5)..DisplayRow(6) + DisplayRow(6)..DisplayRow(7) ), ( "fn another2() {\n".to_string(), DiffHunkStatus::Removed, - DisplayRow(9)..DisplayRow(9) + DisplayRow(11)..DisplayRow(11) ), ], "Hunk list should still return shifted folded hunks" @@ -11700,16 +11696,21 @@ async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::Test ( "".to_string(), DiffHunkStatus::Added, - DisplayRow(5)..DisplayRow(6) + DisplayRow(6)..DisplayRow(7) ), ( "fn another2() {\n".to_string(), DiffHunkStatus::Removed, - DisplayRow(9)..DisplayRow(9) + 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| { @@ -11746,51 +11747,51 @@ async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::Test 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(9)..=DisplayRow(10), - DisplayRow(13)..=DisplayRow(14), - DisplayRow(19)..=DisplayRow(19) - ], - "After unfolding, all hunk diffs should be visible again" - ); assert_eq!( all_hunks, vec![ ( "use some::mod1;\n".to_string(), DiffHunkStatus::Removed, - DisplayRow(1)..DisplayRow(1) + DisplayRow(2)..DisplayRow(2) ), ( "const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, - DisplayRow(5)..DisplayRow(5) + DisplayRow(7)..DisplayRow(7) ), ( - "fn main(ˇ) {\n println!(\"hello\");\n".to_string(), + " println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, - DisplayRow(9)..DisplayRow(11) + DisplayRow(12)..DisplayRow(13) ), ( "".to_string(), DiffHunkStatus::Added, - DisplayRow(13)..DisplayRow(15) + DisplayRow(16)..DisplayRow(18) ), ( "".to_string(), DiffHunkStatus::Added, - DisplayRow(19)..DisplayRow(20) + DisplayRow(23)..DisplayRow(24) ), ( "fn another2() {\n".to_string(), DiffHunkStatus::Removed, - DisplayRow(23)..DisplayRow(23) + 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" + ); }); } @@ -11940,17 +11941,17 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) ( "bbbb\n".to_string(), DiffHunkStatus::Removed, - DisplayRow(5)..DisplayRow(5), + DisplayRow(6)..DisplayRow(6), ), ( "nnnn\n".to_string(), DiffHunkStatus::Modified, - DisplayRow(23)..DisplayRow(24), + DisplayRow(25)..DisplayRow(26), ), ( "".to_string(), DiffHunkStatus::Added, - DisplayRow(43)..DisplayRow(44), + DisplayRow(46)..DisplayRow(47), ), ]; @@ -11975,8 +11976,8 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) assert_eq!( expanded_hunks_background_highlights(editor, cx), vec![ - DisplayRow(23)..=DisplayRow(23), - DisplayRow(43)..=DisplayRow(43) + DisplayRow(25)..=DisplayRow(25), + DisplayRow(46)..=DisplayRow(46) ], ); assert_eq!(all_hunks, expected_all_hunks_shifted); @@ -12007,8 +12008,8 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) assert_eq!( expanded_hunks_background_highlights(editor, cx), vec![ - DisplayRow(23)..=DisplayRow(23), - DisplayRow(43)..=DisplayRow(43) + DisplayRow(25)..=DisplayRow(25), + DisplayRow(46)..=DisplayRow(46) ], ); assert_eq!(all_hunks, expected_all_hunks_shifted); @@ -12116,12 +12117,12 @@ async fn test_edits_around_toggled_additions( vec![( "".to_string(), DiffHunkStatus::Added, - DisplayRow(4)..DisplayRow(7) + DisplayRow(5)..DisplayRow(8) )] ); assert_eq!( expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(4)..=DisplayRow(6)] + vec![DisplayRow(5)..=DisplayRow(7)] ); assert_eq!(all_hunks, all_expanded_hunks); }); @@ -12156,12 +12157,12 @@ async fn test_edits_around_toggled_additions( vec![( "".to_string(), DiffHunkStatus::Added, - DisplayRow(4)..DisplayRow(8) + DisplayRow(5)..DisplayRow(9) )] ); assert_eq!( expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(4)..=DisplayRow(6)], + vec![DisplayRow(5)..=DisplayRow(7)], "Edited hunk should have one more line added" ); assert_eq!( @@ -12201,12 +12202,12 @@ async fn test_edits_around_toggled_additions( vec![( "".to_string(), DiffHunkStatus::Added, - DisplayRow(4)..DisplayRow(9) + DisplayRow(5)..DisplayRow(10) )] ); assert_eq!( expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(4)..=DisplayRow(6)], + vec![DisplayRow(5)..=DisplayRow(7)], "Edited hunk should have one more line added" ); assert_eq!(all_hunks, all_expanded_hunks); @@ -12245,12 +12246,12 @@ async fn test_edits_around_toggled_additions( vec![( "".to_string(), DiffHunkStatus::Added, - DisplayRow(4)..DisplayRow(8) + DisplayRow(5)..DisplayRow(9) )] ); assert_eq!( expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(4)..=DisplayRow(6)], + vec![DisplayRow(5)..=DisplayRow(7)], "Deleting a line should shrint the hunk" ); assert_eq!( @@ -12293,12 +12294,12 @@ async fn test_edits_around_toggled_additions( vec![( "".to_string(), DiffHunkStatus::Added, - DisplayRow(5)..DisplayRow(6) + DisplayRow(6)..DisplayRow(7) )] ); assert_eq!( expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(5)..=DisplayRow(5)] + vec![DisplayRow(6)..=DisplayRow(6)] ); assert_eq!(all_hunks, all_expanded_hunks); }); @@ -12335,7 +12336,7 @@ async fn test_edits_around_toggled_additions( ( "const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, - DisplayRow(2)..DisplayRow(2) + DisplayRow(3)..DisplayRow(3) ) ] ); @@ -12349,7 +12350,7 @@ async fn test_edits_around_toggled_additions( vec![( "const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, - DisplayRow(2)..DisplayRow(2) + DisplayRow(3)..DisplayRow(3) )], "Should open hunks that were adjacent to the stale addition one" ); @@ -12445,7 +12446,7 @@ async fn test_edits_around_toggled_deletions( vec![( "const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, - DisplayRow(4)..DisplayRow(4) + DisplayRow(5)..DisplayRow(5) )] ); assert_eq!(all_hunks, all_expanded_hunks); @@ -12485,7 +12486,7 @@ async fn test_edits_around_toggled_deletions( vec![( "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, - DisplayRow(5)..DisplayRow(5) + DisplayRow(6)..DisplayRow(6) )] ); assert_eq!(all_hunks, all_expanded_hunks); @@ -12520,7 +12521,7 @@ async fn test_edits_around_toggled_deletions( vec![( "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, - DisplayRow(6)..DisplayRow(6) + DisplayRow(7)..DisplayRow(7) )] ); assert_eq!(all_hunks, all_expanded_hunks); @@ -12554,12 +12555,12 @@ async fn test_edits_around_toggled_deletions( vec![( "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(), DiffHunkStatus::Modified, - DisplayRow(7)..DisplayRow(8) + DisplayRow(8)..DisplayRow(9) )] ); assert_eq!( expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(7)..=DisplayRow(7)], + vec![DisplayRow(8)..=DisplayRow(8)], "Modified expanded hunks should display additions and highlight their background" ); assert_eq!(all_hunks, all_expanded_hunks); @@ -12653,14 +12654,14 @@ async fn test_edits_around_toggled_modifications( let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); assert_eq!( expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(6)..=DisplayRow(6)], + vec![DisplayRow(7)..=DisplayRow(7)], ); assert_eq!( all_hunks, vec![( "const C: u32 = 42;\n".to_string(), DiffHunkStatus::Modified, - DisplayRow(6)..DisplayRow(7) + DisplayRow(7)..DisplayRow(8) )] ); assert_eq!(all_hunks, all_expanded_hunks); @@ -12696,7 +12697,7 @@ async fn test_edits_around_toggled_modifications( let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); assert_eq!( expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(6)..=DisplayRow(6)], + vec![DisplayRow(7)..=DisplayRow(7)], "Modified hunk should grow highlighted lines on more text additions" ); assert_eq!( @@ -12704,7 +12705,7 @@ async fn test_edits_around_toggled_modifications( vec![( "const C: u32 = 42;\n".to_string(), DiffHunkStatus::Modified, - DisplayRow(6)..DisplayRow(9) + DisplayRow(7)..DisplayRow(10) )] ); assert_eq!(all_hunks, all_expanded_hunks); @@ -12742,14 +12743,14 @@ async fn test_edits_around_toggled_modifications( let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); assert_eq!( expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(6)..=DisplayRow(8)], + vec![DisplayRow(7)..=DisplayRow(9)], ); assert_eq!( all_hunks, vec![( "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(), DiffHunkStatus::Modified, - DisplayRow(6)..DisplayRow(9) + DisplayRow(7)..DisplayRow(10) )], "Modified hunk should grow deleted lines on text deletions above" ); @@ -12786,7 +12787,7 @@ async fn test_edits_around_toggled_modifications( let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); assert_eq!( expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(6)..=DisplayRow(9)], + vec![DisplayRow(7)..=DisplayRow(10)], "Modified hunk should grow deleted lines on text modifications above" ); assert_eq!( @@ -12794,7 +12795,7 @@ async fn test_edits_around_toggled_modifications( vec![( "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(), DiffHunkStatus::Modified, - DisplayRow(6)..DisplayRow(10) + DisplayRow(7)..DisplayRow(11) )] ); assert_eq!(all_hunks, all_expanded_hunks); @@ -12830,7 +12831,7 @@ async fn test_edits_around_toggled_modifications( let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); assert_eq!( expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(6)..=DisplayRow(8)], + vec![DisplayRow(7)..=DisplayRow(9)], "Modified hunk should grow shrink lines on modification lines removal" ); assert_eq!( @@ -12838,7 +12839,7 @@ async fn test_edits_around_toggled_modifications( vec![( "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(), DiffHunkStatus::Modified, - DisplayRow(6)..DisplayRow(9) + DisplayRow(7)..DisplayRow(10) )] ); assert_eq!(all_hunks, all_expanded_hunks); @@ -12880,7 +12881,7 @@ async fn test_edits_around_toggled_modifications( "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n" .to_string(), DiffHunkStatus::Removed, - DisplayRow(7)..DisplayRow(7) + DisplayRow(8)..DisplayRow(8) )] ); assert_eq!(all_hunks, all_expanded_hunks); @@ -12974,14 +12975,14 @@ async fn test_multiple_expanded_hunks_merge( let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); assert_eq!( expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(6)..=DisplayRow(6)], + vec![DisplayRow(7)..=DisplayRow(7)], ); assert_eq!( all_hunks, vec![( "const C: u32 = 42;\n".to_string(), DiffHunkStatus::Modified, - DisplayRow(6)..DisplayRow(7) + DisplayRow(7)..DisplayRow(8) )] ); assert_eq!(all_hunks, all_expanded_hunks); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index cf8edb67dc..9fe05bc4f2 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -11,7 +11,7 @@ use crate::{ hover_popover::{ self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, }, - hunk_diff::{diff_hunk_to_display, DisplayDiffHunk, ExpandedHunk}, + hunk_diff::{diff_hunk_to_display, DisplayDiffHunk}, hunk_status, items::BufferSearchHighlights, mouse_context_menu::{self, MenuPosition, MouseContextMenu}, @@ -20,8 +20,8 @@ use crate::{ DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, LineDown, LineUp, OpenExcerpts, PageDown, - PageUp, Point, RangeToAnchorExt, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, - ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN, + PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint, + CURSORS_VISIBLE_FOR, MAX_LINE_LEN, }; use client::ParticipantIndex; use collections::{BTreeMap, HashMap}; @@ -302,7 +302,7 @@ impl EditorElement { } register_action(view, cx, Editor::go_to_diagnostic); register_action(view, cx, Editor::go_to_prev_diagnostic); - register_action(view, cx, Editor::go_to_hunk); + register_action(view, cx, Editor::go_to_next_hunk); register_action(view, cx, Editor::go_to_prev_hunk); register_action(view, cx, |editor, a, cx| { editor.go_to_definition(a, cx).detach_and_log_err(cx); @@ -489,28 +489,7 @@ impl EditorElement { let mut modifiers = event.modifiers; if let Some(hovered_hunk) = hovered_hunk { - if modifiers.control || modifiers.platform { - editor.toggle_hovered_hunk(&hovered_hunk, cx); - } else { - let display_range = hovered_hunk - .multi_buffer_range - .clone() - .to_display_points(&position_map.snapshot); - let hunk_bounds = Self::diff_hunk_bounds( - &position_map.snapshot, - position_map.line_height, - gutter_hitbox.bounds, - &DisplayDiffHunk::Unfolded { - diff_base_byte_range: hovered_hunk.diff_base_byte_range.clone(), - display_row_range: display_range.start.row()..display_range.end.row(), - multi_buffer_range: hovered_hunk.multi_buffer_range.clone(), - status: hovered_hunk.status, - }, - ); - if hunk_bounds.contains(&event.position) { - editor.open_hunk_context_menu(hovered_hunk, event.position, cx); - } - } + editor.toggle_hovered_hunk(&hovered_hunk, cx); cx.notify(); return; } else if gutter_hitbox.is_hovered(cx) { @@ -1303,13 +1282,13 @@ impl EditorElement { let display_hunks = buffer_snapshot .git_diff_hunks_in_range(buffer_start_row..buffer_end_row) .filter_map(|hunk| { - let mut display_hunk = diff_hunk_to_display(&hunk, snapshot); + let display_hunk = diff_hunk_to_display(&hunk, snapshot); if let DisplayDiffHunk::Unfolded { multi_buffer_range, status, .. - } = &mut display_hunk + } = &display_hunk { let mut is_expanded = false; while let Some(expanded_hunk) = expanded_hunks.peek() { @@ -1332,11 +1311,7 @@ impl EditorElement { } match status { DiffHunkStatus::Added => {} - DiffHunkStatus::Modified => { - if is_expanded { - *status = DiffHunkStatus::Added; - } - } + DiffHunkStatus::Modified => {} DiffHunkStatus::Removed => { if is_expanded { return None; @@ -3371,9 +3346,6 @@ impl EditorElement { for test_indicator in layout.test_indicators.iter_mut() { test_indicator.paint(cx); } - for close_indicator in layout.close_indicators.iter_mut() { - close_indicator.paint(cx); - } if let Some(indicator) = layout.code_actions_indicator.as_mut() { indicator.paint(cx); @@ -4159,46 +4131,6 @@ impl EditorElement { + 1; self.column_pixels(digit_count, cx) } - - #[allow(clippy::too_many_arguments)] - fn layout_hunk_diff_close_indicators( - &self, - line_height: Pixels, - scroll_pixel_position: gpui::Point, - gutter_dimensions: &GutterDimensions, - gutter_hitbox: &Hitbox, - rows_with_hunk_bounds: &HashMap>, - expanded_hunks_by_rows: HashMap, - cx: &mut WindowContext, - ) -> Vec { - self.editor.update(cx, |editor, cx| { - expanded_hunks_by_rows - .into_iter() - .map(|(display_row, hunk)| { - let button = editor.close_hunk_diff_button( - HoveredHunk { - multi_buffer_range: hunk.hunk_range, - status: hunk.status, - diff_base_byte_range: hunk.diff_base_byte_range, - }, - display_row, - cx, - ); - - prepaint_gutter_button( - button, - display_row, - line_height, - gutter_dimensions, - scroll_pixel_position, - gutter_hitbox, - rows_with_hunk_bounds, - cx, - ) - }) - .collect() - }) - } } #[allow(clippy::too_many_arguments)] @@ -5549,15 +5481,6 @@ impl Element for EditorElement { } else { Vec::new() }; - let close_indicators = self.layout_hunk_diff_close_indicators( - line_height, - scroll_pixel_position, - &gutter_dimensions, - &gutter_hitbox, - &rows_with_hunk_bounds, - expanded_add_hunks_by_rows, - cx, - ); self.layout_signature_help( &hitbox, @@ -5670,7 +5593,6 @@ impl Element for EditorElement { selections, mouse_context_menu, test_indicators, - close_indicators, code_actions_indicator, gutter_fold_toggles, crease_trailers, @@ -5812,7 +5734,6 @@ pub struct EditorLayout { selections: Vec<(PlayerColor, Vec)>, code_actions_indicator: Option, test_indicators: Vec, - close_indicators: Vec, gutter_fold_toggles: Vec>, crease_trailers: Vec>, mouse_context_menu: Option, diff --git a/crates/editor/src/hunk_diff.rs b/crates/editor/src/hunk_diff.rs index 67e8a25df5..4fa1f10a8a 100644 --- a/crates/editor/src/hunk_diff.rs +++ b/crates/editor/src/hunk_diff.rs @@ -1,28 +1,26 @@ use collections::{hash_map, HashMap, HashSet}; use git::diff::DiffHunkStatus; -use gpui::{Action, AppContext, CursorStyle, Hsla, Model, MouseButton, Subscription, Task, View}; +use gpui::{Action, AnchorCorner, AppContext, CursorStyle, Hsla, Model, MouseButton, Task, View}; use language::{Buffer, BufferId, Point}; use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptRange, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow, MultiBufferSnapshot, ToPoint, }; -use settings::SettingsStore; use std::{ ops::{Range, RangeInclusive}, sync::Arc, }; use ui::{ - prelude::*, ActiveTheme, ContextMenu, InteractiveElement, IntoElement, ParentElement, Pixels, - Styled, ViewContext, VisualContext, + prelude::*, ActiveTheme, ContextMenu, IconButtonShape, InteractiveElement, IntoElement, + ParentElement, PopoverMenu, Styled, Tooltip, ViewContext, VisualContext, }; -use util::{debug_panic, RangeExt}; +use util::RangeExt; use crate::{ - editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, - mouse_context_menu::MouseContextMenu, BlockDisposition, BlockProperties, BlockStyle, - CustomBlockId, DiffRowHighlight, DisplayRow, DisplaySnapshot, Editor, EditorElement, - EditorSnapshot, ExpandAllHunkDiffs, RangeToAnchorExt, RevertFile, RevertSelectedHunks, - ToDisplayPoint, ToggleHunkDiff, + 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, }; #[derive(Debug, Clone)] @@ -41,7 +39,7 @@ pub(super) struct ExpandedHunks { #[derive(Debug, Clone)] pub(super) struct ExpandedHunk { - pub block: Option, + pub blocks: Vec, pub hunk_range: Range, pub diff_base_byte_range: Range, pub status: DiffHunkStatus, @@ -77,85 +75,6 @@ impl ExpandedHunks { } impl Editor { - pub(super) fn open_hunk_context_menu( - &mut self, - hovered_hunk: HoveredHunk, - clicked_point: gpui::Point, - cx: &mut ViewContext, - ) { - let focus_handle = self.focus_handle.clone(); - let expanded = self - .expanded_hunks - .hunks(false) - .any(|expanded_hunk| expanded_hunk.hunk_range == hovered_hunk.multi_buffer_range); - let editor_handle = cx.view().clone(); - let editor_snapshot = self.snapshot(cx); - let start_point = self - .to_pixel_point(hovered_hunk.multi_buffer_range.start, &editor_snapshot, cx) - .unwrap_or(clicked_point); - let end_point = self - .to_pixel_point(hovered_hunk.multi_buffer_range.start, &editor_snapshot, cx) - .unwrap_or(clicked_point); - let norm = - |a: gpui::Point, b: gpui::Point| (a.x - b.x).abs() + (a.y - b.y).abs(); - let closest_source = if norm(start_point, clicked_point) < norm(end_point, clicked_point) { - hovered_hunk.multi_buffer_range.start - } else { - hovered_hunk.multi_buffer_range.end - }; - - self.mouse_context_menu = MouseContextMenu::pinned_to_editor( - self, - closest_source, - clicked_point, - ContextMenu::build(cx, move |menu, _| { - menu.on_blur_subscription(Subscription::new(|| {})) - .context(focus_handle) - .entry( - if expanded { - "Collapse Hunk" - } else { - "Expand Hunk" - }, - Some(ToggleHunkDiff.boxed_clone()), - { - let editor = editor_handle.clone(); - let hunk = hovered_hunk.clone(); - move |cx| { - editor.update(cx, |editor, cx| { - editor.toggle_hovered_hunk(&hunk, cx); - }); - } - }, - ) - .entry("Revert Hunk", Some(RevertSelectedHunks.boxed_clone()), { - let editor = editor_handle.clone(); - let hunk = hovered_hunk.clone(); - move |cx| { - let multi_buffer = editor.read(cx).buffer().clone(); - let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); - let mut revert_changes = HashMap::default(); - if let Some(hunk) = - crate::hunk_diff::to_diff_hunk(&hunk, &multi_buffer_snapshot) - { - Editor::prepare_revert_change( - &mut revert_changes, - &multi_buffer, - &hunk, - cx, - ); - } - if !revert_changes.is_empty() { - editor.update(cx, |editor, cx| editor.revert(revert_changes, cx)); - } - } - }) - .action("Revert File", RevertFile.boxed_clone()) - }), - cx, - ) - } - pub(super) fn toggle_hovered_hunk( &mut self, hovered_hunk: &HoveredHunk, @@ -264,7 +183,8 @@ impl Editor { break; } else if expanded_hunk_row_range == hunk_to_toggle_row_range { highlights_to_remove.push(expanded_hunk.hunk_range.clone()); - blocks_to_remove.extend(expanded_hunk.block); + blocks_to_remove + .extend(expanded_hunk.blocks.iter().copied()); hunks_to_toggle.next(); retain = false; break; @@ -371,9 +291,17 @@ impl Editor { Err(ix) => ix, }; - let block = match hunk.status { + let blocks; + match hunk.status { DiffHunkStatus::Removed => { - self.insert_deleted_text_block(diff_base_buffer, deleted_text_lines, hunk, cx) + blocks = self.insert_blocks( + [ + self.hunk_header_block(&hunk, cx), + Self::deleted_text_block(hunk, diff_base_buffer, deleted_text_lines, cx), + ], + None, + cx, + ); } DiffHunkStatus::Added => { self.highlight_rows::( @@ -382,7 +310,7 @@ impl Editor { false, cx, ); - None + blocks = self.insert_blocks([self.hunk_header_block(&hunk, cx)], None, cx); } DiffHunkStatus::Modified => { self.highlight_rows::( @@ -391,13 +319,20 @@ impl Editor { false, cx, ); - self.insert_deleted_text_block(diff_base_buffer, deleted_text_lines, hunk, cx) + blocks = self.insert_blocks( + [ + self.hunk_header_block(&hunk, cx), + Self::deleted_text_block(hunk, diff_base_buffer, deleted_text_lines, cx), + ], + None, + cx, + ); } }; self.expanded_hunks.hunks.insert( block_insert_index, ExpandedHunk { - block, + blocks, hunk_range: hunk_start..hunk_end, status: hunk.status, folded: false, @@ -408,109 +343,368 @@ impl Editor { Some(()) } - fn insert_deleted_text_block( - &mut self, + fn hunk_header_block( + &self, + hunk: &HoveredHunk, + cx: &mut ViewContext<'_, Editor>, + ) -> BlockProperties { + let border_color = cx.theme().colors().border_disabled; + let gutter_color = match hunk.status { + DiffHunkStatus::Added => cx.theme().status().created, + DiffHunkStatus::Modified => cx.theme().status().modified, + DiffHunkStatus::Removed => cx.theme().status().deleted, + }; + + BlockProperties { + position: hunk.multi_buffer_range.start, + height: 1, + style: BlockStyle::Sticky, + disposition: BlockDisposition::Above, + priority: 0, + render: Box::new({ + let editor = cx.view().clone(); + let hunk = hunk.clone(); + move |cx| { + let hunk_controls_menu_handle = + editor.read(cx).hunk_controls_menu_handle.clone(); + + h_flex() + .id(cx.block_id) + .w_full() + .h(cx.line_height()) + .child( + div() + .id("gutter-strip") + .w(EditorElement::diff_hunk_strip_width(cx.line_height())) + .h_full() + .bg(gutter_color) + .cursor(CursorStyle::PointingHand) + .on_click({ + let editor = editor.clone(); + let hunk = hunk.clone(); + move |_event, cx| { + editor.update(cx, |editor, cx| { + editor.toggle_hovered_hunk(&hunk, cx); + }); + } + }), + ) + .child( + h_flex() + .size_full() + .justify_between() + .border_t_1() + .border_color(border_color) + .child( + h_flex() + .gap_2() + .pl_6() + .child( + IconButton::new("next-hunk", IconName::ArrowDown) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small) + .tooltip({ + let focus_handle = editor.focus_handle(cx); + move |cx| { + Tooltip::for_action_in( + "Next Hunk", + &GoToHunk, + &focus_handle, + cx, + ) + } + }) + .on_click({ + let editor = editor.clone(); + let hunk = hunk.clone(); + move |_event, cx| { + editor.update(cx, |editor, cx| { + let snapshot = editor.snapshot(cx); + let position = hunk + .multi_buffer_range + .end + .to_point( + &snapshot.buffer_snapshot, + ); + if let Some(hunk) = editor + .go_to_hunk_after_position( + &snapshot, position, cx, + ) + { + let multi_buffer_start = snapshot + .buffer_snapshot + .anchor_before(Point::new( + hunk.row_range.start.0, + 0, + )); + let multi_buffer_end = snapshot + .buffer_snapshot + .anchor_after(Point::new( + hunk.row_range.end.0, + 0, + )); + editor.expand_diff_hunk( + None, + &HoveredHunk { + multi_buffer_range: + multi_buffer_start + ..multi_buffer_end, + status: hunk_status(&hunk), + diff_base_byte_range: hunk + .diff_base_byte_range, + }, + cx, + ); + } + }); + } + }), + ) + .child( + IconButton::new("prev-hunk", IconName::ArrowUp) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small) + .tooltip({ + let focus_handle = editor.focus_handle(cx); + move |cx| { + Tooltip::for_action_in( + "Previous Hunk", + &GoToPrevHunk, + &focus_handle, + cx, + ) + } + }) + .on_click({ + let editor = editor.clone(); + let hunk = hunk.clone(); + move |_event, cx| { + editor.update(cx, |editor, cx| { + let snapshot = editor.snapshot(cx); + let position = hunk + .multi_buffer_range + .start + .to_point( + &snapshot.buffer_snapshot, + ); + let hunk = editor + .go_to_hunk_before_position( + &snapshot, position, cx, + ); + if let Some(hunk) = hunk { + let multi_buffer_start = snapshot + .buffer_snapshot + .anchor_before(Point::new( + hunk.row_range.start.0, + 0, + )); + let multi_buffer_end = snapshot + .buffer_snapshot + .anchor_after(Point::new( + hunk.row_range.end.0, + 0, + )); + editor.expand_diff_hunk( + None, + &HoveredHunk { + multi_buffer_range: + multi_buffer_start + ..multi_buffer_end, + status: hunk_status(&hunk), + diff_base_byte_range: hunk + .diff_base_byte_range, + }, + cx, + ); + } + }); + } + }), + ), + ) + .child( + h_flex() + .gap_2() + .pr_6() + .child({ + let focus = editor.focus_handle(cx); + PopoverMenu::new("hunk-controls-dropdown") + .trigger( + IconButton::new( + "toggle_editor_selections_icon", + IconName::EllipsisVertical, + ) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small) + .style(ButtonStyle::Subtle) + .selected( + hunk_controls_menu_handle.is_deployed(), + ) + .when( + !hunk_controls_menu_handle.is_deployed(), + |this| { + this.tooltip(|cx| { + Tooltip::text("Hunk Controls", cx) + }) + }, + ), + ) + .anchor(AnchorCorner::TopRight) + .with_handle(hunk_controls_menu_handle) + .menu(move |cx| { + let focus = focus.clone(); + let menu = + ContextMenu::build(cx, move |menu, _| { + menu.context(focus.clone()).action( + "Discard All", + RevertFile.boxed_clone(), + ) + }); + Some(menu) + }) + }) + .child( + IconButton::new("discard", IconName::RotateCcw) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small) + .tooltip({ + let focus_handle = editor.focus_handle(cx); + move |cx| { + Tooltip::for_action_in( + "Discard Hunk", + &RevertSelectedHunks, + &focus_handle, + cx, + ) + } + }) + .on_click({ + let editor = editor.clone(); + let hunk = hunk.clone(); + move |_event, cx| { + let multi_buffer = + editor.read(cx).buffer().clone(); + let multi_buffer_snapshot = + multi_buffer.read(cx).snapshot(cx); + let mut revert_changes = HashMap::default(); + if let Some(hunk) = + crate::hunk_diff::to_diff_hunk( + &hunk, + &multi_buffer_snapshot, + ) + { + Editor::prepare_revert_change( + &mut revert_changes, + &multi_buffer, + &hunk, + cx, + ); + } + if !revert_changes.is_empty() { + editor.update(cx, |editor, cx| { + editor.revert(revert_changes, cx) + }); + } + } + }), + ) + .child( + IconButton::new("collapse", IconName::Close) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small) + .tooltip({ + let focus_handle = editor.focus_handle(cx); + move |cx| { + Tooltip::for_action_in( + "Collapse Hunk", + &ToggleHunkDiff, + &focus_handle, + cx, + ) + } + }) + .on_click({ + let editor = editor.clone(); + let hunk = hunk.clone(); + move |_event, cx| { + editor.update(cx, |editor, cx| { + editor.toggle_hovered_hunk(&hunk, cx); + }); + } + }), + ), + ), + ) + .into_any_element() + } + }), + } + } + + fn deleted_text_block( + hunk: &HoveredHunk, diff_base_buffer: Model, deleted_text_height: u32, - hunk: &HoveredHunk, - cx: &mut ViewContext<'_, Self>, - ) -> Option { + cx: &mut ViewContext<'_, Editor>, + ) -> BlockProperties { + let gutter_color = match hunk.status { + DiffHunkStatus::Added => unreachable!(), + DiffHunkStatus::Modified => cx.theme().status().modified, + DiffHunkStatus::Removed => cx.theme().status().deleted, + }; let deleted_hunk_color = deleted_hunk_color(cx); let (editor_height, editor_with_deleted_text) = editor_with_deleted_text(diff_base_buffer, deleted_hunk_color, hunk, cx); let editor = cx.view().clone(); let hunk = hunk.clone(); let height = editor_height.max(deleted_text_height); - let mut new_block_ids = self.insert_blocks( - Some(BlockProperties { - position: hunk.multi_buffer_range.start, - height, - style: BlockStyle::Flex, - disposition: BlockDisposition::Above, - render: Box::new(move |cx| { - let width = EditorElement::diff_hunk_strip_width(cx.line_height()); - let gutter_dimensions = editor.read(cx.context).gutter_dimensions; + BlockProperties { + position: hunk.multi_buffer_range.start, + height, + style: BlockStyle::Flex, + disposition: BlockDisposition::Above, + priority: 0, + render: Box::new(move |cx| { + let width = EditorElement::diff_hunk_strip_width(cx.line_height()); + let gutter_dimensions = editor.read(cx.context).gutter_dimensions; - let close_button = editor.update(cx.context, |editor, cx| { - let editor_snapshot = editor.snapshot(cx); - let hunk_display_range = hunk - .multi_buffer_range - .clone() - .to_display_points(&editor_snapshot); - editor.close_hunk_diff_button( - hunk.clone(), - hunk_display_range.start.row(), - cx, - ) - }); - - h_flex() - .id("gutter with editor") - .bg(deleted_hunk_color) - .h(height as f32 * cx.line_height()) - .w_full() - .child( - h_flex() - .id("gutter") - .max_w(gutter_dimensions.full_width()) - .min_w(gutter_dimensions.full_width()) - .size_full() - .child( - h_flex() - .id("gutter hunk") - .bg(cx.theme().status().deleted) - .pl(gutter_dimensions.margin - + gutter_dimensions - .git_blame_entries_width - .unwrap_or_default()) - .max_w(width) - .min_w(width) - .size_full() - .cursor(CursorStyle::PointingHand) - .on_mouse_down(MouseButton::Left, { - let editor = editor.clone(); - let hunk = hunk.clone(); - move |event, cx| { - let modifiers = event.modifiers; - if modifiers.control || modifiers.platform { - editor.update(cx, |editor, cx| { - editor.toggle_hovered_hunk(&hunk, cx); - }); - } else { - editor.update(cx, |editor, cx| { - editor.open_hunk_context_menu( - hunk.clone(), - event.position, - cx, - ); - }); - } - } - }), - ) - .child( - v_flex() - .size_full() - .pt(rems(0.25)) - .justify_start() - .child(close_button), - ), - ) - .child(editor_with_deleted_text.clone()) - .into_any_element() - }), - priority: 0, + h_flex() + .id(cx.block_id) + .bg(deleted_hunk_color) + .h(height as f32 * cx.line_height()) + .w_full() + .child( + h_flex() + .id("gutter") + .max_w(gutter_dimensions.full_width()) + .min_w(gutter_dimensions.full_width()) + .size_full() + .child( + h_flex() + .id("gutter hunk") + .bg(gutter_color) + .pl(gutter_dimensions.margin + + gutter_dimensions + .git_blame_entries_width + .unwrap_or_default()) + .max_w(width) + .min_w(width) + .size_full() + .cursor(CursorStyle::PointingHand) + .on_mouse_down(MouseButton::Left, { + let editor = editor.clone(); + let hunk = hunk.clone(); + move |_event, cx| { + editor.update(cx, |editor, cx| { + editor.toggle_hovered_hunk(&hunk, cx); + }); + } + }), + ), + ) + .child(editor_with_deleted_text.clone()) + .into_any_element() }), - None, - cx, - ); - if new_block_ids.len() == 1 { - new_block_ids.pop() - } else { - debug_panic!( - "Inserted one editor block but did not receive exactly one block id: {new_block_ids:?}" - ); - None } } @@ -521,7 +715,7 @@ impl Editor { .expanded_hunks .hunks .drain(..) - .filter_map(|expanded_hunk| expanded_hunk.block) + .flat_map(|expanded_hunk| expanded_hunk.blocks.into_iter()) .collect::>(); if to_remove.is_empty() { false @@ -603,7 +797,7 @@ impl Editor { expanded_hunk.folded = true; highlights_to_remove .push(expanded_hunk.hunk_range.clone()); - if let Some(block) = expanded_hunk.block.take() { + for block in expanded_hunk.blocks.drain(..) { blocks_to_remove.insert(block); } break; @@ -650,7 +844,7 @@ impl Editor { } } if !retain { - blocks_to_remove.extend(expanded_hunk.block); + blocks_to_remove.extend(expanded_hunk.blocks.drain(..)); highlights_to_remove.push(expanded_hunk.hunk_range.clone()); } retain @@ -749,7 +943,7 @@ fn added_hunk_color(cx: &AppContext) -> Hsla { } fn deleted_hunk_color(cx: &AppContext) -> Hsla { - let mut deleted_color = cx.theme().status().git().deleted; + let mut deleted_color = cx.theme().status().deleted; deleted_color.fade_out(0.7); deleted_color } @@ -788,32 +982,15 @@ fn editor_with_deleted_text( false, cx, ); - - let subscription_editor = parent_editor.clone(); - editor._subscriptions.extend([ - cx.on_blur(&editor.focus_handle, |editor, cx| { - editor.set_current_line_highlight(Some(CurrentLineHighlight::None)); + editor.set_current_line_highlight(Some(CurrentLineHighlight::None)); + editor + ._subscriptions + .extend([cx.on_blur(&editor.focus_handle, |editor, cx| { editor.change_selections(None, cx, |s| { s.try_cancel(); }); - cx.notify(); - }), - cx.on_focus(&editor.focus_handle, move |editor, cx| { - let restored_highlight = if let Some(parent_editor) = subscription_editor.upgrade() - { - parent_editor.read(cx).current_line_highlight - } else { - None - }; - editor.set_current_line_highlight(restored_highlight); - cx.notify(); - }), - cx.observe_global::(|editor, cx| { - if !editor.is_focused(cx) { - editor.set_current_line_highlight(Some(CurrentLineHighlight::None)); - } - }), - ]); + })]); + let parent_editor_for_reverts = parent_editor.clone(); let original_multi_buffer_range = hunk.multi_buffer_range.clone(); let diff_base_range = hunk.diff_base_byte_range.clone();