change settings link implementation to use search filters, prefixed by

cameron and Ben Kunkle created

`#`

Co-authored-by: Ben Kunkle <ben@zed.dev>

Change summary

crates/settings_ui/src/page_data.rs   |   2 
crates/settings_ui/src/settings_ui.rs | 100 +++++++++++++++-------------
2 files changed, 54 insertions(+), 48 deletions(-)

Detailed changes

crates/settings_ui/src/page_data.rs 🔗

@@ -4500,7 +4500,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                                     title: "Program",
                                     description: "The shell program to use.",
                                     field: Box::new(SettingField {
-                                        json_path: Some("terminal.shell.program"),
+                                        json_path: Some("terminal.shell"),  
                                         pick: |settings_content| {
                                             match settings_content.terminal.as_ref()?.project.shell.as_ref() {
                                                 Some(settings::Shell::Program(program)) => Some(program),

crates/settings_ui/src/settings_ui.rs 🔗

@@ -6,10 +6,10 @@ use editor::{Editor, EditorEvent};
 use feature_flags::FeatureFlag;
 use fuzzy::StringMatchCandidate;
 use gpui::{
-    Action, App, DEFAULT_ADDITIONAL_WINDOW_SIZE, Div, Entity, FocusHandle, Focusable, Global,
-    ListState, ReadGlobal as _, ScrollHandle, Stateful, Subscription, Task, TitlebarOptions,
-    UniformListScrollHandle, Window, WindowBounds, WindowHandle, WindowOptions, actions, div, list,
-    point, prelude::*, px, uniform_list,
+    Action, AnimationExt, App, DEFAULT_ADDITIONAL_WINDOW_SIZE, Div, Entity, FocusHandle, Focusable,
+    Global, ListState, ReadGlobal as _, ScrollHandle, Stateful, Subscription, Task,
+    TitlebarOptions, UniformListScrollHandle, Window, WindowBounds, WindowHandle, WindowOptions,
+    actions, div, list, point, prelude::*, px, uniform_list,
 };
 use heck::ToTitleCase as _;
 use project::{Project, WorktreeId};
@@ -512,43 +512,10 @@ pub fn open_settings_editor(
             return;
         }
 
-        settings_window.current_file = SettingsUiFile::User;
-        settings_window.build_ui(window, cx);
-
-        let mut item_info = None;
-        'search: for (nav_entry_index, entry) in settings_window.navbar_entries.iter().enumerate() {
-            if entry.is_root {
-                continue;
-            }
-            let page_index = entry.page_index;
-            let header_index = entry
-                .item_index
-                .expect("non-root entries should have an item index");
-            for item_index in header_index + 1..settings_window.pages[page_index].items.len() {
-                let item = &settings_window.pages[page_index].items[item_index];
-                if let SettingsPageItem::SectionHeader(_) = item {
-                    break;
-                }
-                if let SettingsPageItem::SettingItem(item) = item {
-                    if item.field.json_path() == Some(path) {
-                        if !item.files.contains(USER) {
-                            log::error!("Found item {}, but it is not a user setting", path);
-                            return;
-                        }
-                        item_info = Some((item_index, nav_entry_index));
-                        break 'search;
-                    }
-                }
-            }
-        }
-        let Some((item_index, navbar_entry_index)) = item_info else {
-            log::error!("Failed to find item for {}", path);
-            return;
-        };
-
-        settings_window.open_navbar_entry_page(navbar_entry_index);
-        window.focus(&settings_window.focus_handle_for_content_element(item_index, cx));
-        settings_window.scroll_to_content_item(item_index, window, cx);
+        settings_window.search_bar.update(cx, |editor, cx| {
+            editor.set_text(format!("#{path}"), window, cx);
+        });
+        settings_window.update_matches(cx);
     }
 
     let existing_window = cx
@@ -673,13 +640,14 @@ pub struct SettingsWindow {
 struct SearchIndex {
     bm25_engine: bm25::SearchEngine<usize>,
     fuzzy_match_candidates: Vec<StringMatchCandidate>,
-    key_lut: Vec<SearchItemKey>,
+    key_lut: Vec<SearchKeyLUTEntry>,
 }
 
-struct SearchItemKey {
+struct SearchKeyLUTEntry {
     page_index: usize,
     header_index: usize,
     item_index: usize,
+    json_path: Option<&'static str>,
 }
 
 struct SubPage {
@@ -1462,7 +1430,7 @@ impl SettingsWindow {
 
     fn update_matches(&mut self, cx: &mut Context<SettingsWindow>) {
         self.search_task.take();
-        let query = self.search_bar.read(cx).text(cx);
+        let mut query = self.search_bar.read(cx).text(cx);
         if query.is_empty() || self.search_index.is_none() {
             for page in &mut self.filter_table {
                 page.fill(true);
@@ -1474,6 +1442,14 @@ impl SettingsWindow {
             return;
         }
 
+        let is_json_link_query;
+        if query.starts_with("#") {
+            query.remove(0);
+            is_json_link_query = true;
+        } else {
+            is_json_link_query = false;
+        }
+
         let search_index = self.search_index.as_ref().unwrap().clone();
 
         fn update_matches_inner(
@@ -1487,10 +1463,11 @@ impl SettingsWindow {
             }
 
             for match_index in match_indices {
-                let SearchItemKey {
+                let SearchKeyLUTEntry {
                     page_index,
                     header_index,
                     item_index,
+                    ..
                 } = search_index.key_lut[match_index];
                 let page = &mut this.filter_table[page_index];
                 page[header_index] = true;
@@ -1504,6 +1481,29 @@ impl SettingsWindow {
         }
 
         self.search_task = Some(cx.spawn(async move |this, cx| {
+            if is_json_link_query {
+                let mut indices = vec![];
+                for (index, SearchKeyLUTEntry { json_path, .. }) in
+                    search_index.key_lut.iter().enumerate()
+                {
+                    let Some(json_path) = json_path else {
+                        continue;
+                    };
+
+                    if let Some(post) = query.strip_prefix(json_path)
+                        && (post.is_empty() || post.starts_with('.'))
+                    {
+                        indices.push(index);
+                    }
+                }
+                if !indices.is_empty() {
+                    this.update(cx, |this, cx| {
+                        update_matches_inner(this, search_index.as_ref(), indices.into_iter(), cx);
+                    })
+                    .ok();
+                    return;
+                }
+            }
             let bm25_task = cx.background_spawn({
                 let search_index = search_index.clone();
                 let max_results = search_index.key_lut.len();
@@ -1591,7 +1591,7 @@ impl SettingsWindow {
     }
 
     fn build_search_index(&mut self) {
-        let mut key_lut: Vec<SearchItemKey> = vec![];
+        let mut key_lut: Vec<SearchKeyLUTEntry> = vec![];
         let mut documents = Vec::default();
         let mut fuzzy_match_candidates = Vec::default();
 
@@ -1613,11 +1613,16 @@ impl SettingsWindow {
             let mut header_str = "";
             for (item_index, item) in page.items.iter().enumerate() {
                 let key_index = key_lut.len();
+                let mut json_path = None;
                 match item {
                     SettingsPageItem::DynamicItem(DynamicItem {
                         discriminant: item, ..
                     })
                     | SettingsPageItem::SettingItem(item) => {
+                        json_path = item
+                            .field
+                            .json_path()
+                            .map(|path| path.trim_end_matches('$'));
                         documents.push(bm25::Document {
                             id: key_index,
                             contents: [page.title, header_str, item.title, item.description]
@@ -1651,10 +1656,11 @@ impl SettingsWindow {
                 push_candidates(&mut fuzzy_match_candidates, key_index, page.title);
                 push_candidates(&mut fuzzy_match_candidates, key_index, header_str);
 
-                key_lut.push(SearchItemKey {
+                key_lut.push(SearchKeyLUTEntry {
                     page_index,
                     header_index,
                     item_index,
+                    json_path,
                 });
             }
         }