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