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