1mod keymap_file;
2pub mod settings_file;
3pub mod watched_json;
4
5use anyhow::{bail, Result};
6use gpui::{
7 font_cache::{FamilyId, FontCache},
8 fonts, AssetSource,
9};
10use schemars::{
11 gen::{SchemaGenerator, SchemaSettings},
12 schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec},
13 JsonSchema,
14};
15use serde::{de::DeserializeOwned, Deserialize, Serialize};
16use serde_json::Value;
17use sqlez::{
18 bindable::{Bind, Column, StaticColumnCount},
19 statement::Statement,
20};
21use std::{collections::HashMap, num::NonZeroU32, str, sync::Arc};
22use theme::{Theme, ThemeRegistry};
23use tree_sitter::Query;
24use util::ResultExt as _;
25
26pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
27pub use watched_json::watch_files;
28
29#[derive(Clone)]
30pub struct Settings {
31 pub buffer_font_family_name: String,
32 pub buffer_font_features: fonts::Features,
33 pub buffer_font_family: FamilyId,
34 pub default_buffer_font_size: f32,
35 pub enable_copilot_integration: bool,
36 pub buffer_font_size: f32,
37 pub active_pane_magnification: f32,
38 pub cursor_blink: bool,
39 pub confirm_quit: bool,
40 pub hover_popover_enabled: bool,
41 pub show_completions_on_input: bool,
42 pub show_call_status_icon: bool,
43 pub vim_mode: bool,
44 pub autosave: Autosave,
45 pub default_dock_anchor: DockAnchor,
46 pub editor_defaults: EditorSettings,
47 pub editor_overrides: EditorSettings,
48 pub git: GitSettings,
49 pub git_overrides: GitSettings,
50 pub journal_defaults: JournalSettings,
51 pub journal_overrides: JournalSettings,
52 pub terminal_defaults: TerminalSettings,
53 pub terminal_overrides: TerminalSettings,
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 telemetry_defaults: TelemetrySettings,
59 pub telemetry_overrides: TelemetrySettings,
60 pub auto_update: bool,
61 pub base_keymap: BaseKeymap,
62}
63
64#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
65#[serde(rename_all = "snake_case")]
66pub enum CopilotSettings {
67 #[default]
68 On,
69 Off,
70}
71
72impl From<CopilotSettings> for bool {
73 fn from(value: CopilotSettings) -> Self {
74 match value {
75 CopilotSettings::On => true,
76 CopilotSettings::Off => false,
77 }
78 }
79}
80
81impl CopilotSettings {
82 pub fn is_on(&self) -> bool {
83 <CopilotSettings as Into<bool>>::into(*self)
84 }
85}
86
87#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
88pub enum BaseKeymap {
89 #[default]
90 VSCode,
91 JetBrains,
92 SublimeText,
93 Atom,
94 TextMate,
95}
96
97impl BaseKeymap {
98 pub const OPTIONS: [(&'static str, Self); 5] = [
99 ("VSCode (Default)", Self::VSCode),
100 ("Atom", Self::Atom),
101 ("JetBrains", Self::JetBrains),
102 ("Sublime Text", Self::SublimeText),
103 ("TextMate", Self::TextMate),
104 ];
105
106 pub fn asset_path(&self) -> Option<&'static str> {
107 match self {
108 BaseKeymap::JetBrains => Some("keymaps/jetbrains.json"),
109 BaseKeymap::SublimeText => Some("keymaps/sublime_text.json"),
110 BaseKeymap::Atom => Some("keymaps/atom.json"),
111 BaseKeymap::TextMate => Some("keymaps/textmate.json"),
112 BaseKeymap::VSCode => None,
113 }
114 }
115
116 pub fn names() -> impl Iterator<Item = &'static str> {
117 Self::OPTIONS.iter().map(|(name, _)| *name)
118 }
119
120 pub fn from_names(option: &str) -> BaseKeymap {
121 Self::OPTIONS
122 .iter()
123 .copied()
124 .find_map(|(name, value)| (name == option).then(|| value))
125 .unwrap_or_default()
126 }
127}
128
129#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
130pub struct TelemetrySettings {
131 diagnostics: Option<bool>,
132 metrics: Option<bool>,
133}
134
135impl TelemetrySettings {
136 pub fn metrics(&self) -> bool {
137 self.metrics.unwrap()
138 }
139
140 pub fn diagnostics(&self) -> bool {
141 self.diagnostics.unwrap()
142 }
143
144 pub fn set_metrics(&mut self, value: bool) {
145 self.metrics = Some(value);
146 }
147
148 pub fn set_diagnostics(&mut self, value: bool) {
149 self.diagnostics = Some(value);
150 }
151}
152
153#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
154pub struct GitSettings {
155 pub git_gutter: Option<GitGutter>,
156 pub gutter_debounce: Option<u64>,
157}
158
159#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
160#[serde(rename_all = "snake_case")]
161pub enum GitGutter {
162 #[default]
163 TrackedFiles,
164 Hide,
165}
166
167pub struct GitGutterConfig {}
168
169#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
170pub struct EditorSettings {
171 pub tab_size: Option<NonZeroU32>,
172 pub hard_tabs: Option<bool>,
173 pub soft_wrap: Option<SoftWrap>,
174 pub preferred_line_length: Option<u32>,
175 pub format_on_save: Option<FormatOnSave>,
176 pub remove_trailing_whitespace_on_save: Option<bool>,
177 pub ensure_final_newline_on_save: Option<bool>,
178 pub formatter: Option<Formatter>,
179 pub enable_language_server: Option<bool>,
180 pub copilot: Option<OnOff>,
181}
182
183#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
184#[serde(rename_all = "snake_case")]
185pub enum OnOff {
186 On,
187 Off,
188}
189
190impl OnOff {
191 fn as_bool(&self) -> bool {
192 match self {
193 OnOff::On => true,
194 OnOff::Off => false,
195 }
196 }
197}
198
199impl Into<bool> for OnOff {
200 fn into(self) -> bool {
201 self.as_bool()
202 }
203}
204
205#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
206#[serde(rename_all = "snake_case")]
207pub enum SoftWrap {
208 None,
209 EditorWidth,
210 PreferredLineLength,
211}
212#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
213#[serde(rename_all = "snake_case")]
214pub enum FormatOnSave {
215 On,
216 Off,
217 LanguageServer,
218 External {
219 command: String,
220 arguments: Vec<String>,
221 },
222}
223
224#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
225#[serde(rename_all = "snake_case")]
226pub enum Formatter {
227 LanguageServer,
228 External {
229 command: String,
230 arguments: Vec<String>,
231 },
232}
233
234#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
235#[serde(rename_all = "snake_case")]
236pub enum Autosave {
237 Off,
238 AfterDelay { milliseconds: u64 },
239 OnFocusChange,
240 OnWindowChange,
241}
242
243#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
244pub struct JournalSettings {
245 pub path: Option<String>,
246 pub hour_format: Option<HourFormat>,
247}
248
249impl Default for JournalSettings {
250 fn default() -> Self {
251 Self {
252 path: Some("~".into()),
253 hour_format: Some(Default::default()),
254 }
255 }
256}
257
258#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
259#[serde(rename_all = "snake_case")]
260pub enum HourFormat {
261 Hour12,
262 Hour24,
263}
264
265impl Default for HourFormat {
266 fn default() -> Self {
267 Self::Hour12
268 }
269}
270
271#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
272pub struct TerminalSettings {
273 pub shell: Option<Shell>,
274 pub working_directory: Option<WorkingDirectory>,
275 pub font_size: Option<f32>,
276 pub font_family: Option<String>,
277 pub font_features: Option<fonts::Features>,
278 pub env: Option<HashMap<String, String>>,
279 pub blinking: Option<TerminalBlink>,
280 pub alternate_scroll: Option<AlternateScroll>,
281 pub option_as_meta: Option<bool>,
282 pub copy_on_select: Option<bool>,
283}
284
285#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
286#[serde(rename_all = "snake_case")]
287pub enum TerminalBlink {
288 Off,
289 TerminalControlled,
290 On,
291}
292
293impl Default for TerminalBlink {
294 fn default() -> Self {
295 TerminalBlink::TerminalControlled
296 }
297}
298
299#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
300#[serde(rename_all = "snake_case")]
301pub enum Shell {
302 System,
303 Program(String),
304 WithArguments { program: String, args: Vec<String> },
305}
306
307impl Default for Shell {
308 fn default() -> Self {
309 Shell::System
310 }
311}
312
313#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
314#[serde(rename_all = "snake_case")]
315pub enum AlternateScroll {
316 On,
317 Off,
318}
319
320impl Default for AlternateScroll {
321 fn default() -> Self {
322 AlternateScroll::On
323 }
324}
325
326#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
327#[serde(rename_all = "snake_case")]
328pub enum WorkingDirectory {
329 CurrentProjectDirectory,
330 FirstProjectDirectory,
331 AlwaysHome,
332 Always { directory: String },
333}
334
335impl Default for WorkingDirectory {
336 fn default() -> Self {
337 Self::CurrentProjectDirectory
338 }
339}
340
341#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)]
342#[serde(rename_all = "snake_case")]
343pub enum DockAnchor {
344 #[default]
345 Bottom,
346 Right,
347 Expanded,
348}
349
350impl StaticColumnCount for DockAnchor {}
351impl Bind for DockAnchor {
352 fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
353 match self {
354 DockAnchor::Bottom => "Bottom",
355 DockAnchor::Right => "Right",
356 DockAnchor::Expanded => "Expanded",
357 }
358 .bind(statement, start_index)
359 }
360}
361
362impl Column for DockAnchor {
363 fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
364 String::column(statement, start_index).and_then(|(anchor_text, next_index)| {
365 Ok((
366 match anchor_text.as_ref() {
367 "Bottom" => DockAnchor::Bottom,
368 "Right" => DockAnchor::Right,
369 "Expanded" => DockAnchor::Expanded,
370 _ => bail!("Stored dock anchor is incorrect"),
371 },
372 next_index,
373 ))
374 })
375 }
376}
377
378#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
379pub struct SettingsFileContent {
380 #[serde(default)]
381 pub buffer_font_family: Option<String>,
382 #[serde(default)]
383 pub buffer_font_size: Option<f32>,
384 #[serde(default)]
385 pub buffer_font_features: Option<fonts::Features>,
386 #[serde(default)]
387 pub active_pane_magnification: Option<f32>,
388 #[serde(default)]
389 pub cursor_blink: Option<bool>,
390 #[serde(default)]
391 pub confirm_quit: Option<bool>,
392 #[serde(default)]
393 pub hover_popover_enabled: Option<bool>,
394 #[serde(default)]
395 pub show_completions_on_input: Option<bool>,
396 #[serde(default)]
397 pub show_call_status_icon: Option<bool>,
398 #[serde(default)]
399 pub vim_mode: Option<bool>,
400 #[serde(default)]
401 pub autosave: Option<Autosave>,
402 #[serde(default)]
403 pub default_dock_anchor: Option<DockAnchor>,
404 #[serde(flatten)]
405 pub editor: EditorSettings,
406 #[serde(default)]
407 pub journal: JournalSettings,
408 #[serde(default)]
409 pub terminal: TerminalSettings,
410 #[serde(default)]
411 pub git: Option<GitSettings>,
412 #[serde(default)]
413 #[serde(alias = "language_overrides")]
414 pub languages: HashMap<Arc<str>, EditorSettings>,
415 #[serde(default)]
416 pub lsp: HashMap<Arc<str>, LspSettings>,
417 #[serde(default)]
418 pub theme: Option<String>,
419 #[serde(default)]
420 pub telemetry: TelemetrySettings,
421 #[serde(default)]
422 pub auto_update: Option<bool>,
423 #[serde(default)]
424 pub base_keymap: Option<BaseKeymap>,
425 #[serde(default)]
426 pub enable_copilot_integration: Option<bool>,
427}
428
429#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
430#[serde(rename_all = "snake_case")]
431pub struct LspSettings {
432 pub initialization_options: Option<Value>,
433}
434
435impl Settings {
436 /// Fill out the settings corresponding to the default.json file, overrides will be set later
437 pub fn defaults(
438 assets: impl AssetSource,
439 font_cache: &FontCache,
440 themes: &ThemeRegistry,
441 ) -> Self {
442 #[track_caller]
443 fn required<T>(value: Option<T>) -> Option<T> {
444 assert!(value.is_some(), "missing default setting value");
445 value
446 }
447
448 let defaults: SettingsFileContent = parse_json_with_comments(
449 str::from_utf8(assets.load("settings/default.json").unwrap().as_ref()).unwrap(),
450 )
451 .unwrap();
452
453 let buffer_font_features = defaults.buffer_font_features.unwrap();
454 Self {
455 buffer_font_family: font_cache
456 .load_family(
457 &[defaults.buffer_font_family.as_ref().unwrap()],
458 &buffer_font_features,
459 )
460 .unwrap(),
461 buffer_font_family_name: defaults.buffer_font_family.unwrap(),
462 buffer_font_features,
463 buffer_font_size: defaults.buffer_font_size.unwrap(),
464 active_pane_magnification: defaults.active_pane_magnification.unwrap(),
465 default_buffer_font_size: defaults.buffer_font_size.unwrap(),
466 confirm_quit: defaults.confirm_quit.unwrap(),
467 cursor_blink: defaults.cursor_blink.unwrap(),
468 hover_popover_enabled: defaults.hover_popover_enabled.unwrap(),
469 show_completions_on_input: defaults.show_completions_on_input.unwrap(),
470 show_call_status_icon: defaults.show_call_status_icon.unwrap(),
471 vim_mode: defaults.vim_mode.unwrap(),
472 autosave: defaults.autosave.unwrap(),
473 default_dock_anchor: defaults.default_dock_anchor.unwrap(),
474 editor_defaults: EditorSettings {
475 tab_size: required(defaults.editor.tab_size),
476 hard_tabs: required(defaults.editor.hard_tabs),
477 soft_wrap: required(defaults.editor.soft_wrap),
478 preferred_line_length: required(defaults.editor.preferred_line_length),
479 remove_trailing_whitespace_on_save: required(
480 defaults.editor.remove_trailing_whitespace_on_save,
481 ),
482 ensure_final_newline_on_save: required(
483 defaults.editor.ensure_final_newline_on_save,
484 ),
485 format_on_save: required(defaults.editor.format_on_save),
486 formatter: required(defaults.editor.formatter),
487 enable_language_server: required(defaults.editor.enable_language_server),
488 copilot: required(defaults.editor.copilot),
489 },
490 editor_overrides: Default::default(),
491 git: defaults.git.unwrap(),
492 git_overrides: Default::default(),
493 journal_defaults: defaults.journal,
494 journal_overrides: Default::default(),
495 terminal_defaults: defaults.terminal,
496 terminal_overrides: Default::default(),
497 language_defaults: defaults.languages,
498 language_overrides: Default::default(),
499 lsp: defaults.lsp.clone(),
500 theme: themes.get(&defaults.theme.unwrap()).unwrap(),
501 telemetry_defaults: defaults.telemetry,
502 telemetry_overrides: Default::default(),
503 auto_update: defaults.auto_update.unwrap(),
504 base_keymap: Default::default(),
505 enable_copilot_integration: defaults.enable_copilot_integration.unwrap(),
506 }
507 }
508
509 // Fill out the overrride and etc. settings from the user's settings.json
510 pub fn set_user_settings(
511 &mut self,
512 data: SettingsFileContent,
513 theme_registry: &ThemeRegistry,
514 font_cache: &FontCache,
515 ) {
516 let mut family_changed = false;
517 if let Some(value) = data.buffer_font_family {
518 self.buffer_font_family_name = value;
519 family_changed = true;
520 }
521 if let Some(value) = data.buffer_font_features {
522 self.buffer_font_features = value;
523 family_changed = true;
524 }
525 if family_changed {
526 if let Some(id) = font_cache
527 .load_family(&[&self.buffer_font_family_name], &self.buffer_font_features)
528 .log_err()
529 {
530 self.buffer_font_family = id;
531 }
532 }
533
534 if let Some(value) = &data.theme {
535 if let Some(theme) = theme_registry.get(value).log_err() {
536 self.theme = theme;
537 }
538 }
539
540 merge(&mut self.buffer_font_size, data.buffer_font_size);
541 merge(
542 &mut self.active_pane_magnification,
543 data.active_pane_magnification,
544 );
545 merge(&mut self.default_buffer_font_size, data.buffer_font_size);
546 merge(&mut self.cursor_blink, data.cursor_blink);
547 merge(&mut self.confirm_quit, data.confirm_quit);
548 merge(&mut self.hover_popover_enabled, data.hover_popover_enabled);
549 merge(
550 &mut self.show_completions_on_input,
551 data.show_completions_on_input,
552 );
553 merge(&mut self.vim_mode, data.vim_mode);
554 merge(&mut self.autosave, data.autosave);
555 merge(&mut self.default_dock_anchor, data.default_dock_anchor);
556 merge(&mut self.base_keymap, data.base_keymap);
557
558 self.editor_overrides = data.editor;
559 self.git_overrides = data.git.unwrap_or_default();
560 self.journal_overrides = data.journal;
561 self.terminal_defaults.font_size = data.terminal.font_size;
562 self.terminal_overrides.copy_on_select = data.terminal.copy_on_select;
563 self.terminal_overrides = data.terminal;
564 self.language_overrides = data.languages;
565 self.telemetry_overrides = data.telemetry;
566 self.lsp = data.lsp;
567 merge(&mut self.auto_update, data.auto_update);
568 }
569
570 pub fn with_language_defaults(
571 mut self,
572 language_name: impl Into<Arc<str>>,
573 overrides: EditorSettings,
574 ) -> Self {
575 self.language_defaults
576 .insert(language_name.into(), overrides);
577 self
578 }
579
580 pub fn copilot_on(&self, language: Option<&str>) -> bool {
581 if self.enable_copilot_integration {
582 self.language_setting(language, |settings| settings.copilot.map(Into::into))
583 } else {
584 false
585 }
586 }
587
588 pub fn tab_size(&self, language: Option<&str>) -> NonZeroU32 {
589 self.language_setting(language, |settings| settings.tab_size)
590 }
591
592 pub fn hard_tabs(&self, language: Option<&str>) -> bool {
593 self.language_setting(language, |settings| settings.hard_tabs)
594 }
595
596 pub fn soft_wrap(&self, language: Option<&str>) -> SoftWrap {
597 self.language_setting(language, |settings| settings.soft_wrap)
598 }
599
600 pub fn preferred_line_length(&self, language: Option<&str>) -> u32 {
601 self.language_setting(language, |settings| settings.preferred_line_length)
602 }
603
604 pub fn remove_trailing_whitespace_on_save(&self, language: Option<&str>) -> bool {
605 self.language_setting(language, |settings| {
606 settings.remove_trailing_whitespace_on_save.clone()
607 })
608 }
609
610 pub fn ensure_final_newline_on_save(&self, language: Option<&str>) -> bool {
611 self.language_setting(language, |settings| {
612 settings.ensure_final_newline_on_save.clone()
613 })
614 }
615
616 pub fn format_on_save(&self, language: Option<&str>) -> FormatOnSave {
617 self.language_setting(language, |settings| settings.format_on_save.clone())
618 }
619
620 pub fn formatter(&self, language: Option<&str>) -> Formatter {
621 self.language_setting(language, |settings| settings.formatter.clone())
622 }
623
624 pub fn enable_language_server(&self, language: Option<&str>) -> bool {
625 self.language_setting(language, |settings| settings.enable_language_server)
626 }
627
628 fn language_setting<F, R>(&self, language: Option<&str>, f: F) -> R
629 where
630 F: Fn(&EditorSettings) -> Option<R>,
631 {
632 None.or_else(|| language.and_then(|l| self.language_overrides.get(l).and_then(&f)))
633 .or_else(|| f(&self.editor_overrides))
634 .or_else(|| language.and_then(|l| self.language_defaults.get(l).and_then(&f)))
635 .or_else(|| f(&self.editor_defaults))
636 .expect("missing default")
637 }
638
639 pub fn git_gutter(&self) -> GitGutter {
640 self.git_overrides.git_gutter.unwrap_or_else(|| {
641 self.git
642 .git_gutter
643 .expect("git_gutter should be some by setting setup")
644 })
645 }
646
647 fn terminal_setting<F, R: Default + Clone>(&self, f: F) -> R
648 where
649 F: Fn(&TerminalSettings) -> Option<&R>,
650 {
651 f(&self.terminal_overrides)
652 .or_else(|| f(&self.terminal_defaults))
653 .cloned()
654 .unwrap_or_else(|| R::default())
655 }
656
657 pub fn telemetry(&self) -> TelemetrySettings {
658 TelemetrySettings {
659 diagnostics: Some(self.telemetry_diagnostics()),
660 metrics: Some(self.telemetry_metrics()),
661 }
662 }
663
664 pub fn telemetry_diagnostics(&self) -> bool {
665 self.telemetry_overrides
666 .diagnostics
667 .or(self.telemetry_defaults.diagnostics)
668 .expect("missing default")
669 }
670
671 pub fn telemetry_metrics(&self) -> bool {
672 self.telemetry_overrides
673 .metrics
674 .or(self.telemetry_defaults.metrics)
675 .expect("missing default")
676 }
677
678 pub fn terminal_scroll(&self) -> AlternateScroll {
679 self.terminal_setting(|terminal_setting| terminal_setting.alternate_scroll.as_ref())
680 }
681
682 pub fn terminal_shell(&self) -> Shell {
683 self.terminal_setting(|terminal_setting| terminal_setting.shell.as_ref())
684 }
685
686 pub fn terminal_env(&self) -> HashMap<String, String> {
687 self.terminal_setting(|terminal_setting| terminal_setting.env.as_ref())
688 }
689
690 pub fn terminal_strategy(&self) -> WorkingDirectory {
691 self.terminal_setting(|terminal_setting| terminal_setting.working_directory.as_ref())
692 }
693
694 #[cfg(any(test, feature = "test-support"))]
695 pub fn test(cx: &gpui::AppContext) -> Settings {
696 Settings {
697 buffer_font_family_name: "Monaco".to_string(),
698 buffer_font_features: Default::default(),
699 buffer_font_family: cx
700 .font_cache()
701 .load_family(&["Monaco"], &Default::default())
702 .unwrap(),
703 buffer_font_size: 14.,
704 active_pane_magnification: 1.,
705 default_buffer_font_size: 14.,
706 confirm_quit: false,
707 cursor_blink: true,
708 hover_popover_enabled: true,
709 show_completions_on_input: true,
710 show_call_status_icon: true,
711 vim_mode: false,
712 autosave: Autosave::Off,
713 default_dock_anchor: DockAnchor::Bottom,
714 editor_defaults: EditorSettings {
715 tab_size: Some(4.try_into().unwrap()),
716 hard_tabs: Some(false),
717 soft_wrap: Some(SoftWrap::None),
718 preferred_line_length: Some(80),
719 remove_trailing_whitespace_on_save: Some(true),
720 ensure_final_newline_on_save: Some(true),
721 format_on_save: Some(FormatOnSave::On),
722 formatter: Some(Formatter::LanguageServer),
723 enable_language_server: Some(true),
724 copilot: Some(OnOff::On),
725 },
726 editor_overrides: Default::default(),
727 journal_defaults: Default::default(),
728 journal_overrides: Default::default(),
729 terminal_defaults: Default::default(),
730 terminal_overrides: Default::default(),
731 git: Default::default(),
732 git_overrides: Default::default(),
733 language_defaults: Default::default(),
734 language_overrides: Default::default(),
735 lsp: Default::default(),
736 theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default),
737 telemetry_defaults: TelemetrySettings {
738 diagnostics: Some(true),
739 metrics: Some(true),
740 },
741 telemetry_overrides: Default::default(),
742 auto_update: true,
743 base_keymap: Default::default(),
744 enable_copilot_integration: true,
745 }
746 }
747
748 #[cfg(any(test, feature = "test-support"))]
749 pub fn test_async(cx: &mut gpui::TestAppContext) {
750 cx.update(|cx| {
751 let settings = Self::test(cx);
752 cx.set_global(settings);
753 });
754 }
755}
756
757pub fn settings_file_json_schema(
758 theme_names: Vec<String>,
759 language_names: &[String],
760) -> serde_json::Value {
761 let settings = SchemaSettings::draft07().with(|settings| {
762 settings.option_add_null_type = false;
763 });
764 let generator = SchemaGenerator::new(settings);
765 let mut root_schema = generator.into_root_schema_for::<SettingsFileContent>();
766
767 // Create a schema for a theme name.
768 let theme_name_schema = SchemaObject {
769 instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
770 enum_values: Some(theme_names.into_iter().map(Value::String).collect()),
771 ..Default::default()
772 };
773
774 // Create a schema for a 'languages overrides' object, associating editor
775 // settings with specific langauges.
776 assert!(root_schema.definitions.contains_key("EditorSettings"));
777 let languages_object_schema = SchemaObject {
778 instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
779 object: Some(Box::new(ObjectValidation {
780 properties: language_names
781 .iter()
782 .map(|name| {
783 (
784 name.clone(),
785 Schema::new_ref("#/definitions/EditorSettings".into()),
786 )
787 })
788 .collect(),
789 ..Default::default()
790 })),
791 ..Default::default()
792 };
793
794 // Add these new schemas as definitions, and modify properties of the root
795 // schema to reference them.
796 root_schema.definitions.extend([
797 ("ThemeName".into(), theme_name_schema.into()),
798 ("Languages".into(), languages_object_schema.into()),
799 ]);
800 let root_schema_object = &mut root_schema.schema.object.as_mut().unwrap();
801
802 root_schema_object.properties.extend([
803 (
804 "theme".to_owned(),
805 Schema::new_ref("#/definitions/ThemeName".into()),
806 ),
807 (
808 "languages".to_owned(),
809 Schema::new_ref("#/definitions/Languages".into()),
810 ),
811 // For backward compatibility
812 (
813 "language_overrides".to_owned(),
814 Schema::new_ref("#/definitions/Languages".into()),
815 ),
816 ]);
817
818 serde_json::to_value(root_schema).unwrap()
819}
820
821fn merge<T: Copy>(target: &mut T, value: Option<T>) {
822 if let Some(value) = value {
823 *target = value;
824 }
825}
826
827pub fn parse_json_with_comments<T: DeserializeOwned>(content: &str) -> Result<T> {
828 Ok(serde_json::from_reader(
829 json_comments::CommentSettings::c_style().strip_comments(content.as_bytes()),
830 )?)
831}
832
833fn write_settings_key(settings_content: &mut String, key_path: &[&str], new_value: &Value) {
834 let mut parser = tree_sitter::Parser::new();
835 parser.set_language(tree_sitter_json::language()).unwrap();
836 let tree = parser.parse(&settings_content, None).unwrap();
837
838 let mut cursor = tree_sitter::QueryCursor::new();
839
840 let query = Query::new(
841 tree_sitter_json::language(),
842 "
843 (pair
844 key: (string) @key
845 value: (_) @value)
846 ",
847 )
848 .unwrap();
849
850 let mut depth = 0;
851 let mut first_key_start = None;
852 let mut existing_value_range = 0..settings_content.len();
853 let matches = cursor.matches(&query, tree.root_node(), settings_content.as_bytes());
854 for mat in matches {
855 if mat.captures.len() != 2 {
856 continue;
857 }
858
859 let key_range = mat.captures[0].node.byte_range();
860 let value_range = mat.captures[1].node.byte_range();
861
862 if key_range.start > existing_value_range.end {
863 break;
864 }
865
866 first_key_start.get_or_insert_with(|| key_range.start);
867
868 let found_key = settings_content
869 .get(key_range.clone())
870 .map(|key_text| key_text == format!("\"{}\"", key_path[depth]))
871 .unwrap_or(false);
872
873 if found_key {
874 existing_value_range = value_range;
875 depth += 1;
876
877 if depth == key_path.len() {
878 break;
879 } else {
880 first_key_start = None;
881 }
882 }
883 }
884
885 // We found the exact key we want, insert the new value
886 if depth == key_path.len() {
887 let new_val = serde_json::to_string_pretty(new_value)
888 .expect("Could not serialize new json field to string");
889 settings_content.replace_range(existing_value_range, &new_val);
890 } else {
891 // We have key paths, construct the sub objects
892 let new_key = key_path[depth];
893
894 // We don't have the key, construct the nested objects
895 let mut new_value = serde_json::to_value(new_value).unwrap();
896 for key in key_path[(depth + 1)..].iter().rev() {
897 new_value = serde_json::json!({ key.to_string(): new_value });
898 }
899
900 if let Some(first_key_start) = first_key_start {
901 let mut row = 0;
902 let mut column = 0;
903 for (ix, char) in settings_content.char_indices() {
904 if ix == first_key_start {
905 break;
906 }
907 if char == '\n' {
908 row += 1;
909 column = 0;
910 } else {
911 column += char.len_utf8();
912 }
913 }
914
915 if row > 0 {
916 let new_val = to_pretty_json(&new_value, column, column);
917 let content = format!(r#""{new_key}": {new_val},"#);
918 settings_content.insert_str(first_key_start, &content);
919
920 settings_content.insert_str(
921 first_key_start + content.len(),
922 &format!("\n{:width$}", ' ', width = column),
923 )
924 } else {
925 let new_val = serde_json::to_string(&new_value).unwrap();
926 let mut content = format!(r#""{new_key}": {new_val},"#);
927 content.push(' ');
928 settings_content.insert_str(first_key_start, &content);
929 }
930 } else {
931 new_value = serde_json::json!({ new_key.to_string(): new_value });
932 let indent_prefix_len = 4 * depth;
933 let new_val = to_pretty_json(&new_value, 4, indent_prefix_len);
934
935 settings_content.replace_range(existing_value_range, &new_val);
936 if depth == 0 {
937 settings_content.push('\n');
938 }
939 }
940 }
941}
942
943fn to_pretty_json(
944 value: &serde_json::Value,
945 indent_size: usize,
946 indent_prefix_len: usize,
947) -> String {
948 const SPACES: [u8; 32] = [b' '; 32];
949
950 debug_assert!(indent_size <= SPACES.len());
951 debug_assert!(indent_prefix_len <= SPACES.len());
952
953 let mut output = Vec::new();
954 let mut ser = serde_json::Serializer::with_formatter(
955 &mut output,
956 serde_json::ser::PrettyFormatter::with_indent(&SPACES[0..indent_size.min(SPACES.len())]),
957 );
958
959 value.serialize(&mut ser).unwrap();
960 let text = String::from_utf8(output).unwrap();
961
962 let mut adjusted_text = String::new();
963 for (i, line) in text.split('\n').enumerate() {
964 if i > 0 {
965 adjusted_text.push_str(str::from_utf8(&SPACES[0..indent_prefix_len]).unwrap());
966 }
967 adjusted_text.push_str(line);
968 adjusted_text.push('\n');
969 }
970 adjusted_text.pop();
971 adjusted_text
972}
973
974pub fn update_settings_file(
975 mut text: String,
976 old_file_content: SettingsFileContent,
977 update: impl FnOnce(&mut SettingsFileContent),
978) -> String {
979 let mut new_file_content = old_file_content.clone();
980
981 update(&mut new_file_content);
982
983 let old_object = to_json_object(old_file_content);
984 let new_object = to_json_object(new_file_content);
985
986 fn apply_changes_to_json_text(
987 old_object: &serde_json::Map<String, Value>,
988 new_object: &serde_json::Map<String, Value>,
989 current_key_path: Vec<&str>,
990 json_text: &mut String,
991 ) {
992 for (key, old_value) in old_object.iter() {
993 // We know that these two are from the same shape of object, so we can just unwrap
994 let new_value = new_object.get(key).unwrap();
995 if old_value != new_value {
996 match new_value {
997 Value::Bool(_) | Value::Number(_) | Value::String(_) => {
998 let mut key_path = current_key_path.clone();
999 key_path.push(key);
1000 write_settings_key(json_text, &key_path, &new_value);
1001 }
1002 Value::Object(new_sub_object) => {
1003 let mut key_path = current_key_path.clone();
1004 key_path.push(key);
1005 if let Value::Object(old_sub_object) = old_value {
1006 apply_changes_to_json_text(
1007 old_sub_object,
1008 new_sub_object,
1009 key_path,
1010 json_text,
1011 );
1012 } else {
1013 unimplemented!("This function doesn't support changing values from simple values to objects yet");
1014 }
1015 }
1016 Value::Null | Value::Array(_) => {
1017 unimplemented!("We only support objects and simple values");
1018 }
1019 }
1020 }
1021 }
1022 }
1023
1024 apply_changes_to_json_text(&old_object, &new_object, vec![], &mut text);
1025
1026 text
1027}
1028
1029fn to_json_object(settings_file: SettingsFileContent) -> serde_json::Map<String, Value> {
1030 let tmp = serde_json::to_value(settings_file).unwrap();
1031 match tmp {
1032 Value::Object(map) => map,
1033 _ => unreachable!("SettingsFileContent represents a JSON map"),
1034 }
1035}
1036
1037#[cfg(test)]
1038mod tests {
1039 use super::*;
1040 use unindent::Unindent;
1041
1042 fn assert_new_settings<S1: Into<String>, S2: Into<String>>(
1043 old_json: S1,
1044 update: fn(&mut SettingsFileContent),
1045 expected_new_json: S2,
1046 ) {
1047 let old_json = old_json.into();
1048 let old_content: SettingsFileContent = serde_json::from_str(&old_json).unwrap_or_default();
1049 let new_json = update_settings_file(old_json, old_content, update);
1050 assert_eq!(new_json, expected_new_json.into());
1051 }
1052
1053 #[test]
1054 fn test_update_telemetry_setting_multiple_fields() {
1055 assert_new_settings(
1056 r#"
1057 {
1058 "telemetry": {
1059 "metrics": false,
1060 "diagnostics": false
1061 }
1062 }
1063 "#
1064 .unindent(),
1065 |settings| {
1066 settings.telemetry.set_diagnostics(true);
1067 settings.telemetry.set_metrics(true);
1068 },
1069 r#"
1070 {
1071 "telemetry": {
1072 "metrics": true,
1073 "diagnostics": true
1074 }
1075 }
1076 "#
1077 .unindent(),
1078 );
1079 }
1080
1081 #[test]
1082 fn test_update_telemetry_setting_weird_formatting() {
1083 assert_new_settings(
1084 r#"{
1085 "telemetry": { "metrics": false, "diagnostics": true }
1086 }"#
1087 .unindent(),
1088 |settings| settings.telemetry.set_diagnostics(false),
1089 r#"{
1090 "telemetry": { "metrics": false, "diagnostics": false }
1091 }"#
1092 .unindent(),
1093 );
1094 }
1095
1096 #[test]
1097 fn test_update_telemetry_setting_other_fields() {
1098 assert_new_settings(
1099 r#"
1100 {
1101 "telemetry": {
1102 "metrics": false,
1103 "diagnostics": true
1104 }
1105 }
1106 "#
1107 .unindent(),
1108 |settings| settings.telemetry.set_diagnostics(false),
1109 r#"
1110 {
1111 "telemetry": {
1112 "metrics": false,
1113 "diagnostics": false
1114 }
1115 }
1116 "#
1117 .unindent(),
1118 );
1119 }
1120
1121 #[test]
1122 fn test_update_telemetry_setting_empty_telemetry() {
1123 assert_new_settings(
1124 r#"
1125 {
1126 "telemetry": {}
1127 }
1128 "#
1129 .unindent(),
1130 |settings| settings.telemetry.set_diagnostics(false),
1131 r#"
1132 {
1133 "telemetry": {
1134 "diagnostics": false
1135 }
1136 }
1137 "#
1138 .unindent(),
1139 );
1140 }
1141
1142 #[test]
1143 fn test_update_telemetry_setting_pre_existing() {
1144 assert_new_settings(
1145 r#"
1146 {
1147 "telemetry": {
1148 "diagnostics": true
1149 }
1150 }
1151 "#
1152 .unindent(),
1153 |settings| settings.telemetry.set_diagnostics(false),
1154 r#"
1155 {
1156 "telemetry": {
1157 "diagnostics": false
1158 }
1159 }
1160 "#
1161 .unindent(),
1162 );
1163 }
1164
1165 #[test]
1166 fn test_update_telemetry_setting() {
1167 assert_new_settings(
1168 "{}",
1169 |settings| settings.telemetry.set_diagnostics(true),
1170 r#"
1171 {
1172 "telemetry": {
1173 "diagnostics": true
1174 }
1175 }
1176 "#
1177 .unindent(),
1178 );
1179 }
1180
1181 #[test]
1182 fn test_update_object_empty_doc() {
1183 assert_new_settings(
1184 "",
1185 |settings| settings.telemetry.set_diagnostics(true),
1186 r#"
1187 {
1188 "telemetry": {
1189 "diagnostics": true
1190 }
1191 }
1192 "#
1193 .unindent(),
1194 );
1195 }
1196
1197 #[test]
1198 fn test_write_theme_into_settings_with_theme() {
1199 assert_new_settings(
1200 r#"
1201 {
1202 "theme": "One Dark"
1203 }
1204 "#
1205 .unindent(),
1206 |settings| settings.theme = Some("summerfruit-light".to_string()),
1207 r#"
1208 {
1209 "theme": "summerfruit-light"
1210 }
1211 "#
1212 .unindent(),
1213 );
1214 }
1215
1216 #[test]
1217 fn test_write_theme_into_empty_settings() {
1218 assert_new_settings(
1219 r#"
1220 {
1221 }
1222 "#
1223 .unindent(),
1224 |settings| settings.theme = Some("summerfruit-light".to_string()),
1225 r#"
1226 {
1227 "theme": "summerfruit-light"
1228 }
1229 "#
1230 .unindent(),
1231 );
1232 }
1233
1234 #[test]
1235 fn write_key_no_document() {
1236 assert_new_settings(
1237 "",
1238 |settings| settings.theme = Some("summerfruit-light".to_string()),
1239 r#"
1240 {
1241 "theme": "summerfruit-light"
1242 }
1243 "#
1244 .unindent(),
1245 );
1246 }
1247
1248 #[test]
1249 fn test_write_theme_into_single_line_settings_without_theme() {
1250 assert_new_settings(
1251 r#"{ "a": "", "ok": true }"#,
1252 |settings| settings.theme = Some("summerfruit-light".to_string()),
1253 r#"{ "theme": "summerfruit-light", "a": "", "ok": true }"#,
1254 );
1255 }
1256
1257 #[test]
1258 fn test_write_theme_pre_object_whitespace() {
1259 assert_new_settings(
1260 r#" { "a": "", "ok": true }"#,
1261 |settings| settings.theme = Some("summerfruit-light".to_string()),
1262 r#" { "theme": "summerfruit-light", "a": "", "ok": true }"#.unindent(),
1263 );
1264 }
1265
1266 #[test]
1267 fn test_write_theme_into_multi_line_settings_without_theme() {
1268 assert_new_settings(
1269 r#"
1270 {
1271 "a": "b"
1272 }
1273 "#
1274 .unindent(),
1275 |settings| settings.theme = Some("summerfruit-light".to_string()),
1276 r#"
1277 {
1278 "theme": "summerfruit-light",
1279 "a": "b"
1280 }
1281 "#
1282 .unindent(),
1283 );
1284 }
1285}