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::{RangeExt, 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 pub fn as_bool(&self) -> bool {
192 match self {
193 OnOff::On => true,
194 OnOff::Off => false,
195 }
196 }
197
198 pub fn from_bool(value: bool) -> OnOff {
199 match value {
200 true => OnOff::On,
201 false => OnOff::Off,
202 }
203 }
204}
205
206impl From<OnOff> for bool {
207 fn from(value: OnOff) -> bool {
208 value.as_bool()
209 }
210}
211
212impl From<bool> for OnOff {
213 fn from(value: bool) -> OnOff {
214 OnOff::from_bool(value)
215 }
216}
217
218#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
219#[serde(rename_all = "snake_case")]
220pub enum SoftWrap {
221 None,
222 EditorWidth,
223 PreferredLineLength,
224}
225#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
226#[serde(rename_all = "snake_case")]
227pub enum FormatOnSave {
228 On,
229 Off,
230 LanguageServer,
231 External {
232 command: String,
233 arguments: Vec<String>,
234 },
235}
236
237#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
238#[serde(rename_all = "snake_case")]
239pub enum Formatter {
240 LanguageServer,
241 External {
242 command: String,
243 arguments: Vec<String>,
244 },
245}
246
247#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
248#[serde(rename_all = "snake_case")]
249pub enum Autosave {
250 Off,
251 AfterDelay { milliseconds: u64 },
252 OnFocusChange,
253 OnWindowChange,
254}
255
256#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
257pub struct JournalSettings {
258 pub path: Option<String>,
259 pub hour_format: Option<HourFormat>,
260}
261
262impl Default for JournalSettings {
263 fn default() -> Self {
264 Self {
265 path: Some("~".into()),
266 hour_format: Some(Default::default()),
267 }
268 }
269}
270
271#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
272#[serde(rename_all = "snake_case")]
273pub enum HourFormat {
274 Hour12,
275 Hour24,
276}
277
278impl Default for HourFormat {
279 fn default() -> Self {
280 Self::Hour12
281 }
282}
283
284#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
285pub struct TerminalSettings {
286 pub shell: Option<Shell>,
287 pub working_directory: Option<WorkingDirectory>,
288 pub font_size: Option<f32>,
289 pub font_family: Option<String>,
290 pub font_features: Option<fonts::Features>,
291 pub env: Option<HashMap<String, String>>,
292 pub blinking: Option<TerminalBlink>,
293 pub alternate_scroll: Option<AlternateScroll>,
294 pub option_as_meta: Option<bool>,
295 pub copy_on_select: Option<bool>,
296}
297
298#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
299#[serde(rename_all = "snake_case")]
300pub enum TerminalBlink {
301 Off,
302 TerminalControlled,
303 On,
304}
305
306impl Default for TerminalBlink {
307 fn default() -> Self {
308 TerminalBlink::TerminalControlled
309 }
310}
311
312#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
313#[serde(rename_all = "snake_case")]
314pub enum Shell {
315 System,
316 Program(String),
317 WithArguments { program: String, args: Vec<String> },
318}
319
320impl Default for Shell {
321 fn default() -> Self {
322 Shell::System
323 }
324}
325
326#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
327#[serde(rename_all = "snake_case")]
328pub enum AlternateScroll {
329 On,
330 Off,
331}
332
333impl Default for AlternateScroll {
334 fn default() -> Self {
335 AlternateScroll::On
336 }
337}
338
339#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
340#[serde(rename_all = "snake_case")]
341pub enum WorkingDirectory {
342 CurrentProjectDirectory,
343 FirstProjectDirectory,
344 AlwaysHome,
345 Always { directory: String },
346}
347
348impl Default for WorkingDirectory {
349 fn default() -> Self {
350 Self::CurrentProjectDirectory
351 }
352}
353
354#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)]
355#[serde(rename_all = "snake_case")]
356pub enum DockAnchor {
357 #[default]
358 Bottom,
359 Right,
360 Expanded,
361}
362
363impl StaticColumnCount for DockAnchor {}
364impl Bind for DockAnchor {
365 fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
366 match self {
367 DockAnchor::Bottom => "Bottom",
368 DockAnchor::Right => "Right",
369 DockAnchor::Expanded => "Expanded",
370 }
371 .bind(statement, start_index)
372 }
373}
374
375impl Column for DockAnchor {
376 fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
377 String::column(statement, start_index).and_then(|(anchor_text, next_index)| {
378 Ok((
379 match anchor_text.as_ref() {
380 "Bottom" => DockAnchor::Bottom,
381 "Right" => DockAnchor::Right,
382 "Expanded" => DockAnchor::Expanded,
383 _ => bail!("Stored dock anchor is incorrect"),
384 },
385 next_index,
386 ))
387 })
388 }
389}
390
391#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
392pub struct SettingsFileContent {
393 #[serde(default)]
394 pub buffer_font_family: Option<String>,
395 #[serde(default)]
396 pub buffer_font_size: Option<f32>,
397 #[serde(default)]
398 pub buffer_font_features: Option<fonts::Features>,
399 #[serde(default)]
400 pub active_pane_magnification: Option<f32>,
401 #[serde(default)]
402 pub cursor_blink: Option<bool>,
403 #[serde(default)]
404 pub confirm_quit: Option<bool>,
405 #[serde(default)]
406 pub hover_popover_enabled: Option<bool>,
407 #[serde(default)]
408 pub show_completions_on_input: Option<bool>,
409 #[serde(default)]
410 pub show_call_status_icon: Option<bool>,
411 #[serde(default)]
412 pub vim_mode: Option<bool>,
413 #[serde(default)]
414 pub autosave: Option<Autosave>,
415 #[serde(default)]
416 pub default_dock_anchor: Option<DockAnchor>,
417 #[serde(flatten)]
418 pub editor: EditorSettings,
419 #[serde(default)]
420 pub journal: JournalSettings,
421 #[serde(default)]
422 pub terminal: TerminalSettings,
423 #[serde(default)]
424 pub git: Option<GitSettings>,
425 #[serde(default)]
426 #[serde(alias = "language_overrides")]
427 pub languages: HashMap<Arc<str>, EditorSettings>,
428 #[serde(default)]
429 pub lsp: HashMap<Arc<str>, LspSettings>,
430 #[serde(default)]
431 pub theme: Option<String>,
432 #[serde(default)]
433 pub telemetry: TelemetrySettings,
434 #[serde(default)]
435 pub auto_update: Option<bool>,
436 #[serde(default)]
437 pub base_keymap: Option<BaseKeymap>,
438 #[serde(default)]
439 pub enable_copilot_integration: Option<bool>,
440}
441
442#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
443#[serde(rename_all = "snake_case")]
444pub struct LspSettings {
445 pub initialization_options: Option<Value>,
446}
447
448impl Settings {
449 /// Fill out the settings corresponding to the default.json file, overrides will be set later
450 pub fn defaults(
451 assets: impl AssetSource,
452 font_cache: &FontCache,
453 themes: &ThemeRegistry,
454 ) -> Self {
455 #[track_caller]
456 fn required<T>(value: Option<T>) -> Option<T> {
457 assert!(value.is_some(), "missing default setting value");
458 value
459 }
460
461 let defaults: SettingsFileContent = parse_json_with_comments(
462 str::from_utf8(assets.load("settings/default.json").unwrap().as_ref()).unwrap(),
463 )
464 .unwrap();
465
466 let buffer_font_features = defaults.buffer_font_features.unwrap();
467 Self {
468 buffer_font_family: font_cache
469 .load_family(
470 &[defaults.buffer_font_family.as_ref().unwrap()],
471 &buffer_font_features,
472 )
473 .unwrap(),
474 buffer_font_family_name: defaults.buffer_font_family.unwrap(),
475 buffer_font_features,
476 buffer_font_size: defaults.buffer_font_size.unwrap(),
477 active_pane_magnification: defaults.active_pane_magnification.unwrap(),
478 default_buffer_font_size: defaults.buffer_font_size.unwrap(),
479 confirm_quit: defaults.confirm_quit.unwrap(),
480 cursor_blink: defaults.cursor_blink.unwrap(),
481 hover_popover_enabled: defaults.hover_popover_enabled.unwrap(),
482 show_completions_on_input: defaults.show_completions_on_input.unwrap(),
483 show_call_status_icon: defaults.show_call_status_icon.unwrap(),
484 vim_mode: defaults.vim_mode.unwrap(),
485 autosave: defaults.autosave.unwrap(),
486 default_dock_anchor: defaults.default_dock_anchor.unwrap(),
487 editor_defaults: EditorSettings {
488 tab_size: required(defaults.editor.tab_size),
489 hard_tabs: required(defaults.editor.hard_tabs),
490 soft_wrap: required(defaults.editor.soft_wrap),
491 preferred_line_length: required(defaults.editor.preferred_line_length),
492 remove_trailing_whitespace_on_save: required(
493 defaults.editor.remove_trailing_whitespace_on_save,
494 ),
495 ensure_final_newline_on_save: required(
496 defaults.editor.ensure_final_newline_on_save,
497 ),
498 format_on_save: required(defaults.editor.format_on_save),
499 formatter: required(defaults.editor.formatter),
500 enable_language_server: required(defaults.editor.enable_language_server),
501 copilot: required(defaults.editor.copilot),
502 },
503 editor_overrides: Default::default(),
504 git: defaults.git.unwrap(),
505 git_overrides: Default::default(),
506 journal_defaults: defaults.journal,
507 journal_overrides: Default::default(),
508 terminal_defaults: defaults.terminal,
509 terminal_overrides: Default::default(),
510 language_defaults: defaults.languages,
511 language_overrides: Default::default(),
512 lsp: defaults.lsp.clone(),
513 theme: themes.get(&defaults.theme.unwrap()).unwrap(),
514 telemetry_defaults: defaults.telemetry,
515 telemetry_overrides: Default::default(),
516 auto_update: defaults.auto_update.unwrap(),
517 base_keymap: Default::default(),
518 enable_copilot_integration: defaults.enable_copilot_integration.unwrap(),
519 }
520 }
521
522 // Fill out the overrride and etc. settings from the user's settings.json
523 pub 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.default_buffer_font_size, data.buffer_font_size);
559 merge(&mut self.cursor_blink, data.cursor_blink);
560 merge(&mut self.confirm_quit, data.confirm_quit);
561 merge(&mut self.hover_popover_enabled, data.hover_popover_enabled);
562 merge(
563 &mut self.show_completions_on_input,
564 data.show_completions_on_input,
565 );
566 merge(&mut self.vim_mode, data.vim_mode);
567 merge(&mut self.autosave, data.autosave);
568 merge(&mut self.default_dock_anchor, data.default_dock_anchor);
569 merge(&mut self.base_keymap, data.base_keymap);
570 merge(
571 &mut self.enable_copilot_integration,
572 data.enable_copilot_integration,
573 );
574
575 self.editor_overrides = data.editor;
576 self.git_overrides = data.git.unwrap_or_default();
577 self.journal_overrides = data.journal;
578 self.terminal_defaults.font_size = data.terminal.font_size;
579 self.terminal_overrides.copy_on_select = data.terminal.copy_on_select;
580 self.terminal_overrides = data.terminal;
581 self.language_overrides = data.languages;
582 self.telemetry_overrides = data.telemetry;
583 self.lsp = data.lsp;
584 merge(&mut self.auto_update, data.auto_update);
585 }
586
587 pub fn with_language_defaults(
588 mut self,
589 language_name: impl Into<Arc<str>>,
590 overrides: EditorSettings,
591 ) -> Self {
592 self.language_defaults
593 .insert(language_name.into(), overrides);
594 self
595 }
596
597 pub fn copilot_on(&self, language: Option<&str>) -> bool {
598 if self.enable_copilot_integration {
599 self.language_setting(language, |settings| settings.copilot.map(Into::into))
600 } else {
601 false
602 }
603 }
604
605 pub fn tab_size(&self, language: Option<&str>) -> NonZeroU32 {
606 self.language_setting(language, |settings| settings.tab_size)
607 }
608
609 pub fn hard_tabs(&self, language: Option<&str>) -> bool {
610 self.language_setting(language, |settings| settings.hard_tabs)
611 }
612
613 pub fn soft_wrap(&self, language: Option<&str>) -> SoftWrap {
614 self.language_setting(language, |settings| settings.soft_wrap)
615 }
616
617 pub fn preferred_line_length(&self, language: Option<&str>) -> u32 {
618 self.language_setting(language, |settings| settings.preferred_line_length)
619 }
620
621 pub fn remove_trailing_whitespace_on_save(&self, language: Option<&str>) -> bool {
622 self.language_setting(language, |settings| {
623 settings.remove_trailing_whitespace_on_save.clone()
624 })
625 }
626
627 pub fn ensure_final_newline_on_save(&self, language: Option<&str>) -> bool {
628 self.language_setting(language, |settings| {
629 settings.ensure_final_newline_on_save.clone()
630 })
631 }
632
633 pub fn format_on_save(&self, language: Option<&str>) -> FormatOnSave {
634 self.language_setting(language, |settings| settings.format_on_save.clone())
635 }
636
637 pub fn formatter(&self, language: Option<&str>) -> Formatter {
638 self.language_setting(language, |settings| settings.formatter.clone())
639 }
640
641 pub fn enable_language_server(&self, language: Option<&str>) -> bool {
642 self.language_setting(language, |settings| settings.enable_language_server)
643 }
644
645 fn language_setting<F, R>(&self, language: Option<&str>, f: F) -> R
646 where
647 F: Fn(&EditorSettings) -> Option<R>,
648 {
649 None.or_else(|| language.and_then(|l| self.language_overrides.get(l).and_then(&f)))
650 .or_else(|| f(&self.editor_overrides))
651 .or_else(|| language.and_then(|l| self.language_defaults.get(l).and_then(&f)))
652 .or_else(|| f(&self.editor_defaults))
653 .expect("missing default")
654 }
655
656 pub fn git_gutter(&self) -> GitGutter {
657 self.git_overrides.git_gutter.unwrap_or_else(|| {
658 self.git
659 .git_gutter
660 .expect("git_gutter should be some by setting setup")
661 })
662 }
663
664 fn terminal_setting<F, R: Default + Clone>(&self, f: F) -> R
665 where
666 F: Fn(&TerminalSettings) -> Option<&R>,
667 {
668 f(&self.terminal_overrides)
669 .or_else(|| f(&self.terminal_defaults))
670 .cloned()
671 .unwrap_or_else(|| R::default())
672 }
673
674 pub fn telemetry(&self) -> TelemetrySettings {
675 TelemetrySettings {
676 diagnostics: Some(self.telemetry_diagnostics()),
677 metrics: Some(self.telemetry_metrics()),
678 }
679 }
680
681 pub fn telemetry_diagnostics(&self) -> bool {
682 self.telemetry_overrides
683 .diagnostics
684 .or(self.telemetry_defaults.diagnostics)
685 .expect("missing default")
686 }
687
688 pub fn telemetry_metrics(&self) -> bool {
689 self.telemetry_overrides
690 .metrics
691 .or(self.telemetry_defaults.metrics)
692 .expect("missing default")
693 }
694
695 pub fn terminal_scroll(&self) -> AlternateScroll {
696 self.terminal_setting(|terminal_setting| terminal_setting.alternate_scroll.as_ref())
697 }
698
699 pub fn terminal_shell(&self) -> Shell {
700 self.terminal_setting(|terminal_setting| terminal_setting.shell.as_ref())
701 }
702
703 pub fn terminal_env(&self) -> HashMap<String, String> {
704 self.terminal_setting(|terminal_setting| terminal_setting.env.as_ref())
705 }
706
707 pub fn terminal_strategy(&self) -> WorkingDirectory {
708 self.terminal_setting(|terminal_setting| terminal_setting.working_directory.as_ref())
709 }
710
711 #[cfg(any(test, feature = "test-support"))]
712 pub fn test(cx: &gpui::AppContext) -> Settings {
713 Settings {
714 buffer_font_family_name: "Monaco".to_string(),
715 buffer_font_features: Default::default(),
716 buffer_font_family: cx
717 .font_cache()
718 .load_family(&["Monaco"], &Default::default())
719 .unwrap(),
720 buffer_font_size: 14.,
721 active_pane_magnification: 1.,
722 default_buffer_font_size: 14.,
723 confirm_quit: false,
724 cursor_blink: true,
725 hover_popover_enabled: true,
726 show_completions_on_input: true,
727 show_call_status_icon: true,
728 vim_mode: false,
729 autosave: Autosave::Off,
730 default_dock_anchor: DockAnchor::Bottom,
731 editor_defaults: EditorSettings {
732 tab_size: Some(4.try_into().unwrap()),
733 hard_tabs: Some(false),
734 soft_wrap: Some(SoftWrap::None),
735 preferred_line_length: Some(80),
736 remove_trailing_whitespace_on_save: Some(true),
737 ensure_final_newline_on_save: Some(true),
738 format_on_save: Some(FormatOnSave::On),
739 formatter: Some(Formatter::LanguageServer),
740 enable_language_server: Some(true),
741 copilot: Some(OnOff::On),
742 },
743 editor_overrides: Default::default(),
744 journal_defaults: Default::default(),
745 journal_overrides: Default::default(),
746 terminal_defaults: Default::default(),
747 terminal_overrides: Default::default(),
748 git: Default::default(),
749 git_overrides: Default::default(),
750 language_defaults: Default::default(),
751 language_overrides: Default::default(),
752 lsp: Default::default(),
753 theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default),
754 telemetry_defaults: TelemetrySettings {
755 diagnostics: Some(true),
756 metrics: Some(true),
757 },
758 telemetry_overrides: Default::default(),
759 auto_update: true,
760 base_keymap: Default::default(),
761 enable_copilot_integration: true,
762 }
763 }
764
765 #[cfg(any(test, feature = "test-support"))]
766 pub fn test_async(cx: &mut gpui::TestAppContext) {
767 cx.update(|cx| {
768 let settings = Self::test(cx);
769 cx.set_global(settings);
770 });
771 }
772}
773
774pub fn settings_file_json_schema(
775 theme_names: Vec<String>,
776 language_names: &[String],
777) -> serde_json::Value {
778 let settings = SchemaSettings::draft07().with(|settings| {
779 settings.option_add_null_type = false;
780 });
781 let generator = SchemaGenerator::new(settings);
782 let mut root_schema = generator.into_root_schema_for::<SettingsFileContent>();
783
784 // Create a schema for a theme name.
785 let theme_name_schema = SchemaObject {
786 instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
787 enum_values: Some(theme_names.into_iter().map(Value::String).collect()),
788 ..Default::default()
789 };
790
791 // Create a schema for a 'languages overrides' object, associating editor
792 // settings with specific langauges.
793 assert!(root_schema.definitions.contains_key("EditorSettings"));
794 let languages_object_schema = SchemaObject {
795 instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
796 object: Some(Box::new(ObjectValidation {
797 properties: language_names
798 .iter()
799 .map(|name| {
800 (
801 name.clone(),
802 Schema::new_ref("#/definitions/EditorSettings".into()),
803 )
804 })
805 .collect(),
806 ..Default::default()
807 })),
808 ..Default::default()
809 };
810
811 // Add these new schemas as definitions, and modify properties of the root
812 // schema to reference them.
813 root_schema.definitions.extend([
814 ("ThemeName".into(), theme_name_schema.into()),
815 ("Languages".into(), languages_object_schema.into()),
816 ]);
817 let root_schema_object = &mut root_schema.schema.object.as_mut().unwrap();
818
819 root_schema_object.properties.extend([
820 (
821 "theme".to_owned(),
822 Schema::new_ref("#/definitions/ThemeName".into()),
823 ),
824 (
825 "languages".to_owned(),
826 Schema::new_ref("#/definitions/Languages".into()),
827 ),
828 // For backward compatibility
829 (
830 "language_overrides".to_owned(),
831 Schema::new_ref("#/definitions/Languages".into()),
832 ),
833 ]);
834
835 serde_json::to_value(root_schema).unwrap()
836}
837
838fn merge<T: Copy>(target: &mut T, value: Option<T>) {
839 if let Some(value) = value {
840 *target = value;
841 }
842}
843
844pub fn parse_json_with_comments<T: DeserializeOwned>(content: &str) -> Result<T> {
845 Ok(serde_json::from_reader(
846 json_comments::CommentSettings::c_style().strip_comments(content.as_bytes()),
847 )?)
848}
849
850fn write_settings_key(settings_content: &mut String, key_path: &[&str], new_value: &Value) {
851 const LANGUAGE_OVERRIDES: &'static str = "language_overrides";
852 const LANGAUGES: &'static str = "languages";
853
854 let mut parser = tree_sitter::Parser::new();
855 parser.set_language(tree_sitter_json::language()).unwrap();
856 let tree = parser.parse(&settings_content, None).unwrap();
857
858 let mut cursor = tree_sitter::QueryCursor::new();
859
860 let query = Query::new(
861 tree_sitter_json::language(),
862 "
863 (pair
864 key: (string) @key
865 value: (_) @value)
866 ",
867 )
868 .unwrap();
869
870 let has_language_overrides = settings_content.contains(LANGUAGE_OVERRIDES);
871
872 let mut depth = 0;
873 let mut last_value_range = 0..0;
874 let mut first_key_start = None;
875 let mut existing_value_range = 0..settings_content.len();
876 let matches = cursor.matches(&query, tree.root_node(), settings_content.as_bytes());
877 for mat in matches {
878 if mat.captures.len() != 2 {
879 continue;
880 }
881
882 let key_range = mat.captures[0].node.byte_range();
883 let value_range = mat.captures[1].node.byte_range();
884
885 // Don't enter sub objects until we find an exact
886 // match for the current keypath
887 if last_value_range.contains_inclusive(&value_range) {
888 continue;
889 }
890
891 last_value_range = value_range.clone();
892
893 if key_range.start > existing_value_range.end {
894 break;
895 }
896
897 first_key_start.get_or_insert_with(|| key_range.start);
898
899 let found_key = settings_content
900 .get(key_range.clone())
901 .map(|key_text| {
902 if key_path[depth] == LANGAUGES && has_language_overrides {
903 return key_text == format!("\"{}\"", LANGUAGE_OVERRIDES);
904 } else {
905 return key_text == format!("\"{}\"", key_path[depth]);
906 }
907 })
908 .unwrap_or(false);
909
910 if found_key {
911 existing_value_range = value_range;
912 // Reset last value range when increasing in depth
913 last_value_range = existing_value_range.start..existing_value_range.start;
914 depth += 1;
915
916 if depth == key_path.len() {
917 break;
918 } else {
919 first_key_start = None;
920 }
921 }
922 }
923
924 // We found the exact key we want, insert the new value
925 if depth == key_path.len() {
926 let new_val = serde_json::to_string_pretty(new_value)
927 .expect("Could not serialize new json field to string");
928 settings_content.replace_range(existing_value_range, &new_val);
929 } else {
930 // We have key paths, construct the sub objects
931 let new_key = if has_language_overrides && key_path[depth] == LANGAUGES {
932 LANGUAGE_OVERRIDES
933 } else {
934 key_path[depth]
935 };
936
937 // We don't have the key, construct the nested objects
938 let mut new_value = serde_json::to_value(new_value).unwrap();
939 for key in key_path[(depth + 1)..].iter().rev() {
940 if has_language_overrides && key == &LANGAUGES {
941 new_value = serde_json::json!({ LANGUAGE_OVERRIDES.to_string(): new_value });
942 } else {
943 new_value = serde_json::json!({ key.to_string(): new_value });
944 }
945 }
946
947 if let Some(first_key_start) = first_key_start {
948 let mut row = 0;
949 let mut column = 0;
950 for (ix, char) in settings_content.char_indices() {
951 if ix == first_key_start {
952 break;
953 }
954 if char == '\n' {
955 row += 1;
956 column = 0;
957 } else {
958 column += char.len_utf8();
959 }
960 }
961
962 if row > 0 {
963 // depth is 0 based, but division needs to be 1 based.
964 let new_val = to_pretty_json(&new_value, column / (depth + 1), column);
965 let content = format!(r#""{new_key}": {new_val},"#);
966 settings_content.insert_str(first_key_start, &content);
967
968 settings_content.insert_str(
969 first_key_start + content.len(),
970 &format!("\n{:width$}", ' ', width = column),
971 )
972 } else {
973 let new_val = serde_json::to_string(&new_value).unwrap();
974 let mut content = format!(r#""{new_key}": {new_val},"#);
975 content.push(' ');
976 settings_content.insert_str(first_key_start, &content);
977 }
978 } else {
979 new_value = serde_json::json!({ new_key.to_string(): new_value });
980 let indent_prefix_len = 4 * depth;
981 let new_val = to_pretty_json(&new_value, 4, indent_prefix_len);
982
983 settings_content.replace_range(existing_value_range, &new_val);
984 if depth == 0 {
985 settings_content.push('\n');
986 }
987 }
988 }
989}
990
991fn to_pretty_json(
992 value: &serde_json::Value,
993 indent_size: usize,
994 indent_prefix_len: usize,
995) -> String {
996 const SPACES: [u8; 32] = [b' '; 32];
997
998 debug_assert!(indent_size <= SPACES.len());
999 debug_assert!(indent_prefix_len <= SPACES.len());
1000
1001 let mut output = Vec::new();
1002 let mut ser = serde_json::Serializer::with_formatter(
1003 &mut output,
1004 serde_json::ser::PrettyFormatter::with_indent(&SPACES[0..indent_size.min(SPACES.len())]),
1005 );
1006
1007 value.serialize(&mut ser).unwrap();
1008 let text = String::from_utf8(output).unwrap();
1009
1010 let mut adjusted_text = String::new();
1011 for (i, line) in text.split('\n').enumerate() {
1012 if i > 0 {
1013 adjusted_text.push_str(str::from_utf8(&SPACES[0..indent_prefix_len]).unwrap());
1014 }
1015 adjusted_text.push_str(line);
1016 adjusted_text.push('\n');
1017 }
1018 adjusted_text.pop();
1019 adjusted_text
1020}
1021
1022pub fn update_settings_file(
1023 mut text: String,
1024 mut old_file_content: SettingsFileContent,
1025 update: impl FnOnce(&mut SettingsFileContent),
1026) -> String {
1027 let mut new_file_content = old_file_content.clone();
1028
1029 update(&mut new_file_content);
1030
1031 if new_file_content.languages.len() != old_file_content.languages.len() {
1032 for language in new_file_content.languages.keys() {
1033 old_file_content
1034 .languages
1035 .entry(language.clone())
1036 .or_default();
1037 }
1038 for language in old_file_content.languages.keys() {
1039 new_file_content
1040 .languages
1041 .entry(language.clone())
1042 .or_default();
1043 }
1044 }
1045
1046 let old_object = to_json_object(old_file_content);
1047 let new_object = to_json_object(new_file_content);
1048
1049 fn apply_changes_to_json_text(
1050 old_object: &serde_json::Map<String, Value>,
1051 new_object: &serde_json::Map<String, Value>,
1052 current_key_path: Vec<&str>,
1053 json_text: &mut String,
1054 ) {
1055 for (key, old_value) in old_object.iter() {
1056 // We know that these two are from the same shape of object, so we can just unwrap
1057 let new_value = new_object.get(key).unwrap();
1058
1059 if old_value != new_value {
1060 match new_value {
1061 Value::Bool(_) | Value::Number(_) | Value::String(_) => {
1062 let mut key_path = current_key_path.clone();
1063 key_path.push(key);
1064 write_settings_key(json_text, &key_path, &new_value);
1065 }
1066 Value::Object(new_sub_object) => {
1067 let mut key_path = current_key_path.clone();
1068 key_path.push(key);
1069 if let Value::Object(old_sub_object) = old_value {
1070 apply_changes_to_json_text(
1071 old_sub_object,
1072 new_sub_object,
1073 key_path,
1074 json_text,
1075 );
1076 } else {
1077 unimplemented!("This function doesn't support changing values from simple values to objects yet");
1078 }
1079 }
1080 Value::Null | Value::Array(_) => {
1081 unimplemented!("We only support objects and simple values");
1082 }
1083 }
1084 }
1085 }
1086 }
1087
1088 apply_changes_to_json_text(&old_object, &new_object, vec![], &mut text);
1089
1090 text
1091}
1092
1093fn to_json_object(settings_file: SettingsFileContent) -> serde_json::Map<String, Value> {
1094 let tmp = serde_json::to_value(settings_file).unwrap();
1095 match tmp {
1096 Value::Object(map) => map,
1097 _ => unreachable!("SettingsFileContent represents a JSON map"),
1098 }
1099}
1100
1101#[cfg(test)]
1102mod tests {
1103 use super::*;
1104 use unindent::Unindent;
1105
1106 fn assert_new_settings<S1: Into<String>, S2: Into<String>>(
1107 old_json: S1,
1108 update: fn(&mut SettingsFileContent),
1109 expected_new_json: S2,
1110 ) {
1111 let old_json = old_json.into();
1112 let old_content: SettingsFileContent = serde_json::from_str(&old_json).unwrap_or_default();
1113 let new_json = update_settings_file(old_json, old_content, update);
1114 pretty_assertions::assert_eq!(new_json, expected_new_json.into());
1115 }
1116
1117 #[test]
1118 fn test_update_language_overrides_copilot() {
1119 assert_new_settings(
1120 r#"
1121 {
1122 "language_overrides": {
1123 "JSON": {
1124 "copilot": "off"
1125 }
1126 }
1127 }
1128 "#
1129 .unindent(),
1130 |settings| {
1131 settings.languages.insert(
1132 "Rust".into(),
1133 EditorSettings {
1134 copilot: Some(OnOff::On),
1135 ..Default::default()
1136 },
1137 );
1138 },
1139 r#"
1140 {
1141 "language_overrides": {
1142 "Rust": {
1143 "copilot": "on"
1144 },
1145 "JSON": {
1146 "copilot": "off"
1147 }
1148 }
1149 }
1150 "#
1151 .unindent(),
1152 );
1153 }
1154
1155 #[test]
1156 fn test_update_copilot() {
1157 assert_new_settings(
1158 r#"
1159 {
1160 "languages": {
1161 "JSON": {
1162 "copilot": "off"
1163 }
1164 }
1165 }
1166 "#
1167 .unindent(),
1168 |settings| {
1169 settings.editor.copilot = Some(OnOff::On);
1170 },
1171 r#"
1172 {
1173 "copilot": "on",
1174 "languages": {
1175 "JSON": {
1176 "copilot": "off"
1177 }
1178 }
1179 }
1180 "#
1181 .unindent(),
1182 );
1183 }
1184
1185 #[test]
1186 fn test_update_langauge_copilot() {
1187 assert_new_settings(
1188 r#"
1189 {
1190 "languages": {
1191 "JSON": {
1192 "copilot": "off"
1193 }
1194 }
1195 }
1196 "#
1197 .unindent(),
1198 |settings| {
1199 settings.languages.insert(
1200 "Rust".into(),
1201 EditorSettings {
1202 copilot: Some(OnOff::On),
1203 ..Default::default()
1204 },
1205 );
1206 },
1207 r#"
1208 {
1209 "languages": {
1210 "Rust": {
1211 "copilot": "on"
1212 },
1213 "JSON": {
1214 "copilot": "off"
1215 }
1216 }
1217 }
1218 "#
1219 .unindent(),
1220 );
1221 }
1222
1223 #[test]
1224 fn test_update_telemetry_setting_multiple_fields() {
1225 assert_new_settings(
1226 r#"
1227 {
1228 "telemetry": {
1229 "metrics": false,
1230 "diagnostics": false
1231 }
1232 }
1233 "#
1234 .unindent(),
1235 |settings| {
1236 settings.telemetry.set_diagnostics(true);
1237 settings.telemetry.set_metrics(true);
1238 },
1239 r#"
1240 {
1241 "telemetry": {
1242 "metrics": true,
1243 "diagnostics": true
1244 }
1245 }
1246 "#
1247 .unindent(),
1248 );
1249 }
1250
1251 #[test]
1252 fn test_update_telemetry_setting_weird_formatting() {
1253 assert_new_settings(
1254 r#"{
1255 "telemetry": { "metrics": false, "diagnostics": true }
1256 }"#
1257 .unindent(),
1258 |settings| settings.telemetry.set_diagnostics(false),
1259 r#"{
1260 "telemetry": { "metrics": false, "diagnostics": false }
1261 }"#
1262 .unindent(),
1263 );
1264 }
1265
1266 #[test]
1267 fn test_update_telemetry_setting_other_fields() {
1268 assert_new_settings(
1269 r#"
1270 {
1271 "telemetry": {
1272 "metrics": false,
1273 "diagnostics": true
1274 }
1275 }
1276 "#
1277 .unindent(),
1278 |settings| settings.telemetry.set_diagnostics(false),
1279 r#"
1280 {
1281 "telemetry": {
1282 "metrics": false,
1283 "diagnostics": false
1284 }
1285 }
1286 "#
1287 .unindent(),
1288 );
1289 }
1290
1291 #[test]
1292 fn test_update_telemetry_setting_empty_telemetry() {
1293 assert_new_settings(
1294 r#"
1295 {
1296 "telemetry": {}
1297 }
1298 "#
1299 .unindent(),
1300 |settings| settings.telemetry.set_diagnostics(false),
1301 r#"
1302 {
1303 "telemetry": {
1304 "diagnostics": false
1305 }
1306 }
1307 "#
1308 .unindent(),
1309 );
1310 }
1311
1312 #[test]
1313 fn test_update_telemetry_setting_pre_existing() {
1314 assert_new_settings(
1315 r#"
1316 {
1317 "telemetry": {
1318 "diagnostics": true
1319 }
1320 }
1321 "#
1322 .unindent(),
1323 |settings| settings.telemetry.set_diagnostics(false),
1324 r#"
1325 {
1326 "telemetry": {
1327 "diagnostics": false
1328 }
1329 }
1330 "#
1331 .unindent(),
1332 );
1333 }
1334
1335 #[test]
1336 fn test_update_telemetry_setting() {
1337 assert_new_settings(
1338 "{}",
1339 |settings| settings.telemetry.set_diagnostics(true),
1340 r#"
1341 {
1342 "telemetry": {
1343 "diagnostics": true
1344 }
1345 }
1346 "#
1347 .unindent(),
1348 );
1349 }
1350
1351 #[test]
1352 fn test_update_object_empty_doc() {
1353 assert_new_settings(
1354 "",
1355 |settings| settings.telemetry.set_diagnostics(true),
1356 r#"
1357 {
1358 "telemetry": {
1359 "diagnostics": true
1360 }
1361 }
1362 "#
1363 .unindent(),
1364 );
1365 }
1366
1367 #[test]
1368 fn test_write_theme_into_settings_with_theme() {
1369 assert_new_settings(
1370 r#"
1371 {
1372 "theme": "One Dark"
1373 }
1374 "#
1375 .unindent(),
1376 |settings| settings.theme = Some("summerfruit-light".to_string()),
1377 r#"
1378 {
1379 "theme": "summerfruit-light"
1380 }
1381 "#
1382 .unindent(),
1383 );
1384 }
1385
1386 #[test]
1387 fn test_write_theme_into_empty_settings() {
1388 assert_new_settings(
1389 r#"
1390 {
1391 }
1392 "#
1393 .unindent(),
1394 |settings| settings.theme = Some("summerfruit-light".to_string()),
1395 r#"
1396 {
1397 "theme": "summerfruit-light"
1398 }
1399 "#
1400 .unindent(),
1401 );
1402 }
1403
1404 #[test]
1405 fn write_key_no_document() {
1406 assert_new_settings(
1407 "",
1408 |settings| settings.theme = Some("summerfruit-light".to_string()),
1409 r#"
1410 {
1411 "theme": "summerfruit-light"
1412 }
1413 "#
1414 .unindent(),
1415 );
1416 }
1417
1418 #[test]
1419 fn test_write_theme_into_single_line_settings_without_theme() {
1420 assert_new_settings(
1421 r#"{ "a": "", "ok": true }"#,
1422 |settings| settings.theme = Some("summerfruit-light".to_string()),
1423 r#"{ "theme": "summerfruit-light", "a": "", "ok": true }"#,
1424 );
1425 }
1426
1427 #[test]
1428 fn test_write_theme_pre_object_whitespace() {
1429 assert_new_settings(
1430 r#" { "a": "", "ok": true }"#,
1431 |settings| settings.theme = Some("summerfruit-light".to_string()),
1432 r#" { "theme": "summerfruit-light", "a": "", "ok": true }"#.unindent(),
1433 );
1434 }
1435
1436 #[test]
1437 fn test_write_theme_into_multi_line_settings_without_theme() {
1438 assert_new_settings(
1439 r#"
1440 {
1441 "a": "b"
1442 }
1443 "#
1444 .unindent(),
1445 |settings| settings.theme = Some("summerfruit-light".to_string()),
1446 r#"
1447 {
1448 "theme": "summerfruit-light",
1449 "a": "b"
1450 }
1451 "#
1452 .unindent(),
1453 );
1454 }
1455}