file_finder: Add skip_focus_for_active_in_search setting (#27624)

Closes #27073

Currently, when searching for a file with Ctrl+P, and the first file
found is the active one, file_finder skips focus to the second file
automatically. This PR adds a setting to disable this and make the first
file always the focused one.

Default setting is still skipping the active file.

Release Notes: 

- Added the `skip_focus_for_active_in_search` setting for the file
finder, which allows turning off the default behavior of skipping focus
on the active file while searching in the file finder.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This commit is contained in:
Patrick 2025-04-30 14:28:33 -03:00 committed by GitHub
parent d03d8ccec1
commit 84e4891d54
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 107 additions and 9 deletions

View file

@ -822,7 +822,6 @@ impl FileFinderDelegate {
did_cancel: bool,
query: FileSearchQuery,
matches: impl IntoIterator<Item = ProjectPanelOrdMatch>,
cx: &mut Context<Picker<Self>>,
) {
if search_id >= self.latest_search_id {
@ -849,7 +848,7 @@ impl FileFinderDelegate {
);
self.selected_index = selected_match.map_or_else(
|| self.calculate_selected_index(),
|| self.calculate_selected_index(cx),
|m| {
self.matches
.position(&m, self.currently_opened_path.as_ref())
@ -1092,12 +1091,14 @@ impl FileFinderDelegate {
}
/// Skips first history match (that is displayed topmost) if it's currently opened.
fn calculate_selected_index(&self) -> usize {
if let Some(Match::History { path, .. }) = self.matches.get(0) {
if Some(path) == self.currently_opened_path.as_ref() {
let elements_after_first = self.matches.len() - 1;
if elements_after_first > 0 {
return 1;
fn calculate_selected_index(&self, cx: &mut Context<Picker<Self>>) -> usize {
if FileFinderSettings::get_global(cx).skip_focus_for_active_in_search {
if let Some(Match::History { path, .. }) = self.matches.get(0) {
if Some(path) == self.currently_opened_path.as_ref() {
let elements_after_first = self.matches.len() - 1;
if elements_after_first > 0 {
return 1;
}
}
}
}

View file

@ -7,6 +7,7 @@ use settings::{Settings, SettingsSources};
pub struct FileFinderSettings {
pub file_icons: bool,
pub modal_max_width: Option<FileFinderWidth>,
pub skip_focus_for_active_in_search: bool,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
@ -19,6 +20,10 @@ pub struct FileFinderSettingsContent {
///
/// Default: small
pub modal_max_width: Option<FileFinderWidth>,
/// Determines whether the file finder should skip focus for the active file in search results.
///
/// Default: true
pub skip_focus_for_active_in_search: Option<bool>,
}
impl Settings for FileFinderSettings {

View file

@ -1359,6 +1359,73 @@ async fn test_keep_opened_file_on_top_of_search_results_and_select_next_one(
});
}
#[gpui::test]
async fn test_setting_auto_select_first_and_select_active_file(cx: &mut TestAppContext) {
let app_state = init_test(cx);
cx.update(|cx| {
let settings = *FileFinderSettings::get_global(cx);
FileFinderSettings::override_global(
FileFinderSettings {
skip_focus_for_active_in_search: false,
..settings
},
cx,
);
});
app_state
.fs
.as_fake()
.insert_tree(
path!("/src"),
json!({
"test": {
"bar.rs": "// Bar file",
"lib.rs": "// Lib file",
"maaa.rs": "// Maaaaaaa",
"main.rs": "// Main file",
"moo.rs": "// Moooooo",
}
}),
)
.await;
let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
open_close_queried_buffer("bar", 1, "bar.rs", &workspace, cx).await;
open_close_queried_buffer("lib", 1, "lib.rs", &workspace, cx).await;
open_queried_buffer("main", 1, "main.rs", &workspace, cx).await;
// main.rs is on top, previously used is selected
let picker = open_file_picker(&workspace, cx);
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 3);
assert_match_selection(finder, 0, "main.rs");
assert_match_at_position(finder, 1, "lib.rs");
assert_match_at_position(finder, 2, "bar.rs");
});
// all files match, main.rs is on top, and is selected
picker
.update_in(cx, |finder, window, cx| {
finder
.delegate
.update_matches(".rs".to_string(), window, cx)
})
.await;
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 5);
assert_match_selection(finder, 0, "main.rs");
assert_match_at_position(finder, 1, "bar.rs");
assert_match_at_position(finder, 2, "lib.rs");
assert_match_at_position(finder, 3, "moo.rs");
assert_match_at_position(finder, 4, "maaa.rs");
});
}
#[gpui::test]
async fn test_non_separate_history_items(cx: &mut TestAppContext) {
let app_state = init_test(cx);