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