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-shift-v": "markdown::OpenPreview",
|
||||||
"ctrl-alt-shift-c": "editor::DisplayCursorNames",
|
"ctrl-alt-shift-c": "editor::DisplayCursorNames",
|
||||||
"ctrl-alt-y": "git::ToggleStaged",
|
"ctrl-alt-y": "git::ToggleStaged",
|
||||||
"alt-y": "git::StageAndNext",
|
"alt-y": ["git::StageAndNext", { "whole_excerpt": false }],
|
||||||
"alt-shift-y": "git::UnstageAndNext",
|
"alt-shift-y": ["git::UnstageAndNext", { "whole_excerpt": false }],
|
||||||
"alt-.": "editor::GoToHunk",
|
"alt-.": "editor::GoToHunk",
|
||||||
"alt-,": "editor::GoToPrevHunk"
|
"alt-,": "editor::GoToPrevHunk"
|
||||||
}
|
}
|
||||||
|
@ -131,8 +131,8 @@
|
|||||||
"cmd-;": "editor::ToggleLineNumbers",
|
"cmd-;": "editor::ToggleLineNumbers",
|
||||||
"cmd-alt-z": "git::Restore",
|
"cmd-alt-z": "git::Restore",
|
||||||
"cmd-alt-y": "git::ToggleStaged",
|
"cmd-alt-y": "git::ToggleStaged",
|
||||||
"cmd-y": "git::StageAndNext",
|
"cmd-y": ["git::StageAndNext", { "whole_excerpt": false }],
|
||||||
"cmd-shift-y": "git::UnstageAndNext",
|
"cmd-shift-y": ["git::UnstageAndNext", { "whole_excerpt": false }],
|
||||||
"cmd-'": "editor::ToggleSelectedDiffHunks",
|
"cmd-'": "editor::ToggleSelectedDiffHunks",
|
||||||
"cmd-\"": "editor::ExpandAllDiffHunks",
|
"cmd-\"": "editor::ExpandAllDiffHunks",
|
||||||
"cmd-alt-g b": "editor::ToggleGitBlame",
|
"cmd-alt-g b": "editor::ToggleGitBlame",
|
||||||
|
@ -7722,7 +7722,7 @@ impl Editor {
|
|||||||
let mut revert_changes = HashMap::default();
|
let mut revert_changes = HashMap::default();
|
||||||
let chunk_by = self
|
let chunk_by = self
|
||||||
.snapshot(window, cx)
|
.snapshot(window, cx)
|
||||||
.hunks_for_ranges(ranges.into_iter())
|
.hunks_for_ranges(ranges)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chunk_by(|hunk| hunk.buffer_id);
|
.chunk_by(|hunk| hunk.buffer_id);
|
||||||
for (buffer_id, hunks) in &chunk_by {
|
for (buffer_id, hunks) in &chunk_by {
|
||||||
@ -11424,27 +11424,37 @@ impl Editor {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Editor>,
|
cx: &mut Context<Editor>,
|
||||||
) -> Option<MultiBufferDiffHunk> {
|
) -> Option<MultiBufferDiffHunk> {
|
||||||
let mut hunk = snapshot
|
let hunk = self.hunk_after_position(snapshot, position);
|
||||||
.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)
|
|
||||||
}
|
|
||||||
if let Some(hunk) = &hunk {
|
if let Some(hunk) = &hunk {
|
||||||
let destination = Point::new(hunk.row_range.start.0, 0);
|
let point = Point::new(hunk.row_range.start.0, 0);
|
||||||
self.unfold_ranges(&[destination..destination], false, false, cx);
|
|
||||||
|
self.unfold_ranges(&[point..point], false, false, cx);
|
||||||
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
s.select_ranges(vec![destination..destination]);
|
s.select_ranges([point..point]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
hunk
|
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>) {
|
fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let snapshot = self.snapshot(window, cx);
|
let snapshot = self.snapshot(window, cx);
|
||||||
let selection = self.selections.newest::<Point>(cx);
|
let selection = self.selections.newest::<Point>(cx);
|
||||||
@ -13528,20 +13538,20 @@ impl Editor {
|
|||||||
|
|
||||||
pub fn stage_and_next(
|
pub fn stage_and_next(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &::git::StageAndNext,
|
action: &::git::StageAndNext,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
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(
|
pub fn unstage_and_next(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &::git::UnstageAndNext,
|
action: &::git::UnstageAndNext,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
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(
|
pub fn stage_or_unstage_diff_hunks(
|
||||||
@ -13563,16 +13573,36 @@ impl Editor {
|
|||||||
fn do_stage_or_unstage_and_next(
|
fn do_stage_or_unstage_and_next(
|
||||||
&mut self,
|
&mut self,
|
||||||
stage: bool,
|
stage: bool,
|
||||||
|
whole_excerpt: bool,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let mut ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
|
let mut ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
|
||||||
|
|
||||||
if ranges.iter().any(|range| range.start != range.end) {
|
if ranges.iter().any(|range| range.start != range.end) {
|
||||||
self.stage_or_unstage_diff_hunks(stage, &ranges[..], window, cx);
|
self.stage_or_unstage_diff_hunks(stage, &ranges[..], window, cx);
|
||||||
return;
|
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 let Some((excerpt_id, buffer, range)) = self.active_excerpt(cx) {
|
||||||
if buffer.read(cx).is_empty() {
|
if buffer.read(cx).is_empty() {
|
||||||
let buffer = buffer.read(cx);
|
let buffer = buffer.read(cx);
|
||||||
@ -13586,9 +13616,9 @@ impl Editor {
|
|||||||
let Some(project) = self.project.as_ref() else {
|
let Some(project) = self.project.as_ref() else {
|
||||||
return;
|
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;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -13620,7 +13650,7 @@ impl Editor {
|
|||||||
point = snapshot.clip_point(point, Bias::Right);
|
point = snapshot.clip_point(point, Bias::Right);
|
||||||
self.change_selections(Some(Autoscroll::top_relative(6)), window, cx, |s| {
|
self.change_selections(Some(Autoscroll::top_relative(6)), window, cx, |s| {
|
||||||
s.select_ranges([point..point]);
|
s.select_ranges([point..point]);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -13756,7 +13786,7 @@ impl Editor {
|
|||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let snapshot = self.snapshot(window, cx);
|
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();
|
let mut ranges_by_buffer = HashMap::default();
|
||||||
self.transact(window, cx, |editor, _window, cx| {
|
self.transact(window, cx, |editor, _window, cx| {
|
||||||
for hunk in hunks {
|
for hunk in hunks {
|
||||||
@ -17083,7 +17113,7 @@ impl EditorSnapshot {
|
|||||||
|
|
||||||
pub fn hunks_for_ranges(
|
pub fn hunks_for_ranges(
|
||||||
&self,
|
&self,
|
||||||
ranges: impl Iterator<Item = Range<Point>>,
|
ranges: impl IntoIterator<Item = Range<Point>>,
|
||||||
) -> Vec<MultiBufferDiffHunk> {
|
) -> Vec<MultiBufferDiffHunk> {
|
||||||
let mut hunks = Vec::new();
|
let mut hunks = Vec::new();
|
||||||
let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
|
let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
|
||||||
|
@ -35,15 +35,23 @@ pub struct Push {
|
|||||||
pub options: Option<PushOptions>,
|
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!(
|
actions!(
|
||||||
git,
|
git,
|
||||||
[
|
[
|
||||||
// per-hunk
|
// per-hunk
|
||||||
ToggleStaged,
|
ToggleStaged,
|
||||||
StageAndNext,
|
|
||||||
UnstageAndNext,
|
|
||||||
// per-file
|
// per-file
|
||||||
StageFile,
|
StageFile,
|
||||||
UnstageFile,
|
UnstageFile,
|
||||||
|
@ -813,7 +813,9 @@ impl Render for ProjectDiffToolbar {
|
|||||||
Button::new("stage", "Stage")
|
Button::new("stage", "Stage")
|
||||||
.tooltip(Tooltip::for_action_title_in(
|
.tooltip(Tooltip::for_action_title_in(
|
||||||
"Stage",
|
"Stage",
|
||||||
&StageAndNext,
|
&StageAndNext {
|
||||||
|
whole_excerpt: false,
|
||||||
|
},
|
||||||
&focus_handle,
|
&focus_handle,
|
||||||
))
|
))
|
||||||
// don't actually disable the button so it's mashable
|
// don't actually disable the button so it's mashable
|
||||||
@ -823,14 +825,22 @@ impl Render for ProjectDiffToolbar {
|
|||||||
Color::Disabled
|
Color::Disabled
|
||||||
})
|
})
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
this.dispatch_action(&StageAndNext, window, cx)
|
this.dispatch_action(
|
||||||
|
&StageAndNext {
|
||||||
|
whole_excerpt: false,
|
||||||
|
},
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("unstage", "Unstage")
|
Button::new("unstage", "Unstage")
|
||||||
.tooltip(Tooltip::for_action_title_in(
|
.tooltip(Tooltip::for_action_title_in(
|
||||||
"Unstage",
|
"Unstage",
|
||||||
&UnstageAndNext,
|
&UnstageAndNext {
|
||||||
|
whole_excerpt: false,
|
||||||
|
},
|
||||||
&focus_handle,
|
&focus_handle,
|
||||||
))
|
))
|
||||||
.color(if button_states.unstage {
|
.color(if button_states.unstage {
|
||||||
@ -839,7 +849,13 @@ impl Render for ProjectDiffToolbar {
|
|||||||
Color::Disabled
|
Color::Disabled
|
||||||
})
|
})
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
.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,
|
/// Registers the action and implements the Action trait for any struct that implements Clone,
|
||||||
/// Default, PartialEq, serde_deserialize::Deserialize, and schemars::JsonSchema.
|
/// 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
|
/// Fields and variants that don't make sense for user configuration should be annotated with
|
||||||
/// #[serde(skip)].
|
/// #[serde(skip)].
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
@ -4112,6 +4112,8 @@ mod tests {
|
|||||||
| "vim::PushLiteral"
|
| "vim::PushLiteral"
|
||||||
| "vim::Number"
|
| "vim::Number"
|
||||||
| "vim::SelectRegister"
|
| "vim::SelectRegister"
|
||||||
|
| "git::StageAndNext"
|
||||||
|
| "git::UnstageAndNext"
|
||||||
| "terminal::SendText"
|
| "terminal::SendText"
|
||||||
| "terminal::SendKeystroke"
|
| "terminal::SendKeystroke"
|
||||||
| "app_menu::OpenApplicationMenu"
|
| "app_menu::OpenApplicationMenu"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user