settings.rs

   1mod keymap_file;
   2mod settings_file;
   3mod settings_store;
   4
   5use anyhow::bail;
   6use gpui::{
   7    font_cache::{FamilyId, FontCache},
   8    fonts, AppContext, AssetSource,
   9};
  10use schemars::{
  11    gen::SchemaGenerator,
  12    schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec},
  13    JsonSchema,
  14};
  15use serde::{Deserialize, Serialize};
  16use serde_json::Value;
  17use settings_store::Setting;
  18use sqlez::{
  19    bindable::{Bind, Column, StaticColumnCount},
  20    statement::Statement,
  21};
  22use std::{borrow::Cow, collections::HashMap, num::NonZeroU32, path::Path, str, sync::Arc};
  23use theme::{Theme, ThemeRegistry};
  24use util::ResultExt as _;
  25
  26pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
  27pub use settings_file::*;
  28pub use settings_store::{SettingsJsonSchemaParams, SettingsStore};
  29
  30pub const DEFAULT_SETTINGS_ASSET_PATH: &str = "settings/default.json";
  31pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json";
  32
  33#[derive(Clone)]
  34pub struct Settings {
  35    pub features: Features,
  36    pub buffer_font_family_name: String,
  37    pub buffer_font_features: fonts::Features,
  38    pub buffer_font_family: FamilyId,
  39    pub default_buffer_font_size: f32,
  40    pub buffer_font_size: f32,
  41    pub active_pane_magnification: f32,
  42    pub cursor_blink: bool,
  43    pub confirm_quit: bool,
  44    pub hover_popover_enabled: bool,
  45    pub show_completions_on_input: bool,
  46    pub show_call_status_icon: bool,
  47    pub vim_mode: bool,
  48    pub autosave: Autosave,
  49    pub default_dock_anchor: DockAnchor,
  50    pub editor_defaults: EditorSettings,
  51    pub editor_overrides: EditorSettings,
  52    pub git: GitSettings,
  53    pub git_overrides: GitSettings,
  54    pub copilot: CopilotSettings,
  55    pub journal_defaults: JournalSettings,
  56    pub journal_overrides: JournalSettings,
  57    pub terminal_defaults: TerminalSettings,
  58    pub terminal_overrides: TerminalSettings,
  59    pub language_defaults: HashMap<Arc<str>, EditorSettings>,
  60    pub language_overrides: HashMap<Arc<str>, EditorSettings>,
  61    pub lsp: HashMap<Arc<str>, LspSettings>,
  62    pub theme: Arc<Theme>,
  63    pub telemetry_defaults: TelemetrySettings,
  64    pub telemetry_overrides: TelemetrySettings,
  65    pub auto_update: bool,
  66    pub base_keymap: BaseKeymap,
  67}
  68
  69impl Setting for Settings {
  70    type FileContent = SettingsFileContent;
  71
  72    fn load(
  73        defaults: &Self::FileContent,
  74        user_values: &[&Self::FileContent],
  75        cx: &AppContext,
  76    ) -> Self {
  77        let buffer_font_features = defaults.buffer_font_features.clone().unwrap();
  78        let themes = cx.global::<Arc<ThemeRegistry>>();
  79
  80        let mut this = Self {
  81            buffer_font_family: cx
  82                .font_cache()
  83                .load_family(
  84                    &[defaults.buffer_font_family.as_ref().unwrap()],
  85                    &buffer_font_features,
  86                )
  87                .unwrap(),
  88            buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(),
  89            buffer_font_features,
  90            buffer_font_size: defaults.buffer_font_size.unwrap(),
  91            active_pane_magnification: defaults.active_pane_magnification.unwrap(),
  92            default_buffer_font_size: defaults.buffer_font_size.unwrap(),
  93            confirm_quit: defaults.confirm_quit.unwrap(),
  94            cursor_blink: defaults.cursor_blink.unwrap(),
  95            hover_popover_enabled: defaults.hover_popover_enabled.unwrap(),
  96            show_completions_on_input: defaults.show_completions_on_input.unwrap(),
  97            show_call_status_icon: defaults.show_call_status_icon.unwrap(),
  98            vim_mode: defaults.vim_mode.unwrap(),
  99            autosave: defaults.autosave.unwrap(),
 100            default_dock_anchor: defaults.default_dock_anchor.unwrap(),
 101            editor_defaults: EditorSettings {
 102                tab_size: defaults.editor.tab_size,
 103                hard_tabs: defaults.editor.hard_tabs,
 104                soft_wrap: defaults.editor.soft_wrap,
 105                preferred_line_length: defaults.editor.preferred_line_length,
 106                remove_trailing_whitespace_on_save: defaults
 107                    .editor
 108                    .remove_trailing_whitespace_on_save,
 109                ensure_final_newline_on_save: defaults.editor.ensure_final_newline_on_save,
 110                format_on_save: defaults.editor.format_on_save.clone(),
 111                formatter: defaults.editor.formatter.clone(),
 112                enable_language_server: defaults.editor.enable_language_server,
 113                show_copilot_suggestions: defaults.editor.show_copilot_suggestions,
 114                show_whitespaces: defaults.editor.show_whitespaces,
 115            },
 116            editor_overrides: Default::default(),
 117            copilot: CopilotSettings {
 118                disabled_globs: defaults
 119                    .copilot
 120                    .clone()
 121                    .unwrap()
 122                    .disabled_globs
 123                    .unwrap()
 124                    .into_iter()
 125                    .map(|s| glob::Pattern::new(&s).unwrap())
 126                    .collect(),
 127            },
 128            git: defaults.git.unwrap(),
 129            git_overrides: Default::default(),
 130            journal_defaults: defaults.journal.clone(),
 131            journal_overrides: Default::default(),
 132            terminal_defaults: defaults.terminal.clone(),
 133            terminal_overrides: Default::default(),
 134            language_defaults: defaults.languages.clone(),
 135            language_overrides: Default::default(),
 136            lsp: defaults.lsp.clone(),
 137            theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(),
 138            telemetry_defaults: defaults.telemetry,
 139            telemetry_overrides: Default::default(),
 140            auto_update: defaults.auto_update.unwrap(),
 141            base_keymap: Default::default(),
 142            features: Features {
 143                copilot: defaults.features.copilot.unwrap(),
 144            },
 145        };
 146
 147        for value in user_values.into_iter().copied().cloned() {
 148            this.set_user_settings(value, themes.as_ref(), cx.font_cache());
 149        }
 150
 151        this
 152    }
 153
 154    fn json_schema(
 155        generator: &mut SchemaGenerator,
 156        params: &SettingsJsonSchemaParams,
 157    ) -> schemars::schema::RootSchema {
 158        let mut root_schema = generator.root_schema_for::<SettingsFileContent>();
 159
 160        // Create a schema for a theme name.
 161        let theme_name_schema = SchemaObject {
 162            instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
 163            enum_values: Some(
 164                params
 165                    .theme_names
 166                    .iter()
 167                    .cloned()
 168                    .map(Value::String)
 169                    .collect(),
 170            ),
 171            ..Default::default()
 172        };
 173
 174        // Create a schema for a 'languages overrides' object, associating editor
 175        // settings with specific langauges.
 176        assert!(root_schema.definitions.contains_key("EditorSettings"));
 177
 178        let languages_object_schema = SchemaObject {
 179            instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
 180            object: Some(Box::new(ObjectValidation {
 181                properties: params
 182                    .language_names
 183                    .iter()
 184                    .map(|name| {
 185                        (
 186                            name.clone(),
 187                            Schema::new_ref("#/definitions/EditorSettings".into()),
 188                        )
 189                    })
 190                    .collect(),
 191                ..Default::default()
 192            })),
 193            ..Default::default()
 194        };
 195
 196        // Add these new schemas as definitions, and modify properties of the root
 197        // schema to reference them.
 198        root_schema.definitions.extend([
 199            ("ThemeName".into(), theme_name_schema.into()),
 200            ("Languages".into(), languages_object_schema.into()),
 201        ]);
 202        let root_schema_object = &mut root_schema.schema.object.as_mut().unwrap();
 203
 204        root_schema_object.properties.extend([
 205            (
 206                "theme".to_owned(),
 207                Schema::new_ref("#/definitions/ThemeName".into()),
 208            ),
 209            (
 210                "languages".to_owned(),
 211                Schema::new_ref("#/definitions/Languages".into()),
 212            ),
 213            // For backward compatibility
 214            (
 215                "language_overrides".to_owned(),
 216                Schema::new_ref("#/definitions/Languages".into()),
 217            ),
 218        ]);
 219
 220        root_schema
 221    }
 222}
 223
 224#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
 225pub enum BaseKeymap {
 226    #[default]
 227    VSCode,
 228    JetBrains,
 229    SublimeText,
 230    Atom,
 231    TextMate,
 232}
 233
 234impl BaseKeymap {
 235    pub const OPTIONS: [(&'static str, Self); 5] = [
 236        ("VSCode (Default)", Self::VSCode),
 237        ("Atom", Self::Atom),
 238        ("JetBrains", Self::JetBrains),
 239        ("Sublime Text", Self::SublimeText),
 240        ("TextMate", Self::TextMate),
 241    ];
 242
 243    pub fn asset_path(&self) -> Option<&'static str> {
 244        match self {
 245            BaseKeymap::JetBrains => Some("keymaps/jetbrains.json"),
 246            BaseKeymap::SublimeText => Some("keymaps/sublime_text.json"),
 247            BaseKeymap::Atom => Some("keymaps/atom.json"),
 248            BaseKeymap::TextMate => Some("keymaps/textmate.json"),
 249            BaseKeymap::VSCode => None,
 250        }
 251    }
 252
 253    pub fn names() -> impl Iterator<Item = &'static str> {
 254        Self::OPTIONS.iter().map(|(name, _)| *name)
 255    }
 256
 257    pub fn from_names(option: &str) -> BaseKeymap {
 258        Self::OPTIONS
 259            .iter()
 260            .copied()
 261            .find_map(|(name, value)| (name == option).then(|| value))
 262            .unwrap_or_default()
 263    }
 264}
 265
 266#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 267pub struct TelemetrySettings {
 268    diagnostics: Option<bool>,
 269    metrics: Option<bool>,
 270}
 271
 272impl TelemetrySettings {
 273    pub fn metrics(&self) -> bool {
 274        self.metrics.unwrap()
 275    }
 276
 277    pub fn diagnostics(&self) -> bool {
 278        self.diagnostics.unwrap()
 279    }
 280
 281    pub fn set_metrics(&mut self, value: bool) {
 282        self.metrics = Some(value);
 283    }
 284
 285    pub fn set_diagnostics(&mut self, value: bool) {
 286        self.diagnostics = Some(value);
 287    }
 288}
 289
 290#[derive(Clone, Debug, Default)]
 291pub struct CopilotSettings {
 292    pub disabled_globs: Vec<glob::Pattern>,
 293}
 294
 295#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 296pub struct CopilotSettingsContent {
 297    #[serde(default)]
 298    pub disabled_globs: Option<Vec<String>>,
 299}
 300
 301#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 302pub struct GitSettings {
 303    pub git_gutter: Option<GitGutter>,
 304    pub gutter_debounce: Option<u64>,
 305}
 306
 307#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
 308#[serde(rename_all = "snake_case")]
 309pub enum GitGutter {
 310    #[default]
 311    TrackedFiles,
 312    Hide,
 313}
 314
 315pub struct GitGutterConfig {}
 316
 317#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 318pub struct EditorSettings {
 319    pub tab_size: Option<NonZeroU32>,
 320    pub hard_tabs: Option<bool>,
 321    pub soft_wrap: Option<SoftWrap>,
 322    pub preferred_line_length: Option<u32>,
 323    pub format_on_save: Option<FormatOnSave>,
 324    pub remove_trailing_whitespace_on_save: Option<bool>,
 325    pub ensure_final_newline_on_save: Option<bool>,
 326    pub formatter: Option<Formatter>,
 327    pub enable_language_server: Option<bool>,
 328    pub show_copilot_suggestions: Option<bool>,
 329    pub show_whitespaces: Option<ShowWhitespaces>,
 330}
 331
 332#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 333#[serde(rename_all = "snake_case")]
 334pub enum SoftWrap {
 335    None,
 336    EditorWidth,
 337    PreferredLineLength,
 338}
 339#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 340#[serde(rename_all = "snake_case")]
 341pub enum FormatOnSave {
 342    On,
 343    Off,
 344    LanguageServer,
 345    External {
 346        command: String,
 347        arguments: Vec<String>,
 348    },
 349}
 350
 351#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 352#[serde(rename_all = "snake_case")]
 353pub enum Formatter {
 354    LanguageServer,
 355    External {
 356        command: String,
 357        arguments: Vec<String>,
 358    },
 359}
 360
 361#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 362#[serde(rename_all = "snake_case")]
 363pub enum Autosave {
 364    Off,
 365    AfterDelay { milliseconds: u64 },
 366    OnFocusChange,
 367    OnWindowChange,
 368}
 369
 370#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
 371pub struct JournalSettings {
 372    pub path: Option<String>,
 373    pub hour_format: Option<HourFormat>,
 374}
 375
 376impl Default for JournalSettings {
 377    fn default() -> Self {
 378        Self {
 379            path: Some("~".into()),
 380            hour_format: Some(Default::default()),
 381        }
 382    }
 383}
 384
 385#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
 386#[serde(rename_all = "snake_case")]
 387pub enum HourFormat {
 388    Hour12,
 389    Hour24,
 390}
 391
 392impl Default for HourFormat {
 393    fn default() -> Self {
 394        Self::Hour12
 395    }
 396}
 397
 398#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 399pub struct TerminalSettings {
 400    pub shell: Option<Shell>,
 401    pub working_directory: Option<WorkingDirectory>,
 402    pub font_size: Option<f32>,
 403    pub font_family: Option<String>,
 404    pub line_height: Option<TerminalLineHeight>,
 405    pub font_features: Option<fonts::Features>,
 406    pub env: Option<HashMap<String, String>>,
 407    pub blinking: Option<TerminalBlink>,
 408    pub alternate_scroll: Option<AlternateScroll>,
 409    pub option_as_meta: Option<bool>,
 410    pub copy_on_select: Option<bool>,
 411}
 412
 413#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
 414#[serde(rename_all = "snake_case")]
 415pub enum TerminalLineHeight {
 416    #[default]
 417    Comfortable,
 418    Standard,
 419    Custom(f32),
 420}
 421
 422impl TerminalLineHeight {
 423    fn value(&self) -> f32 {
 424        match self {
 425            TerminalLineHeight::Comfortable => 1.618,
 426            TerminalLineHeight::Standard => 1.3,
 427            TerminalLineHeight::Custom(line_height) => *line_height,
 428        }
 429    }
 430}
 431
 432#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 433#[serde(rename_all = "snake_case")]
 434pub enum TerminalBlink {
 435    Off,
 436    TerminalControlled,
 437    On,
 438}
 439
 440impl Default for TerminalBlink {
 441    fn default() -> Self {
 442        TerminalBlink::TerminalControlled
 443    }
 444}
 445
 446#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 447#[serde(rename_all = "snake_case")]
 448pub enum Shell {
 449    System,
 450    Program(String),
 451    WithArguments { program: String, args: Vec<String> },
 452}
 453
 454impl Default for Shell {
 455    fn default() -> Self {
 456        Shell::System
 457    }
 458}
 459
 460#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 461#[serde(rename_all = "snake_case")]
 462pub enum AlternateScroll {
 463    On,
 464    Off,
 465}
 466
 467impl Default for AlternateScroll {
 468    fn default() -> Self {
 469        AlternateScroll::On
 470    }
 471}
 472
 473#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 474#[serde(rename_all = "snake_case")]
 475pub enum WorkingDirectory {
 476    CurrentProjectDirectory,
 477    FirstProjectDirectory,
 478    AlwaysHome,
 479    Always { directory: String },
 480}
 481
 482impl Default for WorkingDirectory {
 483    fn default() -> Self {
 484        Self::CurrentProjectDirectory
 485    }
 486}
 487
 488impl TerminalSettings {
 489    fn line_height(&self) -> Option<f32> {
 490        self.line_height
 491            .to_owned()
 492            .map(|line_height| line_height.value())
 493    }
 494}
 495
 496#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)]
 497#[serde(rename_all = "snake_case")]
 498pub enum DockAnchor {
 499    #[default]
 500    Bottom,
 501    Right,
 502    Expanded,
 503}
 504
 505impl StaticColumnCount for DockAnchor {}
 506impl Bind for DockAnchor {
 507    fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
 508        match self {
 509            DockAnchor::Bottom => "Bottom",
 510            DockAnchor::Right => "Right",
 511            DockAnchor::Expanded => "Expanded",
 512        }
 513        .bind(statement, start_index)
 514    }
 515}
 516
 517impl Column for DockAnchor {
 518    fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
 519        String::column(statement, start_index).and_then(|(anchor_text, next_index)| {
 520            Ok((
 521                match anchor_text.as_ref() {
 522                    "Bottom" => DockAnchor::Bottom,
 523                    "Right" => DockAnchor::Right,
 524                    "Expanded" => DockAnchor::Expanded,
 525                    _ => bail!("Stored dock anchor is incorrect"),
 526                },
 527                next_index,
 528            ))
 529        })
 530    }
 531}
 532
 533#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 534pub struct SettingsFileContent {
 535    #[serde(default)]
 536    pub buffer_font_family: Option<String>,
 537    #[serde(default)]
 538    pub buffer_font_size: Option<f32>,
 539    #[serde(default)]
 540    pub buffer_font_features: Option<fonts::Features>,
 541    #[serde(default)]
 542    pub copilot: Option<CopilotSettingsContent>,
 543    #[serde(default)]
 544    pub active_pane_magnification: Option<f32>,
 545    #[serde(default)]
 546    pub cursor_blink: Option<bool>,
 547    #[serde(default)]
 548    pub confirm_quit: Option<bool>,
 549    #[serde(default)]
 550    pub hover_popover_enabled: Option<bool>,
 551    #[serde(default)]
 552    pub show_completions_on_input: Option<bool>,
 553    #[serde(default)]
 554    pub show_call_status_icon: Option<bool>,
 555    #[serde(default)]
 556    pub vim_mode: Option<bool>,
 557    #[serde(default)]
 558    pub autosave: Option<Autosave>,
 559    #[serde(default)]
 560    pub default_dock_anchor: Option<DockAnchor>,
 561    #[serde(flatten)]
 562    pub editor: EditorSettings,
 563    #[serde(default)]
 564    pub journal: JournalSettings,
 565    #[serde(default)]
 566    pub terminal: TerminalSettings,
 567    #[serde(default)]
 568    pub git: Option<GitSettings>,
 569    #[serde(default)]
 570    #[serde(alias = "language_overrides")]
 571    pub languages: HashMap<Arc<str>, EditorSettings>,
 572    #[serde(default)]
 573    pub lsp: HashMap<Arc<str>, LspSettings>,
 574    #[serde(default)]
 575    pub theme: Option<String>,
 576    #[serde(default)]
 577    pub telemetry: TelemetrySettings,
 578    #[serde(default)]
 579    pub auto_update: Option<bool>,
 580    #[serde(default)]
 581    pub base_keymap: Option<BaseKeymap>,
 582    #[serde(default)]
 583    pub features: FeaturesContent,
 584}
 585
 586#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 587#[serde(rename_all = "snake_case")]
 588pub struct LspSettings {
 589    pub initialization_options: Option<Value>,
 590}
 591
 592#[derive(Clone, Debug, PartialEq, Eq)]
 593pub struct Features {
 594    pub copilot: bool,
 595}
 596
 597#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 598#[serde(rename_all = "snake_case")]
 599pub struct FeaturesContent {
 600    pub copilot: Option<bool>,
 601}
 602
 603#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 604#[serde(rename_all = "snake_case")]
 605pub enum ShowWhitespaces {
 606    #[default]
 607    Selection,
 608    None,
 609    All,
 610}
 611
 612impl Settings {
 613    pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> {
 614        match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() {
 615            Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
 616            Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
 617        }
 618    }
 619
 620    /// Fill out the settings corresponding to the default.json file, overrides will be set later
 621    pub fn defaults(
 622        assets: impl AssetSource,
 623        font_cache: &FontCache,
 624        themes: &ThemeRegistry,
 625    ) -> Self {
 626        #[track_caller]
 627        fn required<T>(value: Option<T>) -> Option<T> {
 628            assert!(value.is_some(), "missing default setting value");
 629            value
 630        }
 631
 632        let defaults: SettingsFileContent = settings_store::parse_json_with_comments(
 633            str::from_utf8(assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap().as_ref()).unwrap(),
 634        )
 635        .unwrap();
 636
 637        let buffer_font_features = defaults.buffer_font_features.unwrap();
 638        Self {
 639            buffer_font_family: font_cache
 640                .load_family(
 641                    &[defaults.buffer_font_family.as_ref().unwrap()],
 642                    &buffer_font_features,
 643                )
 644                .unwrap(),
 645            buffer_font_family_name: defaults.buffer_font_family.unwrap(),
 646            buffer_font_features,
 647            buffer_font_size: defaults.buffer_font_size.unwrap(),
 648            active_pane_magnification: defaults.active_pane_magnification.unwrap(),
 649            default_buffer_font_size: defaults.buffer_font_size.unwrap(),
 650            confirm_quit: defaults.confirm_quit.unwrap(),
 651            cursor_blink: defaults.cursor_blink.unwrap(),
 652            hover_popover_enabled: defaults.hover_popover_enabled.unwrap(),
 653            show_completions_on_input: defaults.show_completions_on_input.unwrap(),
 654            show_call_status_icon: defaults.show_call_status_icon.unwrap(),
 655            vim_mode: defaults.vim_mode.unwrap(),
 656            autosave: defaults.autosave.unwrap(),
 657            default_dock_anchor: defaults.default_dock_anchor.unwrap(),
 658            editor_defaults: EditorSettings {
 659                tab_size: required(defaults.editor.tab_size),
 660                hard_tabs: required(defaults.editor.hard_tabs),
 661                soft_wrap: required(defaults.editor.soft_wrap),
 662                preferred_line_length: required(defaults.editor.preferred_line_length),
 663                remove_trailing_whitespace_on_save: required(
 664                    defaults.editor.remove_trailing_whitespace_on_save,
 665                ),
 666                ensure_final_newline_on_save: required(
 667                    defaults.editor.ensure_final_newline_on_save,
 668                ),
 669                format_on_save: required(defaults.editor.format_on_save),
 670                formatter: required(defaults.editor.formatter),
 671                enable_language_server: required(defaults.editor.enable_language_server),
 672                show_copilot_suggestions: required(defaults.editor.show_copilot_suggestions),
 673                show_whitespaces: required(defaults.editor.show_whitespaces),
 674            },
 675            editor_overrides: Default::default(),
 676            copilot: CopilotSettings {
 677                disabled_globs: defaults
 678                    .copilot
 679                    .unwrap()
 680                    .disabled_globs
 681                    .unwrap()
 682                    .into_iter()
 683                    .map(|s| glob::Pattern::new(&s).unwrap())
 684                    .collect(),
 685            },
 686            git: defaults.git.unwrap(),
 687            git_overrides: Default::default(),
 688            journal_defaults: defaults.journal,
 689            journal_overrides: Default::default(),
 690            terminal_defaults: defaults.terminal,
 691            terminal_overrides: Default::default(),
 692            language_defaults: defaults.languages,
 693            language_overrides: Default::default(),
 694            lsp: defaults.lsp.clone(),
 695            theme: themes.get(&defaults.theme.unwrap()).unwrap(),
 696            telemetry_defaults: defaults.telemetry,
 697            telemetry_overrides: Default::default(),
 698            auto_update: defaults.auto_update.unwrap(),
 699            base_keymap: Default::default(),
 700            features: Features {
 701                copilot: defaults.features.copilot.unwrap(),
 702            },
 703        }
 704    }
 705
 706    // Fill out the overrride and etc. settings from the user's settings.json
 707    pub fn set_user_settings(
 708        &mut self,
 709        data: SettingsFileContent,
 710        theme_registry: &ThemeRegistry,
 711        font_cache: &FontCache,
 712    ) {
 713        let mut family_changed = false;
 714        if let Some(value) = data.buffer_font_family {
 715            self.buffer_font_family_name = value;
 716            family_changed = true;
 717        }
 718        if let Some(value) = data.buffer_font_features {
 719            self.buffer_font_features = value;
 720            family_changed = true;
 721        }
 722        if family_changed {
 723            if let Some(id) = font_cache
 724                .load_family(&[&self.buffer_font_family_name], &self.buffer_font_features)
 725                .log_err()
 726            {
 727                self.buffer_font_family = id;
 728            }
 729        }
 730
 731        if let Some(value) = &data.theme {
 732            if let Some(theme) = theme_registry.get(value).log_err() {
 733                self.theme = theme;
 734            }
 735        }
 736
 737        merge(&mut self.buffer_font_size, data.buffer_font_size);
 738        merge(
 739            &mut self.active_pane_magnification,
 740            data.active_pane_magnification,
 741        );
 742        merge(&mut self.default_buffer_font_size, data.buffer_font_size);
 743        merge(&mut self.cursor_blink, data.cursor_blink);
 744        merge(&mut self.confirm_quit, data.confirm_quit);
 745        merge(&mut self.hover_popover_enabled, data.hover_popover_enabled);
 746        merge(
 747            &mut self.show_completions_on_input,
 748            data.show_completions_on_input,
 749        );
 750        merge(&mut self.vim_mode, data.vim_mode);
 751        merge(&mut self.autosave, data.autosave);
 752        merge(&mut self.default_dock_anchor, data.default_dock_anchor);
 753        merge(&mut self.base_keymap, data.base_keymap);
 754        merge(&mut self.features.copilot, data.features.copilot);
 755
 756        if let Some(copilot) = data.copilot {
 757            if let Some(disabled_globs) = copilot.disabled_globs {
 758                self.copilot.disabled_globs = disabled_globs
 759                    .into_iter()
 760                    .filter_map(|s| glob::Pattern::new(&s).ok())
 761                    .collect()
 762            }
 763        }
 764        self.editor_overrides = data.editor;
 765        self.git_overrides = data.git.unwrap_or_default();
 766        self.journal_overrides = data.journal;
 767        self.terminal_defaults.font_size = data.terminal.font_size;
 768        self.terminal_overrides.copy_on_select = data.terminal.copy_on_select;
 769        self.terminal_overrides = data.terminal;
 770        self.language_overrides = data.languages;
 771        self.telemetry_overrides = data.telemetry;
 772        self.lsp = data.lsp;
 773        merge(&mut self.auto_update, data.auto_update);
 774    }
 775
 776    pub fn with_language_defaults(
 777        mut self,
 778        language_name: impl Into<Arc<str>>,
 779        overrides: EditorSettings,
 780    ) -> Self {
 781        self.language_defaults
 782            .insert(language_name.into(), overrides);
 783        self
 784    }
 785
 786    pub fn features(&self) -> &Features {
 787        &self.features
 788    }
 789
 790    pub fn show_copilot_suggestions(&self, language: Option<&str>, path: Option<&Path>) -> bool {
 791        if !self.features.copilot {
 792            return false;
 793        }
 794
 795        if !self.copilot_enabled_for_language(language) {
 796            return false;
 797        }
 798
 799        if let Some(path) = path {
 800            if !self.copilot_enabled_for_path(path) {
 801                return false;
 802            }
 803        }
 804
 805        true
 806    }
 807
 808    pub fn copilot_enabled_for_path(&self, path: &Path) -> bool {
 809        !self
 810            .copilot
 811            .disabled_globs
 812            .iter()
 813            .any(|glob| glob.matches_path(path))
 814    }
 815
 816    pub fn copilot_enabled_for_language(&self, language: Option<&str>) -> bool {
 817        self.language_setting(language, |settings| settings.show_copilot_suggestions)
 818    }
 819
 820    pub fn tab_size(&self, language: Option<&str>) -> NonZeroU32 {
 821        self.language_setting(language, |settings| settings.tab_size)
 822    }
 823
 824    pub fn show_whitespaces(&self, language: Option<&str>) -> ShowWhitespaces {
 825        self.language_setting(language, |settings| settings.show_whitespaces)
 826    }
 827
 828    pub fn hard_tabs(&self, language: Option<&str>) -> bool {
 829        self.language_setting(language, |settings| settings.hard_tabs)
 830    }
 831
 832    pub fn soft_wrap(&self, language: Option<&str>) -> SoftWrap {
 833        self.language_setting(language, |settings| settings.soft_wrap)
 834    }
 835
 836    pub fn preferred_line_length(&self, language: Option<&str>) -> u32 {
 837        self.language_setting(language, |settings| settings.preferred_line_length)
 838    }
 839
 840    pub fn remove_trailing_whitespace_on_save(&self, language: Option<&str>) -> bool {
 841        self.language_setting(language, |settings| {
 842            settings.remove_trailing_whitespace_on_save.clone()
 843        })
 844    }
 845
 846    pub fn ensure_final_newline_on_save(&self, language: Option<&str>) -> bool {
 847        self.language_setting(language, |settings| {
 848            settings.ensure_final_newline_on_save.clone()
 849        })
 850    }
 851
 852    pub fn format_on_save(&self, language: Option<&str>) -> FormatOnSave {
 853        self.language_setting(language, |settings| settings.format_on_save.clone())
 854    }
 855
 856    pub fn formatter(&self, language: Option<&str>) -> Formatter {
 857        self.language_setting(language, |settings| settings.formatter.clone())
 858    }
 859
 860    pub fn enable_language_server(&self, language: Option<&str>) -> bool {
 861        self.language_setting(language, |settings| settings.enable_language_server)
 862    }
 863
 864    fn language_setting<F, R>(&self, language: Option<&str>, f: F) -> R
 865    where
 866        F: Fn(&EditorSettings) -> Option<R>,
 867    {
 868        None.or_else(|| language.and_then(|l| self.language_overrides.get(l).and_then(&f)))
 869            .or_else(|| f(&self.editor_overrides))
 870            .or_else(|| language.and_then(|l| self.language_defaults.get(l).and_then(&f)))
 871            .or_else(|| f(&self.editor_defaults))
 872            .expect("missing default")
 873    }
 874
 875    pub fn git_gutter(&self) -> GitGutter {
 876        self.git_overrides.git_gutter.unwrap_or_else(|| {
 877            self.git
 878                .git_gutter
 879                .expect("git_gutter should be some by setting setup")
 880        })
 881    }
 882
 883    pub fn telemetry(&self) -> TelemetrySettings {
 884        TelemetrySettings {
 885            diagnostics: Some(self.telemetry_diagnostics()),
 886            metrics: Some(self.telemetry_metrics()),
 887        }
 888    }
 889
 890    pub fn telemetry_diagnostics(&self) -> bool {
 891        self.telemetry_overrides
 892            .diagnostics
 893            .or(self.telemetry_defaults.diagnostics)
 894            .expect("missing default")
 895    }
 896
 897    pub fn telemetry_metrics(&self) -> bool {
 898        self.telemetry_overrides
 899            .metrics
 900            .or(self.telemetry_defaults.metrics)
 901            .expect("missing default")
 902    }
 903
 904    fn terminal_setting<F, R>(&self, f: F) -> R
 905    where
 906        F: Fn(&TerminalSettings) -> Option<R>,
 907    {
 908        None.or_else(|| f(&self.terminal_overrides))
 909            .or_else(|| f(&self.terminal_defaults))
 910            .expect("missing default")
 911    }
 912
 913    pub fn terminal_line_height(&self) -> f32 {
 914        self.terminal_setting(|terminal_setting| terminal_setting.line_height())
 915    }
 916
 917    pub fn terminal_scroll(&self) -> AlternateScroll {
 918        self.terminal_setting(|terminal_setting| terminal_setting.alternate_scroll.to_owned())
 919    }
 920
 921    pub fn terminal_shell(&self) -> Shell {
 922        self.terminal_setting(|terminal_setting| terminal_setting.shell.to_owned())
 923    }
 924
 925    pub fn terminal_env(&self) -> HashMap<String, String> {
 926        self.terminal_setting(|terminal_setting| terminal_setting.env.to_owned())
 927    }
 928
 929    pub fn terminal_strategy(&self) -> WorkingDirectory {
 930        self.terminal_setting(|terminal_setting| terminal_setting.working_directory.to_owned())
 931    }
 932
 933    #[cfg(any(test, feature = "test-support"))]
 934    pub fn test(cx: &gpui::AppContext) -> Settings {
 935        Settings {
 936            buffer_font_family_name: "Monaco".to_string(),
 937            buffer_font_features: Default::default(),
 938            buffer_font_family: cx
 939                .font_cache()
 940                .load_family(&["Monaco"], &Default::default())
 941                .unwrap(),
 942            buffer_font_size: 14.,
 943            active_pane_magnification: 1.,
 944            default_buffer_font_size: 14.,
 945            confirm_quit: false,
 946            cursor_blink: true,
 947            hover_popover_enabled: true,
 948            show_completions_on_input: true,
 949            show_call_status_icon: true,
 950            vim_mode: false,
 951            autosave: Autosave::Off,
 952            default_dock_anchor: DockAnchor::Bottom,
 953            editor_defaults: EditorSettings {
 954                tab_size: Some(4.try_into().unwrap()),
 955                hard_tabs: Some(false),
 956                soft_wrap: Some(SoftWrap::None),
 957                preferred_line_length: Some(80),
 958                remove_trailing_whitespace_on_save: Some(true),
 959                ensure_final_newline_on_save: Some(true),
 960                format_on_save: Some(FormatOnSave::On),
 961                formatter: Some(Formatter::LanguageServer),
 962                enable_language_server: Some(true),
 963                show_copilot_suggestions: Some(true),
 964                show_whitespaces: Some(ShowWhitespaces::None),
 965            },
 966            editor_overrides: Default::default(),
 967            copilot: Default::default(),
 968            journal_defaults: Default::default(),
 969            journal_overrides: Default::default(),
 970            terminal_defaults: Default::default(),
 971            terminal_overrides: Default::default(),
 972            git: Default::default(),
 973            git_overrides: Default::default(),
 974            language_defaults: Default::default(),
 975            language_overrides: Default::default(),
 976            lsp: Default::default(),
 977            theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default),
 978            telemetry_defaults: TelemetrySettings {
 979                diagnostics: Some(true),
 980                metrics: Some(true),
 981            },
 982            telemetry_overrides: Default::default(),
 983            auto_update: true,
 984            base_keymap: Default::default(),
 985            features: Features { copilot: true },
 986        }
 987    }
 988
 989    #[cfg(any(test, feature = "test-support"))]
 990    pub fn test_async(cx: &mut gpui::TestAppContext) {
 991        cx.update(|cx| {
 992            let settings = Self::test(cx);
 993            cx.set_global(settings);
 994        });
 995    }
 996}
 997
 998fn merge<T: Copy>(target: &mut T, value: Option<T>) {
 999    if let Some(value) = value {
1000        *target = value;
1001    }
1002}