Implement staging of partially-staged hunks (#25520)
Closes: #25475 This PR makes it possible to stage uncommitted hunks that overlap but do not coincide with an unstaged hunk. Release Notes: - Made it possible to stage hunks that are already partially staged --------- Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com> Co-authored-by: Max <max@zed.dev>
This commit is contained in:
parent
bcbb19e06e
commit
45146b6f30
6 changed files with 432 additions and 179 deletions
|
@ -13329,7 +13329,7 @@ impl Editor {
|
|||
snapshot: &MultiBufferSnapshot,
|
||||
) -> bool {
|
||||
let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
|
||||
hunks.any(|hunk| hunk.secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk)
|
||||
hunks.any(|hunk| hunk.secondary_status != DiffHunkSecondaryStatus::None)
|
||||
}
|
||||
|
||||
pub fn toggle_staged_selected_diff_hunks(
|
||||
|
@ -13474,12 +13474,8 @@ impl Editor {
|
|||
log::debug!("no diff for buffer id");
|
||||
return;
|
||||
};
|
||||
let Some(secondary_diff) = diff.secondary_diff() else {
|
||||
log::debug!("no secondary diff for buffer id");
|
||||
return;
|
||||
};
|
||||
|
||||
let edits = diff.secondary_edits_for_stage_or_unstage(
|
||||
let Some(new_index_text) = diff.new_secondary_text_for_stage_or_unstage(
|
||||
stage,
|
||||
hunks.filter_map(|hunk| {
|
||||
if stage && hunk.secondary_status == DiffHunkSecondaryStatus::None {
|
||||
|
@ -13489,29 +13485,14 @@ impl Editor {
|
|||
{
|
||||
return None;
|
||||
}
|
||||
Some((
|
||||
hunk.diff_base_byte_range.clone(),
|
||||
hunk.secondary_diff_base_byte_range.clone(),
|
||||
hunk.buffer_range.clone(),
|
||||
))
|
||||
Some((hunk.buffer_range.clone(), hunk.diff_base_byte_range.clone()))
|
||||
}),
|
||||
&buffer_snapshot,
|
||||
);
|
||||
|
||||
let Some(index_base) = secondary_diff
|
||||
.base_text()
|
||||
.map(|snapshot| snapshot.text.as_rope().clone())
|
||||
else {
|
||||
log::debug!("no index base");
|
||||
cx,
|
||||
) else {
|
||||
log::debug!("missing secondary diff or index text");
|
||||
return;
|
||||
};
|
||||
let index_buffer = cx.new(|cx| {
|
||||
Buffer::local_normalized(index_base.clone(), text::LineEnding::default(), cx)
|
||||
});
|
||||
let new_index_text = index_buffer.update(cx, |index_buffer, cx| {
|
||||
index_buffer.edit(edits, None, cx);
|
||||
index_buffer.snapshot().as_rope().to_string()
|
||||
});
|
||||
let new_index_text = if new_index_text.is_empty()
|
||||
&& !stage
|
||||
&& (diff.is_single_insertion
|
||||
|
@ -13531,7 +13512,7 @@ impl Editor {
|
|||
|
||||
cx.background_spawn(
|
||||
repo.read(cx)
|
||||
.set_index_text(&path, new_index_text)
|
||||
.set_index_text(&path, new_index_text.map(|rope| rope.to_string()))
|
||||
.log_err(),
|
||||
)
|
||||
.detach();
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
},
|
||||
JoinLines,
|
||||
};
|
||||
use buffer_diff::{BufferDiff, DiffHunkStatus};
|
||||
use buffer_diff::{BufferDiff, DiffHunkStatus, DiffHunkStatusKind};
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
|
||||
|
@ -3389,7 +3389,7 @@ async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &m
|
|||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(&diff_base);
|
||||
cx.set_head_text(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
// Join lines
|
||||
|
@ -3429,7 +3429,7 @@ async fn test_custom_newlines_cause_no_false_positive_diffs(
|
|||
init_test(cx, |_| {});
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
|
||||
cx.set_diff_base("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
|
||||
cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
|
@ -5811,7 +5811,7 @@ async fn test_fold_function_bodies(cx: &mut TestAppContext) {
|
|||
|
||||
let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
|
||||
cx.set_state(&text);
|
||||
cx.set_diff_base(&base_text);
|
||||
cx.set_head_text(&base_text);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.expand_all_diff_hunks(&Default::default(), window, cx);
|
||||
});
|
||||
|
@ -11039,7 +11039,7 @@ async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext)
|
|||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(&diff_base);
|
||||
cx.set_head_text(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
|
@ -12531,7 +12531,7 @@ async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
|
|||
three
|
||||
"#};
|
||||
|
||||
cx.set_diff_base(base_text);
|
||||
cx.set_head_text(base_text);
|
||||
cx.set_state("\nˇ\n");
|
||||
cx.executor().run_until_parked();
|
||||
cx.update_editor(|editor, _window, cx| {
|
||||
|
@ -13168,7 +13168,7 @@ async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut
|
|||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(&diff_base);
|
||||
cx.set_head_text(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
|
@ -13302,7 +13302,7 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
|
|||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(&diff_base);
|
||||
cx.set_head_text(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
|
@ -13330,7 +13330,7 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
|
|||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base("new diff base!");
|
||||
cx.set_head_text("new diff base!");
|
||||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
|
@ -13630,7 +13630,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
|||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(&diff_base);
|
||||
cx.set_head_text(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
|
@ -13778,7 +13778,7 @@ async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
|
|||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.set_diff_base(indoc! { "
|
||||
cx.set_head_text(indoc! { "
|
||||
one
|
||||
two
|
||||
three
|
||||
|
@ -13901,7 +13901,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
|||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(&diff_base);
|
||||
cx.set_head_text(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
|
@ -14024,7 +14024,7 @@ async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &m
|
|||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(&base_text);
|
||||
cx.set_head_text(&base_text);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
|
@ -14106,7 +14106,7 @@ async fn test_edit_after_expanded_modification_hunk(
|
|||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(&diff_base);
|
||||
cx.set_head_text(&diff_base);
|
||||
executor.run_until_parked();
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
|
||||
|
@ -14841,7 +14841,7 @@ async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestApp
|
|||
"#
|
||||
.unindent(),
|
||||
);
|
||||
cx.set_diff_base(&diff_base);
|
||||
cx.set_head_text(&diff_base);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
|
||||
});
|
||||
|
@ -14978,6 +14978,80 @@ async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestApp
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.set_head_text(indoc! { "
|
||||
one
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
"
|
||||
});
|
||||
cx.set_index_text(indoc! { "
|
||||
one
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
"
|
||||
});
|
||||
cx.set_state(indoc! {"
|
||||
one
|
||||
TWO
|
||||
ˇTHREE
|
||||
FOUR
|
||||
five
|
||||
"});
|
||||
cx.run_until_parked();
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
cx.assert_index_text(Some(indoc! {"
|
||||
one
|
||||
TWO
|
||||
THREE
|
||||
FOUR
|
||||
five
|
||||
"}));
|
||||
cx.set_state(indoc! { "
|
||||
one
|
||||
TWO
|
||||
ˇTHREE-HUNDRED
|
||||
FOUR
|
||||
five
|
||||
"});
|
||||
cx.run_until_parked();
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let hunks = editor
|
||||
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(hunks.len(), 1);
|
||||
assert_eq!(
|
||||
hunks[0].status(),
|
||||
DiffHunkStatus {
|
||||
kind: DiffHunkStatusKind::Modified,
|
||||
secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
|
||||
}
|
||||
);
|
||||
|
||||
editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
cx.assert_index_text(Some(indoc! {"
|
||||
one
|
||||
TWO
|
||||
THREE-HUNDRED
|
||||
FOUR
|
||||
five
|
||||
"}));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
@ -16341,7 +16415,7 @@ fn assert_hunk_revert(
|
|||
cx: &mut EditorLspTestContext,
|
||||
) {
|
||||
cx.set_state(not_reverted_text_with_selections);
|
||||
cx.set_diff_base(base_text);
|
||||
cx.set_head_text(base_text);
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
|
||||
|
|
|
@ -285,7 +285,7 @@ impl EditorTestContext {
|
|||
snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
|
||||
}
|
||||
|
||||
pub fn set_diff_base(&mut self, diff_base: &str) {
|
||||
pub fn set_head_text(&mut self, diff_base: &str) {
|
||||
self.cx.run_until_parked();
|
||||
let fs = self.update_editor(|editor, _, cx| {
|
||||
editor.project.as_ref().unwrap().read(cx).fs().as_fake()
|
||||
|
@ -298,6 +298,19 @@ impl EditorTestContext {
|
|||
self.cx.run_until_parked();
|
||||
}
|
||||
|
||||
pub fn set_index_text(&mut self, diff_base: &str) {
|
||||
self.cx.run_until_parked();
|
||||
let fs = self.update_editor(|editor, _, cx| {
|
||||
editor.project.as_ref().unwrap().read(cx).fs().as_fake()
|
||||
});
|
||||
let path = self.update_buffer(|buffer, _| buffer.file().unwrap().path().clone());
|
||||
fs.set_index_for_repo(
|
||||
&Self::root_path().join(".git"),
|
||||
&[(path.into(), diff_base.to_string())],
|
||||
);
|
||||
self.cx.run_until_parked();
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn assert_index_text(&mut self, expected: Option<&str>) {
|
||||
let fs = self.update_editor(|editor, _, cx| {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue