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