settings_ui: Dynamic languages list (#40123)

Ben Kunkle created

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

crates/settings_ui/src/page_data.rs   | 97 +++++++---------------------
crates/settings_ui/src/settings_ui.rs | 76 +++++++++++++---------
2 files changed, 69 insertions(+), 104 deletions(-)

Detailed changes

crates/settings_ui/src/page_data.rs 🔗

@@ -1,13 +1,14 @@
+use gpui::App;
 use settings::{LanguageSettingsContent, SettingsContent};
 use std::sync::Arc;
 use ui::{IntoElement, SharedString};
 
 use crate::{
     LOCAL, SettingField, SettingItem, SettingsFieldMetadata, SettingsPage, SettingsPageItem,
-    SubPageLink, USER, sub_page_stack,
+    SubPageLink, USER, all_language_names, sub_page_stack,
 };
 
-pub(crate) fn settings_data() -> Vec<SettingsPage> {
+pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
     vec![
         SettingsPage {
             title: "General",
@@ -1405,74 +1406,27 @@ pub(crate) fn settings_data() -> Vec<SettingsPage> {
         },
         SettingsPage {
             title: "Languages",
-            items: vec![
-                SettingsPageItem::SectionHeader(LANGUAGES_SECTION_HEADER),
-                SettingsPageItem::SubPageLink(SubPageLink {
-                    title: "JSON",
-                    files: USER | LOCAL,
-                    render: Arc::new(|this, window, cx| {
-                        this.render_page_items(
-                            language_settings_data().iter().enumerate(),
-                            None,
-                            window,
-                            cx,
-                        )
-                        .into_any_element()
-                    }),
-                }),
-                SettingsPageItem::SubPageLink(SubPageLink {
-                    title: "JSONC",
-                    files: USER | LOCAL,
-                    render: Arc::new(|this, window, cx| {
-                        this.render_page_items(
-                            language_settings_data().iter().enumerate(),
-                            None,
-                            window,
-                            cx,
-                        )
-                        .into_any_element()
-                    }),
-                }),
-                SettingsPageItem::SubPageLink(SubPageLink {
-                    title: "Rust",
-                    files: USER | LOCAL,
-                    render: Arc::new(|this, window, cx| {
-                        this.render_page_items(
-                            language_settings_data().iter().enumerate(),
-                            None,
-                            window,
-                            cx,
-                        )
-                        .into_any_element()
-                    }),
-                }),
-                SettingsPageItem::SubPageLink(SubPageLink {
-                    title: "Python",
-                    files: USER | LOCAL,
-                    render: Arc::new(|this, window, cx| {
-                        this.render_page_items(
-                            language_settings_data().iter().enumerate(),
-                            None,
-                            window,
-                            cx,
-                        )
-                        .into_any_element()
-                    }),
-                }),
-                SettingsPageItem::SubPageLink(SubPageLink {
-                    title: "TSX",
-                    files: USER | LOCAL,
-                    render: Arc::new(|this, window, cx| {
-                        this.render_page_items(
-                            language_settings_data().iter().enumerate(),
-                            None,
-                            window,
-                            cx,
-                        )
-                        .into_any_element()
-                    }),
-                }),
-            ],
+            items: {
+                let mut items = vec![SettingsPageItem::SectionHeader(LANGUAGES_SECTION_HEADER)];
+                // todo(settings_ui): Refresh on extension (un)/installed
+                // Note that `crates/json_schema_store` solves the same problem, there is probably a way to unify the two
+                items.extend(all_language_names(cx).into_iter().map(|language_name| {
+                    SettingsPageItem::SubPageLink(SubPageLink {
+                        title: language_name,
+                        files: USER | LOCAL,
+                        render: Arc::new(|this, window, cx| {
+                            this.render_page_items(
+                                language_settings_data().iter().enumerate(),
+                                None,
+                                window,
+                                cx,
+                            )
+                            .into_any_element()
+                        }),
+                    })
+                }));
+                items
+            },
         },
         SettingsPage {
             title: "Search & Files",
@@ -4678,8 +4632,7 @@ const LANGUAGES_SECTION_HEADER: &'static str = "Languages";
 fn language_settings_data() -> Vec<SettingsPageItem> {
     fn current_language() -> Option<SharedString> {
         sub_page_stack().iter().find_map(|page| {
-            (page.section_header == LANGUAGES_SECTION_HEADER)
-                .then(|| SharedString::new_static(page.link.title))
+            (page.section_header == LANGUAGES_SECTION_HEADER).then(|| page.link.title.clone())
         })
     }
 

crates/settings_ui/src/settings_ui.rs 🔗

@@ -608,7 +608,7 @@ impl SettingsPageItem {
                     .into_any_element()
             }
             SettingsPageItem::SubPageLink(sub_page_link) => h_flex()
-                .id(sub_page_link.title)
+                .id(sub_page_link.title.clone())
                 .w_full()
                 .min_w_0()
                 .gap_2()
@@ -623,24 +623,27 @@ impl SettingsPageItem {
                     v_flex()
                         .w_full()
                         .max_w_1_2()
-                        .child(Label::new(SharedString::new_static(sub_page_link.title))),
+                        .child(Label::new(sub_page_link.title.clone())),
                 )
                 .child(
-                    Button::new(("sub-page".into(), sub_page_link.title), "Configure")
-                        .icon(IconName::ChevronRight)
-                        .tab_index(0_isize)
-                        .icon_position(IconPosition::End)
-                        .icon_color(Color::Muted)
-                        .icon_size(IconSize::Small)
-                        .style(ButtonStyle::Outlined)
-                        .size(ButtonSize::Medium),
+                    Button::new(
+                        ("sub-page".into(), sub_page_link.title.clone()),
+                        "Configure",
+                    )
+                    .icon(IconName::ChevronRight)
+                    .tab_index(0_isize)
+                    .icon_position(IconPosition::End)
+                    .icon_color(Color::Muted)
+                    .icon_size(IconSize::Small)
+                    .style(ButtonStyle::Outlined)
+                    .size(ButtonSize::Medium)
+                    .on_click({
+                        let sub_page_link = sub_page_link.clone();
+                        cx.listener(move |this, _, _, cx| {
+                            this.push_sub_page(sub_page_link.clone(), section_header, cx)
+                        })
+                    }),
                 )
-                .on_click({
-                    let sub_page_link = sub_page_link.clone();
-                    cx.listener(move |this, _, _, cx| {
-                        this.push_sub_page(sub_page_link.clone(), section_header, cx)
-                    })
-                })
                 .into_any_element(),
         }
     }
@@ -765,7 +768,7 @@ impl PartialEq for SettingItem {
 
 #[derive(Clone)]
 struct SubPageLink {
-    title: &'static str,
+    title: SharedString,
     files: FileMask,
     render: Arc<
         dyn Fn(&mut SettingsWindow, &mut Window, &mut Context<SettingsWindow>) -> AnyElement
@@ -781,6 +784,20 @@ impl PartialEq for SubPageLink {
     }
 }
 
+fn all_language_names(cx: &App) -> Vec<SharedString> {
+    workspace::AppState::global(cx)
+        .upgrade()
+        .map_or(vec![], |state| {
+            state
+                .languages
+                .language_names()
+                .into_iter()
+                .filter(|name| name.as_ref() != "Zed Keybind Context")
+                .map(Into::into)
+                .collect()
+        })
+}
+
 #[allow(unused)]
 #[derive(Clone, PartialEq)]
 enum SettingsUiFile {
@@ -1049,15 +1066,9 @@ impl SettingsWindow {
                         header_index = index;
                         any_found_since_last_header = false;
                     }
-                    SettingsPageItem::SettingItem(setting_item) => {
-                        if !setting_item.files.contains(current_file) {
-                            page_filter[index] = false;
-                        } else {
-                            any_found_since_last_header = true;
-                        }
-                    }
-                    SettingsPageItem::SubPageLink(sub_page_link) => {
-                        if !sub_page_link.files.contains(current_file) {
+                    SettingsPageItem::SettingItem(SettingItem { files, .. })
+                    | SettingsPageItem::SubPageLink(SubPageLink { files, .. }) => {
+                        if !files.contains(current_file) {
                             page_filter[index] = false;
                         } else {
                             any_found_since_last_header = true;
@@ -1246,12 +1257,13 @@ impl SettingsWindow {
                     SettingsPageItem::SubPageLink(sub_page_link) => {
                         documents.push(bm25::Document {
                             id: key_index,
-                            contents: [page.title, header_str, sub_page_link.title].join("\n"),
+                            contents: [page.title, header_str, sub_page_link.title.as_ref()]
+                                .join("\n"),
                         });
                         push_candidates(
                             &mut fuzzy_match_candidates,
                             key_index,
-                            sub_page_link.title,
+                            sub_page_link.title.as_ref(),
                         );
                     }
                 }
@@ -1288,7 +1300,7 @@ impl SettingsWindow {
 
     fn build_ui(&mut self, window: &mut Window, cx: &mut Context<SettingsWindow>) {
         if self.pages.is_empty() {
-            self.pages = page_data::settings_data();
+            self.pages = page_data::settings_data(cx);
             self.build_navbar(cx);
             self.setup_navbar_focus_subscriptions(window, cx);
             self.build_content_handles(window, cx);
@@ -1809,11 +1821,11 @@ impl SettingsWindow {
 
     fn render_sub_page_breadcrumbs(&self) -> impl IntoElement {
         let mut items = vec![];
-        items.push(self.current_page().title);
+        items.push(self.current_page().title.into());
         items.extend(
             sub_page_stack()
                 .iter()
-                .flat_map(|page| [page.section_header, page.link.title]),
+                .flat_map(|page| [page.section_header.into(), page.link.title.clone()]),
         );
 
         let last = items.pop().unwrap();
@@ -1822,7 +1834,7 @@ impl SettingsWindow {
             .children(
                 items
                     .into_iter()
-                    .flat_map(|item| [item, "/"])
+                    .flat_map(|item| [item, "/".into()])
                     .map(|item| Label::new(item).color(Color::Muted)),
             )
             .child(Label::new(last))