settings.rs

  1mod font_size;
  2mod keymap_file;
  3mod settings_file;
  4mod settings_store;
  5
  6use anyhow::bail;
  7use gpui::{
  8    font_cache::{FamilyId, FontCache},
  9    fonts, AppContext, AssetSource,
 10};
 11use schemars::{
 12    gen::SchemaGenerator,
 13    schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec},
 14    JsonSchema,
 15};
 16use serde::{Deserialize, Serialize};
 17use serde_json::Value;
 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 font_size::{adjust_font_size_delta, font_size_for_setting};
 27pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
 28pub use settings_file::*;
 29pub use settings_store::{Setting, SettingsJsonSchemaParams, SettingsStore};
 30
 31pub const DEFAULT_SETTINGS_ASSET_PATH: &str = "settings/default.json";
 32pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json";
 33
 34#[derive(Clone)]
 35pub struct Settings {
 36    pub features: Features,
 37    pub buffer_font_family_name: String,
 38    pub buffer_font_features: fonts::Features,
 39    pub buffer_font_family: FamilyId,
 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 autosave: Autosave,
 48    pub default_dock_anchor: DockAnchor,
 49    pub editor_defaults: EditorSettings,
 50    pub editor_overrides: EditorSettings,
 51    pub git: GitSettings,
 52    pub git_overrides: GitSettings,
 53    pub copilot: CopilotSettings,
 54    pub language_defaults: HashMap<Arc<str>, EditorSettings>,
 55    pub language_overrides: HashMap<Arc<str>, EditorSettings>,
 56    pub lsp: HashMap<Arc<str>, LspSettings>,
 57    pub theme: Arc<Theme>,
 58    pub base_keymap: BaseKeymap,
 59}
 60
 61impl Setting for Settings {
 62    const KEY: Option<&'static str> = None;
 63
 64    type FileContent = SettingsFileContent;
 65
 66    fn load(
 67        defaults: &Self::FileContent,
 68        user_values: &[&Self::FileContent],
 69        cx: &AppContext,
 70    ) -> Self {
 71        let buffer_font_features = defaults.buffer_font_features.clone().unwrap();
 72        let themes = cx.global::<Arc<ThemeRegistry>>();
 73
 74        let mut this = Self {
 75            buffer_font_family: cx
 76                .font_cache()
 77                .load_family(
 78                    &[defaults.buffer_font_family.as_ref().unwrap()],
 79                    &buffer_font_features,
 80                )
 81                .unwrap(),
 82            buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(),
 83            buffer_font_features,
 84            buffer_font_size: defaults.buffer_font_size.unwrap(),
 85            active_pane_magnification: defaults.active_pane_magnification.unwrap(),
 86            confirm_quit: defaults.confirm_quit.unwrap(),
 87            cursor_blink: defaults.cursor_blink.unwrap(),
 88            hover_popover_enabled: defaults.hover_popover_enabled.unwrap(),
 89            show_completions_on_input: defaults.show_completions_on_input.unwrap(),
 90            show_call_status_icon: defaults.show_call_status_icon.unwrap(),
 91            autosave: defaults.autosave.unwrap(),
 92            default_dock_anchor: defaults.default_dock_anchor.unwrap(),
 93            editor_defaults: EditorSettings {
 94                tab_size: defaults.editor.tab_size,
 95                hard_tabs: defaults.editor.hard_tabs,
 96                soft_wrap: defaults.editor.soft_wrap,
 97                preferred_line_length: defaults.editor.preferred_line_length,
 98                remove_trailing_whitespace_on_save: defaults
 99                    .editor
100                    .remove_trailing_whitespace_on_save,
101                ensure_final_newline_on_save: defaults.editor.ensure_final_newline_on_save,
102                format_on_save: defaults.editor.format_on_save.clone(),
103                formatter: defaults.editor.formatter.clone(),
104                enable_language_server: defaults.editor.enable_language_server,
105                show_copilot_suggestions: defaults.editor.show_copilot_suggestions,
106                show_whitespaces: defaults.editor.show_whitespaces,
107            },
108            editor_overrides: Default::default(),
109            copilot: CopilotSettings {
110                disabled_globs: defaults
111                    .copilot
112                    .clone()
113                    .unwrap()
114                    .disabled_globs
115                    .unwrap()
116                    .into_iter()
117                    .map(|s| glob::Pattern::new(&s).unwrap())
118                    .collect(),
119            },
120            git: defaults.git.unwrap(),
121            git_overrides: Default::default(),
122            language_defaults: defaults.languages.clone(),
123            language_overrides: Default::default(),
124            lsp: defaults.lsp.clone(),
125            theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(),
126            base_keymap: Default::default(),
127            features: Features {
128                copilot: defaults.features.copilot.unwrap(),
129            },
130        };
131
132        for value in user_values.into_iter().copied().cloned() {
133            this.set_user_settings(value, themes.as_ref(), cx.font_cache());
134        }
135
136        this
137    }
138
139    fn json_schema(
140        generator: &mut SchemaGenerator,
141        params: &SettingsJsonSchemaParams,
142    ) -> schemars::schema::RootSchema {
143        let mut root_schema = generator.root_schema_for::<SettingsFileContent>();
144
145        // Create a schema for a theme name.
146        let theme_name_schema = SchemaObject {
147            instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
148            enum_values: Some(
149                params
150                    .theme_names
151                    .iter()
152                    .cloned()
153                    .map(Value::String)
154                    .collect(),
155            ),
156            ..Default::default()
157        };
158
159        // Create a schema for a 'languages overrides' object, associating editor
160        // settings with specific langauges.
161        assert!(root_schema.definitions.contains_key("EditorSettings"));
162
163        let languages_object_schema = SchemaObject {
164            instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
165            object: Some(Box::new(ObjectValidation {
166                properties: params
167                    .language_names
168                    .iter()
169                    .map(|name| {
170                        (
171                            name.clone(),
172                            Schema::new_ref("#/definitions/EditorSettings".into()),
173                        )
174                    })
175                    .collect(),
176                ..Default::default()
177            })),
178            ..Default::default()
179        };
180
181        // Add these new schemas as definitions, and modify properties of the root
182        // schema to reference them.
183        root_schema.definitions.extend([
184            ("ThemeName".into(), theme_name_schema.into()),
185            ("Languages".into(), languages_object_schema.into()),
186        ]);
187        let root_schema_object = &mut root_schema.schema.object.as_mut().unwrap();
188
189        root_schema_object.properties.extend([
190            (
191                "theme".to_owned(),
192                Schema::new_ref("#/definitions/ThemeName".into()),
193            ),
194            (
195                "languages".to_owned(),
196                Schema::new_ref("#/definitions/Languages".into()),
197            ),
198            // For backward compatibility
199            (
200                "language_overrides".to_owned(),
201                Schema::new_ref("#/definitions/Languages".into()),
202            ),
203        ]);
204
205        root_schema
206    }
207}
208
209#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
210pub enum BaseKeymap {
211    #[default]
212    VSCode,
213    JetBrains,
214    SublimeText,
215    Atom,
216    TextMate,
217}
218
219impl BaseKeymap {
220    pub const OPTIONS: [(&'static str, Self); 5] = [
221        ("VSCode (Default)", Self::VSCode),
222        ("Atom", Self::Atom),
223        ("JetBrains", Self::JetBrains),
224        ("Sublime Text", Self::SublimeText),
225        ("TextMate", Self::TextMate),
226    ];
227
228    pub fn asset_path(&self) -> Option<&'static str> {
229        match self {
230            BaseKeymap::JetBrains => Some("keymaps/jetbrains.json"),
231            BaseKeymap::SublimeText => Some("keymaps/sublime_text.json"),
232            BaseKeymap::Atom => Some("keymaps/atom.json"),
233            BaseKeymap::TextMate => Some("keymaps/textmate.json"),
234            BaseKeymap::VSCode => None,
235        }
236    }
237
238    pub fn names() -> impl Iterator<Item = &'static str> {
239        Self::OPTIONS.iter().map(|(name, _)| *name)
240    }
241
242    pub fn from_names(option: &str) -> BaseKeymap {
243        Self::OPTIONS
244            .iter()
245            .copied()
246            .find_map(|(name, value)| (name == option).then(|| value))
247            .unwrap_or_default()
248    }
249}
250
251#[derive(Clone, Debug, Default)]
252pub struct CopilotSettings {
253    pub disabled_globs: Vec<glob::Pattern>,
254}
255
256#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
257pub struct CopilotSettingsContent {
258    #[serde(default)]
259    pub disabled_globs: Option<Vec<String>>,
260}
261
262#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
263pub struct GitSettings {
264    pub git_gutter: Option<GitGutter>,
265    pub gutter_debounce: Option<u64>,
266}
267
268#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
269#[serde(rename_all = "snake_case")]
270pub enum GitGutter {
271    #[default]
272    TrackedFiles,
273    Hide,
274}
275
276pub struct GitGutterConfig {}
277
278#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
279pub struct EditorSettings {
280    pub tab_size: Option<NonZeroU32>,
281    pub hard_tabs: Option<bool>,
282    pub soft_wrap: Option<SoftWrap>,
283    pub preferred_line_length: Option<u32>,
284    pub format_on_save: Option<FormatOnSave>,
285    pub remove_trailing_whitespace_on_save: Option<bool>,
286    pub ensure_final_newline_on_save: Option<bool>,
287    pub formatter: Option<Formatter>,
288    pub enable_language_server: Option<bool>,
289    pub show_copilot_suggestions: Option<bool>,
290    pub show_whitespaces: Option<ShowWhitespaces>,
291}
292
293#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
294#[serde(rename_all = "snake_case")]
295pub enum SoftWrap {
296    None,
297    EditorWidth,
298    PreferredLineLength,
299}
300#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
301#[serde(rename_all = "snake_case")]
302pub enum FormatOnSave {
303    On,
304    Off,
305    LanguageServer,
306    External {
307        command: String,
308        arguments: Vec<String>,
309    },
310}
311
312#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
313#[serde(rename_all = "snake_case")]
314pub enum Formatter {
315    LanguageServer,
316    External {
317        command: String,
318        arguments: Vec<String>,
319    },
320}
321
322#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
323#[serde(rename_all = "snake_case")]
324pub enum Autosave {
325    Off,
326    AfterDelay { milliseconds: u64 },
327    OnFocusChange,
328    OnWindowChange,
329}
330
331#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)]
332#[serde(rename_all = "snake_case")]
333pub enum DockAnchor {
334    #[default]
335    Bottom,
336    Right,
337    Expanded,
338}
339
340impl StaticColumnCount for DockAnchor {}
341impl Bind for DockAnchor {
342    fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
343        match self {
344            DockAnchor::Bottom => "Bottom",
345            DockAnchor::Right => "Right",
346            DockAnchor::Expanded => "Expanded",
347        }
348        .bind(statement, start_index)
349    }
350}
351
352impl Column for DockAnchor {
353    fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
354        String::column(statement, start_index).and_then(|(anchor_text, next_index)| {
355            Ok((
356                match anchor_text.as_ref() {
357                    "Bottom" => DockAnchor::Bottom,
358                    "Right" => DockAnchor::Right,
359                    "Expanded" => DockAnchor::Expanded,
360                    _ => bail!("Stored dock anchor is incorrect"),
361                },
362                next_index,
363            ))
364        })
365    }
366}
367
368#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
369pub struct SettingsFileContent {
370    #[serde(default)]
371    pub buffer_font_family: Option<String>,
372    #[serde(default)]
373    pub buffer_font_size: Option<f32>,
374    #[serde(default)]
375    pub buffer_font_features: Option<fonts::Features>,
376    #[serde(default)]
377    pub copilot: Option<CopilotSettingsContent>,
378    #[serde(default)]
379    pub active_pane_magnification: Option<f32>,
380    #[serde(default)]
381    pub cursor_blink: Option<bool>,
382    #[serde(default)]
383    pub confirm_quit: Option<bool>,
384    #[serde(default)]
385    pub hover_popover_enabled: Option<bool>,
386    #[serde(default)]
387    pub show_completions_on_input: Option<bool>,
388    #[serde(default)]
389    pub show_call_status_icon: Option<bool>,
390    #[serde(default)]
391    pub autosave: Option<Autosave>,
392    #[serde(default)]
393    pub default_dock_anchor: Option<DockAnchor>,
394    #[serde(flatten)]
395    pub editor: EditorSettings,
396    #[serde(default)]
397    pub git: Option<GitSettings>,
398    #[serde(default)]
399    #[serde(alias = "language_overrides")]
400    pub languages: HashMap<Arc<str>, EditorSettings>,
401    #[serde(default)]
402    pub lsp: HashMap<Arc<str>, LspSettings>,
403    #[serde(default)]
404    pub theme: Option<String>,
405    #[serde(default)]
406    pub base_keymap: Option<BaseKeymap>,
407    #[serde(default)]
408    pub features: FeaturesContent,
409}
410
411#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
412#[serde(rename_all = "snake_case")]
413pub struct LspSettings {
414    pub initialization_options: Option<Value>,
415}
416
417#[derive(Clone, Debug, PartialEq, Eq)]
418pub struct Features {
419    pub copilot: bool,
420}
421
422#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
423#[serde(rename_all = "snake_case")]
424pub struct FeaturesContent {
425    pub copilot: Option<bool>,
426}
427
428#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
429#[serde(rename_all = "snake_case")]
430pub enum ShowWhitespaces {
431    #[default]
432    Selection,
433    None,
434    All,
435}
436
437impl Settings {
438    pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> {
439        match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() {
440            Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
441            Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
442        }
443    }
444
445    /// Fill out the settings corresponding to the default.json file, overrides will be set later
446    pub fn defaults(
447        assets: impl AssetSource,
448        font_cache: &FontCache,
449        themes: &ThemeRegistry,
450    ) -> Self {
451        #[track_caller]
452        fn required<T>(value: Option<T>) -> Option<T> {
453            assert!(value.is_some(), "missing default setting value");
454            value
455        }
456
457        let defaults: SettingsFileContent = settings_store::parse_json_with_comments(
458            str::from_utf8(assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap().as_ref()).unwrap(),
459        )
460        .unwrap();
461
462        let buffer_font_features = defaults.buffer_font_features.unwrap();
463        Self {
464            buffer_font_family: font_cache
465                .load_family(
466                    &[defaults.buffer_font_family.as_ref().unwrap()],
467                    &buffer_font_features,
468                )
469                .unwrap(),
470            buffer_font_family_name: defaults.buffer_font_family.unwrap(),
471            buffer_font_features,
472            buffer_font_size: defaults.buffer_font_size.unwrap(),
473            active_pane_magnification: defaults.active_pane_magnification.unwrap(),
474            confirm_quit: defaults.confirm_quit.unwrap(),
475            cursor_blink: defaults.cursor_blink.unwrap(),
476            hover_popover_enabled: defaults.hover_popover_enabled.unwrap(),
477            show_completions_on_input: defaults.show_completions_on_input.unwrap(),
478            show_call_status_icon: defaults.show_call_status_icon.unwrap(),
479            autosave: defaults.autosave.unwrap(),
480            default_dock_anchor: defaults.default_dock_anchor.unwrap(),
481            editor_defaults: EditorSettings {
482                tab_size: required(defaults.editor.tab_size),
483                hard_tabs: required(defaults.editor.hard_tabs),
484                soft_wrap: required(defaults.editor.soft_wrap),
485                preferred_line_length: required(defaults.editor.preferred_line_length),
486                remove_trailing_whitespace_on_save: required(
487                    defaults.editor.remove_trailing_whitespace_on_save,
488                ),
489                ensure_final_newline_on_save: required(
490                    defaults.editor.ensure_final_newline_on_save,
491                ),
492                format_on_save: required(defaults.editor.format_on_save),
493                formatter: required(defaults.editor.formatter),
494                enable_language_server: required(defaults.editor.enable_language_server),
495                show_copilot_suggestions: required(defaults.editor.show_copilot_suggestions),
496                show_whitespaces: required(defaults.editor.show_whitespaces),
497            },
498            editor_overrides: Default::default(),
499            copilot: CopilotSettings {
500                disabled_globs: defaults
501                    .copilot
502                    .unwrap()
503                    .disabled_globs
504                    .unwrap()
505                    .into_iter()
506                    .map(|s| glob::Pattern::new(&s).unwrap())
507                    .collect(),
508            },
509            git: defaults.git.unwrap(),
510            git_overrides: Default::default(),
511            language_defaults: defaults.languages,
512            language_overrides: Default::default(),
513            lsp: defaults.lsp.clone(),
514            theme: themes.get(&defaults.theme.unwrap()).unwrap(),
515            base_keymap: Default::default(),
516            features: Features {
517                copilot: defaults.features.copilot.unwrap(),
518            },
519        }
520    }
521
522    // Fill out the overrride and etc. settings from the user's settings.json
523    fn set_user_settings(
524        &mut self,
525        data: SettingsFileContent,
526        theme_registry: &ThemeRegistry,
527        font_cache: &FontCache,
528    ) {
529        let mut family_changed = false;
530        if let Some(value) = data.buffer_font_family {
531            self.buffer_font_family_name = value;
532            family_changed = true;
533        }
534        if let Some(value) = data.buffer_font_features {
535            self.buffer_font_features = value;
536            family_changed = true;
537        }
538        if family_changed {
539            if let Some(id) = font_cache
540                .load_family(&[&self.buffer_font_family_name], &self.buffer_font_features)
541                .log_err()
542            {
543                self.buffer_font_family = id;
544            }
545        }
546
547        if let Some(value) = &data.theme {
548            if let Some(theme) = theme_registry.get(value).log_err() {
549                self.theme = theme;
550            }
551        }
552
553        merge(&mut self.buffer_font_size, data.buffer_font_size);
554        merge(
555            &mut self.active_pane_magnification,
556            data.active_pane_magnification,
557        );
558        merge(&mut self.cursor_blink, data.cursor_blink);
559        merge(&mut self.confirm_quit, data.confirm_quit);
560        merge(&mut self.hover_popover_enabled, data.hover_popover_enabled);
561        merge(
562            &mut self.show_completions_on_input,
563            data.show_completions_on_input,
564        );
565        merge(&mut self.autosave, data.autosave);
566        merge(&mut self.default_dock_anchor, data.default_dock_anchor);
567        merge(&mut self.base_keymap, data.base_keymap);
568        merge(&mut self.features.copilot, data.features.copilot);
569
570        if let Some(copilot) = data.copilot {
571            if let Some(disabled_globs) = copilot.disabled_globs {
572                self.copilot.disabled_globs = disabled_globs
573                    .into_iter()
574                    .filter_map(|s| glob::Pattern::new(&s).ok())
575                    .collect()
576            }
577        }
578        self.editor_overrides = data.editor;
579        self.git_overrides = data.git.unwrap_or_default();
580        self.language_overrides = data.languages;
581        self.lsp = data.lsp;
582    }
583
584    pub fn with_language_defaults(
585        mut self,
586        language_name: impl Into<Arc<str>>,
587        overrides: EditorSettings,
588    ) -> Self {
589        self.language_defaults
590            .insert(language_name.into(), overrides);
591        self
592    }
593
594    pub fn features(&self) -> &Features {
595        &self.features
596    }
597
598    pub fn show_copilot_suggestions(&self, language: Option<&str>, path: Option<&Path>) -> bool {
599        if !self.features.copilot {
600            return false;
601        }
602
603        if !self.copilot_enabled_for_language(language) {
604            return false;
605        }
606
607        if let Some(path) = path {
608            if !self.copilot_enabled_for_path(path) {
609                return false;
610            }
611        }
612
613        true
614    }
615
616    pub fn copilot_enabled_for_path(&self, path: &Path) -> bool {
617        !self
618            .copilot
619            .disabled_globs
620            .iter()
621            .any(|glob| glob.matches_path(path))
622    }
623
624    pub fn copilot_enabled_for_language(&self, language: Option<&str>) -> bool {
625        self.language_setting(language, |settings| settings.show_copilot_suggestions)
626    }
627
628    pub fn tab_size(&self, language: Option<&str>) -> NonZeroU32 {
629        self.language_setting(language, |settings| settings.tab_size)
630    }
631
632    pub fn show_whitespaces(&self, language: Option<&str>) -> ShowWhitespaces {
633        self.language_setting(language, |settings| settings.show_whitespaces)
634    }
635
636    pub fn hard_tabs(&self, language: Option<&str>) -> bool {
637        self.language_setting(language, |settings| settings.hard_tabs)
638    }
639
640    pub fn soft_wrap(&self, language: Option<&str>) -> SoftWrap {
641        self.language_setting(language, |settings| settings.soft_wrap)
642    }
643
644    pub fn preferred_line_length(&self, language: Option<&str>) -> u32 {
645        self.language_setting(language, |settings| settings.preferred_line_length)
646    }
647
648    pub fn remove_trailing_whitespace_on_save(&self, language: Option<&str>) -> bool {
649        self.language_setting(language, |settings| {
650            settings.remove_trailing_whitespace_on_save.clone()
651        })
652    }
653
654    pub fn ensure_final_newline_on_save(&self, language: Option<&str>) -> bool {
655        self.language_setting(language, |settings| {
656            settings.ensure_final_newline_on_save.clone()
657        })
658    }
659
660    pub fn format_on_save(&self, language: Option<&str>) -> FormatOnSave {
661        self.language_setting(language, |settings| settings.format_on_save.clone())
662    }
663
664    pub fn formatter(&self, language: Option<&str>) -> Formatter {
665        self.language_setting(language, |settings| settings.formatter.clone())
666    }
667
668    pub fn enable_language_server(&self, language: Option<&str>) -> bool {
669        self.language_setting(language, |settings| settings.enable_language_server)
670    }
671
672    fn language_setting<F, R>(&self, language: Option<&str>, f: F) -> R
673    where
674        F: Fn(&EditorSettings) -> Option<R>,
675    {
676        None.or_else(|| language.and_then(|l| self.language_overrides.get(l).and_then(&f)))
677            .or_else(|| f(&self.editor_overrides))
678            .or_else(|| language.and_then(|l| self.language_defaults.get(l).and_then(&f)))
679            .or_else(|| f(&self.editor_defaults))
680            .expect("missing default")
681    }
682
683    pub fn git_gutter(&self) -> GitGutter {
684        self.git_overrides.git_gutter.unwrap_or_else(|| {
685            self.git
686                .git_gutter
687                .expect("git_gutter should be some by setting setup")
688        })
689    }
690
691    #[cfg(any(test, feature = "test-support"))]
692    pub fn test(cx: &gpui::AppContext) -> Settings {
693        Settings {
694            buffer_font_family_name: "Monaco".to_string(),
695            buffer_font_features: Default::default(),
696            buffer_font_family: cx
697                .font_cache()
698                .load_family(&["Monaco"], &Default::default())
699                .unwrap(),
700            buffer_font_size: 14.,
701            active_pane_magnification: 1.,
702            confirm_quit: false,
703            cursor_blink: true,
704            hover_popover_enabled: true,
705            show_completions_on_input: true,
706            show_call_status_icon: true,
707            autosave: Autosave::Off,
708            default_dock_anchor: DockAnchor::Bottom,
709            editor_defaults: EditorSettings {
710                tab_size: Some(4.try_into().unwrap()),
711                hard_tabs: Some(false),
712                soft_wrap: Some(SoftWrap::None),
713                preferred_line_length: Some(80),
714                remove_trailing_whitespace_on_save: Some(true),
715                ensure_final_newline_on_save: Some(true),
716                format_on_save: Some(FormatOnSave::On),
717                formatter: Some(Formatter::LanguageServer),
718                enable_language_server: Some(true),
719                show_copilot_suggestions: Some(true),
720                show_whitespaces: Some(ShowWhitespaces::None),
721            },
722            editor_overrides: Default::default(),
723            copilot: Default::default(),
724            git: Default::default(),
725            git_overrides: Default::default(),
726            language_defaults: Default::default(),
727            language_overrides: Default::default(),
728            lsp: Default::default(),
729            theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default),
730            base_keymap: Default::default(),
731            features: Features { copilot: true },
732        }
733    }
734
735    #[cfg(any(test, feature = "test-support"))]
736    pub fn test_async(cx: &mut gpui::TestAppContext) {
737        cx.update(|cx| {
738            let settings = Self::test(cx);
739            cx.set_global(settings);
740        });
741    }
742}
743
744fn merge<T: Copy>(target: &mut T, value: Option<T>) {
745    if let Some(value) = value {
746        *target = value;
747    }
748}