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