settings_store.rs

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