git_graph: Fix search bar Vim key handling (#55510) (cherry-pick to preview) (#55511)

Cherry-pick of #55510 to preview

----
#53609 introduced a regression where Git Graph keybindings could take
precedence over the search bar. As a result, typing characters like `j`
or `k` in the search field could move the table selection instead of
updating the search query.

This PR fixes that regression by scoping Vim table navigation bindings
away from the search bar. It also adds dedicated `tab` and `shift-tab`
handling for Git Graph focus traversal, with the search bar and graph
table participating as separate tab groups.

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)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- N/A

Co-authored-by: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com>
This commit is contained in:
zed-zippy[bot] 2026-05-03 17:59:27 +00:00 committed by GitHub
parent cd6dd882e4
commit c85e35c93c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 125 additions and 3 deletions

View file

@ -1534,4 +1534,18 @@
"ctrl-shift-backspace": "worktree_picker::DeleteWorktree",
},
},
{
"context": "GitGraph",
"bindings": {
"tab": "git_graph::FocusNextTabStop",
"shift-tab": "git_graph::FocusPreviousTabStop",
},
},
{
"context": "GitGraphSearchBar > Editor",
"bindings": {
"tab": "git_graph::FocusNextTabStop",
"shift-tab": "git_graph::FocusPreviousTabStop",
},
},
]

View file

@ -1626,4 +1626,18 @@
"escape": "notebook::EnterCommandMode",
},
},
{
"context": "GitGraph",
"bindings": {
"tab": "git_graph::FocusNextTabStop",
"shift-tab": "git_graph::FocusPreviousTabStop",
},
},
{
"context": "GitGraphSearchBar > Editor",
"bindings": {
"tab": "git_graph::FocusNextTabStop",
"shift-tab": "git_graph::FocusPreviousTabStop",
},
},
]

View file

@ -1551,4 +1551,18 @@
"escape": "notebook::EnterCommandMode",
},
},
{
"context": "GitGraph",
"bindings": {
"tab": "git_graph::FocusNextTabStop",
"shift-tab": "git_graph::FocusPreviousTabStop",
},
},
{
"context": "GitGraphSearchBar > Editor",
"bindings": {
"tab": "git_graph::FocusNextTabStop",
"shift-tab": "git_graph::FocusPreviousTabStop",
},
},
]

View file

