Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:57:37.362Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

settings_store.rs

3284 lines · 112.2 KB · rust
1use anyhow::{Context as _, Result};
2use collections::{BTreeMap, HashMap, TypeIdHashMap, btree_map, hash_map};
3use fs::Fs;
4use futures::{
5    FutureExt, StreamExt,
6    channel::{mpsc, oneshot},
7    future::LocalBoxFuture,
8};
9use gpui::{
10    App, AppContext, AsyncApp, BorrowAppContext, Entity, Global, SharedString, Task, UpdateGlobal,
11};
12
13use paths::{local_settings_file_relative_path, task_file_name};
14use schemars::{JsonSchema, json_schema};
15use serde_json::Value;
16use settings_content::{CommandAliasTarget, ParseStatus};
17use std::{
18    any::{Any, TypeId, type_name},
19    fmt::Debug,
20    ops::Range,
21    path::{Path, PathBuf},
22    rc::Rc,
23    str,
24    sync::Arc,
25};
26use util::{
27    ResultExt as _,
28    rel_path::RelPath,
29    schemars::{AllowTrailingCommas, DefaultDenyUnknownFields, replace_subschema},
30};
31
32use crate::editorconfig_store::EditorconfigStore;
33
34use crate::{
35    ActiveSettingsProfileName, FileTypeMap, FontFamilyName, IconThemeName, LanguageSettingsContent,
36    LanguageToSettingsMap, LspSettings, LspSettingsMap, SemanticTokenRules, ThemeName,
37    UserSettingsContentExt, VsCodeSettings, WorktreeId,
38    settings_content::{
39        ExtendingSet, ExtensionsSettingsContent, ProfileBase, ProjectSettingsContent,
40        RootUserSettings, SettingsContent, UserSettingsContent, merge_from::MergeFrom,
41    },
42};
43
44use settings_json::{infer_json_indent_size, update_value_in_json_text};
45
46pub const LSP_SETTINGS_SCHEMA_URL_PREFIX: &str = "zed://schemas/settings/lsp/";
47
48pub trait SettingsKey: 'static + Send + Sync {
49    /// The name of a key within the JSON file from which this setting should
50    /// be deserialized. If this is `None`, then the setting will be deserialized
51    /// from the root object.
52    const KEY: Option<&'static str>;
53
54    const FALLBACK_KEY: Option<&'static str> = None;
55}
56
57/// A value that can be defined as a user setting.
58///
59/// Settings can be loaded from a combination of multiple JSON files.
60pub trait Settings: 'static + Send + Sync + Sized {
61    /// The name of the keys in the [`SettingsContent`] that should
62    /// always be written to a settings file, even if their value matches the default
63    /// value.
64    ///
65    /// This is useful for tagged [`SettingsContent`]s where the tag
66    /// is a "version" field that should always be persisted, even if the current
67    /// user settings match the current version of the settings.
68    const PRESERVED_KEYS: Option<&'static [&'static str]> = None;
69
70    /// Read the value from default.json.
71    ///
72    /// This function *should* panic if default values are missing,
73    /// and you should add a default to default.json for documentation.
74    fn from_settings(content: &SettingsContent) -> Self;
75
76    #[track_caller]
77    fn register(cx: &mut App)
78    where
79        Self: Sized,
80    {
81        SettingsStore::update_global(cx, |store, _| {
82            store.register_setting::<Self>();
83        });
84    }
85
86    #[track_caller]
87    fn get<'a>(path: Option<SettingsLocation>, cx: &'a App) -> &'a Self
88    where
89        Self: Sized,
90    {
91        cx.global::<SettingsStore>().get(path)
92    }
93
94    #[track_caller]
95    fn get_global(cx: &App) -> &Self
96    where
97        Self: Sized,
98    {
99        cx.global::<SettingsStore>().get(None)
100    }
101
102    #[track_caller]
103    fn try_get(cx: &App) -> Option<&Self>
104    where
105        Self: Sized,
106    {
107        if cx.has_global::<SettingsStore>() {
108            cx.global::<SettingsStore>().try_get(None)
109        } else {
110            None
111        }
112    }
113
114    #[track_caller]
115    fn try_read_global<R>(cx: &AsyncApp, f: impl FnOnce(&Self) -> R) -> Option<R>
116    where
117        Self: Sized,
118    {
119        cx.try_read_global(|s: &SettingsStore, _| f(s.get(None)))
120    }
121
122    #[track_caller]
123    fn override_global(settings: Self, cx: &mut App)
124    where
125        Self: Sized,
126    {
127        cx.global_mut::<SettingsStore>().override_global(settings)
128    }
129}
130
131pub struct RegisteredSetting {
132    pub settings_value: fn() -> Box<dyn AnySettingValue>,
133    pub from_settings: fn(&SettingsContent) -> Box<dyn Any>,
134    pub id: fn() -> TypeId,
135}
136
137inventory::collect!(RegisteredSetting);
138
139#[derive(Clone, Copy, Debug)]
140pub struct SettingsLocation<'a> {
141    pub worktree_id: WorktreeId,
142    pub path: &'a RelPath,
143}
144
145pub struct SettingsStore {
146    setting_values: TypeIdHashMap<Box<dyn AnySettingValue>>,
147    default_settings: Rc<SettingsContent>,
148    user_settings: Option<UserSettingsContent>,
149    global_settings: Option<Box<SettingsContent>>,
150
151    extension_settings: Option<Box<SettingsContent>>,
152    server_settings: Option<Box<SettingsContent>>,
153
154    language_semantic_token_rules: HashMap<SharedString, SemanticTokenRules>,
155
156    merged_settings: Rc<SettingsContent>,
157
158    last_user_settings_content: Option<String>,
159    last_global_settings_content: Option<String>,
160    local_settings: BTreeMap<(WorktreeId, Arc<RelPath>), SettingsContent>,
161    pub editorconfig_store: Entity<EditorconfigStore>,
162
163    _settings_files_watcher: Option<Task<()>>,
164    _setting_file_updates: Task<()>,
165    setting_file_updates_tx:
166        mpsc::UnboundedSender<Box<dyn FnOnce(AsyncApp) -> LocalBoxFuture<'static, Result<()>>>>,
167    file_errors: BTreeMap<SettingsFile, SettingsParseResult>,
168}
169
170#[derive(Clone, PartialEq, Eq, Debug)]
171pub enum SettingsFile {
172    Default,
173    Global,
174    User,
175    Server,
176    /// Represents project settings in ssh projects as well as local projects
177    Project((WorktreeId, Arc<RelPath>)),
178}
179
180impl PartialOrd for SettingsFile {
181    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
182        Some(self.cmp(other))
183    }
184}
185
186/// Sorted in order of precedence
187impl Ord for SettingsFile {
188    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
189        use SettingsFile::*;
190        use std::cmp::Ordering;
191        match (self, other) {
192            (User, User) => Ordering::Equal,
193            (Server, Server) => Ordering::Equal,
194            (Default, Default) => Ordering::Equal,
195            (Project((id1, rel_path1)), Project((id2, rel_path2))) => id1
196                .cmp(id2)
197                .then_with(|| rel_path1.cmp(rel_path2).reverse()),
198            (Project(_), _) => Ordering::Less,
199            (_, Project(_)) => Ordering::Greater,
200            (Server, _) => Ordering::Less,
201            (_, Server) => Ordering::Greater,
202            (User, _) => Ordering::Less,
203            (_, User) => Ordering::Greater,
204            (Global, _) => Ordering::Less,
205            (_, Global) => Ordering::Greater,
206        }
207    }
208}
209
210#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
211pub enum LocalSettingsKind {
212    Settings,
213    Tasks,
214    Editorconfig,
215    Debug,
216}
217
218#[derive(Clone, Debug, PartialEq, Eq, Hash)]
219pub enum LocalSettingsPath {
220    InWorktree(Arc<RelPath>),
221    OutsideWorktree(Arc<Path>),
222}
223
224impl LocalSettingsPath {
225    pub fn is_outside_worktree(&self) -> bool {
226        matches!(self, Self::OutsideWorktree(_))
227    }
228
229    pub fn to_proto(&self) -> String {
230        match self {
231            Self::InWorktree(path) => path.as_unix_str().to_owned(),
232            Self::OutsideWorktree(path) => path.to_string_lossy().to_string(),
233        }
234    }
235
236    pub fn from_proto(path: &str, is_outside_worktree: bool) -> anyhow::Result<Self> {
237        if is_outside_worktree {
238            Ok(Self::OutsideWorktree(PathBuf::from(path).into()))
239        } else {
240            Ok(Self::InWorktree(RelPath::from_unix_str(path)?.into()))
241        }
242    }
243}
244
245impl Global for SettingsStore {}
246
247#[derive(Default)]
248pub struct DefaultSemanticTokenRules(pub SemanticTokenRules);
249
250impl gpui::Global for DefaultSemanticTokenRules {}
251
252#[doc(hidden)]
253#[derive(Debug)]
254pub struct SettingValue<T> {
255    #[doc(hidden)]
256    pub global_value: Option<T>,
257    #[doc(hidden)]
258    pub local_values: Vec<(WorktreeId, Arc<RelPath>, T)>,
259}
260
261#[doc(hidden)]
262pub trait AnySettingValue: 'static + Send + Sync {
263    fn setting_type_name(&self) -> &'static str;
264
265    fn from_settings(&self, s: &SettingsContent) -> Box<dyn Any>;
266
267    fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any;
268    fn all_local_values(&self) -> Vec<(WorktreeId, Arc<RelPath>, &dyn Any)>;
269    fn set_global_value(&mut self, value: Box<dyn Any>);
270    fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<RelPath>, value: Box<dyn Any>);
271    fn clear_local_values(&mut self, root_id: WorktreeId);
272}
273
274/// Parameters that are used when generating some JSON schemas at runtime.
275pub struct SettingsJsonSchemaParams<'a> {
276    pub language_names: &'a [String],
277    pub font_names: &'a [String],
278    pub theme_names: &'a [SharedString],
279    pub icon_theme_names: &'a [SharedString],
280    pub lsp_adapter_names: &'a [String],
281    pub action_names: &'a [&'a str],
282    pub action_documentation: &'a HashMap<&'a str, &'a str>,
283    pub deprecations: &'a HashMap<&'a str, &'a str>,
284    pub deprecation_messages: &'a HashMap<&'a str, &'a str>,
285}
286
287impl SettingsStore {
288    pub fn new(cx: &mut App, default_settings: &str) -> Self {
289        Self::new_with_semantic_tokens(cx, default_settings)
290    }
291
292    pub fn new_with_semantic_tokens(cx: &mut App, default_settings: &str) -> Self {
293        let default_settings = Self::parse_default_settings(default_settings).unwrap();
294        Self::from_settings_content(cx, default_settings)
295    }
296
297    fn from_settings_content(cx: &mut App, default_settings: SettingsContent) -> Self {
298        let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded();
299        if !cx.has_global::<DefaultSemanticTokenRules>() {
300            cx.set_global::<DefaultSemanticTokenRules>(
301                crate::parse_json_with_comments::<SemanticTokenRules>(
302                    &crate::default_semantic_token_rules(),
303                )
304                .map(DefaultSemanticTokenRules)
305                .unwrap_or_default(),
306            );
307        }
308        let default_settings: Rc<SettingsContent> = default_settings.into();
309        let mut this = Self {
310            setting_values: Default::default(),
311            default_settings: default_settings.clone(),
312            global_settings: None,
313            server_settings: None,
314            user_settings: None,
315            extension_settings: None,
316            language_semantic_token_rules: HashMap::default(),
317
318            merged_settings: default_settings,
319            last_user_settings_content: None,
320            last_global_settings_content: None,
321            local_settings: BTreeMap::default(),
322            editorconfig_store: cx.new(|_| EditorconfigStore::default()),
323            _settings_files_watcher: None,
324            setting_file_updates_tx,
325            _setting_file_updates: cx.spawn(async move |cx| {
326                while let Some(setting_file_update) = setting_file_updates_rx.next().await {
327                    (setting_file_update)(cx.clone()).await.log_err();
328                }
329            }),
330            file_errors: BTreeMap::default(),
331        };
332
333        this.load_settings_types();
334
335        this
336    }
337
338    pub fn observe_active_settings_profile_name(cx: &mut App) -> gpui::Subscription {
339        cx.observe_global::<ActiveSettingsProfileName>(|cx| {
340            Self::update_global(cx, |store, cx| {
341                store.recompute_values(None, cx);
342            });
343        })
344    }
345
346    pub fn update<C, R>(cx: &mut C, f: impl FnOnce(&mut Self, &mut C) -> R) -> R
347    where
348        C: BorrowAppContext,
349    {
350        cx.update_global(f)
351    }
352
353    pub fn watch_settings_files(
354        &mut self,
355        fs: Arc<dyn Fs>,
356        cx: &mut App,
357        settings_changed: impl 'static + Fn(SettingsFile, SettingsParseResult, &mut App),
358    ) {
359        let (mut user_settings_file_rx, user_settings_watcher) = crate::watch_config_file(
360            cx.background_executor(),
361            fs.clone(),
362            paths::settings_file().clone(),
363        );
364        let (mut global_settings_file_rx, global_settings_watcher) = crate::watch_config_file(
365            cx.background_executor(),
366            fs,
367            paths::global_settings_file().clone(),
368        );
369
370        let global_content = cx
371            .foreground_executor()
372            .block_on(global_settings_file_rx.next())
373            .unwrap();
374        let user_content = cx
375            .foreground_executor()
376            .block_on(user_settings_file_rx.next())
377            .unwrap();
378
379        let result = self.set_user_settings(&user_content, cx);
380        settings_changed(SettingsFile::User, result, cx);
381        let result = self.set_global_settings(&global_content, cx);
382        settings_changed(SettingsFile::Global, result, cx);
383
384        self._settings_files_watcher = Some(cx.spawn(async move |cx| {
385            let _user_settings_watcher = user_settings_watcher;
386            let _global_settings_watcher = global_settings_watcher;
387            let mut settings_streams = futures::stream::select(
388                global_settings_file_rx.map(|content| (SettingsFile::Global, content)),
389                user_settings_file_rx.map(|content| (SettingsFile::User, content)),
390            );
391
392            while let Some((settings_file, content)) = settings_streams.next().await {
393                cx.update_global(|store: &mut SettingsStore, cx| {
394                    let result = match settings_file {
395                        SettingsFile::User => store.set_user_settings(&content, cx),
396                        SettingsFile::Global => store.set_global_settings(&content, cx),
397                        _ => return,
398                    };
399                    settings_changed(settings_file, result, cx);
400                    cx.refresh_windows();
401                });
402            }
403        }));
404    }
405
406    /// Add a new type of setting to the store.
407    pub fn register_setting<T: Settings>(&mut self) {
408        self.register_setting_internal(&RegisteredSetting {
409            settings_value: || {
410                Box::new(SettingValue::<T> {
411                    global_value: None,
412                    local_values: Vec::new(),
413                })
414            },
415            from_settings: |content| Box::new(T::from_settings(content)),
416            id: || TypeId::of::<T>(),
417        });
418    }
419
420    fn load_settings_types(&mut self) {
421        for registered_setting in inventory::iter::<RegisteredSetting>() {
422            self.register_setting_internal(registered_setting);
423        }
424    }
425
426    fn register_setting_internal(&mut self, registered_setting: &RegisteredSetting) {
427        let entry = self.setting_values.entry((registered_setting.id)());
428
429        if matches!(entry, hash_map::Entry::Occupied(_)) {
430            return;
431        }
432
433        let setting_value = entry.or_insert((registered_setting.settings_value)());
434        let value = (registered_setting.from_settings)(&self.merged_settings);
435        setting_value.set_global_value(value);
436    }
437
438    pub fn merged_settings(&self) -> &SettingsContent {
439        &self.merged_settings
440    }
441
442    /// Get the value of a setting.
443    ///
444    /// Panics if the given setting type has not been registered, or if there is no
445    /// value for this setting.
446    pub fn get<T: Settings>(&self, path: Option<SettingsLocation>) -> &T {
447        self.setting_values
448            .get(&TypeId::of::<T>())
449            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
450            .value_for_path(path)
451            .downcast_ref::<T>()
452            .expect("no default value for setting type")
453    }
454
455    /// Get the value of a setting.
456    ///
457    /// Does not panic
458    pub fn try_get<T: Settings>(&self, path: Option<SettingsLocation>) -> Option<&T> {
459        self.setting_values
460            .get(&TypeId::of::<T>())
461            .map(|value| value.value_for_path(path))
462            .and_then(|value| value.downcast_ref::<T>())
463    }
464
465    /// Get all values from project specific settings
466    pub fn get_all_locals<T: Settings>(&self) -> Vec<(WorktreeId, Arc<RelPath>, &T)> {
467        self.setting_values
468            .get(&TypeId::of::<T>())
469            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
470            .all_local_values()
471            .into_iter()
472            .map(|(id, path, any)| {
473                (
474                    id,
475                    path,
476                    any.downcast_ref::<T>()
477                        .expect("wrong value type for setting"),
478                )
479            })
480            .collect()
481    }
482
483    /// Override the global value for a setting.
484    ///
485    /// The given value will be overwritten if the user settings file changes.
486    pub fn override_global<T: Settings>(&mut self, value: T) {
487        self.setting_values
488            .get_mut(&TypeId::of::<T>())
489            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
490            .set_global_value(Box::new(value))
491    }
492
493    /// Get the user's settings content.
494    ///
495    /// For user-facing functionality use the typed setting interface.
496    /// (e.g. ProjectSettings::get_global(cx))
497    pub fn raw_user_settings(&self) -> Option<&UserSettingsContent> {
498        self.user_settings.as_ref()
499    }
500
501    /// Get the default settings content as a raw JSON value.
502    pub fn raw_default_settings(&self) -> &SettingsContent {
503        &self.default_settings
504    }
505
506    /// Get the configured settings profile names.
507    pub fn configured_settings_profiles(&self) -> impl Iterator<Item = &str> {
508        self.user_settings
509            .iter()
510            .flat_map(|settings| settings.profiles.keys().map(|k| k.as_str()))
511    }
512
513    #[cfg(any(test, feature = "test-support"))]
514    pub fn test(cx: &mut App) -> Self {
515        static CACHED_SETTINGS_CONTENT: std::sync::LazyLock<SettingsContent> =
516            std::sync::LazyLock::new(|| {
517                SettingsContent::parse_json_with_comments(crate::test_settings()).unwrap()
518            });
519        Self::from_settings_content(cx, CACHED_SETTINGS_CONTENT.clone())
520    }
521
522    /// Updates the value of a setting in the user's global configuration.
523    ///
524    /// This is only for tests. Normally, settings are only loaded from
525    /// JSON files.
526    #[cfg(any(test, feature = "test-support"))]
527    pub fn update_user_settings(
528        &mut self,
529        cx: &mut App,
530        update: impl FnOnce(&mut SettingsContent),
531    ) {
532        let mut content = self.user_settings.clone().unwrap_or_default().content;
533        update(&mut content);
534        fn trail(this: &mut SettingsStore, content: Box<SettingsContent>, cx: &mut App) {
535            let new_text = serde_json::to_string(&UserSettingsContent {
536                content,
537                ..Default::default()
538            })
539            .unwrap();
540            _ = this.set_user_settings(&new_text, cx);
541        }
542        trail(self, content, cx);
543    }
544
545    pub async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> {
546        match fs.load(paths::settings_file()).await {
547            result @ Ok(_) => result,
548            Err(err) => {
549                if let Some(e) = err.downcast_ref::<std::io::Error>()
550                    && e.kind() == std::io::ErrorKind::NotFound
551                {
552                    return Ok(crate::initial_user_settings_content().to_string());
553                }
554                Err(err)
555            }
556        }
557    }
558
559    fn update_settings_file_inner(
560        &self,
561        fs: Arc<dyn Fs>,
562        update: impl 'static + Send + FnOnce(String, AsyncApp) -> Result<String>,
563    ) -> oneshot::Receiver<Result<()>> {
564        let (tx, rx) = oneshot::channel::<Result<()>>();
565        self.setting_file_updates_tx
566            .unbounded_send(Box::new(move |cx: AsyncApp| {
567                async move {
568                    let res = async move {
569                        let old_text = Self::load_settings(&fs).await?;
570                        let new_text = update(old_text, cx.clone())?;
571
572                        let settings_path = paths::settings_file().as_path();
573                        if fs.is_file(settings_path).await {
574                            let resolved_path =
575                                fs.canonicalize(settings_path).await.with_context(|| {
576                                    format!(
577                                        "Failed to canonicalize settings path {:?}",
578                                        settings_path
579                                    )
580                                })?;
581
582                            fs.atomic_write(resolved_path.clone(), new_text.clone())
583                                .await
584                                .with_context(|| {
585                                    format!("Failed to write settings to file {:?}", resolved_path)
586                                })?;
587                        } else {
588                            fs.atomic_write(settings_path.to_path_buf(), new_text.clone())
589                                .await
590                                .with_context(|| {
591                                    format!("Failed to write settings to file {:?}", settings_path)
592                                })?;
593                        }
594
595                        cx.update_global(|store: &mut SettingsStore, cx| {
596                            store.set_user_settings(&new_text, cx).result().map(|_| ())
597                        })
598                    }
599                    .await;
600
601                    let new_res = match &res {
602                        Ok(_) => anyhow::Ok(()),
603                        Err(e) => Err(anyhow::anyhow!("{:?}", e)),
604                    };
605
606                    _ = tx.send(new_res);
607                    res
608                }
609                .boxed_local()
610            }))
611            .map_err(|err| anyhow::format_err!("Failed to update settings file: {}", err))
612            .log_with_level(log::Level::Warn);
613        return rx;
614    }
615
616    pub fn update_settings_file(
617        &self,
618        fs: Arc<dyn Fs>,
619        update: impl 'static + Send + FnOnce(&mut SettingsContent, &App),
620    ) {
621        _ = self.update_settings_file_with_completion(fs, update);
622    }
623
624    pub fn update_settings_file_with_completion(
625        &self,
626        fs: Arc<dyn Fs>,
627        update: impl 'static + Send + FnOnce(&mut SettingsContent, &App),
628    ) -> oneshot::Receiver<Result<()>> {
629        self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
630            cx.read_global(|store: &SettingsStore, cx| {
631                store.new_text_for_update(old_text, |content| update(content, cx))
632            })
633        })
634    }
635
636    pub fn import_vscode_settings(
637        &self,
638        fs: Arc<dyn Fs>,
639        vscode_settings: VsCodeSettings,
640    ) -> oneshot::Receiver<Result<()>> {
641        self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
642            cx.read_global(|store: &SettingsStore, _cx| {
643                store.get_vscode_edits(old_text, &vscode_settings)
644            })
645        })
646    }
647
648    pub fn get_all_files(&self) -> Vec<SettingsFile> {
649        let mut files = Vec::from_iter(
650            self.local_settings
651                .keys()
652                // rev because these are sorted by path, so highest precedence is last
653                .rev()
654                .cloned()
655                .map(SettingsFile::Project),
656        );
657
658        if self.server_settings.is_some() {
659            files.push(SettingsFile::Server);
660        }
661        // ignoring profiles
662        // ignoring os profiles
663        // ignoring release channel profiles
664        // ignoring global
665        // ignoring extension
666
667        if self.user_settings.is_some() {
668            files.push(SettingsFile::User);
669        }
670        files.push(SettingsFile::Default);
671        files
672    }
673
674    pub fn get_content_for_file(&self, file: SettingsFile) -> Option<&SettingsContent> {
675        match file {
676            SettingsFile::User => self
677                .user_settings
678                .as_ref()
679                .map(|settings| settings.content.as_ref()),
680            SettingsFile::Default => Some(self.default_settings.as_ref()),
681            SettingsFile::Server => self.server_settings.as_deref(),
682            SettingsFile::Project(ref key) => self.local_settings.get(key),
683            SettingsFile::Global => self.global_settings.as_deref(),
684        }
685    }
686
687    pub fn get_overrides_for_field<T>(
688        &self,
689        target_file: SettingsFile,
690        get: fn(&SettingsContent) -> &Option<T>,
691    ) -> Vec<SettingsFile> {
692        let all_files = self.get_all_files();
693        let mut found_file = false;
694        let mut overrides = Vec::new();
695
696        for file in all_files.into_iter().rev() {
697            if !found_file {
698                found_file = file == target_file;
699                continue;
700            }
701
702            if let SettingsFile::Project((wt_id, ref path)) = file
703                && let SettingsFile::Project((target_wt_id, ref target_path)) = target_file
704                && (wt_id != target_wt_id || !target_path.starts_with(path))
705            {
706                // if requesting value from a local file, don't return values from local files in different worktrees
707                continue;
708            }
709
710            let Some(content) = self.get_content_for_file(file.clone()) else {
711                continue;
712            };
713            if get(content).is_some() {
714                overrides.push(file);
715            }
716        }
717
718        overrides
719    }
720
721    /// Checks the given file, and files that the passed file overrides for the given field.
722    /// Returns the first file found that contains the value.
723    /// The value will only be None if no file contains the value.
724    /// I.e. if no file contains the value, returns `(File::Default, None)`
725    pub fn get_value_from_file<'a, T: 'a>(
726        &'a self,
727        target_file: SettingsFile,
728        pick: fn(&'a SettingsContent) -> Option<T>,
729    ) -> (SettingsFile, Option<T>) {
730        self.get_value_from_file_inner(target_file, pick, true)
731    }
732
733    /// Same as `Self::get_value_from_file` except that it does not include the current file.
734    /// Therefore it returns the value that was potentially overloaded by the target file.
735    pub fn get_value_up_to_file<'a, T: 'a>(
736        &'a self,
737        target_file: SettingsFile,
738        pick: fn(&'a SettingsContent) -> Option<T>,
739    ) -> (SettingsFile, Option<T>) {
740        self.get_value_from_file_inner(target_file, pick, false)
741    }
742
743    fn get_value_from_file_inner<'a, T: 'a>(
744        &'a self,
745        target_file: SettingsFile,
746        pick: fn(&'a SettingsContent) -> Option<T>,
747        include_target_file: bool,
748    ) -> (SettingsFile, Option<T>) {
749        // todo(settings_ui): Add a metadata field for overriding the "overrides" tag, for contextually different settings
750        //  e.g. disable AI isn't overridden, or a vec that gets extended instead or some such
751
752        // todo(settings_ui) cache all files
753        let all_files = self.get_all_files();
754        let mut found_file = false;
755
756        for file in all_files.into_iter() {
757            if !found_file && file != SettingsFile::Default {
758                if file != target_file {
759                    continue;
760                }
761                found_file = true;
762                if !include_target_file {
763                    continue;
764                }
765            }
766
767            if let SettingsFile::Project((worktree_id, ref path)) = file
768                && let SettingsFile::Project((target_worktree_id, ref target_path)) = target_file
769                && (worktree_id != target_worktree_id || !target_path.starts_with(&path))
770            {
771                // if requesting value from a local file, don't return values from local files in different worktrees
772                continue;
773            }
774
775            let Some(content) = self.get_content_for_file(file.clone()) else {
776                continue;
777            };
778            if let Some(value) = pick(content) {
779                return (file, Some(value));
780            }
781        }
782
783        (SettingsFile::Default, None)
784    }
785
786    #[inline(always)]
787    fn parse_and_migrate_zed_settings<SettingsContentType: RootUserSettings>(
788        &mut self,
789        user_settings_content: &str,
790        file: SettingsFile,
791    ) -> (Option<SettingsContentType>, SettingsParseResult) {
792        let mut migration_status = MigrationStatus::NotNeeded;
793        let (settings, parse_status) = if user_settings_content.is_empty() {
794            SettingsContentType::parse_json("{}")
795        } else {
796            let migration_res = migrator::migrate_settings(user_settings_content);
797            migration_status = match &migration_res {
798                Ok(Some(_)) => MigrationStatus::Succeeded,
799                Ok(None) => MigrationStatus::NotNeeded,
800                Err(err) => MigrationStatus::Failed {
801                    error: err.to_string(),
802                },
803            };
804            let content = match &migration_res {
805                Ok(Some(content)) => content,
806                Ok(None) => user_settings_content,
807                Err(_) => user_settings_content,
808            };
809            SettingsContentType::parse_json(content)
810        };
811
812        let result = SettingsParseResult {
813            parse_status,
814            migration_status,
815        };
816        self.file_errors.insert(file, result.clone());
817        return (settings, result);
818    }
819
820    pub fn error_for_file(&self, file: SettingsFile) -> Option<SettingsParseResult> {
821        self.file_errors
822            .get(&file)
823            .filter(|parse_result| parse_result.requires_user_action())
824            .cloned()
825    }
826}
827
828impl SettingsStore {
829    /// Updates the value of a setting in a JSON file, returning the new text
830    /// for that JSON file.
831    pub fn new_text_for_update(
832        &self,
833        old_text: String,
834        update: impl FnOnce(&mut SettingsContent),
835    ) -> Result<String> {
836        let edits = self.edits_for_update(&old_text, update)?;
837        let mut new_text = old_text;
838        for (range, replacement) in edits.into_iter() {
839            new_text.replace_range(range, &replacement);
840        }
841        Ok(new_text)
842    }
843
844    pub fn get_vscode_edits(&self, old_text: String, vscode: &VsCodeSettings) -> Result<String> {
845        self.new_text_for_update(old_text, |content| {
846            content.merge_from(&vscode.settings_content())
847        })
848    }
849
850    /// Updates the value of a setting in a JSON file, returning a list
851    /// of edits to apply to the JSON file.
852    pub fn edits_for_update(
853        &self,
854        text: &str,
855        update: impl FnOnce(&mut SettingsContent),
856    ) -> Result<Vec<(Range<usize>, String)>> {
857        let old_content = if text.trim().is_empty() {
858            UserSettingsContent::default()
859        } else {
860            let (old_content, parse_status) = UserSettingsContent::parse_json(text);
861            if let ParseStatus::Failed { error } = &parse_status {
862                log::error!("Failed to parse settings for update: {error}");
863            }
864            old_content
865                .context("Settings file could not be parsed. Fix syntax errors before updating.")?
866        };
867        let mut new_content = old_content.clone();
868        update(&mut new_content.content);
869
870        let old_value = serde_json::to_value(&old_content).unwrap();
871        let new_value = serde_json::to_value(new_content).unwrap();
872
873        let mut key_path = Vec::new();
874        let mut edits = Vec::new();
875        let tab_size = infer_json_indent_size(&text);
876        let mut text = text.to_string();
877        update_value_in_json_text(
878            &mut text,
879            &mut key_path,
880            tab_size,
881            &old_value,
882            &new_value,
883            &mut edits,
884        );
885        Ok(edits)
886    }
887
888    /// Mutates the default settings in place and recomputes all setting values.
889    pub fn update_default_settings(
890        &mut self,
891        cx: &mut App,
892        update: impl FnOnce(&mut SettingsContent),
893    ) {
894        let default_settings = Rc::make_mut(&mut self.default_settings);
895        update(default_settings);
896        self.recompute_values(None, cx);
897    }
898
899    /// Sets the default settings via a JSON string.
900    ///
901    /// The string should contain a JSON object with a default value for every setting.
902    pub fn set_default_settings(
903        &mut self,
904        default_settings_content: &str,
905        cx: &mut App,
906    ) -> Result<()> {
907        self.default_settings = Self::parse_default_settings(default_settings_content)?.into();
908        self.recompute_values(None, cx);
909        Ok(())
910    }
911
912    /// Parses the default settings JSON and folds any `dev`/`nightly`/`preview`/`stable`
913    /// release-channel overrides and `macos`/`linux`/`windows` platform overrides into
914    /// the returned [`SettingsContent`].
915    ///
916    /// Unlike user settings, default settings are used directly as the base for all
917    /// merges, so overrides must be resolved up front.
918    fn parse_default_settings(default_settings: &str) -> Result<SettingsContent> {
919        let parsed = UserSettingsContent::parse_json_with_comments(default_settings)?;
920        let mut merged = (*parsed.content).clone();
921        merged.merge_from_option(parsed.for_release_channel());
922        merged.merge_from_option(parsed.for_os());
923        Ok(merged)
924    }
925
926    /// Sets the user settings via a JSON string.
927    #[must_use]
928    pub fn set_user_settings(
929        &mut self,
930        user_settings_content: &str,
931        cx: &mut App,
932    ) -> SettingsParseResult {
933        if self.last_user_settings_content.as_deref() == Some(user_settings_content) {
934            return SettingsParseResult {
935                parse_status: ParseStatus::Unchanged,
936                migration_status: MigrationStatus::NotNeeded,
937            };
938        }
939        self.last_user_settings_content = Some(user_settings_content.to_string());
940
941        let (settings, parse_result) = self.parse_and_migrate_zed_settings::<UserSettingsContent>(
942            user_settings_content,
943            SettingsFile::User,
944        );
945
946        if let Some(settings) = settings {
947            self.user_settings = Some(settings);
948            self.recompute_values(None, cx);
949        }
950        return parse_result;
951    }
952
953    /// Sets the global settings via a JSON string.
954    #[must_use]
955    pub fn set_global_settings(
956        &mut self,
957        global_settings_content: &str,
958        cx: &mut App,
959    ) -> SettingsParseResult {
960        if self.last_global_settings_content.as_deref() == Some(global_settings_content) {
961            return SettingsParseResult {
962                parse_status: ParseStatus::Unchanged,
963                migration_status: MigrationStatus::NotNeeded,
964            };
965        }
966        self.last_global_settings_content = Some(global_settings_content.to_string());
967
968        let (settings, parse_result) = self.parse_and_migrate_zed_settings::<SettingsContent>(
969            global_settings_content,
970            SettingsFile::Global,
971        );
972
973        if let Some(settings) = settings {
974            self.global_settings = Some(Box::new(settings));
975            self.recompute_values(None, cx);
976        }
977        return parse_result;
978    }
979
980    pub fn set_server_settings(
981        &mut self,
982        server_settings_content: &str,
983        cx: &mut App,
984    ) -> Result<()> {
985        let settings = if server_settings_content.is_empty() {
986            None
987        } else {
988            Option::<SettingsContent>::parse_json_with_comments(server_settings_content)?
989        };
990
991        // Rewrite the server settings into a content type
992        self.server_settings = settings.map(|settings| Box::new(settings));
993
994        self.recompute_values(None, cx);
995        Ok(())
996    }
997
998    /// Sets language-specific semantic token rules.
999    ///
1000    /// These rules are registered by language modules (e.g. the Rust language module)
1001    /// or by third-party extensions (via `semantic_token_rules.json` in their language
1002    /// directories). They are stored separately from the global rules and are only
1003    /// applied to buffers of the matching language by the `SemanticTokenStylizer`.
1004    ///
1005    /// This triggers a settings recomputation so that observers (e.g. `LspStore`)
1006    /// are notified and can invalidate cached stylizers.
1007    pub fn set_language_semantic_token_rules(
1008        &mut self,
1009        language: SharedString,
1010        rules: SemanticTokenRules,
1011        cx: &mut App,
1012    ) {
1013        self.language_semantic_token_rules.insert(language, rules);
1014        self.recompute_values(None, cx);
1015    }
1016
1017    /// Removes language-specific semantic token rules for the given language.
1018    ///
1019    /// This should be called when an extension that registered rules for a language
1020    /// is unloaded. Triggers a settings recomputation so that observers (e.g.
1021    /// `LspStore`) are notified and can invalidate cached stylizers.
1022    pub fn remove_language_semantic_token_rules(&mut self, language: &str, cx: &mut App) {
1023        self.language_semantic_token_rules.remove(language);
1024        self.recompute_values(None, cx);
1025    }
1026
1027    /// Returns the language-specific semantic token rules for the given language,
1028    /// if any have been registered.
1029    pub fn language_semantic_token_rules(&self, language: &str) -> Option<&SemanticTokenRules> {
1030        self.language_semantic_token_rules.get(language)
1031    }
1032
1033    /// Add or remove a set of local settings via a JSON string.
1034    pub fn set_local_settings(
1035        &mut self,
1036        root_id: WorktreeId,
1037        path: LocalSettingsPath,
1038        kind: LocalSettingsKind,
1039        settings_content: Option<&str>,
1040        cx: &mut App,
1041    ) -> std::result::Result<(), InvalidSettingsError> {
1042        let content = settings_content
1043            .map(|content| content.trim())
1044            .filter(|content| !content.is_empty());
1045        let mut zed_settings_changed = false;
1046        match (path.clone(), kind, content) {
1047            (LocalSettingsPath::InWorktree(directory_path), LocalSettingsKind::Tasks, _) => {
1048                return Err(InvalidSettingsError::Tasks {
1049                    message: "Attempted to submit tasks into the settings store".to_string(),
1050                    path: directory_path
1051                        .join(RelPath::from_unix_str(task_file_name()).unwrap())
1052                        .as_std_path()
1053                        .to_path_buf(),
1054                });
1055            }
1056            (LocalSettingsPath::InWorktree(directory_path), LocalSettingsKind::Debug, _) => {
1057                return Err(InvalidSettingsError::Debug {
1058                    message: "Attempted to submit debugger config into the settings store"
1059                        .to_string(),
1060                    path: directory_path
1061                        .join(RelPath::from_unix_str(task_file_name()).unwrap())
1062                        .as_std_path()
1063                        .to_path_buf(),
1064                });
1065            }
1066            (LocalSettingsPath::InWorktree(directory_path), LocalSettingsKind::Settings, None) => {
1067                zed_settings_changed = self
1068                    .local_settings
1069                    .remove(&(root_id, directory_path.clone()))
1070                    .is_some();
1071                self.file_errors
1072                    .remove(&SettingsFile::Project((root_id, directory_path)));
1073            }
1074            (
1075                LocalSettingsPath::InWorktree(directory_path),
1076                LocalSettingsKind::Settings,
1077                Some(settings_contents),
1078            ) => {
1079                let (new_settings, parse_result) = self
1080                    .parse_and_migrate_zed_settings::<ProjectSettingsContent>(
1081                        settings_contents,
1082                        SettingsFile::Project((root_id, directory_path.clone())),
1083                    );
1084                match parse_result.parse_status {
1085                    ParseStatus::Success => Ok(()),
1086                    ParseStatus::Unchanged => Ok(()),
1087                    ParseStatus::Failed { error } => Err(InvalidSettingsError::LocalSettings {
1088                        path: directory_path
1089                            .join(local_settings_file_relative_path())
1090                            .into(),
1091                        message: error,
1092                    }),
1093                }?;
1094                if let Some(new_settings) = new_settings {
1095                    match self.local_settings.entry((root_id, directory_path)) {
1096                        btree_map::Entry::Vacant(v) => {
1097                            v.insert(SettingsContent {
1098                                project: new_settings,
1099                                ..Default::default()
1100                            });
1101                            zed_settings_changed = true;
1102                        }
1103                        btree_map::Entry::Occupied(mut o) => {
1104                            if &o.get().project != &new_settings {
1105                                o.insert(SettingsContent {
1106                                    project: new_settings,
1107                                    ..Default::default()
1108                                });
1109                                zed_settings_changed = true;
1110                            }
1111                        }
1112                    }
1113                }
1114            }
1115            (directory_path, LocalSettingsKind::Editorconfig, editorconfig_contents) => {
1116                self.editorconfig_store.update(cx, |store, _| {
1117                    store.set_configs(root_id, directory_path, editorconfig_contents)
1118                })?;
1119            }
1120            (LocalSettingsPath::OutsideWorktree(path), kind, _) => {
1121                log::error!(
1122                    "OutsideWorktree path {:?} with kind {:?} is only supported by editorconfig",
1123                    path,
1124                    kind
1125                );
1126                return Ok(());
1127            }
1128        }
1129        if let LocalSettingsPath::InWorktree(directory_path) = &path {
1130            if zed_settings_changed {
1131                self.recompute_values(Some((root_id, &directory_path)), cx);
1132            }
1133        }
1134        Ok(())
1135    }
1136
1137    pub fn set_extension_settings(
1138        &mut self,
1139        content: ExtensionsSettingsContent,
1140        cx: &mut App,
1141    ) -> Result<()> {
1142        self.extension_settings = Some(Box::new(SettingsContent {
1143            project: ProjectSettingsContent {
1144                all_languages: content.all_languages,
1145                ..Default::default()
1146            },
1147            ..Default::default()
1148        }));
1149        self.recompute_values(None, cx);
1150        Ok(())
1151    }
1152
1153    /// Add or remove a set of local settings via a JSON string.
1154    pub fn clear_local_settings(&mut self, root_id: WorktreeId, cx: &mut App) -> Result<()> {
1155        self.local_settings
1156            .retain(|(worktree_id, _), _| worktree_id != &root_id);
1157
1158        self.editorconfig_store
1159            .update(cx, |store, _cx| store.remove_for_worktree(root_id));
1160
1161        for setting_value in self.setting_values.values_mut() {
1162            setting_value.clear_local_values(root_id);
1163        }
1164        self.recompute_values(Some((root_id, RelPath::empty())), cx);
1165        Ok(())
1166    }
1167
1168    pub fn local_settings(
1169        &self,
1170        root_id: WorktreeId,
1171    ) -> impl '_ + Iterator<Item = (Arc<RelPath>, &ProjectSettingsContent)> {
1172        self.local_settings
1173            .range(
1174                (root_id, RelPath::empty_arc())
1175                    ..(
1176                        WorktreeId::from_usize(root_id.to_usize() + 1),
1177                        RelPath::empty_arc(),
1178                    ),
1179            )
1180            .map(|((_, path), content)| (path.clone(), &content.project))
1181    }
1182
1183    /// Configures common schema replacements shared between user and project
1184    /// settings schemas.
1185    ///
1186    /// This sets up language-specific settings and LSP adapter settings that
1187    /// are valid in both user and project settings.
1188    fn configure_schema_generator(
1189        generator: &mut schemars::SchemaGenerator,
1190        params: &SettingsJsonSchemaParams,
1191    ) {
1192        let language_settings_content_ref = generator
1193            .subschema_for::<LanguageSettingsContent>()
1194            .to_value();
1195
1196        if !params.language_names.is_empty() {
1197            replace_subschema::<LanguageToSettingsMap>(generator, || {
1198                json_schema!({
1199                    "type": "object",
1200                    "errorMessage": "No language with this name is installed.",
1201                    "properties": params.language_names.iter().map(|name| (name.clone(), language_settings_content_ref.clone())).collect::<serde_json::Map<_, _>>()
1202                })
1203            });
1204
1205            let file_type_patterns_ref =
1206                generator.subschema_for::<ExtendingSet<String>>().to_value();
1207            replace_subschema::<FileTypeMap>(generator, || {
1208                json_schema!({
1209                    "type": "object",
1210                    "errorMessage": "No language with this name is installed.",
1211                    "properties": params.language_names.iter().map(|name| (name.clone(), file_type_patterns_ref.clone())).collect::<serde_json::Map<_, _>>(),
1212                    "additionalProperties": file_type_patterns_ref.clone()
1213                })
1214            });
1215        }
1216
1217        generator.subschema_for::<LspSettings>();
1218
1219        let lsp_settings_definition = generator
1220            .definitions()
1221            .get("LspSettings")
1222            .expect("LspSettings should be defined")
1223            .clone();
1224
1225        if !params.lsp_adapter_names.is_empty() {
1226            replace_subschema::<LspSettingsMap>(generator, || {
1227                let mut lsp_properties = serde_json::Map::new();
1228
1229                for adapter_name in params.lsp_adapter_names {
1230                    let mut base_lsp_settings = lsp_settings_definition
1231                        .as_object()
1232                        .expect("LspSettings should be an object")
1233                        .clone();
1234
1235                    if let Some(properties) = base_lsp_settings.get_mut("properties") {
1236                        if let Some(properties_object) = properties.as_object_mut() {
1237                            properties_object.insert(
1238                            "initialization_options".to_string(),
1239                            serde_json::json!({
1240                                "$ref": format!("{LSP_SETTINGS_SCHEMA_URL_PREFIX}{adapter_name}/initialization_options")
1241                            }),
1242                        );
1243                            properties_object.insert(
1244                            "settings".to_string(),
1245                            serde_json::json!({
1246                                "$ref": format!("{LSP_SETTINGS_SCHEMA_URL_PREFIX}{adapter_name}/settings")
1247                            }),
1248                        );
1249                        }
1250                    }
1251
1252                    lsp_properties.insert(
1253                        adapter_name.clone(),
1254                        serde_json::Value::Object(base_lsp_settings),
1255                    );
1256                }
1257
1258                json_schema!({
1259                    "type": "object",
1260                    "properties": lsp_properties
1261                })
1262            });
1263        }
1264    }
1265
1266    pub fn json_schema(params: &SettingsJsonSchemaParams) -> Value {
1267        let mut generator = schemars::generate::SchemaSettings::draft2019_09()
1268            .with_transform(DefaultDenyUnknownFields)
1269            .with_transform(AllowTrailingCommas)
1270            .into_generator();
1271
1272        UserSettingsContent::json_schema(&mut generator);
1273        Self::configure_schema_generator(&mut generator, params);
1274
1275        if !params.font_names.is_empty() {
1276            replace_subschema::<FontFamilyName>(&mut generator, || {
1277                json_schema!({
1278                     "type": "string",
1279                     "enum": params.font_names,
1280                })
1281            });
1282        }
1283
1284        if !params.theme_names.is_empty() {
1285            replace_subschema::<ThemeName>(&mut generator, || {
1286                json_schema!({
1287                    "type": "string",
1288                    "enum": params.theme_names,
1289                })
1290            });
1291        }
1292
1293        if !params.icon_theme_names.is_empty() {
1294            replace_subschema::<IconThemeName>(&mut generator, || {
1295                json_schema!({
1296                    "type": "string",
1297                    "enum": params.icon_theme_names,
1298                })
1299            });
1300        }
1301
1302        if !params.action_names.is_empty() {
1303            replace_subschema::<CommandAliasTarget>(&mut generator, || {
1304                CommandAliasTarget::build_schema(
1305                    params.action_names,
1306                    params.action_documentation,
1307                    params.deprecations,
1308                    params.deprecation_messages,
1309                )
1310            });
1311        }
1312
1313        generator
1314            .root_schema_for::<UserSettingsContent>()
1315            .to_value()
1316    }
1317
1318    /// Generate JSON schema for project settings, including only settings valid
1319    /// for project-level configurations.
1320    pub fn project_json_schema(params: &SettingsJsonSchemaParams) -> Value {
1321        let mut generator = schemars::generate::SchemaSettings::draft2019_09()
1322            .with_transform(DefaultDenyUnknownFields)
1323            .with_transform(AllowTrailingCommas)
1324            .into_generator();
1325
1326        ProjectSettingsContent::json_schema(&mut generator);
1327        Self::configure_schema_generator(&mut generator, params);
1328
1329        generator
1330            .root_schema_for::<ProjectSettingsContent>()
1331            .to_value()
1332    }
1333
1334    fn recompute_values(
1335        &mut self,
1336        changed_local_path: Option<(WorktreeId, &RelPath)>,
1337        cx: &mut App,
1338    ) {
1339        // Reload the global and local values for every setting.
1340        let mut project_settings_stack = Vec::<SettingsContent>::new();
1341        let mut paths_stack = Vec::<Option<(WorktreeId, &RelPath)>>::new();
1342
1343        if changed_local_path.is_none() {
1344            let mut merged = self.default_settings.as_ref().clone();
1345            merged.merge_from_option(self.extension_settings.as_deref());
1346            merged.merge_from_option(self.global_settings.as_deref());
1347            if let Some(user_settings) = self.user_settings.as_ref() {
1348                let active_profile = user_settings.for_profile(cx);
1349                let should_merge_user_settings =
1350                    active_profile.is_none_or(|profile| profile.base == ProfileBase::User);
1351
1352                if should_merge_user_settings {
1353                    merged.merge_from(&user_settings.content);
1354                    merged.merge_from_option(user_settings.for_release_channel());
1355                    merged.merge_from_option(user_settings.for_os());
1356                }
1357
1358                if let Some(profile) = active_profile {
1359                    merged.merge_from(&profile.settings);
1360                }
1361            }
1362            merged.merge_from_option(self.server_settings.as_deref());
1363
1364            // Merge `disable_ai` from all project/local settings into the global value.
1365            // Since `SaturatingBool` uses OR logic, if any project has `disable_ai: true`,
1366            // the global value will be true. This allows project-level `disable_ai` to
1367            // affect the global setting used by UI elements without file context.
1368            for local_settings in self.local_settings.values() {
1369                merged
1370                    .project
1371                    .disable_ai
1372                    .merge_from(&local_settings.project.disable_ai);
1373            }
1374
1375            self.merged_settings = Rc::new(merged);
1376
1377            for setting_value in self.setting_values.values_mut() {
1378                let value = setting_value.from_settings(&self.merged_settings);
1379                setting_value.set_global_value(value);
1380            }
1381        } else {
1382            // When only a local path changed, we still need to recompute the global
1383            // `disable_ai` value since it depends on all local settings.
1384            let mut merged = (*self.merged_settings).clone();
1385            // Reset disable_ai to compute fresh from base settings
1386            merged.project.disable_ai = self.default_settings.project.disable_ai;
1387            if let Some(global) = &self.global_settings {
1388                merged
1389                    .project
1390                    .disable_ai
1391                    .merge_from(&global.project.disable_ai);
1392            }
1393            if let Some(user) = &self.user_settings {
1394                merged
1395                    .project
1396                    .disable_ai
1397                    .merge_from(&user.content.project.disable_ai);
1398            }
1399            if let Some(server) = &self.server_settings {
1400                merged
1401                    .project
1402                    .disable_ai
1403                    .merge_from(&server.project.disable_ai);
1404            }
1405            for local_settings in self.local_settings.values() {
1406                merged
1407                    .project
1408                    .disable_ai
1409                    .merge_from(&local_settings.project.disable_ai);
1410            }
1411            self.merged_settings = Rc::new(merged);
1412
1413            for setting_value in self.setting_values.values_mut() {
1414                let value = setting_value.from_settings(&self.merged_settings);
1415                setting_value.set_global_value(value);
1416            }
1417        }
1418
1419        for ((root_id, directory_path), local_settings) in &self.local_settings {
1420            // Build a stack of all of the local values for that setting.
1421            while let Some(prev_entry) = paths_stack.last() {
1422                if let Some((prev_root_id, prev_path)) = prev_entry
1423                    && (root_id != prev_root_id || !directory_path.starts_with(prev_path))
1424                {
1425                    paths_stack.pop();
1426                    project_settings_stack.pop();
1427                    continue;
1428                }
1429                break;
1430            }
1431
1432            paths_stack.push(Some((*root_id, directory_path.as_ref())));
1433            let mut merged_local_settings = if let Some(deepest) = project_settings_stack.last() {
1434                (*deepest).clone()
1435            } else {
1436                self.merged_settings.as_ref().clone()
1437            };
1438            merged_local_settings.merge_from(local_settings);
1439
1440            project_settings_stack.push(merged_local_settings);
1441
1442            // If a local settings file changed, then avoid recomputing local
1443            // settings for any path outside of that directory.
1444            if changed_local_path.is_some_and(|(changed_root_id, changed_local_path)| {
1445                *root_id != changed_root_id || !directory_path.starts_with(changed_local_path)
1446            }) {
1447                continue;
1448            }
1449
1450            for setting_value in self.setting_values.values_mut() {
1451                let value = setting_value.from_settings(&project_settings_stack.last().unwrap());
1452                setting_value.set_local_value(*root_id, directory_path.clone(), value);
1453            }
1454        }
1455    }
1456}
1457
1458/// The result of parsing settings, including any migration attempts
1459#[derive(Debug, Clone, PartialEq, Eq)]
1460pub struct SettingsParseResult {
1461    /// The result of parsing the settings file (possibly after migration)
1462    pub parse_status: ParseStatus,
1463    /// The result of attempting to migrate the settings file
1464    pub migration_status: MigrationStatus,
1465}
1466
1467#[derive(Debug, Clone, PartialEq, Eq)]
1468pub enum MigrationStatus {
1469    /// No migration was needed - settings are up to date
1470    NotNeeded,
1471    /// Settings were automatically migrated in memory, but the file needs to be updated
1472    Succeeded,
1473    /// Migration was attempted but failed. Original settings were parsed instead.
1474    Failed { error: String },
1475}
1476
1477impl Default for SettingsParseResult {
1478    fn default() -> Self {
1479        Self {
1480            parse_status: ParseStatus::Success,
1481            migration_status: MigrationStatus::NotNeeded,
1482        }
1483    }
1484}
1485
1486impl SettingsParseResult {
1487    pub fn unwrap(self) -> bool {
1488        self.result().unwrap()
1489    }
1490
1491    pub fn expect(self, message: &str) -> bool {
1492        self.result().expect(message)
1493    }
1494
1495    /// Formats the ParseResult as a Result type. This is a lossy conversion
1496    pub fn result(self) -> Result<bool> {
1497        let migration_result = match self.migration_status {
1498            MigrationStatus::NotNeeded => Ok(false),
1499            MigrationStatus::Succeeded => Ok(true),
1500            MigrationStatus::Failed { error } => {
1501                Err(anyhow::format_err!(error)).context("Failed to migrate settings")
1502            }
1503        };
1504
1505        let parse_result = match self.parse_status {
1506            ParseStatus::Success | ParseStatus::Unchanged => Ok(()),
1507            ParseStatus::Failed { error } => {
1508                Err(anyhow::format_err!(error)).context("Failed to parse settings")
1509            }
1510        };
1511
1512        match (migration_result, parse_result) {
1513            (migration_result @ Ok(_), Ok(())) => migration_result,
1514            (Err(migration_err), Ok(())) => Err(migration_err),
1515            (_, Err(parse_err)) => Err(parse_err),
1516        }
1517    }
1518
1519    /// Returns true if there were any errors migrating and parsing the settings content or if migration was required but there were no errors
1520    pub fn requires_user_action(&self) -> bool {
1521        matches!(self.parse_status, ParseStatus::Failed { .. })
1522            || matches!(
1523                self.migration_status,
1524                MigrationStatus::Succeeded | MigrationStatus::Failed { .. }
1525            )
1526    }
1527
1528    pub fn ok(self) -> Option<bool> {
1529        self.result().ok()
1530    }
1531
1532    pub fn parse_error(&self) -> Option<String> {
1533        match &self.parse_status {
1534            ParseStatus::Failed { error } => Some(error.clone()),
1535            ParseStatus::Success | ParseStatus::Unchanged => None,
1536        }
1537    }
1538}
1539
1540#[derive(Debug, Clone, PartialEq)]
1541pub enum InvalidSettingsError {
1542    LocalSettings {
1543        path: Arc<RelPath>,
1544        message: String,
1545    },
1546    UserSettings {
1547        message: String,
1548    },
1549    ServerSettings {
1550        message: String,
1551    },
1552    DefaultSettings {
1553        message: String,
1554    },
1555    Editorconfig {
1556        path: LocalSettingsPath,
1557        message: String,
1558    },
1559    Tasks {
1560        path: PathBuf,
1561        message: String,
1562    },
1563    Debug {
1564        path: PathBuf,
1565        message: String,
1566    },
1567}
1568
1569impl std::fmt::Display for InvalidSettingsError {
1570    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1571        match self {
1572            InvalidSettingsError::LocalSettings { message, .. }
1573            | InvalidSettingsError::UserSettings { message }
1574            | InvalidSettingsError::ServerSettings { message }
1575            | InvalidSettingsError::DefaultSettings { message }
1576            | InvalidSettingsError::Tasks { message, .. }
1577            | InvalidSettingsError::Editorconfig { message, .. }
1578            | InvalidSettingsError::Debug { message, .. } => write!(f, "{message}"),
1579        }
1580    }
1581}
1582impl std::error::Error for InvalidSettingsError {}
1583
1584impl Debug for SettingsStore {
1585    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1586        f.debug_struct("SettingsStore")
1587            .field(
1588                "types",
1589                &self
1590                    .setting_values
1591                    .values()
1592                    .map(|value| value.setting_type_name())
1593                    .collect::<Vec<_>>(),
1594            )
1595            .field("default_settings", &self.default_settings)
1596            .field("user_settings", &self.user_settings)
1597            .field("local_settings", &self.local_settings)
1598            .finish_non_exhaustive()
1599    }
1600}
1601
1602impl<T: Settings> AnySettingValue for SettingValue<T> {
1603    fn from_settings(&self, s: &SettingsContent) -> Box<dyn Any> {
1604        Box::new(T::from_settings(s)) as _
1605    }
1606
1607    fn setting_type_name(&self) -> &'static str {
1608        type_name::<T>()
1609    }
1610
1611    fn all_local_values(&self) -> Vec<(WorktreeId, Arc<RelPath>, &dyn Any)> {
1612        self.local_values
1613            .iter()
1614            .map(|(id, path, value)| (*id, path.clone(), value as _))
1615            .collect()
1616    }
1617
1618    fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any {
1619        if let Some(SettingsLocation { worktree_id, path }) = path {
1620            for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {
1621                if worktree_id == *settings_root_id && path.starts_with(settings_path) {
1622                    return value;
1623                }
1624            }
1625        }
1626
1627        self.global_value
1628            .as_ref()
1629            .unwrap_or_else(|| panic!("no default value for setting {}", self.setting_type_name()))
1630    }
1631
1632    fn set_global_value(&mut self, value: Box<dyn Any>) {
1633        self.global_value = Some(*value.downcast().unwrap());
1634    }
1635
1636    fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<RelPath>, value: Box<dyn Any>) {
1637        let value = *value.downcast().unwrap();
1638        match self
1639            .local_values
1640            .binary_search_by_key(&(root_id, &path), |e| (e.0, &e.1))
1641        {
1642            Ok(ix) => self.local_values[ix].2 = value,
1643            Err(ix) => self.local_values.insert(ix, (root_id, path, value)),
1644        }
1645    }
1646
1647    fn clear_local_values(&mut self, root_id: WorktreeId) {
1648        self.local_values
1649            .retain(|(worktree_id, _, _)| *worktree_id != root_id);
1650    }
1651}
1652
1653#[cfg(test)]
1654mod tests {
1655    use std::{cell::RefCell, num::NonZeroU32};
1656
1657    use crate::{
1658        ClosePosition, ItemSettingsContent, VsCodeSettingsSource, default_settings,
1659        settings_content::LanguageSettingsContent, test_settings,
1660    };
1661
1662    use super::*;
1663    use fs::FakeFs;
1664    use unindent::Unindent;
1665    use util::rel_path::rel_path;
1666
1667    #[derive(Debug, PartialEq)]
1668    struct AutoUpdateSetting {
1669        auto_update: bool,
1670    }
1671
1672    impl Settings for AutoUpdateSetting {
1673        fn from_settings(content: &SettingsContent) -> Self {
1674            AutoUpdateSetting {
1675                auto_update: content.auto_update.unwrap(),
1676            }
1677        }
1678    }
1679
1680    #[derive(Debug, PartialEq)]
1681    struct ItemSettings {
1682        close_position: ClosePosition,
1683        git_status: bool,
1684    }
1685
1686    impl Settings for ItemSettings {
1687        fn from_settings(content: &SettingsContent) -> Self {
1688            let content = content.tabs.clone().unwrap();
1689            ItemSettings {
1690                close_position: content.close_position.unwrap(),
1691                git_status: content.git_status.unwrap(),
1692            }
1693        }
1694    }
1695
1696    #[derive(Debug, PartialEq)]
1697    struct DefaultLanguageSettings {
1698        tab_size: NonZeroU32,
1699        preferred_line_length: u32,
1700    }
1701
1702    impl Settings for DefaultLanguageSettings {
1703        fn from_settings(content: &SettingsContent) -> Self {
1704            let content = &content.project.all_languages.defaults;
1705            DefaultLanguageSettings {
1706                tab_size: content.tab_size.unwrap(),
1707                preferred_line_length: content.preferred_line_length.unwrap(),
1708            }
1709        }
1710    }
1711
1712    #[derive(Debug, PartialEq)]
1713    struct ThemeSettings {
1714        buffer_font_family: FontFamilyName,
1715        buffer_font_fallbacks: Vec<FontFamilyName>,
1716    }
1717
1718    impl Settings for ThemeSettings {
1719        fn from_settings(content: &SettingsContent) -> Self {
1720            let content = content.theme.clone();
1721            ThemeSettings {
1722                buffer_font_family: content.buffer_font_family.unwrap(),
1723                buffer_font_fallbacks: content.buffer_font_fallbacks.unwrap(),
1724            }
1725        }
1726    }
1727
1728    #[gpui::test]
1729    async fn test_update_settings_file_updates_store_before_watcher(cx: &mut gpui::TestAppContext) {
1730        let fs = FakeFs::new(cx.background_executor.clone());
1731        fs.create_dir(paths::settings_file().parent().unwrap())
1732            .await
1733            .unwrap();
1734        fs.insert_file(
1735            paths::settings_file(),
1736            r#"{ "tabs": { "close_position": "right" } }"#.as_bytes().to_vec(),
1737        )
1738        .await;
1739        fs.pause_events();
1740        cx.run_until_parked();
1741
1742        let success = SettingsParseResult {
1743            parse_status: ParseStatus::Success,
1744            migration_status: MigrationStatus::NotNeeded,
1745        };
1746        let parse_results = Rc::new(RefCell::new(Vec::new()));
1747
1748        cx.update(|cx| {
1749            let mut store = SettingsStore::new(cx, &default_settings());
1750            store.register_setting::<ItemSettings>();
1751            store.watch_settings_files(fs.clone(), cx, {
1752                let parse_results = parse_results.clone();
1753                move |_, result, _| {
1754                    parse_results.borrow_mut().push(result);
1755                }
1756            });
1757            cx.set_global(store);
1758        });
1759
1760        // Calling watch_settings_files loads user and global settings.
1761        assert_eq!(
1762            parse_results.borrow().as_slice(),
1763            &[success.clone(), success.clone()]
1764        );
1765        cx.update(|cx| {
1766            assert_eq!(
1767                cx.global::<SettingsStore>()
1768                    .get::<ItemSettings>(None)
1769                    .close_position,
1770                ClosePosition::Right
1771            );
1772        });
1773
1774        // Updating the settings file returns a channel that resolves once the settings are loaded.
1775        let rx = cx.update(|cx| {
1776            cx.global::<SettingsStore>()
1777                .update_settings_file_with_completion(fs.clone(), move |settings, _| {
1778                    settings.tabs.get_or_insert_default().close_position =
1779                        Some(ClosePosition::Left);
1780                })
1781        });
1782        assert!(rx.await.unwrap().is_ok());
1783        assert_eq!(
1784            parse_results.borrow().as_slice(),
1785            &[success.clone(), success.clone()]
1786        );
1787        cx.update(|cx| {
1788            assert_eq!(
1789                cx.global::<SettingsStore>()
1790                    .get::<ItemSettings>(None)
1791                    .close_position,
1792                ClosePosition::Left
1793            );
1794        });
1795
1796        // When the FS event occurs, the settings are recognized as unchanged.
1797        fs.flush_events(100);
1798        cx.run_until_parked();
1799        assert_eq!(
1800            parse_results.borrow().as_slice(),
1801            &[
1802                success.clone(),
1803                success.clone(),
1804                SettingsParseResult {
1805                    parse_status: ParseStatus::Unchanged,
1806                    migration_status: MigrationStatus::NotNeeded
1807                }
1808            ]
1809        );
1810    }
1811
1812    #[gpui::test]
1813    fn test_default_settings_release_channel_overrides(cx: &mut App) {
1814        // The test deals with overrides and should ignore the other set-ups (Preview and Stable runs)
1815        if *release_channel::RELEASE_CHANNEL != release_channel::ReleaseChannel::Dev {
1816            return;
1817        }
1818
1819        let mut defaults: serde_json::Value =
1820            crate::parse_json_with_comments(&default_settings()).unwrap();
1821        let root = defaults
1822            .as_object_mut()
1823            .expect("default settings must be a JSON object");
1824        root.insert("dev".into(), serde_json::json!({ "auto_update": false }));
1825        root.insert("stable".into(), serde_json::json!({ "auto_update": true }));
1826        let defaults_with_overrides = serde_json::to_string(&defaults).unwrap();
1827
1828        let mut store = SettingsStore::new(cx, &defaults_with_overrides);
1829        store.register_setting::<AutoUpdateSetting>();
1830
1831        assert_eq!(
1832            store.get::<AutoUpdateSetting>(None),
1833            &AutoUpdateSetting { auto_update: false },
1834            "dev override from default settings should apply",
1835        );
1836    }
1837
1838    #[gpui::test]
1839    fn test_settings_store_basic(cx: &mut App) {
1840        let mut store = SettingsStore::new(cx, &default_settings());
1841        store.register_setting::<AutoUpdateSetting>();
1842        store.register_setting::<ItemSettings>();
1843        store.register_setting::<DefaultLanguageSettings>();
1844
1845        // Omega ships `auto_update: false`; upstream Zed ships `true`. The
1846        // Omega value is asserted by app_identity's
1847        // `default_settings_enable_registry_acp_without_enabling_zed_production`.
1848        // What this test is actually about is settings layering, so the user
1849        // and local values below were flipped with it: each layer still has to
1850        // change the value it inherits, or the layering proves nothing.
1851        assert_eq!(
1852            store.get::<AutoUpdateSetting>(None),
1853            &AutoUpdateSetting {
1854                auto_update: false
1855            }
1856        );
1857        assert_eq!(
1858            store.get::<ItemSettings>(None).close_position,
1859            ClosePosition::Right
1860        );
1861
1862        store
1863            .set_user_settings(
1864                r#"{
1865                    "auto_update": true,
1866                    "tabs": {
1867                      "close_position": "left"
1868                    }
1869                }"#,
1870                cx,
1871            )
1872            .unwrap();
1873
1874        // User settings override the default.
1875        assert_eq!(
1876            store.get::<AutoUpdateSetting>(None),
1877            &AutoUpdateSetting { auto_update: true }
1878        );
1879        assert_eq!(
1880            store.get::<ItemSettings>(None).close_position,
1881            ClosePosition::Left
1882        );
1883
1884        store
1885            .set_local_settings(
1886                WorktreeId::from_usize(1),
1887                LocalSettingsPath::InWorktree(rel_path("root1").into()),
1888                LocalSettingsKind::Settings,
1889                Some(r#"{ "tab_size": 5 }"#),
1890                cx,
1891            )
1892            .unwrap();
1893        store
1894            .set_local_settings(
1895                WorktreeId::from_usize(1),
1896                LocalSettingsPath::InWorktree(rel_path("root1/subdir").into()),
1897                LocalSettingsKind::Settings,
1898                Some(r#"{ "preferred_line_length": 50 }"#),
1899                cx,
1900            )
1901            .unwrap();
1902
1903        store
1904            .set_local_settings(
1905                WorktreeId::from_usize(1),
1906                LocalSettingsPath::InWorktree(rel_path("root2").into()),
1907                LocalSettingsKind::Settings,
1908                Some(r#"{ "tab_size": 9, "auto_update": false}"#),
1909                cx,
1910            )
1911            .unwrap();
1912
1913        assert_eq!(
1914            store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1915                worktree_id: WorktreeId::from_usize(1),
1916                path: rel_path("root1/something"),
1917            })),
1918            &DefaultLanguageSettings {
1919                preferred_line_length: 80,
1920                tab_size: 5.try_into().unwrap(),
1921            }
1922        );
1923        assert_eq!(
1924            store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1925                worktree_id: WorktreeId::from_usize(1),
1926                path: rel_path("root1/subdir/something"),
1927            })),
1928            &DefaultLanguageSettings {
1929                preferred_line_length: 50,
1930                tab_size: 5.try_into().unwrap(),
1931            }
1932        );
1933        assert_eq!(
1934            store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1935                worktree_id: WorktreeId::from_usize(1),
1936                path: rel_path("root2/something"),
1937            })),
1938            &DefaultLanguageSettings {
1939                preferred_line_length: 80,
1940                tab_size: 9.try_into().unwrap(),
1941            }
1942        );
1943        // `auto_update` is not a project-local setting, so the `false` written
1944        // into root2's local settings above is ignored and the user value
1945        // stands. This is the assertion the flip above had to keep meaningful:
1946        // the local value must differ from the user value, or a local setting
1947        // silently winning would look identical to it being ignored.
1948        assert_eq!(
1949            store.get::<AutoUpdateSetting>(Some(SettingsLocation {
1950                worktree_id: WorktreeId::from_usize(1),
1951                path: rel_path("root2/something")
1952            })),
1953            &AutoUpdateSetting { auto_update: true }
1954        );
1955    }
1956
1957    #[gpui::test]
1958    fn test_setting_store_assign_json_before_register(cx: &mut App) {
1959        let mut store = SettingsStore::new(cx, &test_settings());
1960        store
1961            .set_user_settings(r#"{ "auto_update": false }"#, cx)
1962            .unwrap();
1963        store.register_setting::<AutoUpdateSetting>();
1964
1965        assert_eq!(
1966            store.get::<AutoUpdateSetting>(None),
1967            &AutoUpdateSetting { auto_update: false }
1968        );
1969    }
1970
1971    #[track_caller]
1972    fn check_settings_update(
1973        store: &mut SettingsStore,
1974        old_json: String,
1975        update: fn(&mut SettingsContent),
1976        expected_new_json: String,
1977        cx: &mut App,
1978    ) {
1979        store.set_user_settings(&old_json, cx).ok();
1980        let edits = store.edits_for_update(&old_json, update).unwrap();
1981        let mut new_json = old_json;
1982        for (range, replacement) in edits.into_iter() {
1983            new_json.replace_range(range, &replacement);
1984        }
1985        pretty_assertions::assert_eq!(new_json, expected_new_json);
1986    }
1987
1988    #[gpui::test]
1989    fn test_setting_store_update(cx: &mut App) {
1990        let mut store = SettingsStore::new(cx, &test_settings());
1991
1992        // entries added and updated
1993        check_settings_update(
1994            &mut store,
1995            r#"{
1996                "languages": {
1997                    "JSON": {
1998                        "auto_indent": "syntax_aware"
1999                    }
2000                }
2001            }"#
2002            .unindent(),
2003            |settings| {
2004                settings
2005                    .languages_mut()
2006                    .get_mut("JSON")
2007                    .unwrap()
2008                    .auto_indent = Some(crate::AutoIndentMode::None);
2009
2010                settings.languages_mut().insert(
2011                    "Rust".into(),
2012                    LanguageSettingsContent {
2013                        auto_indent: Some(crate::AutoIndentMode::SyntaxAware),
2014                        ..Default::default()
2015                    },
2016                );
2017            },
2018            r#"{
2019                "languages": {
2020                    "Rust": {
2021                        "auto_indent": "syntax_aware"
2022                    },
2023                    "JSON": {
2024                        "auto_indent": "none"
2025                    }
2026                }
2027            }"#
2028            .unindent(),
2029            cx,
2030        );
2031
2032        // entries removed
2033        check_settings_update(
2034            &mut store,
2035            r#"{
2036                "languages": {
2037                    "Rust": {
2038                        "language_setting_2": true
2039                    },
2040                    "JSON": {
2041                        "language_setting_1": false
2042                    }
2043                }
2044            }"#
2045            .unindent(),
2046            |settings| {
2047                settings.languages_mut().remove("JSON").unwrap();
2048            },
2049            r#"{
2050                "languages": {
2051                    "Rust": {
2052                        "language_setting_2": true
2053                    }
2054                }
2055            }"#
2056            .unindent(),
2057            cx,
2058        );
2059
2060        check_settings_update(
2061            &mut store,
2062            r#"{
2063                "languages": {
2064                    "Rust": {
2065                        "language_setting_2": true
2066                    },
2067                    "JSON": {
2068                        "language_setting_1": false
2069                    }
2070                }
2071            }"#
2072            .unindent(),
2073            |settings| {
2074                settings.languages_mut().remove("Rust").unwrap();
2075            },
2076            r#"{
2077                "languages": {
2078                    "JSON": {
2079                        "language_setting_1": false
2080                    }
2081                }
2082            }"#
2083            .unindent(),
2084            cx,
2085        );
2086
2087        // weird formatting
2088        check_settings_update(
2089            &mut store,
2090            r#"{
2091                "tabs":   { "close_position": "left", "name": "Max"  }
2092                }"#
2093            .unindent(),
2094            |settings| {
2095                settings.tabs.as_mut().unwrap().close_position = Some(ClosePosition::Left);
2096            },
2097            r#"{
2098                "tabs":   { "close_position": "left", "name": "Max"  }
2099                }"#
2100            .unindent(),
2101            cx,
2102        );
2103
2104        // single-line formatting, other keys
2105        check_settings_update(
2106            &mut store,
2107            r#"{ "one": 1, "two": 2 }"#.to_owned(),
2108            |settings| settings.auto_update = Some(true),
2109            r#"{ "auto_update": true, "one": 1, "two": 2 }"#.to_owned(),
2110            cx,
2111        );
2112
2113        // empty object
2114        check_settings_update(
2115            &mut store,
2116            r#"{
2117                "tabs": {}
2118            }"#
2119            .unindent(),
2120            |settings| settings.tabs.as_mut().unwrap().close_position = Some(ClosePosition::Left),
2121            r#"{
2122                "tabs": {
2123                    "close_position": "left"
2124                }
2125            }"#
2126            .unindent(),
2127            cx,
2128        );
2129
2130        // no content
2131        check_settings_update(
2132            &mut store,
2133            r#""#.unindent(),
2134            |settings| {
2135                settings.tabs = Some(ItemSettingsContent {
2136                    git_status: Some(true),
2137                    ..Default::default()
2138                })
2139            },
2140            r#"{
2141              "tabs": {
2142                "git_status": true
2143              }
2144            }
2145            "#
2146            .unindent(),
2147            cx,
2148        );
2149
2150        check_settings_update(
2151            &mut store,
2152            r#"{
2153            }
2154            "#
2155            .unindent(),
2156            |settings| settings.title_bar.get_or_insert_default().show_branch_name = Some(true),
2157            r#"{
2158              "title_bar": {
2159                "show_branch_name": true
2160              }
2161            }
2162            "#
2163            .unindent(),
2164            cx,
2165        );
2166    }
2167
2168    #[gpui::test]
2169    fn test_edits_for_update_preserves_unknown_keys(cx: &mut App) {
2170        let mut store = SettingsStore::new(cx, &test_settings());
2171        store.register_setting::<AutoUpdateSetting>();
2172
2173        let old_json = r#"{
2174            "some_unknown_key": "should_be_preserved",
2175            "auto_update": false
2176        }"#
2177        .unindent();
2178
2179        check_settings_update(
2180            &mut store,
2181            old_json,
2182            |settings| settings.auto_update = Some(true),
2183            r#"{
2184            "some_unknown_key": "should_be_preserved",
2185            "auto_update": true
2186        }"#
2187            .unindent(),
2188            cx,
2189        );
2190    }
2191
2192    #[gpui::test]
2193    fn test_edits_for_update_returns_error_on_invalid_json(cx: &mut App) {
2194        let store = SettingsStore::new(cx, &test_settings());
2195
2196        let invalid_json = r#"{ this is not valid json at all !!!"#;
2197        let result = store.edits_for_update(invalid_json, |_| {});
2198        assert!(result.is_err());
2199    }
2200
2201    #[gpui::test]
2202    fn test_vscode_import(cx: &mut App) {
2203        let mut store = SettingsStore::new(cx, &test_settings());
2204        store.register_setting::<DefaultLanguageSettings>();
2205        store.register_setting::<ItemSettings>();
2206        store.register_setting::<AutoUpdateSetting>();
2207        store.register_setting::<ThemeSettings>();
2208
2209        // create settings that werent present
2210        check_vscode_import(
2211            &mut store,
2212            r#"{
2213            }
2214            "#
2215            .unindent(),
2216            r#" { "editor.tabSize": 37 } "#.to_owned(),
2217            r#"{
2218              "base_keymap": "VSCode",
2219              "minimap": {
2220                "show": "always"
2221              },
2222              "tab_size": 37
2223            }
2224            "#
2225            .unindent(),
2226            cx,
2227        );
2228
2229        // persist settings that were present
2230        check_vscode_import(
2231            &mut store,
2232            r#"{
2233                "preferred_line_length": 99,
2234            }
2235            "#
2236            .unindent(),
2237            r#"{ "editor.tabSize": 42 }"#.to_owned(),
2238            r#"{
2239                "base_keymap": "VSCode",
2240                "minimap": {
2241                    "show": "always"
2242                },
2243                "tab_size": 42,
2244                "preferred_line_length": 99,
2245            }
2246            "#
2247            .unindent(),
2248            cx,
2249        );
2250
2251        // don't clobber settings that aren't present in vscode
2252        check_vscode_import(
2253            &mut store,
2254            r#"{
2255                "preferred_line_length": 99,
2256                "tab_size": 42
2257            }
2258            "#
2259            .unindent(),
2260            r#"{}"#.to_owned(),
2261            r#"{
2262                "base_keymap": "VSCode",
2263                "minimap": {
2264                    "show": "always"
2265                },
2266                "preferred_line_length": 99,
2267                "tab_size": 42
2268            }
2269            "#
2270            .unindent(),
2271            cx,
2272        );
2273
2274        // custom enum
2275        check_vscode_import(
2276            &mut store,
2277            r#"{
2278            }
2279            "#
2280            .unindent(),
2281            r#"{ "git.decorations.enabled": true }"#.to_owned(),
2282            r#"{
2283              "project_panel": {
2284                "git_status": true
2285              },
2286              "outline_panel": {
2287                "git_status": true
2288              },
2289              "base_keymap": "VSCode",
2290              "tabs": {
2291                "git_status": true
2292              },
2293              "minimap": {
2294                "show": "always"
2295              }
2296            }
2297            "#
2298            .unindent(),
2299            cx,
2300        );
2301
2302        // explorer sort settings
2303        check_vscode_import(
2304            &mut store,
2305            r#"{
2306            }
2307            "#
2308            .unindent(),
2309            r#"{
2310              "explorer.sortOrder": "mixed",
2311              "explorer.sortOrderLexicographicOptions": "lower"
2312            }"#
2313            .unindent(),
2314            r#"{
2315              "project_panel": {
2316                "sort_mode": "mixed",
2317                "sort_order": "lower"
2318              },
2319              "base_keymap": "VSCode",
2320              "minimap": {
2321                "show": "always"
2322              }
2323            }
2324            "#
2325            .unindent(),
2326            cx,
2327        );
2328
2329        // font-family
2330        check_vscode_import(
2331            &mut store,
2332            r#"{
2333            }
2334            "#
2335            .unindent(),
2336            r#"{ "editor.fontFamily": "Cascadia Code, 'Consolas', Courier New" }"#.to_owned(),
2337            r#"{
2338              "base_keymap": "VSCode",
2339              "minimap": {
2340                "show": "always"
2341              },
2342              "buffer_font_fallbacks": [
2343                "Consolas",
2344                "Courier New"
2345              ],
2346              "buffer_font_family": "Cascadia Code"
2347            }
2348            "#
2349            .unindent(),
2350            cx,
2351        );
2352
2353        // terminal bell settings - newer accessibility setting
2354        check_vscode_import(
2355            &mut store,
2356            r#"{
2357            }
2358            "#
2359            .unindent(),
2360            r#"{ "accessibility.signals.terminalBell": { "sound": "on" } }"#.to_owned(),
2361            r#"{
2362              "terminal": {
2363                "bell": "system"
2364              },
2365              "base_keymap": "VSCode",
2366              "minimap": {
2367                "show": "always"
2368              }
2369            }
2370            "#
2371            .unindent(),
2372            cx,
2373        );
2374
2375        // terminal bell settings - newer accessibility setting disabled
2376        check_vscode_import(
2377            &mut store,
2378            r#"{
2379            }
2380            "#
2381            .unindent(),
2382            r#"{ "accessibility.signals.terminalBell": { "sound": "off" } }"#.to_owned(),
2383            r#"{
2384              "terminal": {
2385                "bell": "off"
2386              },
2387              "base_keymap": "VSCode",
2388              "minimap": {
2389                "show": "always"
2390              }
2391            }
2392            "#
2393            .unindent(),
2394            cx,
2395        );
2396
2397        // terminal bell settings - older enableBell setting (true)
2398        check_vscode_import(
2399            &mut store,
2400            r#"{
2401            }
2402            "#
2403            .unindent(),
2404            r#"{ "terminal.integrated.enableBell": true }"#.to_owned(),
2405            r#"{
2406              "terminal": {
2407                "bell": "system"
2408              },
2409              "base_keymap": "VSCode",
2410              "minimap": {
2411                "show": "always"
2412              }
2413            }
2414            "#
2415            .unindent(),
2416            cx,
2417        );
2418
2419        // terminal bell settings - older enableBell setting (false)
2420        check_vscode_import(
2421            &mut store,
2422            r#"{
2423            }
2424            "#
2425            .unindent(),
2426            r#"{ "terminal.integrated.enableBell": false }"#.to_owned(),
2427            r#"{
2428              "terminal": {
2429                "bell": "off"
2430              },
2431              "base_keymap": "VSCode",
2432              "minimap": {
2433                "show": "always"
2434              }
2435            }
2436            "#
2437            .unindent(),
2438            cx,
2439        );
2440
2441        // newer accessibility setting takes precedence over older enableBell
2442        check_vscode_import(
2443            &mut store,
2444            r#"{
2445            }
2446            "#
2447            .unindent(),
2448            r#"{
2449              "accessibility.signals.terminalBell": { "sound": "off" },
2450              "terminal.integrated.enableBell": true
2451            }"#
2452            .to_owned(),
2453            r#"{
2454              "terminal": {
2455                "bell": "off"
2456              },
2457              "base_keymap": "VSCode",
2458              "minimap": {
2459                "show": "always"
2460              }
2461            }
2462            "#
2463            .unindent(),
2464            cx,
2465        );
2466
2467        // hover sticky settings
2468        check_vscode_import(
2469            &mut store,
2470            r#"{
2471            }
2472            "#
2473            .unindent(),
2474            r#"{
2475              "editor.hover.sticky": false,
2476              "editor.hover.hidingDelay": 500
2477            }"#
2478            .to_owned(),
2479            r#"{
2480              "base_keymap": "VSCode",
2481              "minimap": {
2482                "show": "always"
2483              },
2484              "hover_popover_hiding_delay": 500,
2485              "hover_popover_sticky": false
2486            }
2487            "#
2488            .unindent(),
2489            cx,
2490        );
2491
2492        // formatOnSave: true with formatOnSaveMode: modificationsIfAvailable
2493        check_vscode_import(
2494            &mut store,
2495            r#"{
2496            }
2497            "#
2498            .unindent(),
2499            r#"{ "editor.formatOnSave": true, "editor.formatOnSaveMode": "modificationsIfAvailable" }"#
2500                .to_owned(),
2501            r#"{
2502              "base_keymap": "VSCode",
2503              "minimap": {
2504                "show": "always"
2505              },
2506              "format_on_save": "modifications_if_available"
2507            }
2508            "#
2509            .unindent(),
2510            cx,
2511        );
2512
2513        // formatOnSave: true with formatOnSaveMode: modifications
2514        check_vscode_import(
2515            &mut store,
2516            r#"{
2517            }
2518            "#
2519            .unindent(),
2520            r#"{ "editor.formatOnSave": true, "editor.formatOnSaveMode": "modifications" }"#
2521                .to_owned(),
2522            r#"{
2523              "base_keymap": "VSCode",
2524              "minimap": {
2525                "show": "always"
2526              },
2527              "format_on_save": "modifications"
2528            }
2529            "#
2530            .unindent(),
2531            cx,
2532        );
2533
2534        // formatOnSave: true with formatOnSaveMode: file
2535        check_vscode_import(
2536            &mut store,
2537            r#"{
2538            }
2539            "#
2540            .unindent(),
2541            r#"{ "editor.formatOnSave": true, "editor.formatOnSaveMode": "file" }"#.to_owned(),
2542            r#"{
2543              "base_keymap": "VSCode",
2544              "minimap": {
2545                "show": "always"
2546              },
2547              "format_on_save": "on"
2548            }
2549            "#
2550            .unindent(),
2551            cx,
2552        );
2553
2554        // formatOnSaveMode is ignored when formatOnSave is disabled, as in VS Code
2555        check_vscode_import(
2556            &mut store,
2557            r#"{
2558            }
2559            "#
2560            .unindent(),
2561            r#"{ "editor.formatOnSave": false, "editor.formatOnSaveMode": "modifications" }"#
2562                .to_owned(),
2563            r#"{
2564              "base_keymap": "VSCode",
2565              "minimap": {
2566                "show": "always"
2567              },
2568              "format_on_save": "off"
2569            }
2570            "#
2571            .unindent(),
2572            cx,
2573        );
2574
2575        // formatOnSaveMode alone does nothing, as formatOnSave defaults to false in VS Code
2576        check_vscode_import(
2577            &mut store,
2578            r#"{
2579            }
2580            "#
2581            .unindent(),
2582            r#"{ "editor.formatOnSaveMode": "modifications" }"#.to_owned(),
2583            r#"{
2584              "base_keymap": "VSCode",
2585              "minimap": {
2586                "show": "always"
2587              }
2588            }
2589            "#
2590            .unindent(),
2591            cx,
2592        );
2593
2594        // formatOnSaveMode not set, formatOnSave: true
2595        check_vscode_import(
2596            &mut store,
2597            r#"{
2598            }
2599            "#
2600            .unindent(),
2601            r#"{ "editor.formatOnSave": true }"#.to_owned(),
2602            r#"{
2603              "base_keymap": "VSCode",
2604              "minimap": {
2605                "show": "always"
2606              },
2607              "format_on_save": "on"
2608            }
2609            "#
2610            .unindent(),
2611            cx,
2612        );
2613
2614        // formatOnSaveMode not set, formatOnSave: false
2615        check_vscode_import(
2616            &mut store,
2617            r#"{
2618            }
2619            "#
2620            .unindent(),
2621            r#"{ "editor.formatOnSave": false }"#.to_owned(),
2622            r#"{
2623              "base_keymap": "VSCode",
2624              "minimap": {
2625                "show": "always"
2626              },
2627              "format_on_save": "off"
2628            }
2629            "#
2630            .unindent(),
2631            cx,
2632        );
2633
2634        // re-importing a file association that is already present should be
2635        // idempotent rather than appending a duplicate extension (#56536)
2636        check_vscode_import(
2637            &mut store,
2638            r#"{
2639              "file_types": {
2640                "c": ["*.keymap"]
2641              }
2642            }
2643            "#
2644            .unindent(),
2645            r#"{ "files.associations": { "*.keymap": "c" } }"#.to_owned(),
2646            r#"{
2647              "base_keymap": "VSCode",
2648              "minimap": {
2649                "show": "always"
2650              },
2651              "file_types": {
2652                "c": ["*.keymap"]
2653              }
2654            }
2655            "#
2656            .unindent(),
2657            cx,
2658        );
2659    }
2660
2661    #[track_caller]
2662    fn check_vscode_import(
2663        store: &mut SettingsStore,
2664        old: String,
2665        vscode: String,
2666        expected: String,
2667        cx: &mut App,
2668    ) {
2669        store.set_user_settings(&old, cx).ok();
2670        let new = store
2671            .get_vscode_edits(
2672                old,
2673                &VsCodeSettings::from_str(&vscode, VsCodeSettingsSource::VsCode).unwrap(),
2674            )
2675            .unwrap();
2676        pretty_assertions::assert_eq!(new, expected);
2677    }
2678
2679    #[gpui::test]
2680    fn test_update_git_settings(cx: &mut App) {
2681        let store = SettingsStore::new(cx, &test_settings());
2682
2683        let actual = store
2684            .new_text_for_update("{}".to_string(), |current| {
2685                current
2686                    .git
2687                    .get_or_insert_default()
2688                    .inline_blame
2689                    .get_or_insert_default()
2690                    .enabled = Some(true);
2691            })
2692            .unwrap();
2693        pretty_assertions::assert_str_eq!(
2694            actual,
2695            r#"{
2696              "git": {
2697                "inline_blame": {
2698                  "enabled": true
2699                }
2700              }
2701            }
2702            "#
2703            .unindent()
2704        );
2705    }
2706
2707    #[gpui::test]
2708    fn test_global_settings(cx: &mut App) {
2709        let mut store = SettingsStore::new(cx, &test_settings());
2710        store.register_setting::<ItemSettings>();
2711
2712        // Set global settings - these should override defaults but not user settings
2713        store
2714            .set_global_settings(
2715                r#"{
2716                    "tabs": {
2717                        "close_position": "right",
2718                        "git_status": true,
2719                    }
2720                }"#,
2721                cx,
2722            )
2723            .unwrap();
2724
2725        // Before user settings, global settings should apply
2726        assert_eq!(
2727            store.get::<ItemSettings>(None),
2728            &ItemSettings {
2729                close_position: ClosePosition::Right,
2730                git_status: true,
2731            }
2732        );
2733
2734        // Set user settings - these should override both defaults and global
2735        store
2736            .set_user_settings(
2737                r#"{
2738                    "tabs": {
2739                        "close_position": "left"
2740                    }
2741                }"#,
2742                cx,
2743            )
2744            .unwrap();
2745
2746        // User settings should override global settings
2747        assert_eq!(
2748            store.get::<ItemSettings>(None),
2749            &ItemSettings {
2750                close_position: ClosePosition::Left,
2751                git_status: true, // Staff from global settings
2752            }
2753        );
2754    }
2755
2756    #[gpui::test]
2757    fn test_get_value_for_field_basic(cx: &mut App) {
2758        let mut store = SettingsStore::new(cx, &test_settings());
2759        store.register_setting::<DefaultLanguageSettings>();
2760
2761        store
2762            .set_user_settings(r#"{"preferred_line_length": 0}"#, cx)
2763            .unwrap();
2764        let local = (WorktreeId::from_usize(0), RelPath::empty_arc());
2765        store
2766            .set_local_settings(
2767                local.0,
2768                LocalSettingsPath::InWorktree(local.1.clone()),
2769                LocalSettingsKind::Settings,
2770                Some(r#"{}"#),
2771                cx,
2772            )
2773            .unwrap();
2774
2775        fn get(content: &SettingsContent) -> Option<&u32> {
2776            content
2777                .project
2778                .all_languages
2779                .defaults
2780                .preferred_line_length
2781                .as_ref()
2782        }
2783
2784        let default_value = *get(&store.default_settings).unwrap();
2785
2786        assert_eq!(
2787            store.get_value_from_file(SettingsFile::Project(local.clone()), get),
2788            (SettingsFile::User, Some(&0))
2789        );
2790        assert_eq!(
2791            store.get_value_from_file(SettingsFile::User, get),
2792            (SettingsFile::User, Some(&0))
2793        );
2794        store.set_user_settings(r#"{}"#, cx).unwrap();
2795        assert_eq!(
2796            store.get_value_from_file(SettingsFile::Project(local.clone()), get),
2797            (SettingsFile::Default, Some(&default_value))
2798        );
2799        store
2800            .set_local_settings(
2801                local.0,
2802                LocalSettingsPath::InWorktree(local.1.clone()),
2803                LocalSettingsKind::Settings,
2804                Some(r#"{"preferred_line_length": 80}"#),
2805                cx,
2806            )
2807            .unwrap();
2808        assert_eq!(
2809            store.get_value_from_file(SettingsFile::Project(local.clone()), get),
2810            (SettingsFile::Project(local), Some(&80))
2811        );
2812        assert_eq!(
2813            store.get_value_from_file(SettingsFile::User, get),
2814            (SettingsFile::Default, Some(&default_value))
2815        );
2816    }
2817
2818    #[gpui::test]
2819    fn test_get_value_for_field_local_worktrees_dont_interfere(cx: &mut App) {
2820        let mut store = SettingsStore::new(cx, &test_settings());
2821        store.register_setting::<DefaultLanguageSettings>();
2822        store.register_setting::<AutoUpdateSetting>();
2823
2824        let local_1 = (WorktreeId::from_usize(0), RelPath::empty_arc());
2825
2826        let local_1_child = (
2827            WorktreeId::from_usize(0),
2828            RelPath::new(std::path::Path::new("child1"), util::paths::PathStyle::Unix)
2829                .unwrap()
2830                .into_arc(),
2831        );
2832
2833        let local_2 = (WorktreeId::from_usize(1), RelPath::empty_arc());
2834        let local_2_child = (
2835            WorktreeId::from_usize(1),
2836            RelPath::new(std::path::Path::new("child2"), util::paths::PathStyle::Unix)
2837                .unwrap()
2838                .into_arc(),
2839        );
2840
2841        fn get(content: &SettingsContent) -> Option<&u32> {
2842            content
2843                .project
2844                .all_languages
2845                .defaults
2846                .preferred_line_length
2847                .as_ref()
2848        }
2849
2850        store
2851            .set_local_settings(
2852                local_1.0,
2853                LocalSettingsPath::InWorktree(local_1.1.clone()),
2854                LocalSettingsKind::Settings,
2855                Some(r#"{"preferred_line_length": 1}"#),
2856                cx,
2857            )
2858            .unwrap();
2859        store
2860            .set_local_settings(
2861                local_1_child.0,
2862                LocalSettingsPath::InWorktree(local_1_child.1.clone()),
2863                LocalSettingsKind::Settings,
2864                Some(r#"{}"#),
2865                cx,
2866            )
2867            .unwrap();
2868        store
2869            .set_local_settings(
2870                local_2.0,
2871                LocalSettingsPath::InWorktree(local_2.1.clone()),
2872                LocalSettingsKind::Settings,
2873                Some(r#"{"preferred_line_length": 2}"#),
2874                cx,
2875            )
2876            .unwrap();
2877        store
2878            .set_local_settings(
2879                local_2_child.0,
2880                LocalSettingsPath::InWorktree(local_2_child.1.clone()),
2881                LocalSettingsKind::Settings,
2882                Some(r#"{}"#),
2883                cx,
2884            )
2885            .unwrap();
2886
2887        // each local child should only inherit from it's parent
2888        assert_eq!(
2889            store.get_value_from_file(SettingsFile::Project(local_2_child), get),
2890            (SettingsFile::Project(local_2), Some(&2))
2891        );
2892        assert_eq!(
2893            store.get_value_from_file(SettingsFile::Project(local_1_child.clone()), get),
2894            (SettingsFile::Project(local_1.clone()), Some(&1))
2895        );
2896
2897        // adjacent children should be treated as siblings not inherit from each other
2898        let local_1_adjacent_child = (local_1.0, rel_path("adjacent_child").into_arc());
2899        store
2900            .set_local_settings(
2901                local_1_adjacent_child.0,
2902                LocalSettingsPath::InWorktree(local_1_adjacent_child.1.clone()),
2903                LocalSettingsKind::Settings,
2904                Some(r#"{}"#),
2905                cx,
2906            )
2907            .unwrap();
2908        store
2909            .set_local_settings(
2910                local_1_child.0,
2911                LocalSettingsPath::InWorktree(local_1_child.1.clone()),
2912                LocalSettingsKind::Settings,
2913                Some(r#"{"preferred_line_length": 3}"#),
2914                cx,
2915            )
2916            .unwrap();
2917
2918        assert_eq!(
2919            store.get_value_from_file(SettingsFile::Project(local_1_adjacent_child.clone()), get),
2920            (SettingsFile::Project(local_1.clone()), Some(&1))
2921        );
2922        store
2923            .set_local_settings(
2924                local_1_adjacent_child.0,
2925                LocalSettingsPath::InWorktree(local_1_adjacent_child.1),
2926                LocalSettingsKind::Settings,
2927                Some(r#"{"preferred_line_length": 3}"#),
2928                cx,
2929            )
2930            .unwrap();
2931        store
2932            .set_local_settings(
2933                local_1_child.0,
2934                LocalSettingsPath::InWorktree(local_1_child.1.clone()),
2935                LocalSettingsKind::Settings,
2936                Some(r#"{}"#),
2937                cx,
2938            )
2939            .unwrap();
2940        assert_eq!(
2941            store.get_value_from_file(SettingsFile::Project(local_1_child), get),
2942            (SettingsFile::Project(local_1), Some(&1))
2943        );
2944    }
2945
2946    #[gpui::test]
2947    fn test_get_overrides_for_field(cx: &mut App) {
2948        let mut store = SettingsStore::new(cx, &test_settings());
2949        store.register_setting::<DefaultLanguageSettings>();
2950
2951        let wt0_root = (WorktreeId::from_usize(0), RelPath::empty_arc());
2952        let wt0_child1 = (WorktreeId::from_usize(0), rel_path("child1").into_arc());
2953        let wt0_child2 = (WorktreeId::from_usize(0), rel_path("child2").into_arc());
2954
2955        let wt1_root = (WorktreeId::from_usize(1), RelPath::empty_arc());
2956        let wt1_subdir = (WorktreeId::from_usize(1), rel_path("subdir").into_arc());
2957
2958        fn get(content: &SettingsContent) -> &Option<u32> {
2959            &content.project.all_languages.defaults.preferred_line_length
2960        }
2961
2962        store
2963            .set_user_settings(r#"{"preferred_line_length": 100}"#, cx)
2964            .unwrap();
2965
2966        store
2967            .set_local_settings(
2968                wt0_root.0,
2969                LocalSettingsPath::InWorktree(wt0_root.1.clone()),
2970                LocalSettingsKind::Settings,
2971                Some(r#"{"preferred_line_length": 80}"#),
2972                cx,
2973            )
2974            .unwrap();
2975        store
2976            .set_local_settings(
2977                wt0_child1.0,
2978                LocalSettingsPath::InWorktree(wt0_child1.1.clone()),
2979                LocalSettingsKind::Settings,
2980                Some(r#"{"preferred_line_length": 120}"#),
2981                cx,
2982            )
2983            .unwrap();
2984        store
2985            .set_local_settings(
2986                wt0_child2.0,
2987                LocalSettingsPath::InWorktree(wt0_child2.1.clone()),
2988                LocalSettingsKind::Settings,
2989                Some(r#"{}"#),
2990                cx,
2991            )
2992            .unwrap();
2993
2994        store
2995            .set_local_settings(
2996                wt1_root.0,
2997                LocalSettingsPath::InWorktree(wt1_root.1.clone()),
2998                LocalSettingsKind::Settings,
2999                Some(r#"{"preferred_line_length": 90}"#),
3000                cx,
3001            )
3002            .unwrap();
3003        store
3004            .set_local_settings(
3005                wt1_subdir.0,
3006                LocalSettingsPath::InWorktree(wt1_subdir.1.clone()),
3007                LocalSettingsKind::Settings,
3008                Some(r#"{}"#),
3009                cx,
3010            )
3011            .unwrap();
3012
3013        let overrides = store.get_overrides_for_field(SettingsFile::Default, get);
3014        assert_eq!(
3015            overrides,
3016            vec![
3017                SettingsFile::User,
3018                SettingsFile::Project(wt0_root.clone()),
3019                SettingsFile::Project(wt0_child1.clone()),
3020                SettingsFile::Project(wt1_root.clone()),
3021            ]
3022        );
3023
3024        let overrides = store.get_overrides_for_field(SettingsFile::User, get);
3025        assert_eq!(
3026            overrides,
3027            vec![
3028                SettingsFile::Project(wt0_root.clone()),
3029                SettingsFile::Project(wt0_child1.clone()),
3030                SettingsFile::Project(wt1_root.clone()),
3031            ]
3032        );
3033
3034        let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_root), get);
3035        assert_eq!(overrides, vec![]);
3036
3037        let overrides =
3038            store.get_overrides_for_field(SettingsFile::Project(wt0_child1.clone()), get);
3039        assert_eq!(overrides, vec![]);
3040
3041        let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_child2), get);
3042        assert_eq!(overrides, vec![]);
3043
3044        let overrides = store.get_overrides_for_field(SettingsFile::Project(wt1_root), get);
3045        assert_eq!(overrides, vec![]);
3046
3047        let overrides = store.get_overrides_for_field(SettingsFile::Project(wt1_subdir), get);
3048        assert_eq!(overrides, vec![]);
3049
3050        let wt0_deep_child = (
3051            WorktreeId::from_usize(0),
3052            rel_path("child1/subdir").into_arc(),
3053        );
3054        store
3055            .set_local_settings(
3056                wt0_deep_child.0,
3057                LocalSettingsPath::InWorktree(wt0_deep_child.1.clone()),
3058                LocalSettingsKind::Settings,
3059                Some(r#"{"preferred_line_length": 140}"#),
3060                cx,
3061            )
3062            .unwrap();
3063
3064        let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_deep_child), get);
3065        assert_eq!(overrides, vec![]);
3066
3067        let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_child1), get);
3068        assert_eq!(overrides, vec![]);
3069    }
3070
3071    #[test]
3072    fn test_file_ord() {
3073        let wt0_root = SettingsFile::Project((WorktreeId::from_usize(0), RelPath::empty_arc()));
3074        let wt0_child1 =
3075            SettingsFile::Project((WorktreeId::from_usize(0), rel_path("child1").into_arc()));
3076        let wt0_child2 =
3077            SettingsFile::Project((WorktreeId::from_usize(0), rel_path("child2").into_arc()));
3078
3079        let wt1_root = SettingsFile::Project((WorktreeId::from_usize(1), RelPath::empty_arc()));
3080        let wt1_subdir =
3081            SettingsFile::Project((WorktreeId::from_usize(1), rel_path("subdir").into_arc()));
3082
3083        let mut files = vec![
3084            &wt1_root,
3085            &SettingsFile::Default,
3086            &wt0_root,
3087            &wt1_subdir,
3088            &wt0_child2,
3089            &SettingsFile::Server,
3090            &wt0_child1,
3091            &SettingsFile::User,
3092        ];
3093
3094        files.sort();
3095        pretty_assertions::assert_eq!(
3096            files,
3097            vec![
3098                &wt0_child2,
3099                &wt0_child1,
3100                &wt0_root,
3101                &wt1_subdir,
3102                &wt1_root,
3103                &SettingsFile::Server,
3104                &SettingsFile::User,
3105                &SettingsFile::Default,
3106            ]
3107        )
3108    }
3109
3110    #[gpui::test]
3111    fn test_lsp_settings_schema_generation(cx: &mut App) {
3112        SettingsStore::test(cx);
3113
3114        let schema = SettingsStore::json_schema(&SettingsJsonSchemaParams {
3115            language_names: &["Rust".to_string(), "TypeScript".to_string()],
3116            font_names: &["Zed Mono".to_string()],
3117            theme_names: &["One Dark".into()],
3118            icon_theme_names: &["Zed Icons".into()],
3119            lsp_adapter_names: &[
3120                "rust-analyzer".to_string(),
3121                "typescript-language-server".to_string(),
3122            ],
3123            action_names: &[],
3124            action_documentation: &HashMap::default(),
3125            deprecations: &HashMap::default(),
3126            deprecation_messages: &HashMap::default(),
3127        });
3128
3129        let properties = schema
3130            .pointer("/$defs/LspSettingsMap/properties")
3131            .expect("LspSettingsMap should have properties")
3132            .as_object()
3133            .unwrap();
3134
3135        assert!(properties.contains_key("rust-analyzer"));
3136        assert!(properties.contains_key("typescript-language-server"));
3137
3138        let init_options_ref = properties
3139            .get("rust-analyzer")
3140            .unwrap()
3141            .pointer("/properties/initialization_options/$ref")
3142            .expect("initialization_options should have a $ref")
3143            .as_str()
3144            .unwrap();
3145
3146        assert_eq!(
3147            init_options_ref,
3148            "zed://schemas/settings/lsp/rust-analyzer/initialization_options"
3149        );
3150
3151        let settings_ref = properties
3152            .get("rust-analyzer")
3153            .unwrap()
3154            .pointer("/properties/settings/$ref")
3155            .expect("settings should have a $ref")
3156            .as_str()
3157            .unwrap();
3158
3159        assert_eq!(
3160            settings_ref,
3161            "zed://schemas/settings/lsp/rust-analyzer/settings"
3162        );
3163    }
3164
3165    #[gpui::test]
3166    fn test_lsp_project_settings_schema_generation(cx: &mut App) {
3167        SettingsStore::test(cx);
3168
3169        let schema = SettingsStore::project_json_schema(&SettingsJsonSchemaParams {
3170            language_names: &["Rust".to_string(), "TypeScript".to_string()],
3171            font_names: &["Zed Mono".to_string()],
3172            theme_names: &["One Dark".into()],
3173            icon_theme_names: &["Zed Icons".into()],
3174            lsp_adapter_names: &[
3175                "rust-analyzer".to_string(),
3176                "typescript-language-server".to_string(),
3177            ],
3178            action_names: &[],
3179            action_documentation: &HashMap::default(),
3180            deprecations: &HashMap::default(),
3181            deprecation_messages: &HashMap::default(),
3182        });
3183
3184        let properties = schema
3185            .pointer("/$defs/LspSettingsMap/properties")
3186            .expect("LspSettingsMap should have properties")
3187            .as_object()
3188            .unwrap();
3189
3190        assert!(properties.contains_key("rust-analyzer"));
3191        assert!(properties.contains_key("typescript-language-server"));
3192
3193        let init_options_ref = properties
3194            .get("rust-analyzer")
3195            .unwrap()
3196            .pointer("/properties/initialization_options/$ref")
3197            .expect("initialization_options should have a $ref")
3198            .as_str()
3199            .unwrap();
3200
3201        assert_eq!(
3202            init_options_ref,
3203            "zed://schemas/settings/lsp/rust-analyzer/initialization_options"
3204        );
3205
3206        let settings_ref = properties
3207            .get("rust-analyzer")
3208            .unwrap()
3209            .pointer("/properties/settings/$ref")
3210            .expect("settings should have a $ref")
3211            .as_str()
3212            .unwrap();
3213
3214        assert_eq!(
3215            settings_ref,
3216            "zed://schemas/settings/lsp/rust-analyzer/settings"
3217        );
3218    }
3219
3220    #[gpui::test]
3221    fn test_file_types_schema_generation(cx: &mut App) {
3222        SettingsStore::test(cx);
3223
3224        let schema = SettingsStore::json_schema(&SettingsJsonSchemaParams {
3225            language_names: &["Rust".to_string(), "TypeScript".to_string()],
3226            font_names: &["Zed Mono".to_string()],
3227            theme_names: &["One Dark".into()],
3228            icon_theme_names: &["Zed Icons".into()],
3229            lsp_adapter_names: &[],
3230            action_names: &[],
3231            action_documentation: &HashMap::default(),
3232            deprecations: &HashMap::default(),
3233            deprecation_messages: &HashMap::default(),
3234        });
3235
3236        let file_type_map = schema
3237            .pointer("/$defs/FileTypeMap")
3238            .expect("schema should have a FileTypeMap definition");
3239        let properties = file_type_map
3240            .pointer("/properties")
3241            .expect("FileTypeMap should have properties")
3242            .as_object()
3243            .expect("FileTypeMap properties should be an object");
3244
3245        let mut language_names = properties.keys().collect::<Vec<_>>();
3246        language_names.sort();
3247        assert_eq!(language_names, ["Rust", "TypeScript"]);
3248
3249        let patterns_schema = file_type_map
3250            .pointer("/additionalProperties")
3251            .expect("FileTypeMap should validate values of unknown language names");
3252        assert_eq!(properties.get("Rust"), Some(patterns_schema));
3253        assert_eq!(properties.get("TypeScript"), Some(patterns_schema));
3254    }
3255
3256    #[gpui::test]
3257    fn test_project_json_schema_differs_from_user_schema(cx: &mut App) {
3258        SettingsStore::test(cx);
3259
3260        let params = SettingsJsonSchemaParams {
3261            language_names: &["Rust".to_string()],
3262            font_names: &["Zed Mono".to_string()],
3263            theme_names: &["One Dark".into()],
3264            icon_theme_names: &["Zed Icons".into()],
3265            lsp_adapter_names: &["rust-analyzer".to_string()],
3266            action_names: &[],
3267            action_documentation: &HashMap::default(),
3268            deprecations: &HashMap::default(),
3269            deprecation_messages: &HashMap::default(),
3270        };
3271
3272        let user_schema = SettingsStore::json_schema(&params);
3273        let project_schema = SettingsStore::project_json_schema(&params);
3274
3275        assert_ne!(user_schema, project_schema);
3276
3277        let user_schema_str = serde_json::to_string(&user_schema).unwrap();
3278        let project_schema_str = serde_json::to_string(&project_schema).unwrap();
3279
3280        assert!(user_schema_str.contains("\"auto_update\""));
3281        assert!(!project_schema_str.contains("\"auto_update\""));
3282    }
3283}
3284
Served at tenant.openagents/omega Member data and write actions are omitted.