diff --git a/crates/editor/src/git.rs b/crates/editor/src/git.rs index d9871fa2cb5898..ab5b035a05a5f4 100644 --- a/crates/editor/src/git.rs +++ b/crates/editor/src/git.rs @@ -1050,11 +1050,7 @@ impl Editor { ); } - pub(super) fn restore_diff_hunks( - &mut self, - hunks: Vec, - cx: &mut Context, - ) { + pub 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 { @@ -2110,6 +2106,17 @@ impl Editor { .detach_and_log_err(cx); } + pub fn restore_diff_hunks_in_ranges( + &mut self, + ranges: Vec>, + window: &mut Window, + cx: &mut Context, + ) { + let snapshot = self.buffer.read(cx).snapshot(cx); + let hunks = self.diff_hunks_in_ranges(&ranges, &snapshot).collect(); + self.apply_restore(hunks, window, cx); + } + fn toggle_diff_hunks_in_ranges( &mut self, ranges: Vec>, diff --git a/crates/git_ui/src/diff_multibuffer.rs b/crates/git_ui/src/diff_multibuffer.rs index 96c05303719e2d..7c548952d7b86f 100644 --- a/crates/git_ui/src/diff_multibuffer.rs +++ b/crates/git_ui/src/diff_multibuffer.rs @@ -365,6 +365,28 @@ impl DiffMultibuffer { } } + pub(crate) fn restore_selected_hunks( + &mut self, + move_to_next: bool, + window: &mut Window, + cx: &mut Context, + ) { + let editor = self.editor.read(cx).rhs_editor().clone(); + let ranges = self.hunk_action_ranges(cx); + editor.update(cx, |editor, cx| { + let snapshot = editor.buffer().read(cx).snapshot(cx); + let hunks: Vec<_> = editor.diff_hunks_in_ranges(&ranges, &snapshot).collect(); + if !hunks.is_empty() { + editor.apply_restore(hunks, window, cx); + } + }); + if move_to_next { + editor + .focus_handle(cx) + .dispatch_action(&GoToHunk, window, cx); + } + } + fn handle_editor_event( &mut self, editor: &Entity, diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 798b1bad24ba41..d5e6e65b132260 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -206,6 +206,11 @@ fn git_panel_context_menu( .context(focus_handle.clone()) .action_disabled_when(!has_unstaged_changes, "Stage All", StageAll.boxed_clone()) .action_disabled_when(!has_staged_changes, "Unstage All", UnstageAll.boxed_clone()) + .action_disabled_when( + !has_tracked_changes, + "Restore All Changes", + RestoreTrackedFiles.boxed_clone(), + ) .separator() .action_disabled_when( !(has_new_changes || has_tracked_changes), diff --git a/crates/git_ui/src/unstaged_diff.rs b/crates/git_ui/src/unstaged_diff.rs index a31ea12b3fe5b3..02cb8f12c24d45 100644 --- a/crates/git_ui/src/unstaged_diff.rs +++ b/crates/git_ui/src/unstaged_diff.rs @@ -81,12 +81,38 @@ impl DiffHunkDelegate for UnstagedDiffDelegate { } } + 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); + }, + ); + }); + } + fn render_hunk_controls( &self, row: u32, status: &DiffHunkStatus, hunk_range: Range, - _is_created_file: bool, + is_created_file: bool, line_height: Pixels, editor: &Entity, _window: &mut Window, @@ -98,6 +124,7 @@ impl DiffHunkDelegate for UnstagedDiffDelegate { { return gpui::Empty.into_any_element(); } + let hunk_range_for_restore = hunk_range.clone(); let hunk_range = hunk_range.start..hunk_range.start; h_flex() .h(line_height) @@ -130,6 +157,29 @@ impl DiffHunkDelegate for UnstagedDiffDelegate { } }), ) + .child( + Button::new(("restore", row as u64), "Restore") + .tooltip(Tooltip::text("Restore Hunk")) + .on_click({ + let editor = editor.clone(); + let hunk_range = hunk_range_for_restore; + move |_event, window, cx| { + editor.update(cx, |editor, cx| { + let snapshot = editor.buffer().read(cx).snapshot(cx); + let hunks: Vec<_> = editor + .diff_hunks_in_ranges( + std::slice::from_ref(&hunk_range), + &snapshot, + ) + .collect(); + if !hunks.is_empty() { + editor.apply_restore(hunks, window, cx); + } + }); + } + }) + .disabled(is_created_file), + ) .into_any_element() } @@ -278,6 +328,9 @@ impl UnstagedDiff { .diff_hunks_in_ranges(&ranges, &snapshot) .next() .is_some(); + let restore = editor + .diff_hunks_in_ranges(&ranges, &snapshot) + .any(|h| !h.is_created_file()); let mut stage_all = false; self.workspace .read_with(cx, |workspace, cx| { @@ -286,9 +339,12 @@ impl UnstagedDiff { } }) .ok(); + let restore_all = snapshot.diff_hunks().any(|h| !h.is_created_file()); ButtonStates { stage, + restore, + restore_all, prev_next, selection, stage_all, @@ -305,10 +361,23 @@ impl UnstagedDiff { diff.stage_or_unstage_selected_hunks(true, move_to_next, window, cx) }); } + + fn restore_selected_unstaged_hunks( + &mut self, + move_to_next: bool, + window: &mut Window, + cx: &mut Context, + ) { + self.diff.update(cx, |diff, cx| { + diff.restore_selected_hunks(move_to_next, window, cx) + }); + } } struct ButtonStates { stage: bool, + restore: bool, + restore_all: bool, prev_next: bool, selection: bool, stage_all: bool, @@ -587,6 +656,20 @@ impl UnstagedDiffToolbar { }); } + fn restore_selected_unstaged_hunks( + &mut self, + move_to_next: bool, + window: &mut Window, + cx: &mut Context, + ) { + let Some(unstaged_diff) = self.unstaged_diff(cx) else { + return; + }; + unstaged_diff.update(cx, |unstaged_diff, cx| { + unstaged_diff.restore_selected_unstaged_hunks(move_to_next, window, cx); + }); + } + fn stage_all(&mut self, window: &mut Window, cx: &mut Context) { self.workspace .update(cx, |workspace, cx| { @@ -599,6 +682,24 @@ impl UnstagedDiffToolbar { }) .ok(); } + + fn restore_all(&mut self, window: &mut Window, cx: &mut Context) { + let Some(unstaged_diff) = self.unstaged_diff(cx) else { + return; + }; + let diff = unstaged_diff.read(cx).diff.read(cx); + let editor = diff.editor().read(cx).rhs_editor().clone(); + let snapshot = diff.multibuffer().read(cx).snapshot(cx); + let hunks: Vec<_> = snapshot + .diff_hunks() + .filter(|h| !h.is_created_file()) + .collect(); + if !hunks.is_empty() { + editor.update(cx, |editor, cx| { + editor.apply_restore(hunks, window, cx); + }); + } + } } impl EventEmitter for UnstagedDiffToolbar {} @@ -712,7 +813,15 @@ impl Render for UnstagedDiffToolbar { this.stage_selected_unstaged_hunks(true, window, cx) })), ) - }), + }) + .child( + Button::new("restore", "Restore") + .disabled(!button_states.restore) + .tooltip(Tooltip::text("Restore Selected Hunks")) + .on_click(cx.listener(|this, _, window, cx| { + this.restore_selected_unstaged_hunks(false, window, cx) + })), + ), ) .child(Divider::vertical()) .child( @@ -726,5 +835,13 @@ impl Render for UnstagedDiffToolbar { )) .on_click(cx.listener(|this, _, window, cx| this.stage_all(window, cx))), ) + .child(Divider::vertical()) + .child( + Button::new("restore-all", "Restore All") + .width(rems_from_px(80.)) + .disabled(!button_states.restore_all) + .tooltip(Tooltip::text("Restore All Changes")) + .on_click(cx.listener(|this, _, window, cx| this.restore_all(window, cx))), + ) } }