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