settings.rs

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