Expand git diffs when clicking the gutter strip, display their controls in a block above (#18313)
Todo:
* [x] Tooltips for hunk buttons
* [x] Buttons to go to next and previous hunk
* [x] Ellipsis button that opens a context menu with `Revert all`
/cc @iamnbutler @danilo-leal for design 👀
Release Notes:
- Changed the behavior of the git gutter so that diff hunk are expanded
immediately when clicking the gutter, and hunk controls are displayed
above the hunk.
---------
Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
This commit is contained in:
parent
ae6a3d15af
commit
21a023980d
5 changed files with 580 additions and 758 deletions
|
@ -7,18 +7,12 @@ use collections::HashMap;
|
|||
use editor::{
|
||||
actions::{
|
||||
ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, Redo, Rename,
|
||||
RevertSelectedHunks, ToggleCodeActions, Undo,
|
||||
},
|
||||
display_map::DisplayRow,
|
||||
test::{
|
||||
editor_hunks,
|
||||
editor_test_context::{AssertionContextManager, EditorTestContext},
|
||||
expanded_hunks, expanded_hunks_background_highlights,
|
||||
ToggleCodeActions, Undo,
|
||||
},
|
||||
test::editor_test_context::{AssertionContextManager, EditorTestContext},
|
||||
Editor,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use git::diff::DiffHunkStatus;
|
||||
use gpui::{TestAppContext, UpdateGlobal, VisualContext, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
|
@ -1970,285 +1964,6 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multiple_hunk_types_revert(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_dev_server_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.into()), 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.into()), cx);
|
||||
});
|
||||
});
|
||||
cx_a.executor().run_until_parked();
|
||||
cx_b.executor().run_until_parked();
|
||||
|
||||
// the client selects a range in the updated buffer, expands it to see the diff for each hunk in the selection
|
||||
// the host does not see the diffs toggled
|
||||
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.toggle_hunk_diff(&editor::actions::ToggleHunkDiff, cx));
|
||||
cx_a.executor().run_until_parked();
|
||||
cx_b.executor().run_until_parked();
|
||||
editor_cx_a.update_editor(|editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let all_hunks = editor_hunks(editor, &snapshot, cx);
|
||||
let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
|
||||
assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
|
||||
assert_eq!(
|
||||
all_hunks,
|
||||
vec![
|
||||
(
|
||||
"".to_string(),
|
||||
DiffHunkStatus::Added,
|
||||
DisplayRow(1)..DisplayRow(3)
|
||||
),
|
||||
(
|
||||
"struct Row2;\n".to_string(),
|
||||
DiffHunkStatus::Removed,
|
||||
DisplayRow(4)..DisplayRow(4)
|
||||
),
|
||||
(
|
||||
"struct Row5;\n".to_string(),
|
||||
DiffHunkStatus::Modified,
|
||||
DisplayRow(6)..DisplayRow(7)
|
||||
),
|
||||
(
|
||||
"struct Row8;\n".to_string(),
|
||||
DiffHunkStatus::Removed,
|
||||
DisplayRow(9)..DisplayRow(9)
|
||||
),
|
||||
(
|
||||
"struct Row10;".to_string(),
|
||||
DiffHunkStatus::Modified,
|
||||
DisplayRow(10)..DisplayRow(10),
|
||||
),
|
||||
]
|
||||
);
|
||||
assert_eq!(all_expanded_hunks, Vec::new());
|
||||
});
|
||||
editor_cx_b.update_editor(|editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let all_hunks = editor_hunks(editor, &snapshot, cx);
|
||||
let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
|
||||
assert_eq!(
|
||||
expanded_hunks_background_highlights(editor, cx),
|
||||
vec![DisplayRow(1)..=DisplayRow(2), DisplayRow(8)..=DisplayRow(8)],
|
||||
);
|
||||
assert_eq!(
|
||||
all_hunks,
|
||||
vec![
|
||||
(
|
||||
"".to_string(),
|
||||
DiffHunkStatus::Added,
|
||||
DisplayRow(1)..DisplayRow(3)
|
||||
),
|
||||
(
|
||||
"struct Row2;\n".to_string(),
|
||||
DiffHunkStatus::Removed,
|
||||
DisplayRow(5)..DisplayRow(5)
|
||||
),
|
||||
(
|
||||
"struct Row5;\n".to_string(),
|
||||
DiffHunkStatus::Modified,
|
||||
DisplayRow(8)..DisplayRow(9)
|
||||
),
|
||||
(
|
||||
"struct Row8;\n".to_string(),
|
||||
DiffHunkStatus::Removed,
|
||||
DisplayRow(12)..DisplayRow(12)
|
||||
),
|
||||
(
|
||||
"struct Row10;".to_string(),
|
||||
DiffHunkStatus::Modified,
|
||||
DisplayRow(13)..DisplayRow(13),
|
||||
),
|
||||
]
|
||||
);
|
||||
assert_eq!(all_expanded_hunks, &all_hunks[..all_hunks.len() - 1]);
|
||||
});
|
||||
|
||||
// the client reverts the hunks, removing the expanded diffs too
|
||||
// both host and the client observe the reverted state (with one hunk left, not covered by client's selection)
|
||||
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.update_editor(|editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let all_hunks = editor_hunks(editor, &snapshot, cx);
|
||||
let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
|
||||
assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
|
||||
assert_eq!(
|
||||
all_hunks,
|
||||
vec![(
|
||||
"struct Row10;".to_string(),
|
||||
DiffHunkStatus::Modified,
|
||||
DisplayRow(10)..DisplayRow(10),
|
||||
)]
|
||||
);
|
||||
assert_eq!(all_expanded_hunks, Vec::new());
|
||||
});
|
||||
editor_cx_b.update_editor(|editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let all_hunks = editor_hunks(editor, &snapshot, cx);
|
||||
let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
|
||||
assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
|
||||
assert_eq!(
|
||||
all_hunks,
|
||||
vec![(
|
||||
"struct Row10;".to_string(),
|
||||
DiffHunkStatus::Modified,
|
||||
DisplayRow(10)..DisplayRow(10),
|
||||
)]
|
||||
);
|
||||
assert_eq!(all_expanded_hunks, Vec::new());
|
||||
});
|
||||
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;"#});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
|
||||
let mut server = TestServer::start(cx_a.executor()).await;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue