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