mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
Add editor::RevertSelectedHunks
to revert git diff hunks in the editor (#9068)
https://github.com/zed-industries/zed/assets/2690773/653b5658-e3f3-4aee-9a9d-0f2153b4141b Release Notes: - Added `editor::RevertSelectedHunks` (`cmd-alt-z` by default) for reverting git hunks from the editor
This commit is contained in:
parent
6a7a3b257a
commit
347178039c
@ -118,7 +118,8 @@
|
||||
"stop_at_soft_wraps": true
|
||||
}
|
||||
],
|
||||
"ctrl-;": "editor::ToggleLineNumbers"
|
||||
"ctrl-;": "editor::ToggleLineNumbers",
|
||||
"ctrl-alt-z": "editor::RevertSelectedHunks"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -153,7 +153,8 @@
|
||||
}
|
||||
],
|
||||
"ctrl-cmd-space": "editor::ShowCharacterPalette",
|
||||
"cmd-;": "editor::ToggleLineNumbers"
|
||||
"cmd-;": "editor::ToggleLineNumbers",
|
||||
"cmd-alt-z": "editor::RevertSelectedHunks"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -5,7 +5,8 @@ use crate::{
|
||||
use call::ActiveCall;
|
||||
use editor::{
|
||||
actions::{
|
||||
ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Redo, Rename, ToggleCodeActions, Undo,
|
||||
ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Redo, Rename, RevertSelectedHunks,
|
||||
ToggleCodeActions, Undo,
|
||||
},
|
||||
test::editor_test_context::{AssertionContextManager, EditorTestContext},
|
||||
Editor,
|
||||
@ -1814,6 +1815,171 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multiple_types_reverts(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
|
||||
let mut server = TestServer::start(cx_a.executor()).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
server
|
||||
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
let active_call_b = cx_b.read(ActiveCall::global);
|
||||
|
||||
cx_a.update(editor::init);
|
||||
cx_b.update(editor::init);
|
||||
|
||||
client_a.language_registry().add(rust_lang());
|
||||
client_b.language_registry().add(rust_lang());
|
||||
|
||||
let base_text = indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row2;
|
||||
|
||||
struct Row4;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
|
||||
struct Row8;
|
||||
struct Row9;
|
||||
struct Row10;"#};
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
.insert_tree(
|
||||
"/a",
|
||||
json!({
|
||||
"main.rs": base_text,
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||
active_call_a
|
||||
.update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_id = active_call_a
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||
active_call_b
|
||||
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
|
||||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
|
||||
let editor_a = workspace_a
|
||||
.update(cx_a, |workspace, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
|
||||
let editor_b = workspace_b
|
||||
.update(cx_b, |workspace, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
|
||||
let mut editor_cx_a = EditorTestContext {
|
||||
cx: cx_a.clone(),
|
||||
window: cx_a.handle(),
|
||||
editor: editor_a,
|
||||
assertion_cx: AssertionContextManager::new(),
|
||||
};
|
||||
let mut editor_cx_b = EditorTestContext {
|
||||
cx: cx_b.clone(),
|
||||
window: cx_b.handle(),
|
||||
editor: editor_b,
|
||||
assertion_cx: AssertionContextManager::new(),
|
||||
};
|
||||
|
||||
// host edits the file, that differs from the base text, producing diff hunks
|
||||
editor_cx_a.set_state(indoc! {r#"struct Row;
|
||||
struct Row0.1;
|
||||
struct Row0.2;
|
||||
struct Row1;
|
||||
|
||||
struct Row4;
|
||||
struct Row5444;
|
||||
struct Row6;
|
||||
|
||||
struct Row9;
|
||||
struct Row1220;ˇ"#});
|
||||
editor_cx_a.update_editor(|editor, cx| {
|
||||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.unwrap()
|
||||
.update(cx, |buffer, cx| {
|
||||
buffer.set_diff_base(Some(base_text.to_string()), cx);
|
||||
});
|
||||
});
|
||||
editor_cx_b.update_editor(|editor, cx| {
|
||||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.unwrap()
|
||||
.update(cx, |buffer, cx| {
|
||||
buffer.set_diff_base(Some(base_text.to_string()), cx);
|
||||
});
|
||||
});
|
||||
cx_a.executor().run_until_parked();
|
||||
cx_b.executor().run_until_parked();
|
||||
|
||||
// client, selects a range in the updated buffer, and reverts it
|
||||
// both host and the client observe the reverted state (with one hunk left, not covered by client's selection)
|
||||
editor_cx_b.set_selections_state(indoc! {r#"«ˇstruct Row;
|
||||
struct Row0.1;
|
||||
struct Row0.2;
|
||||
struct Row1;
|
||||
|
||||
struct Row4;
|
||||
struct Row5444;
|
||||
struct Row6;
|
||||
|
||||
struct R»ow9;
|
||||
struct Row1220;"#});
|
||||
editor_cx_b.update_editor(|editor, cx| {
|
||||
editor.revert_selected_hunks(&RevertSelectedHunks, cx);
|
||||
});
|
||||
cx_a.executor().run_until_parked();
|
||||
cx_b.executor().run_until_parked();
|
||||
editor_cx_a.assert_editor_state(indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row2;
|
||||
|
||||
struct Row4;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
|
||||
struct Row8;
|
||||
struct Row9;
|
||||
struct Row1220;ˇ"#});
|
||||
editor_cx_b.assert_editor_state(indoc! {r#"«ˇstruct Row;
|
||||
struct Row1;
|
||||
struct Row2;
|
||||
|
||||
struct Row4;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
|
||||
struct Row8;
|
||||
struct R»ow9;
|
||||
struct Row1220;"#});
|
||||
}
|
||||
|
||||
fn extract_hint_labels(editor: &Editor) -> Vec<String> {
|
||||
let mut labels = Vec::new();
|
||||
for hint in editor.inlay_hint_cache().hints() {
|
||||
|
@ -210,6 +210,7 @@ gpui::actions!(
|
||||
PageDown,
|
||||
PageUp,
|
||||
Paste,
|
||||
RevertSelectedHunks,
|
||||
Redo,
|
||||
RedoSelection,
|
||||
Rename,
|
||||
|
@ -36,7 +36,7 @@ mod selections_collection;
|
||||
mod editor_tests;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod test;
|
||||
use ::git::diff::DiffHunk;
|
||||
use ::git::diff::{DiffHunk, DiffHunkStatus};
|
||||
pub(crate) use actions::*;
|
||||
use aho_corasick::AhoCorasick;
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
@ -4908,6 +4908,105 @@ impl Editor {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn revert_selected_hunks(&mut self, _: &RevertSelectedHunks, cx: &mut ViewContext<Self>) {
|
||||
let revert_changes = self.gather_revert_changes(&self.selections.disjoint_anchors(), cx);
|
||||
if !revert_changes.is_empty() {
|
||||
self.transact(cx, |editor, cx| {
|
||||
editor.buffer().update(cx, |multi_buffer, cx| {
|
||||
for (buffer_id, buffer_revert_ranges) in revert_changes {
|
||||
if let Some(buffer) = multi_buffer.buffer(buffer_id) {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(buffer_revert_ranges, None, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
editor.change_selections(None, cx, |selections| selections.refresh());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn gather_revert_changes(
|
||||
&mut self,
|
||||
selections: &[Selection<Anchor>],
|
||||
cx: &mut ViewContext<'_, Editor>,
|
||||
) -> HashMap<BufferId, Vec<(Range<text::Anchor>, Arc<str>)>> {
|
||||
let mut revert_changes = HashMap::default();
|
||||
self.buffer.update(cx, |multi_buffer, cx| {
|
||||
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
|
||||
let selected_multi_buffer_rows = selections.iter().map(|selection| {
|
||||
let head = selection.head();
|
||||
let tail = selection.tail();
|
||||
let start = tail.to_point(&multi_buffer_snapshot).row;
|
||||
let end = head.to_point(&multi_buffer_snapshot).row;
|
||||
if start > end {
|
||||
end..start
|
||||
} else {
|
||||
start..end
|
||||
}
|
||||
});
|
||||
|
||||
let mut processed_buffer_rows =
|
||||
HashMap::<BufferId, HashSet<Range<text::Anchor>>>::default();
|
||||
for selected_multi_buffer_rows in selected_multi_buffer_rows {
|
||||
let query_rows =
|
||||
selected_multi_buffer_rows.start..selected_multi_buffer_rows.end + 1;
|
||||
for hunk in multi_buffer_snapshot.git_diff_hunks_in_range(query_rows.clone()) {
|
||||
// Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it
|
||||
// when the caret is just above or just below the deleted hunk.
|
||||
let allow_adjacent = hunk.status() == DiffHunkStatus::Removed;
|
||||
let related_to_selection = if allow_adjacent {
|
||||
hunk.associated_range.overlaps(&query_rows)
|
||||
|| hunk.associated_range.start == query_rows.end
|
||||
|| hunk.associated_range.end == query_rows.start
|
||||
} else {
|
||||
// `selected_multi_buffer_rows` are inclusive (e.g. [2..2] means 2nd row is selected)
|
||||
// `hunk.associated_range` is exclusive (e.g. [2..3] means 2nd row is selected)
|
||||
hunk.associated_range.overlaps(&selected_multi_buffer_rows)
|
||||
|| selected_multi_buffer_rows.end == hunk.associated_range.start
|
||||
};
|
||||
if related_to_selection {
|
||||
if !processed_buffer_rows
|
||||
.entry(hunk.buffer_id)
|
||||
.or_default()
|
||||
.insert(hunk.buffer_range.start..hunk.buffer_range.end)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Self::prepare_revert_change(&mut revert_changes, &multi_buffer, &hunk, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
revert_changes
|
||||
}
|
||||
|
||||
fn prepare_revert_change(
|
||||
revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Arc<str>)>>,
|
||||
multi_buffer: &MultiBuffer,
|
||||
hunk: &DiffHunk<u32>,
|
||||
cx: &mut AppContext,
|
||||
) -> Option<()> {
|
||||
let buffer = multi_buffer.buffer(hunk.buffer_id)?;
|
||||
let buffer = buffer.read(cx);
|
||||
let original_text = buffer.diff_base()?.get(hunk.diff_base_byte_range.clone())?;
|
||||
let buffer_snapshot = buffer.snapshot();
|
||||
let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
|
||||
if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
|
||||
probe
|
||||
.0
|
||||
.start
|
||||
.cmp(&hunk.buffer_range.start, &buffer_snapshot)
|
||||
.then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
|
||||
.then(probe.1.as_ref().cmp(original_text))
|
||||
}) {
|
||||
buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), Arc::from(original_text)));
|
||||
Some(())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext<Self>) {
|
||||
self.manipulate_lines(cx, |lines| lines.reverse())
|
||||
}
|
||||
|
@ -8743,6 +8743,560 @@ async fn test_find_all_references(cx: &mut gpui::TestAppContext) {
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
|
||||
let base_text = indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row2;
|
||||
|
||||
struct Row4;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
|
||||
struct Row8;
|
||||
struct Row9;
|
||||
struct Row10;"#};
|
||||
|
||||
// When addition hunks are not adjacent to carets, no hunk revert is performed
|
||||
assert_hunk_revert(
|
||||
indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row1.1;
|
||||
struct Row1.2;
|
||||
struct Row2;ˇ
|
||||
|
||||
struct Row4;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
|
||||
struct Row8;
|
||||
ˇstruct Row9;
|
||||
struct Row9.1;
|
||||
struct Row9.2;
|
||||
struct Row9.3;
|
||||
struct Row10;"#},
|
||||
vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
|
||||
indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row1.1;
|
||||
struct Row1.2;
|
||||
struct Row2;ˇ
|
||||
|
||||
struct Row4;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
|
||||
struct Row8;
|
||||
ˇstruct Row9;
|
||||
struct Row9.1;
|
||||
struct Row9.2;
|
||||
struct Row9.3;
|
||||
struct Row10;"#},
|
||||
base_text,
|
||||
&mut cx,
|
||||
);
|
||||
// Same for selections
|
||||
assert_hunk_revert(
|
||||
indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row2;
|
||||
struct Row2.1;
|
||||
struct Row2.2;
|
||||
«ˇ
|
||||
struct Row4;
|
||||
struct» Row5;
|
||||
«struct Row6;
|
||||
ˇ»
|
||||
struct Row9.1;
|
||||
struct Row9.2;
|
||||
struct Row9.3;
|
||||
struct Row8;
|
||||
struct Row9;
|
||||
struct Row10;"#},
|
||||
vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
|
||||
indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row2;
|
||||
struct Row2.1;
|
||||
struct Row2.2;
|
||||
«ˇ
|
||||
struct Row4;
|
||||
struct» Row5;
|
||||
«struct Row6;
|
||||
ˇ»
|
||||
struct Row9.1;
|
||||
struct Row9.2;
|
||||
struct Row9.3;
|
||||
struct Row8;
|
||||
struct Row9;
|
||||
struct Row10;"#},
|
||||
base_text,
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
// When carets and selections intersect the addition hunks, those are reverted.
|
||||
// Adjacent carets got merged.
|
||||
assert_hunk_revert(
|
||||
indoc! {r#"struct Row;
|
||||
ˇ// something on the top
|
||||
struct Row1;
|
||||
struct Row2;
|
||||
struct Roˇw3.1;
|
||||
struct Row2.2;
|
||||
struct Row2.3;ˇ
|
||||
|
||||
struct Row4;
|
||||
struct ˇRow5.1;
|
||||
struct Row5.2;
|
||||
struct «Rowˇ»5.3;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
ˇ
|
||||
struct Row9.1;
|
||||
struct «Rowˇ»9.2;
|
||||
struct «ˇRow»9.3;
|
||||
struct Row8;
|
||||
struct Row9;
|
||||
«ˇ// something on bottom»
|
||||
struct Row10;"#},
|
||||
vec![
|
||||
DiffHunkStatus::Added,
|
||||
DiffHunkStatus::Added,
|
||||
DiffHunkStatus::Added,
|
||||
DiffHunkStatus::Added,
|
||||
DiffHunkStatus::Added,
|
||||
],
|
||||
indoc! {r#"struct Row;
|
||||
ˇstruct Row1;
|
||||
struct Row2;
|
||||
ˇ
|
||||
struct Row4;
|
||||
ˇstruct Row5;
|
||||
struct Row6;
|
||||
ˇ
|
||||
ˇstruct Row8;
|
||||
struct Row9;
|
||||
ˇstruct Row10;"#},
|
||||
base_text,
|
||||
&mut cx,
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
|
||||
let base_text = indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row2;
|
||||
|
||||
struct Row4;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
|
||||
struct Row8;
|
||||
struct Row9;
|
||||
struct Row10;"#};
|
||||
|
||||
// Modification hunks behave the same as the addition ones.
|
||||
assert_hunk_revert(
|
||||
indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row33;
|
||||
ˇ
|
||||
struct Row4;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
ˇ
|
||||
struct Row99;
|
||||
struct Row9;
|
||||
struct Row10;"#},
|
||||
vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
|
||||
indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row33;
|
||||
ˇ
|
||||
struct Row4;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
ˇ
|
||||
struct Row99;
|
||||
struct Row9;
|
||||
struct Row10;"#},
|
||||
base_text,
|
||||
&mut cx,
|
||||
);
|
||||
assert_hunk_revert(
|
||||
indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row33;
|
||||
«ˇ
|
||||
struct Row4;
|
||||
struct» Row5;
|
||||
«struct Row6;
|
||||
ˇ»
|
||||
struct Row99;
|
||||
struct Row9;
|
||||
struct Row10;"#},
|
||||
vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
|
||||
indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row33;
|
||||
«ˇ
|
||||
struct Row4;
|
||||
struct» Row5;
|
||||
«struct Row6;
|
||||
ˇ»
|
||||
struct Row99;
|
||||
struct Row9;
|
||||
struct Row10;"#},
|
||||
base_text,
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
assert_hunk_revert(
|
||||
indoc! {r#"ˇstruct Row1.1;
|
||||
struct Row1;
|
||||
«ˇstr»uct Row22;
|
||||
|
||||
struct ˇRow44;
|
||||
struct Row5;
|
||||
struct «Rˇ»ow66;ˇ
|
||||
|
||||
«struˇ»ct Row88;
|
||||
struct Row9;
|
||||
struct Row1011;ˇ"#},
|
||||
vec![
|
||||
DiffHunkStatus::Modified,
|
||||
DiffHunkStatus::Modified,
|
||||
DiffHunkStatus::Modified,
|
||||
DiffHunkStatus::Modified,
|
||||
DiffHunkStatus::Modified,
|
||||
DiffHunkStatus::Modified,
|
||||
],
|
||||
indoc! {r#"struct Row;
|
||||
ˇstruct Row1;
|
||||
struct Row2;
|
||||
ˇ
|
||||
struct Row4;
|
||||
ˇstruct Row5;
|
||||
struct Row6;
|
||||
ˇ
|
||||
struct Row8;
|
||||
ˇstruct Row9;
|
||||
struct Row10;ˇ"#},
|
||||
base_text,
|
||||
&mut cx,
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
|
||||
let base_text = indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row2;
|
||||
|
||||
struct Row4;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
|
||||
struct Row8;
|
||||
struct Row9;
|
||||
struct Row10;"#};
|
||||
|
||||
// Deletion hunks trigger with carets on ajacent rows, so carets and selections have to stay farther to avoid the revert
|
||||
assert_hunk_revert(
|
||||
indoc! {r#"struct Row;
|
||||
struct Row2;
|
||||
|
||||
ˇstruct Row4;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
ˇ
|
||||
struct Row8;
|
||||
struct Row10;"#},
|
||||
vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
|
||||
indoc! {r#"struct Row;
|
||||
struct Row2;
|
||||
|
||||
ˇstruct Row4;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
ˇ
|
||||
struct Row8;
|
||||
struct Row10;"#},
|
||||
base_text,
|
||||
&mut cx,
|
||||
);
|
||||
assert_hunk_revert(
|
||||
indoc! {r#"struct Row;
|
||||
struct Row2;
|
||||
|
||||
«ˇstruct Row4;
|
||||
struct» Row5;
|
||||
«struct Row6;
|
||||
ˇ»
|
||||
struct Row8;
|
||||
struct Row10;"#},
|
||||
vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
|
||||
indoc! {r#"struct Row;
|
||||
struct Row2;
|
||||
|
||||
«ˇstruct Row4;
|
||||
struct» Row5;
|
||||
«struct Row6;
|
||||
ˇ»
|
||||
struct Row8;
|
||||
struct Row10;"#},
|
||||
base_text,
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
// Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
|
||||
assert_hunk_revert(
|
||||
indoc! {r#"struct Row;
|
||||
ˇstruct Row2;
|
||||
|
||||
struct Row4;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
|
||||
struct Row8;ˇ
|
||||
struct Row10;"#},
|
||||
vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
|
||||
indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
ˇstruct Row2;
|
||||
|
||||
struct Row4;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
|
||||
struct Row8;ˇ
|
||||
struct Row9;
|
||||
struct Row10;"#},
|
||||
base_text,
|
||||
&mut cx,
|
||||
);
|
||||
assert_hunk_revert(
|
||||
indoc! {r#"struct Row;
|
||||
struct Row2«ˇ;
|
||||
struct Row4;
|
||||
struct» Row5;
|
||||
«struct Row6;
|
||||
|
||||
struct Row8;ˇ»
|
||||
struct Row10;"#},
|
||||
vec![
|
||||
DiffHunkStatus::Removed,
|
||||
DiffHunkStatus::Removed,
|
||||
DiffHunkStatus::Removed,
|
||||
],
|
||||
indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row2«ˇ;
|
||||
|
||||
struct Row4;
|
||||
struct» Row5;
|
||||
«struct Row6;
|
||||
|
||||
struct Row8;ˇ»
|
||||
struct Row9;
|
||||
struct Row10;"#},
|
||||
base_text,
|
||||
&mut cx,
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let cols = 4;
|
||||
let rows = 10;
|
||||
let sample_text_1 = sample_text(rows, cols, 'a');
|
||||
assert_eq!(
|
||||
sample_text_1,
|
||||
"aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
|
||||
);
|
||||
let sample_text_2 = sample_text(rows, cols, 'l');
|
||||
assert_eq!(
|
||||
sample_text_2,
|
||||
"llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
|
||||
);
|
||||
let sample_text_3 = sample_text(rows, cols, 'v');
|
||||
assert_eq!(
|
||||
sample_text_3,
|
||||
"vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
|
||||
);
|
||||
|
||||
fn diff_every_buffer_row(
|
||||
buffer: &Model<Buffer>,
|
||||
sample_text: String,
|
||||
cols: usize,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
// revert first character in each row, creating one large diff hunk per buffer
|
||||
let is_first_char = |offset: usize| offset % cols == 0;
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.set_text(
|
||||
sample_text
|
||||
.chars()
|
||||
.enumerate()
|
||||
.map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
|
||||
.collect::<String>(),
|
||||
cx,
|
||||
);
|
||||
buffer.set_diff_base(Some(sample_text), cx);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
}
|
||||
|
||||
let buffer_1 = cx.new_model(|cx| {
|
||||
Buffer::new(
|
||||
0,
|
||||
BufferId::new(cx.entity_id().as_u64()).unwrap(),
|
||||
sample_text_1.clone(),
|
||||
)
|
||||
});
|
||||
diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
|
||||
|
||||
let buffer_2 = cx.new_model(|cx| {
|
||||
Buffer::new(
|
||||
1,
|
||||
BufferId::new(cx.entity_id().as_u64() + 1).unwrap(),
|
||||
sample_text_2.clone(),
|
||||
)
|
||||
});
|
||||
diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
|
||||
|
||||
let buffer_3 = cx.new_model(|cx| {
|
||||
Buffer::new(
|
||||
2,
|
||||
BufferId::new(cx.entity_id().as_u64() + 2).unwrap(),
|
||||
sample_text_3.clone(),
|
||||
)
|
||||
});
|
||||
diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
|
||||
|
||||
let multibuffer = cx.new_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(0, ReadWrite);
|
||||
multibuffer.push_excerpts(
|
||||
buffer_1.clone(),
|
||||
[
|
||||
ExcerptRange {
|
||||
context: Point::new(0, 0)..Point::new(3, 0),
|
||||
primary: None,
|
||||
},
|
||||
ExcerptRange {
|
||||
context: Point::new(5, 0)..Point::new(7, 0),
|
||||
primary: None,
|
||||
},
|
||||
ExcerptRange {
|
||||
context: Point::new(9, 0)..Point::new(10, 4),
|
||||
primary: None,
|
||||
},
|
||||
],
|
||||
cx,
|
||||
);
|
||||
multibuffer.push_excerpts(
|
||||
buffer_2.clone(),
|
||||
[
|
||||
ExcerptRange {
|
||||
context: Point::new(0, 0)..Point::new(3, 0),
|
||||
primary: None,
|
||||
},
|
||||
ExcerptRange {
|
||||
context: Point::new(5, 0)..Point::new(7, 0),
|
||||
primary: None,
|
||||
},
|
||||
ExcerptRange {
|
||||
context: Point::new(9, 0)..Point::new(10, 4),
|
||||
primary: None,
|
||||
},
|
||||
],
|
||||
cx,
|
||||
);
|
||||
multibuffer.push_excerpts(
|
||||
buffer_3.clone(),
|
||||
[
|
||||
ExcerptRange {
|
||||
context: Point::new(0, 0)..Point::new(3, 0),
|
||||
primary: None,
|
||||
},
|
||||
ExcerptRange {
|
||||
context: Point::new(5, 0)..Point::new(7, 0),
|
||||
primary: None,
|
||||
},
|
||||
ExcerptRange {
|
||||
context: Point::new(9, 0)..Point::new(10, 4),
|
||||
primary: None,
|
||||
},
|
||||
],
|
||||
cx,
|
||||
);
|
||||
multibuffer
|
||||
});
|
||||
|
||||
let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "XaaaXbbbX\nccXc\ndXdd\n\nhXhh\nXiiiXjjjX\n\nXlllXmmmX\nnnXn\noXoo\n\nsXss\nXtttXuuuX\n\nXvvvXwwwX\nxxXx\nyXyy\n\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n");
|
||||
editor.select_all(&SelectAll, cx);
|
||||
editor.revert_selected_hunks(&RevertSelectedHunks, cx);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
// When all ranges are selected, all buffer hunks are reverted.
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nllll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu\n\n\nvvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}\n\n");
|
||||
});
|
||||
buffer_1.update(cx, |buffer, _| {
|
||||
assert_eq!(buffer.text(), sample_text_1);
|
||||
});
|
||||
buffer_2.update(cx, |buffer, _| {
|
||||
assert_eq!(buffer.text(), sample_text_2);
|
||||
});
|
||||
buffer_3.update(cx, |buffer, _| {
|
||||
assert_eq!(buffer.text(), sample_text_3);
|
||||
});
|
||||
|
||||
diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
|
||||
diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
|
||||
diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
|
||||
});
|
||||
editor.revert_selected_hunks(&RevertSelectedHunks, cx);
|
||||
});
|
||||
// Now, when all ranges selected belong to buffer_1, the revert should succeed,
|
||||
// but not affect buffer_2 and its related excerpts.
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
"aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX\n\n\nXvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n\n"
|
||||
);
|
||||
});
|
||||
buffer_1.update(cx, |buffer, _| {
|
||||
assert_eq!(buffer.text(), sample_text_1);
|
||||
});
|
||||
buffer_2.update(cx, |buffer, _| {
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
"XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
|
||||
);
|
||||
});
|
||||
buffer_3.update(cx, |buffer, _| {
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
"XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
|
||||
let point = DisplayPoint::new(row as u32, column as u32);
|
||||
point..point
|
||||
@ -8913,3 +9467,45 @@ pub(crate) fn rust_lang() -> Arc<Language> {
|
||||
Some(tree_sitter_rust::language()),
|
||||
))
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_hunk_revert(
|
||||
not_reverted_text_with_selections: &str,
|
||||
expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
|
||||
expected_reverted_text_with_selections: &str,
|
||||
base_text: &str,
|
||||
cx: &mut EditorLspTestContext,
|
||||
) {
|
||||
cx.set_state(not_reverted_text_with_selections);
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.unwrap()
|
||||
.update(cx, |buffer, cx| {
|
||||
buffer.set_diff_base(Some(base_text.to_string()), cx);
|
||||
});
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
|
||||
let snapshot = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.snapshot();
|
||||
let reverted_hunk_statuses = snapshot
|
||||
.git_diff_hunks_in_row_range(0..u32::MAX)
|
||||
.map(|hunk| hunk.status())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
editor.revert_selected_hunks(&RevertSelectedHunks, cx);
|
||||
reverted_hunk_statuses
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
cx.assert_editor_state(expected_reverted_text_with_selections);
|
||||
assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
|
||||
}
|
||||
|
@ -339,6 +339,7 @@ impl EditorElement {
|
||||
register_action(view, cx, Editor::unique_lines_case_insensitive);
|
||||
register_action(view, cx, Editor::unique_lines_case_sensitive);
|
||||
register_action(view, cx, Editor::accept_partial_copilot_suggestion);
|
||||
register_action(view, cx, Editor::revert_selected_hunks);
|
||||
}
|
||||
|
||||
fn register_key_listeners(
|
||||
@ -1452,12 +1453,12 @@ impl EditorElement {
|
||||
.buffer_snapshot
|
||||
.git_diff_hunks_in_range(0..(max_row.floor() as u32))
|
||||
{
|
||||
let start_display = Point::new(hunk.buffer_range.start, 0)
|
||||
let start_display = Point::new(hunk.associated_range.start, 0)
|
||||
.to_display_point(&layout.position_map.snapshot.display_snapshot);
|
||||
let end_display = Point::new(hunk.buffer_range.end, 0)
|
||||
let end_display = Point::new(hunk.associated_range.end, 0)
|
||||
.to_display_point(&layout.position_map.snapshot.display_snapshot);
|
||||
let start_y = y_for_row(start_display.row() as f32);
|
||||
let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
|
||||
let mut end_y = if hunk.associated_range.start == hunk.associated_range.end {
|
||||
y_for_row((end_display.row() + 1) as f32)
|
||||
} else {
|
||||
y_for_row((end_display.row()) as f32)
|
||||
|
@ -46,20 +46,20 @@ impl DisplayDiffHunk {
|
||||
}
|
||||
|
||||
pub fn diff_hunk_to_display(hunk: DiffHunk<u32>, snapshot: &DisplaySnapshot) -> DisplayDiffHunk {
|
||||
let hunk_start_point = Point::new(hunk.buffer_range.start, 0);
|
||||
let hunk_start_point_sub = Point::new(hunk.buffer_range.start.saturating_sub(1), 0);
|
||||
let hunk_start_point = Point::new(hunk.associated_range.start, 0);
|
||||
let hunk_start_point_sub = Point::new(hunk.associated_range.start.saturating_sub(1), 0);
|
||||
let hunk_end_point_sub = Point::new(
|
||||
hunk.buffer_range
|
||||
hunk.associated_range
|
||||
.end
|
||||
.saturating_sub(1)
|
||||
.max(hunk.buffer_range.start),
|
||||
.max(hunk.associated_range.start),
|
||||
0,
|
||||
);
|
||||
|
||||
let is_removal = hunk.status() == DiffHunkStatus::Removed;
|
||||
|
||||
let folds_start = Point::new(hunk.buffer_range.start.saturating_sub(2), 0);
|
||||
let folds_end = Point::new(hunk.buffer_range.end + 2, 0);
|
||||
let folds_start = Point::new(hunk.associated_range.start.saturating_sub(2), 0);
|
||||
let folds_end = Point::new(hunk.associated_range.end + 2, 0);
|
||||
let folds_range = folds_start..folds_end;
|
||||
|
||||
let containing_fold = snapshot.folds_in_range(folds_range).find(|fold| {
|
||||
@ -79,7 +79,7 @@ pub fn diff_hunk_to_display(hunk: DiffHunk<u32>, snapshot: &DisplaySnapshot) ->
|
||||
} else {
|
||||
let start = hunk_start_point.to_display_point(snapshot).row();
|
||||
|
||||
let hunk_end_row = hunk.buffer_range.end.max(hunk.buffer_range.start);
|
||||
let hunk_end_row = hunk.associated_range.end.max(hunk.associated_range.start);
|
||||
let hunk_end_point = Point::new(hunk_end_row, 0);
|
||||
let end = hunk_end_point.to_display_point(snapshot).row();
|
||||
|
||||
@ -264,7 +264,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.git_diff_hunks_in_range(0..12)
|
||||
.map(|hunk| (hunk.status(), hunk.buffer_range))
|
||||
.map(|hunk| (hunk.status(), hunk.associated_range))
|
||||
.collect::<Vec<_>>(),
|
||||
&expected,
|
||||
);
|
||||
@ -272,7 +272,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.git_diff_hunks_in_range_rev(0..12)
|
||||
.map(|hunk| (hunk.status(), hunk.buffer_range))
|
||||
.map(|hunk| (hunk.status(), hunk.associated_range))
|
||||
.collect::<Vec<_>>(),
|
||||
expected
|
||||
.iter()
|
||||
|
@ -274,7 +274,7 @@ impl EditorTestContext {
|
||||
let buffer_text = self.buffer_text();
|
||||
|
||||
if buffer_text != unmarked_text {
|
||||
panic!("Unmarked text doesn't match buffer text\nBuffer text: {buffer_text:?}\nUnmarked text: {unmarked_text:?}\nRaw buffer text\n{buffer_text}Raw unmarked text\n{unmarked_text}");
|
||||
panic!("Unmarked text doesn't match buffer text\nBuffer text: {buffer_text:?}\nUnmarked text: {unmarked_text:?}\nRaw buffer text\n{buffer_text}\nRaw unmarked text\n{unmarked_text}");
|
||||
}
|
||||
|
||||
self.assert_selections(expected_selections, marked_text.to_string())
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::{iter, ops::Range};
|
||||
use sum_tree::SumTree;
|
||||
use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point};
|
||||
use text::{Anchor, BufferId, BufferSnapshot, OffsetRangeExt, Point};
|
||||
|
||||
pub use git2 as libgit;
|
||||
use libgit::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
|
||||
@ -12,17 +12,53 @@ pub enum DiffHunkStatus {
|
||||
Removed,
|
||||
}
|
||||
|
||||
/// A diff hunk, representing a range of consequent lines in a singleton buffer, associated with a generic range.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DiffHunk<T> {
|
||||
pub buffer_range: Range<T>,
|
||||
/// E.g. a range in multibuffer, that has an excerpt added, singleton buffer for which has this diff hunk.
|
||||
/// Consider a singleton buffer with 10 lines, all of them are modified — so a corresponding diff hunk would have a range 0..10.
|
||||
/// And a multibuffer with the excerpt of lines 2-6 from the singleton buffer.
|
||||
/// If the multibuffer is searched for diff hunks, the associated range would be multibuffer rows, corresponding to rows 2..6 from the singleton buffer.
|
||||
/// But the hunk range would be 0..10, same for any other excerpts from the same singleton buffer.
|
||||
pub associated_range: Range<T>,
|
||||
/// Singleton buffer ID this hunk belongs to.
|
||||
pub buffer_id: BufferId,
|
||||
/// A consequent range of lines in the singleton buffer, that were changed and produced this diff hunk.
|
||||
pub buffer_range: Range<Anchor>,
|
||||
/// Original singleton buffer text before the change, that was instead of the `buffer_range`.
|
||||
pub diff_base_byte_range: Range<usize>,
|
||||
}
|
||||
|
||||
impl<T> DiffHunk<T> {
|
||||
fn buffer_range_empty(&self) -> bool {
|
||||
if self.buffer_range.start == self.buffer_range.end {
|
||||
return true;
|
||||
}
|
||||
|
||||
// buffer diff hunks are per line, so if we arrive to the same line with different bias, it's the same hunk
|
||||
let Anchor {
|
||||
timestamp: timestamp_start,
|
||||
offset: offset_start,
|
||||
buffer_id: buffer_id_start,
|
||||
bias: _,
|
||||
} = self.buffer_range.start;
|
||||
let Anchor {
|
||||
timestamp: timestamp_end,
|
||||
offset: offset_end,
|
||||
buffer_id: buffer_id_end,
|
||||
bias: _,
|
||||
} = self.buffer_range.end;
|
||||
timestamp_start == timestamp_end
|
||||
&& offset_start == offset_end
|
||||
&& buffer_id_start == buffer_id_end
|
||||
}
|
||||
}
|
||||
|
||||
impl DiffHunk<u32> {
|
||||
pub fn status(&self) -> DiffHunkStatus {
|
||||
if self.diff_base_byte_range.is_empty() {
|
||||
DiffHunkStatus::Added
|
||||
} else if self.buffer_range.is_empty() {
|
||||
} else if self.buffer_range_empty() {
|
||||
DiffHunkStatus::Removed
|
||||
} else {
|
||||
DiffHunkStatus::Modified
|
||||
@ -35,7 +71,7 @@ impl sum_tree::Item for DiffHunk<Anchor> {
|
||||
|
||||
fn summary(&self) -> Self::Summary {
|
||||
DiffHunkSummary {
|
||||
buffer_range: self.buffer_range.clone(),
|
||||
buffer_range: self.associated_range.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -57,7 +93,7 @@ impl sum_tree::Summary for DiffHunkSummary {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BufferDiff {
|
||||
last_buffer_version: Option<clock::Global>,
|
||||
tree: SumTree<DiffHunk<Anchor>>,
|
||||
@ -103,8 +139,11 @@ impl BufferDiff {
|
||||
})
|
||||
.flat_map(move |hunk| {
|
||||
[
|
||||
(&hunk.buffer_range.start, hunk.diff_base_byte_range.start),
|
||||
(&hunk.buffer_range.end, hunk.diff_base_byte_range.end),
|
||||
(
|
||||
&hunk.associated_range.start,
|
||||
hunk.diff_base_byte_range.start,
|
||||
),
|
||||
(&hunk.associated_range.end, hunk.diff_base_byte_range.end),
|
||||
]
|
||||
.into_iter()
|
||||
});
|
||||
@ -112,17 +151,17 @@ impl BufferDiff {
|
||||
let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
|
||||
iter::from_fn(move || {
|
||||
let (start_point, start_base) = summaries.next()?;
|
||||
let (end_point, end_base) = summaries.next()?;
|
||||
let (mut end_point, end_base) = summaries.next()?;
|
||||
|
||||
let end_row = if end_point.column > 0 {
|
||||
end_point.row + 1
|
||||
} else {
|
||||
end_point.row
|
||||
};
|
||||
if end_point.column > 0 {
|
||||
end_point.row += 1;
|
||||
}
|
||||
|
||||
Some(DiffHunk {
|
||||
buffer_range: start_point.row..end_row,
|
||||
associated_range: start_point.row..end_point.row,
|
||||
diff_base_byte_range: start_base..end_base,
|
||||
buffer_range: buffer.anchor_before(start_point)..buffer.anchor_after(end_point),
|
||||
buffer_id: buffer.remote_id(),
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -142,7 +181,7 @@ impl BufferDiff {
|
||||
cursor.prev(buffer);
|
||||
|
||||
let hunk = cursor.item()?;
|
||||
let range = hunk.buffer_range.to_point(buffer);
|
||||
let range = hunk.associated_range.to_point(buffer);
|
||||
let end_row = if range.end.column > 0 {
|
||||
range.end.row + 1
|
||||
} else {
|
||||
@ -150,8 +189,10 @@ impl BufferDiff {
|
||||
};
|
||||
|
||||
Some(DiffHunk {
|
||||
buffer_range: range.start.row..end_row,
|
||||
associated_range: range.start.row..end_row,
|
||||
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
|
||||
buffer_range: hunk.buffer_range.clone(),
|
||||
buffer_id: hunk.buffer_id,
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -269,8 +310,10 @@ impl BufferDiff {
|
||||
let end = Point::new(buffer_row_range.end, 0);
|
||||
let buffer_range = buffer.anchor_before(start)..buffer.anchor_before(end);
|
||||
DiffHunk {
|
||||
associated_range: buffer_range.clone(),
|
||||
buffer_range,
|
||||
diff_base_byte_range,
|
||||
buffer_id: buffer.remote_id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -289,12 +332,12 @@ pub fn assert_hunks<Iter>(
|
||||
let actual_hunks = diff_hunks
|
||||
.map(|hunk| {
|
||||
(
|
||||
hunk.buffer_range.clone(),
|
||||
hunk.associated_range.clone(),
|
||||
&diff_base[hunk.diff_base_byte_range],
|
||||
buffer
|
||||
.text_for_range(
|
||||
Point::new(hunk.buffer_range.start, 0)
|
||||
..Point::new(hunk.buffer_range.end, 0),
|
||||
Point::new(hunk.associated_range.start, 0)
|
||||
..Point::new(hunk.associated_range.end, 0),
|
||||
)
|
||||
.collect::<String>(),
|
||||
)
|
||||
|
@ -930,8 +930,17 @@ impl Buffer {
|
||||
/// against the buffer text.
|
||||
pub fn set_diff_base(&mut self, diff_base: Option<String>, cx: &mut ModelContext<Self>) {
|
||||
self.diff_base = diff_base;
|
||||
self.git_diff_recalc(cx);
|
||||
cx.emit(Event::DiffBaseChanged);
|
||||
if let Some(recalc_task) = self.git_diff_recalc(cx) {
|
||||
cx.spawn(|buffer, mut cx| async move {
|
||||
recalc_task.await;
|
||||
buffer
|
||||
.update(&mut cx, |_, cx| {
|
||||
cx.emit(Event::DiffBaseChanged);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
/// Recomputes the Git diff status.
|
||||
|
@ -3186,19 +3186,21 @@ impl MultiBufferSnapshot {
|
||||
.map(move |hunk| {
|
||||
let start = multibuffer_start.row
|
||||
+ hunk
|
||||
.buffer_range
|
||||
.associated_range
|
||||
.start
|
||||
.saturating_sub(excerpt_start_point.row);
|
||||
let end = multibuffer_start.row
|
||||
+ hunk
|
||||
.buffer_range
|
||||
.associated_range
|
||||
.end
|
||||
.min(excerpt_end_point.row + 1)
|
||||
.saturating_sub(excerpt_start_point.row);
|
||||
|
||||
DiffHunk {
|
||||
buffer_range: start..end,
|
||||
associated_range: start..end,
|
||||
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
|
||||
buffer_range: hunk.buffer_range.clone(),
|
||||
buffer_id: hunk.buffer_id,
|
||||
}
|
||||
});
|
||||
|
||||
@ -3215,52 +3217,65 @@ impl MultiBufferSnapshot {
|
||||
) -> impl Iterator<Item = DiffHunk<u32>> + '_ {
|
||||
let mut cursor = self.excerpts.cursor::<Point>();
|
||||
|
||||
cursor.seek(&Point::new(row_range.start, 0), Bias::Right, &());
|
||||
cursor.seek(&Point::new(row_range.start, 0), Bias::Left, &());
|
||||
|
||||
std::iter::from_fn(move || {
|
||||
let excerpt = cursor.item()?;
|
||||
let multibuffer_start = *cursor.start();
|
||||
let multibuffer_end = multibuffer_start + excerpt.text_summary.lines;
|
||||
if multibuffer_start.row >= row_range.end {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut buffer_start = excerpt.range.context.start;
|
||||
let mut buffer_end = excerpt.range.context.end;
|
||||
let excerpt_start_point = buffer_start.to_point(&excerpt.buffer);
|
||||
let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines;
|
||||
|
||||
if row_range.start > multibuffer_start.row {
|
||||
let buffer_start_point =
|
||||
excerpt_start_point + Point::new(row_range.start - multibuffer_start.row, 0);
|
||||
buffer_start = excerpt.buffer.anchor_before(buffer_start_point);
|
||||
}
|
||||
let excerpt_rows = match multibuffer_start.row.cmp(&row_range.end) {
|
||||
cmp::Ordering::Less => {
|
||||
let excerpt_start_point = buffer_start.to_point(&excerpt.buffer);
|
||||
let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines;
|
||||
|
||||
if row_range.end < multibuffer_end.row {
|
||||
let buffer_end_point =
|
||||
excerpt_start_point + Point::new(row_range.end - multibuffer_start.row, 0);
|
||||
buffer_end = excerpt.buffer.anchor_before(buffer_end_point);
|
||||
}
|
||||
if row_range.start > multibuffer_start.row {
|
||||
let buffer_start_point = excerpt_start_point
|
||||
+ Point::new(row_range.start - multibuffer_start.row, 0);
|
||||
buffer_start = excerpt.buffer.anchor_before(buffer_start_point);
|
||||
}
|
||||
|
||||
if row_range.end < multibuffer_end.row {
|
||||
let buffer_end_point = excerpt_start_point
|
||||
+ Point::new(row_range.end - multibuffer_start.row, 0);
|
||||
buffer_end = excerpt.buffer.anchor_before(buffer_end_point);
|
||||
}
|
||||
excerpt_start_point.row..excerpt_end_point.row
|
||||
}
|
||||
cmp::Ordering::Equal if row_range.end == 0 => {
|
||||
buffer_end = buffer_start;
|
||||
0..0
|
||||
}
|
||||
cmp::Ordering::Greater | cmp::Ordering::Equal => return None,
|
||||
};
|
||||
|
||||
let buffer_hunks = excerpt
|
||||
.buffer
|
||||
.git_diff_hunks_intersecting_range(buffer_start..buffer_end)
|
||||
.map(move |hunk| {
|
||||
let start = multibuffer_start.row
|
||||
+ hunk
|
||||
.buffer_range
|
||||
.start
|
||||
.saturating_sub(excerpt_start_point.row);
|
||||
let end = multibuffer_start.row
|
||||
+ hunk
|
||||
.buffer_range
|
||||
.end
|
||||
.min(excerpt_end_point.row + 1)
|
||||
.saturating_sub(excerpt_start_point.row);
|
||||
|
||||
let buffer_range = if excerpt_rows.start == 0 && excerpt_rows.end == 0 {
|
||||
0..1
|
||||
} else {
|
||||
let start = multibuffer_start.row
|
||||
+ hunk
|
||||
.associated_range
|
||||
.start
|
||||
.saturating_sub(excerpt_rows.start);
|
||||
let end = multibuffer_start.row
|
||||
+ hunk
|
||||
.associated_range
|
||||
.end
|
||||
.min(excerpt_rows.end + 1)
|
||||
.saturating_sub(excerpt_rows.start);
|
||||
start..end
|
||||
};
|
||||
DiffHunk {
|
||||
buffer_range: start..end,
|
||||
associated_range: buffer_range,
|
||||
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
|
||||
buffer_range: hunk.buffer_range.clone(),
|
||||
buffer_id: hunk.buffer_id,
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -4427,7 +4427,6 @@ impl Project {
|
||||
project_transaction.0.extend(new.0);
|
||||
}
|
||||
|
||||
// TODO kb here too:
|
||||
if let Some(command) = action.lsp_action.command {
|
||||
project.update(&mut cx, |this, _| {
|
||||
this.last_workspace_edits_by_language_server
|
||||
|
Loading…
Reference in New Issue
Block a user