From bd13c90accf7813dc6f2ce079c611b322ac232cd Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Mon, 13 Oct 2025 12:47:14 -0500 Subject: [PATCH] settings_ui: Dynamic languages list (#40123) Closes #ISSUE Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/settings_ui/src/page_data.rs | 97 +++++++-------------------- crates/settings_ui/src/settings_ui.rs | 76 ++++++++++++--------- 2 files changed, 69 insertions(+), 104 deletions(-) diff --git a/crates/settings_ui/src/page_data.rs b/crates/settings_ui/src/page_data.rs index 912f5e18b8e7c9b44e0c22bbc087639f4d7a4361..e7c8cdb6e7eef1e9d05774b6cba5de49c0d9b4b6 100644 --- a/crates/settings_ui/src/page_data.rs +++ b/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 { +pub(crate) fn settings_data(cx: &App) -> Vec { vec![ SettingsPage { title: "General", @@ -1405,74 +1406,27 @@ pub(crate) fn settings_data() -> Vec { }, 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 { fn current_language() -> Option { 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()) }) } diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 431b88f812f4114b0b20d1a2b333366ff792d7b8..bf761b0ca3fa71612b18f0839788f025e64eb6ee 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/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) -> AnyElement @@ -781,6 +784,20 @@ impl PartialEq for SubPageLink { } } +fn all_language_names(cx: &App) -> Vec { + 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) { 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))