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