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