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}