From 7555272dbdfe5f2cdef7f80a9b47efc94f655031 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Mon, 28 Sep 2026 19:04:34 +0800 Subject: [PATCH] fix(search): Go to File stands alone like the editor's pickers Go to File (cmd-O) is reached only by its chord, yet it sat under the scope row with nothing lit and offered Tab to leave for the terminals. Every tab outside the row now stands alone the same way: its name in place of the row, Tab stays put, no scope hint in the footer. --- src/ui/search/mod.rs | 29 ++++++++++++++++++----------- src/ui/search/view.rs | 34 ++++++++++++++++++++++------------ 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/src/ui/search/mod.rs b/src/ui/search/mod.rs index e247bfe8..dc7d6eae 100644 --- a/src/ui/search/mod.rs +++ b/src/ui/search/mod.rs @@ -72,15 +72,13 @@ impl SearchTab { }) } - /// The editor's own pickers — Go to Symbol, a language server's places, - /// Go to Symbol in Workspace. Each answers one question about the code in - /// front, so it stands alone: no scope row to wander off along, and Tab - /// does not trade the list for the terminals. - pub(crate) fn is_editor_picker(self) -> bool { - matches!( - self, - SearchTab::Symbols | SearchTab::Locations | SearchTab::WorkspaceSymbols - ) + /// A tab reached only by its own chord — Go to File, Go to Symbol, a + /// language server's places, Go to Symbol in Workspace. Each answers one + /// question, so it stands alone: its name where the scope row would be, + /// none of the row's tabs lit, and Tab does not trade the list for the + /// terminals. + pub(crate) fn stands_alone(self) -> bool { + !Self::ORDER.contains(&self) } pub(crate) fn placeholder(self) -> &'static str { @@ -126,8 +124,17 @@ mod tests { #[test] fn files_is_reached_by_its_chord_not_by_the_row() { assert!(!SearchTab::ORDER.contains(&SearchTab::Files)); - // Tab out of Go to File lands on the row again. - assert_eq!(SearchTab::Files.step(true), SearchTab::Terminals); assert!(!SearchTab::ORDER.contains(&SearchTab::Symbols)); + // Reached by a chord, they stand alone rather than sit under a row + // with nothing in it lit. + for tab in [ + SearchTab::Files, + SearchTab::Symbols, + SearchTab::Locations, + SearchTab::WorkspaceSymbols, + ] { + assert!(tab.stands_alone()); + } + assert!(SearchTab::ORDER.iter().all(|tab| !tab.stands_alone())); } } diff --git a/src/ui/search/view.rs b/src/ui/search/view.rs index 1d263a37..db05b9a7 100644 --- a/src/ui/search/view.rs +++ b/src/ui/search/view.rs @@ -80,7 +80,7 @@ impl SearchDelegate { }) } - /// How many rows the filter left, for an editor picker's heading. + /// How many rows the filter left, for a standalone tab's heading. fn row_count(&self) -> usize { self.sections .iter() @@ -434,7 +434,8 @@ pub struct SearchView { symbol_initial: Option, symbol_moved: bool, /// What a language server's list is of — References, Definitions — shown - /// where the scope row would be. The other pickers are named by their tab. + /// where the scope row would be. The other standalone tabs are named by + /// their title. heading: Option<&'static str>, _sub: Subscription, } @@ -663,7 +664,7 @@ impl SearchView { } fn step_tab(&mut self, forward: bool, window: &mut Window, cx: &mut Context) { - if self.tab.is_editor_picker() { + if self.tab.stands_alone() { return; } self.set_tab(self.tab.step(forward), None, window, cx); @@ -850,8 +851,17 @@ impl SearchView { .bg(theme.popover) .border_b_1() .border_color(theme.border) - .when(self.tab.is_editor_picker(), |row| { - let count = self.list.read(cx).delegate().row_count(); + .when(self.tab.stands_alone(), |row| { + // Go to File's rows are a capped sample of the project, so a + // count there would read as the size of something it is not. + let count = match self.tab { + SearchTab::Files => 0, + _ => self.list.read(cx).delegate().row_count(), + }; + let heading = match self.tab { + SearchTab::Locations => self.heading.unwrap_or_else(|| self.tab.title()), + _ => self.tab.title(), + }; row.child( div() .h(px(24.)) @@ -863,7 +873,7 @@ impl SearchView { .text_size(rems(SCOPE_TEXT)) .text_color(fg) .font_weight(FontWeight::MEDIUM) - .child(self.heading.unwrap_or_else(|| self.tab.title())), + .child(heading), ) .when(count > 0, |row| { row.child( @@ -875,7 +885,7 @@ impl SearchView { ) }) }) - .when(!self.tab.is_editor_picker(), |row| { + .when(!self.tab.stands_alone(), |row| { row.children(SearchTab::ORDER.into_iter().enumerate().map(|(i, tab)| { let active = tab == self.tab; div() @@ -916,7 +926,7 @@ impl SearchView { .children(keys) .child(label) }; - let tabs = !self.in_sub_list() && !self.tab.is_editor_picker(); + let tabs = !self.in_sub_list() && !self.tab.stands_alone(); h_flex() .flex_none() .items_center() @@ -1335,14 +1345,14 @@ mod tests { assert!(app.read_with(&vcx, |app, _| app.search.is_none())); } - /// An editor picker stands alone: Tab does not swap the places a - /// language server found for the terminals, and the footer does not - /// offer it. + /// A tab reached by its own chord stands alone: Tab does not swap Go to + /// File or the places a language server found for the terminals. #[gpui::test] - fn an_editor_picker_keeps_its_list_on_tab(cx: &mut TestAppContext) { + fn a_standalone_tab_keeps_its_list_on_tab(cx: &mut TestAppContext) { let (app, mut vcx, _streams) = harness_with_tabs(cx, 1); crate::ui::i18n::set_locale("en"); for tab in [ + SearchTab::Files, SearchTab::Symbols, SearchTab::Locations, SearchTab::WorkspaceSymbols,