git_graph: Wire up Vim mode navigation (#53609)

Added Vim mode navigation (`j`, `k`, `gg`, `G`) to the Git Graph view.


[gitgraph-vim.webm](https://github.com/user-attachments/assets/b2dd31a5-deb0-48ab-a48d-8721ee500dad)

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Closes #53525 

Release Notes:

- Added vim mode navigation to git graph

---------

Co-authored-by: Anthony Eid <anthony@zed.dev>
This commit is contained in:
Nihal Kumar 2026-04-27 16:44:33 +05:30 committed by GitHub
parent eced4eab77
commit fdbdb1707d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 149 additions and 0 deletions

View file

@ -1033,6 +1033,15 @@
"enter": "menu::Cancel", "enter": "menu::Cancel",
}, },
}, },
{
"context": "GitGraph",
"bindings": {
"j": "vim::MenuSelectNext",
"k": "vim::MenuSelectPrevious",
"shift-g": "menu::SelectLast",
"g g": "menu::SelectFirst"
}
},
{ {
"context": "GitPanel && ChangesList && !GitBranchSelector", "context": "GitPanel && ChangesList && !GitBranchSelector",
"use_key_equivalents": true, "use_key_equivalents": true,

View file

@ -5072,4 +5072,144 @@ mod tests {
); );
}); });
} }
#[gpui::test]
async fn test_git_graph_navigation(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
Path::new("/project"),
serde_json::json!({
".git": {},
"file.txt": "content",
}),
)
.await;
let mut rng = StdRng::seed_from_u64(42);
let commits = generate_random_commit_dag(&mut rng, 10, false);
fs.set_graph_commits(Path::new("/project/.git"), commits);
let project = Project::test(fs.clone(), [Path::new("/project")], cx).await;
cx.run_until_parked();
let repository = project.read_with(cx, |project, cx| {
project
.active_repository(cx)
.expect("should have a repository")
});
let (multi_workspace, cx) = cx.add_window_view(|window, cx| {
workspace::MultiWorkspace::test_new(project.clone(), window, cx)
});
let workspace_weak =
multi_workspace.read_with(&*cx, |multi, _| multi.workspace().downgrade());
let git_graph = cx.new_window_entity(|window, cx| {
GitGraph::new(
repository.read(cx).id,
project.read(cx).git_store().clone(),
workspace_weak,
None,
window,
cx,
)
});
cx.run_until_parked();
git_graph.update_in(cx, |graph, window, cx| {
graph.focus_handle(cx).focus(window, cx);
});
cx.run_until_parked();
cx.draw(
point(px(0.), px(0.)),
gpui::size(px(1200.), px(800.)),
|_, _| git_graph.clone().into_any_element(),
);
cx.run_until_parked();
git_graph.read_with(&*cx, |graph, _| {
assert_eq!(graph.graph_data.commits.len(), 10);
});
git_graph.read_with(&*cx, |graph, _| {
assert_eq!(graph.selected_entry_idx, None);
});
git_graph.update_in(cx, |graph, window, cx| {
graph.select_first(&menu::SelectFirst, window, cx);
});
cx.run_until_parked();
git_graph.read_with(&*cx, |graph, _| {
assert_eq!(graph.selected_entry_idx, Some(0));
});
git_graph.update_in(cx, |graph, window, cx| {
graph.select_next(&menu::SelectNext, window, cx);
});
cx.run_until_parked();
git_graph.read_with(&*cx, |graph, _| {
assert_eq!(graph.selected_entry_idx, Some(1));
});
git_graph.update_in(cx, |graph, window, cx| {
graph.select_prev(&menu::SelectPrevious, window, cx);
});
cx.run_until_parked();
git_graph.read_with(&*cx, |graph, _| {
assert_eq!(graph.selected_entry_idx, Some(0));
});
git_graph.update_in(cx, |graph, window, cx| {
graph.select_last(&menu::SelectLast, window, cx);
});
cx.run_until_parked();
git_graph.read_with(&*cx, |graph, _| {
assert_eq!(graph.selected_entry_idx, Some(9));
});
git_graph.update_in(cx, |graph, window, cx| {
graph.select_next(&menu::SelectNext, window, cx);
});
cx.run_until_parked();
git_graph.read_with(&*cx, |graph, _| {
assert_eq!(graph.selected_entry_idx, Some(9));
});
git_graph.update_in(cx, |graph, window, cx| {
graph.select_prev(&menu::SelectPrevious, window, cx);
});
cx.run_until_parked();
git_graph.read_with(&*cx, |graph, _| {
assert_eq!(graph.selected_entry_idx, Some(8));
});
git_graph.update(cx, |graph, cx| {
graph.selected_entry_idx = None;
cx.notify();
});
cx.run_until_parked();
git_graph.update_in(cx, |graph, window, cx| {
graph.select_prev(&menu::SelectPrevious, window, cx);
});
cx.run_until_parked();
git_graph.read_with(&*cx, |graph, _| {
assert_eq!(graph.selected_entry_idx, Some(0));
});
git_graph.update(cx, |graph, cx| {
graph.selected_entry_idx = None;
cx.notify();
});
cx.run_until_parked();
git_graph.update_in(cx, |graph, window, cx| {
graph.select_next(&menu::SelectNext, window, cx);
});
cx.run_until_parked();
git_graph.read_with(&*cx, |graph, _| {
assert_eq!(graph.selected_entry_idx, Some(0));
});
}
} }