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