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