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