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