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