1use crate::types::{context_keys, LspLanguageConfig, LspServerConfig, ProcessLimits};
2
3use rust_i18n::t;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use std::borrow::Cow;
7use std::collections::HashMap;
8use std::ops::Deref;
9use std::path::Path;
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(transparent)]
14pub struct ThemeName(pub String);
15
16impl ThemeName {
17 pub const BUILTIN_OPTIONS: &'static [&'static str] =
19 &["dark", "light", "high-contrast", "nostalgia", "terminal"];
20}
21
22impl Deref for ThemeName {
23 type Target = str;
24 fn deref(&self) -> &Self::Target {
25 &self.0
26 }
27}
28
29impl From<String> for ThemeName {
30 fn from(s: String) -> Self {
31 Self(s)
32 }
33}
34
35impl From<&str> for ThemeName {
36 fn from(s: &str) -> Self {
37 Self(s.to_string())
38 }
39}
40
41impl PartialEq<str> for ThemeName {
42 fn eq(&self, other: &str) -> bool {
43 self.0 == other
44 }
45}
46
47impl PartialEq<ThemeName> for str {
48 fn eq(&self, other: &ThemeName) -> bool {
49 self == other.0
50 }
51}
52
53impl JsonSchema for ThemeName {
54 fn schema_name() -> Cow<'static, str> {
55 Cow::Borrowed("ThemeOptions")
56 }
57
58 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
59 schemars::json_schema!({
60 "description": "Available color themes",
61 "type": "string",
62 "enum": Self::BUILTIN_OPTIONS
63 })
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
70#[serde(transparent)]
71pub struct LocaleName(pub Option<String>);
72
73include!(concat!(env!("OUT_DIR"), "/locale_options.rs"));
75
76impl LocaleName {
77 pub const LOCALE_OPTIONS: &'static [Option<&'static str>] = GENERATED_LOCALE_OPTIONS;
81
82 pub fn as_option(&self) -> Option<&str> {
84 self.0.as_deref()
85 }
86}
87
88impl From<Option<String>> for LocaleName {
89 fn from(s: Option<String>) -> Self {
90 Self(s)
91 }
92}
93
94impl From<Option<&str>> for LocaleName {
95 fn from(s: Option<&str>) -> Self {
96 Self(s.map(|s| s.to_string()))
97 }
98}
99
100impl JsonSchema for LocaleName {
101 fn schema_name() -> Cow<'static, str> {
102 Cow::Borrowed("LocaleOptions")
103 }
104
105 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
106 schemars::json_schema!({
107 "description": "UI locale (language). Use null for auto-detection from environment.",
108 "enum": Self::LOCALE_OPTIONS
109 })
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
115#[serde(rename_all = "snake_case")]
116pub enum CursorStyle {
117 #[default]
119 Default,
120 BlinkingBlock,
122 SteadyBlock,
124 BlinkingBar,
126 SteadyBar,
128 BlinkingUnderline,
130 SteadyUnderline,
132}
133
134impl CursorStyle {
135 pub const OPTIONS: &'static [&'static str] = &[
137 "default",
138 "blinking_block",
139 "steady_block",
140 "blinking_bar",
141 "steady_bar",
142 "blinking_underline",
143 "steady_underline",
144 ];
145
146 pub const DESCRIPTIONS: &'static [&'static str] = &[
148 "Terminal default",
149 "█ Blinking block",
150 "█ Solid block",
151 "│ Blinking bar",
152 "│ Solid bar",
153 "_ Blinking underline",
154 "_ Solid underline",
155 ];
156
157 #[cfg(feature = "runtime")]
159 pub fn to_crossterm_style(self) -> crossterm::cursor::SetCursorStyle {
160 use crossterm::cursor::SetCursorStyle;
161 match self {
162 Self::Default => SetCursorStyle::DefaultUserShape,
163 Self::BlinkingBlock => SetCursorStyle::BlinkingBlock,
164 Self::SteadyBlock => SetCursorStyle::SteadyBlock,
165 Self::BlinkingBar => SetCursorStyle::BlinkingBar,
166 Self::SteadyBar => SetCursorStyle::SteadyBar,
167 Self::BlinkingUnderline => SetCursorStyle::BlinkingUnderScore,
168 Self::SteadyUnderline => SetCursorStyle::SteadyUnderScore,
169 }
170 }
171
172 pub fn to_escape_sequence(self) -> &'static [u8] {
175 match self {
176 Self::Default => b"\x1b[0 q",
177 Self::BlinkingBlock => b"\x1b[1 q",
178 Self::SteadyBlock => b"\x1b[2 q",
179 Self::BlinkingUnderline => b"\x1b[3 q",
180 Self::SteadyUnderline => b"\x1b[4 q",
181 Self::BlinkingBar => b"\x1b[5 q",
182 Self::SteadyBar => b"\x1b[6 q",
183 }
184 }
185
186 pub fn parse(s: &str) -> Option<Self> {
188 match s {
189 "default" => Some(CursorStyle::Default),
190 "blinking_block" => Some(CursorStyle::BlinkingBlock),
191 "steady_block" => Some(CursorStyle::SteadyBlock),
192 "blinking_bar" => Some(CursorStyle::BlinkingBar),
193 "steady_bar" => Some(CursorStyle::SteadyBar),
194 "blinking_underline" => Some(CursorStyle::BlinkingUnderline),
195 "steady_underline" => Some(CursorStyle::SteadyUnderline),
196 _ => None,
197 }
198 }
199
200 pub fn as_str(self) -> &'static str {
202 match self {
203 Self::Default => "default",
204 Self::BlinkingBlock => "blinking_block",
205 Self::SteadyBlock => "steady_block",
206 Self::BlinkingBar => "blinking_bar",
207 Self::SteadyBar => "steady_bar",
208 Self::BlinkingUnderline => "blinking_underline",
209 Self::SteadyUnderline => "steady_underline",
210 }
211 }
212}
213
214impl JsonSchema for CursorStyle {
215 fn schema_name() -> Cow<'static, str> {
216 Cow::Borrowed("CursorStyle")
217 }
218
219 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
220 schemars::json_schema!({
221 "description": "Terminal cursor style",
222 "type": "string",
223 "enum": Self::OPTIONS
224 })
225 }
226}
227
228#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
230#[serde(transparent)]
231pub struct KeybindingMapName(pub String);
232
233impl KeybindingMapName {
234 pub const BUILTIN_OPTIONS: &'static [&'static str] =
236 &["default", "emacs", "vscode", "macos", "macos-gui"];
237}
238
239impl Deref for KeybindingMapName {
240 type Target = str;
241 fn deref(&self) -> &Self::Target {
242 &self.0
243 }
244}
245
246impl From<String> for KeybindingMapName {
247 fn from(s: String) -> Self {
248 Self(s)
249 }
250}
251
252impl From<&str> for KeybindingMapName {
253 fn from(s: &str) -> Self {
254 Self(s.to_string())
255 }
256}
257
258impl PartialEq<str> for KeybindingMapName {
259 fn eq(&self, other: &str) -> bool {
260 self.0 == other
261 }
262}
263
264#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
266#[serde(rename_all = "lowercase")]
267pub enum LineEndingOption {
268 #[default]
270 Lf,
271 Crlf,
273 Cr,
275}
276
277impl LineEndingOption {
278 pub fn to_line_ending(&self) -> crate::model::buffer::LineEnding {
280 match self {
281 Self::Lf => crate::model::buffer::LineEnding::LF,
282 Self::Crlf => crate::model::buffer::LineEnding::CRLF,
283 Self::Cr => crate::model::buffer::LineEnding::CR,
284 }
285 }
286}
287
288impl JsonSchema for LineEndingOption {
289 fn schema_name() -> Cow<'static, str> {
290 Cow::Borrowed("LineEndingOption")
291 }
292
293 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
294 schemars::json_schema!({
295 "description": "Default line ending format for new files",
296 "type": "string",
297 "enum": ["lf", "crlf", "cr"],
298 "default": "lf"
299 })
300 }
301}
302
303impl PartialEq<KeybindingMapName> for str {
304 fn eq(&self, other: &KeybindingMapName) -> bool {
305 self == other.0
306 }
307}
308
309impl JsonSchema for KeybindingMapName {
310 fn schema_name() -> Cow<'static, str> {
311 Cow::Borrowed("KeybindingMapOptions")
312 }
313
314 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
315 schemars::json_schema!({
316 "description": "Available keybinding maps",
317 "type": "string",
318 "enum": Self::BUILTIN_OPTIONS
319 })
320 }
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
325pub struct Config {
326 #[serde(default)]
329 pub version: u32,
330
331 #[serde(default = "default_theme_name")]
333 pub theme: ThemeName,
334
335 #[serde(default)]
338 pub locale: LocaleName,
339
340 #[serde(default = "default_true")]
343 pub check_for_updates: bool,
344
345 #[serde(default)]
347 pub editor: EditorConfig,
348
349 #[serde(default)]
351 pub file_explorer: FileExplorerConfig,
352
353 #[serde(default)]
355 pub file_browser: FileBrowserConfig,
356
357 #[serde(default)]
359 pub clipboard: ClipboardConfig,
360
361 #[serde(default)]
363 pub terminal: TerminalConfig,
364
365 #[serde(default)]
367 pub keybindings: Vec<Keybinding>,
368
369 #[serde(default)]
372 pub keybinding_maps: HashMap<String, KeymapConfig>,
373
374 #[serde(default = "default_keybinding_map_name")]
376 #[schemars(default = "default_keybinding_map_schema")]
377 pub active_keybinding_map: KeybindingMapName,
378
379 #[serde(default)]
381 pub languages: HashMap<String, LanguageConfig>,
382
383 #[serde(default)]
389 #[schemars(extend("x-enum-from" = "/languages"))]
390 pub default_language: Option<String>,
391
392 #[serde(default = "default_true")]
398 pub lsp_enabled: bool,
399
400 #[serde(default)]
404 pub lsp: HashMap<String, LspLanguageConfig>,
405
406 #[serde(default)]
410 pub universal_lsp: HashMap<String, LspLanguageConfig>,
411
412 #[serde(default)]
414 pub warnings: WarningsConfig,
415
416 #[serde(default)]
420 #[schemars(extend("x-standalone-category" = true, "x-no-add" = true))]
421 pub plugins: HashMap<String, PluginConfig>,
422
423 #[serde(default)]
425 pub packages: PackagesConfig,
426}
427
428fn default_keybinding_map_name() -> KeybindingMapName {
429 if cfg!(target_os = "macos") {
432 KeybindingMapName("macos".to_string())
433 } else {
434 KeybindingMapName("default".to_string())
435 }
436}
437
438fn default_keybinding_map_schema() -> KeybindingMapName {
441 KeybindingMapName("default".to_string())
442}
443
444fn default_theme_name() -> ThemeName {
445 ThemeName("high-contrast".to_string())
446}
447
448#[derive(Debug, Clone, Copy)]
453pub struct WhitespaceVisibility {
454 pub spaces_leading: bool,
455 pub spaces_inner: bool,
456 pub spaces_trailing: bool,
457 pub tabs_leading: bool,
458 pub tabs_inner: bool,
459 pub tabs_trailing: bool,
460}
461
462impl Default for WhitespaceVisibility {
463 fn default() -> Self {
464 Self {
466 spaces_leading: false,
467 spaces_inner: false,
468 spaces_trailing: false,
469 tabs_leading: true,
470 tabs_inner: true,
471 tabs_trailing: true,
472 }
473 }
474}
475
476impl WhitespaceVisibility {
477 pub fn from_editor_config(editor: &EditorConfig) -> Self {
479 if !editor.whitespace_show {
480 return Self {
481 spaces_leading: false,
482 spaces_inner: false,
483 spaces_trailing: false,
484 tabs_leading: false,
485 tabs_inner: false,
486 tabs_trailing: false,
487 };
488 }
489 Self {
490 spaces_leading: editor.whitespace_spaces_leading,
491 spaces_inner: editor.whitespace_spaces_inner,
492 spaces_trailing: editor.whitespace_spaces_trailing,
493 tabs_leading: editor.whitespace_tabs_leading,
494 tabs_inner: editor.whitespace_tabs_inner,
495 tabs_trailing: editor.whitespace_tabs_trailing,
496 }
497 }
498
499 pub fn with_language_tab_override(mut self, show_whitespace_tabs: bool) -> Self {
502 if !show_whitespace_tabs {
503 self.tabs_leading = false;
504 self.tabs_inner = false;
505 self.tabs_trailing = false;
506 }
507 self
508 }
509
510 pub fn any_spaces(&self) -> bool {
512 self.spaces_leading || self.spaces_inner || self.spaces_trailing
513 }
514
515 pub fn any_tabs(&self) -> bool {
517 self.tabs_leading || self.tabs_inner || self.tabs_trailing
518 }
519
520 pub fn any_visible(&self) -> bool {
522 self.any_spaces() || self.any_tabs()
523 }
524
525 pub fn toggle_all(&mut self) {
529 if self.any_visible() {
530 *self = Self {
531 spaces_leading: false,
532 spaces_inner: false,
533 spaces_trailing: false,
534 tabs_leading: false,
535 tabs_inner: false,
536 tabs_trailing: false,
537 };
538 } else {
539 *self = Self::default();
540 }
541 }
542}
543
544#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
564#[serde(try_from = "String", into = "String")]
565pub enum StatusBarElement {
566 Filename,
568 Cursor,
570 CursorCompact,
572 Diagnostics,
574 CursorCount,
576 Messages,
578 Chord,
580 LineEnding,
582 Encoding,
584 Language,
586 Lsp,
588 Warnings,
590 Update,
592 Palette,
594 Clock,
596 RemoteIndicator,
601 WorkspaceTrust,
606 CustomToken(String),
608}
609
610impl TryFrom<String> for StatusBarElement {
611 type Error = String;
612 fn try_from(s: String) -> Result<Self, String> {
613 let inner = s
615 .strip_prefix('{')
616 .and_then(|s| s.strip_suffix('}'))
617 .unwrap_or(&s);
618 match inner {
619 "filename" => Ok(Self::Filename),
620 "cursor" => Ok(Self::Cursor),
621 "cursor:compact" => Ok(Self::CursorCompact),
622 "diagnostics" => Ok(Self::Diagnostics),
623 "cursor_count" => Ok(Self::CursorCount),
624 "messages" => Ok(Self::Messages),
625 "chord" => Ok(Self::Chord),
626 "line_ending" => Ok(Self::LineEnding),
627 "encoding" => Ok(Self::Encoding),
628 "language" => Ok(Self::Language),
629 "lsp" => Ok(Self::Lsp),
630 "warnings" => Ok(Self::Warnings),
631 "update" => Ok(Self::Update),
632 "palette" => Ok(Self::Palette),
633 "clock" => Ok(Self::Clock),
634 "remote" => Ok(Self::RemoteIndicator),
635 "trust" => Ok(Self::WorkspaceTrust),
636 _ => {
637 if inner.contains(':') {
639 Ok(Self::CustomToken(inner.to_string()))
640 } else {
641 Err(format!("Unknown status bar element: {}", s))
642 }
643 }
644 }
645 }
646}
647
648impl From<StatusBarElement> for String {
649 fn from(e: StatusBarElement) -> String {
650 match e {
651 StatusBarElement::Filename => "{filename}".to_string(),
652 StatusBarElement::Cursor => "{cursor}".to_string(),
653 StatusBarElement::CursorCompact => "{cursor:compact}".to_string(),
654 StatusBarElement::Diagnostics => "{diagnostics}".to_string(),
655 StatusBarElement::CursorCount => "{cursor_count}".to_string(),
656 StatusBarElement::Messages => "{messages}".to_string(),
657 StatusBarElement::Chord => "{chord}".to_string(),
658 StatusBarElement::LineEnding => "{line_ending}".to_string(),
659 StatusBarElement::Encoding => "{encoding}".to_string(),
660 StatusBarElement::Language => "{language}".to_string(),
661 StatusBarElement::Lsp => "{lsp}".to_string(),
662 StatusBarElement::Warnings => "{warnings}".to_string(),
663 StatusBarElement::Update => "{update}".to_string(),
664 StatusBarElement::Palette => "{palette}".to_string(),
665 StatusBarElement::Clock => "{clock}".to_string(),
666 StatusBarElement::RemoteIndicator => "{remote}".to_string(),
667 StatusBarElement::WorkspaceTrust => "{trust}".to_string(),
668 StatusBarElement::CustomToken(name) => format!("{{{}}}", name),
669 }
670 }
671}
672
673impl schemars::JsonSchema for StatusBarElement {
674 fn schema_name() -> std::borrow::Cow<'static, str> {
675 std::borrow::Cow::Borrowed("StatusBarElement")
676 }
677 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
678 schemars::json_schema!({
679 "type": "string",
680 "x-dual-list-options": [
681 {"value": "{filename}", "name": "Filename"},
682 {"value": "{cursor}", "name": "Cursor"},
683 {"value": "{cursor:compact}", "name": "Cursor (compact)"},
684 {"value": "{diagnostics}", "name": "Diagnostics"},
685 {"value": "{cursor_count}", "name": "Cursor Count"},
686 {"value": "{messages}", "name": "Messages"},
687 {"value": "{chord}", "name": "Chord"},
688 {"value": "{line_ending}", "name": "Line Ending"},
689 {"value": "{encoding}", "name": "Encoding"},
690 {"value": "{language}", "name": "Language"},
691 {"value": "{lsp}", "name": "LSP"},
692 {"value": "{warnings}", "name": "Warnings"},
693 {"value": "{update}", "name": "Update"},
694 {"value": "{palette}", "name": "Palette"},
695 {"value": "{clock}", "name": "Clock"},
696 {"value": "{remote}", "name": "Remote Indicator"},
697 {"value": "{trust}", "name": "Workspace Trust"}
698 ]
699 })
700 }
701}
702
703fn default_status_bar_left() -> Vec<StatusBarElement> {
704 vec![
714 StatusBarElement::WorkspaceTrust,
715 StatusBarElement::RemoteIndicator,
716 StatusBarElement::Cursor,
717 StatusBarElement::Diagnostics,
718 StatusBarElement::CursorCount,
719 StatusBarElement::Messages,
720 ]
721}
722
723fn default_status_bar_right() -> Vec<StatusBarElement> {
724 vec![
725 StatusBarElement::LineEnding,
726 StatusBarElement::Encoding,
727 StatusBarElement::Language,
728 StatusBarElement::Lsp,
729 StatusBarElement::Warnings,
730 StatusBarElement::Update,
731 StatusBarElement::Palette,
732 ]
733}
734
735#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
750pub struct StatusBarConfig {
751 #[serde(default = "default_status_bar_left")]
754 #[schemars(extend("x-section" = "Status Bar", "x-dual-list-sibling" = "/editor/status_bar/right", "x-dynamically-extendable-status-bar-elements" = true))]
755 pub left: Vec<StatusBarElement>,
756
757 #[serde(default = "default_status_bar_right")]
760 #[schemars(extend("x-section" = "Status Bar", "x-dual-list-sibling" = "/editor/status_bar/left", "x-dynamically-extendable-status-bar-elements" = true))]
761 pub right: Vec<StatusBarElement>,
762
763 #[serde(default = "default_status_bar_separator")]
769 #[schemars(extend("x-section" = "Status Bar"))]
770 pub separator: String,
771}
772
773fn default_status_bar_separator() -> String {
774 "".to_string()
775}
776
777impl Default for StatusBarConfig {
778 fn default() -> Self {
779 Self {
780 left: default_status_bar_left(),
781 right: default_status_bar_right(),
782 separator: default_status_bar_separator(),
783 }
784 }
785}
786
787#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
789pub struct EditorConfig {
790 #[serde(default = "default_true")]
797 #[schemars(extend("x-section" = "Display"))]
798 pub animations: bool,
799
800 #[serde(default = "default_true")]
804 #[schemars(extend("x-section" = "Display"))]
805 pub cursor_jump_animation: bool,
806
807 #[serde(default = "default_true")]
809 #[schemars(extend("x-section" = "Display"))]
810 pub line_numbers: bool,
811
812 #[serde(default = "default_false")]
814 #[schemars(extend("x-section" = "Display"))]
815 pub relative_line_numbers: bool,
816
817 #[serde(default = "default_true")]
819 #[schemars(extend("x-section" = "Display"))]
820 pub highlight_current_line: bool,
821
822 #[serde(default = "default_true")]
824 #[schemars(extend("x-section" = "Display"))]
825 pub highlight_occurrences: bool,
826
827 #[serde(default = "default_false")]
832 #[schemars(extend("x-section" = "Display"))]
833 pub hide_current_line_on_selection: bool,
834
835 #[serde(default = "default_false")]
837 #[schemars(extend("x-section" = "Display"))]
838 pub highlight_current_column: bool,
839
840 #[serde(default = "default_true")]
842 #[schemars(extend("x-section" = "Display"))]
843 pub line_wrap: bool,
844
845 #[serde(default = "default_true")]
847 #[schemars(extend("x-section" = "Display"))]
848 pub wrap_indent: bool,
849
850 #[serde(default)]
856 #[schemars(extend("x-section" = "Display"))]
857 pub wrap_column: Option<usize>,
858
859 #[serde(default = "default_page_width")]
863 #[schemars(extend("x-section" = "Display"))]
864 pub page_width: Option<usize>,
865
866 #[serde(default = "default_true")]
868 #[schemars(extend("x-section" = "Display"))]
869 pub syntax_highlighting: bool,
870
871 #[serde(default = "default_true")]
876 #[schemars(extend("x-section" = "Display"))]
877 pub show_menu_bar: bool,
878
879 #[serde(default = "default_false")]
885 #[schemars(extend("x-section" = "Display"))]
886 pub screensaver_enabled: bool,
887
888 #[serde(default = "default_screensaver_idle_minutes")]
892 #[schemars(extend("x-section" = "Display"))]
893 pub screensaver_idle_minutes: u32,
894
895 #[serde(default = "default_true")]
900 #[schemars(extend("x-section" = "Display"))]
901 pub menu_bar_mnemonics: bool,
902
903 #[serde(default = "default_true")]
908 #[schemars(extend("x-section" = "Display"))]
909 pub show_tab_bar: bool,
910
911 #[serde(default = "default_true")]
916 #[schemars(extend("x-section" = "Display"))]
917 pub show_status_bar: bool,
918
919 #[serde(default)]
922 #[schemars(extend("x-section" = "Status Bar"))]
923 pub status_bar: StatusBarConfig,
924
925 #[serde(default = "default_false")]
932 #[schemars(extend("x-section" = "Display"))]
933 pub show_prompt_line: bool,
934
935 #[serde(default = "default_true")]
939 #[schemars(extend("x-section" = "Display"))]
940 pub show_vertical_scrollbar: bool,
941
942 #[serde(default = "default_false")]
947 #[schemars(extend("x-section" = "Display"))]
948 pub show_horizontal_scrollbar: bool,
949
950 #[serde(default = "default_true")]
954 #[schemars(extend("x-section" = "Display"))]
955 pub show_tilde: bool,
956
957 #[serde(default = "default_false")]
962 #[schemars(extend("x-section" = "Display"))]
963 pub use_terminal_bg: bool,
964
965 #[serde(default = "default_true")]
971 #[schemars(extend("x-section" = "Display"))]
972 pub set_window_title: bool,
973
974 #[serde(default = "default_true")]
981 #[schemars(extend("x-section" = "Display"))]
982 pub terminal_auto_title: bool,
983
984 #[serde(default)]
988 #[schemars(extend("x-section" = "Display"))]
989 pub cursor_style: CursorStyle,
990
991 #[serde(default)]
996 #[schemars(extend("x-section" = "Display"))]
997 pub rulers: Vec<usize>,
998
999 #[serde(default = "default_true")]
1005 #[schemars(extend("x-section" = "Whitespace"))]
1006 pub whitespace_show: bool,
1007
1008 #[serde(default = "default_false")]
1012 #[schemars(extend("x-section" = "Whitespace"))]
1013 pub whitespace_spaces_leading: bool,
1014
1015 #[serde(default = "default_false")]
1019 #[schemars(extend("x-section" = "Whitespace"))]
1020 pub whitespace_spaces_inner: bool,
1021
1022 #[serde(default = "default_false")]
1026 #[schemars(extend("x-section" = "Whitespace"))]
1027 pub whitespace_spaces_trailing: bool,
1028
1029 #[serde(default = "default_true")]
1033 #[schemars(extend("x-section" = "Whitespace"))]
1034 pub whitespace_tabs_leading: bool,
1035
1036 #[serde(default = "default_true")]
1040 #[schemars(extend("x-section" = "Whitespace"))]
1041 pub whitespace_tabs_inner: bool,
1042
1043 #[serde(default = "default_true")]
1047 #[schemars(extend("x-section" = "Whitespace"))]
1048 pub whitespace_tabs_trailing: bool,
1049
1050 #[serde(default = "default_false")]
1056 #[schemars(extend("x-section" = "Editing"))]
1057 pub use_tabs: bool,
1058
1059 #[serde(default = "default_tab_size")]
1062 #[schemars(extend("x-section" = "Editing"))]
1063 pub tab_size: usize,
1064
1065 #[serde(default = "default_true")]
1067 #[schemars(extend("x-section" = "Editing"))]
1068 pub auto_indent: bool,
1069
1070 #[serde(default = "default_true")]
1077 #[schemars(extend("x-section" = "Editing"))]
1078 pub auto_close: bool,
1079
1080 #[serde(default = "default_true")]
1085 #[schemars(extend("x-section" = "Editing"))]
1086 pub auto_surround: bool,
1087
1088 #[serde(default = "default_scroll_offset")]
1090 #[schemars(extend("x-section" = "Editing"))]
1091 pub scroll_offset: usize,
1092
1093 #[serde(default)]
1098 #[schemars(extend("x-section" = "Editing"))]
1099 pub default_line_ending: LineEndingOption,
1100
1101 #[serde(default = "default_false")]
1104 #[schemars(extend("x-section" = "Editing"))]
1105 pub trim_trailing_whitespace_on_save: bool,
1106
1107 #[serde(default = "default_false")]
1110 #[schemars(extend("x-section" = "Editing"))]
1111 pub ensure_final_newline_on_save: bool,
1112
1113 #[serde(default = "default_true")]
1121 #[schemars(extend("x-section" = "Editing"))]
1122 pub auto_read_only: bool,
1123
1124 #[serde(default = "default_true")]
1128 #[schemars(extend("x-section" = "Bracket Matching"))]
1129 pub highlight_matching_brackets: bool,
1130
1131 #[serde(default = "default_true")]
1135 #[schemars(extend("x-section" = "Bracket Matching"))]
1136 pub rainbow_brackets: bool,
1137
1138 #[serde(default = "default_false")]
1145 #[schemars(extend("x-section" = "Completion"))]
1146 pub completion_popup_auto_show: bool,
1147
1148 #[serde(default = "default_true")]
1154 #[schemars(extend("x-section" = "Completion"))]
1155 pub quick_suggestions: bool,
1156
1157 #[serde(default = "default_quick_suggestions_delay")]
1163 #[schemars(extend("x-section" = "Completion"))]
1164 pub quick_suggestions_delay_ms: u64,
1165
1166 #[serde(default = "default_true")]
1170 #[schemars(extend("x-section" = "Completion"))]
1171 pub suggest_on_trigger_characters: bool,
1172
1173 #[serde(default = "default_true")]
1176 #[schemars(extend("x-section" = "LSP"))]
1177 pub enable_inlay_hints: bool,
1178
1179 #[serde(default = "default_false")]
1183 #[schemars(extend("x-section" = "LSP"))]
1184 pub enable_semantic_tokens_full: bool,
1185
1186 #[serde(default = "default_false")]
1191 #[schemars(extend("x-section" = "Diagnostics"))]
1192 pub diagnostics_inline_text: bool,
1193
1194 #[serde(default = "default_mouse_hover_enabled")]
1205 #[schemars(extend("x-section" = "Mouse"))]
1206 pub mouse_hover_enabled: bool,
1207
1208 #[serde(default = "default_mouse_hover_delay")]
1212 #[schemars(extend("x-section" = "Mouse"))]
1213 pub mouse_hover_delay_ms: u64,
1214
1215 #[serde(default = "default_double_click_time")]
1219 #[schemars(extend("x-section" = "Mouse"))]
1220 pub double_click_time_ms: u64,
1221
1222 #[serde(default = "default_false")]
1227 #[schemars(extend("x-section" = "Recovery"))]
1228 pub auto_save_enabled: bool,
1229
1230 #[serde(default = "default_auto_save_interval")]
1235 #[schemars(extend("x-section" = "Recovery"))]
1236 pub auto_save_interval_secs: u32,
1237
1238 #[serde(default = "default_true", alias = "persist_unnamed_buffers")]
1245 #[schemars(extend("x-section" = "Recovery"))]
1246 pub hot_exit: bool,
1247
1248 #[serde(default)]
1255 #[schemars(extend("x-section" = "Startup"))]
1256 pub confirm_quit: bool,
1257
1258 #[serde(default = "default_true")]
1269 #[schemars(extend("x-section" = "Startup"))]
1270 pub restore_previous_session: bool,
1271
1272 #[serde(default = "default_true")]
1283 #[schemars(extend("x-section" = "Startup"))]
1284 pub skip_session_restore_when_files_passed: bool,
1285
1286 #[serde(default = "default_true")]
1294 #[schemars(extend("x-section" = "Startup"))]
1295 pub auto_create_empty_buffer_on_last_buffer_close: bool,
1296
1297 #[serde(default = "default_true")]
1302 #[schemars(extend("x-section" = "Recovery"))]
1303 pub recovery_enabled: bool,
1304
1305 #[serde(default = "default_auto_recovery_save_interval")]
1310 #[schemars(extend("x-section" = "Recovery"))]
1311 pub auto_recovery_save_interval_secs: u32,
1312
1313 #[serde(default = "default_auto_revert_poll_interval")]
1318 #[schemars(extend("x-section" = "Recovery"))]
1319 pub auto_revert_poll_interval_ms: u64,
1320
1321 #[serde(default = "default_true")]
1327 #[schemars(extend("x-section" = "Keyboard"))]
1328 pub keyboard_disambiguate_escape_codes: bool,
1329
1330 #[serde(default = "default_false")]
1335 #[schemars(extend("x-section" = "Keyboard"))]
1336 pub keyboard_report_event_types: bool,
1337
1338 #[serde(default = "default_true")]
1343 #[schemars(extend("x-section" = "Keyboard"))]
1344 pub keyboard_report_alternate_keys: bool,
1345
1346 #[serde(default = "default_false")]
1352 #[schemars(extend("x-section" = "Keyboard"))]
1353 pub keyboard_report_all_keys_as_escape_codes: bool,
1354
1355 #[serde(default = "default_highlight_timeout")]
1358 #[schemars(extend("x-section" = "Performance"))]
1359 pub highlight_timeout_ms: u64,
1360
1361 #[serde(default = "default_snapshot_interval")]
1363 #[schemars(extend("x-section" = "Performance"))]
1364 pub snapshot_interval: usize,
1365
1366 #[serde(default = "default_highlight_context_bytes")]
1371 #[schemars(extend("x-section" = "Performance"))]
1372 pub highlight_context_bytes: usize,
1373
1374 #[serde(default = "default_large_file_threshold")]
1381 #[schemars(extend("x-section" = "Performance"))]
1382 pub large_file_threshold_bytes: u64,
1383
1384 #[serde(default = "default_estimated_line_length")]
1388 #[schemars(extend("x-section" = "Performance"))]
1389 pub estimated_line_length: usize,
1390
1391 #[serde(default = "default_read_concurrency")]
1396 #[schemars(extend("x-section" = "Performance"))]
1397 pub read_concurrency: usize,
1398
1399 #[serde(default = "default_file_tree_poll_interval")]
1404 #[schemars(extend("x-section" = "Performance"))]
1405 pub file_tree_poll_interval_ms: u64,
1406}
1407
1408fn default_tab_size() -> usize {
1409 4
1410}
1411
1412pub const LARGE_FILE_THRESHOLD_BYTES: u64 = 1024 * 1024; fn default_large_file_threshold() -> u64 {
1418 LARGE_FILE_THRESHOLD_BYTES
1419}
1420
1421pub const INDENT_FOLD_MAX_SCAN_LINES: usize = 10_000;
1424
1425pub const INDENT_FOLD_INDICATOR_MAX_SCAN: usize = 50;
1428
1429pub const INDENT_FOLD_MAX_UPWARD_SCAN: usize = 200;
1432
1433fn default_read_concurrency() -> usize {
1434 64
1435}
1436
1437fn default_true() -> bool {
1438 true
1439}
1440
1441fn default_screensaver_idle_minutes() -> u32 {
1442 5
1443}
1444
1445fn default_false() -> bool {
1446 false
1447}
1448
1449fn default_quick_suggestions_delay() -> u64 {
1450 150 }
1452
1453fn default_scroll_offset() -> usize {
1454 3
1455}
1456
1457fn default_highlight_timeout() -> u64 {
1458 5
1459}
1460
1461fn default_snapshot_interval() -> usize {
1462 100
1463}
1464
1465fn default_estimated_line_length() -> usize {
1466 80
1467}
1468
1469fn default_auto_save_interval() -> u32 {
1470 30 }
1472
1473fn default_auto_recovery_save_interval() -> u32 {
1474 2 }
1476
1477fn default_highlight_context_bytes() -> usize {
1478 10_000 }
1480
1481fn default_mouse_hover_enabled() -> bool {
1482 !cfg!(windows)
1483}
1484
1485fn default_mouse_hover_delay() -> u64 {
1486 500 }
1488
1489fn default_double_click_time() -> u64 {
1490 500 }
1492
1493fn default_auto_revert_poll_interval() -> u64 {
1494 2000 }
1496
1497fn default_file_tree_poll_interval() -> u64 {
1498 3000 }
1500
1501impl Default for EditorConfig {
1502 fn default() -> Self {
1503 Self {
1504 use_tabs: false,
1505 tab_size: default_tab_size(),
1506 auto_indent: true,
1507 auto_close: true,
1508 auto_surround: true,
1509 animations: true,
1510 cursor_jump_animation: true,
1511 line_numbers: true,
1512 relative_line_numbers: false,
1513 scroll_offset: default_scroll_offset(),
1514 syntax_highlighting: true,
1515 highlight_current_line: true,
1516 highlight_occurrences: true,
1517 hide_current_line_on_selection: false,
1518 highlight_current_column: false,
1519 line_wrap: true,
1520 wrap_indent: true,
1521 wrap_column: None,
1522 page_width: default_page_width(),
1523 highlight_timeout_ms: default_highlight_timeout(),
1524 snapshot_interval: default_snapshot_interval(),
1525 large_file_threshold_bytes: default_large_file_threshold(),
1526 estimated_line_length: default_estimated_line_length(),
1527 enable_inlay_hints: true,
1528 enable_semantic_tokens_full: false,
1529 diagnostics_inline_text: false,
1530 auto_save_enabled: false,
1531 auto_save_interval_secs: default_auto_save_interval(),
1532 hot_exit: true,
1533 confirm_quit: false,
1534 restore_previous_session: true,
1535 skip_session_restore_when_files_passed: true,
1536 auto_create_empty_buffer_on_last_buffer_close: true,
1537 recovery_enabled: true,
1538 auto_recovery_save_interval_secs: default_auto_recovery_save_interval(),
1539 highlight_context_bytes: default_highlight_context_bytes(),
1540 mouse_hover_enabled: default_mouse_hover_enabled(),
1541 mouse_hover_delay_ms: default_mouse_hover_delay(),
1542 double_click_time_ms: default_double_click_time(),
1543 auto_revert_poll_interval_ms: default_auto_revert_poll_interval(),
1544 read_concurrency: default_read_concurrency(),
1545 file_tree_poll_interval_ms: default_file_tree_poll_interval(),
1546 default_line_ending: LineEndingOption::default(),
1547 trim_trailing_whitespace_on_save: false,
1548 ensure_final_newline_on_save: false,
1549 auto_read_only: true,
1550 highlight_matching_brackets: true,
1551 rainbow_brackets: true,
1552 cursor_style: CursorStyle::default(),
1553 keyboard_disambiguate_escape_codes: true,
1554 keyboard_report_event_types: false,
1555 keyboard_report_alternate_keys: true,
1556 keyboard_report_all_keys_as_escape_codes: false,
1557 completion_popup_auto_show: false,
1558 quick_suggestions: true,
1559 quick_suggestions_delay_ms: default_quick_suggestions_delay(),
1560 suggest_on_trigger_characters: true,
1561 show_menu_bar: true,
1562 screensaver_enabled: false,
1563 screensaver_idle_minutes: default_screensaver_idle_minutes(),
1564 menu_bar_mnemonics: true,
1565 show_tab_bar: true,
1566 show_status_bar: true,
1567 status_bar: StatusBarConfig::default(),
1568 show_prompt_line: false,
1569 show_vertical_scrollbar: true,
1570 show_horizontal_scrollbar: false,
1571 show_tilde: true,
1572 use_terminal_bg: false,
1573 set_window_title: true,
1574 terminal_auto_title: true,
1575 rulers: Vec::new(),
1576 whitespace_show: true,
1577 whitespace_spaces_leading: false,
1578 whitespace_spaces_inner: false,
1579 whitespace_spaces_trailing: false,
1580 whitespace_tabs_leading: true,
1581 whitespace_tabs_inner: true,
1582 whitespace_tabs_trailing: true,
1583 }
1584 }
1585}
1586
1587#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
1589#[serde(rename_all = "snake_case")]
1590pub enum FileExplorerSide {
1591 #[default]
1592 Left,
1593 Right,
1594}
1595
1596#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1598pub struct FileExplorerConfig {
1599 #[serde(default = "default_true")]
1601 pub respect_gitignore: bool,
1602
1603 #[serde(default = "default_false")]
1605 pub show_hidden: bool,
1606
1607 #[serde(default = "default_false")]
1609 pub show_gitignored: bool,
1610
1611 #[serde(default)]
1613 pub custom_ignore_patterns: Vec<String>,
1614
1615 #[serde(default = "default_explorer_width")]
1621 pub width: ExplorerWidth,
1622
1623 #[serde(default = "default_true")]
1630 pub preview_tabs: bool,
1631
1632 #[serde(default = "default_explorer_side")]
1635 pub side: FileExplorerSide,
1636
1637 #[serde(default = "default_true")]
1643 pub auto_open_on_last_buffer_close: bool,
1644
1645 #[serde(default = "default_false")]
1651 pub follow_active_buffer: bool,
1652
1653 #[serde(default = "default_true")]
1660 pub compact_directories: bool,
1661
1662 #[serde(default = "default_tree_indicator_collapsed")]
1668 pub tree_indicator_collapsed: String,
1669
1670 #[serde(default = "default_tree_indicator_expanded")]
1676 pub tree_indicator_expanded: String,
1677}
1678
1679#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1707pub enum ExplorerWidth {
1708 Percent(u8),
1709 Columns(u16),
1710}
1711
1712impl ExplorerWidth {
1713 pub const DEFAULT: Self = Self::Percent(30);
1715
1716 pub const MIN_COLS: u16 = 5;
1722
1723 pub fn to_cols(self, terminal_width: u16) -> u16 {
1731 let raw = match self {
1732 Self::Percent(pct) => ((terminal_width as u32 * pct as u32) / 100) as u16,
1733 Self::Columns(cols) => cols,
1734 };
1735 raw.max(Self::MIN_COLS).min(terminal_width)
1736 }
1737}
1738
1739impl Default for ExplorerWidth {
1740 fn default() -> Self {
1741 Self::DEFAULT
1742 }
1743}
1744
1745impl std::fmt::Display for ExplorerWidth {
1746 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1747 match self {
1748 Self::Percent(n) => write!(f, "{}%", n),
1749 Self::Columns(n) => write!(f, "{}", n),
1750 }
1751 }
1752}
1753
1754#[derive(Debug)]
1756pub struct ExplorerWidthParseError(String);
1757
1758impl std::fmt::Display for ExplorerWidthParseError {
1759 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1760 write!(f, "{}", self.0)
1761 }
1762}
1763
1764impl std::error::Error for ExplorerWidthParseError {}
1765
1766impl std::str::FromStr for ExplorerWidth {
1767 type Err = ExplorerWidthParseError;
1768
1769 fn from_str(s: &str) -> Result<Self, Self::Err> {
1770 let s = s.trim();
1771 if s.is_empty() {
1772 return Err(ExplorerWidthParseError(
1773 "explorer width: empty string".into(),
1774 ));
1775 }
1776 if let Some(rest) = s.strip_suffix('%') {
1777 let n: u16 = rest.trim().parse().map_err(|_| {
1778 ExplorerWidthParseError(format!("explorer width: {:?} is not a valid percent", s))
1779 })?;
1780 if n > 100 {
1781 return Err(ExplorerWidthParseError(format!(
1782 "explorer width: {}% exceeds 100%",
1783 n
1784 )));
1785 }
1786 Ok(Self::Percent(n as u8))
1787 } else {
1788 let n: u16 = s.parse().map_err(|_| {
1789 ExplorerWidthParseError(format!(
1790 "explorer width: {:?} is neither a percent (e.g. \"30%\") nor a column count (e.g. \"24\")",
1791 s
1792 ))
1793 })?;
1794 Ok(Self::Columns(n))
1795 }
1796 }
1797}
1798
1799impl serde::Serialize for ExplorerWidth {
1800 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1801 s.collect_str(self)
1802 }
1803}
1804
1805impl<'de> serde::Deserialize<'de> for ExplorerWidth {
1806 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
1807 let raw = serde_json::Value::deserialize(d)?;
1808 explorer_width::from_value(&raw)
1809 }
1810}
1811
1812impl schemars::JsonSchema for ExplorerWidth {
1813 fn schema_name() -> std::borrow::Cow<'static, str> {
1814 std::borrow::Cow::Borrowed("ExplorerWidth")
1815 }
1816
1817 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
1818 schemars::json_schema!({
1822 "type": "string",
1823 "pattern": r"^(100%|[1-9]?[0-9]%|\d+)$",
1824 "description": "Either a percent like \"30%\" (0–100) or an absolute column count like \"24\".",
1825 })
1826 }
1827}
1828
1829fn default_explorer_width() -> ExplorerWidth {
1830 ExplorerWidth::DEFAULT
1831}
1832
1833fn default_explorer_side() -> FileExplorerSide {
1834 FileExplorerSide::default()
1835}
1836
1837fn default_tree_indicator_collapsed() -> String {
1838 ">".to_string()
1839}
1840
1841fn default_tree_indicator_expanded() -> String {
1842 "▼".to_string()
1843}
1844
1845pub fn default_explorer_width_value() -> ExplorerWidth {
1847 ExplorerWidth::DEFAULT
1848}
1849
1850pub(crate) mod explorer_width {
1854 use super::ExplorerWidth;
1855 use serde::de::{self, Deserialize, Deserializer};
1856 use std::str::FromStr;
1857
1858 pub fn deserialize_optional<'de, D>(d: D) -> Result<Option<ExplorerWidth>, D::Error>
1863 where
1864 D: Deserializer<'de>,
1865 {
1866 let raw = Option::<serde_json::Value>::deserialize(d)?;
1867 match raw {
1868 None | Some(serde_json::Value::Null) => Ok(None),
1869 Some(v) => from_value(&v).map(Some),
1870 }
1871 }
1872
1873 pub(super) fn from_value<E: de::Error>(v: &serde_json::Value) -> Result<ExplorerWidth, E> {
1874 match v {
1875 serde_json::Value::String(s) => ExplorerWidth::from_str(s).map_err(E::custom),
1876 serde_json::Value::Number(n) => {
1877 if let Some(u) = n.as_u64() {
1878 if u > 100 {
1882 return Err(E::custom(format!(
1883 "explorer width: {} exceeds 100 (percent). Use \"{}\" for columns.",
1884 u, u
1885 )));
1886 }
1887 Ok(ExplorerWidth::Percent(u as u8))
1888 } else if let Some(f) = n.as_f64() {
1889 let pct = if (0.0..=1.0).contains(&f) {
1891 f * 100.0
1892 } else {
1893 f
1894 };
1895 if !(0.0..=100.0).contains(&pct) {
1896 return Err(E::custom(format!(
1897 "explorer width: percent {} out of range 0..=100",
1898 pct
1899 )));
1900 }
1901 Ok(ExplorerWidth::Percent(pct.round() as u8))
1902 } else {
1903 Err(E::custom("explorer width: unsupported number"))
1904 }
1905 }
1906 _ => Err(E::custom(
1907 "explorer width: expected \"30%\", \"24\" (columns), or a number",
1908 )),
1909 }
1910 }
1911}
1912
1913#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1924pub struct ClipboardConfig {
1925 #[serde(default = "default_true")]
1928 pub use_osc52: bool,
1929
1930 #[serde(default = "default_true")]
1933 pub use_system_clipboard: bool,
1934}
1935
1936impl Default for ClipboardConfig {
1937 fn default() -> Self {
1938 Self {
1939 use_osc52: true,
1940 use_system_clipboard: true,
1941 }
1942 }
1943}
1944
1945#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1947pub struct TerminalConfig {
1948 #[serde(default = "default_true")]
1951 pub jump_to_end_on_output: bool,
1952
1953 #[serde(default)]
1965 pub shell: Option<TerminalShellConfig>,
1966
1967 #[serde(default = "default_true")]
1979 pub skip_app_execution_alias: bool,
1980
1981 #[serde(default = "default_true")]
1990 pub resume_agents: bool,
1991}
1992
1993impl Default for TerminalConfig {
1994 fn default() -> Self {
1995 Self {
1996 jump_to_end_on_output: true,
1997 shell: None,
1998 skip_app_execution_alias: true,
1999 resume_agents: true,
2000 }
2001 }
2002}
2003
2004#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2006pub struct TerminalShellConfig {
2007 pub command: String,
2010
2011 #[serde(default)]
2013 pub args: Vec<String>,
2014}
2015
2016#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2018pub struct WarningsConfig {
2019 #[serde(default = "default_true")]
2022 pub show_status_indicator: bool,
2023}
2024
2025impl Default for WarningsConfig {
2026 fn default() -> Self {
2027 Self {
2028 show_status_indicator: true,
2029 }
2030 }
2031}
2032
2033#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2035pub struct PackagesConfig {
2036 #[serde(default = "default_package_sources")]
2039 pub sources: Vec<String>,
2040}
2041
2042fn default_package_sources() -> Vec<String> {
2043 vec!["https://github.com/sinelaw/fresh-plugins-registry".to_string()]
2044}
2045
2046impl Default for PackagesConfig {
2047 fn default() -> Self {
2048 Self {
2049 sources: default_package_sources(),
2050 }
2051 }
2052}
2053
2054pub use fresh_core::config::PluginConfig;
2056
2057impl Default for FileExplorerConfig {
2058 fn default() -> Self {
2059 Self {
2060 respect_gitignore: true,
2061 show_hidden: false,
2062 show_gitignored: false,
2063 custom_ignore_patterns: Vec::new(),
2064 width: default_explorer_width(),
2065 preview_tabs: true,
2066 side: default_explorer_side(),
2067 auto_open_on_last_buffer_close: true,
2068 follow_active_buffer: false,
2069 compact_directories: true,
2070 tree_indicator_collapsed: default_tree_indicator_collapsed(),
2071 tree_indicator_expanded: default_tree_indicator_expanded(),
2072 }
2073 }
2074}
2075
2076#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
2078pub struct FileBrowserConfig {
2079 #[serde(default = "default_false")]
2081 pub show_hidden: bool,
2082}
2083
2084#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2086pub struct KeyPress {
2087 pub key: String,
2089 #[serde(default)]
2091 pub modifiers: Vec<String>,
2092}
2093
2094#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2096#[schemars(extend("x-display-field" = "/action"))]
2097pub struct Keybinding {
2098 #[serde(default, skip_serializing_if = "String::is_empty")]
2100 pub key: String,
2101
2102 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2104 pub modifiers: Vec<String>,
2105
2106 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2109 pub keys: Vec<KeyPress>,
2110
2111 pub action: String,
2113
2114 #[serde(default)]
2116 pub args: HashMap<String, serde_json::Value>,
2117
2118 #[serde(default)]
2120 pub when: Option<String>,
2121}
2122
2123#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2125#[schemars(extend("x-display-field" = "/inherits"))]
2126pub struct KeymapConfig {
2127 #[serde(default, skip_serializing_if = "Option::is_none")]
2129 pub inherits: Option<String>,
2130
2131 #[serde(default)]
2133 pub bindings: Vec<Keybinding>,
2134}
2135
2136#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2138#[schemars(extend("x-display-field" = "/command"))]
2139pub struct FormatterConfig {
2140 pub command: String,
2142
2143 #[serde(default)]
2146 pub args: Vec<String>,
2147
2148 #[serde(default = "default_true")]
2151 pub stdin: bool,
2152
2153 #[serde(default = "default_on_save_timeout")]
2155 pub timeout_ms: u64,
2156}
2157
2158#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2160#[schemars(extend("x-display-field" = "/command"))]
2161pub struct OnSaveAction {
2162 pub command: String,
2165
2166 #[serde(default)]
2169 pub args: Vec<String>,
2170
2171 #[serde(default)]
2173 pub working_dir: Option<String>,
2174
2175 #[serde(default)]
2177 pub stdin: bool,
2178
2179 #[serde(default = "default_on_save_timeout")]
2181 pub timeout_ms: u64,
2182
2183 #[serde(default = "default_true")]
2186 pub enabled: bool,
2187}
2188
2189fn default_on_save_timeout() -> u64 {
2190 10000
2191}
2192
2193fn default_page_width() -> Option<usize> {
2194 Some(80)
2195}
2196
2197#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2199#[schemars(extend("x-display-field" = "/grammar"))]
2200pub struct LanguageConfig {
2201 #[serde(default)]
2203 pub extensions: Vec<String>,
2204
2205 #[serde(default)]
2207 pub filenames: Vec<String>,
2208
2209 #[serde(default)]
2211 pub grammar: String,
2212
2213 #[serde(default)]
2215 pub comment_prefix: Option<String>,
2216
2217 #[serde(default = "default_true")]
2219 pub auto_indent: bool,
2220
2221 #[serde(default)]
2224 pub auto_close: Option<bool>,
2225
2226 #[serde(default)]
2229 pub auto_surround: Option<bool>,
2230
2231 #[serde(default)]
2234 pub textmate_grammar: Option<std::path::PathBuf>,
2235
2236 #[serde(default = "default_true")]
2239 pub show_whitespace_tabs: bool,
2240
2241 #[serde(default)]
2246 pub line_wrap: Option<bool>,
2247
2248 #[serde(default)]
2252 pub wrap_column: Option<usize>,
2253
2254 #[serde(default)]
2259 pub page_view: Option<bool>,
2260
2261 #[serde(default)]
2266 pub page_width: Option<usize>,
2267
2268 #[serde(default)]
2272 pub use_tabs: Option<bool>,
2273
2274 #[serde(default)]
2278 pub tab_size: Option<usize>,
2279
2280 #[serde(default)]
2282 pub formatter: Option<FormatterConfig>,
2283
2284 #[serde(default)]
2286 pub format_on_save: bool,
2287
2288 #[serde(default)]
2292 pub on_save: Vec<OnSaveAction>,
2293
2294 #[serde(default)]
2304 pub word_characters: Option<String>,
2305
2306 #[serde(default)]
2311 pub indent: Option<IndentRulesConfig>,
2312}
2313
2314#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
2329pub struct IndentRulesConfig {
2330 #[serde(default)]
2334 pub increase_indent_pattern: Option<String>,
2335
2336 #[serde(default)]
2339 pub decrease_indent_pattern: Option<String>,
2340
2341 #[serde(default)]
2345 pub indent_next_line_pattern: Option<String>,
2346
2347 #[serde(default)]
2351 pub dedent_next_line_pattern: Option<String>,
2352
2353 #[serde(default)]
2357 pub self_close_pattern: Option<String>,
2358}
2359
2360impl IndentRulesConfig {
2361 pub fn is_empty(&self) -> bool {
2363 self.increase_indent_pattern.is_none()
2364 && self.decrease_indent_pattern.is_none()
2365 && self.indent_next_line_pattern.is_none()
2366 && self.dedent_next_line_pattern.is_none()
2367 && self.self_close_pattern.is_none()
2368 }
2369}
2370
2371#[cfg(any(feature = "runtime", feature = "wasm"))]
2375pub fn reload_indent_overrides(languages: &HashMap<String, LanguageConfig>) {
2376 use crate::primitives::indent_rules;
2377 indent_rules::clear_user_rules();
2378 for (id, lc) in languages {
2379 let Some(ind) = &lc.indent else { continue };
2380 if ind.is_empty() {
2381 continue;
2382 }
2383 indent_rules::set_user_rule(
2384 id,
2385 ind.increase_indent_pattern.as_deref(),
2386 ind.decrease_indent_pattern.as_deref(),
2387 ind.indent_next_line_pattern.as_deref(),
2388 ind.dedent_next_line_pattern.as_deref(),
2389 ind.self_close_pattern.as_deref(),
2390 );
2391 }
2392}
2393
2394#[derive(Debug, Clone)]
2401pub struct BufferConfig {
2402 pub tab_size: usize,
2404
2405 pub use_tabs: bool,
2407
2408 pub auto_indent: bool,
2410
2411 pub auto_close: bool,
2413
2414 pub auto_surround: bool,
2416
2417 pub line_wrap: bool,
2419
2420 pub wrap_column: Option<usize>,
2422
2423 pub whitespace: WhitespaceVisibility,
2425
2426 pub formatter: Option<FormatterConfig>,
2428
2429 pub format_on_save: bool,
2431
2432 pub on_save: Vec<OnSaveAction>,
2434
2435 pub textmate_grammar: Option<std::path::PathBuf>,
2437
2438 pub word_characters: String,
2441}
2442
2443impl BufferConfig {
2444 pub fn resolve(global_config: &Config, language_id: Option<&str>) -> Self {
2453 let editor = &global_config.editor;
2454
2455 let mut whitespace = WhitespaceVisibility::from_editor_config(editor);
2457 let mut config = BufferConfig {
2458 tab_size: editor.tab_size,
2459 use_tabs: editor.use_tabs,
2460 auto_indent: editor.auto_indent,
2461 auto_close: editor.auto_close,
2462 auto_surround: editor.auto_surround,
2463 line_wrap: editor.line_wrap,
2464 wrap_column: editor.wrap_column,
2465 whitespace,
2466 formatter: None,
2467 format_on_save: false,
2468 on_save: Vec::new(),
2469 textmate_grammar: None,
2470 word_characters: String::new(),
2471 };
2472
2473 let lang_config_ref = language_id
2477 .and_then(|id| global_config.languages.get(id))
2478 .or_else(|| {
2479 match language_id {
2481 None | Some("text") => global_config
2482 .default_language
2483 .as_deref()
2484 .and_then(|lang| global_config.languages.get(lang)),
2485 _ => None,
2486 }
2487 });
2488 if let Some(lang_config) = lang_config_ref {
2489 if let Some(ts) = lang_config.tab_size {
2491 config.tab_size = ts;
2492 }
2493
2494 if let Some(use_tabs) = lang_config.use_tabs {
2496 config.use_tabs = use_tabs;
2497 }
2498
2499 if let Some(line_wrap) = lang_config.line_wrap {
2501 config.line_wrap = line_wrap;
2502 }
2503
2504 if lang_config.wrap_column.is_some() {
2506 config.wrap_column = lang_config.wrap_column;
2507 }
2508
2509 config.auto_indent = lang_config.auto_indent;
2511
2512 if config.auto_close {
2514 if let Some(lang_auto_close) = lang_config.auto_close {
2515 config.auto_close = lang_auto_close;
2516 }
2517 }
2518
2519 if config.auto_surround {
2521 if let Some(lang_auto_surround) = lang_config.auto_surround {
2522 config.auto_surround = lang_auto_surround;
2523 }
2524 }
2525
2526 whitespace = whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
2528 config.whitespace = whitespace;
2529
2530 config.formatter = lang_config.formatter.clone();
2532
2533 config.format_on_save = lang_config.format_on_save;
2535
2536 config.on_save = lang_config.on_save.clone();
2538
2539 config.textmate_grammar = lang_config.textmate_grammar.clone();
2541
2542 if let Some(ref wc) = lang_config.word_characters {
2544 config.word_characters = wc.clone();
2545 }
2546 }
2547
2548 config
2549 }
2550
2551 pub fn indent_string(&self) -> String {
2556 if self.use_tabs {
2557 "\t".to_string()
2558 } else {
2559 " ".repeat(self.tab_size)
2560 }
2561 }
2562}
2563
2564#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
2566pub struct MenuConfig {
2567 #[serde(default)]
2569 pub menus: Vec<Menu>,
2570}
2571
2572pub use fresh_core::menu::{Menu, MenuItem};
2574
2575pub trait MenuExt {
2577 fn match_id(&self) -> &str;
2580
2581 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path);
2584}
2585
2586impl MenuExt for Menu {
2587 fn match_id(&self) -> &str {
2588 self.id.as_deref().unwrap_or(&self.label)
2589 }
2590
2591 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path) {
2592 self.items = self
2593 .items
2594 .iter()
2595 .map(|item| item.expand_dynamic(themes_dir))
2596 .collect();
2597 }
2598}
2599
2600pub trait MenuItemExt {
2602 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem;
2605}
2606
2607impl MenuItemExt for MenuItem {
2608 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem {
2609 match self {
2610 MenuItem::DynamicSubmenu { label, source } => {
2611 let items = generate_dynamic_items(source, themes_dir);
2612 MenuItem::Submenu {
2613 label: label.clone(),
2614 items,
2615 }
2616 }
2617 other => other.clone(),
2618 }
2619 }
2620}
2621
2622#[cfg(feature = "runtime")]
2624pub fn generate_dynamic_items(source: &str, themes_dir: &std::path::Path) -> Vec<MenuItem> {
2625 match source {
2626 "copy_with_theme" => {
2627 let loader = crate::view::theme::ThemeLoader::new(themes_dir.to_path_buf());
2629 let registry = loader.load_all(&[]);
2630 registry
2631 .list()
2632 .iter()
2633 .map(|info| {
2634 let mut args = HashMap::new();
2635 args.insert("theme".to_string(), serde_json::json!(info.key));
2636 MenuItem::Action {
2637 label: info.name.clone(),
2638 action: "copy_with_theme".to_string(),
2639 args,
2640 when: Some(context_keys::HAS_SELECTION.to_string()),
2641 checkbox: None,
2642 }
2643 })
2644 .collect()
2645 }
2646 _ => vec![MenuItem::Label {
2647 info: format!("Unknown source: {}", source),
2648 }],
2649 }
2650}
2651
2652#[cfg(not(feature = "runtime"))]
2654pub fn generate_dynamic_items(_source: &str, _themes_dir: &std::path::Path) -> Vec<MenuItem> {
2655 vec![]
2657}
2658
2659impl Default for Config {
2660 fn default() -> Self {
2661 Self {
2662 version: 0,
2663 theme: default_theme_name(),
2664 locale: LocaleName::default(),
2665 check_for_updates: true,
2666 editor: EditorConfig::default(),
2667 file_explorer: FileExplorerConfig::default(),
2668 file_browser: FileBrowserConfig::default(),
2669 clipboard: ClipboardConfig::default(),
2670 terminal: TerminalConfig::default(),
2671 keybindings: vec![], keybinding_maps: HashMap::new(), active_keybinding_map: default_keybinding_map_name(),
2674 languages: Self::default_languages(),
2675 default_language: None,
2676 lsp_enabled: true,
2677 lsp: Self::default_lsp_config(),
2678 universal_lsp: Self::default_universal_lsp_config(),
2679 warnings: WarningsConfig::default(),
2680 plugins: HashMap::new(),
2681 packages: PackagesConfig::default(),
2682 }
2683 }
2684}
2685
2686impl MenuConfig {
2687 pub fn translated() -> Self {
2689 Self {
2690 menus: Self::translated_menus(),
2691 }
2692 }
2693
2694 pub fn translated_menus() -> Vec<Menu> {
2700 vec![
2701 Menu {
2703 id: Some("File".to_string()),
2704 label: t!("menu.file").to_string(),
2705 when: None,
2706 items: vec![
2707 MenuItem::Action {
2708 label: t!("menu.file.new_file").to_string(),
2709 action: "new".to_string(),
2710 args: HashMap::new(),
2711 when: None,
2712 checkbox: None,
2713 },
2714 MenuItem::Action {
2715 label: t!("menu.file.open_file").to_string(),
2716 action: "open".to_string(),
2717 args: HashMap::new(),
2718 when: None,
2719 checkbox: None,
2720 },
2721 MenuItem::Separator { separator: true },
2722 MenuItem::Action {
2723 label: t!("menu.file.save").to_string(),
2724 action: "save".to_string(),
2725 args: HashMap::new(),
2726 when: Some(context_keys::HAS_BUFFER.to_string()),
2727 checkbox: None,
2728 },
2729 MenuItem::Action {
2730 label: t!("menu.file.save_as").to_string(),
2731 action: "save_as".to_string(),
2732 args: HashMap::new(),
2733 when: Some(context_keys::HAS_BUFFER.to_string()),
2734 checkbox: None,
2735 },
2736 MenuItem::Action {
2737 label: t!("menu.file.revert").to_string(),
2738 action: "revert".to_string(),
2739 args: HashMap::new(),
2740 when: Some(context_keys::HAS_BUFFER.to_string()),
2741 checkbox: None,
2742 },
2743 MenuItem::Action {
2744 label: t!("menu.file.reload_with_encoding").to_string(),
2745 action: "reload_with_encoding".to_string(),
2746 args: HashMap::new(),
2747 when: Some(context_keys::HAS_BUFFER.to_string()),
2748 checkbox: None,
2749 },
2750 MenuItem::Separator { separator: true },
2751 MenuItem::Action {
2752 label: t!("menu.file.close_buffer").to_string(),
2753 action: "close".to_string(),
2754 args: HashMap::new(),
2755 when: Some(context_keys::HAS_BUFFER.to_string()),
2756 checkbox: None,
2757 },
2758 MenuItem::Separator { separator: true },
2759 MenuItem::Action {
2760 label: t!("menu.file.switch_project").to_string(),
2761 action: "switch_project".to_string(),
2762 args: HashMap::new(),
2763 when: None,
2764 checkbox: None,
2765 },
2766 MenuItem::Separator { separator: true },
2767 MenuItem::Action {
2768 label: t!("menu.file.detach").to_string(),
2769 action: "detach".to_string(),
2770 args: HashMap::new(),
2771 when: Some(context_keys::SESSION_MODE.to_string()),
2772 checkbox: None,
2773 },
2774 MenuItem::Action {
2775 label: t!("menu.file.quit").to_string(),
2776 action: "quit".to_string(),
2777 args: HashMap::new(),
2778 when: None,
2779 checkbox: None,
2780 },
2781 ],
2782 },
2783 Menu {
2785 id: Some("Edit".to_string()),
2786 label: t!("menu.edit").to_string(),
2787 when: None,
2788 items: vec![
2789 MenuItem::Action {
2790 label: t!("menu.edit.undo").to_string(),
2791 action: "undo".to_string(),
2792 args: HashMap::new(),
2793 when: Some(context_keys::HAS_BUFFER.to_string()),
2794 checkbox: None,
2795 },
2796 MenuItem::Action {
2797 label: t!("menu.edit.redo").to_string(),
2798 action: "redo".to_string(),
2799 args: HashMap::new(),
2800 when: Some(context_keys::HAS_BUFFER.to_string()),
2801 checkbox: None,
2802 },
2803 MenuItem::Separator { separator: true },
2804 MenuItem::Action {
2805 label: t!("menu.edit.cut").to_string(),
2806 action: "cut".to_string(),
2807 args: HashMap::new(),
2808 when: Some(context_keys::CAN_COPY.to_string()),
2809 checkbox: None,
2810 },
2811 MenuItem::Action {
2812 label: t!("menu.edit.copy").to_string(),
2813 action: "copy".to_string(),
2814 args: HashMap::new(),
2815 when: Some(context_keys::CAN_COPY.to_string()),
2816 checkbox: None,
2817 },
2818 MenuItem::DynamicSubmenu {
2819 label: t!("menu.edit.copy_with_formatting").to_string(),
2820 source: "copy_with_theme".to_string(),
2821 },
2822 MenuItem::Action {
2823 label: t!("menu.edit.paste").to_string(),
2824 action: "paste".to_string(),
2825 args: HashMap::new(),
2826 when: Some(context_keys::CAN_PASTE.to_string()),
2827 checkbox: None,
2828 },
2829 MenuItem::Separator { separator: true },
2830 MenuItem::Action {
2831 label: t!("menu.edit.select_all").to_string(),
2832 action: "select_all".to_string(),
2833 args: HashMap::new(),
2834 when: Some(context_keys::HAS_BUFFER.to_string()),
2835 checkbox: None,
2836 },
2837 MenuItem::Separator { separator: true },
2838 MenuItem::Action {
2839 label: t!("menu.edit.find").to_string(),
2840 action: "search".to_string(),
2841 args: HashMap::new(),
2842 when: Some(context_keys::HAS_BUFFER.to_string()),
2843 checkbox: None,
2844 },
2845 MenuItem::Action {
2846 label: t!("menu.edit.find_in_selection").to_string(),
2847 action: "find_in_selection".to_string(),
2848 args: HashMap::new(),
2849 when: Some(context_keys::HAS_SELECTION.to_string()),
2850 checkbox: None,
2851 },
2852 MenuItem::Action {
2853 label: t!("menu.edit.find_next").to_string(),
2854 action: "find_next".to_string(),
2855 args: HashMap::new(),
2856 when: Some(context_keys::HAS_BUFFER.to_string()),
2857 checkbox: None,
2858 },
2859 MenuItem::Action {
2860 label: t!("menu.edit.find_previous").to_string(),
2861 action: "find_previous".to_string(),
2862 args: HashMap::new(),
2863 when: Some(context_keys::HAS_BUFFER.to_string()),
2864 checkbox: None,
2865 },
2866 MenuItem::Action {
2867 label: t!("menu.edit.replace").to_string(),
2868 action: "query_replace".to_string(),
2869 args: HashMap::new(),
2870 when: Some(context_keys::HAS_BUFFER.to_string()),
2871 checkbox: None,
2872 },
2873 MenuItem::Separator { separator: true },
2874 MenuItem::Action {
2875 label: t!("menu.edit.delete_line").to_string(),
2876 action: "delete_line".to_string(),
2877 args: HashMap::new(),
2878 when: Some(context_keys::HAS_BUFFER.to_string()),
2879 checkbox: None,
2880 },
2881 MenuItem::Action {
2882 label: t!("menu.edit.format_buffer").to_string(),
2883 action: "format_buffer".to_string(),
2884 args: HashMap::new(),
2885 when: Some(context_keys::FORMATTER_AVAILABLE.to_string()),
2886 checkbox: None,
2887 },
2888 MenuItem::Separator { separator: true },
2889 MenuItem::Action {
2890 label: t!("menu.edit.settings").to_string(),
2891 action: "open_settings".to_string(),
2892 args: HashMap::new(),
2893 when: None,
2894 checkbox: None,
2895 },
2896 MenuItem::Action {
2897 label: t!("menu.edit.keybinding_editor").to_string(),
2898 action: "open_keybinding_editor".to_string(),
2899 args: HashMap::new(),
2900 when: None,
2901 checkbox: None,
2902 },
2903 ],
2904 },
2905 Menu {
2907 id: Some("View".to_string()),
2908 label: t!("menu.view").to_string(),
2909 when: None,
2910 items: vec![
2911 MenuItem::Action {
2912 label: t!("menu.view.file_explorer").to_string(),
2913 action: "toggle_file_explorer".to_string(),
2914 args: HashMap::new(),
2915 when: None,
2916 checkbox: Some(context_keys::FILE_EXPLORER.to_string()),
2917 },
2918 MenuItem::Separator { separator: true },
2919 MenuItem::Action {
2920 label: t!("menu.view.line_numbers").to_string(),
2921 action: "toggle_line_numbers".to_string(),
2922 args: HashMap::new(),
2923 when: None,
2924 checkbox: Some(context_keys::LINE_NUMBERS.to_string()),
2925 },
2926 MenuItem::Action {
2927 label: t!("menu.view.line_wrap").to_string(),
2928 action: "toggle_line_wrap".to_string(),
2929 args: HashMap::new(),
2930 when: None,
2931 checkbox: Some(context_keys::LINE_WRAP.to_string()),
2932 },
2933 MenuItem::Action {
2934 label: t!("menu.view.mouse_support").to_string(),
2935 action: "toggle_mouse_capture".to_string(),
2936 args: HashMap::new(),
2937 when: None,
2938 checkbox: Some(context_keys::MOUSE_CAPTURE.to_string()),
2939 },
2940 MenuItem::Separator { separator: true },
2941 MenuItem::Action {
2942 label: t!("menu.view.vertical_scrollbar").to_string(),
2943 action: "toggle_vertical_scrollbar".to_string(),
2944 args: HashMap::new(),
2945 when: None,
2946 checkbox: Some(context_keys::VERTICAL_SCROLLBAR.to_string()),
2947 },
2948 MenuItem::Action {
2949 label: t!("menu.view.horizontal_scrollbar").to_string(),
2950 action: "toggle_horizontal_scrollbar".to_string(),
2951 args: HashMap::new(),
2952 when: None,
2953 checkbox: Some(context_keys::HORIZONTAL_SCROLLBAR.to_string()),
2954 },
2955 MenuItem::Separator { separator: true },
2956 MenuItem::Action {
2957 label: t!("menu.view.set_background").to_string(),
2958 action: "set_background".to_string(),
2959 args: HashMap::new(),
2960 when: None,
2961 checkbox: None,
2962 },
2963 MenuItem::Action {
2964 label: t!("menu.view.set_background_blend").to_string(),
2965 action: "set_background_blend".to_string(),
2966 args: HashMap::new(),
2967 when: None,
2968 checkbox: None,
2969 },
2970 MenuItem::Action {
2971 label: t!("menu.view.set_page_width").to_string(),
2972 action: "set_page_width".to_string(),
2973 args: HashMap::new(),
2974 when: None,
2975 checkbox: None,
2976 },
2977 MenuItem::Separator { separator: true },
2978 MenuItem::Action {
2979 label: t!("menu.view.select_theme").to_string(),
2980 action: "select_theme".to_string(),
2981 args: HashMap::new(),
2982 when: None,
2983 checkbox: None,
2984 },
2985 MenuItem::Action {
2986 label: t!("menu.view.select_locale").to_string(),
2987 action: "select_locale".to_string(),
2988 args: HashMap::new(),
2989 when: None,
2990 checkbox: None,
2991 },
2992 MenuItem::Action {
2993 label: t!("menu.view.settings").to_string(),
2994 action: "open_settings".to_string(),
2995 args: HashMap::new(),
2996 when: None,
2997 checkbox: None,
2998 },
2999 MenuItem::Action {
3000 label: t!("menu.view.calibrate_input").to_string(),
3001 action: "calibrate_input".to_string(),
3002 args: HashMap::new(),
3003 when: None,
3004 checkbox: None,
3005 },
3006 MenuItem::Separator { separator: true },
3007 MenuItem::Action {
3008 label: t!("menu.view.split_horizontal").to_string(),
3009 action: "split_horizontal".to_string(),
3010 args: HashMap::new(),
3011 when: Some(context_keys::HAS_BUFFER.to_string()),
3012 checkbox: None,
3013 },
3014 MenuItem::Action {
3015 label: t!("menu.view.split_vertical").to_string(),
3016 action: "split_vertical".to_string(),
3017 args: HashMap::new(),
3018 when: Some(context_keys::HAS_BUFFER.to_string()),
3019 checkbox: None,
3020 },
3021 MenuItem::Action {
3022 label: t!("menu.view.close_split").to_string(),
3023 action: "close_split".to_string(),
3024 args: HashMap::new(),
3025 when: Some(context_keys::HAS_BUFFER.to_string()),
3026 checkbox: None,
3027 },
3028 MenuItem::Action {
3029 label: t!("menu.view.scroll_sync").to_string(),
3030 action: "toggle_scroll_sync".to_string(),
3031 args: HashMap::new(),
3032 when: Some(context_keys::HAS_SAME_BUFFER_SPLITS.to_string()),
3033 checkbox: Some(context_keys::SCROLL_SYNC.to_string()),
3034 },
3035 MenuItem::Action {
3036 label: t!("menu.view.focus_next_split").to_string(),
3037 action: "next_split".to_string(),
3038 args: HashMap::new(),
3039 when: None,
3040 checkbox: None,
3041 },
3042 MenuItem::Action {
3043 label: t!("menu.view.focus_prev_split").to_string(),
3044 action: "prev_split".to_string(),
3045 args: HashMap::new(),
3046 when: None,
3047 checkbox: None,
3048 },
3049 MenuItem::Action {
3050 label: t!("menu.view.toggle_maximize_split").to_string(),
3051 action: "toggle_maximize_split".to_string(),
3052 args: HashMap::new(),
3053 when: None,
3054 checkbox: None,
3055 },
3056 MenuItem::Separator { separator: true },
3057 MenuItem::Submenu {
3058 label: t!("menu.terminal").to_string(),
3059 items: vec![
3060 MenuItem::Action {
3061 label: t!("menu.terminal.open").to_string(),
3062 action: "open_terminal".to_string(),
3063 args: HashMap::new(),
3064 when: None,
3065 checkbox: None,
3066 },
3067 MenuItem::Action {
3068 label: t!("menu.terminal.close").to_string(),
3069 action: "close_terminal".to_string(),
3070 args: HashMap::new(),
3071 when: None,
3072 checkbox: None,
3073 },
3074 MenuItem::Action {
3075 label: t!("menu.terminal.send_selection").to_string(),
3076 action: "send_selection_to_terminal".to_string(),
3077 args: HashMap::new(),
3078 when: None,
3079 checkbox: None,
3080 },
3081 MenuItem::Separator { separator: true },
3082 MenuItem::Action {
3083 label: t!("menu.terminal.toggle_keyboard_capture").to_string(),
3084 action: "toggle_keyboard_capture".to_string(),
3085 args: HashMap::new(),
3086 when: None,
3087 checkbox: None,
3088 },
3089 ],
3090 },
3091 MenuItem::Separator { separator: true },
3092 MenuItem::Submenu {
3093 label: t!("menu.view.keybinding_style").to_string(),
3094 items: vec![
3095 MenuItem::Action {
3096 label: t!("menu.view.keybinding_default").to_string(),
3097 action: "switch_keybinding_map".to_string(),
3098 args: {
3099 let mut map = HashMap::new();
3100 map.insert("map".to_string(), serde_json::json!("default"));
3101 map
3102 },
3103 when: None,
3104 checkbox: Some(context_keys::KEYMAP_DEFAULT.to_string()),
3105 },
3106 MenuItem::Action {
3107 label: t!("menu.view.keybinding_emacs").to_string(),
3108 action: "switch_keybinding_map".to_string(),
3109 args: {
3110 let mut map = HashMap::new();
3111 map.insert("map".to_string(), serde_json::json!("emacs"));
3112 map
3113 },
3114 when: None,
3115 checkbox: Some(context_keys::KEYMAP_EMACS.to_string()),
3116 },
3117 MenuItem::Action {
3118 label: t!("menu.view.keybinding_vscode").to_string(),
3119 action: "switch_keybinding_map".to_string(),
3120 args: {
3121 let mut map = HashMap::new();
3122 map.insert("map".to_string(), serde_json::json!("vscode"));
3123 map
3124 },
3125 when: None,
3126 checkbox: Some(context_keys::KEYMAP_VSCODE.to_string()),
3127 },
3128 MenuItem::Action {
3129 label: "macOS GUI (⌘)".to_string(),
3130 action: "switch_keybinding_map".to_string(),
3131 args: {
3132 let mut map = HashMap::new();
3133 map.insert("map".to_string(), serde_json::json!("macos-gui"));
3134 map
3135 },
3136 when: None,
3137 checkbox: Some(context_keys::KEYMAP_MACOS_GUI.to_string()),
3138 },
3139 ],
3140 },
3141 ],
3142 },
3143 Menu {
3145 id: Some("Selection".to_string()),
3146 label: t!("menu.selection").to_string(),
3147 when: Some(context_keys::HAS_BUFFER.to_string()),
3148 items: vec![
3149 MenuItem::Action {
3150 label: t!("menu.selection.select_all").to_string(),
3151 action: "select_all".to_string(),
3152 args: HashMap::new(),
3153 when: None,
3154 checkbox: None,
3155 },
3156 MenuItem::Action {
3157 label: t!("menu.selection.select_word").to_string(),
3158 action: "select_word".to_string(),
3159 args: HashMap::new(),
3160 when: None,
3161 checkbox: None,
3162 },
3163 MenuItem::Action {
3164 label: t!("menu.selection.select_line").to_string(),
3165 action: "select_line".to_string(),
3166 args: HashMap::new(),
3167 when: None,
3168 checkbox: None,
3169 },
3170 MenuItem::Action {
3171 label: t!("menu.selection.expand_selection").to_string(),
3172 action: "expand_selection".to_string(),
3173 args: HashMap::new(),
3174 when: None,
3175 checkbox: None,
3176 },
3177 MenuItem::Separator { separator: true },
3178 MenuItem::Action {
3179 label: t!("menu.selection.add_cursor_above").to_string(),
3180 action: "add_cursor_above".to_string(),
3181 args: HashMap::new(),
3182 when: None,
3183 checkbox: None,
3184 },
3185 MenuItem::Action {
3186 label: t!("menu.selection.add_cursor_below").to_string(),
3187 action: "add_cursor_below".to_string(),
3188 args: HashMap::new(),
3189 when: None,
3190 checkbox: None,
3191 },
3192 MenuItem::Action {
3193 label: t!("menu.selection.add_cursor_next_match").to_string(),
3194 action: "add_cursor_next_match".to_string(),
3195 args: HashMap::new(),
3196 when: None,
3197 checkbox: None,
3198 },
3199 MenuItem::Action {
3200 label: t!("menu.selection.add_cursors_to_line_ends").to_string(),
3201 action: "add_cursors_to_line_ends".to_string(),
3202 args: HashMap::new(),
3203 when: None,
3204 checkbox: None,
3205 },
3206 MenuItem::Action {
3207 label: t!("menu.selection.remove_secondary_cursors").to_string(),
3208 action: "remove_secondary_cursors".to_string(),
3209 args: HashMap::new(),
3210 when: None,
3211 checkbox: None,
3212 },
3213 ],
3214 },
3215 Menu {
3217 id: Some("Go".to_string()),
3218 label: t!("menu.go").to_string(),
3219 when: None,
3220 items: vec![
3221 MenuItem::Action {
3222 label: t!("menu.go.goto_line").to_string(),
3223 action: "goto_line".to_string(),
3224 args: HashMap::new(),
3225 when: Some(context_keys::HAS_BUFFER.to_string()),
3226 checkbox: None,
3227 },
3228 MenuItem::Action {
3229 label: t!("menu.go.goto_definition").to_string(),
3230 action: "lsp_goto_definition".to_string(),
3231 args: HashMap::new(),
3232 when: Some(context_keys::HAS_BUFFER.to_string()),
3233 checkbox: None,
3234 },
3235 MenuItem::Action {
3236 label: t!("menu.go.find_references").to_string(),
3237 action: "lsp_references".to_string(),
3238 args: HashMap::new(),
3239 when: Some(context_keys::HAS_BUFFER.to_string()),
3240 checkbox: None,
3241 },
3242 MenuItem::Separator { separator: true },
3243 MenuItem::Action {
3244 label: t!("menu.go.next_buffer").to_string(),
3245 action: "next_buffer".to_string(),
3246 args: HashMap::new(),
3247 when: Some(context_keys::HAS_BUFFER.to_string()),
3248 checkbox: None,
3249 },
3250 MenuItem::Action {
3251 label: t!("menu.go.prev_buffer").to_string(),
3252 action: "prev_buffer".to_string(),
3253 args: HashMap::new(),
3254 when: Some(context_keys::HAS_BUFFER.to_string()),
3255 checkbox: None,
3256 },
3257 MenuItem::Separator { separator: true },
3258 MenuItem::Action {
3259 label: t!("menu.go.command_palette").to_string(),
3260 action: "command_palette".to_string(),
3261 args: HashMap::new(),
3262 when: None,
3263 checkbox: None,
3264 },
3265 ],
3266 },
3267 Menu {
3269 id: Some("LSP".to_string()),
3270 label: t!("menu.lsp").to_string(),
3271 when: None,
3272 items: vec![
3273 MenuItem::Action {
3274 label: t!("menu.lsp.show_hover").to_string(),
3275 action: "lsp_hover".to_string(),
3276 args: HashMap::new(),
3277 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3278 checkbox: None,
3279 },
3280 MenuItem::Action {
3281 label: t!("menu.lsp.goto_definition").to_string(),
3282 action: "lsp_goto_definition".to_string(),
3283 args: HashMap::new(),
3284 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3285 checkbox: None,
3286 },
3287 MenuItem::Action {
3288 label: t!("menu.lsp.find_references").to_string(),
3289 action: "lsp_references".to_string(),
3290 args: HashMap::new(),
3291 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3292 checkbox: None,
3293 },
3294 MenuItem::Action {
3295 label: t!("menu.lsp.rename_symbol").to_string(),
3296 action: "lsp_rename".to_string(),
3297 args: HashMap::new(),
3298 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3299 checkbox: None,
3300 },
3301 MenuItem::Separator { separator: true },
3302 MenuItem::Action {
3303 label: t!("menu.lsp.show_completions").to_string(),
3304 action: "lsp_completion".to_string(),
3305 args: HashMap::new(),
3306 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3307 checkbox: None,
3308 },
3309 MenuItem::Action {
3310 label: t!("menu.lsp.show_signature").to_string(),
3311 action: "lsp_signature_help".to_string(),
3312 args: HashMap::new(),
3313 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3314 checkbox: None,
3315 },
3316 MenuItem::Action {
3317 label: t!("menu.lsp.code_actions").to_string(),
3318 action: "lsp_code_actions".to_string(),
3319 args: HashMap::new(),
3320 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3321 checkbox: None,
3322 },
3323 MenuItem::Separator { separator: true },
3324 MenuItem::Action {
3325 label: t!("menu.lsp.toggle_inlay_hints").to_string(),
3326 action: "toggle_inlay_hints".to_string(),
3327 args: HashMap::new(),
3328 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3329 checkbox: Some(context_keys::INLAY_HINTS.to_string()),
3330 },
3331 MenuItem::Action {
3332 label: t!("menu.lsp.toggle_mouse_hover").to_string(),
3333 action: "toggle_mouse_hover".to_string(),
3334 args: HashMap::new(),
3335 when: None,
3336 checkbox: Some(context_keys::MOUSE_HOVER.to_string()),
3337 },
3338 MenuItem::Separator { separator: true },
3339 MenuItem::Action {
3340 label: t!("menu.lsp.show_status").to_string(),
3341 action: "show_lsp_status".to_string(),
3342 args: HashMap::new(),
3343 when: None,
3344 checkbox: None,
3345 },
3346 MenuItem::Action {
3347 label: t!("menu.lsp.restart_server").to_string(),
3348 action: "lsp_restart".to_string(),
3349 args: HashMap::new(),
3350 when: None,
3351 checkbox: None,
3352 },
3353 MenuItem::Action {
3354 label: t!("menu.lsp.stop_server").to_string(),
3355 action: "lsp_stop".to_string(),
3356 args: HashMap::new(),
3357 when: None,
3358 checkbox: None,
3359 },
3360 MenuItem::Separator { separator: true },
3361 MenuItem::Action {
3362 label: t!("menu.lsp.toggle_for_buffer").to_string(),
3363 action: "lsp_toggle_for_buffer".to_string(),
3364 args: HashMap::new(),
3365 when: Some(context_keys::HAS_BUFFER.to_string()),
3366 checkbox: None,
3367 },
3368 ],
3369 },
3370 Menu {
3372 id: Some("Explorer".to_string()),
3373 label: t!("menu.explorer").to_string(),
3374 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3375 items: vec![
3376 MenuItem::Action {
3377 label: t!("menu.explorer.new_file").to_string(),
3378 action: "file_explorer_new_file".to_string(),
3379 args: HashMap::new(),
3380 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3381 checkbox: None,
3382 },
3383 MenuItem::Action {
3384 label: t!("menu.explorer.new_folder").to_string(),
3385 action: "file_explorer_new_directory".to_string(),
3386 args: HashMap::new(),
3387 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3388 checkbox: None,
3389 },
3390 MenuItem::Separator { separator: true },
3391 MenuItem::Action {
3392 label: t!("menu.explorer.open").to_string(),
3393 action: "file_explorer_open".to_string(),
3394 args: HashMap::new(),
3395 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3396 checkbox: None,
3397 },
3398 MenuItem::Action {
3399 label: t!("menu.explorer.rename").to_string(),
3400 action: "file_explorer_rename".to_string(),
3401 args: HashMap::new(),
3402 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3403 checkbox: None,
3404 },
3405 MenuItem::Action {
3406 label: t!("menu.explorer.delete").to_string(),
3407 action: "file_explorer_delete".to_string(),
3408 args: HashMap::new(),
3409 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3410 checkbox: None,
3411 },
3412 MenuItem::Separator { separator: true },
3413 MenuItem::Action {
3414 label: t!("menu.explorer.cut").to_string(),
3415 action: "cut".to_string(),
3416 args: HashMap::new(),
3417 when: Some(context_keys::CAN_COPY.to_string()),
3418 checkbox: None,
3419 },
3420 MenuItem::Action {
3421 label: t!("menu.explorer.copy").to_string(),
3422 action: "copy".to_string(),
3423 args: HashMap::new(),
3424 when: Some(context_keys::CAN_COPY.to_string()),
3425 checkbox: None,
3426 },
3427 MenuItem::Action {
3428 label: t!("menu.explorer.paste").to_string(),
3429 action: "paste".to_string(),
3430 args: HashMap::new(),
3431 when: Some(context_keys::CAN_PASTE.to_string()),
3432 checkbox: None,
3433 },
3434 MenuItem::Separator { separator: true },
3435 MenuItem::Action {
3436 label: t!("menu.explorer.refresh").to_string(),
3437 action: "file_explorer_refresh".to_string(),
3438 args: HashMap::new(),
3439 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3440 checkbox: None,
3441 },
3442 MenuItem::Separator { separator: true },
3443 MenuItem::Action {
3444 label: t!("menu.explorer.show_hidden").to_string(),
3445 action: "file_explorer_toggle_hidden".to_string(),
3446 args: HashMap::new(),
3447 when: Some(context_keys::FILE_EXPLORER.to_string()),
3448 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_HIDDEN.to_string()),
3449 },
3450 MenuItem::Action {
3451 label: t!("menu.explorer.show_gitignored").to_string(),
3452 action: "file_explorer_toggle_gitignored".to_string(),
3453 args: HashMap::new(),
3454 when: Some(context_keys::FILE_EXPLORER.to_string()),
3455 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_GITIGNORED.to_string()),
3456 },
3457 ],
3458 },
3459 Menu {
3461 id: Some("Help".to_string()),
3462 label: t!("menu.help").to_string(),
3463 when: None,
3464 items: vec![
3465 MenuItem::Label {
3466 info: format!("Fresh v{}", env!("CARGO_PKG_VERSION")),
3467 },
3468 MenuItem::Separator { separator: true },
3469 MenuItem::Action {
3470 label: t!("menu.help.show_manual").to_string(),
3471 action: "show_help".to_string(),
3472 args: HashMap::new(),
3473 when: None,
3474 checkbox: None,
3475 },
3476 MenuItem::Action {
3477 label: t!("menu.help.keyboard_shortcuts").to_string(),
3478 action: "keyboard_shortcuts".to_string(),
3479 args: HashMap::new(),
3480 when: None,
3481 checkbox: None,
3482 },
3483 MenuItem::Separator { separator: true },
3484 MenuItem::Action {
3485 label: t!("menu.help.event_debug").to_string(),
3486 action: "event_debug".to_string(),
3487 args: HashMap::new(),
3488 when: None,
3489 checkbox: None,
3490 },
3491 ],
3492 },
3493 ]
3494 }
3495}
3496
3497impl Config {
3498 pub(crate) const FILENAME: &'static str = "config.json";
3500
3501 pub(crate) fn normalize_zero_sentinels(&mut self) {
3515 if self.editor.wrap_column == Some(0) {
3516 self.editor.wrap_column = None;
3517 }
3518 if self.editor.page_width == Some(0) {
3519 self.editor.page_width = None;
3520 }
3521 if self.editor.tab_size == 0 {
3522 self.editor.tab_size = default_tab_size();
3523 }
3524 for lang in self.languages.values_mut() {
3525 if lang.wrap_column == Some(0) {
3526 lang.wrap_column = None;
3527 }
3528 if lang.page_width == Some(0) {
3529 lang.page_width = None;
3530 }
3531 if lang.tab_size == Some(0) {
3532 lang.tab_size = None;
3533 }
3534 }
3535 }
3536
3537 pub fn apply_runtime_flags(&self) {
3542 #[cfg(windows)]
3543 {
3544 crate::services::terminal::set_skip_app_execution_alias(
3545 self.terminal.skip_app_execution_alias,
3546 );
3547 }
3548 }
3549
3550 pub(crate) fn local_config_path(working_dir: &Path) -> std::path::PathBuf {
3552 working_dir.join(Self::FILENAME)
3553 }
3554
3555 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
3561 let contents = std::fs::read_to_string(path.as_ref())
3562 .map_err(|e| ConfigError::IoError(e.to_string()))?;
3563
3564 let partial: crate::partial_config::PartialConfig =
3566 serde_json::from_str(&contents).map_err(|e| ConfigError::ParseError(e.to_string()))?;
3567
3568 Ok(partial.resolve())
3569 }
3570
3571 fn load_builtin_keymap(name: &str) -> Option<KeymapConfig> {
3573 let json_content = match name {
3574 "default" => include_str!("../keymaps/default.json"),
3575 "emacs" => include_str!("../keymaps/emacs.json"),
3576 "vscode" => include_str!("../keymaps/vscode.json"),
3577 "macos" => include_str!("../keymaps/macos.json"),
3578 "macos-gui" => include_str!("../keymaps/macos-gui.json"),
3579 _ => return None,
3580 };
3581
3582 match serde_json::from_str(json_content) {
3583 Ok(config) => Some(config),
3584 Err(e) => {
3585 eprintln!("Failed to parse builtin keymap '{}': {}", name, e);
3586 None
3587 }
3588 }
3589 }
3590
3591 pub fn resolve_keymap(&self, map_name: &str) -> Vec<Keybinding> {
3594 let mut visited = std::collections::HashSet::new();
3595 self.resolve_keymap_recursive(map_name, &mut visited)
3596 }
3597
3598 fn resolve_keymap_recursive(
3600 &self,
3601 map_name: &str,
3602 visited: &mut std::collections::HashSet<String>,
3603 ) -> Vec<Keybinding> {
3604 if visited.contains(map_name) {
3606 eprintln!(
3607 "Warning: Circular inheritance detected in keymap '{}'",
3608 map_name
3609 );
3610 return Vec::new();
3611 }
3612 visited.insert(map_name.to_string());
3613
3614 let keymap = self
3616 .keybinding_maps
3617 .get(map_name)
3618 .cloned()
3619 .or_else(|| Self::load_builtin_keymap(map_name));
3620
3621 let Some(keymap) = keymap else {
3622 return Vec::new();
3623 };
3624
3625 let mut all_bindings = if let Some(ref parent_name) = keymap.inherits {
3627 self.resolve_keymap_recursive(parent_name, visited)
3628 } else {
3629 Vec::new()
3630 };
3631
3632 all_bindings.extend(keymap.bindings);
3634
3635 all_bindings
3636 }
3637 fn default_languages() -> HashMap<String, LanguageConfig> {
3639 let mut languages = HashMap::new();
3640
3641 languages.insert(
3642 "rust".to_string(),
3643 LanguageConfig {
3644 extensions: vec!["rs".to_string()],
3645 filenames: vec![],
3646 grammar: "rust".to_string(),
3647 comment_prefix: Some("//".to_string()),
3648 auto_indent: true,
3649 auto_close: None,
3650 auto_surround: None,
3651 textmate_grammar: None,
3652 show_whitespace_tabs: true,
3653 line_wrap: None,
3654 wrap_column: None,
3655 page_view: None,
3656 page_width: None,
3657 use_tabs: None,
3658 tab_size: None,
3659 formatter: Some(FormatterConfig {
3660 command: "rustfmt".to_string(),
3661 args: vec!["--edition".to_string(), "2021".to_string()],
3662 stdin: true,
3663 timeout_ms: 10000,
3664 }),
3665 format_on_save: false,
3666 on_save: vec![],
3667 word_characters: None,
3668 indent: None,
3669 },
3670 );
3671
3672 languages.insert(
3673 "javascript".to_string(),
3674 LanguageConfig {
3675 extensions: vec!["js".to_string(), "jsx".to_string(), "mjs".to_string()],
3676 filenames: vec![],
3677 grammar: "javascript".to_string(),
3678 comment_prefix: Some("//".to_string()),
3679 auto_indent: true,
3680 auto_close: None,
3681 auto_surround: None,
3682 textmate_grammar: None,
3683 show_whitespace_tabs: true,
3684 line_wrap: None,
3685 wrap_column: None,
3686 page_view: None,
3687 page_width: None,
3688 use_tabs: None,
3689 tab_size: None,
3690 formatter: Some(FormatterConfig {
3691 command: "prettier".to_string(),
3692 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3693 stdin: true,
3694 timeout_ms: 10000,
3695 }),
3696 format_on_save: false,
3697 on_save: vec![],
3698 word_characters: None,
3699 indent: None,
3700 },
3701 );
3702
3703 languages.insert(
3704 "typescript".to_string(),
3705 LanguageConfig {
3706 extensions: vec!["ts".to_string(), "tsx".to_string(), "mts".to_string()],
3707 filenames: vec![],
3708 grammar: "typescript".to_string(),
3709 comment_prefix: Some("//".to_string()),
3710 auto_indent: true,
3711 auto_close: None,
3712 auto_surround: None,
3713 textmate_grammar: None,
3714 show_whitespace_tabs: true,
3715 line_wrap: None,
3716 wrap_column: None,
3717 page_view: None,
3718 page_width: None,
3719 use_tabs: None,
3720 tab_size: None,
3721 formatter: Some(FormatterConfig {
3722 command: "prettier".to_string(),
3723 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3724 stdin: true,
3725 timeout_ms: 10000,
3726 }),
3727 format_on_save: false,
3728 on_save: vec![],
3729 word_characters: None,
3730 indent: None,
3731 },
3732 );
3733
3734 languages.insert(
3735 "python".to_string(),
3736 LanguageConfig {
3737 extensions: vec!["py".to_string(), "pyi".to_string()],
3738 filenames: vec![],
3739 grammar: "python".to_string(),
3740 comment_prefix: Some("#".to_string()),
3741 auto_indent: true,
3742 auto_close: None,
3743 auto_surround: None,
3744 textmate_grammar: None,
3745 show_whitespace_tabs: true,
3746 line_wrap: None,
3747 wrap_column: None,
3748 page_view: None,
3749 page_width: None,
3750 use_tabs: None,
3751 tab_size: None,
3752 formatter: Some(FormatterConfig {
3753 command: "ruff".to_string(),
3754 args: vec![
3755 "format".to_string(),
3756 "--stdin-filename".to_string(),
3757 "$FILE".to_string(),
3758 ],
3759 stdin: true,
3760 timeout_ms: 10000,
3761 }),
3762 format_on_save: false,
3763 on_save: vec![],
3764 word_characters: None,
3765 indent: None,
3766 },
3767 );
3768
3769 languages.insert(
3770 "gdscript".to_string(),
3771 LanguageConfig {
3772 extensions: vec!["gd".to_string()],
3773 filenames: vec![],
3774 grammar: "gdscript".to_string(),
3775 comment_prefix: Some("#".to_string()),
3776 auto_indent: true,
3777 auto_close: None,
3778 auto_surround: None,
3779 textmate_grammar: None,
3780 show_whitespace_tabs: true,
3781 line_wrap: None,
3782 wrap_column: None,
3783 page_view: None,
3784 page_width: None,
3785 use_tabs: None,
3786 tab_size: None,
3787 formatter: None,
3788 format_on_save: false,
3789 on_save: vec![],
3790 word_characters: None,
3791 indent: None,
3792 },
3793 );
3794
3795 languages.insert(
3796 "c".to_string(),
3797 LanguageConfig {
3798 extensions: vec!["c".to_string(), "h".to_string()],
3799 filenames: vec![],
3800 grammar: "c".to_string(),
3801 comment_prefix: Some("//".to_string()),
3802 auto_indent: true,
3803 auto_close: None,
3804 auto_surround: None,
3805 textmate_grammar: None,
3806 show_whitespace_tabs: true,
3807 line_wrap: None,
3808 wrap_column: None,
3809 page_view: None,
3810 page_width: None,
3811 use_tabs: None,
3812 tab_size: None,
3813 formatter: Some(FormatterConfig {
3814 command: "clang-format".to_string(),
3815 args: vec![],
3816 stdin: true,
3817 timeout_ms: 10000,
3818 }),
3819 format_on_save: false,
3820 on_save: vec![],
3821 word_characters: None,
3822 indent: None,
3823 },
3824 );
3825
3826 languages.insert(
3827 "cpp".to_string(),
3828 LanguageConfig {
3829 extensions: vec![
3830 "cpp".to_string(),
3831 "cc".to_string(),
3832 "cxx".to_string(),
3833 "hpp".to_string(),
3834 "hh".to_string(),
3835 "hxx".to_string(),
3836 ],
3837 filenames: vec![],
3838 grammar: "cpp".to_string(),
3839 comment_prefix: Some("//".to_string()),
3840 auto_indent: true,
3841 auto_close: None,
3842 auto_surround: None,
3843 textmate_grammar: None,
3844 show_whitespace_tabs: true,
3845 line_wrap: None,
3846 wrap_column: None,
3847 page_view: None,
3848 page_width: None,
3849 use_tabs: None,
3850 tab_size: None,
3851 formatter: Some(FormatterConfig {
3852 command: "clang-format".to_string(),
3853 args: vec![],
3854 stdin: true,
3855 timeout_ms: 10000,
3856 }),
3857 format_on_save: false,
3858 on_save: vec![],
3859 word_characters: None,
3860 indent: None,
3861 },
3862 );
3863
3864 languages.insert(
3865 "csharp".to_string(),
3866 LanguageConfig {
3867 extensions: vec!["cs".to_string()],
3868 filenames: vec![],
3869 grammar: "C#".to_string(),
3870 comment_prefix: Some("//".to_string()),
3871 auto_indent: true,
3872 auto_close: None,
3873 auto_surround: None,
3874 textmate_grammar: None,
3875 show_whitespace_tabs: true,
3876 line_wrap: None,
3877 wrap_column: None,
3878 page_view: None,
3879 page_width: None,
3880 use_tabs: None,
3881 tab_size: None,
3882 formatter: None,
3883 format_on_save: false,
3884 on_save: vec![],
3885 word_characters: None,
3886 indent: None,
3887 },
3888 );
3889
3890 languages.insert(
3891 "bash".to_string(),
3892 LanguageConfig {
3893 extensions: vec!["sh".to_string(), "bash".to_string()],
3894 filenames: vec![
3895 ".bash_aliases".to_string(),
3896 ".bash_logout".to_string(),
3897 ".bash_profile".to_string(),
3898 ".bashrc".to_string(),
3899 ".env".to_string(),
3900 ".profile".to_string(),
3901 ".zlogin".to_string(),
3902 ".zlogout".to_string(),
3903 ".zprofile".to_string(),
3904 ".zshenv".to_string(),
3905 ".zshrc".to_string(),
3906 "PKGBUILD".to_string(),
3908 "APKBUILD".to_string(),
3909 ],
3910 grammar: "bash".to_string(),
3911 comment_prefix: Some("#".to_string()),
3912 auto_indent: true,
3913 auto_close: None,
3914 auto_surround: None,
3915 textmate_grammar: None,
3916 show_whitespace_tabs: true,
3917 line_wrap: None,
3918 wrap_column: None,
3919 page_view: None,
3920 page_width: None,
3921 use_tabs: None,
3922 tab_size: None,
3923 formatter: None,
3924 format_on_save: false,
3925 on_save: vec![],
3926 word_characters: None,
3927 indent: None,
3928 },
3929 );
3930
3931 languages.insert(
3932 "makefile".to_string(),
3933 LanguageConfig {
3934 extensions: vec!["mk".to_string()],
3935 filenames: vec![
3936 "Makefile".to_string(),
3937 "makefile".to_string(),
3938 "GNUmakefile".to_string(),
3939 ],
3940 grammar: "Makefile".to_string(),
3941 comment_prefix: Some("#".to_string()),
3942 auto_indent: false,
3943 auto_close: None,
3944 auto_surround: None,
3945 textmate_grammar: None,
3946 show_whitespace_tabs: true,
3947 line_wrap: None,
3948 wrap_column: None,
3949 page_view: None,
3950 page_width: None,
3951 use_tabs: Some(true), tab_size: Some(8), formatter: None,
3954 format_on_save: false,
3955 on_save: vec![],
3956 word_characters: None,
3957 indent: None,
3958 },
3959 );
3960
3961 languages.insert(
3962 "dockerfile".to_string(),
3963 LanguageConfig {
3964 extensions: vec!["dockerfile".to_string()],
3965 filenames: vec!["Dockerfile".to_string(), "Containerfile".to_string()],
3966 grammar: "dockerfile".to_string(),
3967 comment_prefix: Some("#".to_string()),
3968 auto_indent: true,
3969 auto_close: None,
3970 auto_surround: None,
3971 textmate_grammar: None,
3972 show_whitespace_tabs: true,
3973 line_wrap: None,
3974 wrap_column: None,
3975 page_view: None,
3976 page_width: None,
3977 use_tabs: None,
3978 tab_size: None,
3979 formatter: None,
3980 format_on_save: false,
3981 on_save: vec![],
3982 word_characters: None,
3983 indent: None,
3984 },
3985 );
3986
3987 languages.insert(
3988 "json".to_string(),
3989 LanguageConfig {
3990 extensions: vec!["json".to_string()],
3991 filenames: vec![],
3992 grammar: "json".to_string(),
3993 comment_prefix: None,
3994 auto_indent: true,
3995 auto_close: None,
3996 auto_surround: None,
3997 textmate_grammar: None,
3998 show_whitespace_tabs: true,
3999 line_wrap: None,
4000 wrap_column: None,
4001 page_view: None,
4002 page_width: None,
4003 use_tabs: None,
4004 tab_size: None,
4005 formatter: Some(FormatterConfig {
4006 command: "prettier".to_string(),
4007 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
4008 stdin: true,
4009 timeout_ms: 10000,
4010 }),
4011 format_on_save: false,
4012 on_save: vec![],
4013 word_characters: None,
4014 indent: None,
4015 },
4016 );
4017
4018 languages.insert(
4025 "jsonc".to_string(),
4026 LanguageConfig {
4027 extensions: vec!["jsonc".to_string()],
4028 filenames: vec![
4029 "devcontainer.json".to_string(),
4030 ".devcontainer.json".to_string(),
4031 "tsconfig.json".to_string(),
4032 "tsconfig.*.json".to_string(),
4033 "jsconfig.json".to_string(),
4034 "jsconfig.*.json".to_string(),
4035 ".eslintrc.json".to_string(),
4036 ".babelrc".to_string(),
4037 ".babelrc.json".to_string(),
4038 ".swcrc".to_string(),
4039 ".jshintrc".to_string(),
4040 ".hintrc".to_string(),
4041 "settings.json".to_string(),
4042 "keybindings.json".to_string(),
4043 "tasks.json".to_string(),
4044 "launch.json".to_string(),
4045 "extensions.json".to_string(),
4046 "argv.json".to_string(),
4047 ],
4048 grammar: "jsonc".to_string(),
4049 comment_prefix: Some("//".to_string()),
4050 auto_indent: true,
4051 auto_close: None,
4052 auto_surround: None,
4053 textmate_grammar: None,
4054 show_whitespace_tabs: true,
4055 line_wrap: None,
4056 wrap_column: None,
4057 page_view: None,
4058 page_width: None,
4059 use_tabs: None,
4060 tab_size: None,
4061 formatter: Some(FormatterConfig {
4062 command: "prettier".to_string(),
4063 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
4064 stdin: true,
4065 timeout_ms: 10000,
4066 }),
4067 format_on_save: false,
4068 on_save: vec![],
4069 word_characters: None,
4070 indent: None,
4071 },
4072 );
4073
4074 languages.insert(
4075 "toml".to_string(),
4076 LanguageConfig {
4077 extensions: vec!["toml".to_string()],
4078 filenames: vec!["Cargo.lock".to_string()],
4079 grammar: "toml".to_string(),
4080 comment_prefix: Some("#".to_string()),
4081 auto_indent: true,
4082 auto_close: None,
4083 auto_surround: None,
4084 textmate_grammar: None,
4085 show_whitespace_tabs: true,
4086 line_wrap: None,
4087 wrap_column: None,
4088 page_view: None,
4089 page_width: None,
4090 use_tabs: None,
4091 tab_size: None,
4092 formatter: None,
4093 format_on_save: false,
4094 on_save: vec![],
4095 word_characters: None,
4096 indent: None,
4097 },
4098 );
4099
4100 languages.insert(
4101 "yaml".to_string(),
4102 LanguageConfig {
4103 extensions: vec!["yml".to_string(), "yaml".to_string()],
4104 filenames: vec![],
4105 grammar: "yaml".to_string(),
4106 comment_prefix: Some("#".to_string()),
4107 auto_indent: true,
4108 auto_close: None,
4109 auto_surround: None,
4110 textmate_grammar: None,
4111 show_whitespace_tabs: true,
4112 line_wrap: None,
4113 wrap_column: None,
4114 page_view: None,
4115 page_width: None,
4116 use_tabs: None,
4117 tab_size: None,
4118 formatter: Some(FormatterConfig {
4119 command: "prettier".to_string(),
4120 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
4121 stdin: true,
4122 timeout_ms: 10000,
4123 }),
4124 format_on_save: false,
4125 on_save: vec![],
4126 word_characters: None,
4127 indent: None,
4128 },
4129 );
4130
4131 languages.insert(
4132 "markdown".to_string(),
4133 LanguageConfig {
4134 extensions: vec!["md".to_string(), "markdown".to_string()],
4135 filenames: vec!["README".to_string()],
4136 grammar: "markdown".to_string(),
4137 comment_prefix: None,
4138 auto_indent: false,
4139 auto_close: None,
4140 auto_surround: None,
4141 textmate_grammar: None,
4142 show_whitespace_tabs: true,
4143 line_wrap: None,
4144 wrap_column: None,
4145 page_view: None,
4146 page_width: None,
4147 use_tabs: None,
4148 tab_size: None,
4149 formatter: None,
4150 format_on_save: false,
4151 on_save: vec![],
4152 word_characters: None,
4153 indent: None,
4154 },
4155 );
4156
4157 languages.insert(
4159 "go".to_string(),
4160 LanguageConfig {
4161 extensions: vec!["go".to_string()],
4162 filenames: vec![],
4163 grammar: "go".to_string(),
4164 comment_prefix: Some("//".to_string()),
4165 auto_indent: true,
4166 auto_close: None,
4167 auto_surround: None,
4168 textmate_grammar: None,
4169 show_whitespace_tabs: false,
4170 line_wrap: None,
4171 wrap_column: None,
4172 page_view: None,
4173 page_width: None,
4174 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
4177 command: "gofmt".to_string(),
4178 args: vec![],
4179 stdin: true,
4180 timeout_ms: 10000,
4181 }),
4182 format_on_save: false,
4183 on_save: vec![],
4184 word_characters: None,
4185 indent: None,
4186 },
4187 );
4188
4189 languages.insert(
4190 "odin".to_string(),
4191 LanguageConfig {
4192 extensions: vec!["odin".to_string()],
4193 filenames: vec![],
4194 grammar: "odin".to_string(),
4195 comment_prefix: Some("//".to_string()),
4196 auto_indent: true,
4197 auto_close: None,
4198 auto_surround: None,
4199 textmate_grammar: None,
4200 show_whitespace_tabs: false,
4201 line_wrap: None,
4202 wrap_column: None,
4203 page_view: None,
4204 page_width: None,
4205 use_tabs: Some(true),
4206 tab_size: Some(8),
4207 formatter: None,
4208 format_on_save: false,
4209 on_save: vec![],
4210 word_characters: None,
4211 indent: None,
4212 },
4213 );
4214
4215 languages.insert(
4216 "zig".to_string(),
4217 LanguageConfig {
4218 extensions: vec!["zig".to_string(), "zon".to_string()],
4219 filenames: vec![],
4220 grammar: "zig".to_string(),
4221 comment_prefix: Some("//".to_string()),
4222 auto_indent: true,
4223 auto_close: None,
4224 auto_surround: None,
4225 textmate_grammar: None,
4226 show_whitespace_tabs: true,
4227 line_wrap: None,
4228 wrap_column: None,
4229 page_view: None,
4230 page_width: None,
4231 use_tabs: None,
4232 tab_size: None,
4233 formatter: None,
4234 format_on_save: false,
4235 on_save: vec![],
4236 word_characters: None,
4237 indent: None,
4238 },
4239 );
4240
4241 languages.insert(
4242 "c3".to_string(),
4243 LanguageConfig {
4244 extensions: vec!["c3".to_string(), "c3i".to_string(), "c3t".to_string()],
4245 filenames: vec![],
4246 grammar: "c3".to_string(),
4247 comment_prefix: Some("//".to_string()),
4248 auto_indent: true,
4249 auto_close: None,
4250 auto_surround: None,
4251 textmate_grammar: None,
4252 show_whitespace_tabs: true,
4253 line_wrap: None,
4254 wrap_column: None,
4255 page_view: None,
4256 page_width: None,
4257 use_tabs: None,
4258 tab_size: None,
4259 formatter: None,
4260 format_on_save: false,
4261 on_save: vec![],
4262 word_characters: None,
4263 indent: None,
4264 },
4265 );
4266
4267 languages.insert(
4268 "java".to_string(),
4269 LanguageConfig {
4270 extensions: vec!["java".to_string()],
4271 filenames: vec![],
4272 grammar: "java".to_string(),
4273 comment_prefix: Some("//".to_string()),
4274 auto_indent: true,
4275 auto_close: None,
4276 auto_surround: None,
4277 textmate_grammar: None,
4278 show_whitespace_tabs: true,
4279 line_wrap: None,
4280 wrap_column: None,
4281 page_view: None,
4282 page_width: None,
4283 use_tabs: None,
4284 tab_size: None,
4285 formatter: None,
4286 format_on_save: false,
4287 on_save: vec![],
4288 word_characters: None,
4289 indent: None,
4290 },
4291 );
4292
4293 languages.insert(
4294 "latex".to_string(),
4295 LanguageConfig {
4296 extensions: vec![
4297 "tex".to_string(),
4298 "latex".to_string(),
4299 "ltx".to_string(),
4300 "sty".to_string(),
4301 "cls".to_string(),
4302 "bib".to_string(),
4303 ],
4304 filenames: vec![],
4305 grammar: "latex".to_string(),
4306 comment_prefix: Some("%".to_string()),
4307 auto_indent: true,
4308 auto_close: None,
4309 auto_surround: None,
4310 textmate_grammar: None,
4311 show_whitespace_tabs: true,
4312 line_wrap: None,
4313 wrap_column: None,
4314 page_view: None,
4315 page_width: None,
4316 use_tabs: None,
4317 tab_size: None,
4318 formatter: None,
4319 format_on_save: false,
4320 on_save: vec![],
4321 word_characters: None,
4322 indent: None,
4323 },
4324 );
4325
4326 languages.insert(
4327 "templ".to_string(),
4328 LanguageConfig {
4329 extensions: vec!["templ".to_string()],
4330 filenames: vec![],
4331 grammar: "go".to_string(), comment_prefix: Some("//".to_string()),
4333 auto_indent: true,
4334 auto_close: None,
4335 auto_surround: None,
4336 textmate_grammar: None,
4337 show_whitespace_tabs: true,
4338 line_wrap: None,
4339 wrap_column: None,
4340 page_view: None,
4341 page_width: None,
4342 use_tabs: None,
4343 tab_size: None,
4344 formatter: None,
4345 format_on_save: false,
4346 on_save: vec![],
4347 word_characters: None,
4348 indent: None,
4349 },
4350 );
4351
4352 languages.insert(
4354 "git-rebase".to_string(),
4355 LanguageConfig {
4356 extensions: vec![],
4357 filenames: vec!["git-rebase-todo".to_string()],
4358 grammar: "Git Rebase Todo".to_string(),
4359 comment_prefix: Some("#".to_string()),
4360 auto_indent: false,
4361 auto_close: None,
4362 auto_surround: None,
4363 textmate_grammar: None,
4364 show_whitespace_tabs: true,
4365 line_wrap: None,
4366 wrap_column: None,
4367 page_view: None,
4368 page_width: None,
4369 use_tabs: None,
4370 tab_size: None,
4371 formatter: None,
4372 format_on_save: false,
4373 on_save: vec![],
4374 word_characters: None,
4375 indent: None,
4376 },
4377 );
4378
4379 languages.insert(
4380 "git-commit".to_string(),
4381 LanguageConfig {
4382 extensions: vec![],
4383 filenames: vec![
4384 "COMMIT_EDITMSG".to_string(),
4385 "MERGE_MSG".to_string(),
4386 "SQUASH_MSG".to_string(),
4387 "TAG_EDITMSG".to_string(),
4388 ],
4389 grammar: "Git Commit Message".to_string(),
4390 comment_prefix: Some("#".to_string()),
4391 auto_indent: false,
4392 auto_close: None,
4393 auto_surround: None,
4394 textmate_grammar: None,
4395 show_whitespace_tabs: true,
4396 line_wrap: None,
4397 wrap_column: None,
4398 page_view: None,
4399 page_width: None,
4400 use_tabs: None,
4401 tab_size: None,
4402 formatter: None,
4403 format_on_save: false,
4404 on_save: vec![],
4405 word_characters: None,
4406 indent: None,
4407 },
4408 );
4409
4410 languages.insert(
4411 "gitignore".to_string(),
4412 LanguageConfig {
4413 extensions: vec!["gitignore".to_string()],
4414 filenames: vec![
4415 ".gitignore".to_string(),
4416 ".dockerignore".to_string(),
4417 ".npmignore".to_string(),
4418 ".hgignore".to_string(),
4419 ],
4420 grammar: "Gitignore".to_string(),
4421 comment_prefix: Some("#".to_string()),
4422 auto_indent: false,
4423 auto_close: None,
4424 auto_surround: None,
4425 textmate_grammar: None,
4426 show_whitespace_tabs: true,
4427 line_wrap: None,
4428 wrap_column: None,
4429 page_view: None,
4430 page_width: None,
4431 use_tabs: None,
4432 tab_size: None,
4433 formatter: None,
4434 format_on_save: false,
4435 on_save: vec![],
4436 word_characters: None,
4437 indent: None,
4438 },
4439 );
4440
4441 languages.insert(
4442 "gitconfig".to_string(),
4443 LanguageConfig {
4444 extensions: vec!["gitconfig".to_string()],
4445 filenames: vec![".gitconfig".to_string(), ".gitmodules".to_string()],
4446 grammar: "Git Config".to_string(),
4447 comment_prefix: Some("#".to_string()),
4448 auto_indent: true,
4449 auto_close: None,
4450 auto_surround: None,
4451 textmate_grammar: None,
4452 show_whitespace_tabs: true,
4453 line_wrap: None,
4454 wrap_column: None,
4455 page_view: None,
4456 page_width: None,
4457 use_tabs: None,
4458 tab_size: None,
4459 formatter: None,
4460 format_on_save: false,
4461 on_save: vec![],
4462 word_characters: None,
4463 indent: None,
4464 },
4465 );
4466
4467 languages.insert(
4468 "gitattributes".to_string(),
4469 LanguageConfig {
4470 extensions: vec!["gitattributes".to_string()],
4471 filenames: vec![".gitattributes".to_string()],
4472 grammar: "Git Attributes".to_string(),
4473 comment_prefix: Some("#".to_string()),
4474 auto_indent: false,
4475 auto_close: None,
4476 auto_surround: None,
4477 textmate_grammar: None,
4478 show_whitespace_tabs: true,
4479 line_wrap: None,
4480 wrap_column: None,
4481 page_view: None,
4482 page_width: None,
4483 use_tabs: None,
4484 tab_size: None,
4485 formatter: None,
4486 format_on_save: false,
4487 on_save: vec![],
4488 word_characters: None,
4489 indent: None,
4490 },
4491 );
4492
4493 languages.insert(
4494 "typst".to_string(),
4495 LanguageConfig {
4496 extensions: vec!["typ".to_string()],
4497 filenames: vec![],
4498 grammar: "Typst".to_string(),
4499 comment_prefix: Some("//".to_string()),
4500 auto_indent: true,
4501 auto_close: None,
4502 auto_surround: None,
4503 textmate_grammar: None,
4504 show_whitespace_tabs: true,
4505 line_wrap: None,
4506 wrap_column: None,
4507 page_view: None,
4508 page_width: None,
4509 use_tabs: None,
4510 tab_size: None,
4511 formatter: None,
4512 format_on_save: false,
4513 on_save: vec![],
4514 word_characters: None,
4515 indent: None,
4516 },
4517 );
4518
4519 languages.insert(
4524 "kotlin".to_string(),
4525 LanguageConfig {
4526 extensions: vec!["kt".to_string(), "kts".to_string()],
4527 filenames: vec![],
4528 grammar: "Kotlin".to_string(),
4529 comment_prefix: Some("//".to_string()),
4530 auto_indent: true,
4531 auto_close: None,
4532 auto_surround: None,
4533 textmate_grammar: None,
4534 show_whitespace_tabs: true,
4535 line_wrap: None,
4536 wrap_column: None,
4537 page_view: None,
4538 page_width: None,
4539 use_tabs: None,
4540 tab_size: None,
4541 formatter: None,
4542 format_on_save: false,
4543 on_save: vec![],
4544 word_characters: None,
4545 indent: None,
4546 },
4547 );
4548
4549 languages.insert(
4550 "swift".to_string(),
4551 LanguageConfig {
4552 extensions: vec!["swift".to_string()],
4553 filenames: vec![],
4554 grammar: "Swift".to_string(),
4555 comment_prefix: Some("//".to_string()),
4556 auto_indent: true,
4557 auto_close: None,
4558 auto_surround: None,
4559 textmate_grammar: None,
4560 show_whitespace_tabs: true,
4561 line_wrap: None,
4562 wrap_column: None,
4563 page_view: None,
4564 page_width: None,
4565 use_tabs: None,
4566 tab_size: None,
4567 formatter: None,
4568 format_on_save: false,
4569 on_save: vec![],
4570 word_characters: None,
4571 indent: None,
4572 },
4573 );
4574
4575 languages.insert(
4576 "scala".to_string(),
4577 LanguageConfig {
4578 extensions: vec!["scala".to_string(), "sc".to_string()],
4579 filenames: vec![],
4580 grammar: "Scala".to_string(),
4581 comment_prefix: Some("//".to_string()),
4582 auto_indent: true,
4583 auto_close: None,
4584 auto_surround: None,
4585 textmate_grammar: None,
4586 show_whitespace_tabs: true,
4587 line_wrap: None,
4588 wrap_column: None,
4589 page_view: None,
4590 page_width: None,
4591 use_tabs: None,
4592 tab_size: None,
4593 formatter: None,
4594 format_on_save: false,
4595 on_save: vec![],
4596 word_characters: None,
4597 indent: None,
4598 },
4599 );
4600
4601 languages.insert(
4602 "dart".to_string(),
4603 LanguageConfig {
4604 extensions: vec!["dart".to_string()],
4605 filenames: vec![],
4606 grammar: "Dart".to_string(),
4607 comment_prefix: Some("//".to_string()),
4608 auto_indent: true,
4609 auto_close: None,
4610 auto_surround: None,
4611 textmate_grammar: None,
4612 show_whitespace_tabs: true,
4613 line_wrap: None,
4614 wrap_column: None,
4615 page_view: None,
4616 page_width: None,
4617 use_tabs: None,
4618 tab_size: None,
4619 formatter: None,
4620 format_on_save: false,
4621 on_save: vec![],
4622 word_characters: None,
4623 indent: None,
4624 },
4625 );
4626
4627 languages.insert(
4628 "elixir".to_string(),
4629 LanguageConfig {
4630 extensions: vec!["ex".to_string(), "exs".to_string()],
4631 filenames: vec![],
4632 grammar: "Elixir".to_string(),
4633 comment_prefix: Some("#".to_string()),
4634 auto_indent: true,
4635 auto_close: None,
4636 auto_surround: None,
4637 textmate_grammar: None,
4638 show_whitespace_tabs: true,
4639 line_wrap: None,
4640 wrap_column: None,
4641 page_view: None,
4642 page_width: None,
4643 use_tabs: None,
4644 tab_size: None,
4645 formatter: None,
4646 format_on_save: false,
4647 on_save: vec![],
4648 word_characters: None,
4649 indent: None,
4650 },
4651 );
4652
4653 languages.insert(
4654 "erlang".to_string(),
4655 LanguageConfig {
4656 extensions: vec!["erl".to_string(), "hrl".to_string()],
4657 filenames: vec![],
4658 grammar: "Erlang".to_string(),
4659 comment_prefix: Some("%".to_string()),
4660 auto_indent: true,
4661 auto_close: None,
4662 auto_surround: None,
4663 textmate_grammar: None,
4664 show_whitespace_tabs: true,
4665 line_wrap: None,
4666 wrap_column: None,
4667 page_view: None,
4668 page_width: None,
4669 use_tabs: None,
4670 tab_size: None,
4671 formatter: None,
4672 format_on_save: false,
4673 on_save: vec![],
4674 word_characters: None,
4675 indent: None,
4676 },
4677 );
4678
4679 languages.insert(
4680 "haskell".to_string(),
4681 LanguageConfig {
4682 extensions: vec!["hs".to_string(), "lhs".to_string()],
4683 filenames: vec![],
4684 grammar: "Haskell".to_string(),
4685 comment_prefix: Some("--".to_string()),
4686 auto_indent: true,
4687 auto_close: None,
4688 auto_surround: None,
4689 textmate_grammar: None,
4690 show_whitespace_tabs: true,
4691 line_wrap: None,
4692 wrap_column: None,
4693 page_view: None,
4694 page_width: None,
4695 use_tabs: None,
4696 tab_size: None,
4697 formatter: None,
4698 format_on_save: false,
4699 on_save: vec![],
4700 word_characters: None,
4701 indent: None,
4702 },
4703 );
4704
4705 languages.insert(
4706 "ocaml".to_string(),
4707 LanguageConfig {
4708 extensions: vec!["ml".to_string(), "mli".to_string()],
4709 filenames: vec![],
4710 grammar: "OCaml".to_string(),
4711 comment_prefix: None,
4712 auto_indent: true,
4713 auto_close: None,
4714 auto_surround: None,
4715 textmate_grammar: None,
4716 show_whitespace_tabs: true,
4717 line_wrap: None,
4718 wrap_column: None,
4719 page_view: None,
4720 page_width: None,
4721 use_tabs: None,
4722 tab_size: None,
4723 formatter: None,
4724 format_on_save: false,
4725 on_save: vec![],
4726 word_characters: None,
4727 indent: None,
4728 },
4729 );
4730
4731 languages.insert(
4732 "clojure".to_string(),
4733 LanguageConfig {
4734 extensions: vec![
4735 "clj".to_string(),
4736 "cljs".to_string(),
4737 "cljc".to_string(),
4738 "edn".to_string(),
4739 ],
4740 filenames: vec![],
4741 grammar: "Clojure".to_string(),
4742 comment_prefix: Some(";".to_string()),
4743 auto_indent: true,
4744 auto_close: None,
4745 auto_surround: None,
4746 textmate_grammar: None,
4747 show_whitespace_tabs: true,
4748 line_wrap: None,
4749 wrap_column: None,
4750 page_view: None,
4751 page_width: None,
4752 use_tabs: None,
4753 tab_size: None,
4754 formatter: None,
4755 format_on_save: false,
4756 on_save: vec![],
4757 word_characters: None,
4758 indent: None,
4759 },
4760 );
4761
4762 languages.insert(
4763 "r".to_string(),
4764 LanguageConfig {
4765 extensions: vec!["r".to_string(), "R".to_string(), "rmd".to_string()],
4766 filenames: vec![],
4767 grammar: "R".to_string(),
4768 comment_prefix: Some("#".to_string()),
4769 auto_indent: true,
4770 auto_close: None,
4771 auto_surround: None,
4772 textmate_grammar: None,
4773 show_whitespace_tabs: true,
4774 line_wrap: None,
4775 wrap_column: None,
4776 page_view: None,
4777 page_width: None,
4778 use_tabs: None,
4779 tab_size: None,
4780 formatter: None,
4781 format_on_save: false,
4782 on_save: vec![],
4783 word_characters: None,
4784 indent: None,
4785 },
4786 );
4787
4788 languages.insert(
4789 "julia".to_string(),
4790 LanguageConfig {
4791 extensions: vec!["jl".to_string()],
4792 filenames: vec![],
4793 grammar: "Julia".to_string(),
4794 comment_prefix: Some("#".to_string()),
4795 auto_indent: true,
4796 auto_close: None,
4797 auto_surround: None,
4798 textmate_grammar: None,
4799 show_whitespace_tabs: true,
4800 line_wrap: None,
4801 wrap_column: None,
4802 page_view: None,
4803 page_width: None,
4804 use_tabs: None,
4805 tab_size: None,
4806 formatter: None,
4807 format_on_save: false,
4808 on_save: vec![],
4809 word_characters: None,
4810 indent: None,
4811 },
4812 );
4813
4814 languages.insert(
4815 "perl".to_string(),
4816 LanguageConfig {
4817 extensions: vec!["pl".to_string(), "pm".to_string(), "t".to_string()],
4818 filenames: vec![],
4819 grammar: "Perl".to_string(),
4820 comment_prefix: Some("#".to_string()),
4821 auto_indent: true,
4822 auto_close: None,
4823 auto_surround: None,
4824 textmate_grammar: None,
4825 show_whitespace_tabs: true,
4826 line_wrap: None,
4827 wrap_column: None,
4828 page_view: None,
4829 page_width: None,
4830 use_tabs: None,
4831 tab_size: None,
4832 formatter: None,
4833 format_on_save: false,
4834 on_save: vec![],
4835 word_characters: None,
4836 indent: None,
4837 },
4838 );
4839
4840 languages.insert(
4841 "nim".to_string(),
4842 LanguageConfig {
4843 extensions: vec!["nim".to_string(), "nims".to_string(), "nimble".to_string()],
4844 filenames: vec![],
4845 grammar: "Nim".to_string(),
4846 comment_prefix: Some("#".to_string()),
4847 auto_indent: true,
4848 auto_close: None,
4849 auto_surround: None,
4850 textmate_grammar: None,
4851 show_whitespace_tabs: true,
4852 line_wrap: None,
4853 wrap_column: None,
4854 page_view: None,
4855 page_width: None,
4856 use_tabs: None,
4857 tab_size: None,
4858 formatter: None,
4859 format_on_save: false,
4860 on_save: vec![],
4861 word_characters: None,
4862 indent: None,
4863 },
4864 );
4865
4866 languages.insert(
4867 "gleam".to_string(),
4868 LanguageConfig {
4869 extensions: vec!["gleam".to_string()],
4870 filenames: vec![],
4871 grammar: "Gleam".to_string(),
4872 comment_prefix: Some("//".to_string()),
4873 auto_indent: true,
4874 auto_close: None,
4875 auto_surround: None,
4876 textmate_grammar: None,
4877 show_whitespace_tabs: true,
4878 line_wrap: None,
4879 wrap_column: None,
4880 page_view: None,
4881 page_width: None,
4882 use_tabs: None,
4883 tab_size: None,
4884 formatter: None,
4885 format_on_save: false,
4886 on_save: vec![],
4887 word_characters: None,
4888 indent: None,
4889 },
4890 );
4891
4892 languages.insert(
4893 "racket".to_string(),
4894 LanguageConfig {
4895 extensions: vec![
4896 "rkt".to_string(),
4897 "rktd".to_string(),
4898 "rktl".to_string(),
4899 "scrbl".to_string(),
4900 ],
4901 filenames: vec![],
4902 grammar: "Racket".to_string(),
4903 comment_prefix: Some(";".to_string()),
4904 auto_indent: true,
4905 auto_close: None,
4906 auto_surround: None,
4907 textmate_grammar: None,
4908 show_whitespace_tabs: true,
4909 line_wrap: None,
4910 wrap_column: None,
4911 page_view: None,
4912 page_width: None,
4913 use_tabs: None,
4914 tab_size: None,
4915 formatter: None,
4916 format_on_save: false,
4917 on_save: vec![],
4918 word_characters: None,
4919 indent: None,
4920 },
4921 );
4922
4923 languages.insert(
4924 "fsharp".to_string(),
4925 LanguageConfig {
4926 extensions: vec!["fs".to_string(), "fsi".to_string(), "fsx".to_string()],
4927 filenames: vec![],
4928 grammar: "FSharp".to_string(),
4929 comment_prefix: Some("//".to_string()),
4930 auto_indent: true,
4931 auto_close: None,
4932 auto_surround: None,
4933 textmate_grammar: None,
4934 show_whitespace_tabs: true,
4935 line_wrap: None,
4936 wrap_column: None,
4937 page_view: None,
4938 page_width: None,
4939 use_tabs: None,
4940 tab_size: None,
4941 formatter: None,
4942 format_on_save: false,
4943 on_save: vec![],
4944 word_characters: None,
4945 indent: None,
4946 },
4947 );
4948
4949 languages.insert(
4950 "nix".to_string(),
4951 LanguageConfig {
4952 extensions: vec!["nix".to_string()],
4953 filenames: vec![],
4954 grammar: "Nix".to_string(),
4955 comment_prefix: Some("#".to_string()),
4956 auto_indent: true,
4957 auto_close: None,
4958 auto_surround: None,
4959 textmate_grammar: None,
4960 show_whitespace_tabs: true,
4961 line_wrap: None,
4962 wrap_column: None,
4963 page_view: None,
4964 page_width: None,
4965 use_tabs: None,
4966 tab_size: None,
4967 formatter: None,
4968 format_on_save: false,
4969 on_save: vec![],
4970 word_characters: None,
4971 indent: None,
4972 },
4973 );
4974
4975 languages.insert(
4976 "nushell".to_string(),
4977 LanguageConfig {
4978 extensions: vec!["nu".to_string()],
4979 filenames: vec![],
4980 grammar: "Nushell".to_string(),
4981 comment_prefix: Some("#".to_string()),
4982 auto_indent: true,
4983 auto_close: None,
4984 auto_surround: None,
4985 textmate_grammar: None,
4986 show_whitespace_tabs: true,
4987 line_wrap: None,
4988 wrap_column: None,
4989 page_view: None,
4990 page_width: None,
4991 use_tabs: None,
4992 tab_size: None,
4993 formatter: None,
4994 format_on_save: false,
4995 on_save: vec![],
4996 word_characters: None,
4997 indent: None,
4998 },
4999 );
5000
5001 languages.insert(
5002 "solidity".to_string(),
5003 LanguageConfig {
5004 extensions: vec!["sol".to_string()],
5005 filenames: vec![],
5006 grammar: "Solidity".to_string(),
5007 comment_prefix: Some("//".to_string()),
5008 auto_indent: true,
5009 auto_close: None,
5010 auto_surround: None,
5011 textmate_grammar: None,
5012 show_whitespace_tabs: true,
5013 line_wrap: None,
5014 wrap_column: None,
5015 page_view: None,
5016 page_width: None,
5017 use_tabs: None,
5018 tab_size: None,
5019 formatter: None,
5020 format_on_save: false,
5021 on_save: vec![],
5022 word_characters: None,
5023 indent: None,
5024 },
5025 );
5026
5027 languages.insert(
5028 "verilog".to_string(),
5029 LanguageConfig {
5030 extensions: vec!["vh".to_string(), "verilog".to_string()],
5031 filenames: vec![],
5032 grammar: "Verilog".to_string(),
5033 comment_prefix: Some("//".to_string()),
5034 auto_indent: true,
5035 auto_close: None,
5036 auto_surround: None,
5037 textmate_grammar: None,
5038 show_whitespace_tabs: true,
5039 line_wrap: None,
5040 wrap_column: None,
5041 page_view: None,
5042 page_width: None,
5043 use_tabs: None,
5044 tab_size: None,
5045 formatter: None,
5046 format_on_save: false,
5047 on_save: vec![],
5048 word_characters: None,
5049 indent: None,
5050 },
5051 );
5052
5053 languages.insert(
5054 "systemverilog".to_string(),
5055 LanguageConfig {
5056 extensions: vec![
5057 "sv".to_string(),
5058 "svh".to_string(),
5059 "svi".to_string(),
5060 "svp".to_string(),
5061 ],
5062 filenames: vec![],
5063 grammar: "SystemVerilog".to_string(),
5064 comment_prefix: Some("//".to_string()),
5065 auto_indent: true,
5066 auto_close: None,
5067 auto_surround: None,
5068 textmate_grammar: None,
5069 show_whitespace_tabs: true,
5070 line_wrap: None,
5071 wrap_column: None,
5072 page_view: None,
5073 page_width: None,
5074 use_tabs: None,
5075 tab_size: None,
5076 formatter: None,
5077 format_on_save: false,
5078 on_save: vec![],
5079 word_characters: None,
5080 indent: None,
5081 },
5082 );
5083
5084 languages.insert(
5085 "vhdl".to_string(),
5086 LanguageConfig {
5087 extensions: vec!["vhd".to_string(), "vhdl".to_string(), "vho".to_string()],
5088 filenames: vec![],
5089 grammar: "VHDL".to_string(),
5090 comment_prefix: Some("--".to_string()),
5091 auto_indent: true,
5092 auto_close: None,
5093 auto_surround: None,
5094 textmate_grammar: None,
5095 show_whitespace_tabs: true,
5096 line_wrap: None,
5097 wrap_column: None,
5098 page_view: None,
5099 page_width: None,
5100 use_tabs: None,
5101 tab_size: None,
5102 formatter: None,
5103 format_on_save: false,
5104 on_save: vec![],
5105 word_characters: None,
5106 indent: None,
5107 },
5108 );
5109
5110 languages.insert(
5111 "ruby".to_string(),
5112 LanguageConfig {
5113 extensions: vec!["rb".to_string(), "rake".to_string(), "gemspec".to_string()],
5114 filenames: vec![
5115 "Gemfile".to_string(),
5116 "Rakefile".to_string(),
5117 "Guardfile".to_string(),
5118 ],
5119 grammar: "Ruby".to_string(),
5120 comment_prefix: Some("#".to_string()),
5121 auto_indent: true,
5122 auto_close: None,
5123 auto_surround: None,
5124 textmate_grammar: None,
5125 show_whitespace_tabs: true,
5126 line_wrap: None,
5127 wrap_column: None,
5128 page_view: None,
5129 page_width: None,
5130 use_tabs: None,
5131 tab_size: None,
5132 formatter: None,
5133 format_on_save: false,
5134 on_save: vec![],
5135 word_characters: None,
5136 indent: None,
5137 },
5138 );
5139
5140 languages.insert(
5141 "php".to_string(),
5142 LanguageConfig {
5143 extensions: vec!["php".to_string(), "phtml".to_string()],
5144 filenames: vec![],
5145 grammar: "PHP".to_string(),
5146 comment_prefix: Some("//".to_string()),
5147 auto_indent: true,
5148 auto_close: None,
5149 auto_surround: None,
5150 textmate_grammar: None,
5151 show_whitespace_tabs: true,
5152 line_wrap: None,
5153 wrap_column: None,
5154 page_view: None,
5155 page_width: None,
5156 use_tabs: None,
5157 tab_size: None,
5158 formatter: None,
5159 format_on_save: false,
5160 on_save: vec![],
5161 word_characters: None,
5162 indent: None,
5163 },
5164 );
5165
5166 languages.insert(
5167 "lua".to_string(),
5168 LanguageConfig {
5169 extensions: vec!["lua".to_string()],
5170 filenames: vec![],
5171 grammar: "Lua".to_string(),
5172 comment_prefix: Some("--".to_string()),
5173 auto_indent: true,
5174 auto_close: None,
5175 auto_surround: None,
5176 textmate_grammar: None,
5177 show_whitespace_tabs: true,
5178 line_wrap: None,
5179 wrap_column: None,
5180 page_view: None,
5181 page_width: None,
5182 use_tabs: None,
5183 tab_size: None,
5184 formatter: None,
5185 format_on_save: false,
5186 on_save: vec![],
5187 word_characters: None,
5188 indent: None,
5189 },
5190 );
5191
5192 languages.insert(
5193 "html".to_string(),
5194 LanguageConfig {
5195 extensions: vec!["html".to_string(), "htm".to_string()],
5196 filenames: vec![],
5197 grammar: "HTML".to_string(),
5198 comment_prefix: None,
5199 auto_indent: true,
5200 auto_close: None,
5201 auto_surround: None,
5202 textmate_grammar: None,
5203 show_whitespace_tabs: true,
5204 line_wrap: None,
5205 wrap_column: None,
5206 page_view: None,
5207 page_width: None,
5208 use_tabs: None,
5209 tab_size: None,
5210 formatter: None,
5211 format_on_save: false,
5212 on_save: vec![],
5213 word_characters: None,
5214 indent: None,
5215 },
5216 );
5217
5218 languages.insert(
5219 "css".to_string(),
5220 LanguageConfig {
5221 extensions: vec!["css".to_string()],
5222 filenames: vec![],
5223 grammar: "CSS".to_string(),
5224 comment_prefix: None,
5225 auto_indent: true,
5226 auto_close: None,
5227 auto_surround: None,
5228 textmate_grammar: None,
5229 show_whitespace_tabs: true,
5230 line_wrap: None,
5231 wrap_column: None,
5232 page_view: None,
5233 page_width: None,
5234 use_tabs: None,
5235 tab_size: None,
5236 formatter: None,
5237 format_on_save: false,
5238 on_save: vec![],
5239 word_characters: None,
5240 indent: None,
5241 },
5242 );
5243
5244 languages.insert(
5245 "sql".to_string(),
5246 LanguageConfig {
5247 extensions: vec!["sql".to_string()],
5248 filenames: vec![],
5249 grammar: "SQL".to_string(),
5250 comment_prefix: Some("--".to_string()),
5251 auto_indent: true,
5252 auto_close: None,
5253 auto_surround: None,
5254 textmate_grammar: None,
5255 show_whitespace_tabs: true,
5256 line_wrap: None,
5257 wrap_column: None,
5258 page_view: None,
5259 page_width: None,
5260 use_tabs: None,
5261 tab_size: None,
5262 formatter: None,
5263 format_on_save: false,
5264 on_save: vec![],
5265 word_characters: None,
5266 indent: None,
5267 },
5268 );
5269
5270 languages.insert(
5271 "graphql".to_string(),
5272 LanguageConfig {
5273 extensions: vec!["graphql".to_string(), "gql".to_string()],
5274 filenames: vec![],
5275 grammar: "GraphQL".to_string(),
5276 comment_prefix: Some("#".to_string()),
5277 auto_indent: true,
5278 auto_close: None,
5279 auto_surround: None,
5280 textmate_grammar: None,
5281 show_whitespace_tabs: true,
5282 line_wrap: None,
5283 wrap_column: None,
5284 page_view: None,
5285 page_width: None,
5286 use_tabs: None,
5287 tab_size: None,
5288 formatter: None,
5289 format_on_save: false,
5290 on_save: vec![],
5291 word_characters: None,
5292 indent: None,
5293 },
5294 );
5295
5296 languages.insert(
5297 "protobuf".to_string(),
5298 LanguageConfig {
5299 extensions: vec!["proto".to_string()],
5300 filenames: vec![],
5301 grammar: "Protocol Buffers".to_string(),
5302 comment_prefix: Some("//".to_string()),
5303 auto_indent: true,
5304 auto_close: None,
5305 auto_surround: None,
5306 textmate_grammar: None,
5307 show_whitespace_tabs: true,
5308 line_wrap: None,
5309 wrap_column: None,
5310 page_view: None,
5311 page_width: None,
5312 use_tabs: None,
5313 tab_size: None,
5314 formatter: None,
5315 format_on_save: false,
5316 on_save: vec![],
5317 word_characters: None,
5318 indent: None,
5319 },
5320 );
5321
5322 languages.insert(
5323 "cmake".to_string(),
5324 LanguageConfig {
5325 extensions: vec!["cmake".to_string()],
5326 filenames: vec!["CMakeLists.txt".to_string()],
5327 grammar: "CMake".to_string(),
5328 comment_prefix: Some("#".to_string()),
5329 auto_indent: true,
5330 auto_close: None,
5331 auto_surround: None,
5332 textmate_grammar: None,
5333 show_whitespace_tabs: true,
5334 line_wrap: None,
5335 wrap_column: None,
5336 page_view: None,
5337 page_width: None,
5338 use_tabs: None,
5339 tab_size: None,
5340 formatter: None,
5341 format_on_save: false,
5342 on_save: vec![],
5343 word_characters: None,
5344 indent: None,
5345 },
5346 );
5347
5348 languages.insert(
5349 "terraform".to_string(),
5350 LanguageConfig {
5351 extensions: vec!["tf".to_string(), "tfvars".to_string(), "hcl".to_string()],
5352 filenames: vec![],
5353 grammar: "HCL".to_string(),
5354 comment_prefix: Some("#".to_string()),
5355 auto_indent: true,
5356 auto_close: None,
5357 auto_surround: None,
5358 textmate_grammar: None,
5359 show_whitespace_tabs: true,
5360 line_wrap: None,
5361 wrap_column: None,
5362 page_view: None,
5363 page_width: None,
5364 use_tabs: None,
5365 tab_size: None,
5366 formatter: None,
5367 format_on_save: false,
5368 on_save: vec![],
5369 word_characters: None,
5370 indent: None,
5371 },
5372 );
5373
5374 languages.insert(
5375 "vue".to_string(),
5376 LanguageConfig {
5377 extensions: vec!["vue".to_string()],
5378 filenames: vec![],
5379 grammar: "Vue".to_string(),
5380 comment_prefix: None,
5381 auto_indent: true,
5382 auto_close: None,
5383 auto_surround: None,
5384 textmate_grammar: None,
5385 show_whitespace_tabs: true,
5386 line_wrap: None,
5387 wrap_column: None,
5388 page_view: None,
5389 page_width: None,
5390 use_tabs: None,
5391 tab_size: None,
5392 formatter: None,
5393 format_on_save: false,
5394 on_save: vec![],
5395 word_characters: None,
5396 indent: None,
5397 },
5398 );
5399
5400 languages.insert(
5401 "svelte".to_string(),
5402 LanguageConfig {
5403 extensions: vec!["svelte".to_string()],
5404 filenames: vec![],
5405 grammar: "Svelte".to_string(),
5406 comment_prefix: None,
5407 auto_indent: true,
5408 auto_close: None,
5409 auto_surround: None,
5410 textmate_grammar: None,
5411 show_whitespace_tabs: true,
5412 line_wrap: None,
5413 wrap_column: None,
5414 page_view: None,
5415 page_width: None,
5416 use_tabs: None,
5417 tab_size: None,
5418 formatter: None,
5419 format_on_save: false,
5420 on_save: vec![],
5421 word_characters: None,
5422 indent: None,
5423 },
5424 );
5425
5426 languages.insert(
5427 "astro".to_string(),
5428 LanguageConfig {
5429 extensions: vec!["astro".to_string()],
5430 filenames: vec![],
5431 grammar: "Astro".to_string(),
5432 comment_prefix: None,
5433 auto_indent: true,
5434 auto_close: None,
5435 auto_surround: None,
5436 textmate_grammar: None,
5437 show_whitespace_tabs: true,
5438 line_wrap: None,
5439 wrap_column: None,
5440 page_view: None,
5441 page_width: None,
5442 use_tabs: None,
5443 tab_size: None,
5444 formatter: None,
5445 format_on_save: false,
5446 on_save: vec![],
5447 word_characters: None,
5448 indent: None,
5449 },
5450 );
5451
5452 languages.insert(
5455 "scss".to_string(),
5456 LanguageConfig {
5457 extensions: vec!["scss".to_string()],
5458 filenames: vec![],
5459 grammar: "SCSS".to_string(),
5460 comment_prefix: Some("//".to_string()),
5461 auto_indent: true,
5462 auto_close: None,
5463 auto_surround: None,
5464 textmate_grammar: None,
5465 show_whitespace_tabs: true,
5466 line_wrap: None,
5467 wrap_column: None,
5468 page_view: None,
5469 page_width: None,
5470 use_tabs: None,
5471 tab_size: None,
5472 formatter: None,
5473 format_on_save: false,
5474 on_save: vec![],
5475 word_characters: None,
5476 indent: None,
5477 },
5478 );
5479
5480 languages.insert(
5481 "less".to_string(),
5482 LanguageConfig {
5483 extensions: vec!["less".to_string()],
5484 filenames: vec![],
5485 grammar: "LESS".to_string(),
5486 comment_prefix: Some("//".to_string()),
5487 auto_indent: true,
5488 auto_close: None,
5489 auto_surround: None,
5490 textmate_grammar: None,
5491 show_whitespace_tabs: true,
5492 line_wrap: None,
5493 wrap_column: None,
5494 page_view: None,
5495 page_width: None,
5496 use_tabs: None,
5497 tab_size: None,
5498 formatter: None,
5499 format_on_save: false,
5500 on_save: vec![],
5501 word_characters: None,
5502 indent: None,
5503 },
5504 );
5505
5506 languages.insert(
5507 "powershell".to_string(),
5508 LanguageConfig {
5509 extensions: vec!["ps1".to_string(), "psm1".to_string(), "psd1".to_string()],
5510 filenames: vec![],
5511 grammar: "PowerShell".to_string(),
5512 comment_prefix: Some("#".to_string()),
5513 auto_indent: true,
5514 auto_close: None,
5515 auto_surround: None,
5516 textmate_grammar: None,
5517 show_whitespace_tabs: true,
5518 line_wrap: None,
5519 wrap_column: None,
5520 page_view: None,
5521 page_width: None,
5522 use_tabs: None,
5523 tab_size: None,
5524 formatter: None,
5525 format_on_save: false,
5526 on_save: vec![],
5527 word_characters: None,
5528 indent: None,
5529 },
5530 );
5531
5532 languages.insert(
5533 "kdl".to_string(),
5534 LanguageConfig {
5535 extensions: vec!["kdl".to_string()],
5536 filenames: vec![],
5537 grammar: "KDL".to_string(),
5538 comment_prefix: Some("//".to_string()),
5539 auto_indent: true,
5540 auto_close: None,
5541 auto_surround: None,
5542 textmate_grammar: None,
5543 show_whitespace_tabs: true,
5544 line_wrap: None,
5545 wrap_column: None,
5546 page_view: None,
5547 page_width: None,
5548 use_tabs: None,
5549 tab_size: None,
5550 formatter: None,
5551 format_on_save: false,
5552 on_save: vec![],
5553 word_characters: None,
5554 indent: None,
5555 },
5556 );
5557
5558 languages.insert(
5559 "starlark".to_string(),
5560 LanguageConfig {
5561 extensions: vec!["bzl".to_string(), "star".to_string()],
5562 filenames: vec!["BUILD".to_string(), "WORKSPACE".to_string()],
5563 grammar: "Starlark".to_string(),
5564 comment_prefix: Some("#".to_string()),
5565 auto_indent: true,
5566 auto_close: None,
5567 auto_surround: None,
5568 textmate_grammar: None,
5569 show_whitespace_tabs: true,
5570 line_wrap: None,
5571 wrap_column: None,
5572 page_view: None,
5573 page_width: None,
5574 use_tabs: None,
5575 tab_size: None,
5576 formatter: None,
5577 format_on_save: false,
5578 on_save: vec![],
5579 word_characters: None,
5580 indent: None,
5581 },
5582 );
5583
5584 languages.insert(
5585 "justfile".to_string(),
5586 LanguageConfig {
5587 extensions: vec![],
5588 filenames: vec![
5589 "justfile".to_string(),
5590 "Justfile".to_string(),
5591 ".justfile".to_string(),
5592 ],
5593 grammar: "Justfile".to_string(),
5594 comment_prefix: Some("#".to_string()),
5595 auto_indent: true,
5596 auto_close: None,
5597 auto_surround: None,
5598 textmate_grammar: None,
5599 show_whitespace_tabs: true,
5600 line_wrap: None,
5601 wrap_column: None,
5602 page_view: None,
5603 page_width: None,
5604 use_tabs: Some(true),
5605 tab_size: None,
5606 formatter: None,
5607 format_on_save: false,
5608 on_save: vec![],
5609 word_characters: None,
5610 indent: None,
5611 },
5612 );
5613
5614 languages.insert(
5615 "earthfile".to_string(),
5616 LanguageConfig {
5617 extensions: vec!["earth".to_string()],
5618 filenames: vec!["Earthfile".to_string()],
5619 grammar: "Earthfile".to_string(),
5620 comment_prefix: Some("#".to_string()),
5621 auto_indent: true,
5622 auto_close: None,
5623 auto_surround: None,
5624 textmate_grammar: None,
5625 show_whitespace_tabs: true,
5626 line_wrap: None,
5627 wrap_column: None,
5628 page_view: None,
5629 page_width: None,
5630 use_tabs: None,
5631 tab_size: None,
5632 formatter: None,
5633 format_on_save: false,
5634 on_save: vec![],
5635 word_characters: None,
5636 indent: None,
5637 },
5638 );
5639
5640 languages.insert(
5641 "gomod".to_string(),
5642 LanguageConfig {
5643 extensions: vec![],
5644 filenames: vec!["go.mod".to_string(), "go.sum".to_string()],
5645 grammar: "Go Module".to_string(),
5646 comment_prefix: Some("//".to_string()),
5647 auto_indent: true,
5648 auto_close: None,
5649 auto_surround: None,
5650 textmate_grammar: None,
5651 show_whitespace_tabs: true,
5652 line_wrap: None,
5653 wrap_column: None,
5654 page_view: None,
5655 page_width: None,
5656 use_tabs: Some(true),
5657 tab_size: None,
5658 formatter: None,
5659 format_on_save: false,
5660 on_save: vec![],
5661 word_characters: None,
5662 indent: None,
5663 },
5664 );
5665
5666 languages.insert(
5667 "vlang".to_string(),
5668 LanguageConfig {
5669 extensions: vec!["v".to_string(), "vv".to_string()],
5670 filenames: vec![],
5671 grammar: "V".to_string(),
5672 comment_prefix: Some("//".to_string()),
5673 auto_indent: true,
5674 auto_close: None,
5675 auto_surround: None,
5676 textmate_grammar: None,
5677 show_whitespace_tabs: true,
5678 line_wrap: None,
5679 wrap_column: None,
5680 page_view: None,
5681 page_width: None,
5682 use_tabs: None,
5683 tab_size: None,
5684 formatter: None,
5685 format_on_save: false,
5686 on_save: vec![],
5687 word_characters: None,
5688 indent: None,
5689 },
5690 );
5691
5692 languages.insert(
5693 "ini".to_string(),
5694 LanguageConfig {
5695 extensions: vec!["ini".to_string(), "cfg".to_string()],
5696 filenames: vec![],
5697 grammar: "INI".to_string(),
5698 comment_prefix: Some(";".to_string()),
5699 auto_indent: false,
5700 auto_close: None,
5701 auto_surround: None,
5702 textmate_grammar: None,
5703 show_whitespace_tabs: true,
5704 line_wrap: None,
5705 wrap_column: None,
5706 page_view: None,
5707 page_width: None,
5708 use_tabs: None,
5709 tab_size: None,
5710 formatter: None,
5711 format_on_save: false,
5712 on_save: vec![],
5713 word_characters: None,
5714 indent: None,
5715 },
5716 );
5717
5718 languages.insert(
5719 "hyprlang".to_string(),
5720 LanguageConfig {
5721 extensions: vec!["hl".to_string()],
5722 filenames: vec!["hyprland.conf".to_string()],
5723 grammar: "Hyprlang".to_string(),
5724 comment_prefix: Some("#".to_string()),
5725 auto_indent: true,
5726 auto_close: None,
5727 auto_surround: None,
5728 textmate_grammar: None,
5729 show_whitespace_tabs: true,
5730 line_wrap: None,
5731 wrap_column: None,
5732 page_view: None,
5733 page_width: None,
5734 use_tabs: None,
5735 tab_size: None,
5736 formatter: None,
5737 format_on_save: false,
5738 on_save: vec![],
5739 word_characters: None,
5740 indent: None,
5741 },
5742 );
5743
5744 languages
5745 }
5746
5747 #[cfg(feature = "runtime")]
5749 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
5750 let mut lsp = HashMap::new();
5751
5752 let ra_log_path = crate::services::log_dirs::lsp_log_path("rust-analyzer")
5755 .to_string_lossy()
5756 .to_string();
5757
5758 Self::populate_lsp_config(&mut lsp, ra_log_path);
5759 lsp
5760 }
5761
5762 #[cfg(not(feature = "runtime"))]
5764 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
5765 HashMap::new()
5767 }
5768
5769 #[cfg(feature = "runtime")]
5771 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
5772 let mut universal = HashMap::new();
5773
5774 universal.insert(
5787 "quicklsp".to_string(),
5788 LspLanguageConfig::Multi(vec![LspServerConfig {
5789 command: "quicklsp".to_string(),
5790 args: vec![],
5791 enabled: false,
5792 auto_start: false,
5793 process_limits: ProcessLimits::default(),
5794 initialization_options: None,
5795 env: Default::default(),
5796 language_id_overrides: Default::default(),
5797 name: Some("QuickLSP".to_string()),
5798 only_features: None,
5799 except_features: None,
5800 root_markers: vec![
5801 "Cargo.toml".to_string(),
5802 "package.json".to_string(),
5803 "go.mod".to_string(),
5804 "pyproject.toml".to_string(),
5805 "requirements.txt".to_string(),
5806 ".git".to_string(),
5807 ],
5808 }]),
5809 );
5810
5811 universal
5812 }
5813
5814 #[cfg(not(feature = "runtime"))]
5816 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
5817 HashMap::new()
5818 }
5819
5820 #[cfg(feature = "runtime")]
5821 fn populate_lsp_config(lsp: &mut HashMap<String, LspLanguageConfig>, ra_log_path: String) {
5822 lsp.insert(
5826 "rust".to_string(),
5827 LspLanguageConfig::Multi(vec![LspServerConfig {
5828 command: "rust-analyzer".to_string(),
5829 args: vec!["--log-file".to_string(), ra_log_path],
5830 enabled: true,
5831 auto_start: false,
5832 process_limits: ProcessLimits::unlimited(),
5833 initialization_options: None,
5834 env: Default::default(),
5835 language_id_overrides: Default::default(),
5836 name: None,
5837 only_features: None,
5838 except_features: None,
5839 root_markers: vec![
5840 "Cargo.toml".to_string(),
5841 "rust-project.json".to_string(),
5842 ".git".to_string(),
5843 ],
5844 }]),
5845 );
5846
5847 lsp.insert(
5849 "python".to_string(),
5850 LspLanguageConfig::Multi(vec![LspServerConfig {
5851 command: "pylsp".to_string(),
5852 args: vec![],
5853 enabled: true,
5854 auto_start: false,
5855 process_limits: ProcessLimits::default(),
5856 initialization_options: None,
5857 env: Default::default(),
5858 language_id_overrides: Default::default(),
5859 name: None,
5860 only_features: None,
5861 except_features: None,
5862 root_markers: vec![
5863 "pyproject.toml".to_string(),
5864 "setup.py".to_string(),
5865 "setup.cfg".to_string(),
5866 "pyrightconfig.json".to_string(),
5867 ".git".to_string(),
5868 ],
5869 }]),
5870 );
5871
5872 lsp.insert(
5876 "gdscript".to_string(),
5877 LspLanguageConfig::Multi(vec![LspServerConfig {
5878 command: "nc".to_string(),
5879 args: vec!["127.0.0.1".to_string(), "6005".to_string()],
5880 enabled: false,
5881 auto_start: false,
5882 process_limits: ProcessLimits::default(),
5883 initialization_options: None,
5884 env: Default::default(),
5885 language_id_overrides: Default::default(),
5886 name: Some("Godot GDScript".to_string()),
5887 only_features: None,
5888 except_features: None,
5889 root_markers: vec!["project.godot".to_string(), ".git".to_string()],
5890 }]),
5891 );
5892
5893 lsp.insert(
5896 "javascript".to_string(),
5897 LspLanguageConfig::Multi(vec![LspServerConfig {
5898 command: "typescript-language-server".to_string(),
5899 args: vec!["--stdio".to_string()],
5900 enabled: true,
5901 auto_start: false,
5902 process_limits: ProcessLimits::default(),
5903 initialization_options: None,
5904 env: Default::default(),
5905 language_id_overrides: HashMap::from([(
5906 "jsx".to_string(),
5907 "javascriptreact".to_string(),
5908 )]),
5909 name: None,
5910 only_features: None,
5911 except_features: None,
5912 root_markers: vec![
5913 "tsconfig.json".to_string(),
5914 "jsconfig.json".to_string(),
5915 "package.json".to_string(),
5916 ".git".to_string(),
5917 ],
5918 }]),
5919 );
5920 lsp.insert(
5921 "typescript".to_string(),
5922 LspLanguageConfig::Multi(vec![LspServerConfig {
5923 command: "typescript-language-server".to_string(),
5924 args: vec!["--stdio".to_string()],
5925 enabled: true,
5926 auto_start: false,
5927 process_limits: ProcessLimits::default(),
5928 initialization_options: None,
5929 env: Default::default(),
5930 language_id_overrides: HashMap::from([(
5931 "tsx".to_string(),
5932 "typescriptreact".to_string(),
5933 )]),
5934 name: None,
5935 only_features: None,
5936 except_features: None,
5937 root_markers: vec![
5938 "tsconfig.json".to_string(),
5939 "jsconfig.json".to_string(),
5940 "package.json".to_string(),
5941 ".git".to_string(),
5942 ],
5943 }]),
5944 );
5945
5946 lsp.insert(
5948 "html".to_string(),
5949 LspLanguageConfig::Multi(vec![LspServerConfig {
5950 command: "vscode-html-language-server".to_string(),
5951 args: vec!["--stdio".to_string()],
5952 enabled: true,
5953 auto_start: false,
5954 process_limits: ProcessLimits::default(),
5955 initialization_options: None,
5956 env: Default::default(),
5957 language_id_overrides: Default::default(),
5958 name: None,
5959 only_features: None,
5960 except_features: None,
5961 root_markers: Default::default(),
5962 }]),
5963 );
5964
5965 lsp.insert(
5967 "css".to_string(),
5968 LspLanguageConfig::Multi(vec![LspServerConfig {
5969 command: "vscode-css-language-server".to_string(),
5970 args: vec!["--stdio".to_string()],
5971 enabled: true,
5972 auto_start: false,
5973 process_limits: ProcessLimits::default(),
5974 initialization_options: None,
5975 env: Default::default(),
5976 language_id_overrides: Default::default(),
5977 name: None,
5978 only_features: None,
5979 except_features: None,
5980 root_markers: Default::default(),
5981 }]),
5982 );
5983
5984 lsp.insert(
5986 "c".to_string(),
5987 LspLanguageConfig::Multi(vec![LspServerConfig {
5988 command: "clangd".to_string(),
5989 args: vec![],
5990 enabled: true,
5991 auto_start: false,
5992 process_limits: ProcessLimits::default(),
5993 initialization_options: None,
5994 env: Default::default(),
5995 language_id_overrides: Default::default(),
5996 name: None,
5997 only_features: None,
5998 except_features: None,
5999 root_markers: vec![
6000 "compile_commands.json".to_string(),
6001 "CMakeLists.txt".to_string(),
6002 "Makefile".to_string(),
6003 ".git".to_string(),
6004 ],
6005 }]),
6006 );
6007 lsp.insert(
6008 "cpp".to_string(),
6009 LspLanguageConfig::Multi(vec![LspServerConfig {
6010 command: "clangd".to_string(),
6011 args: vec![],
6012 enabled: true,
6013 auto_start: false,
6014 process_limits: ProcessLimits::default(),
6015 initialization_options: None,
6016 env: Default::default(),
6017 language_id_overrides: Default::default(),
6018 name: None,
6019 only_features: None,
6020 except_features: None,
6021 root_markers: vec![
6022 "compile_commands.json".to_string(),
6023 "CMakeLists.txt".to_string(),
6024 "Makefile".to_string(),
6025 ".git".to_string(),
6026 ],
6027 }]),
6028 );
6029
6030 lsp.insert(
6032 "go".to_string(),
6033 LspLanguageConfig::Multi(vec![LspServerConfig {
6034 command: "gopls".to_string(),
6035 args: vec![],
6036 enabled: true,
6037 auto_start: false,
6038 process_limits: ProcessLimits::default(),
6039 initialization_options: None,
6040 env: Default::default(),
6041 language_id_overrides: Default::default(),
6042 name: None,
6043 only_features: None,
6044 except_features: None,
6045 root_markers: vec![
6046 "go.mod".to_string(),
6047 "go.work".to_string(),
6048 ".git".to_string(),
6049 ],
6050 }]),
6051 );
6052
6053 lsp.insert(
6055 "json".to_string(),
6056 LspLanguageConfig::Multi(vec![LspServerConfig {
6057 command: "vscode-json-language-server".to_string(),
6058 args: vec!["--stdio".to_string()],
6059 enabled: true,
6060 auto_start: false,
6061 process_limits: ProcessLimits::default(),
6062 initialization_options: None,
6063 env: Default::default(),
6064 language_id_overrides: Default::default(),
6065 name: None,
6066 only_features: None,
6067 except_features: None,
6068 root_markers: Default::default(),
6069 }]),
6070 );
6071
6072 lsp.insert(
6076 "jsonc".to_string(),
6077 LspLanguageConfig::Multi(vec![LspServerConfig {
6078 command: "vscode-json-language-server".to_string(),
6079 args: vec!["--stdio".to_string()],
6080 enabled: true,
6081 auto_start: false,
6082 process_limits: ProcessLimits::default(),
6083 initialization_options: None,
6084 env: Default::default(),
6085 language_id_overrides: Default::default(),
6086 name: None,
6087 only_features: None,
6088 except_features: None,
6089 root_markers: Default::default(),
6090 }]),
6091 );
6092
6093 lsp.insert(
6095 "csharp".to_string(),
6096 LspLanguageConfig::Multi(vec![LspServerConfig {
6097 command: "csharp-ls".to_string(),
6098 args: vec![],
6099 enabled: true,
6100 auto_start: false,
6101 process_limits: ProcessLimits::default(),
6102 initialization_options: None,
6103 env: Default::default(),
6104 language_id_overrides: Default::default(),
6105 name: None,
6106 only_features: None,
6107 except_features: None,
6108 root_markers: vec![
6109 "*.csproj".to_string(),
6110 "*.sln".to_string(),
6111 ".git".to_string(),
6112 ],
6113 }]),
6114 );
6115
6116 lsp.insert(
6119 "odin".to_string(),
6120 LspLanguageConfig::Multi(vec![LspServerConfig {
6121 command: "ols".to_string(),
6122 args: vec![],
6123 enabled: true,
6124 auto_start: false,
6125 process_limits: ProcessLimits::default(),
6126 initialization_options: None,
6127 env: Default::default(),
6128 language_id_overrides: Default::default(),
6129 name: None,
6130 only_features: None,
6131 except_features: None,
6132 root_markers: Default::default(),
6133 }]),
6134 );
6135
6136 lsp.insert(
6139 "zig".to_string(),
6140 LspLanguageConfig::Multi(vec![LspServerConfig {
6141 command: "zls".to_string(),
6142 args: vec![],
6143 enabled: true,
6144 auto_start: false,
6145 process_limits: ProcessLimits::default(),
6146 initialization_options: None,
6147 env: Default::default(),
6148 language_id_overrides: Default::default(),
6149 name: None,
6150 only_features: None,
6151 except_features: None,
6152 root_markers: Default::default(),
6153 }]),
6154 );
6155
6156 lsp.insert(
6159 "c3".to_string(),
6160 LspLanguageConfig::Multi(vec![LspServerConfig {
6161 command: "c3lsp".to_string(),
6162 args: vec![],
6163 enabled: true,
6164 auto_start: false,
6165 process_limits: ProcessLimits::default(),
6166 initialization_options: None,
6167 env: Default::default(),
6168 language_id_overrides: Default::default(),
6169 name: None,
6170 only_features: None,
6171 except_features: None,
6172 root_markers: vec!["project.json".to_string(), ".git".to_string()],
6173 }]),
6174 );
6175
6176 lsp.insert(
6179 "java".to_string(),
6180 LspLanguageConfig::Multi(vec![LspServerConfig {
6181 command: "jdtls".to_string(),
6182 args: vec![],
6183 enabled: true,
6184 auto_start: false,
6185 process_limits: ProcessLimits::default(),
6186 initialization_options: None,
6187 env: Default::default(),
6188 language_id_overrides: Default::default(),
6189 name: None,
6190 only_features: None,
6191 except_features: None,
6192 root_markers: vec![
6193 "pom.xml".to_string(),
6194 "build.gradle".to_string(),
6195 "build.gradle.kts".to_string(),
6196 ".git".to_string(),
6197 ],
6198 }]),
6199 );
6200
6201 lsp.insert(
6204 "latex".to_string(),
6205 LspLanguageConfig::Multi(vec![LspServerConfig {
6206 command: "texlab".to_string(),
6207 args: vec![],
6208 enabled: true,
6209 auto_start: false,
6210 process_limits: ProcessLimits::default(),
6211 initialization_options: None,
6212 env: Default::default(),
6213 language_id_overrides: Default::default(),
6214 name: None,
6215 only_features: None,
6216 except_features: None,
6217 root_markers: Default::default(),
6218 }]),
6219 );
6220
6221 lsp.insert(
6224 "markdown".to_string(),
6225 LspLanguageConfig::Multi(vec![LspServerConfig {
6226 command: "marksman".to_string(),
6227 args: vec!["server".to_string()],
6228 enabled: true,
6229 auto_start: false,
6230 process_limits: ProcessLimits::default(),
6231 initialization_options: None,
6232 env: Default::default(),
6233 language_id_overrides: Default::default(),
6234 name: None,
6235 only_features: None,
6236 except_features: None,
6237 root_markers: Default::default(),
6238 }]),
6239 );
6240
6241 lsp.insert(
6244 "templ".to_string(),
6245 LspLanguageConfig::Multi(vec![LspServerConfig {
6246 command: "templ".to_string(),
6247 args: vec!["lsp".to_string()],
6248 enabled: true,
6249 auto_start: false,
6250 process_limits: ProcessLimits::default(),
6251 initialization_options: None,
6252 env: Default::default(),
6253 language_id_overrides: Default::default(),
6254 name: None,
6255 only_features: None,
6256 except_features: None,
6257 root_markers: Default::default(),
6258 }]),
6259 );
6260
6261 lsp.insert(
6264 "typst".to_string(),
6265 LspLanguageConfig::Multi(vec![LspServerConfig {
6266 command: "tinymist".to_string(),
6267 args: vec![],
6268 enabled: true,
6269 auto_start: false,
6270 process_limits: ProcessLimits::default(),
6271 initialization_options: None,
6272 env: Default::default(),
6273 language_id_overrides: Default::default(),
6274 name: None,
6275 only_features: None,
6276 except_features: None,
6277 root_markers: Default::default(),
6278 }]),
6279 );
6280
6281 lsp.insert(
6283 "bash".to_string(),
6284 LspLanguageConfig::Multi(vec![LspServerConfig {
6285 command: "bash-language-server".to_string(),
6286 args: vec!["start".to_string()],
6287 enabled: true,
6288 auto_start: false,
6289 process_limits: ProcessLimits::default(),
6290 initialization_options: None,
6291 env: Default::default(),
6292 language_id_overrides: Default::default(),
6293 name: None,
6294 only_features: None,
6295 except_features: None,
6296 root_markers: Default::default(),
6297 }]),
6298 );
6299
6300 lsp.insert(
6303 "lua".to_string(),
6304 LspLanguageConfig::Multi(vec![LspServerConfig {
6305 command: "lua-language-server".to_string(),
6306 args: vec![],
6307 enabled: true,
6308 auto_start: false,
6309 process_limits: ProcessLimits::default(),
6310 initialization_options: None,
6311 env: Default::default(),
6312 language_id_overrides: Default::default(),
6313 name: None,
6314 only_features: None,
6315 except_features: None,
6316 root_markers: vec![
6317 ".luarc.json".to_string(),
6318 ".luarc.jsonc".to_string(),
6319 ".luacheckrc".to_string(),
6320 ".stylua.toml".to_string(),
6321 ".git".to_string(),
6322 ],
6323 }]),
6324 );
6325
6326 lsp.insert(
6328 "ruby".to_string(),
6329 LspLanguageConfig::Multi(vec![LspServerConfig {
6330 command: "solargraph".to_string(),
6331 args: vec!["stdio".to_string()],
6332 enabled: true,
6333 auto_start: false,
6334 process_limits: ProcessLimits::default(),
6335 initialization_options: None,
6336 env: Default::default(),
6337 language_id_overrides: Default::default(),
6338 name: None,
6339 only_features: None,
6340 except_features: None,
6341 root_markers: vec![
6342 "Gemfile".to_string(),
6343 ".ruby-version".to_string(),
6344 ".git".to_string(),
6345 ],
6346 }]),
6347 );
6348
6349 lsp.insert(
6352 "php".to_string(),
6353 LspLanguageConfig::Multi(vec![LspServerConfig {
6354 command: "phpactor".to_string(),
6355 args: vec!["language-server".to_string()],
6356 enabled: true,
6357 auto_start: false,
6358 process_limits: ProcessLimits::default(),
6359 initialization_options: None,
6360 env: Default::default(),
6361 language_id_overrides: Default::default(),
6362 name: None,
6363 only_features: None,
6364 except_features: None,
6365 root_markers: vec!["composer.json".to_string(), ".git".to_string()],
6366 }]),
6367 );
6368
6369 lsp.insert(
6371 "yaml".to_string(),
6372 LspLanguageConfig::Multi(vec![LspServerConfig {
6373 command: "yaml-language-server".to_string(),
6374 args: vec!["--stdio".to_string()],
6375 enabled: true,
6376 auto_start: false,
6377 process_limits: ProcessLimits::default(),
6378 initialization_options: None,
6379 env: Default::default(),
6380 language_id_overrides: Default::default(),
6381 name: None,
6382 only_features: None,
6383 except_features: None,
6384 root_markers: Default::default(),
6385 }]),
6386 );
6387
6388 lsp.insert(
6391 "toml".to_string(),
6392 LspLanguageConfig::Multi(vec![LspServerConfig {
6393 command: "taplo".to_string(),
6394 args: vec!["lsp".to_string(), "stdio".to_string()],
6395 enabled: true,
6396 auto_start: false,
6397 process_limits: ProcessLimits::default(),
6398 initialization_options: None,
6399 env: Default::default(),
6400 language_id_overrides: Default::default(),
6401 name: None,
6402 only_features: None,
6403 except_features: None,
6404 root_markers: Default::default(),
6405 }]),
6406 );
6407
6408 lsp.insert(
6411 "dart".to_string(),
6412 LspLanguageConfig::Multi(vec![LspServerConfig {
6413 command: "dart".to_string(),
6414 args: vec!["language-server".to_string(), "--protocol=lsp".to_string()],
6415 enabled: true,
6416 auto_start: false,
6417 process_limits: ProcessLimits::default(),
6418 initialization_options: None,
6419 env: Default::default(),
6420 language_id_overrides: Default::default(),
6421 name: None,
6422 only_features: None,
6423 except_features: None,
6424 root_markers: vec!["pubspec.yaml".to_string(), ".git".to_string()],
6425 }]),
6426 );
6427
6428 lsp.insert(
6431 "nushell".to_string(),
6432 LspLanguageConfig::Multi(vec![LspServerConfig {
6433 command: "nu".to_string(),
6434 args: vec!["--lsp".to_string()],
6435 enabled: true,
6436 auto_start: false,
6437 process_limits: ProcessLimits::default(),
6438 initialization_options: None,
6439 env: Default::default(),
6440 language_id_overrides: Default::default(),
6441 name: None,
6442 only_features: None,
6443 except_features: None,
6444 root_markers: Default::default(),
6445 }]),
6446 );
6447
6448 lsp.insert(
6451 "solidity".to_string(),
6452 LspLanguageConfig::Multi(vec![LspServerConfig {
6453 command: "nomicfoundation-solidity-language-server".to_string(),
6454 args: vec!["--stdio".to_string()],
6455 enabled: true,
6456 auto_start: false,
6457 process_limits: ProcessLimits::default(),
6458 initialization_options: None,
6459 env: Default::default(),
6460 language_id_overrides: Default::default(),
6461 name: None,
6462 only_features: None,
6463 except_features: None,
6464 root_markers: Default::default(),
6465 }]),
6466 );
6467
6468 lsp.insert(
6473 "terraform".to_string(),
6474 LspLanguageConfig::Multi(vec![LspServerConfig {
6475 command: "terraform-ls".to_string(),
6476 args: vec!["serve".to_string()],
6477 enabled: true,
6478 auto_start: false,
6479 process_limits: ProcessLimits::default(),
6480 initialization_options: None,
6481 env: Default::default(),
6482 language_id_overrides: Default::default(),
6483 name: None,
6484 only_features: None,
6485 except_features: None,
6486 root_markers: vec![
6487 "*.tf".to_string(),
6488 ".terraform".to_string(),
6489 ".git".to_string(),
6490 ],
6491 }]),
6492 );
6493
6494 lsp.insert(
6497 "cmake".to_string(),
6498 LspLanguageConfig::Multi(vec![LspServerConfig {
6499 command: "cmake-language-server".to_string(),
6500 args: vec![],
6501 enabled: true,
6502 auto_start: false,
6503 process_limits: ProcessLimits::default(),
6504 initialization_options: None,
6505 env: Default::default(),
6506 language_id_overrides: Default::default(),
6507 name: None,
6508 only_features: None,
6509 except_features: None,
6510 root_markers: vec!["CMakeLists.txt".to_string(), ".git".to_string()],
6511 }]),
6512 );
6513
6514 lsp.insert(
6517 "protobuf".to_string(),
6518 LspLanguageConfig::Multi(vec![LspServerConfig {
6519 command: "buf".to_string(),
6520 args: vec!["beta".to_string(), "lsp".to_string()],
6521 enabled: true,
6522 auto_start: false,
6523 process_limits: ProcessLimits::default(),
6524 initialization_options: None,
6525 env: Default::default(),
6526 language_id_overrides: Default::default(),
6527 name: None,
6528 only_features: None,
6529 except_features: None,
6530 root_markers: Default::default(),
6531 }]),
6532 );
6533
6534 lsp.insert(
6537 "graphql".to_string(),
6538 LspLanguageConfig::Multi(vec![LspServerConfig {
6539 command: "graphql-lsp".to_string(),
6540 args: vec!["server".to_string(), "-m".to_string(), "stream".to_string()],
6541 enabled: true,
6542 auto_start: false,
6543 process_limits: ProcessLimits::default(),
6544 initialization_options: None,
6545 env: Default::default(),
6546 language_id_overrides: Default::default(),
6547 name: None,
6548 only_features: None,
6549 except_features: None,
6550 root_markers: Default::default(),
6551 }]),
6552 );
6553
6554 lsp.insert(
6557 "sql".to_string(),
6558 LspLanguageConfig::Multi(vec![LspServerConfig {
6559 command: "sqls".to_string(),
6560 args: vec![],
6561 enabled: true,
6562 auto_start: false,
6563 process_limits: ProcessLimits::default(),
6564 initialization_options: None,
6565 env: Default::default(),
6566 language_id_overrides: Default::default(),
6567 name: None,
6568 only_features: None,
6569 except_features: None,
6570 root_markers: Default::default(),
6571 }]),
6572 );
6573
6574 lsp.insert(
6578 "vue".to_string(),
6579 LspLanguageConfig::Multi(vec![LspServerConfig {
6580 command: "vue-language-server".to_string(),
6581 args: vec!["--stdio".to_string()],
6582 enabled: true,
6583 auto_start: false,
6584 process_limits: ProcessLimits::default(),
6585 initialization_options: None,
6586 env: Default::default(),
6587 language_id_overrides: Default::default(),
6588 name: None,
6589 only_features: None,
6590 except_features: None,
6591 root_markers: Default::default(),
6592 }]),
6593 );
6594
6595 lsp.insert(
6597 "svelte".to_string(),
6598 LspLanguageConfig::Multi(vec![LspServerConfig {
6599 command: "svelteserver".to_string(),
6600 args: vec!["--stdio".to_string()],
6601 enabled: true,
6602 auto_start: false,
6603 process_limits: ProcessLimits::default(),
6604 initialization_options: None,
6605 env: Default::default(),
6606 language_id_overrides: Default::default(),
6607 name: None,
6608 only_features: None,
6609 except_features: None,
6610 root_markers: Default::default(),
6611 }]),
6612 );
6613
6614 lsp.insert(
6616 "astro".to_string(),
6617 LspLanguageConfig::Multi(vec![LspServerConfig {
6618 command: "astro-ls".to_string(),
6619 args: vec!["--stdio".to_string()],
6620 enabled: true,
6621 auto_start: false,
6622 process_limits: ProcessLimits::default(),
6623 initialization_options: None,
6624 env: Default::default(),
6625 language_id_overrides: Default::default(),
6626 name: None,
6627 only_features: None,
6628 except_features: None,
6629 root_markers: Default::default(),
6630 }]),
6631 );
6632
6633 lsp.insert(
6635 "tailwindcss".to_string(),
6636 LspLanguageConfig::Multi(vec![LspServerConfig {
6637 command: "tailwindcss-language-server".to_string(),
6638 args: vec!["--stdio".to_string()],
6639 enabled: true,
6640 auto_start: false,
6641 process_limits: ProcessLimits::default(),
6642 initialization_options: None,
6643 env: Default::default(),
6644 language_id_overrides: Default::default(),
6645 name: None,
6646 only_features: None,
6647 except_features: None,
6648 root_markers: Default::default(),
6649 }]),
6650 );
6651
6652 lsp.insert(
6657 "nix".to_string(),
6658 LspLanguageConfig::Multi(vec![LspServerConfig {
6659 command: "nil".to_string(),
6660 args: vec![],
6661 enabled: true,
6662 auto_start: false,
6663 process_limits: ProcessLimits::default(),
6664 initialization_options: None,
6665 env: Default::default(),
6666 language_id_overrides: Default::default(),
6667 name: None,
6668 only_features: None,
6669 except_features: None,
6670 root_markers: Default::default(),
6671 }]),
6672 );
6673
6674 lsp.insert(
6677 "kotlin".to_string(),
6678 LspLanguageConfig::Multi(vec![LspServerConfig {
6679 command: "kotlin-language-server".to_string(),
6680 args: vec![],
6681 enabled: true,
6682 auto_start: false,
6683 process_limits: ProcessLimits::default(),
6684 initialization_options: None,
6685 env: Default::default(),
6686 language_id_overrides: Default::default(),
6687 name: None,
6688 only_features: None,
6689 except_features: None,
6690 root_markers: Default::default(),
6691 }]),
6692 );
6693
6694 lsp.insert(
6696 "swift".to_string(),
6697 LspLanguageConfig::Multi(vec![LspServerConfig {
6698 command: "sourcekit-lsp".to_string(),
6699 args: vec![],
6700 enabled: true,
6701 auto_start: false,
6702 process_limits: ProcessLimits::default(),
6703 initialization_options: None,
6704 env: Default::default(),
6705 language_id_overrides: Default::default(),
6706 name: None,
6707 only_features: None,
6708 except_features: None,
6709 root_markers: Default::default(),
6710 }]),
6711 );
6712
6713 lsp.insert(
6716 "scala".to_string(),
6717 LspLanguageConfig::Multi(vec![LspServerConfig {
6718 command: "metals".to_string(),
6719 args: vec![],
6720 enabled: true,
6721 auto_start: false,
6722 process_limits: ProcessLimits::default(),
6723 initialization_options: None,
6724 env: Default::default(),
6725 language_id_overrides: Default::default(),
6726 name: None,
6727 only_features: None,
6728 except_features: None,
6729 root_markers: Default::default(),
6730 }]),
6731 );
6732
6733 lsp.insert(
6736 "elixir".to_string(),
6737 LspLanguageConfig::Multi(vec![LspServerConfig {
6738 command: "elixir-ls".to_string(),
6739 args: vec![],
6740 enabled: true,
6741 auto_start: false,
6742 process_limits: ProcessLimits::default(),
6743 initialization_options: None,
6744 env: Default::default(),
6745 language_id_overrides: Default::default(),
6746 name: None,
6747 only_features: None,
6748 except_features: None,
6749 root_markers: Default::default(),
6750 }]),
6751 );
6752
6753 lsp.insert(
6755 "erlang".to_string(),
6756 LspLanguageConfig::Multi(vec![LspServerConfig {
6757 command: "erlang_ls".to_string(),
6758 args: vec![],
6759 enabled: true,
6760 auto_start: false,
6761 process_limits: ProcessLimits::default(),
6762 initialization_options: None,
6763 env: Default::default(),
6764 language_id_overrides: Default::default(),
6765 name: None,
6766 only_features: None,
6767 except_features: None,
6768 root_markers: Default::default(),
6769 }]),
6770 );
6771
6772 lsp.insert(
6775 "haskell".to_string(),
6776 LspLanguageConfig::Multi(vec![LspServerConfig {
6777 command: "haskell-language-server-wrapper".to_string(),
6778 args: vec!["--lsp".to_string()],
6779 enabled: true,
6780 auto_start: false,
6781 process_limits: ProcessLimits::default(),
6782 initialization_options: None,
6783 env: Default::default(),
6784 language_id_overrides: Default::default(),
6785 name: None,
6786 only_features: None,
6787 except_features: None,
6788 root_markers: Default::default(),
6789 }]),
6790 );
6791
6792 lsp.insert(
6795 "ocaml".to_string(),
6796 LspLanguageConfig::Multi(vec![LspServerConfig {
6797 command: "ocamllsp".to_string(),
6798 args: vec![],
6799 enabled: true,
6800 auto_start: false,
6801 process_limits: ProcessLimits::default(),
6802 initialization_options: None,
6803 env: Default::default(),
6804 language_id_overrides: Default::default(),
6805 name: None,
6806 only_features: None,
6807 except_features: None,
6808 root_markers: Default::default(),
6809 }]),
6810 );
6811
6812 lsp.insert(
6815 "clojure".to_string(),
6816 LspLanguageConfig::Multi(vec![LspServerConfig {
6817 command: "clojure-lsp".to_string(),
6818 args: vec![],
6819 enabled: true,
6820 auto_start: false,
6821 process_limits: ProcessLimits::default(),
6822 initialization_options: None,
6823 env: Default::default(),
6824 language_id_overrides: Default::default(),
6825 name: None,
6826 only_features: None,
6827 except_features: None,
6828 root_markers: Default::default(),
6829 }]),
6830 );
6831
6832 lsp.insert(
6835 "r".to_string(),
6836 LspLanguageConfig::Multi(vec![LspServerConfig {
6837 command: "R".to_string(),
6838 args: vec![
6839 "--vanilla".to_string(),
6840 "-e".to_string(),
6841 "languageserver::run()".to_string(),
6842 ],
6843 enabled: true,
6844 auto_start: false,
6845 process_limits: ProcessLimits::default(),
6846 initialization_options: None,
6847 env: Default::default(),
6848 language_id_overrides: Default::default(),
6849 name: None,
6850 only_features: None,
6851 except_features: None,
6852 root_markers: Default::default(),
6853 }]),
6854 );
6855
6856 lsp.insert(
6859 "julia".to_string(),
6860 LspLanguageConfig::Multi(vec![LspServerConfig {
6861 command: "julia".to_string(),
6862 args: vec![
6863 "--startup-file=no".to_string(),
6864 "--history-file=no".to_string(),
6865 "-e".to_string(),
6866 "using LanguageServer; runserver()".to_string(),
6867 ],
6868 enabled: true,
6869 auto_start: false,
6870 process_limits: ProcessLimits::default(),
6871 initialization_options: None,
6872 env: Default::default(),
6873 language_id_overrides: Default::default(),
6874 name: None,
6875 only_features: None,
6876 except_features: None,
6877 root_markers: Default::default(),
6878 }]),
6879 );
6880
6881 lsp.insert(
6884 "perl".to_string(),
6885 LspLanguageConfig::Multi(vec![LspServerConfig {
6886 command: "perlnavigator".to_string(),
6887 args: vec!["--stdio".to_string()],
6888 enabled: true,
6889 auto_start: false,
6890 process_limits: ProcessLimits::default(),
6891 initialization_options: None,
6892 env: Default::default(),
6893 language_id_overrides: Default::default(),
6894 name: None,
6895 only_features: None,
6896 except_features: None,
6897 root_markers: Default::default(),
6898 }]),
6899 );
6900
6901 lsp.insert(
6904 "nim".to_string(),
6905 LspLanguageConfig::Multi(vec![LspServerConfig {
6906 command: "nimlangserver".to_string(),
6907 args: vec![],
6908 enabled: true,
6909 auto_start: false,
6910 process_limits: ProcessLimits::default(),
6911 initialization_options: None,
6912 env: Default::default(),
6913 language_id_overrides: Default::default(),
6914 name: None,
6915 only_features: None,
6916 except_features: None,
6917 root_markers: Default::default(),
6918 }]),
6919 );
6920
6921 lsp.insert(
6923 "gleam".to_string(),
6924 LspLanguageConfig::Multi(vec![LspServerConfig {
6925 command: "gleam".to_string(),
6926 args: vec!["lsp".to_string()],
6927 enabled: true,
6928 auto_start: false,
6929 process_limits: ProcessLimits::default(),
6930 initialization_options: None,
6931 env: Default::default(),
6932 language_id_overrides: Default::default(),
6933 name: None,
6934 only_features: None,
6935 except_features: None,
6936 root_markers: Default::default(),
6937 }]),
6938 );
6939
6940 lsp.insert(
6943 "racket".to_string(),
6944 LspLanguageConfig::Multi(vec![LspServerConfig {
6945 command: "racket-langserver".to_string(),
6946 args: vec![],
6947 enabled: true,
6948 auto_start: false,
6949 process_limits: ProcessLimits::default(),
6950 initialization_options: None,
6951 env: Default::default(),
6952 language_id_overrides: Default::default(),
6953 name: None,
6954 only_features: None,
6955 except_features: None,
6956 root_markers: vec!["info.rkt".to_string(), ".git".to_string()],
6957 }]),
6958 );
6959
6960 lsp.insert(
6963 "fsharp".to_string(),
6964 LspLanguageConfig::Multi(vec![LspServerConfig {
6965 command: "fsautocomplete".to_string(),
6966 args: vec!["--adaptive-lsp-server-enabled".to_string()],
6967 enabled: true,
6968 auto_start: false,
6969 process_limits: ProcessLimits::default(),
6970 initialization_options: None,
6971 env: Default::default(),
6972 language_id_overrides: Default::default(),
6973 name: None,
6974 only_features: None,
6975 except_features: None,
6976 root_markers: Default::default(),
6977 }]),
6978 );
6979
6980 let svls_config = LspServerConfig {
6984 command: "svls".to_string(),
6985 args: vec![],
6986 enabled: true,
6987 auto_start: false,
6988 process_limits: ProcessLimits::default(),
6989 initialization_options: None,
6990 env: Default::default(),
6991 language_id_overrides: Default::default(),
6992 name: None,
6993 only_features: None,
6994 except_features: None,
6995 root_markers: vec![".svls.toml".to_string(), ".git".to_string()],
6996 };
6997 lsp.insert(
6998 "verilog".to_string(),
6999 LspLanguageConfig::Multi(vec![svls_config.clone()]),
7000 );
7001 lsp.insert(
7002 "systemverilog".to_string(),
7003 LspLanguageConfig::Multi(vec![svls_config]),
7004 );
7005 }
7006 pub fn validate(&self) -> Result<(), ConfigError> {
7007 if self.editor.tab_size == 0 {
7009 return Err(ConfigError::ValidationError(
7010 "tab_size must be greater than 0".to_string(),
7011 ));
7012 }
7013
7014 if self.editor.scroll_offset > 100 {
7016 return Err(ConfigError::ValidationError(
7017 "scroll_offset must be <= 100".to_string(),
7018 ));
7019 }
7020
7021 for binding in &self.keybindings {
7023 if binding.key.is_empty() {
7024 return Err(ConfigError::ValidationError(
7025 "keybinding key cannot be empty".to_string(),
7026 ));
7027 }
7028 if binding.action.is_empty() {
7029 return Err(ConfigError::ValidationError(
7030 "keybinding action cannot be empty".to_string(),
7031 ));
7032 }
7033 }
7034
7035 Ok(())
7036 }
7037}
7038
7039#[derive(Debug)]
7041pub enum ConfigError {
7042 IoError(String),
7043 ParseError(String),
7044 SerializeError(String),
7045 ValidationError(String),
7046}
7047
7048impl std::fmt::Display for ConfigError {
7049 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7050 match self {
7051 Self::IoError(msg) => write!(f, "IO error: {msg}"),
7052 Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
7053 Self::SerializeError(msg) => write!(f, "Serialize error: {msg}"),
7054 Self::ValidationError(msg) => write!(f, "Validation error: {msg}"),
7055 }
7056 }
7057}
7058
7059impl std::error::Error for ConfigError {}
7060
7061#[cfg(test)]
7062mod tests {
7063 use super::*;
7064
7065 #[test]
7066 fn test_file_explorer_width_default_is_percent_30() {
7067 let cfg = FileExplorerConfig::default();
7068 assert_eq!(cfg.width, ExplorerWidth::Percent(30));
7069 }
7070
7071 #[test]
7072 fn test_tree_indicators_defaults_preserve_current_glyphs() {
7073 let cfg = FileExplorerConfig::default();
7077 assert_eq!(cfg.tree_indicator_collapsed, ">");
7078 assert_eq!(cfg.tree_indicator_expanded, "▼");
7079 }
7080
7081 #[test]
7082 fn test_tree_indicators_can_be_overridden_from_json() {
7083 let cfg: FileExplorerConfig = serde_json::from_str(
7084 r#"{"tree_indicator_collapsed": "▸", "tree_indicator_expanded": "▾"}"#,
7085 )
7086 .unwrap();
7087 assert_eq!(cfg.tree_indicator_collapsed, "▸");
7088 assert_eq!(cfg.tree_indicator_expanded, "▾");
7089 }
7090
7091 #[test]
7092 fn test_tree_indicators_partial_override_keeps_default_for_unset() {
7093 let cfg: FileExplorerConfig =
7094 serde_json::from_str(r#"{"tree_indicator_collapsed": "+"}"#).unwrap();
7095 assert_eq!(cfg.tree_indicator_collapsed, "+");
7096 assert_eq!(cfg.tree_indicator_expanded, "▼");
7098 }
7099
7100 #[test]
7103 fn test_width_accepts_legacy_float_fraction() {
7104 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": 0.3}"#).unwrap();
7105 assert_eq!(cfg.width, ExplorerWidth::Percent(30));
7106 }
7107
7108 #[test]
7109 fn test_width_accepts_bare_integer_as_percent() {
7110 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": 42}"#).unwrap();
7112 assert_eq!(cfg.width, ExplorerWidth::Percent(42));
7113 }
7114
7115 #[test]
7116 fn test_width_accepts_percent_string() {
7117 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "75%"}"#).unwrap();
7118 assert_eq!(cfg.width, ExplorerWidth::Percent(75));
7119 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "42 %"}"#).unwrap();
7121 assert_eq!(cfg.width, ExplorerWidth::Percent(42));
7122 }
7123
7124 #[test]
7125 fn test_width_accepts_columns_string() {
7126 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "24"}"#).unwrap();
7127 assert_eq!(cfg.width, ExplorerWidth::Columns(24));
7128 }
7129
7130 #[test]
7131 fn test_width_rejects_percent_over_100() {
7132 let err = serde_json::from_str::<FileExplorerConfig>(r#"{"width": "150%"}"#)
7133 .expect_err("percent > 100 should be rejected");
7134 assert!(err.to_string().contains("100"), "{err}");
7135 }
7136
7137 #[test]
7138 fn test_width_rejects_integer_over_100() {
7139 let err = serde_json::from_str::<FileExplorerConfig>(r#"{"width": 150}"#)
7142 .expect_err("bare integer > 100 should be rejected as percent");
7143 assert!(err.to_string().contains("percent") || err.to_string().contains("100"));
7144 }
7145
7146 #[test]
7147 fn test_width_rejects_garbage_string() {
7148 serde_json::from_str::<FileExplorerConfig>(r#"{"width": "big"}"#)
7149 .expect_err("non-numeric string should be rejected");
7150 }
7151
7152 #[test]
7155 fn test_width_serializes_percent_as_string_with_suffix() {
7156 let cfg = FileExplorerConfig {
7157 width: ExplorerWidth::Percent(30),
7158 ..Default::default()
7159 };
7160 let json = serde_json::to_value(&cfg).unwrap();
7161 assert_eq!(json["width"], serde_json::json!("30%"));
7162 }
7163
7164 #[test]
7165 fn test_width_serializes_columns_as_string_without_suffix() {
7166 let cfg = FileExplorerConfig {
7167 width: ExplorerWidth::Columns(24),
7168 ..Default::default()
7169 };
7170 let json = serde_json::to_value(&cfg).unwrap();
7171 assert_eq!(json["width"], serde_json::json!("24"));
7172 }
7173
7174 #[test]
7175 fn test_width_round_trip_both_variants() {
7176 for value in [ExplorerWidth::Percent(17), ExplorerWidth::Columns(42)] {
7177 let cfg = FileExplorerConfig {
7178 width: value,
7179 ..Default::default()
7180 };
7181 let json = serde_json::to_string(&cfg).unwrap();
7182 let restored: FileExplorerConfig = serde_json::from_str(&json).unwrap();
7183 assert_eq!(restored.width, value, "round trip failed for {:?}", value);
7184 }
7185 }
7186
7187 #[test]
7190 fn test_to_cols_percent() {
7191 assert_eq!(ExplorerWidth::Percent(30).to_cols(100), 30);
7192 assert_eq!(ExplorerWidth::Percent(25).to_cols(120), 30);
7193 assert_eq!(ExplorerWidth::Percent(30).to_cols(0), 0);
7195 assert_eq!(ExplorerWidth::Percent(100).to_cols(200), 200);
7196 }
7197
7198 #[test]
7199 fn test_to_cols_columns_clamps_to_terminal() {
7200 assert_eq!(ExplorerWidth::Columns(24).to_cols(100), 24);
7201 assert_eq!(ExplorerWidth::Columns(999).to_cols(80), 80);
7202 }
7203
7204 #[test]
7207 fn test_to_cols_enforces_min_width() {
7208 assert_eq!(
7210 ExplorerWidth::Columns(0).to_cols(100),
7211 ExplorerWidth::MIN_COLS
7212 );
7213 assert_eq!(
7214 ExplorerWidth::Columns(1).to_cols(100),
7215 ExplorerWidth::MIN_COLS
7216 );
7217 assert_eq!(
7218 ExplorerWidth::Columns(4).to_cols(100),
7219 ExplorerWidth::MIN_COLS
7220 );
7221 assert_eq!(
7222 ExplorerWidth::Percent(0).to_cols(100),
7223 ExplorerWidth::MIN_COLS
7224 );
7225 assert_eq!(
7227 ExplorerWidth::Percent(3).to_cols(100),
7228 ExplorerWidth::MIN_COLS
7229 );
7230 assert_eq!(
7232 ExplorerWidth::Columns(ExplorerWidth::MIN_COLS + 1).to_cols(100),
7233 ExplorerWidth::MIN_COLS + 1
7234 );
7235 }
7236
7237 #[test]
7240 fn test_to_cols_min_floor_yields_to_narrow_terminal() {
7241 assert_eq!(ExplorerWidth::Columns(10).to_cols(3), 3);
7242 assert_eq!(ExplorerWidth::Percent(100).to_cols(2), 2);
7243 assert_eq!(ExplorerWidth::Columns(1).to_cols(0), 0);
7244 }
7245
7246 #[test]
7249 fn test_load_from_file_accepts_legacy_float_fraction_width() {
7250 let dir = tempfile::tempdir().unwrap();
7251 let path = dir.path().join("config.json");
7252 std::fs::write(&path, r#"{"file_explorer":{"width":0.25}}"#).unwrap();
7253 let cfg = Config::load_from_file(&path).expect("legacy float fraction must still load");
7254 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Percent(25));
7255 }
7256
7257 #[test]
7258 fn test_load_from_file_accepts_columns_string_width() {
7259 let dir = tempfile::tempdir().unwrap();
7260 let path = dir.path().join("config.json");
7261 std::fs::write(&path, r#"{"file_explorer":{"width":"42"}}"#).unwrap();
7262 let cfg = Config::load_from_file(&path).unwrap();
7263 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Columns(42));
7264 }
7265
7266 #[test]
7267 fn test_load_from_file_accepts_percent_string_width() {
7268 let dir = tempfile::tempdir().unwrap();
7269 let path = dir.path().join("config.json");
7270 std::fs::write(&path, r#"{"file_explorer":{"width":"55%"}}"#).unwrap();
7271 let cfg = Config::load_from_file(&path).unwrap();
7272 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Percent(55));
7273 }
7274
7275 #[test]
7276 fn test_default_config() {
7277 let config = Config::default();
7278 assert_eq!(config.editor.tab_size, 4);
7279 assert!(config.editor.line_numbers);
7280 assert!(config.editor.syntax_highlighting);
7281 assert!(config.languages.contains_key("gdscript"));
7282 assert_eq!(config.languages["gdscript"].extensions, vec!["gd"]);
7283 assert!(config.keybindings.is_empty());
7286 let resolved = config.resolve_keymap(&config.active_keybinding_map);
7288 assert!(!resolved.is_empty());
7289 }
7290
7291 #[test]
7292 fn test_all_builtin_keymaps_loadable() {
7293 for name in KeybindingMapName::BUILTIN_OPTIONS {
7294 let keymap = Config::load_builtin_keymap(name);
7295 assert!(keymap.is_some(), "Failed to load builtin keymap '{}'", name);
7296 }
7297 }
7298
7299 #[test]
7300 fn test_config_validation() {
7301 let mut config = Config::default();
7302 assert!(config.validate().is_ok());
7303
7304 config.editor.tab_size = 0;
7305 assert!(config.validate().is_err());
7306 }
7307
7308 #[test]
7309 fn test_macos_keymap_inherits_enter_bindings() {
7310 let config = Config::default();
7311 let bindings = config.resolve_keymap("macos");
7312
7313 let enter_bindings: Vec<_> = bindings.iter().filter(|b| b.key == "Enter").collect();
7314 assert!(
7315 !enter_bindings.is_empty(),
7316 "macos keymap should inherit Enter bindings from default, got {} Enter bindings",
7317 enter_bindings.len()
7318 );
7319 let has_insert_newline = enter_bindings.iter().any(|b| b.action == "insert_newline");
7321 assert!(
7322 has_insert_newline,
7323 "macos keymap should have insert_newline action for Enter key"
7324 );
7325 }
7326
7327 #[test]
7328 fn test_config_serialize_deserialize() {
7329 let config = Config::default();
7331
7332 let json = serde_json::to_string_pretty(&config).unwrap();
7334
7335 let loaded: Config = serde_json::from_str(&json).unwrap();
7337
7338 assert_eq!(config.editor.tab_size, loaded.editor.tab_size);
7339 assert_eq!(config.theme, loaded.theme);
7340 }
7341
7342 #[test]
7343 fn test_config_with_custom_keybinding() {
7344 let json = r#"{
7345 "editor": {
7346 "tab_size": 2
7347 },
7348 "keybindings": [
7349 {
7350 "key": "x",
7351 "modifiers": ["ctrl", "shift"],
7352 "action": "custom_action",
7353 "args": {},
7354 "when": null
7355 }
7356 ]
7357 }"#;
7358
7359 let config: Config = serde_json::from_str(json).unwrap();
7360 assert_eq!(config.editor.tab_size, 2);
7361 assert_eq!(config.keybindings.len(), 1);
7362 assert_eq!(config.keybindings[0].key, "x");
7363 assert_eq!(config.keybindings[0].modifiers.len(), 2);
7364 }
7365
7366 #[test]
7367 fn test_sparse_config_merges_with_defaults() {
7368 let temp_dir = tempfile::tempdir().unwrap();
7370 let config_path = temp_dir.path().join("config.json");
7371
7372 let sparse_config = r#"{
7374 "lsp": {
7375 "rust": {
7376 "command": "custom-rust-analyzer",
7377 "args": ["--custom-arg"]
7378 }
7379 }
7380 }"#;
7381 std::fs::write(&config_path, sparse_config).unwrap();
7382
7383 let loaded = Config::load_from_file(&config_path).unwrap();
7385
7386 assert!(loaded.lsp.contains_key("rust"));
7388 assert_eq!(
7389 loaded.lsp["rust"].as_slice()[0].command,
7390 "custom-rust-analyzer".to_string()
7391 );
7392
7393 assert!(
7395 loaded.lsp.contains_key("python"),
7396 "python LSP should be merged from defaults"
7397 );
7398 assert!(
7399 loaded.lsp.contains_key("typescript"),
7400 "typescript LSP should be merged from defaults"
7401 );
7402 assert!(
7403 loaded.lsp.contains_key("javascript"),
7404 "javascript LSP should be merged from defaults"
7405 );
7406
7407 assert!(loaded.languages.contains_key("rust"));
7409 assert!(loaded.languages.contains_key("python"));
7410 assert!(loaded.languages.contains_key("typescript"));
7411 }
7412
7413 #[test]
7414 fn test_empty_config_gets_all_defaults() {
7415 let temp_dir = tempfile::tempdir().unwrap();
7416 let config_path = temp_dir.path().join("config.json");
7417
7418 std::fs::write(&config_path, "{}").unwrap();
7420
7421 let loaded = Config::load_from_file(&config_path).unwrap();
7422 let defaults = Config::default();
7423
7424 assert_eq!(loaded.lsp.len(), defaults.lsp.len());
7426
7427 assert_eq!(loaded.languages.len(), defaults.languages.len());
7429 }
7430
7431 #[test]
7432 fn test_dynamic_submenu_expansion() {
7433 let temp_dir = tempfile::tempdir().unwrap();
7435 let themes_dir = temp_dir.path().to_path_buf();
7436
7437 let dynamic = MenuItem::DynamicSubmenu {
7438 label: "Test".to_string(),
7439 source: "copy_with_theme".to_string(),
7440 };
7441
7442 let expanded = dynamic.expand_dynamic(&themes_dir);
7443
7444 match expanded {
7446 MenuItem::Submenu { label, items } => {
7447 assert_eq!(label, "Test");
7448 let loader = crate::view::theme::ThemeLoader::embedded_only();
7450 let registry = loader.load_all(&[]);
7451 assert_eq!(items.len(), registry.len());
7452
7453 for (item, theme_info) in items.iter().zip(registry.list().iter()) {
7455 match item {
7456 MenuItem::Action {
7457 label,
7458 action,
7459 args,
7460 ..
7461 } => {
7462 assert_eq!(label, &theme_info.name);
7463 assert_eq!(action, "copy_with_theme");
7464 assert_eq!(
7465 args.get("theme").and_then(|v| v.as_str()),
7466 Some(theme_info.name.as_str())
7467 );
7468 }
7469 _ => panic!("Expected Action item"),
7470 }
7471 }
7472 }
7473 _ => panic!("Expected Submenu after expansion"),
7474 }
7475 }
7476
7477 #[test]
7478 fn test_non_dynamic_item_unchanged() {
7479 let temp_dir = tempfile::tempdir().unwrap();
7481 let themes_dir = temp_dir.path();
7482
7483 let action = MenuItem::Action {
7484 label: "Test".to_string(),
7485 action: "test".to_string(),
7486 args: HashMap::new(),
7487 when: None,
7488 checkbox: None,
7489 };
7490
7491 let expanded = action.expand_dynamic(themes_dir);
7492 match expanded {
7493 MenuItem::Action { label, action, .. } => {
7494 assert_eq!(label, "Test");
7495 assert_eq!(action, "test");
7496 }
7497 _ => panic!("Action should remain Action after expand_dynamic"),
7498 }
7499 }
7500
7501 #[test]
7502 fn test_buffer_config_uses_global_defaults() {
7503 let config = Config::default();
7504 let buffer_config = BufferConfig::resolve(&config, None);
7505
7506 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
7507 assert_eq!(buffer_config.auto_indent, config.editor.auto_indent);
7508 assert!(!buffer_config.use_tabs); assert!(buffer_config.whitespace.any_tabs()); assert!(buffer_config.formatter.is_none());
7511 assert!(!buffer_config.format_on_save);
7512 }
7513
7514 #[test]
7515 fn test_buffer_config_applies_language_overrides() {
7516 let mut config = Config::default();
7517
7518 config.languages.insert(
7520 "go".to_string(),
7521 LanguageConfig {
7522 extensions: vec!["go".to_string()],
7523 filenames: vec![],
7524 grammar: "go".to_string(),
7525 comment_prefix: Some("//".to_string()),
7526 auto_indent: true,
7527 auto_close: None,
7528 auto_surround: None,
7529 textmate_grammar: None,
7530 show_whitespace_tabs: false, line_wrap: None,
7532 wrap_column: None,
7533 page_view: None,
7534 page_width: None,
7535 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
7538 command: "gofmt".to_string(),
7539 args: vec![],
7540 stdin: true,
7541 timeout_ms: 10000,
7542 }),
7543 format_on_save: true,
7544 on_save: vec![],
7545 word_characters: None,
7546 indent: None,
7547 },
7548 );
7549
7550 let buffer_config = BufferConfig::resolve(&config, Some("go"));
7551
7552 assert_eq!(buffer_config.tab_size, 8);
7553 assert!(buffer_config.use_tabs);
7554 assert!(!buffer_config.whitespace.any_tabs()); assert!(buffer_config.format_on_save);
7556 assert!(buffer_config.formatter.is_some());
7557 assert_eq!(buffer_config.formatter.as_ref().unwrap().command, "gofmt");
7558 }
7559
7560 #[test]
7561 fn test_buffer_config_unknown_language_uses_global() {
7562 let config = Config::default();
7563 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
7564
7565 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
7567 assert!(!buffer_config.use_tabs);
7568 }
7569
7570 #[test]
7571 fn test_buffer_config_per_language_line_wrap() {
7572 let mut config = Config::default();
7573 config.editor.line_wrap = false;
7574
7575 config.languages.insert(
7577 "markdown".to_string(),
7578 LanguageConfig {
7579 extensions: vec!["md".to_string()],
7580 line_wrap: Some(true),
7581 ..Default::default()
7582 },
7583 );
7584
7585 let md_config = BufferConfig::resolve(&config, Some("markdown"));
7587 assert!(md_config.line_wrap, "Markdown should have line_wrap=true");
7588
7589 let other_config = BufferConfig::resolve(&config, Some("rust"));
7591 assert!(
7592 !other_config.line_wrap,
7593 "Non-configured languages should use global line_wrap=false"
7594 );
7595
7596 let no_lang_config = BufferConfig::resolve(&config, None);
7598 assert!(
7599 !no_lang_config.line_wrap,
7600 "No language should use global line_wrap=false"
7601 );
7602 }
7603
7604 #[test]
7605 fn test_buffer_config_per_language_wrap_column() {
7606 let mut config = Config::default();
7607 config.editor.wrap_column = Some(120);
7608
7609 config.languages.insert(
7611 "markdown".to_string(),
7612 LanguageConfig {
7613 extensions: vec!["md".to_string()],
7614 wrap_column: Some(80),
7615 ..Default::default()
7616 },
7617 );
7618
7619 let md_config = BufferConfig::resolve(&config, Some("markdown"));
7621 assert_eq!(md_config.wrap_column, Some(80));
7622
7623 let other_config = BufferConfig::resolve(&config, Some("rust"));
7625 assert_eq!(other_config.wrap_column, Some(120));
7626
7627 let no_lang_config = BufferConfig::resolve(&config, None);
7629 assert_eq!(no_lang_config.wrap_column, Some(120));
7630 }
7631
7632 #[test]
7633 fn test_normalize_zero_sentinels_global() {
7634 let mut config = Config::default();
7635 config.editor.wrap_column = Some(0);
7636 config.editor.page_width = Some(0);
7637 config.editor.tab_size = 0;
7638
7639 config.normalize_zero_sentinels();
7640
7641 assert_eq!(config.editor.wrap_column, None);
7643 assert_eq!(config.editor.page_width, None);
7644 assert_eq!(config.editor.tab_size, default_tab_size());
7645 }
7646
7647 #[test]
7648 fn test_normalize_zero_sentinels_language_inherits_global() {
7649 let mut config = Config::default();
7650 config.editor.wrap_column = Some(120);
7651 config.editor.page_width = Some(80);
7652 config.editor.tab_size = 8;
7653
7654 config.languages.insert(
7657 "markdown".to_string(),
7658 LanguageConfig {
7659 extensions: vec!["md".to_string()],
7660 wrap_column: Some(0),
7661 page_width: Some(0),
7662 tab_size: Some(0),
7663 ..Default::default()
7664 },
7665 );
7666
7667 config.normalize_zero_sentinels();
7668
7669 let lang = &config.languages["markdown"];
7670 assert_eq!(lang.wrap_column, None);
7671 assert_eq!(lang.page_width, None);
7672 assert_eq!(lang.tab_size, None);
7673
7674 let md = BufferConfig::resolve(&config, Some("markdown"));
7676 assert_eq!(md.wrap_column, Some(120));
7677 assert_eq!(md.tab_size, 8);
7678 }
7679
7680 #[test]
7681 fn test_buffer_config_indent_string() {
7682 let config = Config::default();
7683
7684 let spaces_config = BufferConfig::resolve(&config, None);
7686 assert_eq!(spaces_config.indent_string(), " "); let mut config_with_tabs = Config::default();
7690 config_with_tabs.languages.insert(
7691 "makefile".to_string(),
7692 LanguageConfig {
7693 use_tabs: Some(true),
7694 tab_size: Some(8),
7695 ..Default::default()
7696 },
7697 );
7698 let tabs_config = BufferConfig::resolve(&config_with_tabs, Some("makefile"));
7699 assert_eq!(tabs_config.indent_string(), "\t");
7700 }
7701
7702 #[test]
7703 fn test_buffer_config_global_use_tabs_inherited() {
7704 let mut config = Config::default();
7707 config.editor.use_tabs = true;
7708
7709 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
7711 assert!(buffer_config.use_tabs);
7712
7713 let buffer_config = BufferConfig::resolve(&config, None);
7715 assert!(buffer_config.use_tabs);
7716
7717 config.languages.insert(
7719 "python".to_string(),
7720 LanguageConfig {
7721 use_tabs: Some(false),
7722 ..Default::default()
7723 },
7724 );
7725 let buffer_config = BufferConfig::resolve(&config, Some("python"));
7726 assert!(!buffer_config.use_tabs);
7727
7728 config.languages.insert(
7730 "rust".to_string(),
7731 LanguageConfig {
7732 use_tabs: None,
7733 ..Default::default()
7734 },
7735 );
7736 let buffer_config = BufferConfig::resolve(&config, Some("rust"));
7737 assert!(buffer_config.use_tabs);
7738 }
7739
7740 #[test]
7746 #[cfg(feature = "runtime")]
7747 fn test_lsp_languages_have_language_config() {
7748 let config = Config::default();
7749 let exceptions = ["tailwindcss"];
7750 for lsp_key in config.lsp.keys() {
7751 if exceptions.contains(&lsp_key.as_str()) {
7752 continue;
7753 }
7754 assert!(
7755 config.languages.contains_key(lsp_key),
7756 "LSP config key '{}' has no matching entry in default_languages(). \
7757 Add a LanguageConfig with the correct file extensions so detect_language() \
7758 can map files to this language.",
7759 lsp_key
7760 );
7761 }
7762 }
7763
7764 #[test]
7765 #[cfg(feature = "runtime")]
7766 fn test_gdscript_lsp_disabled_by_default() {
7767 let config = Config::default();
7768 let server = &config.lsp["gdscript"].as_slice()[0];
7769 assert_eq!(server.command, "nc");
7770 assert_eq!(server.args, vec!["127.0.0.1", "6005"]);
7771 assert!(!server.enabled);
7772 assert_eq!(server.name.as_deref(), Some("Godot GDScript"));
7773 }
7774
7775 #[test]
7776 #[cfg(feature = "runtime")]
7777 fn test_default_config_has_quicklsp_in_universal_lsp() {
7778 let config = Config::default();
7779 assert!(
7780 config.universal_lsp.contains_key("quicklsp"),
7781 "Default config should contain quicklsp in universal_lsp"
7782 );
7783 let quicklsp = &config.universal_lsp["quicklsp"];
7784 let server = &quicklsp.as_slice()[0];
7785 assert_eq!(server.command, "quicklsp");
7786 assert!(!server.enabled, "quicklsp should be disabled by default");
7787 assert_eq!(server.name.as_deref(), Some("QuickLSP"));
7788 assert!(
7792 server.only_features.is_none(),
7793 "quicklsp must not default to a feature whitelist"
7794 );
7795 assert!(server.except_features.is_none());
7796 }
7797
7798 #[test]
7799 fn test_empty_config_preserves_universal_lsp_defaults() {
7800 let temp_dir = tempfile::tempdir().unwrap();
7801 let config_path = temp_dir.path().join("config.json");
7802
7803 std::fs::write(&config_path, "{}").unwrap();
7805
7806 let loaded = Config::load_from_file(&config_path).unwrap();
7807 let defaults = Config::default();
7808
7809 assert_eq!(
7811 loaded.universal_lsp.len(),
7812 defaults.universal_lsp.len(),
7813 "Empty config should preserve all default universal_lsp entries"
7814 );
7815 }
7816
7817 #[test]
7818 fn test_universal_lsp_config_merges_with_defaults() {
7819 let temp_dir = tempfile::tempdir().unwrap();
7820 let config_path = temp_dir.path().join("config.json");
7821
7822 let config_json = r#"{
7824 "universal_lsp": {
7825 "quicklsp": {
7826 "enabled": true
7827 }
7828 }
7829 }"#;
7830 std::fs::write(&config_path, config_json).unwrap();
7831
7832 let loaded = Config::load_from_file(&config_path).unwrap();
7833
7834 assert!(loaded.universal_lsp.contains_key("quicklsp"));
7836 let server = &loaded.universal_lsp["quicklsp"].as_slice()[0];
7837 assert!(server.enabled, "User override should enable quicklsp");
7838 assert_eq!(
7840 server.command, "quicklsp",
7841 "Default command should be merged when not specified by user"
7842 );
7843 }
7844
7845 #[test]
7846 fn test_universal_lsp_custom_server_added() {
7847 let temp_dir = tempfile::tempdir().unwrap();
7848 let config_path = temp_dir.path().join("config.json");
7849
7850 let config_json = r#"{
7852 "universal_lsp": {
7853 "my-custom-server": {
7854 "command": "my-server",
7855 "enabled": true,
7856 "auto_start": true
7857 }
7858 }
7859 }"#;
7860 std::fs::write(&config_path, config_json).unwrap();
7861
7862 let loaded = Config::load_from_file(&config_path).unwrap();
7863
7864 assert!(
7866 loaded.universal_lsp.contains_key("my-custom-server"),
7867 "Custom universal server should be loaded"
7868 );
7869 let server = &loaded.universal_lsp["my-custom-server"].as_slice()[0];
7870 assert_eq!(server.command, "my-server");
7871 assert!(server.enabled);
7872 assert!(server.auto_start);
7873
7874 assert!(
7876 loaded.universal_lsp.contains_key("quicklsp"),
7877 "Default quicklsp should be merged from defaults"
7878 );
7879 }
7880}