| @@ -222,6 +222,81 @@ struct SettingFieldRenderer { · 221 unmodified lines |
| 222 | 222 | |
| 223 | 223 | impl Global for SettingFieldRenderer {} |
| 224 | 224 | |
| 225 | +#[derive(Default, Clone)] |
| 226 | +struct SettingsJsonKeyFinder { |
| 227 | + key_finders: Rc<RefCell<HashMap<TypeId, Box<dyn Fn(SettingsContent) -> Option<String>>>>>, |
| 228 | +} |
| 229 | + |
| 230 | +impl Global for SettingsJsonKeyFinder {} |
| 231 | + |
| 232 | +impl SettingsJsonKeyFinder { |
| 233 | + fn add_basic_json_key_finder<T: 'static + Default + serde::Serialize>( |
| 234 | + &mut self, |
| 235 | + field: SettingField<T>, |
| 236 | + ) -> &mut Self { |
| 237 | + let key = TypeId::of::<T>(); |
| 238 | + let key_finder = Box::new(move |mut content: SettingsContent| { |
| 239 | + // Write the field with a default value |
| 240 | + (field.write)(&mut content, Some(T::default())); |
| 241 | + |
| 242 | + // Serialize to a pretty-printed JSON string |
| 243 | + let json_string = serde_json::to_string_pretty(&content) |
| 244 | + .expect("Failed to serialize SettingsContent"); |
| 245 | + |
| 246 | + // Extract the JSON path from the string (should only have one entry) |
| 247 | + extract_json_path_from_string(&json_string) |
| 248 | + }); |
| 249 | + self.key_finders.borrow_mut().insert(key, key_finder); |
| 250 | + self |
| 251 | + } |
| 252 | + |
| 253 | + fn find_json_path<T: 'static>(&self) -> Option<String> { |
| 254 | + let key = TypeId::of::<T>(); |
| 255 | + let key_finders = self.key_finders.borrow(); |
| 256 | + let key_finder = key_finders.get(&key)?; |
| 257 | + let content = SettingsContent::default(); |
| 258 | + key_finder(content) |
| 259 | + } |
| 260 | +} |
| 261 | + |
| 262 | +/// Extracts the JSON key path from a serialized settings string. |
| 263 | +/// Assumes only one field is set in the JSON. |
| 264 | +fn extract_json_path_from_string(json: &str) -> Option<String> { |
| 265 | + use serde_json::Value; |
| 266 | + |
| 267 | + let value: Value = serde_json::from_str(json).ok()?; |
| 268 | + |
| 269 | + fn find_non_empty_path(value: &Value, prefix: &str) -> Option<String> { |
| 270 | + match value { |
| 271 | + Value::Object(map) => { |
| 272 | + for (key, val) in map { |
| 273 | + let new_prefix = if prefix.is_empty() { |
| 274 | + key.clone() |
| 275 | + } else { |
| 276 | + format!("{}.{}", prefix, key) |
| 277 | + }; |
| 278 | + |
| 279 | + match val { |
| 280 | + Value::Null => continue, |
| 281 | + Value::Object(inner_map) if inner_map.is_empty() => continue, |
| 282 | + Value::Array(arr) if arr.is_empty() => continue, |
| 283 | + Value::Object(_) => { |
| 284 | + if let Some(path) = find_non_empty_path(val, &new_prefix) { |
| 285 | + return Some(path); |
| 286 | + } |
| 287 | + } |
| 288 | + _ => return Some(new_prefix), |
| 289 | + } |
| 290 | + } |
| 291 | + None |
| 292 | + } |
| 293 | + _ => None, |
| 294 | + } |
| 295 | + } |
| 296 | + |
| 297 | + find_non_empty_path(&value, "") |
| 298 | +} |
| 299 | + |
| 225 | 300 | impl SettingFieldRenderer { |
| 226 | 301 | fn add_basic_renderer<T: 'static>( |
| 227 | 302 | &mut self, |
| @@ -343,6 +418,7 @@ impl FeatureFlag for SettingsUiFeatureFlag { · 115 unmodified lines |
| 343 | 418 | |
| 344 | 419 | pub fn init(cx: &mut App) { |
| 345 | 420 | init_renderers(cx); |
| 421 | + init_json_key_finders(cx); |
| 346 | 422 | |
| 347 | 423 | cx.observe_new(|workspace: &mut workspace::Workspace, _, _| { |
| 348 | 424 | workspace.register_action(|workspace, _: &OpenSettings, window, cx| { |
| @@ -356,6 +432,14 @@ pub fn init(cx: &mut App) { · 7 unmodified lines |
| 356 | 432 | .detach(); |
| 357 | 433 | } |
| 358 | 434 | |
| 435 | +fn init_json_key_finders(cx: &mut App) { |
| 436 | + cx.default_global::<SettingsJsonKeyFinder>() |
| 437 | + .add_basic_json_key_finder(SettingField::<bool> { |
| 438 | + pick: |content| content.vim_mode.as_ref(), |
| 439 | + write: |content, value| content.vim_mode = value, |
| 440 | + }); |
| 441 | +} |
| 442 | + |
| 359 | 443 | fn init_renderers(cx: &mut App) { |
| 360 | 444 | cx.default_global::<SettingFieldRenderer>() |
| 361 | 445 | .add_basic_renderer::<UnimplementedSettingField>(|_, _, _, _, _| { |
| @@ -3114,8 +3198,25 @@ fn render_icon_theme_picker( · 2752 unmodified lines |
| 3114 | 3198 | #[cfg(test)] |
| 3115 | 3199 | mod test { |
| 3116 | 3200 | |
| 3201 | + use gpui::TestAppContext; |
| 3202 | + use workspace::AppState; |
| 3203 | + |
| 3117 | 3204 | use super::*; |
| 3118 | 3205 | |
| 3206 | + #[gpui::test] |
| 3207 | + fn test_json_path(cx: &mut TestAppContext) { |
| 3208 | + cx.update(|cx| { |
| 3209 | + register_settings(cx); |
| 3210 | + init_json_key_finders(cx); |
| 3211 | + |
| 3212 | + let json_key_finder = cx.global::<SettingsJsonKeyFinder>(); |
| 3213 | + let path = json_key_finder.find_json_path::<bool>(); |
| 3214 | + dbg!(&path); |
| 3215 | + |
| 3216 | + assert_eq!(path, Some("vim_mode".to_string())); |
| 3217 | + }); |
| 3218 | + } |
| 3219 | + |
| 3119 | 3220 | impl SettingsWindow { |
| 3120 | 3221 | fn navbar_entry(&self) -> usize { |
| 3121 | 3222 | self.navbar_entry |
| @@ -3134,9 +3235,14 @@ mod test { · 12 unmodified lines |
| 3134 | 3235 | } |
| 3135 | 3236 | |
| 3136 | 3237 | fn register_settings(cx: &mut App) { |
| 3238 | + let app_state = workspace::AppState::test(cx); |
| 3239 | + |
| 3137 | 3240 | settings::init(cx); |
| 3138 | 3241 | theme::init(theme::LoadThemes::JustBase, cx); |
| 3139 | | − workspace::init_settings(cx); |
| 3242 | + // workspace::init_settings(cx); |
| 3243 | + workspace::init(app_state.clone(), cx); |
| 3244 | + AppState::set_global(Arc::downgrade(&app_state), cx); |
| 3245 | + |
| 3140 | 3246 | project::Project::init_settings(cx); |
| 3141 | 3247 | language::init(cx); |
| 3142 | 3248 | editor::init(cx); |