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