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