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