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