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