settings.rs

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