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