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