diff --git a/crates/acp_thread/src/diff.rs b/crates/acp_thread/src/diff.rs index d238d22c19..06ae822515 100644 --- a/crates/acp_thread/src/diff.rs +++ b/crates/acp_thread/src/diff.rs @@ -89,13 +89,10 @@ impl Diff { let language = buffer.read(cx).language().cloned(); let language_registry = buffer.read(cx).language_registry(); let buffer_diff = cx.new(|cx| { - BufferDiff::new_unchanged( - &buffer_text_snapshot, - language, - language_registry, - buffer_diff::DiffBaseKind::Custom, - cx, - ) + let mut diff = + BufferDiff::new_unchanged(&buffer_text_snapshot, language, language_registry, cx); + diff.set_operations(Arc::new(buffer_diff::RestoreDiffOperations)); + diff }); let multibuffer = cx.new(|cx| { @@ -396,13 +393,9 @@ async fn build_buffer_diff( let base_text = base_text_exists.then(|| old_text); let diff = cx.new(|cx| { - BufferDiff::new( - &buffer, - language, - language_registry, - buffer_diff::DiffBaseKind::Custom, - cx, - ) + let mut diff = BufferDiff::new(&buffer, language, language_registry, cx); + diff.set_operations(Arc::new(buffer_diff::RestoreDiffOperations)); + diff }); diff.update(cx, |diff, cx| { diff.set_base_text(base_text, buffer.text, cx) diff --git a/crates/action_log/src/action_log.rs b/crates/action_log/src/action_log.rs index db4cdef4e3..838e274d52 100644 --- a/crates/action_log/src/action_log.rs +++ b/crates/action_log/src/action_log.rs @@ -160,13 +160,9 @@ impl ActionLog { let language = buffer.read(cx).language().cloned(); let language_registry = buffer.read(cx).language_registry(); let diff = cx.new(|cx| { - BufferDiff::new( - &text_snapshot, - language, - language_registry, - buffer_diff::DiffBaseKind::Custom, - cx, - ) + let mut diff = BufferDiff::new(&text_snapshot, language, language_registry, cx); + diff.set_operations(Arc::new(buffer_diff::RestoreDiffOperations)); + diff }); let (diff_update_tx, diff_update_rx) = mpsc::unbounded(); let diff_base; diff --git a/crates/agent_ui/src/agent_diff.rs b/crates/agent_ui/src/agent_diff.rs index 9995be316a..bef45baa5d 100644 --- a/crates/agent_ui/src/agent_diff.rs +++ b/crates/agent_ui/src/agent_diff.rs @@ -6,8 +6,8 @@ use anyhow::Result; use buffer_diff::DiffHunkStatus; use collections::{HashMap, HashSet}; use editor::{ - DiffHunkDelegate, Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, - MultiBufferSnapshot, ResolvedDiffHunks, SelectionEffects, SplittableEditor, ToPoint, + DiffHunkRenderer, Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, + MultiBufferSnapshot, SelectionEffects, SplittableEditor, ToPoint, actions::{GoToHunk, GoToPreviousHunk}, multibuffer_context_lines, scroll::Autoscroll, @@ -101,7 +101,7 @@ impl AgentDiffPane { cx, ); diff_display_editor - .set_diff_hunk_delegate(Some(agent_diff_delegate(&thread, workspace.clone())), cx); + .set_diff_hunk_renderer(Some(agent_diff_renderer(&thread, workspace.clone())), cx); diff_display_editor.update_editors(cx, |editor, _cx| { editor.register_addon(AgentDiffAddon); }); @@ -734,41 +734,22 @@ impl Render for AgentDiffPane { } } -struct AgentDiffDelegate { +struct AgentDiffHunkRenderer { thread: Entity, workspace: WeakEntity, } -fn agent_diff_delegate( +fn agent_diff_renderer( thread: &Entity, workspace: WeakEntity, -) -> Arc { - Arc::new(AgentDiffDelegate { +) -> Arc { + Arc::new(AgentDiffHunkRenderer { thread: thread.clone(), workspace, }) } -impl DiffHunkDelegate for AgentDiffDelegate { - fn toggle( - &self, - _hunks: Vec, - _editor: &mut Editor, - _window: &mut Window, - _cx: &mut Context, - ) { - } - - fn stage_or_unstage( - &self, - _stage: bool, - _hunks: Vec, - _editor: &mut Editor, - _window: &mut Window, - _cx: &mut Context, - ) { - } - +impl DiffHunkRenderer for AgentDiffHunkRenderer { fn render_hunk_controls( &self, row: u32, @@ -1579,7 +1560,7 @@ impl AgentDiff { for (editor, _) in self.reviewing_editors.drain() { editor .update(cx, |editor, cx| { - editor.set_diff_hunk_delegate(None, cx); + editor.set_diff_hunk_renderer(None, cx); editor.unregister_addon::(); }) .ok(); @@ -1628,8 +1609,8 @@ impl AgentDiff { if previous_state.is_none() { editor.update(cx, |editor, cx| { - editor.set_diff_hunk_delegate( - Some(agent_diff_delegate(&thread, workspace.clone())), + editor.set_diff_hunk_renderer( + Some(agent_diff_renderer(&thread, workspace.clone())), cx, ); editor.set_expand_all_diff_hunks(cx); @@ -1678,7 +1659,7 @@ impl AgentDiff { if in_workspace { editor .update(cx, |editor, cx| { - editor.set_diff_hunk_delegate(None, cx); + editor.set_diff_hunk_renderer(None, cx); editor.unregister_addon::(); }) .ok(); diff --git a/crates/agent_ui/src/entry_view_state.rs b/crates/agent_ui/src/entry_view_state.rs index 092450b0fb..1553879628 100644 --- a/crates/agent_ui/src/entry_view_state.rs +++ b/crates/agent_ui/src/entry_view_state.rs @@ -6,7 +6,7 @@ use agent_client_protocol::schema::v1 as acp; use agent_settings::AgentSettings; use collections::{HashMap, HashSet}; use editor::{ - Editor, EditorEvent, EditorMode, MinimapVisibility, RestoreOnlyUnstagedDiffHunkDelegate, + Editor, EditorEvent, EditorMode, HiddenUnstagedDiffHunkRenderer, MinimapVisibility, SizingBehavior, }; use gpui::{ @@ -685,7 +685,7 @@ fn create_editor_diff( editor.set_show_code_actions(false, cx); editor.set_show_git_diff_gutter(false, cx); editor.set_expand_all_diff_hunks(cx); - editor.set_diff_hunk_delegate(Some(Arc::new(RestoreOnlyUnstagedDiffHunkDelegate)), cx); + editor.set_diff_hunk_renderer(Some(Arc::new(HiddenUnstagedDiffHunkRenderer)), cx); editor.set_text_style_refinement(diff_editor_text_style_refinement(cx)); editor }) diff --git a/crates/buffer_diff/src/buffer_diff.rs b/crates/buffer_diff/src/buffer_diff.rs index aa0ffe4e70..b4249c97f8 100644 --- a/crates/buffer_diff/src/buffer_diff.rs +++ b/crates/buffer_diff/src/buffer_diff.rs @@ -25,23 +25,63 @@ pub struct BufferDiff { diff_snapshot: Option, secondary_diff: Option>, buffer_snapshot: text::BufferSnapshot, - base_kind: DiffBaseKind, + operations: Option>, } -/// Where this diff's base text came from. Only diffs whose base is HEAD -/// support staging and restoring hunks; a diff against any other base (e.g. -/// the merge base with another branch) would rewrite committed work. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum DiffBaseKind { - /// The buffer's committed (HEAD) content. - Head, - /// The buffer's index (staged) content. - Index, - /// An arbitrary blob, such as the merge base with another branch. - Oid, - /// Arbitrary caller-provided text, such as an agent's original text, - /// the clipboard, or another file. - Custom, +pub trait DiffOperations { + fn supports_staging(&self) -> bool; + fn supports_unstaging(&self) -> bool; + fn supports_restore(&self) -> bool; + fn stage( + &self, + _diff: Entity, + _buffer: Option>, + _buffer_ranges: Vec>, + _cx: &mut App, + ) { + } + fn unstage( + &self, + _diff: Entity, + _buffer: Option>, + _buffer_ranges: Vec>, + _cx: &mut App, + ) { + } +} + +pub struct RestoreDiffOperations; + +impl DiffOperations for RestoreDiffOperations { + fn supports_staging(&self) -> bool { + false + } + + fn supports_unstaging(&self) -> bool { + false + } + + fn supports_restore(&self) -> bool { + true + } +} + +#[cfg(any(test, feature = "test-support"))] +struct TestDiffOperations; + +#[cfg(any(test, feature = "test-support"))] +impl DiffOperations for TestDiffOperations { + fn supports_staging(&self) -> bool { + true + } + + fn supports_unstaging(&self) -> bool { + true + } + + fn supports_restore(&self) -> bool { + true + } } #[derive(Clone)] @@ -1570,7 +1610,6 @@ impl BufferDiff { buffer: &text::BufferSnapshot, language: Option>, language_registry: Option>, - base_kind: DiffBaseKind, cx: &mut App, ) -> Self { let base_text = cx.new(|cx| { @@ -1589,14 +1628,13 @@ impl BufferDiff { diff_snapshot: None, buffer_snapshot: buffer.clone(), secondary_diff: None, - base_kind, + operations: None, } } pub fn new_with_base_text_buffer( buffer: &text::BufferSnapshot, base_text_buffer: Entity, - base_kind: DiffBaseKind, _cx: &mut App, ) -> Self { BufferDiff { @@ -1605,7 +1643,7 @@ impl BufferDiff { diff_snapshot: None, buffer_snapshot: buffer.clone(), secondary_diff: None, - base_kind, + operations: None, } } @@ -1613,7 +1651,6 @@ impl BufferDiff { buffer: &text::BufferSnapshot, language: Option>, language_registry: Option>, - base_kind: DiffBaseKind, cx: &mut Context, ) -> Self { let base_text = buffer.text(); @@ -1644,7 +1681,7 @@ impl BufferDiff { diff_snapshot: Some(diff_snapshot), buffer_snapshot: buffer.clone(), secondary_diff: None, - base_kind, + operations: None, } } @@ -1654,7 +1691,8 @@ impl BufferDiff { buffer: &text::BufferSnapshot, cx: &mut Context, ) -> Self { - let mut this = BufferDiff::new(buffer, None, None, DiffBaseKind::Head, cx); + let mut this = BufferDiff::new(buffer, None, None, cx); + this.set_operations(Arc::new(TestDiffOperations)); let mut base_text = base_text.to_owned(); text::LineEnding::normalize(&mut base_text); let base_text_buffer = cx.new(|cx| { @@ -1678,14 +1716,12 @@ impl BufferDiff { self.secondary_diff = Some(diff); } - pub fn base_kind(&self) -> DiffBaseKind { - self.base_kind + pub fn set_operations(&mut self, operations: Arc) { + self.operations = Some(operations); } - /// Whether hunks in this diff can be staged or restored: true only when - /// the diff's base is HEAD. - pub fn is_stageable(&self) -> bool { - self.base_kind == DiffBaseKind::Head + pub fn operations(&self) -> Option> { + self.operations.clone() } pub fn secondary_diff(&self) -> Option> { @@ -2482,8 +2518,7 @@ mod tests { ], ); - diff = cx - .update(|cx| BufferDiff::new(&buffer, None, None, DiffBaseKind::Head, cx).snapshot(cx)); + diff = cx.update(|cx| BufferDiff::new(&buffer, None, None, cx).snapshot(cx)); assert_hunks::<&str, _>( diff.hunks_intersecting_range( Anchor::min_max_range_for_buffer(buffer.remote_id()), @@ -3188,8 +3223,7 @@ mod tests { let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1); - let empty_diff = cx - .update(|cx| BufferDiff::new(&buffer, None, None, DiffBaseKind::Head, cx).snapshot(cx)); + let empty_diff = cx.update(|cx| BufferDiff::new(&buffer, None, None, cx).snapshot(cx)); let diff_1 = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx); let DiffChanged { changed_range, @@ -4347,8 +4381,7 @@ mod tests { ); let buffer_snapshot = buffer.snapshot(); - let diff = - cx.new(|cx| BufferDiff::new(&buffer_snapshot, None, None, DiffBaseKind::Head, cx)); + let diff = cx.new(|cx| BufferDiff::new(&buffer_snapshot, None, None, cx)); diff.update(cx, |diff, cx| { diff.set_base_text(Some(Arc::from(base_text_crlf)), buffer_snapshot.clone(), cx) }) diff --git a/crates/edit_prediction_ui/src/rate_prediction_modal.rs b/crates/edit_prediction_ui/src/rate_prediction_modal.rs index 6f40806912..23607042c7 100644 --- a/crates/edit_prediction_ui/src/rate_prediction_modal.rs +++ b/crates/edit_prediction_ui/src/rate_prediction_modal.rs @@ -605,7 +605,6 @@ impl RatePredictionsModal { &predicted_buffer_snapshot.text, predicted_buffer_snapshot.language().cloned(), predicted_buffer.read(cx).language_registry(), - buffer_diff::DiffBaseKind::Custom, cx, ) }); @@ -712,7 +711,6 @@ impl RatePredictionsModal { &expected_buffer_snapshot.text, expected_buffer_snapshot.language().cloned(), expected_buffer.read(cx).language_registry(), - buffer_diff::DiffBaseKind::Custom, cx, ) }); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 4469963654..e52c283b60 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -108,9 +108,8 @@ pub use element::{ }; pub use git::blame::{BlameRenderer, GitBlame}; pub use git::{ - DiffHunkDelegate, ResolvedDiffHunk, ResolvedDiffHunks, RestoreOnlyDiffHunkDelegate, - RestoreOnlyUnstagedDiffHunkDelegate, UncommittedDiffHunkDelegate, render_diff_hunk_controls, - set_blame_renderer, + DefaultDiffHunkRenderer, DiffHunkRenderer, HiddenDiffHunkRenderer, + HiddenUnstagedDiffHunkRenderer, render_diff_hunk_controls, set_blame_renderer, }; pub(crate) use git::{DiffHunkKey, StoredReviewComment}; use git::{DiffReviewDragState, DiffReviewOverlay, InlineBlamePopover}; @@ -1158,7 +1157,8 @@ pub struct Editor { registered_buffers: HashMap, language_detection_task: Task<()>, load_diff_task: Option>>, - diff_hunk_delegate: Option>, + diff_hunk_renderer: Option>, + diff_hunk_action_target: Option>, selection_mark_mode: bool, toggle_fold_multiple_buffers: Task<()>, _scroll_cursor_center_top_bottom_task: Task<()>, @@ -2507,7 +2507,8 @@ impl Editor { serialize_folds: Task::ready(()), text_style_refinement: None, load_diff_task: None, - diff_hunk_delegate: None, + diff_hunk_renderer: None, + diff_hunk_action_target: None, minimap: None, change_list: ChangeList::new(), mode, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 6e9cfc2620..dbc3b66c13 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -28119,7 +28119,7 @@ async fn test_merge_base_diff_hunks_are_read_only(cx: &mut TestAppContext) { .read(cx) .diff_for(buffer_id) .expect("buffer should have a display diff"); - assert!(!diff.read(cx).is_stageable()); + assert!(diff.read(cx).operations().is_none()); }); editor.update_in(cx, |editor, window, cx| { editor.select_all(&SelectAll, window, cx); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index c6c85605e6..1c618ff094 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -4664,7 +4664,7 @@ impl EditorElement { window: &mut Window, cx: &mut App, ) -> (Vec, Vec<(DisplayRow, Bounds)>) { - let diff_hunk_delegate = editor.read(cx).diff_hunk_delegate(); + let diff_hunk_renderer = editor.read(cx).diff_hunk_renderer(); let hovered_diff_hunk_row = editor.read(cx).hovered_diff_hunk_row; let sticky_top = text_hitbox.bounds.top() + sticky_header_height; @@ -4736,7 +4736,7 @@ impl EditorElement { sticky_top.min(max_y) }; - let mut element = diff_hunk_delegate.render_hunk_controls( + let mut element = diff_hunk_renderer.render_hunk_controls( display_row_range.start.0, status, multi_buffer_range.clone(), @@ -6609,7 +6609,7 @@ impl EditorElement { let unstaged = !self .editor .read(cx) - .diff_hunk_delegate() + .diff_hunk_renderer() .render_hunk_as_staged(&status, cx); let unstaged_hollow = matches!( ProjectSettings::get_global(cx).git.hunk_style, @@ -9398,7 +9398,7 @@ impl Element for EditorElement { }; let (diff_hunk_controls, diff_hunk_control_bounds) = - if is_read_only && self.editor.read(cx).diff_hunk_delegate.is_none() { + if is_read_only && self.editor.read(cx).diff_hunk_renderer.is_none() { (vec![], vec![]) } else { self.layout_diff_hunk_controls( diff --git a/crates/editor/src/git.rs b/crates/editor/src/git.rs index fe3570869e..c380bed76f 100644 --- a/crates/editor/src/git.rs +++ b/crates/editor/src/git.rs @@ -9,65 +9,20 @@ use buffer_diff::{BufferDiff, DiffHunkStatus, DiffHunkStatusKind}; use project::git_store::Repository; #[derive(Clone)] -pub struct ResolvedDiffHunk { - pub buffer_range: Range, - pub diff_base_byte_range: Range, - pub status: DiffHunkStatus, +struct ResolvedDiffHunk { + buffer_range: Range, + diff_base_byte_range: Range, + status: DiffHunkStatus, } #[derive(Clone)] -pub struct ResolvedDiffHunks { - pub diff: Entity, - pub buffer_id: BufferId, - pub buffer: Option>, - pub hunks: Vec, +struct ResolvedDiffHunks { + diff: Entity, + buffer: Option>, + hunks: Vec, } -pub trait DiffHunkDelegate { - fn toggle( - &self, - hunks: Vec, - editor: &mut Editor, - window: &mut Window, - cx: &mut Context, - ); - - fn stage_or_unstage( - &self, - stage: bool, - hunks: Vec, - editor: &mut Editor, - window: &mut Window, - cx: &mut Context, - ); - - fn restore( - &self, - hunks: Vec, - editor: &mut Editor, - window: &mut Window, - cx: &mut Context, - ) { - if hunks.is_empty() || editor.read_only(cx) { - return; - } - self.stage_or_unstage(false, hunks.clone(), editor, window, cx); - editor.transact(window, cx, |editor, window, cx| { - editor.restore_diff_hunks(hunks, cx); - let selections = editor - .selections - .all::(&editor.display_snapshot(cx)); - editor.change_selections( - SelectionEffects::no_scroll(), - window, - cx, - |selections_state| { - selections_state.select(selections); - }, - ); - }); - } - +pub trait DiffHunkRenderer { fn render_hunk_controls( &self, row: u32, @@ -85,63 +40,9 @@ pub trait DiffHunkDelegate { } } -pub struct UncommittedDiffHunkDelegate; - -impl DiffHunkDelegate for UncommittedDiffHunkDelegate { - fn toggle( - &self, - hunks: Vec, - editor: &mut Editor, - window: &mut Window, - cx: &mut Context, - ) { - let stage = hunks - .iter() - .flat_map(|hunks| hunks.hunks.iter()) - .any(|hunk| hunk.status.has_secondary_hunk()); - self.stage_or_unstage(stage, hunks, editor, window, cx); - } - - fn stage_or_unstage( - &self, - stage: bool, - hunks: Vec, - editor: &mut Editor, - _window: &mut Window, - cx: &mut Context, - ) { - let Some(project) = editor.project() else { - return; - }; - for hunks in hunks { - let Some(buffer) = hunks.buffer else { - continue; - }; - - let ranges = hunks - .hunks - .into_iter() - .map(|hunk| hunk.buffer_range) - .collect::>(); - if ranges.is_empty() { - continue; - } - let secondary_diff = hunks.diff.read(cx).secondary_diff(); - project - .update(cx, |project, cx| { - if stage { - let Some(secondary_diff) = secondary_diff else { - return Err(anyhow::anyhow!("diff has no unstaged secondary")); - }; - project.stage_hunks(buffer, secondary_diff, ranges, cx) - } else { - project.unstage_uncommitted_hunks(buffer, hunks.diff, ranges, cx) - } - }) - .log_err(); - } - } +pub struct DefaultDiffHunkRenderer; +impl DiffHunkRenderer for DefaultDiffHunkRenderer { fn render_hunk_controls( &self, row: u32, @@ -166,37 +67,9 @@ impl DiffHunkDelegate for UncommittedDiffHunkDelegate { } } -pub struct RestoreOnlyDiffHunkDelegate; - -impl DiffHunkDelegate for RestoreOnlyDiffHunkDelegate { - fn toggle( - &self, - _hunks: Vec, - _editor: &mut Editor, - _window: &mut Window, - _cx: &mut Context, - ) { - } - - fn stage_or_unstage( - &self, - _stage: bool, - _hunks: Vec, - _editor: &mut Editor, - _window: &mut Window, - _cx: &mut Context, - ) { - } - - fn restore( - &self, - _hunks: Vec, - _editor: &mut Editor, - _window: &mut Window, - _cx: &mut Context, - ) { - } +pub struct HiddenDiffHunkRenderer; +impl DiffHunkRenderer for HiddenDiffHunkRenderer { fn render_hunk_controls( &self, _row: u32, @@ -212,28 +85,9 @@ impl DiffHunkDelegate for RestoreOnlyDiffHunkDelegate { } } -pub struct RestoreOnlyUnstagedDiffHunkDelegate; - -impl DiffHunkDelegate for RestoreOnlyUnstagedDiffHunkDelegate { - fn toggle( - &self, - _hunks: Vec, - _editor: &mut Editor, - _window: &mut Window, - _cx: &mut Context, - ) { - } - - fn stage_or_unstage( - &self, - _stage: bool, - _hunks: Vec, - _editor: &mut Editor, - _window: &mut Window, - _cx: &mut Context, - ) { - } +pub struct HiddenUnstagedDiffHunkRenderer; +impl DiffHunkRenderer for HiddenUnstagedDiffHunkRenderer { fn render_hunk_controls( &self, _row: u32, @@ -465,7 +319,6 @@ impl Editor { if !resolved_hunks.is_empty() { resolved.push(ResolvedDiffHunks { diff, - buffer_id: main_buffer_id, buffer, hunks: resolved_hunks, }); @@ -475,24 +328,24 @@ impl Editor { resolved } - pub fn diff_hunk_delegate(&self) -> Arc { - self.diff_hunk_delegate + pub fn diff_hunk_renderer(&self) -> Arc { + self.diff_hunk_renderer .clone() - .unwrap_or_else(|| Arc::new(UncommittedDiffHunkDelegate)) + .unwrap_or_else(|| Arc::new(DefaultDiffHunkRenderer)) } - pub fn set_diff_hunk_delegate( + pub fn set_diff_hunk_renderer( &mut self, - delegate: Option>, + renderer: Option>, cx: &mut Context, ) { - let had_delegate = self.diff_hunk_delegate.is_some(); - let has_delegate = delegate.is_some(); - self.diff_hunk_delegate = delegate; + let had_renderer = self.diff_hunk_renderer.is_some(); + let has_renderer = renderer.is_some(); + self.diff_hunk_renderer = renderer; - if !had_delegate && has_delegate { + if !had_renderer && has_renderer { self.load_diff_task.take(); - } else if had_delegate && !has_delegate { + } else if had_renderer && !has_renderer { self.buffer.update(cx, |buffer, cx| { buffer.set_all_diff_hunks_collapsed(cx); }); @@ -512,6 +365,10 @@ impl Editor { cx.notify(); } + pub(crate) fn set_diff_hunk_action_target(&mut self, target: Option>) { + self.diff_hunk_action_target = target; + } + pub fn git_blame_inline_enabled(&self) -> bool { self.git_blame_inline_enabled } @@ -1062,7 +919,7 @@ impl Editor { ); } - pub fn restore_diff_hunks(&mut self, hunks: Vec, cx: &mut Context) { + fn restore_diff_hunks(&mut self, hunks: Vec, cx: &mut Context) { let mut revert_changes = Vec::new(); for hunks in hunks { let Some(buffer) = hunks.buffer else { @@ -1774,36 +1631,104 @@ impl Editor { pub fn apply_toggle( &mut self, hunks: Vec, - window: &mut Window, + _window: &mut Window, cx: &mut Context, ) { - let mut hunks = self.resolve_diff_hunks(hunks, cx); - if self.diff_hunk_delegate.is_none() { - hunks.retain(|hunks| hunks.diff.read(cx).is_stageable()); - } + let hunks = self.resolve_diff_hunks(hunks, cx); if hunks.is_empty() { return; } - let delegate = self.diff_hunk_delegate(); - delegate.toggle(hunks, self, window, cx); + if let Some(target) = self.diff_hunk_action_target.clone() { + target + .update(cx, |editor, cx| { + editor.toggle_resolved_diff_hunks(hunks, cx); + }) + .log_err(); + return; + } + self.toggle_resolved_diff_hunks(hunks, cx); + } + + fn toggle_resolved_diff_hunks( + &mut self, + hunks: Vec, + cx: &mut Context, + ) { + for hunks in hunks { + let Some(operations) = hunks.diff.read(cx).operations() else { + continue; + }; + let stage = if !operations.supports_unstaging() { + true + } else if !operations.supports_staging() { + false + } else { + hunks + .hunks + .iter() + .any(|hunk| hunk.status.has_secondary_hunk()) + }; + let ranges = hunks + .hunks + .into_iter() + .map(|hunk| hunk.buffer_range) + .collect(); + if stage { + operations.stage(hunks.diff, hunks.buffer, ranges, cx); + } else { + operations.unstage(hunks.diff, hunks.buffer, ranges, cx); + } + } } pub fn apply_stage_or_unstage( &mut self, stage: bool, hunks: Vec, - window: &mut Window, + _window: &mut Window, cx: &mut Context, ) { - let mut hunks = self.resolve_diff_hunks(hunks, cx); - if self.diff_hunk_delegate.is_none() { - hunks.retain(|hunks| hunks.diff.read(cx).is_stageable()); - } + let hunks = self.resolve_diff_hunks(hunks, cx); if hunks.is_empty() { return; } - let delegate = self.diff_hunk_delegate(); - delegate.stage_or_unstage(stage, hunks, self, window, cx); + if let Some(target) = self.diff_hunk_action_target.clone() { + target + .update(cx, |editor, cx| { + editor.stage_or_unstage_resolved_diff_hunks(stage, hunks, cx); + }) + .log_err(); + return; + } + self.stage_or_unstage_resolved_diff_hunks(stage, hunks, cx); + } + + fn stage_or_unstage_resolved_diff_hunks( + &mut self, + stage: bool, + hunks: Vec, + cx: &mut Context, + ) { + for hunks in hunks { + let Some(operations) = hunks.diff.read(cx).operations() else { + continue; + }; + if (stage && !operations.supports_staging()) + || (!stage && !operations.supports_unstaging()) + { + continue; + } + let ranges = hunks + .hunks + .into_iter() + .map(|hunk| hunk.buffer_range) + .collect(); + if stage { + operations.stage(hunks.diff, hunks.buffer, ranges, cx); + } else { + operations.unstage(hunks.diff, hunks.buffer, ranges, cx); + } + } } pub fn apply_restore( @@ -1812,15 +1737,65 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - let mut hunks = self.resolve_diff_hunks(hunks, cx); - if self.diff_hunk_delegate.is_none() { - hunks.retain(|hunks| hunks.diff.read(cx).is_stageable()); - } + let hunks = self.resolve_diff_hunks(hunks, cx); if hunks.is_empty() { return; } - let delegate = self.diff_hunk_delegate(); - delegate.restore(hunks, self, window, cx); + if let Some(target) = self.diff_hunk_action_target.clone() { + target + .update(cx, |editor, cx| { + editor.restore_resolved_diff_hunks(hunks, window, cx); + }) + .log_err(); + return; + } + self.restore_resolved_diff_hunks(hunks, window, cx); + } + + fn restore_resolved_diff_hunks( + &mut self, + hunks: Vec, + window: &mut Window, + cx: &mut Context, + ) { + if self.read_only(cx) { + return; + } + let mut restorable_hunks = Vec::new(); + for hunks in hunks { + let Some(operations) = hunks.diff.read(cx).operations() else { + continue; + }; + if !operations.supports_restore() { + continue; + } + if operations.supports_unstaging() { + let ranges = hunks + .hunks + .iter() + .map(|hunk| hunk.buffer_range.clone()) + .collect(); + operations.unstage(hunks.diff.clone(), hunks.buffer.clone(), ranges, cx); + } + restorable_hunks.push(hunks); + } + if restorable_hunks.is_empty() { + return; + } + self.transact(window, cx, |editor, window, cx| { + editor.restore_diff_hunks(restorable_hunks, cx); + let selections = editor + .selections + .all::(&editor.display_snapshot(cx)); + editor.change_selections( + SelectionEffects::no_scroll(), + window, + cx, + |selections_state| { + selections_state.select(selections); + }, + ); + }); } pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut Context) -> bool { @@ -3063,15 +3038,23 @@ pub fn render_diff_hunk_controls( _window: &mut Window, cx: &mut App, ) -> AnyElement { - let stageable = hunk_range + let operations = hunk_range .start .buffer_id() .and_then(|buffer_id| editor.read(cx).buffer().read(cx).diff_for(buffer_id)) - .is_some_and(|diff| diff.read(cx).is_stageable()); - let show_stage_restore = stageable - && ProjectSettings::get_global(cx) - .git - .show_stage_restore_buttons; + .and_then(|diff| diff.read(cx).operations()); + let show_stage_restore = ProjectSettings::get_global(cx) + .git + .show_stage_restore_buttons; + let supports_staging = operations + .as_ref() + .is_some_and(|operations| operations.supports_staging()); + let supports_unstaging = operations + .as_ref() + .is_some_and(|operations| operations.supports_unstaging()); + let supports_restore = operations + .as_ref() + .is_some_and(|operations| operations.supports_restore()); h_flex() .h(line_height) @@ -3087,64 +3070,69 @@ pub fn render_diff_hunk_controls( .gap_1() .block_mouse_except_scroll() .shadow_md() - .when(show_stage_restore, |el| { - el.child(if status.has_secondary_hunk() { - Button::new(("stage", row as u64), "Stage") - .alpha(if status.is_pending() { 0.66 } else { 1.0 }) - .tooltip({ - let focus_handle = editor.focus_handle(cx); - move |_window, cx| { - Tooltip::for_action_in( - "Stage Hunk", - &::git::ToggleStaged, - &focus_handle, - cx, - ) - } - }) - .on_click({ - let editor = editor.clone(); - move |_event, window, cx| { - editor.update(cx, |editor, cx| { - editor.stage_or_unstage_diff_hunks( - true, - vec![hunk_range.start..hunk_range.start], - window, + .when( + show_stage_restore + && ((status.has_secondary_hunk() && supports_staging) + || (!status.has_secondary_hunk() && supports_unstaging)), + |el| { + el.child(if status.has_secondary_hunk() { + Button::new(("stage", row as u64), "Stage") + .alpha(if status.is_pending() { 0.66 } else { 1.0 }) + .tooltip({ + let focus_handle = editor.focus_handle(cx); + move |_window, cx| { + Tooltip::for_action_in( + "Stage Hunk", + &::git::ToggleStaged, + &focus_handle, cx, - ); - }); - } - }) - } else { - Button::new(("unstage", row as u64), "Unstage") - .alpha(if status.is_pending() { 0.66 } else { 1.0 }) - .tooltip({ - let focus_handle = editor.focus_handle(cx); - move |_window, cx| { - Tooltip::for_action_in( - "Unstage Hunk", - &::git::ToggleStaged, - &focus_handle, - cx, - ) - } - }) - .on_click({ - let editor = editor.clone(); - move |_event, window, cx| { - editor.update(cx, |editor, cx| { - editor.stage_or_unstage_diff_hunks( - false, - vec![hunk_range.start..hunk_range.start], - window, + ) + } + }) + .on_click({ + let editor = editor.clone(); + move |_event, window, cx| { + editor.update(cx, |editor, cx| { + editor.stage_or_unstage_diff_hunks( + true, + vec![hunk_range.start..hunk_range.start], + window, + cx, + ); + }); + } + }) + } else { + Button::new(("unstage", row as u64), "Unstage") + .alpha(if status.is_pending() { 0.66 } else { 1.0 }) + .tooltip({ + let focus_handle = editor.focus_handle(cx); + move |_window, cx| { + Tooltip::for_action_in( + "Unstage Hunk", + &::git::ToggleStaged, + &focus_handle, cx, - ); - }); - } - }) - }) - }) - .when(show_stage_restore, |el| { + ) + } + }) + .on_click({ + let editor = editor.clone(); + move |_event, window, cx| { + editor.update(cx, |editor, cx| { + editor.stage_or_unstage_diff_hunks( + false, + vec![hunk_range.start..hunk_range.start], + window, + cx, + ); + }); + } + }) + }) + }, + ) + .when(show_stage_restore && supports_restore, |el| { el.child( Button::new(("restore", row as u64), "Restore") .tooltip({ @@ -3269,7 +3257,7 @@ impl Editor { let buffer = self.buffer.clone(); cx.spawn(async move |_, cx| { let diffs = future::join_all(tasks).await; - if editor.read_with(cx, |editor, _cx| editor.diff_hunk_delegate.is_some()) { + if editor.read_with(cx, |editor, _cx| editor.diff_hunk_renderer.is_some()) { return; } diff --git a/crates/editor/src/split.rs b/crates/editor/src/split.rs index 2d2a72a43e..a06d729e6a 100644 --- a/crates/editor/src/split.rs +++ b/crates/editor/src/split.rs @@ -22,14 +22,12 @@ use rope::Point; use settings::{DiffViewStyle, SeedQuerySetting, Settings, SettingsStore, update_settings_file}; use text::{Bias, BufferId, OffsetRangeExt as _, Patch, ToPoint as _}; -use ui::{Toggleable as _, Tooltip, prelude::*, render_modifiers}; -use util::ResultExt as _; - use crate::{ display_map::CompanionExcerptPatch, element::SplitSide, split_editor_view::{SplitEditorState, SplitEditorView}, }; +use ui::{Toggleable as _, Tooltip, prelude::*, render_modifiers}; use workspace::{ ActivatePaneLeft, ActivatePaneRight, Item, ToolbarItemLocation, Workspace, item::{ItemBufferKind, ItemEvent, SaveOptions, TabContentParams}, @@ -37,8 +35,8 @@ use workspace::{ }; use crate::{ - Autoscroll, DiffHunkDelegate, Editor, EditorEvent, EditorSettings, ResolvedDiffHunks, - ToggleSoftWrap, UncommittedDiffHunkDelegate, + Autoscroll, DefaultDiffHunkRenderer, DiffHunkRenderer, Editor, EditorEvent, EditorSettings, + ToggleSoftWrap, actions::{DisableBreakpoint, EditLogBreakpoint, EnableBreakpoint, ToggleBreakpoint}, display_map::Companion, }; @@ -151,63 +149,11 @@ fn translate_lhs_selections_to_rhs( translated } -struct SplitLhsDiffHunkDelegate { +struct SplitLhsDiffHunkRenderer { splittable: WeakEntity, } -impl DiffHunkDelegate for SplitLhsDiffHunkDelegate { - fn toggle( - &self, - hunks: Vec, - _editor: &mut Editor, - window: &mut Window, - cx: &mut Context, - ) { - self.splittable - .update(cx, |splittable, cx| { - splittable.rhs_editor.update(cx, |editor, cx| { - let delegate = editor.diff_hunk_delegate(); - delegate.toggle(hunks, editor, window, cx); - }); - }) - .log_err(); - } - - fn stage_or_unstage( - &self, - stage: bool, - hunks: Vec, - _editor: &mut Editor, - window: &mut Window, - cx: &mut Context, - ) { - self.splittable - .update(cx, |splittable, cx| { - splittable.rhs_editor.update(cx, |editor, cx| { - let delegate = editor.diff_hunk_delegate(); - delegate.stage_or_unstage(stage, hunks, editor, window, cx); - }); - }) - .log_err(); - } - - fn restore( - &self, - hunks: Vec, - _editor: &mut Editor, - window: &mut Window, - cx: &mut Context, - ) { - self.splittable - .update(cx, |splittable, cx| { - splittable.rhs_editor.update(cx, |editor, cx| { - let delegate = editor.diff_hunk_delegate(); - delegate.restore(hunks, editor, window, cx); - }); - }) - .log_err(); - } - +impl DiffHunkRenderer for SplitLhsDiffHunkRenderer { fn render_hunk_controls( &self, row: u32, @@ -222,8 +168,8 @@ impl DiffHunkDelegate for SplitLhsDiffHunkDelegate { let Some(splittable) = self.splittable.upgrade() else { return gpui::Empty.into_any_element(); }; - let delegate = splittable.read(cx).rhs_editor.read(cx).diff_hunk_delegate(); - delegate.render_hunk_controls( + let renderer = splittable.read(cx).rhs_editor.read(cx).diff_hunk_renderer(); + renderer.render_hunk_controls( row, status, hunk_range, @@ -239,8 +185,8 @@ impl DiffHunkDelegate for SplitLhsDiffHunkDelegate { let Some(splittable) = self.splittable.upgrade() else { return false; }; - let delegate = splittable.read(cx).rhs_editor.read(cx).diff_hunk_delegate(); - delegate.render_hunk_as_staged(status, cx) + let renderer = splittable.read(cx).rhs_editor.read(cx).diff_hunk_renderer(); + renderer.render_hunk_as_staged(status, cx) } } @@ -636,13 +582,13 @@ impl SplittableEditor { self.lhs.is_some() } - pub fn set_diff_hunk_delegate( + pub fn set_diff_hunk_renderer( &self, - delegate: Option>, + renderer: Option>, cx: &mut Context, ) { self.rhs_editor.update(cx, |editor, cx| { - editor.set_diff_hunk_delegate(delegate, cx); + editor.set_diff_hunk_renderer(renderer, cx); }); } @@ -683,7 +629,7 @@ impl SplittableEditor { editor.disable_inline_diagnostics(); editor.disable_mouse_wheel_zoom(); editor.set_minimap_visibility(crate::MinimapVisibility::Disabled, window, cx); - editor.set_diff_hunk_delegate(Some(Arc::new(UncommittedDiffHunkDelegate)), cx); + editor.set_diff_hunk_renderer(Some(Arc::new(DefaultDiffHunkRenderer)), cx); editor }); // TODO(split-diff) we might want to tag editor events with whether they came from rhs/lhs @@ -782,15 +728,19 @@ impl SplittableEditor { }); let splittable = cx.weak_entity(); + let rhs_editor = self.rhs_editor.downgrade(); let lhs_editor = cx.new(|cx| { let mut editor = Editor::for_multibuffer(lhs_multibuffer.clone(), Some(project.clone()), window, cx); editor.set_number_deleted_lines(true, cx); editor.set_delegate_expand_excerpts(true); - editor.set_diff_hunk_delegate( - Some(Arc::new(SplitLhsDiffHunkDelegate { splittable })), + editor.set_diff_hunk_renderer( + Some(Arc::new(SplitLhsDiffHunkRenderer { + splittable: splittable.clone(), + })), cx, ); + editor.set_diff_hunk_action_target(Some(rhs_editor)); editor.set_delegate_open_excerpts(true); editor.set_show_vertical_scrollbar(false, cx); editor.disable_lsp_data(); @@ -3894,15 +3844,7 @@ mod tests { .unindent(); let buffer2 = cx.new(|cx| Buffer::local(current_text.to_string(), cx)); - let diff2 = cx.new(|cx| { - BufferDiff::new( - &buffer2.read(cx).text_snapshot(), - None, - None, - buffer_diff::DiffBaseKind::Custom, - cx, - ) - }); + let diff2 = cx.new(|cx| BufferDiff::new(&buffer2.read(cx).text_snapshot(), None, None, cx)); editor.update(cx, |editor, cx| { let path1 = PathKey::sorted(0); diff --git a/crates/git_ui/src/branch_diff.rs b/crates/git_ui/src/branch_diff.rs index c1f6f95bfa..31cf501dd6 100644 --- a/crates/git_ui/src/branch_diff.rs +++ b/crates/git_ui/src/branch_diff.rs @@ -9,7 +9,7 @@ use crate::{ use agent_settings::AgentSettings; use anyhow::{Context as _, Result, anyhow}; use editor::{ - Addon, Editor, EditorEvent, RestoreOnlyDiffHunkDelegate, SplittableEditor, + Addon, Editor, EditorEvent, HiddenDiffHunkRenderer, SplittableEditor, actions::SendReviewToAgent, }; use git::{repository::DiffType, status::FileStatus}; @@ -338,7 +338,7 @@ impl BranchDiff { Capability::ReadWrite, "No changes", move |editor, cx| { - editor.set_diff_hunk_delegate(Some(Arc::new(RestoreOnlyDiffHunkDelegate)), cx); + editor.set_diff_hunk_renderer(Some(Arc::new(HiddenDiffHunkRenderer)), cx); editor.rhs_editor().update(cx, move |rhs_editor, _cx| { rhs_editor.set_read_only(false); rhs_editor.register_addon(BranchDiffAddon { diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index f9ce69c684..e5786864d7 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -2,7 +2,7 @@ use anyhow::{Context as _, Result}; use buffer_diff::BufferDiff; use collections::HashMap; use editor::{ - Addon, Editor, EditorEvent, EditorSettings, MultiBuffer, RestoreOnlyDiffHunkDelegate, + Addon, Editor, EditorEvent, EditorSettings, HiddenDiffHunkRenderer, MultiBuffer, SplittableEditor, hover_markdown_style, multibuffer_context_lines, }; use futures_lite::future::yield_now; @@ -326,7 +326,7 @@ impl CommitView { window, cx, ); - editor.set_diff_hunk_delegate(Some(Arc::new(RestoreOnlyDiffHunkDelegate)), cx); + editor.set_diff_hunk_renderer(Some(Arc::new(HiddenDiffHunkRenderer)), cx); editor.rhs_editor().update(cx, |editor, cx| { editor.set_show_bookmarks(false, cx); @@ -417,7 +417,6 @@ impl CommitView { &snapshot, snapshot.language().cloned(), Some(language_registry.clone()), - buffer_diff::DiffBaseKind::Oid, cx, ) }) @@ -1159,15 +1158,8 @@ async fn build_buffer_diff( let language = cx.update(|_, cx| buffer.read(cx).language().cloned())?; let buffer = cx.update(|_, cx| buffer.read(cx).snapshot())?; - let diff = cx.new(|cx| { - BufferDiff::new( - &buffer.text, - language, - Some(language_registry.clone()), - buffer_diff::DiffBaseKind::Oid, - cx, - ) - }); + let diff = + cx.new(|cx| BufferDiff::new(&buffer.text, language, Some(language_registry.clone()), cx)); diff.update(cx, |diff, cx| { diff.set_base_text( @@ -1346,7 +1338,7 @@ impl Item for CommitView { window, cx, ); - editor.set_diff_hunk_delegate(Some(Arc::new(RestoreOnlyDiffHunkDelegate)), cx); + editor.set_diff_hunk_renderer(Some(Arc::new(HiddenDiffHunkRenderer)), cx); editor.rhs_editor().update(cx, |editor, cx| { editor.set_show_bookmarks(false, cx); editor.set_show_breakpoints(false, cx); diff --git a/crates/git_ui/src/multi_diff_view.rs b/crates/git_ui/src/multi_diff_view.rs index 15602ac11c..92129897ae 100644 --- a/crates/git_ui/src/multi_diff_view.rs +++ b/crates/git_ui/src/multi_diff_view.rs @@ -1,8 +1,7 @@ use anyhow::Result; use buffer_diff::BufferDiff; use editor::{ - Editor, EditorEvent, MultiBuffer, RestoreOnlyUnstagedDiffHunkDelegate, - multibuffer_context_lines, + Editor, EditorEvent, HiddenUnstagedDiffHunkRenderer, MultiBuffer, multibuffer_context_lines, }; use git_ui_core::file_diff_view::build_buffer_diff; use gpui::{ @@ -199,7 +198,7 @@ impl MultiDiffView { let editor = cx.new(|cx| { let mut editor = Editor::for_multibuffer(multibuffer, Some(project.clone()), window, cx); - editor.set_diff_hunk_delegate(Some(Arc::new(RestoreOnlyUnstagedDiffHunkDelegate)), cx); + editor.set_diff_hunk_renderer(Some(Arc::new(HiddenUnstagedDiffHunkRenderer)), cx); editor.disable_diagnostics(cx); editor.set_expand_all_diff_hunks(cx); editor diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index a540038f73..42c0896a31 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -8,7 +8,7 @@ use crate::{ use anyhow::{Context as _, Result}; use buffer_diff::DiffHunkSecondaryStatus; use editor::{ - Editor, EditorEvent, SplittableEditor, UncommittedDiffHunkDelegate, + DefaultDiffHunkRenderer, Editor, EditorEvent, SplittableEditor, actions::{GoToHunk, GoToPreviousHunk, SendReviewToAgent}, }; use git::{Commit, StageAll, StageAndNext, ToggleStaged, UnstageAll, UnstageAndNext}; @@ -243,7 +243,7 @@ impl ProjectDiff { Capability::ReadWrite, "No uncommitted changes", move |editor, cx| { - editor.set_diff_hunk_delegate(Some(Arc::new(UncommittedDiffHunkDelegate)), cx); + editor.set_diff_hunk_renderer(Some(Arc::new(DefaultDiffHunkRenderer)), cx); editor.rhs_editor().update(cx, |rhs_editor, _cx| { rhs_editor.set_read_only(false); rhs_editor.register_addon(GitPanelAddon { diff --git a/crates/git_ui/src/staged_diff.rs b/crates/git_ui/src/staged_diff.rs index b37cb1fee7..0aadd1c118 100644 --- a/crates/git_ui/src/staged_diff.rs +++ b/crates/git_ui/src/staged_diff.rs @@ -5,7 +5,7 @@ use crate::{ use anyhow::{Context as _, Result}; use buffer_diff::DiffHunkStatus; use editor::{ - DiffHunkDelegate, Editor, EditorEvent, ResolvedDiffHunks, SplittableEditor, + DiffHunkRenderer, Editor, EditorEvent, SplittableEditor, actions::{GoToHunk, GoToPreviousHunk}, }; use git::{Commit, UnstageAll, UnstageAndNext}; @@ -26,7 +26,6 @@ use std::{ sync::Arc, }; use ui::{DiffStat, Divider, Icon, Tooltip, Window, prelude::*}; -use util::ResultExt as _; use workspace::{ ItemNavHistory, SerializableItem, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, @@ -34,50 +33,9 @@ use workspace::{ searchable::SearchableItemHandle, }; -pub(crate) struct StagedDiffDelegate; - -impl DiffHunkDelegate for StagedDiffDelegate { - fn toggle( - &self, - hunks: Vec, - editor: &mut Editor, - window: &mut Window, - cx: &mut Context, - ) { - self.stage_or_unstage(false, hunks, editor, window, cx); - } - - fn stage_or_unstage( - &self, - stage: bool, - hunks: Vec, - editor: &mut Editor, - _window: &mut Window, - cx: &mut Context, - ) { - if stage { - return; - } - let Some(project) = editor.project().cloned() else { - return; - }; - for hunks in hunks { - let index_ranges = hunks - .hunks - .into_iter() - .map(|hunk| hunk.buffer_range) - .collect::>(); - if index_ranges.is_empty() { - continue; - } - project - .update(cx, |project, cx| { - project.unstage_staged_hunks(hunks.diff, index_ranges, cx) - }) - .log_err(); - } - } +pub(crate) struct StagedDiffHunkRenderer; +impl DiffHunkRenderer for StagedDiffHunkRenderer { fn render_hunk_controls( &self, row: u32, @@ -228,7 +186,7 @@ impl StagedDiff { Capability::ReadOnly, "No staged changes", move |editor, cx| { - editor.set_diff_hunk_delegate(Some(Arc::new(StagedDiffDelegate)), cx); + editor.set_diff_hunk_renderer(Some(Arc::new(StagedDiffHunkRenderer)), cx); editor.rhs_editor().update(cx, |rhs_editor, _cx| { rhs_editor.set_read_only(true); rhs_editor.register_addon(GitPanelAddon { diff --git a/crates/git_ui/src/text_diff_view.rs b/crates/git_ui/src/text_diff_view.rs index 97bf88edd8..5a1f562284 100644 --- a/crates/git_ui/src/text_diff_view.rs +++ b/crates/git_ui/src/text_diff_view.rs @@ -3,7 +3,7 @@ use anyhow::Result; use buffer_diff::BufferDiff; use editor::{ - Editor, EditorEvent, EditorSettings, MultiBuffer, RestoreOnlyUnstagedDiffHunkDelegate, + Editor, EditorEvent, EditorSettings, HiddenUnstagedDiffHunkRenderer, MultiBuffer, SplittableEditor, ToPoint, actions::DiffClipboardWithSelectionData, }; use futures::{FutureExt, select_biased}; @@ -117,12 +117,13 @@ impl TextDiffView { cx, ); let diff_buffer = cx.new(|cx| { - BufferDiff::new_with_base_text_buffer( + let mut diff = BufferDiff::new_with_base_text_buffer( &source_buffer_snapshot.text, clipboard_buffer.clone(), - buffer_diff::DiffBaseKind::Custom, cx, - ) + ); + diff.set_operations(Arc::new(buffer_diff::RestoreDiffOperations)); + diff }); let task = window.spawn(cx, async move |cx| { @@ -185,8 +186,7 @@ impl TextDiffView { window, cx, ); - splittable - .set_diff_hunk_delegate(Some(Arc::new(RestoreOnlyUnstagedDiffHunkDelegate)), cx); + splittable.set_diff_hunk_renderer(Some(Arc::new(HiddenUnstagedDiffHunkRenderer)), cx); splittable }); diff --git a/crates/git_ui/src/unstaged_diff.rs b/crates/git_ui/src/unstaged_diff.rs index 48bcb4e5ab..b51271fb2c 100644 --- a/crates/git_ui/src/unstaged_diff.rs +++ b/crates/git_ui/src/unstaged_diff.rs @@ -5,7 +5,7 @@ use crate::{ use anyhow::{Context as _, Result}; use buffer_diff::DiffHunkStatus; use editor::{ - DiffHunkDelegate, Editor, EditorEvent, ResolvedDiffHunks, SplittableEditor, + DiffHunkRenderer, Editor, EditorEvent, SplittableEditor, actions::{GoToHunk, GoToPreviousHunk}, }; use git::{StageAll, StageAndNext}; @@ -26,7 +26,6 @@ use std::{ sync::Arc, }; use ui::{DiffStat, Divider, Icon, Tooltip, Window, prelude::*}; -use util::ResultExt as _; use workspace::{ ItemNavHistory, SerializableItem, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, @@ -34,79 +33,9 @@ use workspace::{ searchable::SearchableItemHandle, }; -pub(crate) struct UnstagedDiffDelegate; - -impl DiffHunkDelegate for UnstagedDiffDelegate { - fn toggle( - &self, - hunks: Vec, - editor: &mut Editor, - window: &mut Window, - cx: &mut Context, - ) { - self.stage_or_unstage(true, hunks, editor, window, cx); - } - - fn stage_or_unstage( - &self, - stage: bool, - hunks: Vec, - editor: &mut Editor, - _window: &mut Window, - cx: &mut Context, - ) { - if !stage { - return; - } - let Some(project) = editor.project().cloned() else { - return; - }; - for hunks in hunks { - let Some(buffer) = hunks.buffer else { - continue; - }; - let worktree_ranges = hunks - .hunks - .into_iter() - .map(|hunk| hunk.buffer_range) - .collect::>(); - if worktree_ranges.is_empty() { - continue; - } - project - .update(cx, |project, cx| { - project.stage_hunks(buffer, hunks.diff, worktree_ranges, cx) - }) - .log_err(); - } - } - - fn restore( - &self, - hunks: Vec, - editor: &mut Editor, - window: &mut Window, - cx: &mut Context, - ) { - if hunks.is_empty() || editor.read_only(cx) { - return; - } - editor.transact(window, cx, |editor, window, cx| { - editor.restore_diff_hunks(hunks, cx); - let selections = editor - .selections - .all::(&editor.display_snapshot(cx)); - editor.change_selections( - editor::SelectionEffects::no_scroll(), - window, - cx, - |selections_state| { - selections_state.select(selections); - }, - ); - }); - } +pub(crate) struct UnstagedDiffHunkRenderer; +impl DiffHunkRenderer for UnstagedDiffHunkRenderer { fn render_hunk_controls( &self, row: u32, @@ -282,7 +211,7 @@ impl UnstagedDiff { Capability::ReadWrite, "No unstaged changes", move |editor, cx| { - editor.set_diff_hunk_delegate(Some(Arc::new(UnstagedDiffDelegate)), cx); + editor.set_diff_hunk_renderer(Some(Arc::new(UnstagedDiffHunkRenderer)), cx); editor.rhs_editor().update(cx, |rhs_editor, _cx| { rhs_editor.set_read_only(false); rhs_editor.register_addon(GitPanelAddon { diff --git a/crates/git_ui_core/src/file_diff_view.rs b/crates/git_ui_core/src/file_diff_view.rs index 4e1db897e3..555fca0a4d 100644 --- a/crates/git_ui_core/src/file_diff_view.rs +++ b/crates/git_ui_core/src/file_diff_view.rs @@ -3,7 +3,7 @@ use anyhow::Result; use buffer_diff::BufferDiff; use editor::{ - Editor, EditorEvent, EditorSettings, MultiBuffer, RestoreOnlyUnstagedDiffHunkDelegate, + Editor, EditorEvent, EditorSettings, HiddenUnstagedDiffHunkRenderer, MultiBuffer, SplittableEditor, }; use futures::{FutureExt, select_biased}; @@ -124,8 +124,7 @@ impl FileDiffView { window, cx, ); - splittable - .set_diff_hunk_delegate(Some(Arc::new(RestoreOnlyUnstagedDiffHunkDelegate)), cx); + splittable.set_diff_hunk_renderer(Some(Arc::new(HiddenUnstagedDiffHunkRenderer)), cx); splittable }); @@ -214,13 +213,14 @@ pub async fn build_buffer_diff( let language_registry = new_buffer.read_with(cx, |buffer, _| buffer.language_registry()); let diff = cx.new(|cx| { - BufferDiff::new( + let mut diff = BufferDiff::new( &new_buffer_snapshot.text, new_buffer_snapshot.language().cloned(), language_registry, - buffer_diff::DiffBaseKind::Custom, cx, - ) + ); + diff.set_operations(Arc::new(buffer_diff::RestoreDiffOperations)); + diff }); diff.update(cx, |diff, cx| { diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index 281db42f1c..fe62b0ea04 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -5,7 +5,7 @@ pub mod job_debug_queue; pub mod pending_op; use crate::{ - ProjectEnvironment, ProjectItem, ProjectPath, + Project, ProjectEnvironment, ProjectItem, ProjectPath, buffer_store::{BufferStore, BufferStoreEvent}, project_settings::ProjectSettings, trusted_worktrees::{ @@ -15,7 +15,9 @@ use crate::{ }; use anyhow::{Context as _, Result, anyhow, bail}; use askpass::{AskPassDelegate, EncryptedPassword, IKnowWhatIAmDoingAndIHaveReadTheDocs}; -use buffer_diff::{BufferDiff, DiffHunk, DiffHunkSecondaryStatus, PendingHunk, PendingSense}; +use buffer_diff::{ + BufferDiff, DiffHunk, DiffHunkSecondaryStatus, DiffOperations, PendingHunk, PendingSense, +}; use client::ProjectId; use collections::HashMap; pub use conflict_set::{ConflictRegion, ConflictSet, ConflictSetSnapshot, ConflictSetUpdate}; @@ -98,6 +100,7 @@ use zeroize::Zeroize; pub struct GitStore { state: GitStoreState, + project: Option>, buffer_store: Entity, worktree_store: Entity, repositories: HashMap>, @@ -310,6 +313,68 @@ enum DiffKind { SinceOid(Option), } +struct GitDiffOperations { + project: WeakEntity, + kind: DiffKind, +} + +impl DiffOperations for GitDiffOperations { + fn supports_staging(&self) -> bool { + matches!(self.kind, DiffKind::Unstaged | DiffKind::Uncommitted) + } + + fn supports_unstaging(&self) -> bool { + matches!(self.kind, DiffKind::Staged | DiffKind::Uncommitted) + } + + fn supports_restore(&self) -> bool { + matches!(self.kind, DiffKind::Unstaged | DiffKind::Uncommitted) + } + + fn stage( + &self, + diff: Entity, + buffer: Option>, + buffer_ranges: Vec>, + cx: &mut App, + ) { + let result = self.project.update(cx, |project, cx| match self.kind { + DiffKind::Unstaged => { + let buffer = buffer.context("unstaged diff has no worktree buffer")?; + project.stage_hunks(buffer, diff, buffer_ranges, cx) + } + DiffKind::Uncommitted => { + let buffer = buffer.context("uncommitted diff has no worktree buffer")?; + let secondary_diff = diff + .read(cx) + .secondary_diff() + .context("diff has no unstaged secondary")?; + project.stage_hunks(buffer, secondary_diff, buffer_ranges, cx) + } + _ => Ok(()), + }); + result.and_then(|result| result).log_err(); + } + + fn unstage( + &self, + diff: Entity, + buffer: Option>, + buffer_ranges: Vec>, + cx: &mut App, + ) { + let result = self.project.update(cx, |project, cx| match self.kind { + DiffKind::Staged => project.unstage_staged_hunks(diff, buffer_ranges, cx), + DiffKind::Uncommitted => { + let buffer = buffer.context("uncommitted diff has no worktree buffer")?; + project.unstage_uncommitted_hunks(buffer, diff, buffer_ranges, cx) + } + _ => Ok(()), + }); + result.and_then(|result| result).log_err(); + } +} + struct IndexTextFile { path: Arc, full_path: PathBuf, @@ -942,6 +1007,7 @@ impl GitStore { let diff_base_setting = ProjectSettings::get_global(cx).git.diff_base; GitStore { state, + project: None, buffer_store, worktree_store, repositories: HashMap::default(), @@ -958,6 +1024,10 @@ impl GitStore { } } + pub(crate) fn set_project(&mut self, project: WeakEntity) { + self.project = Some(project); + } + pub fn init(client: &AnyProtoClient) { client.add_entity_request_handler(Self::handle_get_remotes); client.add_entity_request_handler(Self::handle_get_branches); @@ -1628,7 +1698,6 @@ impl GitStore { &buffer_snapshot, buffer_snapshot.language().cloned(), language_registry, - buffer_diff::DiffBaseKind::Oid, cx, ) }); @@ -1801,10 +1870,11 @@ impl GitStore { this.loading_diffs.remove(&(buffer_id, kind)); let git_store = cx.weak_entity(); + let project = this.project.clone(); let diff_state = this .diffs .entry(buffer_id) - .or_insert_with(|| cx.new(|cx| BufferGitState::new(git_store, cx))); + .or_insert_with(|| cx.new(|cx| BufferGitState::new(git_store.clone(), cx))); let existing_unstaged_diff = diff_state.read(cx).unstaged_diff(); @@ -1820,12 +1890,15 @@ impl GitStore { diff_state.get_or_create_index_text_buffer(index_text_file.clone(), cx) }); cx.new(|cx| { - BufferDiff::new_with_base_text_buffer( + let mut diff = BufferDiff::new_with_base_text_buffer( &text_snapshot, base_text_buffer, - buffer_diff::DiffBaseKind::Index, cx, - ) + ); + if let Some(project) = project.clone() { + diff.set_operations(Arc::new(GitDiffOperations { project, kind })); + } + diff }) } DiffKind::Staged => { @@ -1848,12 +1921,15 @@ impl GitStore { let index_text_snapshot = index_text_buffer.read(cx).text_snapshot(); staged_index_text_buffer = Some(index_text_buffer); cx.new(|cx| { - BufferDiff::new_with_base_text_buffer( + let mut diff = BufferDiff::new_with_base_text_buffer( &index_text_snapshot, base_text_buffer, - buffer_diff::DiffBaseKind::Head, cx, - ) + ); + if let Some(project) = project.clone() { + diff.set_operations(Arc::new(GitDiffOperations { project, kind })); + } + diff }) } DiffKind::Uncommitted => { @@ -1861,12 +1937,15 @@ impl GitStore { diff_state.get_or_create_head_text_buffer(cx) }); cx.new(|cx| { - BufferDiff::new_with_base_text_buffer( + let mut diff = BufferDiff::new_with_base_text_buffer( &text_snapshot, base_text_buffer, - buffer_diff::DiffBaseKind::Head, cx, - ) + ); + if let Some(project) = project.clone() { + diff.set_operations(Arc::new(GitDiffOperations { project, kind })); + } + diff }) } DiffKind::SinceOid(_) => { @@ -1901,12 +1980,18 @@ impl GitStore { let base_text_buffer = diff_state.get_or_create_index_text_buffer(index_text_file, cx); let unstaged_diff = cx.new(|cx| { - BufferDiff::new_with_base_text_buffer( + let mut diff = BufferDiff::new_with_base_text_buffer( &text_snapshot, base_text_buffer, - buffer_diff::DiffBaseKind::Index, cx, - ) + ); + if let Some(project) = project.clone() { + diff.set_operations(Arc::new(GitDiffOperations { + project, + kind: DiffKind::Unstaged, + })); + } + diff }); diff_state.unstaged_diff = Some(unstaged_diff.downgrade()); unstaged_diff diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ed9194548f..ed6627f66c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1305,6 +1305,7 @@ impl Project { cx, ) }); + git_store.update(cx, |git_store, _| git_store.set_project(weak_self.clone())); let task_store = cx.new(|cx| { TaskStore::local( @@ -1550,6 +1551,7 @@ impl Project { cx, ) }); + git_store.update(cx, |git_store, _| git_store.set_project(weak_self.clone())); let task_store = cx.new(|cx| { TaskStore::remote( @@ -1857,6 +1859,7 @@ impl Project { let snippets = SnippetProvider::new(fs.clone(), BTreeSet::from_iter([]), cx); let weak_self = cx.weak_entity(); + git_store.update(cx, |git_store, _| git_store.set_project(weak_self.clone())); let context_server_store = cx.new(|cx| { ContextServerStore::local(worktree_store.clone(), Some(weak_self), false, cx) });