@ -1035,6 +1035,20 @@
},
{
"context": "GitGraph",
"bindings": {
"tab": "git_graph::FocusNextTabStop",
"shift-tab": "git_graph::FocusPreviousTabStop",
},
},
{
"context": "GitGraphSearchBar > Editor",
"bindings": {
"tab": "git_graph::FocusNextTabStop",
"shift-tab": "git_graph::FocusPreviousTabStop",
},
},
{
"context": "GitGraph && !GitGraphSearchBar",
"bindings": {
"j": "vim::MenuSelectNext",
"k": "vim::MenuSelectPrevious",

View file

@ -3526,6 +3526,10 @@ impl Editor {
cx.notify();
}
pub fn show_cursor(&mut self, cx: &mut Context<Self>) {
self.blink_manager.update(cx, BlinkManager::show_cursor);
}
pub fn cursor_shape(&self) -> CursorShape {
self.cursor_shape
}

View file

@ -282,6 +282,10 @@ actions!(
OpenCommitView,
/// Focuses the search field.
FocusSearch,
/// Focuses the next git graph tab stop.
FocusNextTabStop,
/// Focuses the previous git graph tab stop.
FocusPreviousTabStop,
]
);
@ -1105,7 +1109,11 @@ impl GitGraph {
editor
});
let table_interaction_state = cx.new(|cx| TableInteractionState::new(cx));
let table_interaction_state = cx.new(|cx| {
let mut state = TableInteractionState::new(cx);
state.focus_handle = state.focus_handle.tab_index(1).tab_stop(true);
state
});
let column_widths = if matches!(log_source, LogSource::File(_)) {
cx.new(|_cx| {
@ -1597,6 +1605,39 @@ impl GitGraph {
self.search(query, cx);
}
fn activate_search_editor_if_focused(&self, window: &mut Window, cx: &mut Context<Self>) {
self.search_state.editor.update(cx, |editor, cx| {
if editor.is_focused(window) {
editor.select_all(&Default::default(), window, cx);
editor.show_cursor(cx);
}
});
}
fn focus_next_tab_stop(
&mut self,
_: &FocusNextTabStop,
window: &mut Window,
cx: &mut Context<Self>,
) {
window.focus_next(cx);
self.activate_search_editor_if_focused(window, cx);
cx.stop_propagation();
cx.notify();
}
fn focus_previous_tab_stop(
&mut self,
_: &FocusPreviousTabStop,
window: &mut Window,
cx: &mut Context<Self>,
) {
window.focus_prev(cx);
self.activate_search_editor_if_focused(window, cx);
cx.stop_propagation();
cx.notify();
}
fn select_entry(
&mut self,
idx: usize,
@ -1778,7 +1819,12 @@ impl GitGraph {
fn render_search_bar(&self, cx: &mut Context<Self>) -> impl IntoElement {
let color = cx.theme().colors();
let query_focus_handle = self.search_state.editor.focus_handle(cx);
let query_focus_handle = self
.search_state
.editor
.focus_handle(cx)
.tab_index(1)
.tab_stop(true);
let search_options = {
let mut options = SearchOptions::NONE;
options.set(
@ -1789,6 +1835,10 @@ impl GitGraph {
};
h_flex()
.key_context("GitGraphSearchBar")
.tab_index(1)
.tab_group()
.tab_stop(false)
.w_full()
.p_1p5()
.gap_1p5()
@ -1801,6 +1851,7 @@ impl GitGraph {
.min_w_0()
.px_1p5()
.gap_1()
.track_focus(&query_focus_handle)
.border_1()
.border_color(color.border_variant)
.rounded_md()
@ -2806,6 +2857,8 @@ impl Render for GitGraph {
let hovered_entry_idx = self.hovered_entry_idx;
let weak_self = cx.weak_entity();
let focus_handle = self.focus_handle.clone();
let table_focus_handle =
self.table_interaction_state.read(cx).focus_handle.clone();
let graph_canvas = div()
.id("graph-canvas")
@ -2834,7 +2887,9 @@ impl Render for GitGraph {
.map_row(move |(index, row), window, cx| {
let is_selected = selected_entry_idx == Some(index);
let is_hovered = hovered_entry_idx == Some(index);
let is_focused = focus_handle.is_focused(window);
let table_focus_handle = table_focus_handle.clone();
let is_focused = focus_handle.is_focused(window)
|| table_focus_handle.is_focused(window);
let weak = weak_self.clone();
let weak_for_hover = weak.clone();
@ -2866,6 +2921,7 @@ impl Render for GitGraph {
})
.on_click(move |event, window, cx| {
let click_count = event.click_count();
table_focus_handle.focus(window, cx);
weak.update(cx, |this, cx| {
this.select_entry(
index,
@ -2907,6 +2963,9 @@ impl Render for GitGraph {
})
.child(
div()
.tab_index(2)
.tab_group()
.tab_stop(false)
.w(DefiniteLength::Fraction(table_fraction))
.h_full()
.min_w_0()
@ -2951,12 +3010,15 @@ impl Render for GitGraph {
this.search_state
.editor
.update(cx, |editor, cx| editor.focus_handle(cx).focus(window, cx));
this.activate_search_editor_if_focused(window, cx);
}))
.on_action(cx.listener(Self::select_first))
.on_action(cx.listener(Self::select_prev))
.on_action(cx.listener(Self::select_next))
.on_action(cx.listener(Self::select_last))
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::focus_next_tab_stop))
.on_action(cx.listener(Self::focus_previous_tab_stop))
.on_action(cx.listener(|this, _: &SelectNextMatch, _window, cx| {
this.select_next_match(cx);
}))