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