Support hunk-wise StageAndNext
and UnstageAndNext
(#25845)
This PR adds the `whole_excerpt` field to the actions: - `git::StageAndNext` - `git::UnstageAndNext` Which is set by false by default, effectively, now staging and unstaging with these actions is done hunk-by-hunk, this also affects the `Stage` and `Unstage` buttons in the Diff View toolbar. A caveat: with this PR, there is no way to configure the buttons in the Diff View toolbar to restore the previous behavior, if we want, I think we can make it a setting, but let's see if anyone really wants that. Release Notes: - N/A
This commit is contained in:
parent
13deaa3f69
commit
a2876f5d3e
@ -370,8 +370,8 @@
|
||||
"ctrl-shift-v": "markdown::OpenPreview",
|
||||
"ctrl-alt-shift-c": "editor::DisplayCursorNames",
|
||||
"ctrl-alt-y": "git::ToggleStaged",
|
||||
"alt-y": "git::StageAndNext",
|
||||
"alt-shift-y": "git::UnstageAndNext",
|
||||
"alt-y": ["git::StageAndNext", { "whole_excerpt": false }],
|
||||
"alt-shift-y": ["git::UnstageAndNext", { "whole_excerpt": false }],
|
||||
"alt-.": "editor::GoToHunk",
|
||||
"alt-,": "editor::GoToPrevHunk"
|
||||
}
|
||||
|
@ -131,8 +131,8 @@
|
||||
"cmd-;": "editor::ToggleLineNumbers",
|
||||
"cmd-alt-z": "git::Restore",
|
||||
"cmd-alt-y": "git::ToggleStaged",
|
||||
"cmd-y": "git::StageAndNext",
|
||||
"cmd-shift-y": "git::UnstageAndNext",
|
||||
"cmd-y": ["git::StageAndNext", { "whole_excerpt": false }],
|
||||
"cmd-shift-y": ["git::UnstageAndNext", { "whole_excerpt": false }],
|
||||
"cmd-'": "editor::ToggleSelectedDiffHunks",
|
||||
"cmd-\"": "editor::ExpandAllDiffHunks",
|
||||
"cmd-alt-g b": "editor::ToggleGitBlame",
|
||||
|
@ -7722,7 +7722,7 @@ impl Editor {
|
||||
let mut revert_changes = HashMap::default();
|
||||
let chunk_by = self
|
||||
.snapshot(window, cx)
|
||||
.hunks_for_ranges(ranges.into_iter())
|
||||
.hunks_for_ranges(ranges)
|
||||
.into_iter()
|
||||
.chunk_by(|hunk| hunk.buffer_id);
|
||||
for (buffer_id, hunks) in &chunk_by {
|
||||
@ -11424,27 +11424,37 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> Option<MultiBufferDiffHunk> {
|
||||
let mut hunk = snapshot
|
||||
.buffer_snapshot
|
||||
.diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
|
||||
.find(|hunk| hunk.row_range.start.0 > position.row);
|
||||
if hunk.is_none() {
|
||||
hunk = snapshot
|
||||
.buffer_snapshot
|
||||
.diff_hunks_in_range(Point::zero()..position)
|
||||
.find(|hunk| hunk.row_range.end.0 < position.row)
|
||||
}
|
||||
let hunk = self.hunk_after_position(snapshot, position);
|
||||
|
||||
if let Some(hunk) = &hunk {
|
||||
let destination = Point::new(hunk.row_range.start.0, 0);
|
||||
self.unfold_ranges(&[destination..destination], false, false, cx);
|
||||
let point = Point::new(hunk.row_range.start.0, 0);
|
||||
|
||||
self.unfold_ranges(&[point..point], false, false, cx);
|
||||
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||
s.select_ranges(vec![destination..destination]);
|
||||
s.select_ranges([point..point]);
|
||||
});
|
||||
}
|
||||
|
||||
hunk
|
||||
}
|
||||
|
||||
fn hunk_after_position(
|
||||
&mut self,
|
||||
snapshot: &EditorSnapshot,
|
||||
position: Point,
|
||||
) -> Option<MultiBufferDiffHunk> {
|
||||
snapshot
|
||||
.buffer_snapshot
|
||||
.diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
|
||||
.find(|hunk| hunk.row_range.start.0 > position.row)
|
||||
.or_else(|| {
|
||||
snapshot
|
||||
.buffer_snapshot
|
||||
.diff_hunks_in_range(Point::zero()..position)
|
||||
.find(|hunk| hunk.row_range.end.0 < position.row)
|
||||
})
|
||||
}
|
||||
|
||||
fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
let selection = self.selections.newest::<Point>(cx);
|
||||
@ -13528,20 +13538,20 @@ impl Editor {
|
||||
|
||||
pub fn stage_and_next(
|
||||
&mut self,
|
||||
_: &::git::StageAndNext,
|
||||
action: &::git::StageAndNext,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.do_stage_or_unstage_and_next(true, window, cx);
|
||||
self.do_stage_or_unstage_and_next(true, action.whole_excerpt, window, cx);
|
||||
}
|
||||
|
||||
pub fn unstage_and_next(
|
||||
&mut self,
|
||||
_: &::git::UnstageAndNext,
|
||||
action: &::git::UnstageAndNext,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.do_stage_or_unstage_and_next(false, window, cx);
|
||||
self.do_stage_or_unstage_and_next(false, action.whole_excerpt, window, cx);
|
||||
}
|
||||
|
||||
pub fn stage_or_unstage_diff_hunks(
|
||||
@ -13563,16 +13573,36 @@ impl Editor {
|
||||
fn do_stage_or_unstage_and_next(
|
||||
&mut self,
|
||||
stage: bool,
|
||||
whole_excerpt: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let mut ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
|
||||
|
||||
if ranges.iter().any(|range| range.start != range.end) {
|
||||
self.stage_or_unstage_diff_hunks(stage, &ranges[..], window, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.buffer().read(cx).is_singleton() {
|
||||
if !whole_excerpt {
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
let newest_range = self.selections.newest::<Point>(cx).range();
|
||||
|
||||
let run_twice = snapshot
|
||||
.hunks_for_ranges([newest_range])
|
||||
.first()
|
||||
.is_some_and(|hunk| {
|
||||
let next_line = Point::new(hunk.row_range.end.0 + 1, 0);
|
||||
self.hunk_after_position(&snapshot, next_line)
|
||||
.is_some_and(|other| other.row_range == hunk.row_range)
|
||||
});
|
||||
|
||||
if run_twice {
|
||||
self.go_to_next_hunk(&Default::default(), window, cx);
|
||||
}
|
||||
} else if !self.buffer().read(cx).is_singleton() {
|
||||
self.stage_or_unstage_diff_hunks(stage, &ranges[..], window, cx);
|
||||
|
||||
if let Some((excerpt_id, buffer, range)) = self.active_excerpt(cx) {
|
||||
if buffer.read(cx).is_empty() {
|
||||
let buffer = buffer.read(cx);
|
||||
@ -13586,9 +13616,9 @@ impl Editor {
|
||||
let Some(project) = self.project.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let project = project.read(cx);
|
||||
|
||||
let Some(repo) = project.git_store().read(cx).active_repository() else {
|
||||
let Some(repo) = project.read(cx).git_store().read(cx).active_repository()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
@ -13620,7 +13650,7 @@ impl Editor {
|
||||
point = snapshot.clip_point(point, Bias::Right);
|
||||
self.change_selections(Some(Autoscroll::top_relative(6)), window, cx, |s| {
|
||||
s.select_ranges([point..point]);
|
||||
})
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -13756,7 +13786,7 @@ impl Editor {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx).into_iter());
|
||||
let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
|
||||
let mut ranges_by_buffer = HashMap::default();
|
||||
self.transact(window, cx, |editor, _window, cx| {
|
||||
for hunk in hunks {
|
||||
@ -17083,7 +17113,7 @@ impl EditorSnapshot {
|
||||
|
||||
pub fn hunks_for_ranges(
|
||||
&self,
|
||||
ranges: impl Iterator<Item = Range<Point>>,
|
||||
ranges: impl IntoIterator<Item = Range<Point>>,
|
||||
) -> Vec<MultiBufferDiffHunk> {
|
||||
let mut hunks = Vec::new();
|
||||
let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
|
||||
|
@ -35,15 +35,23 @@ pub struct Push {
|
||||
pub options: Option<PushOptions>,
|
||||
}
|
||||
|
||||
impl_actions!(git, [Push]);
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, JsonSchema)]
|
||||
pub struct StageAndNext {
|
||||
pub whole_excerpt: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, JsonSchema)]
|
||||
pub struct UnstageAndNext {
|
||||
pub whole_excerpt: bool,
|
||||
}
|
||||
|
||||
impl_actions!(git, [Push, StageAndNext, UnstageAndNext]);
|
||||
|
||||
actions!(
|
||||
git,
|
||||
[
|
||||
// per-hunk
|
||||
ToggleStaged,
|
||||
StageAndNext,
|
||||
UnstageAndNext,
|
||||
// per-file
|
||||
StageFile,
|
||||
UnstageFile,
|
||||
|
@ -813,7 +813,9 @@ impl Render for ProjectDiffToolbar {
|
||||
Button::new("stage", "Stage")
|
||||
.tooltip(Tooltip::for_action_title_in(
|
||||
"Stage",
|
||||
&StageAndNext,
|
||||
&StageAndNext {
|
||||
whole_excerpt: false,
|
||||
},
|
||||
&focus_handle,
|
||||
))
|
||||
// don't actually disable the button so it's mashable
|
||||
@ -823,14 +825,22 @@ impl Render for ProjectDiffToolbar {
|
||||
Color::Disabled
|
||||
})
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.dispatch_action(&StageAndNext, window, cx)
|
||||
this.dispatch_action(
|
||||
&StageAndNext {
|
||||
whole_excerpt: false,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("unstage", "Unstage")
|
||||
.tooltip(Tooltip::for_action_title_in(
|
||||
"Unstage",
|
||||
&UnstageAndNext,
|
||||
&UnstageAndNext {
|
||||
whole_excerpt: false,
|
||||
},
|
||||
&focus_handle,
|
||||
))
|
||||
.color(if button_states.unstage {
|
||||
@ -839,7 +849,13 @@ impl Render for ProjectDiffToolbar {
|
||||
Color::Disabled
|
||||
})
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.dispatch_action(&UnstageAndNext, window, cx)
|
||||
this.dispatch_action(
|
||||
&UnstageAndNext {
|
||||
whole_excerpt: false,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})),
|
||||
)
|
||||
}),
|
||||
|
@ -399,6 +399,8 @@ macro_rules! action_with_deprecated_aliases {
|
||||
/// Registers the action and implements the Action trait for any struct that implements Clone,
|
||||
/// Default, PartialEq, serde_deserialize::Deserialize, and schemars::JsonSchema.
|
||||
///
|
||||
/// Similar to `actions!`, but accepts structs with fields.
|
||||
///
|
||||
/// Fields and variants that don't make sense for user configuration should be annotated with
|
||||
/// #[serde(skip)].
|
||||
#[macro_export]
|
||||
|
@ -4112,6 +4112,8 @@ mod tests {
|
||||
| "vim::PushLiteral"
|
||||
| "vim::Number"
|
||||
| "vim::SelectRegister"
|
||||
| "git::StageAndNext"
|
||||
| "git::UnstageAndNext"
|
||||
| "terminal::SendText"
|
||||
| "terminal::SendKeystroke"
|
||||
| "app_menu::OpenApplicationMenu"
|
||||
|
Loading…
Reference in New Issue
Block a user