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