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