diff --git a/Cargo.lock b/Cargo.lock index 88150a29310eccb928e85949530d0adc2cae97c3..239323517c01045b3ddb0d23b1d99f0904d137a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14906,6 +14906,7 @@ version = "0.1.0" dependencies = [ "anyhow", "command_palette_hooks", + "debugger_ui", "editor", "feature_flags", "gpui", diff --git a/crates/settings/src/settings_ui_core.rs b/crates/settings/src/settings_ui_core.rs index 8ab744f5a8244057e0b87a66cc3e4c7dcf02527f..9086d3c7454465e8abcaf2d30d01a4f928e4ddef 100644 --- a/crates/settings/src/settings_ui_core.rs +++ b/crates/settings/src/settings_ui_core.rs @@ -27,6 +27,7 @@ pub struct SettingsUiEntry { /// The path in the settings JSON file for this setting. Relative to parent /// None implies `#[serde(flatten)]` or `Settings::KEY.is_none()` for top level settings pub path: Option<&'static str>, + /// What is displayed for the text for this entry pub title: &'static str, pub item: SettingsUiItem, } @@ -95,7 +96,7 @@ impl SettingsValue { pub struct SettingsUiItemDynamic { pub options: Vec, - pub determine_option: fn(&serde_json::Value, &mut App) -> usize, + pub determine_option: fn(&serde_json::Value, &App) -> usize, } pub struct SettingsUiItemGroup { diff --git a/crates/settings_ui/Cargo.toml b/crates/settings_ui/Cargo.toml index 7c2b81aee0ecf48afb7131adf5ddb19a165ca351..3ecef880d2bb551c79df67aefa420796829c68ef 100644 --- a/crates/settings_ui/Cargo.toml +++ b/crates/settings_ui/Cargo.toml @@ -13,6 +13,7 @@ path = "src/settings_ui.rs" [features] default = [] +test-support = [] [dependencies] anyhow.workspace = true @@ -29,6 +30,10 @@ ui.workspace = true workspace.workspace = true workspace-hack.workspace = true + +[dev-dependencies] +debugger_ui.workspace = true + # Uncomment other workspace dependencies as needed # assistant.workspace = true # client.workspace = true diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 80e82a304965e4ddb7eb37306fb5345f5552e6c7..f316a318785c7f56d465c2d39e6b6ea9bbbd1bfa 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -151,7 +151,9 @@ struct UiEntry { next_sibling: Option, // expanded: bool, render: Option, - select_descendant: Option usize>, + /// For dynamic items this is a way to select a value from a list of values + /// this is always none for non-dynamic items + select_descendant: Option usize>, } impl UiEntry { @@ -177,7 +179,7 @@ impl UiEntry { } } -struct SettingsUiTree { +pub struct SettingsUiTree { root_entry_indices: Vec, entries: Vec, active_entry_index: usize, @@ -242,7 +244,7 @@ fn build_tree_item( } impl SettingsUiTree { - fn new(cx: &App) -> Self { + pub fn new(cx: &App) -> Self { let settings_store = SettingsStore::global(cx); let mut tree = vec![]; let mut root_entry_indices = vec![]; @@ -269,6 +271,62 @@ impl SettingsUiTree { active_entry_index, } } + + // todo(settings_ui): Make sure `Item::None` paths are added to the paths tree, + // so that we can keep none/skip and still test in CI that all settings have + #[cfg(feature = "test-support")] + pub fn all_paths(&self, cx: &App) -> Vec> { + fn all_paths_rec( + tree: &[UiEntry], + paths: &mut Vec>, + current_path: &mut Vec<&'static str>, + idx: usize, + cx: &App, + ) { + let child = &tree[idx]; + let mut pushed_path = false; + if let Some(path) = child.path.as_ref() { + current_path.push(path); + paths.push(current_path.clone()); + pushed_path = true; + } + // todo(settings_ui): handle dynamic nodes here + let selected_descendant_index = child + .select_descendant + .map(|select_descendant| { + read_settings_value_from_path( + SettingsStore::global(cx).raw_default_settings(), + ¤t_path, + ) + .map(|value| select_descendant(value, cx)) + }) + .and_then(|selected_descendant_index| { + selected_descendant_index.map(|index| child.nth_descendant_index(tree, index)) + }); + + if let Some(selected_descendant_index) = selected_descendant_index { + // just silently fail if we didn't find a setting value for the path + if let Some(descendant_index) = selected_descendant_index { + all_paths_rec(tree, paths, current_path, descendant_index, cx); + } + } else if let Some(desc_idx) = child.first_descendant_index() { + let mut desc_idx = Some(desc_idx); + while let Some(descendant_index) = desc_idx { + all_paths_rec(&tree, paths, current_path, descendant_index, cx); + desc_idx = tree[descendant_index].next_sibling; + } + } + if pushed_path { + current_path.pop(); + } + } + + let mut paths = Vec::new(); + for &index in &self.root_entry_indices { + all_paths_rec(&self.entries, &mut paths, &mut Vec::new(), index, cx); + } + paths + } } fn render_nav(tree: &SettingsUiTree, _window: &mut Window, cx: &mut Context) -> Div { @@ -444,9 +502,9 @@ fn render_item_single( } } -fn read_settings_value_from_path<'a>( +pub fn read_settings_value_from_path<'a>( settings_contents: &'a serde_json::Value, - path: &[&'static str], + path: &[&str], ) -> Option<&'a serde_json::Value> { // todo(settings_ui) make non recursive, and move to `settings` alongside SettingsValue, and add method to SettingsValue to get nested let Some((key, remaining)) = path.split_first() else { diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index f82f544acc9bbd6f42b0017d84f27cf793b64799..9aceec1fbebefd2cd87f83a8546064f376d198b1 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -188,6 +188,7 @@ itertools.workspace = true language = { workspace = true, features = ["test-support"] } pretty_assertions.workspace = true project = { workspace = true, features = ["test-support"] } +settings_ui = { workspace = true, features = ["test-support"] } terminal_view = { workspace = true, features = ["test-support"] } tree-sitter-md.workspace = true tree-sitter-rust.workspace = true diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index d0e4687a132a85645cdbfe52e67ebb6afd894c0e..96f0f261dcce9268976f92ec028f0581fb648913 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -4855,4 +4855,34 @@ mod tests { "BUG FOUND: Project settings were overwritten when opening via command - original custom content was lost" ); } + + #[gpui::test] + fn test_settings_defaults(cx: &mut TestAppContext) { + cx.update(|cx| { + settings::init(cx); + workspace::init_settings(cx); + title_bar::init(cx); + editor::init_settings(cx); + debugger_ui::init(cx); + }); + let default_json = + cx.read(|cx| cx.global::().raw_default_settings().clone()); + + let all_paths = cx.read(|cx| settings_ui::SettingsUiTree::new(cx).all_paths(cx)); + let mut failures = Vec::new(); + for path in all_paths { + if settings_ui::read_settings_value_from_path(&default_json, &path).is_none() { + failures.push(path); + } + } + if !failures.is_empty() { + panic!( + "No default value found for paths: {:#?}", + failures + .into_iter() + .map(|path| path.join(".")) + .collect::>() + ); + } + } }