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