Select and scroll to the best search match in settings (#53916)
When adding code lens to the long list of search items, I've failed to find it when searching, as we always select first item in the matches list, which is not necessarily the best one. Left is before, right is after: https://github.com/user-attachments/assets/9033dfd8-abde-4a45-a214-ae859105d6ad Release Notes: - Improved settings search ergonomics
This commit is contained in:
1 parent
62fa5cd470
commit
d67bbaa93d
1 file changed
+71
-17
@@ -587,9 +587,9 @@ pub fn open_settings_editor(
|
||||
|
||||
settings_window.opening_link = true;
|
||||
settings_window.search_bar.update(cx, |editor, cx| {
|
||||
editor.set_text(query, window, cx);
|
||||
editor.set_text(query.clone(), window, cx);
|
||||
});
|
||||
settings_window.apply_match_indices(indices.iter().copied());
|
||||
settings_window.apply_match_indices(indices.iter().copied(), &query);
|
||||
|
||||
if indices.len() == 1
|
||||
&& let Some(search_index) = settings_window.search_index.as_ref()
|
||||
@@ -1873,7 +1873,7 @@ impl SettingsWindow {
|
||||
indices
|
||||
}
|
||||
|
||||
fn apply_match_indices(&mut self, match_indices: impl Iterator<Item = usize>) {
|
||||
fn apply_match_indices(&mut self, match_indices: impl Iterator<Item = usize>, query: &str) {
|
||||
let Some(search_index) = self.search_index.as_ref() else {
|
||||
return;
|
||||
};
|
||||
@@ -1895,8 +1895,11 @@ impl SettingsWindow {
|
||||
}
|
||||
self.has_query = true;
|
||||
self.filter_matches_to_file();
|
||||
self.open_first_nav_page();
|
||||
let query_lower = query.to_lowercase();
|
||||
let query_words: Vec<&str> = query_lower.split_whitespace().collect();
|
||||
self.open_best_matching_nav_page(&query_words);
|
||||
self.reset_list_state();
|
||||
self.scroll_content_to_best_match(&query_words);
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, cx: &mut Context<SettingsWindow>) {
|
||||
@@ -1917,7 +1920,7 @@ impl SettingsWindow {
|
||||
if is_json_link_query {
|
||||
let indices = self.filter_by_json_path(&query);
|
||||
if !indices.is_empty() {
|
||||
self.apply_match_indices(indices.into_iter());
|
||||
self.apply_match_indices(indices.into_iter(), &query);
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
@@ -1960,19 +1963,18 @@ impl SettingsWindow {
|
||||
let fuzzy_matches = fuzzy_search_task.await;
|
||||
let exact_matches = exact_match_task.await;
|
||||
|
||||
_ = this
|
||||
.update(cx, |this, cx| {
|
||||
let exact_indices = exact_matches.into_iter();
|
||||
let fuzzy_indices = fuzzy_matches
|
||||
.into_iter()
|
||||
.take_while(|fuzzy_match| fuzzy_match.score >= 0.5)
|
||||
.map(|fuzzy_match| fuzzy_match.candidate_id);
|
||||
let merged_indices = exact_indices.chain(fuzzy_indices);
|
||||
this.update(cx, |this, cx| {
|
||||
let exact_indices = exact_matches.into_iter();
|
||||
let fuzzy_indices = fuzzy_matches
|
||||
.into_iter()
|
||||
.take_while(|fuzzy_match| fuzzy_match.score >= 0.5)
|
||||
.map(|fuzzy_match| fuzzy_match.candidate_id);
|
||||
let merged_indices = exact_indices.chain(fuzzy_indices);
|
||||
|
||||
this.apply_match_indices(merged_indices);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
this.apply_match_indices(merged_indices, &query);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
|
||||
cx.background_executor().timer(Duration::from_secs(1)).await;
|
||||
telemetry::event!("Settings Searched", query = query)
|
||||
@@ -2249,6 +2251,58 @@ impl SettingsWindow {
|
||||
self.sub_page_stack.clear();
|
||||
}
|
||||
|
||||
fn open_best_matching_nav_page(&mut self, query_words: &[&str]) {
|
||||
let mut entries = self.visible_navbar_entries().peekable();
|
||||
let first_entry = entries.peek().map(|(index, _)| (0, *index));
|
||||
let best_match = entries
|
||||
.enumerate()
|
||||
.filter(|(_, (_, entry))| !entry.is_root)
|
||||
.map(|(logical_index, (index, entry))| {
|
||||
let title_lower = entry.title.to_lowercase();
|
||||
let matching_words = query_words
|
||||
.iter()
|
||||
.filter(|query_word| {
|
||||
title_lower
|
||||
.split_whitespace()
|
||||
.any(|title_word| title_word.starts_with(*query_word))
|
||||
})
|
||||
.count();
|
||||
(logical_index, index, matching_words)
|
||||
})
|
||||
.filter(|(_, _, count)| *count > 0)
|
||||
.max_by_key(|(_, _, count)| *count)
|
||||
.map(|(logical_index, index, _)| (logical_index, index));
|
||||
if let Some((logical_index, navbar_entry_index)) = best_match.or(first_entry) {
|
||||
self.open_navbar_entry_page(navbar_entry_index);
|
||||
self.navbar_scroll_handle
|
||||
.scroll_to_item(logical_index + 1, gpui::ScrollStrategy::Top);
|
||||
}
|
||||
}
|
||||
|
||||
fn scroll_content_to_best_match(&self, query_words: &[&str]) {
|
||||
let position = self
|
||||
.visible_page_items()
|
||||
.enumerate()
|
||||
.find(|(_, (_, item))| match item {
|
||||
SettingsPageItem::SectionHeader(title) => {
|
||||
let title_lower = title.to_lowercase();
|
||||
query_words.iter().all(|query_word| {
|
||||
title_lower
|
||||
.split_whitespace()
|
||||
.any(|title_word| title_word.starts_with(query_word))
|
||||
})
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
.map(|(position, _)| position);
|
||||
if let Some(position) = position {
|
||||
self.list_state.scroll_to(gpui::ListOffset {
|
||||
item_ix: position + 1,
|
||||
offset_in_item: px(0.),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn open_first_nav_page(&mut self) {
|
||||
let Some(first_navbar_entry_index) = self.visible_navbar_entries().next().map(|e| e.0)
|
||||
else {
|
||||
|
||||
Reference in new issue
Block a user