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