settings.rs

   1mod keymap_file;
   2pub mod settings_file;
   3pub mod watched_json;
   4
   5use anyhow::Result;
   6use gpui::{
   7    font_cache::{FamilyId, FontCache},
   8    fonts, AssetSource,
   9};
  10use lazy_static::lazy_static;
  11use schemars::{
  12    gen::{SchemaGenerator, SchemaSettings},
  13    schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec},
  14    JsonSchema,
  15};
  16use serde::{de::DeserializeOwned, Deserialize, Serialize};
  17use serde_json::Value;
  18use std::{
  19    borrow::Cow, collections::HashMap, num::NonZeroU32, ops::Range, path::Path, str, sync::Arc,
  20};
  21use theme::{Theme, ThemeRegistry};
  22use tree_sitter::{Query, Tree};
  23use util::{RangeExt, ResultExt as _};
  24
  25pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
  26pub use watched_json::watch_files;
  27
  28pub const DEFAULT_SETTINGS_ASSET_PATH: &str = "settings/default.json";
  29pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json";
  30
  31#[derive(Clone)]
  32pub struct Settings {
  33    pub features: Features,
  34    pub buffer_font_family_name: String,
  35    pub buffer_font_features: fonts::Features,
  36    pub buffer_font_family: FamilyId,
  37    pub default_buffer_font_size: f32,
  38    pub buffer_font_size: f32,
  39    pub active_pane_magnification: f32,
  40    pub cursor_blink: bool,
  41    pub confirm_quit: bool,
  42    pub hover_popover_enabled: bool,
  43    pub show_completions_on_input: bool,
  44    pub show_call_status_icon: bool,
  45    pub vim_mode: bool,
  46    pub autosave: Autosave,
  47    pub editor_defaults: EditorSettings,
  48    pub editor_overrides: EditorSettings,
  49    pub git: GitSettings,
  50    pub git_overrides: GitSettings,
  51    pub copilot: CopilotSettings,
  52    pub journal_defaults: JournalSettings,
  53    pub journal_overrides: JournalSettings,
  54    pub terminal_defaults: TerminalSettings,
  55    pub terminal_overrides: TerminalSettings,
  56    pub language_defaults: HashMap<Arc<str>, EditorSettings>,
  57    pub language_overrides: HashMap<Arc<str>, EditorSettings>,
  58    pub lsp: HashMap<Arc<str>, LspSettings>,
  59    pub theme: Arc<Theme>,
  60    pub telemetry_defaults: TelemetrySettings,
  61    pub telemetry_overrides: TelemetrySettings,
  62    pub auto_update: bool,
  63    pub base_keymap: BaseKeymap,
  64}
  65
  66#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
  67pub enum BaseKeymap {
  68    #[default]
  69    VSCode,
  70    JetBrains,
  71    SublimeText,
  72    Atom,
  73    TextMate,
  74}
  75
  76impl BaseKeymap {
  77    pub const OPTIONS: [(&'static str, Self); 5] = [
  78        ("VSCode (Default)", Self::VSCode),
  79        ("Atom", Self::Atom),
  80        ("JetBrains", Self::JetBrains),
  81        ("Sublime Text", Self::SublimeText),
  82        ("TextMate", Self::TextMate),
  83    ];
  84
  85    pub fn asset_path(&self) -> Option<&'static str> {
  86        match self {
  87            BaseKeymap::JetBrains => Some("keymaps/jetbrains.json"),
  88            BaseKeymap::SublimeText => Some("keymaps/sublime_text.json"),
  89            BaseKeymap::Atom => Some("keymaps/atom.json"),
  90            BaseKeymap::TextMate => Some("keymaps/textmate.json"),
  91            BaseKeymap::VSCode => None,
  92        }
  93    }
  94
  95    pub fn names() -> impl Iterator<Item = &'static str> {
  96        Self::OPTIONS.iter().map(|(name, _)| *name)
  97    }
  98
  99    pub fn from_names(option: &str) -> BaseKeymap {
 100        Self::OPTIONS
 101            .iter()
 102            .copied()
 103            .find_map(|(name, value)| (name == option).then(|| value))
 104            .unwrap_or_default()
 105    }
 106}
 107
 108#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 109pub struct TelemetrySettings {
 110    diagnostics: Option<bool>,
 111    metrics: Option<bool>,
 112}
 113
 114impl TelemetrySettings {
 115    pub fn metrics(&self) -> bool {
 116        self.metrics.unwrap()
 117    }
 118
 119    pub fn diagnostics(&self) -> bool {
 120        self.diagnostics.unwrap()
 121    }
 122
 123    pub fn set_metrics(&mut self, value: bool) {
 124        self.metrics = Some(value);
 125    }
 126
 127    pub fn set_diagnostics(&mut self, value: bool) {
 128        self.diagnostics = Some(value);
 129    }
 130}
 131
 132#[derive(Clone, Debug, Default)]
 133pub struct CopilotSettings {
 134    pub disabled_globs: Vec<glob::Pattern>,
 135}
 136
 137#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 138pub struct CopilotSettingsContent {
 139    #[serde(default)]
 140    pub disabled_globs: Option<Vec<String>>,
 141}
 142
 143#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 144pub struct GitSettings {
 145    pub git_gutter: Option<GitGutter>,
 146    pub gutter_debounce: Option<u64>,
 147}
 148
 149#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
 150#[serde(rename_all = "snake_case")]
 151pub enum GitGutter {
 152    #[default]
 153    TrackedFiles,
 154    Hide,
 155}
 156
 157pub struct GitGutterConfig {}
 158
 159#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 160pub struct EditorSettings {
 161    pub tab_size: Option<NonZeroU32>,
 162    pub hard_tabs: Option<bool>,
 163    pub soft_wrap: Option<SoftWrap>,
 164    pub preferred_line_length: Option<u32>,
 165    pub format_on_save: Option<FormatOnSave>,
 166    pub remove_trailing_whitespace_on_save: Option<bool>,
 167    pub ensure_final_newline_on_save: Option<bool>,
 168    pub formatter: Option<Formatter>,
 169    pub enable_language_server: Option<bool>,
 170    pub show_copilot_suggestions: Option<bool>,
 171    pub show_whitespaces: Option<ShowWhitespaces>,
 172}
 173
 174#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 175#[serde(rename_all = "snake_case")]
 176pub enum SoftWrap {
 177    None,
 178    EditorWidth,
 179    PreferredLineLength,
 180}
 181#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 182#[serde(rename_all = "snake_case")]
 183pub enum FormatOnSave {
 184    On,
 185    Off,
 186    LanguageServer,
 187    External {
 188        command: String,
 189        arguments: Vec<String>,
 190    },
 191}
 192
 193#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 194#[serde(rename_all = "snake_case")]
 195pub enum Formatter {
 196    LanguageServer,
 197    External {
 198        command: String,
 199        arguments: Vec<String>,
 200    },
 201}
 202
 203#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 204#[serde(rename_all = "snake_case")]
 205pub enum Autosave {
 206    Off,
 207    AfterDelay { milliseconds: u64 },
 208    OnFocusChange,
 209    OnWindowChange,
 210}
 211
 212#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
 213pub struct JournalSettings {
 214    pub path: Option<String>,
 215    pub hour_format: Option<HourFormat>,
 216}
 217
 218impl Default for JournalSettings {
 219    fn default() -> Self {
 220        Self {
 221            path: Some("~".into()),
 222            hour_format: Some(Default::default()),
 223        }
 224    }
 225}
 226
 227#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
 228#[serde(rename_all = "snake_case")]
 229pub enum HourFormat {
 230    Hour12,
 231    Hour24,
 232}
 233
 234impl Default for HourFormat {
 235    fn default() -> Self {
 236        Self::Hour12
 237    }
 238}
 239
 240#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 241pub struct TerminalSettings {
 242    pub shell: Option<Shell>,
 243    pub working_directory: Option<WorkingDirectory>,
 244    pub font_size: Option<f32>,
 245    pub font_family: Option<String>,
 246    pub line_height: Option<TerminalLineHeight>,
 247    pub font_features: Option<fonts::Features>,
 248    pub env: Option<HashMap<String, String>>,
 249    pub blinking: Option<TerminalBlink>,
 250    pub alternate_scroll: Option<AlternateScroll>,
 251    pub option_as_meta: Option<bool>,
 252    pub copy_on_select: Option<bool>,
 253}
 254
 255#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
 256#[serde(rename_all = "snake_case")]
 257pub enum TerminalLineHeight {
 258    #[default]
 259    Comfortable,
 260    Standard,
 261    Custom(f32),
 262}
 263
 264impl TerminalLineHeight {
 265    fn value(&self) -> f32 {
 266        match self {
 267            TerminalLineHeight::Comfortable => 1.618,
 268            TerminalLineHeight::Standard => 1.3,
 269            TerminalLineHeight::Custom(line_height) => *line_height,
 270        }
 271    }
 272}
 273
 274#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 275#[serde(rename_all = "snake_case")]
 276pub enum TerminalBlink {
 277    Off,
 278    TerminalControlled,
 279    On,
 280}
 281
 282impl Default for TerminalBlink {
 283    fn default() -> Self {
 284        TerminalBlink::TerminalControlled
 285    }
 286}
 287
 288#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 289#[serde(rename_all = "snake_case")]
 290pub enum Shell {
 291    System,
 292    Program(String),
 293    WithArguments { program: String, args: Vec<String> },
 294}
 295
 296impl Default for Shell {
 297    fn default() -> Self {
 298        Shell::System
 299    }
 300}
 301
 302#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 303#[serde(rename_all = "snake_case")]
 304pub enum AlternateScroll {
 305    On,
 306    Off,
 307}
 308
 309impl Default for AlternateScroll {
 310    fn default() -> Self {
 311        AlternateScroll::On
 312    }
 313}
 314
 315#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 316#[serde(rename_all = "snake_case")]
 317pub enum WorkingDirectory {
 318    CurrentProjectDirectory,
 319    FirstProjectDirectory,
 320    AlwaysHome,
 321    Always { directory: String },
 322}
 323
 324impl Default for WorkingDirectory {
 325    fn default() -> Self {
 326        Self::CurrentProjectDirectory
 327    }
 328}
 329
 330impl TerminalSettings {
 331    fn line_height(&self) -> Option<f32> {
 332        self.line_height
 333            .to_owned()
 334            .map(|line_height| line_height.value())
 335    }
 336}
 337
 338#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 339pub struct SettingsFileContent {
 340    #[serde(default)]
 341    pub buffer_font_family: Option<String>,
 342    #[serde(default)]
 343    pub buffer_font_size: Option<f32>,
 344    #[serde(default)]
 345    pub buffer_font_features: Option<fonts::Features>,
 346    #[serde(default)]
 347    pub copilot: Option<CopilotSettingsContent>,
 348    #[serde(default)]
 349    pub active_pane_magnification: Option<f32>,
 350    #[serde(default)]
 351    pub cursor_blink: Option<bool>,
 352    #[serde(default)]
 353    pub confirm_quit: Option<bool>,
 354    #[serde(default)]
 355    pub hover_popover_enabled: Option<bool>,
 356    #[serde(default)]
 357    pub show_completions_on_input: Option<bool>,
 358    #[serde(default)]
 359    pub show_call_status_icon: Option<bool>,
 360    #[serde(default)]
 361    pub vim_mode: Option<bool>,
 362    #[serde(default)]
 363    pub autosave: Option<Autosave>,
 364    #[serde(flatten)]
 365    pub editor: EditorSettings,
 366    #[serde(default)]
 367    pub journal: JournalSettings,
 368    #[serde(default)]
 369    pub terminal: TerminalSettings,
 370    #[serde(default)]
 371    pub git: Option<GitSettings>,
 372    #[serde(default)]
 373    #[serde(alias = "language_overrides")]
 374    pub languages: HashMap<Arc<str>, EditorSettings>,
 375    #[serde(default)]
 376    pub lsp: HashMap<Arc<str>, LspSettings>,
 377    #[serde(default)]
 378    pub theme: Option<String>,
 379    #[serde(default)]
 380    pub telemetry: TelemetrySettings,
 381    #[serde(default)]
 382    pub auto_update: Option<bool>,
 383    #[serde(default)]
 384    pub base_keymap: Option<BaseKeymap>,
 385    #[serde(default)]
 386    pub features: FeaturesContent,
 387}
 388
 389#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 390#[serde(rename_all = "snake_case")]
 391pub struct LspSettings {
 392    pub initialization_options: Option<Value>,
 393}
 394
 395#[derive(Clone, Debug, PartialEq, Eq)]
 396pub struct Features {
 397    pub copilot: bool,
 398}
 399
 400#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 401#[serde(rename_all = "snake_case")]
 402pub struct FeaturesContent {
 403    pub copilot: Option<bool>,
 404}
 405
 406#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 407#[serde(rename_all = "snake_case")]
 408pub enum ShowWhitespaces {
 409    #[default]
 410    Selection,
 411    None,
 412    All,
 413}
 414
 415impl Settings {
 416    pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> {
 417        match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() {
 418            Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
 419            Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
 420        }
 421    }
 422
 423    /// Fill out the settings corresponding to the default.json file, overrides will be set later
 424    pub fn defaults(
 425        assets: impl AssetSource,
 426        font_cache: &FontCache,
 427        themes: &ThemeRegistry,
 428    ) -> Self {
 429        #[track_caller]
 430        fn required<T>(value: Option<T>) -> Option<T> {
 431            assert!(value.is_some(), "missing default setting value");
 432            value
 433        }
 434
 435        let defaults: SettingsFileContent = parse_json_with_comments(
 436            str::from_utf8(assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap().as_ref()).unwrap(),
 437        )
 438        .unwrap();
 439
 440        let buffer_font_features = defaults.buffer_font_features.unwrap();
 441        Self {
 442            buffer_font_family: font_cache
 443                .load_family(
 444                    &[defaults.buffer_font_family.as_ref().unwrap()],
 445                    &buffer_font_features,
 446                )
 447                .unwrap(),
 448            buffer_font_family_name: defaults.buffer_font_family.unwrap(),
 449            buffer_font_features,
 450            buffer_font_size: defaults.buffer_font_size.unwrap(),
 451            active_pane_magnification: defaults.active_pane_magnification.unwrap(),
 452            default_buffer_font_size: defaults.buffer_font_size.unwrap(),
 453            confirm_quit: defaults.confirm_quit.unwrap(),
 454            cursor_blink: defaults.cursor_blink.unwrap(),
 455            hover_popover_enabled: defaults.hover_popover_enabled.unwrap(),
 456            show_completions_on_input: defaults.show_completions_on_input.unwrap(),
 457            show_call_status_icon: defaults.show_call_status_icon.unwrap(),
 458            vim_mode: defaults.vim_mode.unwrap(),
 459            autosave: defaults.autosave.unwrap(),
 460            editor_defaults: EditorSettings {
 461                tab_size: required(defaults.editor.tab_size),
 462                hard_tabs: required(defaults.editor.hard_tabs),
 463                soft_wrap: required(defaults.editor.soft_wrap),
 464                preferred_line_length: required(defaults.editor.preferred_line_length),
 465                remove_trailing_whitespace_on_save: required(
 466                    defaults.editor.remove_trailing_whitespace_on_save,
 467                ),
 468                ensure_final_newline_on_save: required(
 469                    defaults.editor.ensure_final_newline_on_save,
 470                ),
 471                format_on_save: required(defaults.editor.format_on_save),
 472                formatter: required(defaults.editor.formatter),
 473                enable_language_server: required(defaults.editor.enable_language_server),
 474                show_copilot_suggestions: required(defaults.editor.show_copilot_suggestions),
 475                show_whitespaces: required(defaults.editor.show_whitespaces),
 476            },
 477            editor_overrides: Default::default(),
 478            copilot: CopilotSettings {
 479                disabled_globs: defaults
 480                    .copilot
 481                    .unwrap()
 482                    .disabled_globs
 483                    .unwrap()
 484                    .into_iter()
 485                    .map(|s| glob::Pattern::new(&s).unwrap())
 486                    .collect(),
 487            },
 488            git: defaults.git.unwrap(),
 489            git_overrides: Default::default(),
 490            journal_defaults: defaults.journal,
 491            journal_overrides: Default::default(),
 492            terminal_defaults: defaults.terminal,
 493            terminal_overrides: Default::default(),
 494            language_defaults: defaults.languages,
 495            language_overrides: Default::default(),
 496            lsp: defaults.lsp.clone(),
 497            theme: themes.get(&defaults.theme.unwrap()).unwrap(),
 498            telemetry_defaults: defaults.telemetry,
 499            telemetry_overrides: Default::default(),
 500            auto_update: defaults.auto_update.unwrap(),
 501            base_keymap: Default::default(),
 502            features: Features {
 503                copilot: defaults.features.copilot.unwrap(),
 504            },
 505        }
 506    }
 507
 508    // Fill out the overrride and etc. settings from the user's settings.json
 509    pub fn set_user_settings(
 510        &mut self,
 511        data: SettingsFileContent,
 512        theme_registry: &ThemeRegistry,
 513        font_cache: &FontCache,
 514    ) {
 515        let mut family_changed = false;
 516        if let Some(value) = data.buffer_font_family {
 517            self.buffer_font_family_name = value;
 518            family_changed = true;
 519        }
 520        if let Some(value) = data.buffer_font_features {
 521            self.buffer_font_features = value;
 522            family_changed = true;
 523        }
 524        if family_changed {
 525            if let Some(id) = font_cache
 526                .load_family(&[&self.buffer_font_family_name], &self.buffer_font_features)
 527                .log_err()
 528            {
 529                self.buffer_font_family = id;
 530            }
 531        }
 532
 533        if let Some(value) = &data.theme {
 534            if let Some(theme) = theme_registry.get(value).log_err() {
 535                self.theme = theme;
 536            }
 537        }
 538
 539        merge(&mut self.buffer_font_size, data.buffer_font_size);
 540        merge(
 541            &mut self.active_pane_magnification,
 542            data.active_pane_magnification,
 543        );
 544        merge(&mut self.default_buffer_font_size, data.buffer_font_size);
 545        merge(&mut self.cursor_blink, data.cursor_blink);
 546        merge(&mut self.confirm_quit, data.confirm_quit);
 547        merge(&mut self.hover_popover_enabled, data.hover_popover_enabled);
 548        merge(
 549            &mut self.show_completions_on_input,
 550            data.show_completions_on_input,
 551        );
 552        merge(&mut self.vim_mode, data.vim_mode);
 553        merge(&mut self.autosave, data.autosave);
 554        merge(&mut self.base_keymap, data.base_keymap);
 555        merge(&mut self.features.copilot, data.features.copilot);
 556
 557        if let Some(copilot) = data.copilot {
 558            if let Some(disabled_globs) = copilot.disabled_globs {
 559                self.copilot.disabled_globs = disabled_globs
 560                    .into_iter()
 561                    .filter_map(|s| glob::Pattern::new(&s).ok())
 562                    .collect()
 563            }
 564        }
 565        self.editor_overrides = data.editor;
 566        self.git_overrides = data.git.unwrap_or_default();
 567        self.journal_overrides = data.journal;
 568        self.terminal_defaults.font_size = data.terminal.font_size;
 569        self.terminal_overrides.copy_on_select = data.terminal.copy_on_select;
 570        self.terminal_overrides = data.terminal;
 571        self.language_overrides = data.languages;
 572        self.telemetry_overrides = data.telemetry;
 573        self.lsp = data.lsp;
 574        merge(&mut self.auto_update, data.auto_update);
 575    }
 576
 577    pub fn with_language_defaults(
 578        mut self,
 579        language_name: impl Into<Arc<str>>,
 580        overrides: EditorSettings,
 581    ) -> Self {
 582        self.language_defaults
 583            .insert(language_name.into(), overrides);
 584        self
 585    }
 586
 587    pub fn features(&self) -> &Features {
 588        &self.features
 589    }
 590
 591    pub fn show_copilot_suggestions(&self, language: Option<&str>, path: Option<&Path>) -> bool {
 592        if !self.features.copilot {
 593            return false;
 594        }
 595
 596        if !self.copilot_enabled_for_language(language) {
 597            return false;
 598        }
 599
 600        if let Some(path) = path {
 601            if !self.copilot_enabled_for_path(path) {
 602                return false;
 603            }
 604        }
 605
 606        true
 607    }
 608
 609    pub fn copilot_enabled_for_path(&self, path: &Path) -> bool {
 610        !self
 611            .copilot
 612            .disabled_globs
 613            .iter()
 614            .any(|glob| glob.matches_path(path))
 615    }
 616
 617    pub fn copilot_enabled_for_language(&self, language: Option<&str>) -> bool {
 618        self.language_setting(language, |settings| settings.show_copilot_suggestions)
 619    }
 620
 621    pub fn tab_size(&self, language: Option<&str>) -> NonZeroU32 {
 622        self.language_setting(language, |settings| settings.tab_size)
 623    }
 624
 625    pub fn show_whitespaces(&self, language: Option<&str>) -> ShowWhitespaces {
 626        self.language_setting(language, |settings| settings.show_whitespaces)
 627    }
 628
 629    pub fn hard_tabs(&self, language: Option<&str>) -> bool {
 630        self.language_setting(language, |settings| settings.hard_tabs)
 631    }
 632
 633    pub fn soft_wrap(&self, language: Option<&str>) -> SoftWrap {
 634        self.language_setting(language, |settings| settings.soft_wrap)
 635    }
 636
 637    pub fn preferred_line_length(&self, language: Option<&str>) -> u32 {
 638        self.language_setting(language, |settings| settings.preferred_line_length)
 639    }
 640
 641    pub fn remove_trailing_whitespace_on_save(&self, language: Option<&str>) -> bool {
 642        self.language_setting(language, |settings| {
 643            settings.remove_trailing_whitespace_on_save.clone()
 644        })
 645    }
 646
 647    pub fn ensure_final_newline_on_save(&self, language: Option<&str>) -> bool {
 648        self.language_setting(language, |settings| {
 649            settings.ensure_final_newline_on_save.clone()
 650        })
 651    }
 652
 653    pub fn format_on_save(&self, language: Option<&str>) -> FormatOnSave {
 654        self.language_setting(language, |settings| settings.format_on_save.clone())
 655    }
 656
 657    pub fn formatter(&self, language: Option<&str>) -> Formatter {
 658        self.language_setting(language, |settings| settings.formatter.clone())
 659    }
 660
 661    pub fn enable_language_server(&self, language: Option<&str>) -> bool {
 662        self.language_setting(language, |settings| settings.enable_language_server)
 663    }
 664
 665    fn language_setting<F, R>(&self, language: Option<&str>, f: F) -> R
 666    where
 667        F: Fn(&EditorSettings) -> Option<R>,
 668    {
 669        None.or_else(|| language.and_then(|l| self.language_overrides.get(l).and_then(&f)))
 670            .or_else(|| f(&self.editor_overrides))
 671            .or_else(|| language.and_then(|l| self.language_defaults.get(l).and_then(&f)))
 672            .or_else(|| f(&self.editor_defaults))
 673            .expect("missing default")
 674    }
 675
 676    pub fn git_gutter(&self) -> GitGutter {
 677        self.git_overrides.git_gutter.unwrap_or_else(|| {
 678            self.git
 679                .git_gutter
 680                .expect("git_gutter should be some by setting setup")
 681        })
 682    }
 683
 684    pub fn telemetry(&self) -> TelemetrySettings {
 685        TelemetrySettings {
 686            diagnostics: Some(self.telemetry_diagnostics()),
 687            metrics: Some(self.telemetry_metrics()),
 688        }
 689    }
 690
 691    pub fn telemetry_diagnostics(&self) -> bool {
 692        self.telemetry_overrides
 693            .diagnostics
 694            .or(self.telemetry_defaults.diagnostics)
 695            .expect("missing default")
 696    }
 697
 698    pub fn telemetry_metrics(&self) -> bool {
 699        self.telemetry_overrides
 700            .metrics
 701            .or(self.telemetry_defaults.metrics)
 702            .expect("missing default")
 703    }
 704
 705    fn terminal_setting<F, R>(&self, f: F) -> R
 706    where
 707        F: Fn(&TerminalSettings) -> Option<R>,
 708    {
 709        None.or_else(|| f(&self.terminal_overrides))
 710            .or_else(|| f(&self.terminal_defaults))
 711            .expect("missing default")
 712    }
 713
 714    pub fn terminal_line_height(&self) -> f32 {
 715        self.terminal_setting(|terminal_setting| terminal_setting.line_height())
 716    }
 717
 718    pub fn terminal_scroll(&self) -> AlternateScroll {
 719        self.terminal_setting(|terminal_setting| terminal_setting.alternate_scroll.to_owned())
 720    }
 721
 722    pub fn terminal_shell(&self) -> Shell {
 723        self.terminal_setting(|terminal_setting| terminal_setting.shell.to_owned())
 724    }
 725
 726    pub fn terminal_env(&self) -> HashMap<String, String> {
 727        self.terminal_setting(|terminal_setting| terminal_setting.env.to_owned())
 728    }
 729
 730    pub fn terminal_strategy(&self) -> WorkingDirectory {
 731        self.terminal_setting(|terminal_setting| terminal_setting.working_directory.to_owned())
 732    }
 733
 734    #[cfg(any(test, feature = "test-support"))]
 735    pub fn test(cx: &gpui::AppContext) -> Settings {
 736        Settings {
 737            buffer_font_family_name: "Monaco".to_string(),
 738            buffer_font_features: Default::default(),
 739            buffer_font_family: cx
 740                .font_cache()
 741                .load_family(&["Monaco"], &Default::default())
 742                .unwrap(),
 743            buffer_font_size: 14.,
 744            active_pane_magnification: 1.,
 745            default_buffer_font_size: 14.,
 746            confirm_quit: false,
 747            cursor_blink: true,
 748            hover_popover_enabled: true,
 749            show_completions_on_input: true,
 750            show_call_status_icon: true,
 751            vim_mode: false,
 752            autosave: Autosave::Off,
 753            editor_defaults: EditorSettings {
 754                tab_size: Some(4.try_into().unwrap()),
 755                hard_tabs: Some(false),
 756                soft_wrap: Some(SoftWrap::None),
 757                preferred_line_length: Some(80),
 758                remove_trailing_whitespace_on_save: Some(true),
 759                ensure_final_newline_on_save: Some(true),
 760                format_on_save: Some(FormatOnSave::On),
 761                formatter: Some(Formatter::LanguageServer),
 762                enable_language_server: Some(true),
 763                show_copilot_suggestions: Some(true),
 764                show_whitespaces: Some(ShowWhitespaces::None),
 765            },
 766            editor_overrides: Default::default(),
 767            copilot: Default::default(),
 768            journal_defaults: Default::default(),
 769            journal_overrides: Default::default(),
 770            terminal_defaults: Default::default(),
 771            terminal_overrides: Default::default(),
 772            git: Default::default(),
 773            git_overrides: Default::default(),
 774            language_defaults: Default::default(),
 775            language_overrides: Default::default(),
 776            lsp: Default::default(),
 777            theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default),
 778            telemetry_defaults: TelemetrySettings {
 779                diagnostics: Some(true),
 780                metrics: Some(true),
 781            },
 782            telemetry_overrides: Default::default(),
 783            auto_update: true,
 784            base_keymap: Default::default(),
 785            features: Features { copilot: true },
 786        }
 787    }
 788
 789    #[cfg(any(test, feature = "test-support"))]
 790    pub fn test_async(cx: &mut gpui::TestAppContext) {
 791        cx.update(|cx| {
 792            let settings = Self::test(cx);
 793            cx.set_global(settings);
 794        });
 795    }
 796}
 797
 798pub fn settings_file_json_schema(
 799    theme_names: Vec<String>,
 800    language_names: &[String],
 801) -> serde_json::Value {
 802    let settings = SchemaSettings::draft07().with(|settings| {
 803        settings.option_add_null_type = false;
 804    });
 805    let generator = SchemaGenerator::new(settings);
 806
 807    let mut root_schema = generator.into_root_schema_for::<SettingsFileContent>();
 808
 809    // Create a schema for a theme name.
 810    let theme_name_schema = SchemaObject {
 811        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
 812        enum_values: Some(theme_names.into_iter().map(Value::String).collect()),
 813        ..Default::default()
 814    };
 815
 816    // Create a schema for a 'languages overrides' object, associating editor
 817    // settings with specific langauges.
 818    assert!(root_schema.definitions.contains_key("EditorSettings"));
 819
 820    let languages_object_schema = SchemaObject {
 821        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
 822        object: Some(Box::new(ObjectValidation {
 823            properties: language_names
 824                .iter()
 825                .map(|name| {
 826                    (
 827                        name.clone(),
 828                        Schema::new_ref("#/definitions/EditorSettings".into()),
 829                    )
 830                })
 831                .collect(),
 832            ..Default::default()
 833        })),
 834        ..Default::default()
 835    };
 836
 837    // Add these new schemas as definitions, and modify properties of the root
 838    // schema to reference them.
 839    root_schema.definitions.extend([
 840        ("ThemeName".into(), theme_name_schema.into()),
 841        ("Languages".into(), languages_object_schema.into()),
 842    ]);
 843    let root_schema_object = &mut root_schema.schema.object.as_mut().unwrap();
 844
 845    root_schema_object.properties.extend([
 846        (
 847            "theme".to_owned(),
 848            Schema::new_ref("#/definitions/ThemeName".into()),
 849        ),
 850        (
 851            "languages".to_owned(),
 852            Schema::new_ref("#/definitions/Languages".into()),
 853        ),
 854        // For backward compatibility
 855        (
 856            "language_overrides".to_owned(),
 857            Schema::new_ref("#/definitions/Languages".into()),
 858        ),
 859    ]);
 860
 861    serde_json::to_value(root_schema).unwrap()
 862}
 863
 864fn merge<T: Copy>(target: &mut T, value: Option<T>) {
 865    if let Some(value) = value {
 866        *target = value;
 867    }
 868}
 869
 870pub fn parse_json_with_comments<T: DeserializeOwned>(content: &str) -> Result<T> {
 871    Ok(serde_json::from_reader(
 872        json_comments::CommentSettings::c_style().strip_comments(content.as_bytes()),
 873    )?)
 874}
 875
 876lazy_static! {
 877    static ref PAIR_QUERY: Query = Query::new(
 878        tree_sitter_json::language(),
 879        "
 880            (pair
 881                key: (string) @key
 882                value: (_) @value)
 883        ",
 884    )
 885    .unwrap();
 886}
 887
 888fn update_object_in_settings_file<'a>(
 889    old_object: &'a serde_json::Map<String, Value>,
 890    new_object: &'a serde_json::Map<String, Value>,
 891    text: &str,
 892    syntax_tree: &Tree,
 893    tab_size: usize,
 894    key_path: &mut Vec<&'a str>,
 895    edits: &mut Vec<(Range<usize>, String)>,
 896) {
 897    for (key, old_value) in old_object.iter() {
 898        key_path.push(key);
 899        let new_value = new_object.get(key).unwrap_or(&Value::Null);
 900
 901        // If the old and new values are both objects, then compare them key by key,
 902        // preserving the comments and formatting of the unchanged parts. Otherwise,
 903        // replace the old value with the new value.
 904        if let (Value::Object(old_sub_object), Value::Object(new_sub_object)) =
 905            (old_value, new_value)
 906        {
 907            update_object_in_settings_file(
 908                old_sub_object,
 909                new_sub_object,
 910                text,
 911                syntax_tree,
 912                tab_size,
 913                key_path,
 914                edits,
 915            )
 916        } else if old_value != new_value {
 917            let (range, replacement) =
 918                update_key_in_settings_file(text, syntax_tree, &key_path, tab_size, &new_value);
 919            edits.push((range, replacement));
 920        }
 921
 922        key_path.pop();
 923    }
 924}
 925
 926fn update_key_in_settings_file(
 927    text: &str,
 928    syntax_tree: &Tree,
 929    key_path: &[&str],
 930    tab_size: usize,
 931    new_value: impl Serialize,
 932) -> (Range<usize>, String) {
 933    const LANGUAGE_OVERRIDES: &'static str = "language_overrides";
 934    const LANGUAGES: &'static str = "languages";
 935
 936    let mut cursor = tree_sitter::QueryCursor::new();
 937
 938    let has_language_overrides = text.contains(LANGUAGE_OVERRIDES);
 939
 940    let mut depth = 0;
 941    let mut last_value_range = 0..0;
 942    let mut first_key_start = None;
 943    let mut existing_value_range = 0..text.len();
 944    let matches = cursor.matches(&PAIR_QUERY, syntax_tree.root_node(), text.as_bytes());
 945    for mat in matches {
 946        if mat.captures.len() != 2 {
 947            continue;
 948        }
 949
 950        let key_range = mat.captures[0].node.byte_range();
 951        let value_range = mat.captures[1].node.byte_range();
 952
 953        // Don't enter sub objects until we find an exact
 954        // match for the current keypath
 955        if last_value_range.contains_inclusive(&value_range) {
 956            continue;
 957        }
 958
 959        last_value_range = value_range.clone();
 960
 961        if key_range.start > existing_value_range.end {
 962            break;
 963        }
 964
 965        first_key_start.get_or_insert_with(|| key_range.start);
 966
 967        let found_key = text
 968            .get(key_range.clone())
 969            .map(|key_text| {
 970                if key_path[depth] == LANGUAGES && has_language_overrides {
 971                    return key_text == format!("\"{}\"", LANGUAGE_OVERRIDES);
 972                } else {
 973                    return key_text == format!("\"{}\"", key_path[depth]);
 974                }
 975            })
 976            .unwrap_or(false);
 977
 978        if found_key {
 979            existing_value_range = value_range;
 980            // Reset last value range when increasing in depth
 981            last_value_range = existing_value_range.start..existing_value_range.start;
 982            depth += 1;
 983
 984            if depth == key_path.len() {
 985                break;
 986            } else {
 987                first_key_start = None;
 988            }
 989        }
 990    }
 991
 992    // We found the exact key we want, insert the new value
 993    if depth == key_path.len() {
 994        let new_val = to_pretty_json(&new_value, tab_size, tab_size * depth);
 995        (existing_value_range, new_val)
 996    } else {
 997        // We have key paths, construct the sub objects
 998        let new_key = if has_language_overrides && key_path[depth] == LANGUAGES {
 999            LANGUAGE_OVERRIDES
1000        } else {
1001            key_path[depth]
1002        };
1003
1004        // We don't have the key, construct the nested objects
1005        let mut new_value = serde_json::to_value(new_value).unwrap();
1006        for key in key_path[(depth + 1)..].iter().rev() {
1007            if has_language_overrides && key == &LANGUAGES {
1008                new_value = serde_json::json!({ LANGUAGE_OVERRIDES.to_string(): new_value });
1009            } else {
1010                new_value = serde_json::json!({ key.to_string(): new_value });
1011            }
1012        }
1013
1014        if let Some(first_key_start) = first_key_start {
1015            let mut row = 0;
1016            let mut column = 0;
1017            for (ix, char) in text.char_indices() {
1018                if ix == first_key_start {
1019                    break;
1020                }
1021                if char == '\n' {
1022                    row += 1;
1023                    column = 0;
1024                } else {
1025                    column += char.len_utf8();
1026                }
1027            }
1028
1029            if row > 0 {
1030                // depth is 0 based, but division needs to be 1 based.
1031                let new_val = to_pretty_json(&new_value, column / (depth + 1), column);
1032                let space = ' ';
1033                let content = format!("\"{new_key}\": {new_val},\n{space:width$}", width = column);
1034                (first_key_start..first_key_start, content)
1035            } else {
1036                let new_val = serde_json::to_string(&new_value).unwrap();
1037                let mut content = format!(r#""{new_key}": {new_val},"#);
1038                content.push(' ');
1039                (first_key_start..first_key_start, content)
1040            }
1041        } else {
1042            new_value = serde_json::json!({ new_key.to_string(): new_value });
1043            let indent_prefix_len = 4 * depth;
1044            let mut new_val = to_pretty_json(&new_value, 4, indent_prefix_len);
1045            if depth == 0 {
1046                new_val.push('\n');
1047            }
1048
1049            (existing_value_range, new_val)
1050        }
1051    }
1052}
1053
1054fn to_pretty_json(value: &impl Serialize, indent_size: usize, indent_prefix_len: usize) -> String {
1055    const SPACES: [u8; 32] = [b' '; 32];
1056
1057    debug_assert!(indent_size <= SPACES.len());
1058    debug_assert!(indent_prefix_len <= SPACES.len());
1059
1060    let mut output = Vec::new();
1061    let mut ser = serde_json::Serializer::with_formatter(
1062        &mut output,
1063        serde_json::ser::PrettyFormatter::with_indent(&SPACES[0..indent_size.min(SPACES.len())]),
1064    );
1065
1066    value.serialize(&mut ser).unwrap();
1067    let text = String::from_utf8(output).unwrap();
1068
1069    let mut adjusted_text = String::new();
1070    for (i, line) in text.split('\n').enumerate() {
1071        if i > 0 {
1072            adjusted_text.push_str(str::from_utf8(&SPACES[0..indent_prefix_len]).unwrap());
1073        }
1074        adjusted_text.push_str(line);
1075        adjusted_text.push('\n');
1076    }
1077    adjusted_text.pop();
1078    adjusted_text
1079}
1080
1081/// Update the settings file with the given callback.
1082///
1083/// Returns a new JSON string and the offset where the first edit occurred.
1084fn update_settings_file(
1085    text: &str,
1086    mut old_file_content: SettingsFileContent,
1087    tab_size: NonZeroU32,
1088    update: impl FnOnce(&mut SettingsFileContent),
1089) -> Vec<(Range<usize>, String)> {
1090    let mut new_file_content = old_file_content.clone();
1091    update(&mut new_file_content);
1092
1093    if new_file_content.languages.len() != old_file_content.languages.len() {
1094        for language in new_file_content.languages.keys() {
1095            old_file_content
1096                .languages
1097                .entry(language.clone())
1098                .or_default();
1099        }
1100        for language in old_file_content.languages.keys() {
1101            new_file_content
1102                .languages
1103                .entry(language.clone())
1104                .or_default();
1105        }
1106    }
1107
1108    let mut parser = tree_sitter::Parser::new();
1109    parser.set_language(tree_sitter_json::language()).unwrap();
1110    let tree = parser.parse(text, None).unwrap();
1111
1112    let old_object = to_json_object(old_file_content);
1113    let new_object = to_json_object(new_file_content);
1114    let mut key_path = Vec::new();
1115    let mut edits = Vec::new();
1116    update_object_in_settings_file(
1117        &old_object,
1118        &new_object,
1119        &text,
1120        &tree,
1121        tab_size.get() as usize,
1122        &mut key_path,
1123        &mut edits,
1124    );
1125    edits.sort_unstable_by_key(|e| e.0.start);
1126    return edits;
1127}
1128
1129fn to_json_object(settings_file: SettingsFileContent) -> serde_json::Map<String, Value> {
1130    let tmp = serde_json::to_value(settings_file).unwrap();
1131    match tmp {
1132        Value::Object(map) => map,
1133        _ => unreachable!("SettingsFileContent represents a JSON map"),
1134    }
1135}
1136
1137#[cfg(test)]
1138mod tests {
1139    use super::*;
1140    use unindent::Unindent;
1141
1142    fn assert_new_settings(
1143        old_json: String,
1144        update: fn(&mut SettingsFileContent),
1145        expected_new_json: String,
1146    ) {
1147        let old_content: SettingsFileContent = serde_json::from_str(&old_json).unwrap_or_default();
1148        let edits = update_settings_file(&old_json, old_content, 4.try_into().unwrap(), update);
1149        let mut new_json = old_json;
1150        for (range, replacement) in edits.into_iter().rev() {
1151            new_json.replace_range(range, &replacement);
1152        }
1153        pretty_assertions::assert_eq!(new_json, expected_new_json);
1154    }
1155
1156    #[test]
1157    fn test_update_language_overrides_copilot() {
1158        assert_new_settings(
1159            r#"
1160                {
1161                    "language_overrides": {
1162                        "JSON": {
1163                            "show_copilot_suggestions": false
1164                        }
1165                    }
1166                }
1167            "#
1168            .unindent(),
1169            |settings| {
1170                settings.languages.insert(
1171                    "Rust".into(),
1172                    EditorSettings {
1173                        show_copilot_suggestions: Some(true),
1174                        ..Default::default()
1175                    },
1176                );
1177            },
1178            r#"
1179                {
1180                    "language_overrides": {
1181                        "Rust": {
1182                            "show_copilot_suggestions": true
1183                        },
1184                        "JSON": {
1185                            "show_copilot_suggestions": false
1186                        }
1187                    }
1188                }
1189            "#
1190            .unindent(),
1191        );
1192    }
1193
1194    #[test]
1195    fn test_update_copilot_globs() {
1196        assert_new_settings(
1197            r#"
1198                {
1199                }
1200            "#
1201            .unindent(),
1202            |settings| {
1203                settings.copilot = Some(CopilotSettingsContent {
1204                    disabled_globs: Some(vec![]),
1205                });
1206            },
1207            r#"
1208                {
1209                    "copilot": {
1210                        "disabled_globs": []
1211                    }
1212                }
1213            "#
1214            .unindent(),
1215        );
1216
1217        assert_new_settings(
1218            r#"
1219                {
1220                    "copilot": {
1221                        "disabled_globs": [
1222                            "**/*.json"
1223                        ]
1224                    }
1225                }
1226            "#
1227            .unindent(),
1228            |settings| {
1229                settings
1230                    .copilot
1231                    .get_or_insert(Default::default())
1232                    .disabled_globs
1233                    .as_mut()
1234                    .unwrap()
1235                    .push(".env".into());
1236            },
1237            r#"
1238                {
1239                    "copilot": {
1240                        "disabled_globs": [
1241                            "**/*.json",
1242                            ".env"
1243                        ]
1244                    }
1245                }
1246            "#
1247            .unindent(),
1248        );
1249    }
1250
1251    #[test]
1252    fn test_update_copilot() {
1253        assert_new_settings(
1254            r#"
1255                {
1256                    "languages": {
1257                        "JSON": {
1258                            "show_copilot_suggestions": false
1259                        }
1260                    }
1261                }
1262            "#
1263            .unindent(),
1264            |settings| {
1265                settings.editor.show_copilot_suggestions = Some(true);
1266            },
1267            r#"
1268                {
1269                    "show_copilot_suggestions": true,
1270                    "languages": {
1271                        "JSON": {
1272                            "show_copilot_suggestions": false
1273                        }
1274                    }
1275                }
1276            "#
1277            .unindent(),
1278        );
1279    }
1280
1281    #[test]
1282    fn test_update_language_copilot() {
1283        assert_new_settings(
1284            r#"
1285                {
1286                    "languages": {
1287                        "JSON": {
1288                            "show_copilot_suggestions": false
1289                        }
1290                    }
1291                }
1292            "#
1293            .unindent(),
1294            |settings| {
1295                settings.languages.insert(
1296                    "Rust".into(),
1297                    EditorSettings {
1298                        show_copilot_suggestions: Some(true),
1299                        ..Default::default()
1300                    },
1301                );
1302            },
1303            r#"
1304                {
1305                    "languages": {
1306                        "Rust": {
1307                            "show_copilot_suggestions": true
1308                        },
1309                        "JSON": {
1310                            "show_copilot_suggestions": false
1311                        }
1312                    }
1313                }
1314            "#
1315            .unindent(),
1316        );
1317    }
1318
1319    #[test]
1320    fn test_update_telemetry_setting_multiple_fields() {
1321        assert_new_settings(
1322            r#"
1323                {
1324                    "telemetry": {
1325                        "metrics": false,
1326                        "diagnostics": false
1327                    }
1328                }
1329            "#
1330            .unindent(),
1331            |settings| {
1332                settings.telemetry.set_diagnostics(true);
1333                settings.telemetry.set_metrics(true);
1334            },
1335            r#"
1336                {
1337                    "telemetry": {
1338                        "metrics": true,
1339                        "diagnostics": true
1340                    }
1341                }
1342            "#
1343            .unindent(),
1344        );
1345    }
1346
1347    #[test]
1348    fn test_update_telemetry_setting_weird_formatting() {
1349        assert_new_settings(
1350            r#"{
1351                "telemetry":   { "metrics": false, "diagnostics": true }
1352            }"#
1353            .unindent(),
1354            |settings| settings.telemetry.set_diagnostics(false),
1355            r#"{
1356                "telemetry":   { "metrics": false, "diagnostics": false }
1357            }"#
1358            .unindent(),
1359        );
1360    }
1361
1362    #[test]
1363    fn test_update_telemetry_setting_other_fields() {
1364        assert_new_settings(
1365            r#"
1366                {
1367                    "telemetry": {
1368                        "metrics": false,
1369                        "diagnostics": true
1370                    }
1371                }
1372            "#
1373            .unindent(),
1374            |settings| settings.telemetry.set_diagnostics(false),
1375            r#"
1376                {
1377                    "telemetry": {
1378                        "metrics": false,
1379                        "diagnostics": false
1380                    }
1381                }
1382            "#
1383            .unindent(),
1384        );
1385    }
1386
1387    #[test]
1388    fn test_update_telemetry_setting_empty_telemetry() {
1389        assert_new_settings(
1390            r#"
1391                {
1392                    "telemetry": {}
1393                }
1394            "#
1395            .unindent(),
1396            |settings| settings.telemetry.set_diagnostics(false),
1397            r#"
1398                {
1399                    "telemetry": {
1400                        "diagnostics": false
1401                    }
1402                }
1403            "#
1404            .unindent(),
1405        );
1406    }
1407
1408    #[test]
1409    fn test_update_telemetry_setting_pre_existing() {
1410        assert_new_settings(
1411            r#"
1412                {
1413                    "telemetry": {
1414                        "diagnostics": true
1415                    }
1416                }
1417            "#
1418            .unindent(),
1419            |settings| settings.telemetry.set_diagnostics(false),
1420            r#"
1421                {
1422                    "telemetry": {
1423                        "diagnostics": false
1424                    }
1425                }
1426            "#
1427            .unindent(),
1428        );
1429    }
1430
1431    #[test]
1432    fn test_update_telemetry_setting() {
1433        assert_new_settings(
1434            "{}".into(),
1435            |settings| settings.telemetry.set_diagnostics(true),
1436            r#"
1437                {
1438                    "telemetry": {
1439                        "diagnostics": true
1440                    }
1441                }
1442            "#
1443            .unindent(),
1444        );
1445    }
1446
1447    #[test]
1448    fn test_update_object_empty_doc() {
1449        assert_new_settings(
1450            "".into(),
1451            |settings| settings.telemetry.set_diagnostics(true),
1452            r#"
1453                {
1454                    "telemetry": {
1455                        "diagnostics": true
1456                    }
1457                }
1458            "#
1459            .unindent(),
1460        );
1461    }
1462
1463    #[test]
1464    fn test_write_theme_into_settings_with_theme() {
1465        assert_new_settings(
1466            r#"
1467                {
1468                    "theme": "One Dark"
1469                }
1470            "#
1471            .unindent(),
1472            |settings| settings.theme = Some("summerfruit-light".to_string()),
1473            r#"
1474                {
1475                    "theme": "summerfruit-light"
1476                }
1477            "#
1478            .unindent(),
1479        );
1480    }
1481
1482    #[test]
1483    fn test_write_theme_into_empty_settings() {
1484        assert_new_settings(
1485            r#"
1486                {
1487                }
1488            "#
1489            .unindent(),
1490            |settings| settings.theme = Some("summerfruit-light".to_string()),
1491            r#"
1492                {
1493                    "theme": "summerfruit-light"
1494                }
1495            "#
1496            .unindent(),
1497        );
1498    }
1499
1500    #[test]
1501    fn write_key_no_document() {
1502        assert_new_settings(
1503            "".to_string(),
1504            |settings| settings.theme = Some("summerfruit-light".to_string()),
1505            r#"
1506                {
1507                    "theme": "summerfruit-light"
1508                }
1509            "#
1510            .unindent(),
1511        );
1512    }
1513
1514    #[test]
1515    fn test_write_theme_into_single_line_settings_without_theme() {
1516        assert_new_settings(
1517            r#"{ "a": "", "ok": true }"#.to_string(),
1518            |settings| settings.theme = Some("summerfruit-light".to_string()),
1519            r#"{ "theme": "summerfruit-light", "a": "", "ok": true }"#.to_string(),
1520        );
1521    }
1522
1523    #[test]
1524    fn test_write_theme_pre_object_whitespace() {
1525        assert_new_settings(
1526            r#"          { "a": "", "ok": true }"#.to_string(),
1527            |settings| settings.theme = Some("summerfruit-light".to_string()),
1528            r#"          { "theme": "summerfruit-light", "a": "", "ok": true }"#.unindent(),
1529        );
1530    }
1531
1532    #[test]
1533    fn test_write_theme_into_multi_line_settings_without_theme() {
1534        assert_new_settings(
1535            r#"
1536                {
1537                    "a": "b"
1538                }
1539            "#
1540            .unindent(),
1541            |settings| settings.theme = Some("summerfruit-light".to_string()),
1542            r#"
1543                {
1544                    "theme": "summerfruit-light",
1545                    "a": "b"
1546                }
1547            "#
1548            .unindent(),
1549        );
1550    }
1551}