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}