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    AssetSource,
   9};
  10use schemars::{
  11    gen::{SchemaGenerator, SchemaSettings},
  12    schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec},
  13    JsonSchema,
  14};
  15use serde::{de::DeserializeOwned, Deserialize, Serialize};
  16use serde_json::Value;
  17use sqlez::{
  18    bindable::{Bind, Column, StaticColumnCount},
  19    statement::Statement,
  20};
  21use std::{collections::HashMap, num::NonZeroU32, str, sync::Arc};
  22use theme::{Theme, ThemeRegistry};
  23use tree_sitter::Query;
  24use util::ResultExt as _;
  25
  26pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
  27pub use watched_json::watch_files;
  28
  29#[derive(Clone)]
  30pub struct Settings {
  31    pub buffer_font_family: FamilyId,
  32    pub default_buffer_font_size: f32,
  33    pub buffer_font_size: f32,
  34    pub active_pane_magnification: f32,
  35    pub cursor_blink: bool,
  36    pub confirm_quit: bool,
  37    pub hover_popover_enabled: bool,
  38    pub show_completions_on_input: bool,
  39    pub show_call_status_icon: bool,
  40    pub vim_mode: bool,
  41    pub autosave: Autosave,
  42    pub default_dock_anchor: DockAnchor,
  43    pub editor_defaults: EditorSettings,
  44    pub editor_overrides: EditorSettings,
  45    pub git: GitSettings,
  46    pub git_overrides: GitSettings,
  47    pub journal_defaults: JournalSettings,
  48    pub journal_overrides: JournalSettings,
  49    pub terminal_defaults: TerminalSettings,
  50    pub terminal_overrides: TerminalSettings,
  51    pub language_defaults: HashMap<Arc<str>, EditorSettings>,
  52    pub language_overrides: HashMap<Arc<str>, EditorSettings>,
  53    pub lsp: HashMap<Arc<str>, LspSettings>,
  54    pub theme: Arc<Theme>,
  55    pub telemetry_defaults: TelemetrySettings,
  56    pub telemetry_overrides: TelemetrySettings,
  57    pub auto_update: bool,
  58    pub base_keymap: Option<BaseKeymap>,
  59}
  60
  61#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
  62pub enum BaseKeymap {
  63    JetBrains,
  64    Sublime,
  65    Atom,
  66}
  67
  68impl BaseKeymap {
  69    pub fn asset_path(&self) -> &str {
  70        match self {
  71            BaseKeymap::JetBrains => "keymaps/jetbrains.json",
  72            BaseKeymap::Sublime => "keymaps/sublime_text.json",
  73            BaseKeymap::Atom => "keymaps/atom.json",
  74        }
  75    }
  76}
  77
  78#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
  79pub struct TelemetrySettings {
  80    diagnostics: Option<bool>,
  81    metrics: Option<bool>,
  82}
  83
  84impl TelemetrySettings {
  85    pub fn metrics(&self) -> bool {
  86        self.metrics.unwrap()
  87    }
  88
  89    pub fn diagnostics(&self) -> bool {
  90        self.diagnostics.unwrap()
  91    }
  92
  93    pub fn set_metrics(&mut self, value: bool) {
  94        self.metrics = Some(value);
  95    }
  96
  97    pub fn set_diagnostics(&mut self, value: bool) {
  98        self.diagnostics = Some(value);
  99    }
 100}
 101
 102#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 103pub struct GitSettings {
 104    pub git_gutter: Option<GitGutter>,
 105    pub gutter_debounce: Option<u64>,
 106}
 107
 108#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
 109#[serde(rename_all = "snake_case")]
 110pub enum GitGutter {
 111    #[default]
 112    TrackedFiles,
 113    Hide,
 114}
 115
 116pub struct GitGutterConfig {}
 117
 118#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 119pub struct EditorSettings {
 120    pub tab_size: Option<NonZeroU32>,
 121    pub hard_tabs: Option<bool>,
 122    pub soft_wrap: Option<SoftWrap>,
 123    pub preferred_line_length: Option<u32>,
 124    pub format_on_save: Option<FormatOnSave>,
 125    pub remove_trailing_whitespace_on_save: Option<bool>,
 126    pub ensure_final_newline_on_save: Option<bool>,
 127    pub formatter: Option<Formatter>,
 128    pub enable_language_server: Option<bool>,
 129}
 130
 131#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 132#[serde(rename_all = "snake_case")]
 133pub enum SoftWrap {
 134    None,
 135    EditorWidth,
 136    PreferredLineLength,
 137}
 138#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 139#[serde(rename_all = "snake_case")]
 140pub enum FormatOnSave {
 141    On,
 142    Off,
 143    LanguageServer,
 144    External {
 145        command: String,
 146        arguments: Vec<String>,
 147    },
 148}
 149
 150#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 151#[serde(rename_all = "snake_case")]
 152pub enum Formatter {
 153    LanguageServer,
 154    External {
 155        command: String,
 156        arguments: Vec<String>,
 157    },
 158}
 159
 160#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 161#[serde(rename_all = "snake_case")]
 162pub enum Autosave {
 163    Off,
 164    AfterDelay { milliseconds: u64 },
 165    OnFocusChange,
 166    OnWindowChange,
 167}
 168
 169#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
 170pub struct JournalSettings {
 171    pub path: Option<String>,
 172    pub hour_format: Option<HourFormat>,
 173}
 174
 175impl Default for JournalSettings {
 176    fn default() -> Self {
 177        Self {
 178            path: Some("~".into()),
 179            hour_format: Some(Default::default()),
 180        }
 181    }
 182}
 183
 184#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
 185#[serde(rename_all = "snake_case")]
 186pub enum HourFormat {
 187    Hour12,
 188    Hour24,
 189}
 190
 191impl Default for HourFormat {
 192    fn default() -> Self {
 193        Self::Hour12
 194    }
 195}
 196
 197#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 198pub struct TerminalSettings {
 199    pub shell: Option<Shell>,
 200    pub working_directory: Option<WorkingDirectory>,
 201    pub font_size: Option<f32>,
 202    pub font_family: Option<String>,
 203    pub env: Option<HashMap<String, String>>,
 204    pub blinking: Option<TerminalBlink>,
 205    pub alternate_scroll: Option<AlternateScroll>,
 206    pub option_as_meta: Option<bool>,
 207    pub copy_on_select: Option<bool>,
 208}
 209
 210#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 211#[serde(rename_all = "snake_case")]
 212pub enum TerminalBlink {
 213    Off,
 214    TerminalControlled,
 215    On,
 216}
 217
 218impl Default for TerminalBlink {
 219    fn default() -> Self {
 220        TerminalBlink::TerminalControlled
 221    }
 222}
 223
 224#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 225#[serde(rename_all = "snake_case")]
 226pub enum Shell {
 227    System,
 228    Program(String),
 229    WithArguments { program: String, args: Vec<String> },
 230}
 231
 232impl Default for Shell {
 233    fn default() -> Self {
 234        Shell::System
 235    }
 236}
 237
 238#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 239#[serde(rename_all = "snake_case")]
 240pub enum AlternateScroll {
 241    On,
 242    Off,
 243}
 244
 245impl Default for AlternateScroll {
 246    fn default() -> Self {
 247        AlternateScroll::On
 248    }
 249}
 250
 251#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 252#[serde(rename_all = "snake_case")]
 253pub enum WorkingDirectory {
 254    CurrentProjectDirectory,
 255    FirstProjectDirectory,
 256    AlwaysHome,
 257    Always { directory: String },
 258}
 259
 260impl Default for WorkingDirectory {
 261    fn default() -> Self {
 262        Self::CurrentProjectDirectory
 263    }
 264}
 265
 266#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)]
 267#[serde(rename_all = "snake_case")]
 268pub enum DockAnchor {
 269    #[default]
 270    Bottom,
 271    Right,
 272    Expanded,
 273}
 274
 275impl StaticColumnCount for DockAnchor {}
 276impl Bind for DockAnchor {
 277    fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
 278        match self {
 279            DockAnchor::Bottom => "Bottom",
 280            DockAnchor::Right => "Right",
 281            DockAnchor::Expanded => "Expanded",
 282        }
 283        .bind(statement, start_index)
 284    }
 285}
 286
 287impl Column for DockAnchor {
 288    fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
 289        String::column(statement, start_index).and_then(|(anchor_text, next_index)| {
 290            Ok((
 291                match anchor_text.as_ref() {
 292                    "Bottom" => DockAnchor::Bottom,
 293                    "Right" => DockAnchor::Right,
 294                    "Expanded" => DockAnchor::Expanded,
 295                    _ => bail!("Stored dock anchor is incorrect"),
 296                },
 297                next_index,
 298            ))
 299        })
 300    }
 301}
 302
 303#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 304pub struct SettingsFileContent {
 305    #[serde(default)]
 306    pub projects_online_by_default: Option<bool>,
 307    #[serde(default)]
 308    pub buffer_font_family: Option<String>,
 309    #[serde(default)]
 310    pub buffer_font_size: Option<f32>,
 311    #[serde(default)]
 312    pub active_pane_magnification: Option<f32>,
 313    #[serde(default)]
 314    pub cursor_blink: Option<bool>,
 315    #[serde(default)]
 316    pub confirm_quit: Option<bool>,
 317    #[serde(default)]
 318    pub hover_popover_enabled: Option<bool>,
 319    #[serde(default)]
 320    pub show_completions_on_input: Option<bool>,
 321    #[serde(default)]
 322    pub show_call_status_icon: Option<bool>,
 323    #[serde(default)]
 324    pub vim_mode: Option<bool>,
 325    #[serde(default)]
 326    pub autosave: Option<Autosave>,
 327    #[serde(default)]
 328    pub default_dock_anchor: Option<DockAnchor>,
 329    #[serde(flatten)]
 330    pub editor: EditorSettings,
 331    #[serde(default)]
 332    pub journal: JournalSettings,
 333    #[serde(default)]
 334    pub terminal: TerminalSettings,
 335    #[serde(default)]
 336    pub git: Option<GitSettings>,
 337    #[serde(default)]
 338    #[serde(alias = "language_overrides")]
 339    pub languages: HashMap<Arc<str>, EditorSettings>,
 340    #[serde(default)]
 341    pub lsp: HashMap<Arc<str>, LspSettings>,
 342    #[serde(default)]
 343    pub theme: Option<String>,
 344    #[serde(default)]
 345    pub telemetry: TelemetrySettings,
 346    #[serde(default)]
 347    pub auto_update: Option<bool>,
 348    #[serde(default)]
 349    pub base_keymap: Option<BaseKeymap>,
 350}
 351
 352#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 353#[serde(rename_all = "snake_case")]
 354pub struct LspSettings {
 355    pub initialization_options: Option<Value>,
 356}
 357
 358impl Settings {
 359    /// Fill out the settings corresponding to the default.json file, overrides will be set later
 360    pub fn defaults(
 361        assets: impl AssetSource,
 362        font_cache: &FontCache,
 363        themes: &ThemeRegistry,
 364    ) -> Self {
 365        #[track_caller]
 366        fn required<T>(value: Option<T>) -> Option<T> {
 367            assert!(value.is_some(), "missing default setting value");
 368            value
 369        }
 370
 371        let defaults: SettingsFileContent = parse_json_with_comments(
 372            str::from_utf8(assets.load("settings/default.json").unwrap().as_ref()).unwrap(),
 373        )
 374        .unwrap();
 375
 376        Self {
 377            buffer_font_family: font_cache
 378                .load_family(&[defaults.buffer_font_family.as_ref().unwrap()])
 379                .unwrap(),
 380            buffer_font_size: defaults.buffer_font_size.unwrap(),
 381            active_pane_magnification: defaults.active_pane_magnification.unwrap(),
 382            default_buffer_font_size: defaults.buffer_font_size.unwrap(),
 383            confirm_quit: defaults.confirm_quit.unwrap(),
 384            cursor_blink: defaults.cursor_blink.unwrap(),
 385            hover_popover_enabled: defaults.hover_popover_enabled.unwrap(),
 386            show_completions_on_input: defaults.show_completions_on_input.unwrap(),
 387            show_call_status_icon: defaults.show_call_status_icon.unwrap(),
 388            vim_mode: defaults.vim_mode.unwrap(),
 389            autosave: defaults.autosave.unwrap(),
 390            default_dock_anchor: defaults.default_dock_anchor.unwrap(),
 391            editor_defaults: EditorSettings {
 392                tab_size: required(defaults.editor.tab_size),
 393                hard_tabs: required(defaults.editor.hard_tabs),
 394                soft_wrap: required(defaults.editor.soft_wrap),
 395                preferred_line_length: required(defaults.editor.preferred_line_length),
 396                remove_trailing_whitespace_on_save: required(
 397                    defaults.editor.remove_trailing_whitespace_on_save,
 398                ),
 399                ensure_final_newline_on_save: required(
 400                    defaults.editor.ensure_final_newline_on_save,
 401                ),
 402                format_on_save: required(defaults.editor.format_on_save),
 403                formatter: required(defaults.editor.formatter),
 404                enable_language_server: required(defaults.editor.enable_language_server),
 405            },
 406            editor_overrides: Default::default(),
 407            git: defaults.git.unwrap(),
 408            git_overrides: Default::default(),
 409            journal_defaults: defaults.journal,
 410            journal_overrides: Default::default(),
 411            terminal_defaults: defaults.terminal,
 412            terminal_overrides: Default::default(),
 413            language_defaults: defaults.languages,
 414            language_overrides: Default::default(),
 415            lsp: defaults.lsp.clone(),
 416            theme: themes.get(&defaults.theme.unwrap()).unwrap(),
 417            telemetry_defaults: defaults.telemetry,
 418            telemetry_overrides: Default::default(),
 419            auto_update: defaults.auto_update.unwrap(),
 420            base_keymap: Default::default(),
 421        }
 422    }
 423
 424    // Fill out the overrride and etc. settings from the user's settings.json
 425    pub fn set_user_settings(
 426        &mut self,
 427        data: SettingsFileContent,
 428        theme_registry: &ThemeRegistry,
 429        font_cache: &FontCache,
 430    ) {
 431        if let Some(value) = &data.buffer_font_family {
 432            if let Some(id) = font_cache.load_family(&[value]).log_err() {
 433                self.buffer_font_family = id;
 434            }
 435        }
 436        if let Some(value) = &data.theme {
 437            if let Some(theme) = theme_registry.get(value).log_err() {
 438                self.theme = theme;
 439            }
 440        }
 441
 442        merge(&mut self.buffer_font_size, data.buffer_font_size);
 443        merge(
 444            &mut self.active_pane_magnification,
 445            data.active_pane_magnification,
 446        );
 447        merge(&mut self.default_buffer_font_size, data.buffer_font_size);
 448        merge(&mut self.cursor_blink, data.cursor_blink);
 449        merge(&mut self.confirm_quit, data.confirm_quit);
 450        merge(&mut self.hover_popover_enabled, data.hover_popover_enabled);
 451        merge(
 452            &mut self.show_completions_on_input,
 453            data.show_completions_on_input,
 454        );
 455        merge(&mut self.vim_mode, data.vim_mode);
 456        merge(&mut self.autosave, data.autosave);
 457        merge(&mut self.default_dock_anchor, data.default_dock_anchor);
 458        merge(&mut self.base_keymap, Some(data.base_keymap));
 459
 460        // Ensure terminal font is loaded, so we can request it in terminal_element layout
 461        if let Some(terminal_font) = &data.terminal.font_family {
 462            font_cache.load_family(&[terminal_font]).log_err();
 463        }
 464
 465        self.editor_overrides = data.editor;
 466        self.git_overrides = data.git.unwrap_or_default();
 467        self.journal_overrides = data.journal;
 468        self.terminal_defaults.font_size = data.terminal.font_size;
 469        self.terminal_overrides.copy_on_select = data.terminal.copy_on_select;
 470        self.terminal_overrides = data.terminal;
 471        self.language_overrides = data.languages;
 472        self.telemetry_overrides = data.telemetry;
 473        self.lsp = data.lsp;
 474        merge(&mut self.auto_update, data.auto_update);
 475    }
 476
 477    pub fn with_language_defaults(
 478        mut self,
 479        language_name: impl Into<Arc<str>>,
 480        overrides: EditorSettings,
 481    ) -> Self {
 482        self.language_defaults
 483            .insert(language_name.into(), overrides);
 484        self
 485    }
 486
 487    pub fn tab_size(&self, language: Option<&str>) -> NonZeroU32 {
 488        self.language_setting(language, |settings| settings.tab_size)
 489    }
 490
 491    pub fn hard_tabs(&self, language: Option<&str>) -> bool {
 492        self.language_setting(language, |settings| settings.hard_tabs)
 493    }
 494
 495    pub fn soft_wrap(&self, language: Option<&str>) -> SoftWrap {
 496        self.language_setting(language, |settings| settings.soft_wrap)
 497    }
 498
 499    pub fn preferred_line_length(&self, language: Option<&str>) -> u32 {
 500        self.language_setting(language, |settings| settings.preferred_line_length)
 501    }
 502
 503    pub fn remove_trailing_whitespace_on_save(&self, language: Option<&str>) -> bool {
 504        self.language_setting(language, |settings| {
 505            settings.remove_trailing_whitespace_on_save.clone()
 506        })
 507    }
 508
 509    pub fn ensure_final_newline_on_save(&self, language: Option<&str>) -> bool {
 510        self.language_setting(language, |settings| {
 511            settings.ensure_final_newline_on_save.clone()
 512        })
 513    }
 514
 515    pub fn format_on_save(&self, language: Option<&str>) -> FormatOnSave {
 516        self.language_setting(language, |settings| settings.format_on_save.clone())
 517    }
 518
 519    pub fn formatter(&self, language: Option<&str>) -> Formatter {
 520        self.language_setting(language, |settings| settings.formatter.clone())
 521    }
 522
 523    pub fn enable_language_server(&self, language: Option<&str>) -> bool {
 524        self.language_setting(language, |settings| settings.enable_language_server)
 525    }
 526
 527    fn language_setting<F, R>(&self, language: Option<&str>, f: F) -> R
 528    where
 529        F: Fn(&EditorSettings) -> Option<R>,
 530    {
 531        None.or_else(|| language.and_then(|l| self.language_overrides.get(l).and_then(&f)))
 532            .or_else(|| f(&self.editor_overrides))
 533            .or_else(|| language.and_then(|l| self.language_defaults.get(l).and_then(&f)))
 534            .or_else(|| f(&self.editor_defaults))
 535            .expect("missing default")
 536    }
 537
 538    pub fn git_gutter(&self) -> GitGutter {
 539        self.git_overrides.git_gutter.unwrap_or_else(|| {
 540            self.git
 541                .git_gutter
 542                .expect("git_gutter should be some by setting setup")
 543        })
 544    }
 545
 546    fn terminal_setting<F, R: Default + Clone>(&self, f: F) -> R
 547    where
 548        F: Fn(&TerminalSettings) -> Option<&R>,
 549    {
 550        f(&self.terminal_overrides)
 551            .or_else(|| f(&self.terminal_defaults))
 552            .cloned()
 553            .unwrap_or_else(|| R::default())
 554    }
 555
 556    pub fn telemetry(&self) -> TelemetrySettings {
 557        TelemetrySettings {
 558            diagnostics: Some(self.telemetry_diagnostics()),
 559            metrics: Some(self.telemetry_metrics()),
 560        }
 561    }
 562
 563    pub fn telemetry_diagnostics(&self) -> bool {
 564        self.telemetry_overrides
 565            .diagnostics
 566            .or(self.telemetry_defaults.diagnostics)
 567            .expect("missing default")
 568    }
 569
 570    pub fn telemetry_metrics(&self) -> bool {
 571        self.telemetry_overrides
 572            .metrics
 573            .or(self.telemetry_defaults.metrics)
 574            .expect("missing default")
 575    }
 576
 577    pub fn terminal_scroll(&self) -> AlternateScroll {
 578        self.terminal_setting(|terminal_setting| terminal_setting.alternate_scroll.as_ref())
 579    }
 580
 581    pub fn terminal_shell(&self) -> Shell {
 582        self.terminal_setting(|terminal_setting| terminal_setting.shell.as_ref())
 583    }
 584
 585    pub fn terminal_env(&self) -> HashMap<String, String> {
 586        self.terminal_setting(|terminal_setting| terminal_setting.env.as_ref())
 587    }
 588
 589    pub fn terminal_strategy(&self) -> WorkingDirectory {
 590        self.terminal_setting(|terminal_setting| terminal_setting.working_directory.as_ref())
 591    }
 592
 593    #[cfg(any(test, feature = "test-support"))]
 594    pub fn test(cx: &gpui::AppContext) -> Settings {
 595        Settings {
 596            buffer_font_family: cx.font_cache().load_family(&["Monaco"]).unwrap(),
 597            buffer_font_size: 14.,
 598            active_pane_magnification: 1.,
 599            default_buffer_font_size: 14.,
 600            confirm_quit: false,
 601            cursor_blink: true,
 602            hover_popover_enabled: true,
 603            show_completions_on_input: true,
 604            show_call_status_icon: true,
 605            vim_mode: false,
 606            autosave: Autosave::Off,
 607            default_dock_anchor: DockAnchor::Bottom,
 608            editor_defaults: EditorSettings {
 609                tab_size: Some(4.try_into().unwrap()),
 610                hard_tabs: Some(false),
 611                soft_wrap: Some(SoftWrap::None),
 612                preferred_line_length: Some(80),
 613                remove_trailing_whitespace_on_save: Some(true),
 614                ensure_final_newline_on_save: Some(true),
 615                format_on_save: Some(FormatOnSave::On),
 616                formatter: Some(Formatter::LanguageServer),
 617                enable_language_server: Some(true),
 618            },
 619            editor_overrides: Default::default(),
 620            journal_defaults: Default::default(),
 621            journal_overrides: Default::default(),
 622            terminal_defaults: Default::default(),
 623            terminal_overrides: Default::default(),
 624            git: Default::default(),
 625            git_overrides: Default::default(),
 626            language_defaults: Default::default(),
 627            language_overrides: Default::default(),
 628            lsp: Default::default(),
 629            theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default),
 630            telemetry_defaults: TelemetrySettings {
 631                diagnostics: Some(true),
 632                metrics: Some(true),
 633            },
 634            telemetry_overrides: Default::default(),
 635            auto_update: true,
 636            base_keymap: None,
 637        }
 638    }
 639
 640    #[cfg(any(test, feature = "test-support"))]
 641    pub fn test_async(cx: &mut gpui::TestAppContext) {
 642        cx.update(|cx| {
 643            let settings = Self::test(cx);
 644            cx.set_global(settings);
 645        });
 646    }
 647}
 648
 649pub fn settings_file_json_schema(
 650    theme_names: Vec<String>,
 651    language_names: &[String],
 652) -> serde_json::Value {
 653    let settings = SchemaSettings::draft07().with(|settings| {
 654        settings.option_add_null_type = false;
 655    });
 656    let generator = SchemaGenerator::new(settings);
 657    let mut root_schema = generator.into_root_schema_for::<SettingsFileContent>();
 658
 659    // Create a schema for a theme name.
 660    let theme_name_schema = SchemaObject {
 661        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
 662        enum_values: Some(theme_names.into_iter().map(Value::String).collect()),
 663        ..Default::default()
 664    };
 665
 666    // Create a schema for a 'languages overrides' object, associating editor
 667    // settings with specific langauges.
 668    assert!(root_schema.definitions.contains_key("EditorSettings"));
 669    let languages_object_schema = SchemaObject {
 670        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
 671        object: Some(Box::new(ObjectValidation {
 672            properties: language_names
 673                .iter()
 674                .map(|name| {
 675                    (
 676                        name.clone(),
 677                        Schema::new_ref("#/definitions/EditorSettings".into()),
 678                    )
 679                })
 680                .collect(),
 681            ..Default::default()
 682        })),
 683        ..Default::default()
 684    };
 685
 686    // Add these new schemas as definitions, and modify properties of the root
 687    // schema to reference them.
 688    root_schema.definitions.extend([
 689        ("ThemeName".into(), theme_name_schema.into()),
 690        ("Languages".into(), languages_object_schema.into()),
 691    ]);
 692    let root_schema_object = &mut root_schema.schema.object.as_mut().unwrap();
 693
 694    root_schema_object.properties.extend([
 695        (
 696            "theme".to_owned(),
 697            Schema::new_ref("#/definitions/ThemeName".into()),
 698        ),
 699        (
 700            "languages".to_owned(),
 701            Schema::new_ref("#/definitions/Languages".into()),
 702        ),
 703        // For backward compatibility
 704        (
 705            "language_overrides".to_owned(),
 706            Schema::new_ref("#/definitions/Languages".into()),
 707        ),
 708    ]);
 709
 710    serde_json::to_value(root_schema).unwrap()
 711}
 712
 713fn merge<T: Copy>(target: &mut T, value: Option<T>) {
 714    if let Some(value) = value {
 715        *target = value;
 716    }
 717}
 718
 719pub fn parse_json_with_comments<T: DeserializeOwned>(content: &str) -> Result<T> {
 720    Ok(serde_json::from_reader(
 721        json_comments::CommentSettings::c_style().strip_comments(content.as_bytes()),
 722    )?)
 723}
 724
 725/// Expects the key to be unquoted, and the value to be valid JSON
 726/// (e.g. values should be unquoted for numbers and bools, quoted for strings)
 727pub fn write_settings_key<T: ?Sized + Serialize + Clone>(
 728    settings_content: &mut String,
 729    key_path: &[&str],
 730    new_value: &T,
 731) {
 732    let mut parser = tree_sitter::Parser::new();
 733    parser.set_language(tree_sitter_json::language()).unwrap();
 734    let tree = parser.parse(&settings_content, None).unwrap();
 735
 736    let mut cursor = tree_sitter::QueryCursor::new();
 737
 738    let query = Query::new(
 739        tree_sitter_json::language(),
 740        "
 741            (pair
 742                key: (string) @key
 743                value: (_) @value)
 744        ",
 745    )
 746    .unwrap();
 747
 748    let mut depth = 0;
 749    let mut first_key_start = None;
 750    let mut existing_value_range = 0..settings_content.len();
 751    let matches = cursor.matches(&query, tree.root_node(), settings_content.as_bytes());
 752    for mat in matches {
 753        if mat.captures.len() != 2 {
 754            continue;
 755        }
 756
 757        let key_range = mat.captures[0].node.byte_range();
 758        let value_range = mat.captures[1].node.byte_range();
 759
 760        if key_range.start > existing_value_range.end {
 761            break;
 762        }
 763
 764        first_key_start.get_or_insert_with(|| key_range.start);
 765
 766        let found_key = settings_content
 767            .get(key_range.clone())
 768            .map(|key_text| key_text == format!("\"{}\"", key_path[depth]))
 769            .unwrap_or(false);
 770
 771        if found_key {
 772            existing_value_range = value_range;
 773            depth += 1;
 774
 775            if depth == key_path.len() {
 776                break;
 777            } else {
 778                first_key_start = None;
 779            }
 780        }
 781    }
 782
 783    // We found the exact key we want, insert the new value
 784    if depth == key_path.len() {
 785        let new_val = serde_json::to_string_pretty(new_value)
 786            .expect("Could not serialize new json field to string");
 787        settings_content.replace_range(existing_value_range, &new_val);
 788    } else {
 789        // We have key paths, construct the sub objects
 790        let new_key = key_path[depth];
 791
 792        // We don't have the key, construct the nested objects
 793        let mut new_value = serde_json::to_value(new_value).unwrap();
 794        for key in key_path[(depth + 1)..].iter().rev() {
 795            new_value = serde_json::json!({ key.to_string(): new_value });
 796        }
 797
 798        if let Some(first_key_start) = first_key_start {
 799            let mut row = 0;
 800            let mut column = 0;
 801            for (ix, char) in settings_content.char_indices() {
 802                if ix == first_key_start {
 803                    break;
 804                }
 805                if char == '\n' {
 806                    row += 1;
 807                    column = 0;
 808                } else {
 809                    column += char.len_utf8();
 810                }
 811            }
 812
 813            if row > 0 {
 814                let new_val = to_pretty_json(&new_value, column, column);
 815                let content = format!(r#""{new_key}": {new_val},"#);
 816                settings_content.insert_str(first_key_start, &content);
 817
 818                settings_content.insert_str(
 819                    first_key_start + content.len(),
 820                    &format!("\n{:width$}", ' ', width = column),
 821                )
 822            } else {
 823                let new_val = serde_json::to_string(&new_value).unwrap();
 824                let mut content = format!(r#""{new_key}": {new_val},"#);
 825                content.push(' ');
 826                settings_content.insert_str(first_key_start, &content);
 827            }
 828        } else {
 829            new_value = serde_json::json!({ new_key.to_string(): new_value });
 830            let indent_prefix_len = 4 * depth;
 831            let new_val = to_pretty_json(&new_value, 4, indent_prefix_len);
 832
 833            settings_content.replace_range(existing_value_range, &new_val);
 834            if depth == 0 {
 835                settings_content.push('\n');
 836            }
 837        }
 838    }
 839}
 840
 841fn to_pretty_json(
 842    value: &serde_json::Value,
 843    indent_size: usize,
 844    indent_prefix_len: usize,
 845) -> String {
 846    const SPACES: [u8; 32] = [b' '; 32];
 847
 848    debug_assert!(indent_size <= SPACES.len());
 849    debug_assert!(indent_prefix_len <= SPACES.len());
 850
 851    let mut output = Vec::new();
 852    let mut ser = serde_json::Serializer::with_formatter(
 853        &mut output,
 854        serde_json::ser::PrettyFormatter::with_indent(&SPACES[0..indent_size.min(SPACES.len())]),
 855    );
 856
 857    value.serialize(&mut ser).unwrap();
 858    let text = String::from_utf8(output).unwrap();
 859
 860    let mut adjusted_text = String::new();
 861    for (i, line) in text.split('\n').enumerate() {
 862        if i > 0 {
 863            adjusted_text.push_str(str::from_utf8(&SPACES[0..indent_prefix_len]).unwrap());
 864        }
 865        adjusted_text.push_str(line);
 866        adjusted_text.push('\n');
 867    }
 868    adjusted_text.pop();
 869    adjusted_text
 870}
 871
 872pub fn update_settings_file(
 873    mut text: String,
 874    old_file_content: SettingsFileContent,
 875    update: impl FnOnce(&mut SettingsFileContent),
 876) -> String {
 877    let mut new_file_content = old_file_content.clone();
 878
 879    update(&mut new_file_content);
 880
 881    let old_object = to_json_object(old_file_content);
 882    let new_object = to_json_object(new_file_content);
 883
 884    fn apply_changes_to_json_text(
 885        old_object: &serde_json::Map<String, Value>,
 886        new_object: &serde_json::Map<String, Value>,
 887        current_key_path: Vec<&str>,
 888        json_text: &mut String,
 889    ) {
 890        for (key, old_value) in old_object.iter() {
 891            // We know that these two are from the same shape of object, so we can just unwrap
 892            let new_value = new_object.get(key).unwrap();
 893            if old_value != new_value {
 894                match new_value {
 895                    Value::Bool(_) | Value::Number(_) | Value::String(_) => {
 896                        let mut key_path = current_key_path.clone();
 897                        key_path.push(key);
 898                        write_settings_key(json_text, &key_path, &new_value);
 899                    }
 900                    Value::Object(new_sub_object) => {
 901                        let mut key_path = current_key_path.clone();
 902                        key_path.push(key);
 903                        if let Value::Object(old_sub_object) = old_value {
 904                            apply_changes_to_json_text(
 905                                old_sub_object,
 906                                new_sub_object,
 907                                key_path,
 908                                json_text,
 909                            );
 910                        } else {
 911                            unimplemented!("This function doesn't support changing values from simple values to objects yet");
 912                        }
 913                    }
 914                    Value::Null | Value::Array(_) => {
 915                        unimplemented!("We only support objects and simple values");
 916                    }
 917                }
 918            }
 919        }
 920    }
 921
 922    apply_changes_to_json_text(&old_object, &new_object, vec![], &mut text);
 923
 924    text
 925}
 926
 927fn to_json_object(settings_file: SettingsFileContent) -> serde_json::Map<String, Value> {
 928    let tmp = serde_json::to_value(settings_file).unwrap();
 929    match tmp {
 930        Value::Object(map) => map,
 931        _ => unreachable!("SettingsFileContent represents a JSON map"),
 932    }
 933}
 934
 935#[cfg(test)]
 936mod tests {
 937    use super::*;
 938    use unindent::Unindent;
 939
 940    fn assert_new_settings<S1: Into<String>, S2: Into<String>>(
 941        old_json: S1,
 942        update: fn(&mut SettingsFileContent),
 943        expected_new_json: S2,
 944    ) {
 945        let old_json = old_json.into();
 946        let old_content: SettingsFileContent = serde_json::from_str(&old_json).unwrap_or_default();
 947        let new_json = update_settings_file(old_json, old_content, update);
 948        assert_eq!(new_json, expected_new_json.into());
 949    }
 950
 951    #[test]
 952    fn test_update_telemetry_setting_multiple_fields() {
 953        assert_new_settings(
 954            r#"
 955                {
 956                    "telemetry": {
 957                        "metrics": false,
 958                        "diagnostics": false
 959                    }
 960                }
 961            "#
 962            .unindent(),
 963            |settings| {
 964                settings.telemetry.set_diagnostics(true);
 965                settings.telemetry.set_metrics(true);
 966            },
 967            r#"
 968                {
 969                    "telemetry": {
 970                        "metrics": true,
 971                        "diagnostics": true
 972                    }
 973                }
 974            "#
 975            .unindent(),
 976        );
 977    }
 978
 979    #[test]
 980    fn test_update_telemetry_setting_weird_formatting() {
 981        assert_new_settings(
 982            r#"{
 983                "telemetry":   { "metrics": false, "diagnostics": true }
 984            }"#
 985            .unindent(),
 986            |settings| settings.telemetry.set_diagnostics(false),
 987            r#"{
 988                "telemetry":   { "metrics": false, "diagnostics": false }
 989            }"#
 990            .unindent(),
 991        );
 992    }
 993
 994    #[test]
 995    fn test_update_telemetry_setting_other_fields() {
 996        assert_new_settings(
 997            r#"
 998                {
 999                    "telemetry": {
1000                        "metrics": false,
1001                        "diagnostics": true
1002                    }
1003                }
1004            "#
1005            .unindent(),
1006            |settings| settings.telemetry.set_diagnostics(false),
1007            r#"
1008                {
1009                    "telemetry": {
1010                        "metrics": false,
1011                        "diagnostics": false
1012                    }
1013                }
1014            "#
1015            .unindent(),
1016        );
1017    }
1018
1019    #[test]
1020    fn test_update_telemetry_setting_empty_telemetry() {
1021        assert_new_settings(
1022            r#"
1023                {
1024                    "telemetry": {}
1025                }
1026            "#
1027            .unindent(),
1028            |settings| settings.telemetry.set_diagnostics(false),
1029            r#"
1030                {
1031                    "telemetry": {
1032                        "diagnostics": false
1033                    }
1034                }
1035            "#
1036            .unindent(),
1037        );
1038    }
1039
1040    #[test]
1041    fn test_update_telemetry_setting_pre_existing() {
1042        assert_new_settings(
1043            r#"
1044                {
1045                    "telemetry": {
1046                        "diagnostics": true
1047                    }
1048                }
1049            "#
1050            .unindent(),
1051            |settings| settings.telemetry.set_diagnostics(false),
1052            r#"
1053                {
1054                    "telemetry": {
1055                        "diagnostics": false
1056                    }
1057                }
1058            "#
1059            .unindent(),
1060        );
1061    }
1062
1063    #[test]
1064    fn test_update_telemetry_setting() {
1065        assert_new_settings(
1066            "{}",
1067            |settings| settings.telemetry.set_diagnostics(true),
1068            r#"
1069                {
1070                    "telemetry": {
1071                        "diagnostics": true
1072                    }
1073                }
1074            "#
1075            .unindent(),
1076        );
1077    }
1078
1079    #[test]
1080    fn test_update_object_empty_doc() {
1081        assert_new_settings(
1082            "",
1083            |settings| settings.telemetry.set_diagnostics(true),
1084            r#"
1085                {
1086                    "telemetry": {
1087                        "diagnostics": true
1088                    }
1089                }
1090            "#
1091            .unindent(),
1092        );
1093    }
1094
1095    #[test]
1096    fn test_write_theme_into_settings_with_theme() {
1097        assert_new_settings(
1098            r#"
1099                {
1100                    "theme": "One Dark"
1101                }
1102            "#
1103            .unindent(),
1104            |settings| settings.theme = Some("summerfruit-light".to_string()),
1105            r#"
1106                {
1107                    "theme": "summerfruit-light"
1108                }
1109            "#
1110            .unindent(),
1111        );
1112    }
1113
1114    #[test]
1115    fn test_write_theme_into_empty_settings() {
1116        assert_new_settings(
1117            r#"
1118                {
1119                }
1120            "#
1121            .unindent(),
1122            |settings| settings.theme = Some("summerfruit-light".to_string()),
1123            r#"
1124                {
1125                    "theme": "summerfruit-light"
1126                }
1127            "#
1128            .unindent(),
1129        );
1130    }
1131
1132    #[test]
1133    fn write_key_no_document() {
1134        assert_new_settings(
1135            "",
1136            |settings| settings.theme = Some("summerfruit-light".to_string()),
1137            r#"
1138                {
1139                    "theme": "summerfruit-light"
1140                }
1141            "#
1142            .unindent(),
1143        );
1144    }
1145
1146    #[test]
1147    fn test_write_theme_into_single_line_settings_without_theme() {
1148        assert_new_settings(
1149            r#"{ "a": "", "ok": true }"#,
1150            |settings| settings.theme = Some("summerfruit-light".to_string()),
1151            r#"{ "theme": "summerfruit-light", "a": "", "ok": true }"#,
1152        );
1153    }
1154
1155    #[test]
1156    fn test_write_theme_pre_object_whitespace() {
1157        assert_new_settings(
1158            r#"          { "a": "", "ok": true }"#,
1159            |settings| settings.theme = Some("summerfruit-light".to_string()),
1160            r#"          { "theme": "summerfruit-light", "a": "", "ok": true }"#.unindent(),
1161        );
1162    }
1163
1164    #[test]
1165    fn test_write_theme_into_multi_line_settings_without_theme() {
1166        assert_new_settings(
1167            r#"
1168                {
1169                    "a": "b"
1170                }
1171            "#
1172            .unindent(),
1173            |settings| settings.theme = Some("summerfruit-light".to_string()),
1174            r#"
1175                {
1176                    "theme": "summerfruit-light",
1177                    "a": "b"
1178                }
1179            "#
1180            .unindent(),
1181        );
1182    }
1183}