Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions crates/editor/src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1050,11 +1050,7 @@ impl Editor {
);
}

pub(super) fn restore_diff_hunks(
&mut self,
hunks: Vec<ResolvedDiffHunks>,
cx: &mut Context<Self>,
) {
pub fn restore_diff_hunks(&mut self, hunks: Vec<ResolvedDiffHunks>, cx: &mut Context<Self>) {
let mut revert_changes = Vec::new();
for hunks in hunks {
let Some(buffer) = hunks.buffer else {
Expand Down Expand Up @@ -2110,6 +2106,17 @@ impl Editor {
.detach_and_log_err(cx);
}

pub fn restore_diff_hunks_in_ranges(
&mut self,
ranges: Vec<Range<Anchor>>,
window: &mut Window,
cx: &mut Context<Self>,
) {
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<Range<Anchor>>,
Expand Down
22 changes: 22 additions & 0 deletions crates/git_ui/src/diff_multibuffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,28 @@ impl DiffMultibuffer {
}
}

pub(crate) fn restore_selected_hunks(
&mut self,
move_to_next: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
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<SplittableEditor>,
Expand Down
5 changes: 5 additions & 0 deletions crates/git_ui/src/git_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
121 changes: 119 additions & 2 deletions crates/git_ui/src/unstaged_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,38 @@ impl DiffHunkDelegate for UnstagedDiffDelegate {
}
}

fn restore(
&self,
hunks: Vec<ResolvedDiffHunks>,
editor: &mut Editor,
window: &mut Window,
cx: &mut Context<Editor>,
) {
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::MultiBufferOffset>(&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<editor::Anchor>,
_is_created_file: bool,
is_created_file: bool,
line_height: Pixels,
editor: &Entity<Editor>,
_window: &mut Window,
Expand All @@ -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)
Expand Down Expand Up @@ -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()
}

Expand Down Expand Up @@ -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| {
Expand All @@ -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,
Expand 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>,
) {
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,
Expand Down Expand Up @@ -587,6 +656,20 @@ impl UnstagedDiffToolbar {
});
}

fn restore_selected_unstaged_hunks(
&mut self,
move_to_next: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
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>) {
self.workspace
.update(cx, |workspace, cx| {
Expand All @@ -599,6 +682,24 @@ impl UnstagedDiffToolbar {
})
.ok();
}

fn restore_all(&mut self, window: &mut Window, cx: &mut Context<Self>) {
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();
Comment on lines +693 to +696

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wait for all diff buffers before restoring all

When the unstaged diff is still loading, this only restores hunks already present in the current multibuffer snapshot. DiffMultibuffer::refresh registers each loaded file as it arrives and doesn't mark the load task ready until after the whole loop, while this toolbar action is enabled as soon as the first restorable hunk is visible; in a repo with many or slow-to-open changed files, clicking “Restore All” during that window leaves later-loaded unstaged files unchanged despite the button claiming to restore all changes.

Useful? React with 👍 / 👎.

if !hunks.is_empty() {
editor.update(cx, |editor, cx| {
editor.apply_restore(hunks, window, cx);
});
}
}
}

impl EventEmitter<ToolbarItemEvent> for UnstagedDiffToolbar {}
Expand Down Expand Up @@ -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(
Expand All @@ -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))),
)
}
}
Loading