settings_store.rs

   1use anyhow::{Context as _, Result};
   2use collections::{BTreeMap, HashMap, btree_map, hash_map};
   3use ec4rs::{ConfigParser, PropertiesSource, Section};
   4use fs::Fs;
   5use futures::{
   6    FutureExt, StreamExt,
   7    channel::{mpsc, oneshot},
   8    future::LocalBoxFuture,
   9};
  10use gpui::{App, AsyncApp, BorrowAppContext, Global, SharedString, Task, UpdateGlobal};
  11
  12use paths::{EDITORCONFIG_NAME, local_settings_file_relative_path, task_file_name};
  13use schemars::{JsonSchema, json_schema};
  14use serde_json::Value;
  15use smallvec::SmallVec;
  16use std::{
  17    any::{Any, TypeId, type_name},
  18    fmt::Debug,
  19    ops::Range,
  20    path::{Path, PathBuf},
  21    rc::Rc,
  22    str::{self, FromStr},
  23    sync::Arc,
  24};
  25use util::{
  26    ResultExt as _,
  27    schemars::{DefaultDenyUnknownFields, replace_subschema},
  28};
  29
  30pub type EditorconfigProperties = ec4rs::Properties;
  31
  32use crate::{
  33    ActiveSettingsProfileName, FontFamilyName, IconThemeName, LanguageSettingsContent,
  34    LanguageToSettingsMap, SettingsJsonSchemaParams, ThemeName, VsCodeSettings, WorktreeId,
  35    merge_from::MergeFrom,
  36    parse_json_with_comments, replace_value_in_json_text,
  37    settings_content::{
  38        ExtensionsSettingsContent, ProjectSettingsContent, SettingsContent, UserSettingsContent,
  39    },
  40    update_value_in_json_text,
  41};
  42
  43pub trait SettingsKey: 'static + Send + Sync {
  44    /// The name of a key within the JSON file from which this setting should
  45    /// be deserialized. If this is `None`, then the setting will be deserialized
  46    /// from the root object.
  47    const KEY: Option<&'static str>;
  48
  49    const FALLBACK_KEY: Option<&'static str> = None;
  50}
  51
  52/// A value that can be defined as a user setting.
  53///
  54/// Settings can be loaded from a combination of multiple JSON files.
  55pub trait Settings: 'static + Send + Sync + Sized {
  56    /// The name of the keys in the [`FileContent`](Self::FileContent) that should
  57    /// always be written to a settings file, even if their value matches the default
  58    /// value.
  59    ///
  60    /// This is useful for tagged [`FileContent`](Self::FileContent)s where the tag
  61    /// is a "version" field that should always be persisted, even if the current
  62    /// user settings match the current version of the settings.
  63    const PRESERVED_KEYS: Option<&'static [&'static str]> = None;
  64
  65    /// Read the value from default.json.
  66    ///
  67    /// This function *should* panic if default values are missing,
  68    /// and you should add a default to default.json for documentation.
  69    fn from_settings(content: &SettingsContent, cx: &mut App) -> Self;
  70
  71    fn missing_default() -> anyhow::Error {
  72        anyhow::anyhow!("missing default for: {}", std::any::type_name::<Self>())
  73    }
  74
  75    /// Use [the helpers in the vscode_import module](crate::vscode_import) to apply known
  76    /// equivalent settings from a vscode config to our config
  77    fn import_from_vscode(_vscode: &VsCodeSettings, _current: &mut SettingsContent) {}
  78
  79    #[track_caller]
  80    fn register(cx: &mut App)
  81    where
  82        Self: Sized,
  83    {
  84        SettingsStore::update_global(cx, |store, cx| {
  85            store.register_setting::<Self>(cx);
  86        });
  87    }
  88
  89    #[track_caller]
  90    fn get<'a>(path: Option<SettingsLocation>, cx: &'a App) -> &'a Self
  91    where
  92        Self: Sized,
  93    {
  94        cx.global::<SettingsStore>().get(path)
  95    }
  96
  97    #[track_caller]
  98    fn get_global(cx: &App) -> &Self
  99    where
 100        Self: Sized,
 101    {
 102        cx.global::<SettingsStore>().get(None)
 103    }
 104
 105    #[track_caller]
 106    fn try_get(cx: &App) -> Option<&Self>
 107    where
 108        Self: Sized,
 109    {
 110        if cx.has_global::<SettingsStore>() {
 111            cx.global::<SettingsStore>().try_get(None)
 112        } else {
 113            None
 114        }
 115    }
 116
 117    #[track_caller]
 118    fn try_read_global<R>(cx: &AsyncApp, f: impl FnOnce(&Self) -> R) -> Option<R>
 119    where
 120        Self: Sized,
 121    {
 122        cx.try_read_global(|s: &SettingsStore, _| f(s.get(None)))
 123    }
 124
 125    #[track_caller]
 126    fn override_global(settings: Self, cx: &mut App)
 127    where
 128        Self: Sized,
 129    {
 130        cx.global_mut::<SettingsStore>().override_global(settings)
 131    }
 132}
 133
 134#[derive(Clone, Copy, Debug)]
 135pub struct SettingsLocation<'a> {
 136    pub worktree_id: WorktreeId,
 137    pub path: &'a Path,
 138}
 139
 140pub struct SettingsStore {
 141    setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
 142    default_settings: Rc<SettingsContent>,
 143    user_settings: Option<UserSettingsContent>,
 144    global_settings: Option<Box<SettingsContent>>,
 145
 146    extension_settings: Option<Box<SettingsContent>>,
 147    server_settings: Option<Box<SettingsContent>>,
 148
 149    merged_settings: Rc<SettingsContent>,
 150
 151    local_settings: BTreeMap<(WorktreeId, Arc<Path>), SettingsContent>,
 152    raw_editorconfig_settings: BTreeMap<(WorktreeId, Arc<Path>), (String, Option<Editorconfig>)>,
 153
 154    _setting_file_updates: Task<()>,
 155    setting_file_updates_tx:
 156        mpsc::UnboundedSender<Box<dyn FnOnce(AsyncApp) -> LocalBoxFuture<'static, Result<()>>>>,
 157}
 158
 159#[derive(Clone)]
 160pub struct Editorconfig {
 161    pub is_root: bool,
 162    pub sections: SmallVec<[Section; 5]>,
 163}
 164
 165impl FromStr for Editorconfig {
 166    type Err = anyhow::Error;
 167
 168    fn from_str(contents: &str) -> Result<Self, Self::Err> {
 169        let parser = ConfigParser::new_buffered(contents.as_bytes())
 170            .context("creating editorconfig parser")?;
 171        let is_root = parser.is_root;
 172        let sections = parser
 173            .collect::<Result<SmallVec<_>, _>>()
 174            .context("parsing editorconfig sections")?;
 175        Ok(Self { is_root, sections })
 176    }
 177}
 178
 179#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 180pub enum LocalSettingsKind {
 181    Settings,
 182    Tasks,
 183    Editorconfig,
 184    Debug,
 185}
 186
 187impl Global for SettingsStore {}
 188
 189#[derive(Debug)]
 190struct SettingValue<T> {
 191    global_value: Option<T>,
 192    local_values: Vec<(WorktreeId, Arc<Path>, T)>,
 193}
 194
 195trait AnySettingValue: 'static + Send + Sync {
 196    fn setting_type_name(&self) -> &'static str;
 197
 198    fn from_settings(&self, s: &SettingsContent, cx: &mut App) -> Box<dyn Any>;
 199
 200    fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any;
 201    fn all_local_values(&self) -> Vec<(WorktreeId, Arc<Path>, &dyn Any)>;
 202    fn set_global_value(&mut self, value: Box<dyn Any>);
 203    fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<Path>, value: Box<dyn Any>);
 204    fn import_from_vscode(
 205        &self,
 206        vscode_settings: &VsCodeSettings,
 207        settings_content: &mut SettingsContent,
 208    );
 209}
 210
 211impl SettingsStore {
 212    pub fn new(cx: &App, default_settings: &str) -> Self {
 213        let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded();
 214        let default_settings: Rc<SettingsContent> =
 215            parse_json_with_comments(default_settings).unwrap();
 216        Self {
 217            setting_values: Default::default(),
 218            default_settings: default_settings.clone(),
 219            global_settings: None,
 220            server_settings: None,
 221            user_settings: None,
 222            extension_settings: None,
 223
 224            merged_settings: default_settings,
 225            local_settings: BTreeMap::default(),
 226            raw_editorconfig_settings: BTreeMap::default(),
 227            setting_file_updates_tx,
 228            _setting_file_updates: cx.spawn(async move |cx| {
 229                while let Some(setting_file_update) = setting_file_updates_rx.next().await {
 230                    (setting_file_update)(cx.clone()).await.log_err();
 231                }
 232            }),
 233        }
 234    }
 235
 236    pub fn observe_active_settings_profile_name(cx: &mut App) -> gpui::Subscription {
 237        cx.observe_global::<ActiveSettingsProfileName>(|cx| {
 238            Self::update_global(cx, |store, cx| {
 239                store.recompute_values(None, cx).log_err();
 240            });
 241        })
 242    }
 243
 244    pub fn update<C, R>(cx: &mut C, f: impl FnOnce(&mut Self, &mut C) -> R) -> R
 245    where
 246        C: BorrowAppContext,
 247    {
 248        cx.update_global(f)
 249    }
 250
 251    /// Add a new type of setting to the store.
 252    pub fn register_setting<T: Settings>(&mut self, cx: &mut App) {
 253        let setting_type_id = TypeId::of::<T>();
 254        let entry = self.setting_values.entry(setting_type_id);
 255
 256        if matches!(entry, hash_map::Entry::Occupied(_)) {
 257            return;
 258        }
 259
 260        let setting_value = entry.or_insert(Box::new(SettingValue::<T> {
 261            global_value: None,
 262            local_values: Vec::new(),
 263        }));
 264        let value = T::from_settings(&self.merged_settings, cx);
 265        setting_value.set_global_value(Box::new(value));
 266    }
 267
 268    /// Get the value of a setting.
 269    ///
 270    /// Panics if the given setting type has not been registered, or if there is no
 271    /// value for this setting.
 272    pub fn get<T: Settings>(&self, path: Option<SettingsLocation>) -> &T {
 273        self.setting_values
 274            .get(&TypeId::of::<T>())
 275            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
 276            .value_for_path(path)
 277            .downcast_ref::<T>()
 278            .expect("no default value for setting type")
 279    }
 280
 281    /// Get the value of a setting.
 282    ///
 283    /// Does not panic
 284    pub fn try_get<T: Settings>(&self, path: Option<SettingsLocation>) -> Option<&T> {
 285        self.setting_values
 286            .get(&TypeId::of::<T>())
 287            .map(|value| value.value_for_path(path))
 288            .and_then(|value| value.downcast_ref::<T>())
 289    }
 290
 291    /// Get all values from project specific settings
 292    pub fn get_all_locals<T: Settings>(&self) -> Vec<(WorktreeId, Arc<Path>, &T)> {
 293        self.setting_values
 294            .get(&TypeId::of::<T>())
 295            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
 296            .all_local_values()
 297            .into_iter()
 298            .map(|(id, path, any)| {
 299                (
 300                    id,
 301                    path,
 302                    any.downcast_ref::<T>()
 303                        .expect("wrong value type for setting"),
 304                )
 305            })
 306            .collect()
 307    }
 308
 309    /// Override the global value for a setting.
 310    ///
 311    /// The given value will be overwritten if the user settings file changes.
 312    pub fn override_global<T: Settings>(&mut self, value: T) {
 313        self.setting_values
 314            .get_mut(&TypeId::of::<T>())
 315            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
 316            .set_global_value(Box::new(value))
 317    }
 318
 319    /// Get the user's settings content.
 320    ///
 321    /// For user-facing functionality use the typed setting interface.
 322    /// (e.g. ProjectSettings::get_global(cx))
 323    pub fn raw_user_settings(&self) -> Option<&UserSettingsContent> {
 324        self.user_settings.as_ref()
 325    }
 326
 327    /// Get the default settings content as a raw JSON value.
 328    pub fn raw_default_settings(&self) -> &SettingsContent {
 329        &self.default_settings
 330    }
 331
 332    /// Get the configured settings profile names.
 333    pub fn configured_settings_profiles(&self) -> impl Iterator<Item = &str> {
 334        self.user_settings
 335            .iter()
 336            .flat_map(|settings| settings.profiles.keys().map(|k| k.as_str()))
 337    }
 338
 339    #[cfg(any(test, feature = "test-support"))]
 340    pub fn test(cx: &mut App) -> Self {
 341        Self::new(cx, &crate::test_settings())
 342    }
 343
 344    /// Updates the value of a setting in the user's global configuration.
 345    ///
 346    /// This is only for tests. Normally, settings are only loaded from
 347    /// JSON files.
 348    #[cfg(any(test, feature = "test-support"))]
 349    pub fn update_user_settings(
 350        &mut self,
 351        cx: &mut App,
 352        update: impl FnOnce(&mut SettingsContent),
 353    ) {
 354        let mut content = self.user_settings.clone().unwrap_or_default().content;
 355        update(&mut content);
 356        let new_text = serde_json::to_string(&UserSettingsContent {
 357            content,
 358            ..Default::default()
 359        })
 360        .unwrap();
 361        self.set_user_settings(&new_text, cx).unwrap();
 362    }
 363
 364    pub async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> {
 365        match fs.load(paths::settings_file()).await {
 366            result @ Ok(_) => result,
 367            Err(err) => {
 368                if let Some(e) = err.downcast_ref::<std::io::Error>()
 369                    && e.kind() == std::io::ErrorKind::NotFound
 370                {
 371                    return Ok(crate::initial_user_settings_content().to_string());
 372                }
 373                Err(err)
 374            }
 375        }
 376    }
 377
 378    fn update_settings_file_inner(
 379        &self,
 380        fs: Arc<dyn Fs>,
 381        update: impl 'static + Send + FnOnce(String, AsyncApp) -> Result<String>,
 382    ) -> oneshot::Receiver<Result<()>> {
 383        let (tx, rx) = oneshot::channel::<Result<()>>();
 384        self.setting_file_updates_tx
 385            .unbounded_send(Box::new(move |cx: AsyncApp| {
 386                async move {
 387                    let res = async move {
 388                        let old_text = Self::load_settings(&fs).await?;
 389                        let new_text = update(old_text, cx)?;
 390                        let settings_path = paths::settings_file().as_path();
 391                        if fs.is_file(settings_path).await {
 392                            let resolved_path =
 393                                fs.canonicalize(settings_path).await.with_context(|| {
 394                                    format!(
 395                                        "Failed to canonicalize settings path {:?}",
 396                                        settings_path
 397                                    )
 398                                })?;
 399
 400                            fs.atomic_write(resolved_path.clone(), new_text)
 401                                .await
 402                                .with_context(|| {
 403                                    format!("Failed to write settings to file {:?}", resolved_path)
 404                                })?;
 405                        } else {
 406                            fs.atomic_write(settings_path.to_path_buf(), new_text)
 407                                .await
 408                                .with_context(|| {
 409                                    format!("Failed to write settings to file {:?}", settings_path)
 410                                })?;
 411                        }
 412                        anyhow::Ok(())
 413                    }
 414                    .await;
 415
 416                    let new_res = match &res {
 417                        Ok(_) => anyhow::Ok(()),
 418                        Err(e) => Err(anyhow::anyhow!("Failed to write settings to file {:?}", e)),
 419                    };
 420
 421                    _ = tx.send(new_res);
 422                    res
 423                }
 424                .boxed_local()
 425            }))
 426            .map_err(|err| anyhow::format_err!("Failed to update settings file: {}", err))
 427            .log_with_level(log::Level::Warn);
 428        return rx;
 429    }
 430
 431    pub fn update_settings_file_at_path(
 432        &self,
 433        fs: Arc<dyn Fs>,
 434        path: &[impl AsRef<str>],
 435        new_value: serde_json::Value,
 436    ) -> oneshot::Receiver<Result<()>> {
 437        let key_path = path
 438            .into_iter()
 439            .map(AsRef::as_ref)
 440            .map(SharedString::new)
 441            .collect::<Vec<_>>();
 442        let update = move |mut old_text: String, cx: AsyncApp| {
 443            cx.read_global(|store: &SettingsStore, _cx| {
 444                // todo(settings_ui) use `update_value_in_json_text` for merging new and old objects with comment preservation, needs old value though...
 445                let (range, replacement) = replace_value_in_json_text(
 446                    &old_text,
 447                    key_path.as_slice(),
 448                    store.json_tab_size(),
 449                    Some(&new_value),
 450                    None,
 451                );
 452                old_text.replace_range(range, &replacement);
 453                old_text
 454            })
 455        };
 456        self.update_settings_file_inner(fs, update)
 457    }
 458
 459    pub fn update_settings_file(
 460        &self,
 461        fs: Arc<dyn Fs>,
 462        update: impl 'static + Send + FnOnce(&mut SettingsContent, &App),
 463    ) {
 464        _ = self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
 465            cx.read_global(|store: &SettingsStore, cx| {
 466                store.new_text_for_update(old_text, |content| update(content, cx))
 467            })
 468        });
 469    }
 470
 471    pub fn import_vscode_settings(
 472        &self,
 473        fs: Arc<dyn Fs>,
 474        vscode_settings: VsCodeSettings,
 475    ) -> oneshot::Receiver<Result<()>> {
 476        self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
 477            cx.read_global(|store: &SettingsStore, _cx| {
 478                store.get_vscode_edits(old_text, &vscode_settings)
 479            })
 480        })
 481    }
 482}
 483
 484impl SettingsStore {
 485    /// Updates the value of a setting in a JSON file, returning the new text
 486    /// for that JSON file.
 487    pub fn new_text_for_update(
 488        &self,
 489        old_text: String,
 490        update: impl FnOnce(&mut SettingsContent),
 491    ) -> String {
 492        let edits = self.edits_for_update(&old_text, update);
 493        let mut new_text = old_text;
 494        for (range, replacement) in edits.into_iter() {
 495            new_text.replace_range(range, &replacement);
 496        }
 497        new_text
 498    }
 499
 500    pub fn get_vscode_edits(&self, old_text: String, vscode: &VsCodeSettings) -> String {
 501        self.new_text_for_update(old_text, |settings_content| {
 502            for v in self.setting_values.values() {
 503                v.import_from_vscode(vscode, settings_content)
 504            }
 505        })
 506    }
 507
 508    /// Updates the value of a setting in a JSON file, returning a list
 509    /// of edits to apply to the JSON file.
 510    pub fn edits_for_update(
 511        &self,
 512        text: &str,
 513        update: impl FnOnce(&mut SettingsContent),
 514    ) -> Vec<(Range<usize>, String)> {
 515        let old_content: UserSettingsContent =
 516            parse_json_with_comments(text).log_err().unwrap_or_default();
 517        let mut new_content = old_content.clone();
 518        update(&mut new_content.content);
 519
 520        let old_value = serde_json::to_value(&old_content).unwrap();
 521        let new_value = serde_json::to_value(new_content).unwrap();
 522
 523        let mut key_path = Vec::new();
 524        let mut edits = Vec::new();
 525        let tab_size = self.json_tab_size();
 526        let mut text = text.to_string();
 527        update_value_in_json_text(
 528            &mut text,
 529            &mut key_path,
 530            tab_size,
 531            &old_value,
 532            &new_value,
 533            &mut edits,
 534        );
 535        edits
 536    }
 537
 538    pub fn json_tab_size(&self) -> usize {
 539        2
 540    }
 541
 542    /// Sets the default settings via a JSON string.
 543    ///
 544    /// The string should contain a JSON object with a default value for every setting.
 545    pub fn set_default_settings(
 546        &mut self,
 547        default_settings_content: &str,
 548        cx: &mut App,
 549    ) -> Result<()> {
 550        self.default_settings = parse_json_with_comments(default_settings_content)?;
 551        self.recompute_values(None, cx)?;
 552        Ok(())
 553    }
 554
 555    /// Sets the user settings via a JSON string.
 556    pub fn set_user_settings(&mut self, user_settings_content: &str, cx: &mut App) -> Result<()> {
 557        let settings: UserSettingsContent = if user_settings_content.is_empty() {
 558            parse_json_with_comments("{}")?
 559        } else {
 560            parse_json_with_comments(user_settings_content)?
 561        };
 562
 563        self.user_settings = Some(settings);
 564        self.recompute_values(None, cx)?;
 565        Ok(())
 566    }
 567
 568    /// Sets the global settings via a JSON string.
 569    pub fn set_global_settings(
 570        &mut self,
 571        global_settings_content: &str,
 572        cx: &mut App,
 573    ) -> Result<()> {
 574        let settings: SettingsContent = if global_settings_content.is_empty() {
 575            parse_json_with_comments("{}")?
 576        } else {
 577            parse_json_with_comments(global_settings_content)?
 578        };
 579
 580        self.global_settings = Some(Box::new(settings));
 581        self.recompute_values(None, cx)?;
 582        Ok(())
 583    }
 584
 585    pub fn set_server_settings(
 586        &mut self,
 587        server_settings_content: &str,
 588        cx: &mut App,
 589    ) -> Result<()> {
 590        let settings: Option<SettingsContent> = if server_settings_content.is_empty() {
 591            None
 592        } else {
 593            parse_json_with_comments(server_settings_content)?
 594        };
 595
 596        // Rewrite the server settings into a content type
 597        self.server_settings = settings.map(|settings| Box::new(settings));
 598
 599        self.recompute_values(None, cx)?;
 600        Ok(())
 601    }
 602
 603    /// Add or remove a set of local settings via a JSON string.
 604    pub fn set_local_settings(
 605        &mut self,
 606        root_id: WorktreeId,
 607        directory_path: Arc<Path>,
 608        kind: LocalSettingsKind,
 609        settings_content: Option<&str>,
 610        cx: &mut App,
 611    ) -> std::result::Result<(), InvalidSettingsError> {
 612        let mut zed_settings_changed = false;
 613        match (
 614            kind,
 615            settings_content
 616                .map(|content| content.trim())
 617                .filter(|content| !content.is_empty()),
 618        ) {
 619            (LocalSettingsKind::Tasks, _) => {
 620                return Err(InvalidSettingsError::Tasks {
 621                    message: "Attempted to submit tasks into the settings store".to_string(),
 622                    path: directory_path.join(task_file_name()),
 623                });
 624            }
 625            (LocalSettingsKind::Debug, _) => {
 626                return Err(InvalidSettingsError::Debug {
 627                    message: "Attempted to submit debugger config into the settings store"
 628                        .to_string(),
 629                    path: directory_path.join(task_file_name()),
 630                });
 631            }
 632            (LocalSettingsKind::Settings, None) => {
 633                zed_settings_changed = self
 634                    .local_settings
 635                    .remove(&(root_id, directory_path.clone()))
 636                    .is_some()
 637            }
 638            (LocalSettingsKind::Editorconfig, None) => {
 639                self.raw_editorconfig_settings
 640                    .remove(&(root_id, directory_path.clone()));
 641            }
 642            (LocalSettingsKind::Settings, Some(settings_contents)) => {
 643                let new_settings = parse_json_with_comments::<ProjectSettingsContent>(
 644                    settings_contents,
 645                )
 646                .map_err(|e| InvalidSettingsError::LocalSettings {
 647                    path: directory_path.join(local_settings_file_relative_path()),
 648                    message: e.to_string(),
 649                })?;
 650                match self.local_settings.entry((root_id, directory_path.clone())) {
 651                    btree_map::Entry::Vacant(v) => {
 652                        v.insert(SettingsContent {
 653                            project: new_settings,
 654                            ..Default::default()
 655                        });
 656                        zed_settings_changed = true;
 657                    }
 658                    btree_map::Entry::Occupied(mut o) => {
 659                        if &o.get().project != &new_settings {
 660                            o.insert(SettingsContent {
 661                                project: new_settings,
 662                                ..Default::default()
 663                            });
 664                            zed_settings_changed = true;
 665                        }
 666                    }
 667                }
 668            }
 669            (LocalSettingsKind::Editorconfig, Some(editorconfig_contents)) => {
 670                match self
 671                    .raw_editorconfig_settings
 672                    .entry((root_id, directory_path.clone()))
 673                {
 674                    btree_map::Entry::Vacant(v) => match editorconfig_contents.parse() {
 675                        Ok(new_contents) => {
 676                            v.insert((editorconfig_contents.to_owned(), Some(new_contents)));
 677                        }
 678                        Err(e) => {
 679                            v.insert((editorconfig_contents.to_owned(), None));
 680                            return Err(InvalidSettingsError::Editorconfig {
 681                                message: e.to_string(),
 682                                path: directory_path.join(EDITORCONFIG_NAME),
 683                            });
 684                        }
 685                    },
 686                    btree_map::Entry::Occupied(mut o) => {
 687                        if o.get().0 != editorconfig_contents {
 688                            match editorconfig_contents.parse() {
 689                                Ok(new_contents) => {
 690                                    o.insert((
 691                                        editorconfig_contents.to_owned(),
 692                                        Some(new_contents),
 693                                    ));
 694                                }
 695                                Err(e) => {
 696                                    o.insert((editorconfig_contents.to_owned(), None));
 697                                    return Err(InvalidSettingsError::Editorconfig {
 698                                        message: e.to_string(),
 699                                        path: directory_path.join(EDITORCONFIG_NAME),
 700                                    });
 701                                }
 702                            }
 703                        }
 704                    }
 705                }
 706            }
 707        };
 708
 709        if zed_settings_changed {
 710            self.recompute_values(Some((root_id, &directory_path)), cx)?;
 711        }
 712        Ok(())
 713    }
 714
 715    pub fn set_extension_settings(
 716        &mut self,
 717        content: ExtensionsSettingsContent,
 718        cx: &mut App,
 719    ) -> Result<()> {
 720        self.extension_settings = Some(Box::new(SettingsContent {
 721            project: ProjectSettingsContent {
 722                all_languages: content.all_languages,
 723                ..Default::default()
 724            },
 725            ..Default::default()
 726        }));
 727        self.recompute_values(None, cx)?;
 728        Ok(())
 729    }
 730
 731    /// Add or remove a set of local settings via a JSON string.
 732    pub fn clear_local_settings(&mut self, root_id: WorktreeId, cx: &mut App) -> Result<()> {
 733        self.local_settings
 734            .retain(|(worktree_id, _), _| worktree_id != &root_id);
 735        self.recompute_values(Some((root_id, "".as_ref())), cx)?;
 736        Ok(())
 737    }
 738
 739    pub fn local_settings(
 740        &self,
 741        root_id: WorktreeId,
 742    ) -> impl '_ + Iterator<Item = (Arc<Path>, &ProjectSettingsContent)> {
 743        self.local_settings
 744            .range(
 745                (root_id, Path::new("").into())
 746                    ..(
 747                        WorktreeId::from_usize(root_id.to_usize() + 1),
 748                        Path::new("").into(),
 749                    ),
 750            )
 751            .map(|((_, path), content)| (path.clone(), &content.project))
 752    }
 753
 754    pub fn local_editorconfig_settings(
 755        &self,
 756        root_id: WorktreeId,
 757    ) -> impl '_ + Iterator<Item = (Arc<Path>, String, Option<Editorconfig>)> {
 758        self.raw_editorconfig_settings
 759            .range(
 760                (root_id, Path::new("").into())
 761                    ..(
 762                        WorktreeId::from_usize(root_id.to_usize() + 1),
 763                        Path::new("").into(),
 764                    ),
 765            )
 766            .map(|((_, path), (content, parsed_content))| {
 767                (path.clone(), content.clone(), parsed_content.clone())
 768            })
 769    }
 770
 771    pub fn json_schema(&self, params: &SettingsJsonSchemaParams) -> Value {
 772        let mut generator = schemars::generate::SchemaSettings::draft2019_09()
 773            .with_transform(DefaultDenyUnknownFields)
 774            .into_generator();
 775
 776        UserSettingsContent::json_schema(&mut generator);
 777
 778        let language_settings_content_ref = generator
 779            .subschema_for::<LanguageSettingsContent>()
 780            .to_value();
 781        replace_subschema::<LanguageToSettingsMap>(&mut generator, || {
 782            json_schema!({
 783                "type": "object",
 784                "properties": params
 785                    .language_names
 786                    .iter()
 787                    .map(|name| {
 788                        (
 789                            name.clone(),
 790                            language_settings_content_ref.clone(),
 791                        )
 792                    })
 793                    .collect::<serde_json::Map<_, _>>()
 794            })
 795        });
 796
 797        replace_subschema::<FontFamilyName>(&mut generator, || {
 798            json_schema!({
 799                "type": "string",
 800                "enum": params.font_names,
 801            })
 802        });
 803
 804        replace_subschema::<ThemeName>(&mut generator, || {
 805            json_schema!({
 806                "type": "string",
 807                "enum": params.theme_names,
 808            })
 809        });
 810
 811        replace_subschema::<IconThemeName>(&mut generator, || {
 812            json_schema!({
 813                "type": "string",
 814                "enum": params.icon_theme_names,
 815            })
 816        });
 817
 818        generator
 819            .root_schema_for::<UserSettingsContent>()
 820            .to_value()
 821    }
 822
 823    fn recompute_values(
 824        &mut self,
 825        changed_local_path: Option<(WorktreeId, &Path)>,
 826        cx: &mut App,
 827    ) -> std::result::Result<(), InvalidSettingsError> {
 828        // Reload the global and local values for every setting.
 829        let mut project_settings_stack = Vec::<SettingsContent>::new();
 830        let mut paths_stack = Vec::<Option<(WorktreeId, &Path)>>::new();
 831
 832        if changed_local_path.is_none() {
 833            let mut merged = self.default_settings.as_ref().clone();
 834            merged.merge_from_option(self.extension_settings.as_deref());
 835            merged.merge_from_option(self.global_settings.as_deref());
 836            if let Some(user_settings) = self.user_settings.as_ref() {
 837                merged.merge_from(&user_settings.content);
 838                merged.merge_from_option(user_settings.for_release_channel());
 839                merged.merge_from_option(user_settings.for_os());
 840                merged.merge_from_option(user_settings.for_profile(cx));
 841            }
 842            merged.merge_from_option(self.server_settings.as_deref());
 843            self.merged_settings = Rc::new(merged);
 844
 845            for setting_value in self.setting_values.values_mut() {
 846                let value = setting_value.from_settings(&self.merged_settings, cx);
 847                setting_value.set_global_value(value);
 848            }
 849        }
 850
 851        for ((root_id, directory_path), local_settings) in &self.local_settings {
 852            // Build a stack of all of the local values for that setting.
 853            while let Some(prev_entry) = paths_stack.last() {
 854                if let Some((prev_root_id, prev_path)) = prev_entry
 855                    && (root_id != prev_root_id || !directory_path.starts_with(prev_path))
 856                {
 857                    paths_stack.pop();
 858                    project_settings_stack.pop();
 859                    continue;
 860                }
 861                break;
 862            }
 863
 864            paths_stack.push(Some((*root_id, directory_path.as_ref())));
 865            let mut merged_local_settings = if let Some(deepest) = project_settings_stack.last() {
 866                (*deepest).clone()
 867            } else {
 868                self.merged_settings.as_ref().clone()
 869            };
 870            merged_local_settings.merge_from(local_settings);
 871
 872            project_settings_stack.push(merged_local_settings);
 873
 874            // If a local settings file changed, then avoid recomputing local
 875            // settings for any path outside of that directory.
 876            if changed_local_path.is_some_and(|(changed_root_id, changed_local_path)| {
 877                *root_id != changed_root_id || !directory_path.starts_with(changed_local_path)
 878            }) {
 879                continue;
 880            }
 881
 882            for setting_value in self.setting_values.values_mut() {
 883                let value =
 884                    setting_value.from_settings(&project_settings_stack.last().unwrap(), cx);
 885                setting_value.set_local_value(*root_id, directory_path.clone(), value);
 886            }
 887        }
 888        Ok(())
 889    }
 890
 891    pub fn editorconfig_properties(
 892        &self,
 893        for_worktree: WorktreeId,
 894        for_path: &Path,
 895    ) -> Option<EditorconfigProperties> {
 896        let mut properties = EditorconfigProperties::new();
 897
 898        for (directory_with_config, _, parsed_editorconfig) in
 899            self.local_editorconfig_settings(for_worktree)
 900        {
 901            if !for_path.starts_with(&directory_with_config) {
 902                properties.use_fallbacks();
 903                return Some(properties);
 904            }
 905            let parsed_editorconfig = parsed_editorconfig?;
 906            if parsed_editorconfig.is_root {
 907                properties = EditorconfigProperties::new();
 908            }
 909            for section in parsed_editorconfig.sections {
 910                section.apply_to(&mut properties, for_path).log_err()?;
 911            }
 912        }
 913
 914        properties.use_fallbacks();
 915        Some(properties)
 916    }
 917}
 918
 919#[derive(Debug, Clone, PartialEq)]
 920pub enum InvalidSettingsError {
 921    LocalSettings { path: PathBuf, message: String },
 922    UserSettings { message: String },
 923    ServerSettings { message: String },
 924    DefaultSettings { message: String },
 925    Editorconfig { path: PathBuf, message: String },
 926    Tasks { path: PathBuf, message: String },
 927    Debug { path: PathBuf, message: String },
 928}
 929
 930impl std::fmt::Display for InvalidSettingsError {
 931    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 932        match self {
 933            InvalidSettingsError::LocalSettings { message, .. }
 934            | InvalidSettingsError::UserSettings { message }
 935            | InvalidSettingsError::ServerSettings { message }
 936            | InvalidSettingsError::DefaultSettings { message }
 937            | InvalidSettingsError::Tasks { message, .. }
 938            | InvalidSettingsError::Editorconfig { message, .. }
 939            | InvalidSettingsError::Debug { message, .. } => {
 940                write!(f, "{message}")
 941            }
 942        }
 943    }
 944}
 945impl std::error::Error for InvalidSettingsError {}
 946
 947impl Debug for SettingsStore {
 948    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 949        f.debug_struct("SettingsStore")
 950            .field(
 951                "types",
 952                &self
 953                    .setting_values
 954                    .values()
 955                    .map(|value| value.setting_type_name())
 956                    .collect::<Vec<_>>(),
 957            )
 958            .field("default_settings", &self.default_settings)
 959            .field("user_settings", &self.user_settings)
 960            .field("local_settings", &self.local_settings)
 961            .finish_non_exhaustive()
 962    }
 963}
 964
 965impl<T: Settings> AnySettingValue for SettingValue<T> {
 966    fn from_settings(&self, s: &SettingsContent, cx: &mut App) -> Box<dyn Any> {
 967        Box::new(T::from_settings(s, cx)) as _
 968    }
 969
 970    fn setting_type_name(&self) -> &'static str {
 971        type_name::<T>()
 972    }
 973
 974    fn all_local_values(&self) -> Vec<(WorktreeId, Arc<Path>, &dyn Any)> {
 975        self.local_values
 976            .iter()
 977            .map(|(id, path, value)| (*id, path.clone(), value as _))
 978            .collect()
 979    }
 980
 981    fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any {
 982        if let Some(SettingsLocation { worktree_id, path }) = path {
 983            for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {
 984                if worktree_id == *settings_root_id && path.starts_with(settings_path) {
 985                    return value;
 986                }
 987            }
 988        }
 989
 990        self.global_value
 991            .as_ref()
 992            .unwrap_or_else(|| panic!("no default value for setting {}", self.setting_type_name()))
 993    }
 994
 995    fn set_global_value(&mut self, value: Box<dyn Any>) {
 996        self.global_value = Some(*value.downcast().unwrap());
 997    }
 998
 999    fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<Path>, value: Box<dyn Any>) {
1000        let value = *value.downcast().unwrap();
1001        match self
1002            .local_values
1003            .binary_search_by_key(&(root_id, &path), |e| (e.0, &e.1))
1004        {
1005            Ok(ix) => self.local_values[ix].2 = value,
1006            Err(ix) => self.local_values.insert(ix, (root_id, path, value)),
1007        }
1008    }
1009
1010    fn import_from_vscode(
1011        &self,
1012        vscode_settings: &VsCodeSettings,
1013        settings_content: &mut SettingsContent,
1014    ) {
1015        T::import_from_vscode(vscode_settings, settings_content);
1016    }
1017}
1018
1019#[cfg(test)]
1020mod tests {
1021    use std::num::NonZeroU32;
1022
1023    use crate::{
1024        ClosePosition, ItemSettingsContent, VsCodeSettingsSource, default_settings,
1025        settings_content::LanguageSettingsContent, test_settings,
1026    };
1027
1028    use super::*;
1029    use unindent::Unindent;
1030
1031    #[derive(Debug, PartialEq)]
1032    struct AutoUpdateSetting {
1033        auto_update: bool,
1034    }
1035
1036    impl Settings for AutoUpdateSetting {
1037        fn from_settings(content: &SettingsContent, _: &mut App) -> Self {
1038            AutoUpdateSetting {
1039                auto_update: content.auto_update.unwrap(),
1040            }
1041        }
1042    }
1043
1044    #[derive(Debug, PartialEq)]
1045    struct ItemSettings {
1046        close_position: ClosePosition,
1047        git_status: bool,
1048    }
1049
1050    impl Settings for ItemSettings {
1051        fn from_settings(content: &SettingsContent, _: &mut App) -> Self {
1052            let content = content.tabs.clone().unwrap();
1053            ItemSettings {
1054                close_position: content.close_position.unwrap(),
1055                git_status: content.git_status.unwrap(),
1056            }
1057        }
1058
1059        fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) {
1060            let mut show = None;
1061
1062            vscode.bool_setting("workbench.editor.decorations.colors", &mut show);
1063            if let Some(show) = show {
1064                content
1065                    .tabs
1066                    .get_or_insert_default()
1067                    .git_status
1068                    .replace(show);
1069            }
1070        }
1071    }
1072
1073    #[derive(Debug, PartialEq)]
1074    struct DefaultLanguageSettings {
1075        tab_size: NonZeroU32,
1076        preferred_line_length: u32,
1077    }
1078
1079    impl Settings for DefaultLanguageSettings {
1080        fn from_settings(content: &SettingsContent, _: &mut App) -> Self {
1081            let content = &content.project.all_languages.defaults;
1082            DefaultLanguageSettings {
1083                tab_size: content.tab_size.unwrap(),
1084                preferred_line_length: content.preferred_line_length.unwrap(),
1085            }
1086        }
1087
1088        fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) {
1089            let content = &mut content.project.all_languages.defaults;
1090
1091            if let Some(size) = vscode
1092                .read_value("editor.tabSize")
1093                .and_then(|v| v.as_u64())
1094                .and_then(|n| NonZeroU32::new(n as u32))
1095            {
1096                content.tab_size = Some(size);
1097            }
1098        }
1099    }
1100
1101    #[gpui::test]
1102    fn test_settings_store_basic(cx: &mut App) {
1103        let mut store = SettingsStore::new(cx, &default_settings());
1104        store.register_setting::<AutoUpdateSetting>(cx);
1105        store.register_setting::<ItemSettings>(cx);
1106        store.register_setting::<DefaultLanguageSettings>(cx);
1107
1108        assert_eq!(
1109            store.get::<AutoUpdateSetting>(None),
1110            &AutoUpdateSetting { auto_update: true }
1111        );
1112        assert_eq!(
1113            store.get::<ItemSettings>(None).close_position,
1114            ClosePosition::Right
1115        );
1116
1117        store
1118            .set_user_settings(
1119                r#"{
1120                    "auto_update": false,
1121                    "tabs": {
1122                      "close_position": "left"
1123                    }
1124                }"#,
1125                cx,
1126            )
1127            .unwrap();
1128
1129        assert_eq!(
1130            store.get::<AutoUpdateSetting>(None),
1131            &AutoUpdateSetting { auto_update: false }
1132        );
1133        assert_eq!(
1134            store.get::<ItemSettings>(None).close_position,
1135            ClosePosition::Left
1136        );
1137
1138        store
1139            .set_local_settings(
1140                WorktreeId::from_usize(1),
1141                Path::new("/root1").into(),
1142                LocalSettingsKind::Settings,
1143                Some(r#"{ "tab_size": 5 }"#),
1144                cx,
1145            )
1146            .unwrap();
1147        store
1148            .set_local_settings(
1149                WorktreeId::from_usize(1),
1150                Path::new("/root1/subdir").into(),
1151                LocalSettingsKind::Settings,
1152                Some(r#"{ "preferred_line_length": 50 }"#),
1153                cx,
1154            )
1155            .unwrap();
1156
1157        store
1158            .set_local_settings(
1159                WorktreeId::from_usize(1),
1160                Path::new("/root2").into(),
1161                LocalSettingsKind::Settings,
1162                Some(r#"{ "tab_size": 9, "auto_update": true}"#),
1163                cx,
1164            )
1165            .unwrap();
1166
1167        assert_eq!(
1168            store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1169                worktree_id: WorktreeId::from_usize(1),
1170                path: Path::new("/root1/something"),
1171            })),
1172            &DefaultLanguageSettings {
1173                preferred_line_length: 80,
1174                tab_size: 5.try_into().unwrap(),
1175            }
1176        );
1177        assert_eq!(
1178            store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1179                worktree_id: WorktreeId::from_usize(1),
1180                path: Path::new("/root1/subdir/something")
1181            })),
1182            &DefaultLanguageSettings {
1183                preferred_line_length: 50,
1184                tab_size: 5.try_into().unwrap(),
1185            }
1186        );
1187        assert_eq!(
1188            store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1189                worktree_id: WorktreeId::from_usize(1),
1190                path: Path::new("/root2/something")
1191            })),
1192            &DefaultLanguageSettings {
1193                preferred_line_length: 80,
1194                tab_size: 9.try_into().unwrap(),
1195            }
1196        );
1197        assert_eq!(
1198            store.get::<AutoUpdateSetting>(Some(SettingsLocation {
1199                worktree_id: WorktreeId::from_usize(1),
1200                path: Path::new("/root2/something")
1201            })),
1202            &AutoUpdateSetting { auto_update: false }
1203        );
1204    }
1205
1206    #[gpui::test]
1207    fn test_setting_store_assign_json_before_register(cx: &mut App) {
1208        let mut store = SettingsStore::new(cx, &test_settings());
1209        store
1210            .set_user_settings(r#"{ "auto_update": false }"#, cx)
1211            .unwrap();
1212        store.register_setting::<AutoUpdateSetting>(cx);
1213
1214        assert_eq!(
1215            store.get::<AutoUpdateSetting>(None),
1216            &AutoUpdateSetting { auto_update: false }
1217        );
1218    }
1219
1220    #[track_caller]
1221    fn check_settings_update(
1222        store: &mut SettingsStore,
1223        old_json: String,
1224        update: fn(&mut SettingsContent),
1225        expected_new_json: String,
1226        cx: &mut App,
1227    ) {
1228        store.set_user_settings(&old_json, cx).ok();
1229        let edits = store.edits_for_update(&old_json, update);
1230        let mut new_json = old_json;
1231        for (range, replacement) in edits.into_iter() {
1232            new_json.replace_range(range, &replacement);
1233        }
1234        pretty_assertions::assert_eq!(new_json, expected_new_json);
1235    }
1236
1237    #[gpui::test]
1238    fn test_setting_store_update(cx: &mut App) {
1239        let mut store = SettingsStore::new(cx, &test_settings());
1240
1241        // entries added and updated
1242        check_settings_update(
1243            &mut store,
1244            r#"{
1245                "languages": {
1246                    "JSON": {
1247                        "auto_indent": true
1248                    }
1249                }
1250            }"#
1251            .unindent(),
1252            |settings| {
1253                settings
1254                    .languages_mut()
1255                    .get_mut("JSON")
1256                    .unwrap()
1257                    .auto_indent = Some(false);
1258
1259                settings.languages_mut().insert(
1260                    "Rust".into(),
1261                    LanguageSettingsContent {
1262                        auto_indent: Some(true),
1263                        ..Default::default()
1264                    },
1265                );
1266            },
1267            r#"{
1268                "languages": {
1269                    "Rust": {
1270                        "auto_indent": true
1271                    },
1272                    "JSON": {
1273                        "auto_indent": false
1274                    }
1275                }
1276            }"#
1277            .unindent(),
1278            cx,
1279        );
1280
1281        // entries removed
1282        check_settings_update(
1283            &mut store,
1284            r#"{
1285                "languages": {
1286                    "Rust": {
1287                        "language_setting_2": true
1288                    },
1289                    "JSON": {
1290                        "language_setting_1": false
1291                    }
1292                }
1293            }"#
1294            .unindent(),
1295            |settings| {
1296                settings.languages_mut().remove("JSON").unwrap();
1297            },
1298            r#"{
1299                "languages": {
1300                    "Rust": {
1301                        "language_setting_2": true
1302                    }
1303                }
1304            }"#
1305            .unindent(),
1306            cx,
1307        );
1308
1309        check_settings_update(
1310            &mut store,
1311            r#"{
1312                "languages": {
1313                    "Rust": {
1314                        "language_setting_2": true
1315                    },
1316                    "JSON": {
1317                        "language_setting_1": false
1318                    }
1319                }
1320            }"#
1321            .unindent(),
1322            |settings| {
1323                settings.languages_mut().remove("Rust").unwrap();
1324            },
1325            r#"{
1326                "languages": {
1327                    "JSON": {
1328                        "language_setting_1": false
1329                    }
1330                }
1331            }"#
1332            .unindent(),
1333            cx,
1334        );
1335
1336        // weird formatting
1337        check_settings_update(
1338            &mut store,
1339            r#"{
1340                "tabs":   { "close_position": "left", "name": "Max"  }
1341                }"#
1342            .unindent(),
1343            |settings| {
1344                settings.tabs.as_mut().unwrap().close_position = Some(ClosePosition::Left);
1345            },
1346            r#"{
1347                "tabs":   { "close_position": "left", "name": "Max"  }
1348                }"#
1349            .unindent(),
1350            cx,
1351        );
1352
1353        // single-line formatting, other keys
1354        check_settings_update(
1355            &mut store,
1356            r#"{ "one": 1, "two": 2 }"#.to_owned(),
1357            |settings| settings.auto_update = Some(true),
1358            r#"{ "auto_update": true, "one": 1, "two": 2 }"#.to_owned(),
1359            cx,
1360        );
1361
1362        // empty object
1363        check_settings_update(
1364            &mut store,
1365            r#"{
1366                "tabs": {}
1367            }"#
1368            .unindent(),
1369            |settings| settings.tabs.as_mut().unwrap().close_position = Some(ClosePosition::Left),
1370            r#"{
1371                "tabs": {
1372                    "close_position": "left"
1373                }
1374            }"#
1375            .unindent(),
1376            cx,
1377        );
1378
1379        // no content
1380        check_settings_update(
1381            &mut store,
1382            r#""#.unindent(),
1383            |settings| {
1384                settings.tabs = Some(ItemSettingsContent {
1385                    git_status: Some(true),
1386                    ..Default::default()
1387                })
1388            },
1389            r#"{
1390                "tabs": {
1391                    "git_status": true
1392                }
1393            }
1394            "#
1395            .unindent(),
1396            cx,
1397        );
1398
1399        check_settings_update(
1400            &mut store,
1401            r#"{
1402            }
1403            "#
1404            .unindent(),
1405            |settings| settings.title_bar.get_or_insert_default().show_branch_name = Some(true),
1406            r#"{
1407                "title_bar": {
1408                    "show_branch_name": true
1409                }
1410            }
1411            "#
1412            .unindent(),
1413            cx,
1414        );
1415    }
1416
1417    #[gpui::test]
1418    fn test_vscode_import(cx: &mut App) {
1419        let mut store = SettingsStore::new(cx, &test_settings());
1420        store.register_setting::<DefaultLanguageSettings>(cx);
1421        store.register_setting::<ItemSettings>(cx);
1422        store.register_setting::<AutoUpdateSetting>(cx);
1423
1424        // create settings that werent present
1425        check_vscode_import(
1426            &mut store,
1427            r#"{
1428            }
1429            "#
1430            .unindent(),
1431            r#" { "editor.tabSize": 37 } "#.to_owned(),
1432            r#"{
1433                "tab_size": 37
1434            }
1435            "#
1436            .unindent(),
1437            cx,
1438        );
1439
1440        // persist settings that were present
1441        check_vscode_import(
1442            &mut store,
1443            r#"{
1444                "preferred_line_length": 99,
1445            }
1446            "#
1447            .unindent(),
1448            r#"{ "editor.tabSize": 42 }"#.to_owned(),
1449            r#"{
1450                "tab_size": 42,
1451                "preferred_line_length": 99,
1452            }
1453            "#
1454            .unindent(),
1455            cx,
1456        );
1457
1458        // don't clobber settings that aren't present in vscode
1459        check_vscode_import(
1460            &mut store,
1461            r#"{
1462                "preferred_line_length": 99,
1463                "tab_size": 42
1464            }
1465            "#
1466            .unindent(),
1467            r#"{}"#.to_owned(),
1468            r#"{
1469                "preferred_line_length": 99,
1470                "tab_size": 42
1471            }
1472            "#
1473            .unindent(),
1474            cx,
1475        );
1476
1477        // custom enum
1478        check_vscode_import(
1479            &mut store,
1480            r#"{
1481            }
1482            "#
1483            .unindent(),
1484            r#"{ "workbench.editor.decorations.colors": true }"#.to_owned(),
1485            r#"{
1486                "tabs": {
1487                    "git_status": true
1488                }
1489            }
1490            "#
1491            .unindent(),
1492            cx,
1493        );
1494    }
1495
1496    #[track_caller]
1497    fn check_vscode_import(
1498        store: &mut SettingsStore,
1499        old: String,
1500        vscode: String,
1501        expected: String,
1502        cx: &mut App,
1503    ) {
1504        store.set_user_settings(&old, cx).ok();
1505        let new = store.get_vscode_edits(
1506            old,
1507            &VsCodeSettings::from_str(&vscode, VsCodeSettingsSource::VsCode).unwrap(),
1508        );
1509        pretty_assertions::assert_eq!(new, expected);
1510    }
1511
1512    #[gpui::test]
1513    fn test_update_git_settings(cx: &mut App) {
1514        let store = SettingsStore::new(cx, &test_settings());
1515
1516        let actual = store.new_text_for_update("{}".to_string(), |current| {
1517            current
1518                .git
1519                .get_or_insert_default()
1520                .inline_blame
1521                .get_or_insert_default()
1522                .enabled = Some(true);
1523        });
1524        assert_eq!(
1525            actual,
1526            r#"{
1527            "git": {
1528                "inline_blame": {
1529                    "enabled": true
1530                }
1531            }
1532        }
1533        "#
1534            .unindent()
1535        );
1536    }
1537
1538    #[gpui::test]
1539    fn test_global_settings(cx: &mut App) {
1540        let mut store = SettingsStore::new(cx, &test_settings());
1541        store.register_setting::<ItemSettings>(cx);
1542
1543        // Set global settings - these should override defaults but not user settings
1544        store
1545            .set_global_settings(
1546                r#"{
1547                    "tabs": {
1548                        "close_position": "right",
1549                        "git_status": true,
1550                    }
1551                }"#,
1552                cx,
1553            )
1554            .unwrap();
1555
1556        // Before user settings, global settings should apply
1557        assert_eq!(
1558            store.get::<ItemSettings>(None),
1559            &ItemSettings {
1560                close_position: ClosePosition::Right,
1561                git_status: true,
1562            }
1563        );
1564
1565        // Set user settings - these should override both defaults and global
1566        store
1567            .set_user_settings(
1568                r#"{
1569                    "tabs": {
1570                        "close_position": "left"
1571                    }
1572                }"#,
1573                cx,
1574            )
1575            .unwrap();
1576
1577        // User settings should override global settings
1578        assert_eq!(
1579            store.get::<ItemSettings>(None),
1580            &ItemSettings {
1581                close_position: ClosePosition::Left,
1582                git_status: true, // Staff from global settings
1583            }
1584        );
1585    }
1586}