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