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