@@ -6,7 +6,7 @@ use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};
use fuzzy::StringMatchCandidate;
use gpui::{
App, AppContext as _, Context, Div, Entity, Global, IntoElement, ReadGlobal as _, Render,
- ScrollHandle, Stateful, Task, TitlebarOptions, UniformListScrollHandle, Window, WindowHandle,
+ ScrollHandle, Task, TitlebarOptions, UniformListScrollHandle, Window, WindowHandle,
WindowOptions, actions, div, point, px, size, uniform_list,
};
use project::WorktreeId;
@@ -1358,6 +1358,39 @@ fn user_settings_data() -> Vec<SettingsPage> {
// }),
],
},
+ SettingsPage {
+ title: "Languages & Frameworks",
+ expanded: false,
+ items: vec![
+ SettingsPageItem::SectionHeader("General"),
+ SettingsPageItem::SettingItem(SettingItem {
+ title: "Enable Language Server",
+ description: "Whether to use language servers to provide code intelligence",
+ field: Box::new(SettingField {
+ pick: |settings_content| {
+ &settings_content
+ .project
+ .all_languages
+ .defaults
+ .enable_language_server
+ },
+ pick_mut: |settings_content| {
+ &mut settings_content
+ .project
+ .all_languages
+ .defaults
+ .enable_language_server
+ },
+ }),
+ metadata: None,
+ }),
+ SettingsPageItem::SectionHeader("Languages"),
+ SettingsPageItem::SubPageLink(SubPageLink {
+ title: "JSON",
+ render: Rc::new(|_, _, _| "A settings page!".into_any_element()),
+ }),
+ ],
+ },
SettingsPage {
title: "Workbench & Window",
expanded: false,
@@ -2742,6 +2775,15 @@ pub struct SettingsWindow {
navbar_entries: Vec<NavBarEntry>,
list_handle: UniformListScrollHandle,
search_matches: Vec<Vec<bool>>,
+ /// The current sub page path that is selected.
+ /// If this is empty the selected page is rendered,
+ /// otherwise the last sub page gets rendered.
+ sub_page_stack: Vec<SubPage>,
+}
+
+struct SubPage {
+ link: SubPageLink,
+ section_header: &'static str,
}
#[derive(PartialEq, Debug)]
@@ -2761,6 +2803,7 @@ struct SettingsPage {
enum SettingsPageItem {
SectionHeader(&'static str),
SettingItem(SettingItem),
+ SubPageLink(SubPageLink),
}
impl std::fmt::Debug for SettingsPageItem {
@@ -2770,6 +2813,9 @@ impl std::fmt::Debug for SettingsPageItem {
SettingsPageItem::SettingItem(setting_item) => {
write!(f, "SettingItem({})", setting_item.title)
}
+ SettingsPageItem::SubPageLink(sub_page_link) => {
+ write!(f, "SubPageLink({})", sub_page_link.title)
+ }
}
}
}
@@ -2778,9 +2824,10 @@ impl SettingsPageItem {
fn render(
&self,
file: SettingsUiFile,
+ section_header: &'static str,
is_last: bool,
window: &mut Window,
- cx: &mut App,
+ cx: &mut Context<SettingsWindow>,
) -> AnyElement {
match self {
SettingsPageItem::SectionHeader(header) => v_flex()
@@ -2850,6 +2897,36 @@ impl SettingsPageItem {
))
.into_any_element()
}
+ SettingsPageItem::SubPageLink(sub_page_link) => h_flex()
+ .id(sub_page_link.title)
+ .w_full()
+ .gap_2()
+ .flex_wrap()
+ .justify_between()
+ .when(!is_last, |this| {
+ this.pb_4()
+ .border_b_1()
+ .border_color(cx.theme().colors().border_variant)
+ })
+ .child(
+ v_flex().max_w_1_2().flex_shrink().child(
+ Label::new(SharedString::new_static(sub_page_link.title))
+ .size(LabelSize::Default),
+ ),
+ )
+ .child(
+ Button::new(("sub-page".into(), sub_page_link.title), "Configure")
+ .icon(Some(IconName::ChevronRight))
+ .icon_position(Some(IconPosition::End))
+ .style(ButtonStyle::Outlined),
+ )
+ .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(),
}
}
}
@@ -2873,6 +2950,18 @@ impl PartialEq for SettingItem {
}
}
+#[derive(Clone)]
+struct SubPageLink {
+ title: &'static str,
+ render: Rc<dyn Fn(&mut SettingsWindow, &mut Window, &mut App) -> AnyElement>,
+}
+
+impl PartialEq for SubPageLink {
+ fn eq(&self, other: &Self) -> bool {
+ self.title == other.title
+ }
+}
+
#[allow(unused)]
#[derive(Clone, PartialEq)]
enum SettingsUiFile {
@@ -2953,6 +3042,7 @@ impl SettingsWindow {
search_bar,
search_task: None,
search_matches: vec![],
+ sub_page_stack: vec![],
};
this.fetch_files(cx);
@@ -3063,6 +3153,9 @@ impl SettingsWindow {
candidates.push(StringMatchCandidate::new(key_index, header));
header_index = item_index;
}
+ SettingsPageItem::SubPageLink(sub_page_link) => {
+ candidates.push(StringMatchCandidate::new(key_index, sub_page_link.title));
+ }
}
key_lut.push(ItemKey {
page_index,
@@ -3239,23 +3332,81 @@ impl SettingsWindow {
})
}
- fn render_page(&self, window: &mut Window, cx: &mut Context<SettingsWindow>) -> Stateful<Div> {
- let items: Vec<_> = self.page_items().collect();
- let items_len = items.len();
+ fn render_sub_page_breadcrumbs(&self) -> impl IntoElement {
+ let mut items = vec![];
+ items.push(self.current_page().title);
+ items.extend(
+ self.sub_page_stack
+ .iter()
+ .flat_map(|page| [page.section_header, page.link.title]),
+ );
- v_flex()
+ let last = items.pop().unwrap();
+ h_flex()
+ .gap_1()
+ .children(
+ items
+ .into_iter()
+ .flat_map(|item| [item, "/"])
+ .map(|item| Label::new(item).color(Color::Muted)),
+ )
+ .child(Label::new(last))
+ }
+
+ fn render_page(&mut self, window: &mut Window, cx: &mut Context<SettingsWindow>) -> Div {
+ let mut page = v_flex()
+ .w_full()
+ .pt_4()
+ .px_6()
+ .gap_4()
+ .bg(cx.theme().colors().editor_background);
+ let mut page_content = v_flex()
.id("settings-ui-page")
.gap_4()
- .children(items.into_iter().enumerate().map(|(index, item)| {
- let is_last = index == items_len - 1;
- item.render(self.current_file.clone(), is_last, window, cx)
- }))
.overflow_y_scroll()
.track_scroll(
window
.use_state(cx, |_, _| ScrollHandle::default())
.read(cx),
- )
+ );
+ if self.sub_page_stack.len() == 0 {
+ page = page.child(self.render_files(window, cx));
+
+ let items: Vec<_> = self.page_items().collect();
+ let items_len = items.len();
+ let mut section_header = None;
+
+ page_content =
+ page_content.children(items.into_iter().enumerate().map(|(index, item)| {
+ let is_last = index == items_len - 1;
+ if let SettingsPageItem::SectionHeader(header) = item {
+ section_header = Some(*header);
+ }
+ item.render(
+ self.current_file.clone(),
+ section_header.expect("All items rendered after a section header"),
+ is_last,
+ window,
+ cx,
+ )
+ }))
+ } else {
+ page = page.child(
+ h_flex()
+ .gap_2()
+ .child(IconButton::new("back-btn", IconName::ChevronLeft).on_click(
+ cx.listener(|this, _, _, cx| {
+ this.pop_sub_page(cx);
+ }),
+ ))
+ .child(self.render_sub_page_breadcrumbs()),
+ );
+
+ let active_page_render_fn = self.sub_page_stack.last().unwrap().link.render.clone();
+ page_content = page_content.child((active_page_render_fn)(self, window, cx));
+ }
+
+ return page.child(page_content);
}
fn current_page_index(&self) -> usize {
@@ -3282,6 +3433,24 @@ impl SettingsWindow {
fn is_navbar_entry_selected(&self, ix: usize) -> bool {
ix == self.navbar_entry
}
+
+ fn push_sub_page(
+ &mut self,
+ sub_page_link: SubPageLink,
+ section_header: &'static str,
+ cx: &mut Context<SettingsWindow>,
+ ) {
+ self.sub_page_stack.push(SubPage {
+ link: sub_page_link,
+ section_header,
+ });
+ cx.notify();
+ }
+
+ fn pop_sub_page(&mut self, cx: &mut Context<SettingsWindow>) {
+ self.sub_page_stack.pop();
+ cx.notify();
+ }
}
impl Render for SettingsWindow {
@@ -3296,16 +3465,7 @@ impl Render for SettingsWindow {
.bg(cx.theme().colors().background)
.text_color(cx.theme().colors().text)
.child(self.render_nav(window, cx))
- .child(
- v_flex()
- .w_full()
- .pt_4()
- .px_6()
- .gap_4()
- .bg(cx.theme().colors().editor_background)
- .child(self.render_files(window, cx))
- .child(self.render_page(window, cx)),
- )
+ .child(self.render_page(window, cx))
}
}
@@ -3637,6 +3797,7 @@ mod test {
list_handle: UniformListScrollHandle::default(),
search_matches,
search_task: None,
+ sub_page_stack: vec![],
};
settings_window.build_navbar();