git_graph: Add basic keyboard navigation (#49051)

This PR adds basic support for keyboard navigation for the git graph
panel.

**Back and forward**:


https://github.com/user-attachments/assets/5015b0d9-bf83-4944-8c9b-1c5b9badfdb4

**Scrolling**:


https://github.com/user-attachments/assets/451badb5-59df-48a2-aa73-be5188d28dae

- [x] Tests or screenshots needed?
- [x] Code Reviewed
- [x] Manual QA
- Do we need to add keybinds for it, or is falling back to the default
keybindings enough?
 
 **TODO**:
 - [x] Add auto scroll when you select the last visible item

Release Notes:

- N/A (no release notes since its behind a feature flag)
This commit is contained in:
Remco Smits 2026-02-12 21:29:23 +01:00 committed by GitHub
parent 25b1377d1d
commit 45cd96182f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 29 additions and 2 deletions

1
Cargo.lock generated
View file

@ -7221,6 +7221,7 @@ dependencies = [
"git",
"git_ui",
"gpui",
"menu",
"project",
"rand 0.9.2",
"recent_projects",

View file

@ -26,6 +26,7 @@ feature_flags.workspace = true
git.workspace = true
git_ui.workspace = true
gpui.workspace = true
menu.workspace = true
project.workspace = true
settings.workspace = true
smallvec.workspace = true

View file

@ -9,9 +9,10 @@ use git_ui::commit_tooltip::CommitAvatar;
use gpui::{
AnyElement, App, Bounds, ClipboardItem, Context, Corner, DefiniteLength, ElementId, Entity,
EventEmitter, FocusHandle, Focusable, FontWeight, Hsla, InteractiveElement, ParentElement,
PathBuilder, Pixels, Point, Render, ScrollWheelEvent, SharedString, Styled, Subscription, Task,
WeakEntity, Window, actions, anchored, deferred, point, px,
PathBuilder, Pixels, Point, Render, ScrollStrategy, ScrollWheelEvent, SharedString, Styled,
Subscription, Task, WeakEntity, Window, actions, anchored, deferred, point, px,
};
use menu::{SelectNext, SelectPrevious};
use project::{
Project,
git_store::{CommitDataState, GitStoreEvent, Repository, RepositoryEvent},
@ -844,6 +845,22 @@ impl GitGraph {
.collect()
}
fn select_prev(&mut self, _: &SelectPrevious, _window: &mut Window, cx: &mut Context<Self>) {
if let Some(selected_entry_idx) = &self.selected_entry_idx {
self.select_entry(selected_entry_idx.saturating_sub(1), cx);
} else {
self.select_entry(0, cx);
}
}
fn select_next(&mut self, _: &SelectNext, window: &mut Window, cx: &mut Context<Self>) {
if let Some(selected_entry_idx) = &self.selected_entry_idx {
self.select_entry(selected_entry_idx.saturating_add(1), cx);
} else {
self.select_prev(&SelectPrevious, window, cx);
}
}
fn select_entry(&mut self, idx: usize, cx: &mut Context<Self>) {
if self.selected_entry_idx == Some(idx) {
return;
@ -851,6 +868,12 @@ impl GitGraph {
self.selected_entry_idx = Some(idx);
self.selected_commit_diff = None;
self.table_interaction_state.update(cx, |state, cx| {
state
.scroll_handle
.scroll_to_item(idx, ScrollStrategy::Nearest);
cx.notify();
});
let Some(commit) = self.graph_data.commits.get(idx) else {
return;
@ -1604,6 +1627,8 @@ impl Render for GitGraph {
.bg(cx.theme().colors().editor_background)
.key_context("GitGraph")
.track_focus(&self.focus_handle)
.on_action(cx.listener(Self::select_prev))
.on_action(cx.listener(Self::select_next))
.child(content)
.children(self.context_menu.as_ref().map(|(menu, position, _)| {
deferred(