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"];
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 pub active_keybinding_map: KeybindingMapName,
377
378 #[serde(default)]
380 pub languages: HashMap<String, LanguageConfig>,
381
382 #[serde(default)]
388 #[schemars(extend("x-enum-from" = "/languages"))]
389 pub default_language: Option<String>,
390
391 #[serde(default)]
395 pub lsp: HashMap<String, LspLanguageConfig>,
396
397 #[serde(default)]
401 pub universal_lsp: HashMap<String, LspLanguageConfig>,
402
403 #[serde(default)]
405 pub warnings: WarningsConfig,
406
407 #[serde(default)]
411 #[schemars(extend("x-standalone-category" = true, "x-no-add" = true))]
412 pub plugins: HashMap<String, PluginConfig>,
413
414 #[serde(default)]
416 pub packages: PackagesConfig,
417}
418
419fn default_keybinding_map_name() -> KeybindingMapName {
420 if cfg!(target_os = "macos") {
423 KeybindingMapName("macos".to_string())
424 } else {
425 KeybindingMapName("default".to_string())
426 }
427}
428
429fn default_theme_name() -> ThemeName {
430 ThemeName("high-contrast".to_string())
431}
432
433#[derive(Debug, Clone, Copy)]
438pub struct WhitespaceVisibility {
439 pub spaces_leading: bool,
440 pub spaces_inner: bool,
441 pub spaces_trailing: bool,
442 pub tabs_leading: bool,
443 pub tabs_inner: bool,
444 pub tabs_trailing: bool,
445}
446
447impl Default for WhitespaceVisibility {
448 fn default() -> Self {
449 Self {
451 spaces_leading: false,
452 spaces_inner: false,
453 spaces_trailing: false,
454 tabs_leading: true,
455 tabs_inner: true,
456 tabs_trailing: true,
457 }
458 }
459}
460
461impl WhitespaceVisibility {
462 pub fn from_editor_config(editor: &EditorConfig) -> Self {
464 if !editor.whitespace_show {
465 return Self {
466 spaces_leading: false,
467 spaces_inner: false,
468 spaces_trailing: false,
469 tabs_leading: false,
470 tabs_inner: false,
471 tabs_trailing: false,
472 };
473 }
474 Self {
475 spaces_leading: editor.whitespace_spaces_leading,
476 spaces_inner: editor.whitespace_spaces_inner,
477 spaces_trailing: editor.whitespace_spaces_trailing,
478 tabs_leading: editor.whitespace_tabs_leading,
479 tabs_inner: editor.whitespace_tabs_inner,
480 tabs_trailing: editor.whitespace_tabs_trailing,
481 }
482 }
483
484 pub fn with_language_tab_override(mut self, show_whitespace_tabs: bool) -> Self {
487 if !show_whitespace_tabs {
488 self.tabs_leading = false;
489 self.tabs_inner = false;
490 self.tabs_trailing = false;
491 }
492 self
493 }
494
495 pub fn any_spaces(&self) -> bool {
497 self.spaces_leading || self.spaces_inner || self.spaces_trailing
498 }
499
500 pub fn any_tabs(&self) -> bool {
502 self.tabs_leading || self.tabs_inner || self.tabs_trailing
503 }
504
505 pub fn any_visible(&self) -> bool {
507 self.any_spaces() || self.any_tabs()
508 }
509
510 pub fn toggle_all(&mut self) {
514 if self.any_visible() {
515 *self = Self {
516 spaces_leading: false,
517 spaces_inner: false,
518 spaces_trailing: false,
519 tabs_leading: false,
520 tabs_inner: false,
521 tabs_trailing: false,
522 };
523 } else {
524 *self = Self::default();
525 }
526 }
527}
528
529#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
548#[serde(try_from = "String", into = "String")]
549pub enum StatusBarElement {
550 Filename,
552 Cursor,
554 CursorCompact,
556 Diagnostics,
558 CursorCount,
560 Messages,
562 Chord,
564 LineEnding,
566 Encoding,
568 Language,
570 Lsp,
572 Warnings,
574 Update,
576 Palette,
578 Clock,
580}
581
582impl TryFrom<String> for StatusBarElement {
583 type Error = String;
584 fn try_from(s: String) -> Result<Self, String> {
585 let inner = s
587 .strip_prefix('{')
588 .and_then(|s| s.strip_suffix('}'))
589 .unwrap_or(&s);
590 match inner {
591 "filename" => Ok(Self::Filename),
592 "cursor" => Ok(Self::Cursor),
593 "cursor:compact" => Ok(Self::CursorCompact),
594 "diagnostics" => Ok(Self::Diagnostics),
595 "cursor_count" => Ok(Self::CursorCount),
596 "messages" => Ok(Self::Messages),
597 "chord" => Ok(Self::Chord),
598 "line_ending" => Ok(Self::LineEnding),
599 "encoding" => Ok(Self::Encoding),
600 "language" => Ok(Self::Language),
601 "lsp" => Ok(Self::Lsp),
602 "warnings" => Ok(Self::Warnings),
603 "update" => Ok(Self::Update),
604 "palette" => Ok(Self::Palette),
605 "clock" => Ok(Self::Clock),
606 _ => Err(format!("Unknown status bar element: {}", s)),
607 }
608 }
609}
610
611impl From<StatusBarElement> for String {
612 fn from(e: StatusBarElement) -> String {
613 match e {
614 StatusBarElement::Filename => "{filename}".to_string(),
615 StatusBarElement::Cursor => "{cursor}".to_string(),
616 StatusBarElement::CursorCompact => "{cursor:compact}".to_string(),
617 StatusBarElement::Diagnostics => "{diagnostics}".to_string(),
618 StatusBarElement::CursorCount => "{cursor_count}".to_string(),
619 StatusBarElement::Messages => "{messages}".to_string(),
620 StatusBarElement::Chord => "{chord}".to_string(),
621 StatusBarElement::LineEnding => "{line_ending}".to_string(),
622 StatusBarElement::Encoding => "{encoding}".to_string(),
623 StatusBarElement::Language => "{language}".to_string(),
624 StatusBarElement::Lsp => "{lsp}".to_string(),
625 StatusBarElement::Warnings => "{warnings}".to_string(),
626 StatusBarElement::Update => "{update}".to_string(),
627 StatusBarElement::Palette => "{palette}".to_string(),
628 StatusBarElement::Clock => "{clock}".to_string(),
629 }
630 }
631}
632
633impl schemars::JsonSchema for StatusBarElement {
634 fn schema_name() -> std::borrow::Cow<'static, str> {
635 std::borrow::Cow::Borrowed("StatusBarElement")
636 }
637 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
638 schemars::json_schema!({
639 "type": "string",
640 "x-dual-list-options": [
641 {"value": "{filename}", "name": "Filename"},
642 {"value": "{cursor}", "name": "Cursor"},
643 {"value": "{cursor:compact}", "name": "Cursor (compact)"},
644 {"value": "{diagnostics}", "name": "Diagnostics"},
645 {"value": "{cursor_count}", "name": "Cursor Count"},
646 {"value": "{messages}", "name": "Messages"},
647 {"value": "{chord}", "name": "Chord"},
648 {"value": "{line_ending}", "name": "Line Ending"},
649 {"value": "{encoding}", "name": "Encoding"},
650 {"value": "{language}", "name": "Language"},
651 {"value": "{lsp}", "name": "LSP"},
652 {"value": "{warnings}", "name": "Warnings"},
653 {"value": "{update}", "name": "Update"},
654 {"value": "{palette}", "name": "Palette"},
655 {"value": "{clock}", "name": "Clock"}
656 ]
657 })
658 }
659}
660
661fn default_status_bar_left() -> Vec<StatusBarElement> {
662 vec![
663 StatusBarElement::Filename,
664 StatusBarElement::Cursor,
665 StatusBarElement::Diagnostics,
666 StatusBarElement::CursorCount,
667 StatusBarElement::Messages,
668 ]
669}
670
671fn default_status_bar_right() -> Vec<StatusBarElement> {
672 vec![
673 StatusBarElement::LineEnding,
674 StatusBarElement::Encoding,
675 StatusBarElement::Language,
676 StatusBarElement::Lsp,
677 StatusBarElement::Warnings,
678 StatusBarElement::Update,
679 StatusBarElement::Palette,
680 ]
681}
682
683#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
698pub struct StatusBarConfig {
699 #[serde(default = "default_status_bar_left")]
702 #[schemars(extend("x-section" = "Status Bar", "x-dual-list-sibling" = "/editor/status_bar/right"))]
703 pub left: Vec<StatusBarElement>,
704
705 #[serde(default = "default_status_bar_right")]
708 #[schemars(extend("x-section" = "Status Bar", "x-dual-list-sibling" = "/editor/status_bar/left"))]
709 pub right: Vec<StatusBarElement>,
710}
711
712impl Default for StatusBarConfig {
713 fn default() -> Self {
714 Self {
715 left: default_status_bar_left(),
716 right: default_status_bar_right(),
717 }
718 }
719}
720
721#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
723pub struct EditorConfig {
724 #[serde(default = "default_true")]
727 #[schemars(extend("x-section" = "Display"))]
728 pub line_numbers: bool,
729
730 #[serde(default = "default_false")]
732 #[schemars(extend("x-section" = "Display"))]
733 pub relative_line_numbers: bool,
734
735 #[serde(default = "default_true")]
737 #[schemars(extend("x-section" = "Display"))]
738 pub highlight_current_line: bool,
739
740 #[serde(default = "default_true")]
742 #[schemars(extend("x-section" = "Display"))]
743 pub line_wrap: bool,
744
745 #[serde(default = "default_true")]
747 #[schemars(extend("x-section" = "Display"))]
748 pub wrap_indent: bool,
749
750 #[serde(default)]
755 #[schemars(extend("x-section" = "Display"))]
756 pub wrap_column: Option<usize>,
757
758 #[serde(default = "default_page_width")]
762 #[schemars(extend("x-section" = "Display"))]
763 pub page_width: Option<usize>,
764
765 #[serde(default = "default_true")]
767 #[schemars(extend("x-section" = "Display"))]
768 pub syntax_highlighting: bool,
769
770 #[serde(default = "default_true")]
775 #[schemars(extend("x-section" = "Display"))]
776 pub show_menu_bar: bool,
777
778 #[serde(default = "default_true")]
783 #[schemars(extend("x-section" = "Display"))]
784 pub menu_bar_mnemonics: bool,
785
786 #[serde(default = "default_true")]
791 #[schemars(extend("x-section" = "Display"))]
792 pub show_tab_bar: bool,
793
794 #[serde(default = "default_true")]
799 #[schemars(extend("x-section" = "Display"))]
800 pub show_status_bar: bool,
801
802 #[serde(default)]
805 #[schemars(extend("x-section" = "Status Bar"))]
806 pub status_bar: StatusBarConfig,
807
808 #[serde(default = "default_true")]
814 #[schemars(extend("x-section" = "Display"))]
815 pub show_prompt_line: bool,
816
817 #[serde(default = "default_true")]
821 #[schemars(extend("x-section" = "Display"))]
822 pub show_vertical_scrollbar: bool,
823
824 #[serde(default = "default_false")]
829 #[schemars(extend("x-section" = "Display"))]
830 pub show_horizontal_scrollbar: bool,
831
832 #[serde(default = "default_true")]
836 #[schemars(extend("x-section" = "Display"))]
837 pub show_tilde: bool,
838
839 #[serde(default = "default_false")]
844 #[schemars(extend("x-section" = "Display"))]
845 pub use_terminal_bg: bool,
846
847 #[serde(default)]
851 #[schemars(extend("x-section" = "Display"))]
852 pub cursor_style: CursorStyle,
853
854 #[serde(default)]
859 #[schemars(extend("x-section" = "Display"))]
860 pub rulers: Vec<usize>,
861
862 #[serde(default = "default_true")]
868 #[schemars(extend("x-section" = "Whitespace"))]
869 pub whitespace_show: bool,
870
871 #[serde(default = "default_false")]
875 #[schemars(extend("x-section" = "Whitespace"))]
876 pub whitespace_spaces_leading: bool,
877
878 #[serde(default = "default_false")]
882 #[schemars(extend("x-section" = "Whitespace"))]
883 pub whitespace_spaces_inner: bool,
884
885 #[serde(default = "default_false")]
889 #[schemars(extend("x-section" = "Whitespace"))]
890 pub whitespace_spaces_trailing: bool,
891
892 #[serde(default = "default_true")]
896 #[schemars(extend("x-section" = "Whitespace"))]
897 pub whitespace_tabs_leading: bool,
898
899 #[serde(default = "default_true")]
903 #[schemars(extend("x-section" = "Whitespace"))]
904 pub whitespace_tabs_inner: bool,
905
906 #[serde(default = "default_true")]
910 #[schemars(extend("x-section" = "Whitespace"))]
911 pub whitespace_tabs_trailing: bool,
912
913 #[serde(default = "default_false")]
919 #[schemars(extend("x-section" = "Editing"))]
920 pub use_tabs: bool,
921
922 #[serde(default = "default_tab_size")]
924 #[schemars(extend("x-section" = "Editing"))]
925 pub tab_size: usize,
926
927 #[serde(default = "default_true")]
929 #[schemars(extend("x-section" = "Editing"))]
930 pub auto_indent: bool,
931
932 #[serde(default = "default_true")]
939 #[schemars(extend("x-section" = "Editing"))]
940 pub auto_close: bool,
941
942 #[serde(default = "default_true")]
947 #[schemars(extend("x-section" = "Editing"))]
948 pub auto_surround: bool,
949
950 #[serde(default = "default_scroll_offset")]
952 #[schemars(extend("x-section" = "Editing"))]
953 pub scroll_offset: usize,
954
955 #[serde(default)]
960 #[schemars(extend("x-section" = "Editing"))]
961 pub default_line_ending: LineEndingOption,
962
963 #[serde(default = "default_false")]
966 #[schemars(extend("x-section" = "Editing"))]
967 pub trim_trailing_whitespace_on_save: bool,
968
969 #[serde(default = "default_false")]
972 #[schemars(extend("x-section" = "Editing"))]
973 pub ensure_final_newline_on_save: bool,
974
975 #[serde(default = "default_true")]
979 #[schemars(extend("x-section" = "Bracket Matching"))]
980 pub highlight_matching_brackets: bool,
981
982 #[serde(default = "default_true")]
986 #[schemars(extend("x-section" = "Bracket Matching"))]
987 pub rainbow_brackets: bool,
988
989 #[serde(default = "default_false")]
996 #[schemars(extend("x-section" = "Completion"))]
997 pub completion_popup_auto_show: bool,
998
999 #[serde(default = "default_true")]
1005 #[schemars(extend("x-section" = "Completion"))]
1006 pub quick_suggestions: bool,
1007
1008 #[serde(default = "default_quick_suggestions_delay")]
1014 #[schemars(extend("x-section" = "Completion"))]
1015 pub quick_suggestions_delay_ms: u64,
1016
1017 #[serde(default = "default_true")]
1021 #[schemars(extend("x-section" = "Completion"))]
1022 pub suggest_on_trigger_characters: bool,
1023
1024 #[serde(default = "default_true")]
1027 #[schemars(extend("x-section" = "LSP"))]
1028 pub enable_inlay_hints: bool,
1029
1030 #[serde(default = "default_false")]
1034 #[schemars(extend("x-section" = "LSP"))]
1035 pub enable_semantic_tokens_full: bool,
1036
1037 #[serde(default = "default_false")]
1042 #[schemars(extend("x-section" = "Diagnostics"))]
1043 pub diagnostics_inline_text: bool,
1044
1045 #[serde(default = "default_mouse_hover_enabled")]
1056 #[schemars(extend("x-section" = "Mouse"))]
1057 pub mouse_hover_enabled: bool,
1058
1059 #[serde(default = "default_mouse_hover_delay")]
1063 #[schemars(extend("x-section" = "Mouse"))]
1064 pub mouse_hover_delay_ms: u64,
1065
1066 #[serde(default = "default_double_click_time")]
1070 #[schemars(extend("x-section" = "Mouse"))]
1071 pub double_click_time_ms: u64,
1072
1073 #[serde(default = "default_false")]
1078 #[schemars(extend("x-section" = "Recovery"))]
1079 pub auto_save_enabled: bool,
1080
1081 #[serde(default = "default_auto_save_interval")]
1086 #[schemars(extend("x-section" = "Recovery"))]
1087 pub auto_save_interval_secs: u32,
1088
1089 #[serde(default = "default_true", alias = "persist_unnamed_buffers")]
1096 #[schemars(extend("x-section" = "Recovery"))]
1097 pub hot_exit: bool,
1098
1099 #[serde(default = "default_true")]
1104 #[schemars(extend("x-section" = "Recovery"))]
1105 pub recovery_enabled: bool,
1106
1107 #[serde(default = "default_auto_recovery_save_interval")]
1112 #[schemars(extend("x-section" = "Recovery"))]
1113 pub auto_recovery_save_interval_secs: u32,
1114
1115 #[serde(default = "default_auto_revert_poll_interval")]
1120 #[schemars(extend("x-section" = "Recovery"))]
1121 pub auto_revert_poll_interval_ms: u64,
1122
1123 #[serde(default = "default_true")]
1129 #[schemars(extend("x-section" = "Keyboard"))]
1130 pub keyboard_disambiguate_escape_codes: bool,
1131
1132 #[serde(default = "default_false")]
1137 #[schemars(extend("x-section" = "Keyboard"))]
1138 pub keyboard_report_event_types: bool,
1139
1140 #[serde(default = "default_true")]
1145 #[schemars(extend("x-section" = "Keyboard"))]
1146 pub keyboard_report_alternate_keys: bool,
1147
1148 #[serde(default = "default_false")]
1154 #[schemars(extend("x-section" = "Keyboard"))]
1155 pub keyboard_report_all_keys_as_escape_codes: bool,
1156
1157 #[serde(default = "default_highlight_timeout")]
1160 #[schemars(extend("x-section" = "Performance"))]
1161 pub highlight_timeout_ms: u64,
1162
1163 #[serde(default = "default_snapshot_interval")]
1165 #[schemars(extend("x-section" = "Performance"))]
1166 pub snapshot_interval: usize,
1167
1168 #[serde(default = "default_highlight_context_bytes")]
1173 #[schemars(extend("x-section" = "Performance"))]
1174 pub highlight_context_bytes: usize,
1175
1176 #[serde(default = "default_large_file_threshold")]
1183 #[schemars(extend("x-section" = "Performance"))]
1184 pub large_file_threshold_bytes: u64,
1185
1186 #[serde(default = "default_estimated_line_length")]
1190 #[schemars(extend("x-section" = "Performance"))]
1191 pub estimated_line_length: usize,
1192
1193 #[serde(default = "default_read_concurrency")]
1198 #[schemars(extend("x-section" = "Performance"))]
1199 pub read_concurrency: usize,
1200
1201 #[serde(default = "default_file_tree_poll_interval")]
1206 #[schemars(extend("x-section" = "Performance"))]
1207 pub file_tree_poll_interval_ms: u64,
1208}
1209
1210fn default_tab_size() -> usize {
1211 4
1212}
1213
1214pub const LARGE_FILE_THRESHOLD_BYTES: u64 = 1024 * 1024; fn default_large_file_threshold() -> u64 {
1220 LARGE_FILE_THRESHOLD_BYTES
1221}
1222
1223pub const INDENT_FOLD_MAX_SCAN_LINES: usize = 10_000;
1226
1227pub const INDENT_FOLD_INDICATOR_MAX_SCAN: usize = 50;
1230
1231pub const INDENT_FOLD_MAX_UPWARD_SCAN: usize = 200;
1234
1235fn default_read_concurrency() -> usize {
1236 64
1237}
1238
1239fn default_true() -> bool {
1240 true
1241}
1242
1243fn default_false() -> bool {
1244 false
1245}
1246
1247fn default_quick_suggestions_delay() -> u64 {
1248 150 }
1250
1251fn default_scroll_offset() -> usize {
1252 3
1253}
1254
1255fn default_highlight_timeout() -> u64 {
1256 5
1257}
1258
1259fn default_snapshot_interval() -> usize {
1260 100
1261}
1262
1263fn default_estimated_line_length() -> usize {
1264 80
1265}
1266
1267fn default_auto_save_interval() -> u32 {
1268 30 }
1270
1271fn default_auto_recovery_save_interval() -> u32 {
1272 2 }
1274
1275fn default_highlight_context_bytes() -> usize {
1276 10_000 }
1278
1279fn default_mouse_hover_enabled() -> bool {
1280 !cfg!(windows)
1281}
1282
1283fn default_mouse_hover_delay() -> u64 {
1284 500 }
1286
1287fn default_double_click_time() -> u64 {
1288 500 }
1290
1291fn default_auto_revert_poll_interval() -> u64 {
1292 2000 }
1294
1295fn default_file_tree_poll_interval() -> u64 {
1296 3000 }
1298
1299impl Default for EditorConfig {
1300 fn default() -> Self {
1301 Self {
1302 use_tabs: false,
1303 tab_size: default_tab_size(),
1304 auto_indent: true,
1305 auto_close: true,
1306 auto_surround: true,
1307 line_numbers: true,
1308 relative_line_numbers: false,
1309 scroll_offset: default_scroll_offset(),
1310 syntax_highlighting: true,
1311 highlight_current_line: true,
1312 line_wrap: true,
1313 wrap_indent: true,
1314 wrap_column: None,
1315 page_width: default_page_width(),
1316 highlight_timeout_ms: default_highlight_timeout(),
1317 snapshot_interval: default_snapshot_interval(),
1318 large_file_threshold_bytes: default_large_file_threshold(),
1319 estimated_line_length: default_estimated_line_length(),
1320 enable_inlay_hints: true,
1321 enable_semantic_tokens_full: false,
1322 diagnostics_inline_text: false,
1323 auto_save_enabled: false,
1324 auto_save_interval_secs: default_auto_save_interval(),
1325 hot_exit: true,
1326 recovery_enabled: true,
1327 auto_recovery_save_interval_secs: default_auto_recovery_save_interval(),
1328 highlight_context_bytes: default_highlight_context_bytes(),
1329 mouse_hover_enabled: default_mouse_hover_enabled(),
1330 mouse_hover_delay_ms: default_mouse_hover_delay(),
1331 double_click_time_ms: default_double_click_time(),
1332 auto_revert_poll_interval_ms: default_auto_revert_poll_interval(),
1333 read_concurrency: default_read_concurrency(),
1334 file_tree_poll_interval_ms: default_file_tree_poll_interval(),
1335 default_line_ending: LineEndingOption::default(),
1336 trim_trailing_whitespace_on_save: false,
1337 ensure_final_newline_on_save: false,
1338 highlight_matching_brackets: true,
1339 rainbow_brackets: true,
1340 cursor_style: CursorStyle::default(),
1341 keyboard_disambiguate_escape_codes: true,
1342 keyboard_report_event_types: false,
1343 keyboard_report_alternate_keys: true,
1344 keyboard_report_all_keys_as_escape_codes: false,
1345 completion_popup_auto_show: false,
1346 quick_suggestions: true,
1347 quick_suggestions_delay_ms: default_quick_suggestions_delay(),
1348 suggest_on_trigger_characters: true,
1349 show_menu_bar: true,
1350 menu_bar_mnemonics: true,
1351 show_tab_bar: true,
1352 show_status_bar: true,
1353 status_bar: StatusBarConfig::default(),
1354 show_prompt_line: true,
1355 show_vertical_scrollbar: true,
1356 show_horizontal_scrollbar: false,
1357 show_tilde: true,
1358 use_terminal_bg: false,
1359 rulers: Vec::new(),
1360 whitespace_show: true,
1361 whitespace_spaces_leading: false,
1362 whitespace_spaces_inner: false,
1363 whitespace_spaces_trailing: false,
1364 whitespace_tabs_leading: true,
1365 whitespace_tabs_inner: true,
1366 whitespace_tabs_trailing: true,
1367 }
1368 }
1369}
1370
1371#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1373pub struct FileExplorerConfig {
1374 #[serde(default = "default_true")]
1376 pub respect_gitignore: bool,
1377
1378 #[serde(default = "default_false")]
1380 pub show_hidden: bool,
1381
1382 #[serde(default = "default_false")]
1384 pub show_gitignored: bool,
1385
1386 #[serde(default)]
1388 pub custom_ignore_patterns: Vec<String>,
1389
1390 #[serde(default = "default_explorer_width")]
1392 pub width: f32,
1393}
1394
1395fn default_explorer_width() -> f32 {
1396 0.3 }
1398
1399#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1410pub struct ClipboardConfig {
1411 #[serde(default = "default_true")]
1414 pub use_osc52: bool,
1415
1416 #[serde(default = "default_true")]
1419 pub use_system_clipboard: bool,
1420}
1421
1422impl Default for ClipboardConfig {
1423 fn default() -> Self {
1424 Self {
1425 use_osc52: true,
1426 use_system_clipboard: true,
1427 }
1428 }
1429}
1430
1431#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1433pub struct TerminalConfig {
1434 #[serde(default = "default_true")]
1437 pub jump_to_end_on_output: bool,
1438}
1439
1440impl Default for TerminalConfig {
1441 fn default() -> Self {
1442 Self {
1443 jump_to_end_on_output: true,
1444 }
1445 }
1446}
1447
1448#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1450pub struct WarningsConfig {
1451 #[serde(default = "default_true")]
1454 pub show_status_indicator: bool,
1455}
1456
1457impl Default for WarningsConfig {
1458 fn default() -> Self {
1459 Self {
1460 show_status_indicator: true,
1461 }
1462 }
1463}
1464
1465#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1467pub struct PackagesConfig {
1468 #[serde(default = "default_package_sources")]
1471 pub sources: Vec<String>,
1472}
1473
1474fn default_package_sources() -> Vec<String> {
1475 vec!["https://github.com/sinelaw/fresh-plugins-registry".to_string()]
1476}
1477
1478impl Default for PackagesConfig {
1479 fn default() -> Self {
1480 Self {
1481 sources: default_package_sources(),
1482 }
1483 }
1484}
1485
1486pub use fresh_core::config::PluginConfig;
1488
1489impl Default for FileExplorerConfig {
1490 fn default() -> Self {
1491 Self {
1492 respect_gitignore: true,
1493 show_hidden: false,
1494 show_gitignored: false,
1495 custom_ignore_patterns: Vec::new(),
1496 width: default_explorer_width(),
1497 }
1498 }
1499}
1500
1501#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
1503pub struct FileBrowserConfig {
1504 #[serde(default = "default_false")]
1506 pub show_hidden: bool,
1507}
1508
1509#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1511pub struct KeyPress {
1512 pub key: String,
1514 #[serde(default)]
1516 pub modifiers: Vec<String>,
1517}
1518
1519#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1521#[schemars(extend("x-display-field" = "/action"))]
1522pub struct Keybinding {
1523 #[serde(default, skip_serializing_if = "String::is_empty")]
1525 pub key: String,
1526
1527 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1529 pub modifiers: Vec<String>,
1530
1531 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1534 pub keys: Vec<KeyPress>,
1535
1536 pub action: String,
1538
1539 #[serde(default)]
1541 pub args: HashMap<String, serde_json::Value>,
1542
1543 #[serde(default)]
1545 pub when: Option<String>,
1546}
1547
1548#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1550#[schemars(extend("x-display-field" = "/inherits"))]
1551pub struct KeymapConfig {
1552 #[serde(default, skip_serializing_if = "Option::is_none")]
1554 pub inherits: Option<String>,
1555
1556 #[serde(default)]
1558 pub bindings: Vec<Keybinding>,
1559}
1560
1561#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1563#[schemars(extend("x-display-field" = "/command"))]
1564pub struct FormatterConfig {
1565 pub command: String,
1567
1568 #[serde(default)]
1571 pub args: Vec<String>,
1572
1573 #[serde(default = "default_true")]
1576 pub stdin: bool,
1577
1578 #[serde(default = "default_on_save_timeout")]
1580 pub timeout_ms: u64,
1581}
1582
1583#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1585#[schemars(extend("x-display-field" = "/command"))]
1586pub struct OnSaveAction {
1587 pub command: String,
1590
1591 #[serde(default)]
1594 pub args: Vec<String>,
1595
1596 #[serde(default)]
1598 pub working_dir: Option<String>,
1599
1600 #[serde(default)]
1602 pub stdin: bool,
1603
1604 #[serde(default = "default_on_save_timeout")]
1606 pub timeout_ms: u64,
1607
1608 #[serde(default = "default_true")]
1611 pub enabled: bool,
1612}
1613
1614fn default_on_save_timeout() -> u64 {
1615 10000
1616}
1617
1618fn default_page_width() -> Option<usize> {
1619 Some(80)
1620}
1621
1622#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1624#[schemars(extend("x-display-field" = "/grammar"))]
1625pub struct LanguageConfig {
1626 #[serde(default)]
1628 pub extensions: Vec<String>,
1629
1630 #[serde(default)]
1632 pub filenames: Vec<String>,
1633
1634 #[serde(default)]
1636 pub grammar: String,
1637
1638 #[serde(default)]
1640 pub comment_prefix: Option<String>,
1641
1642 #[serde(default = "default_true")]
1644 pub auto_indent: bool,
1645
1646 #[serde(default)]
1649 pub auto_close: Option<bool>,
1650
1651 #[serde(default)]
1654 pub auto_surround: Option<bool>,
1655
1656 #[serde(default)]
1659 pub textmate_grammar: Option<std::path::PathBuf>,
1660
1661 #[serde(default = "default_true")]
1664 pub show_whitespace_tabs: bool,
1665
1666 #[serde(default)]
1671 pub line_wrap: Option<bool>,
1672
1673 #[serde(default)]
1676 pub wrap_column: Option<usize>,
1677
1678 #[serde(default)]
1683 pub page_view: Option<bool>,
1684
1685 #[serde(default)]
1689 pub page_width: Option<usize>,
1690
1691 #[serde(default)]
1695 pub use_tabs: Option<bool>,
1696
1697 #[serde(default)]
1700 pub tab_size: Option<usize>,
1701
1702 #[serde(default)]
1704 pub formatter: Option<FormatterConfig>,
1705
1706 #[serde(default)]
1708 pub format_on_save: bool,
1709
1710 #[serde(default)]
1714 pub on_save: Vec<OnSaveAction>,
1715
1716 #[serde(default)]
1726 pub word_characters: Option<String>,
1727}
1728
1729#[derive(Debug, Clone)]
1736pub struct BufferConfig {
1737 pub tab_size: usize,
1739
1740 pub use_tabs: bool,
1742
1743 pub auto_indent: bool,
1745
1746 pub auto_close: bool,
1748
1749 pub auto_surround: bool,
1751
1752 pub line_wrap: bool,
1754
1755 pub wrap_column: Option<usize>,
1757
1758 pub whitespace: WhitespaceVisibility,
1760
1761 pub formatter: Option<FormatterConfig>,
1763
1764 pub format_on_save: bool,
1766
1767 pub on_save: Vec<OnSaveAction>,
1769
1770 pub textmate_grammar: Option<std::path::PathBuf>,
1772
1773 pub word_characters: String,
1776}
1777
1778impl BufferConfig {
1779 pub fn resolve(global_config: &Config, language_id: Option<&str>) -> Self {
1788 let editor = &global_config.editor;
1789
1790 let mut whitespace = WhitespaceVisibility::from_editor_config(editor);
1792 let mut config = BufferConfig {
1793 tab_size: editor.tab_size,
1794 use_tabs: editor.use_tabs,
1795 auto_indent: editor.auto_indent,
1796 auto_close: editor.auto_close,
1797 auto_surround: editor.auto_surround,
1798 line_wrap: editor.line_wrap,
1799 wrap_column: editor.wrap_column,
1800 whitespace,
1801 formatter: None,
1802 format_on_save: false,
1803 on_save: Vec::new(),
1804 textmate_grammar: None,
1805 word_characters: String::new(),
1806 };
1807
1808 let lang_config_ref = language_id
1812 .and_then(|id| global_config.languages.get(id))
1813 .or_else(|| {
1814 match language_id {
1816 None | Some("text") => global_config
1817 .default_language
1818 .as_deref()
1819 .and_then(|lang| global_config.languages.get(lang)),
1820 _ => None,
1821 }
1822 });
1823 if let Some(lang_config) = lang_config_ref {
1824 if let Some(ts) = lang_config.tab_size {
1826 config.tab_size = ts;
1827 }
1828
1829 if let Some(use_tabs) = lang_config.use_tabs {
1831 config.use_tabs = use_tabs;
1832 }
1833
1834 if let Some(line_wrap) = lang_config.line_wrap {
1836 config.line_wrap = line_wrap;
1837 }
1838
1839 if lang_config.wrap_column.is_some() {
1841 config.wrap_column = lang_config.wrap_column;
1842 }
1843
1844 config.auto_indent = lang_config.auto_indent;
1846
1847 if config.auto_close {
1849 if let Some(lang_auto_close) = lang_config.auto_close {
1850 config.auto_close = lang_auto_close;
1851 }
1852 }
1853
1854 if config.auto_surround {
1856 if let Some(lang_auto_surround) = lang_config.auto_surround {
1857 config.auto_surround = lang_auto_surround;
1858 }
1859 }
1860
1861 whitespace = whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
1863 config.whitespace = whitespace;
1864
1865 config.formatter = lang_config.formatter.clone();
1867
1868 config.format_on_save = lang_config.format_on_save;
1870
1871 config.on_save = lang_config.on_save.clone();
1873
1874 config.textmate_grammar = lang_config.textmate_grammar.clone();
1876
1877 if let Some(ref wc) = lang_config.word_characters {
1879 config.word_characters = wc.clone();
1880 }
1881 }
1882
1883 config
1884 }
1885
1886 pub fn indent_string(&self) -> String {
1891 if self.use_tabs {
1892 "\t".to_string()
1893 } else {
1894 " ".repeat(self.tab_size)
1895 }
1896 }
1897}
1898
1899#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
1901pub struct MenuConfig {
1902 #[serde(default)]
1904 pub menus: Vec<Menu>,
1905}
1906
1907pub use fresh_core::menu::{Menu, MenuItem};
1909
1910pub trait MenuExt {
1912 fn match_id(&self) -> &str;
1915
1916 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path);
1919}
1920
1921impl MenuExt for Menu {
1922 fn match_id(&self) -> &str {
1923 self.id.as_deref().unwrap_or(&self.label)
1924 }
1925
1926 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path) {
1927 self.items = self
1928 .items
1929 .iter()
1930 .map(|item| item.expand_dynamic(themes_dir))
1931 .collect();
1932 }
1933}
1934
1935pub trait MenuItemExt {
1937 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem;
1940}
1941
1942impl MenuItemExt for MenuItem {
1943 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem {
1944 match self {
1945 MenuItem::DynamicSubmenu { label, source } => {
1946 let items = generate_dynamic_items(source, themes_dir);
1947 MenuItem::Submenu {
1948 label: label.clone(),
1949 items,
1950 }
1951 }
1952 other => other.clone(),
1953 }
1954 }
1955}
1956
1957#[cfg(feature = "runtime")]
1959pub fn generate_dynamic_items(source: &str, themes_dir: &std::path::Path) -> Vec<MenuItem> {
1960 match source {
1961 "copy_with_theme" => {
1962 let loader = crate::view::theme::ThemeLoader::new(themes_dir.to_path_buf());
1964 let registry = loader.load_all(&[]);
1965 registry
1966 .list()
1967 .iter()
1968 .map(|info| {
1969 let mut args = HashMap::new();
1970 args.insert("theme".to_string(), serde_json::json!(info.key));
1971 MenuItem::Action {
1972 label: info.name.clone(),
1973 action: "copy_with_theme".to_string(),
1974 args,
1975 when: Some(context_keys::HAS_SELECTION.to_string()),
1976 checkbox: None,
1977 }
1978 })
1979 .collect()
1980 }
1981 _ => vec![MenuItem::Label {
1982 info: format!("Unknown source: {}", source),
1983 }],
1984 }
1985}
1986
1987#[cfg(not(feature = "runtime"))]
1989pub fn generate_dynamic_items(_source: &str, _themes_dir: &std::path::Path) -> Vec<MenuItem> {
1990 vec![]
1992}
1993
1994impl Default for Config {
1995 fn default() -> Self {
1996 Self {
1997 version: 0,
1998 theme: default_theme_name(),
1999 locale: LocaleName::default(),
2000 check_for_updates: true,
2001 editor: EditorConfig::default(),
2002 file_explorer: FileExplorerConfig::default(),
2003 file_browser: FileBrowserConfig::default(),
2004 clipboard: ClipboardConfig::default(),
2005 terminal: TerminalConfig::default(),
2006 keybindings: vec![], keybinding_maps: HashMap::new(), active_keybinding_map: default_keybinding_map_name(),
2009 languages: Self::default_languages(),
2010 default_language: None,
2011 lsp: Self::default_lsp_config(),
2012 universal_lsp: Self::default_universal_lsp_config(),
2013 warnings: WarningsConfig::default(),
2014 plugins: HashMap::new(), packages: PackagesConfig::default(),
2016 }
2017 }
2018}
2019
2020impl MenuConfig {
2021 pub fn translated() -> Self {
2023 Self {
2024 menus: Self::translated_menus(),
2025 }
2026 }
2027
2028 pub fn translated_menus() -> Vec<Menu> {
2034 vec![
2035 Menu {
2037 id: Some("File".to_string()),
2038 label: t!("menu.file").to_string(),
2039 when: None,
2040 items: vec![
2041 MenuItem::Action {
2042 label: t!("menu.file.new_file").to_string(),
2043 action: "new".to_string(),
2044 args: HashMap::new(),
2045 when: None,
2046 checkbox: None,
2047 },
2048 MenuItem::Action {
2049 label: t!("menu.file.open_file").to_string(),
2050 action: "open".to_string(),
2051 args: HashMap::new(),
2052 when: None,
2053 checkbox: None,
2054 },
2055 MenuItem::Separator { separator: true },
2056 MenuItem::Action {
2057 label: t!("menu.file.save").to_string(),
2058 action: "save".to_string(),
2059 args: HashMap::new(),
2060 when: None,
2061 checkbox: None,
2062 },
2063 MenuItem::Action {
2064 label: t!("menu.file.save_as").to_string(),
2065 action: "save_as".to_string(),
2066 args: HashMap::new(),
2067 when: None,
2068 checkbox: None,
2069 },
2070 MenuItem::Action {
2071 label: t!("menu.file.revert").to_string(),
2072 action: "revert".to_string(),
2073 args: HashMap::new(),
2074 when: None,
2075 checkbox: None,
2076 },
2077 MenuItem::Action {
2078 label: t!("menu.file.reload_with_encoding").to_string(),
2079 action: "reload_with_encoding".to_string(),
2080 args: HashMap::new(),
2081 when: None,
2082 checkbox: None,
2083 },
2084 MenuItem::Separator { separator: true },
2085 MenuItem::Action {
2086 label: t!("menu.file.close_buffer").to_string(),
2087 action: "close".to_string(),
2088 args: HashMap::new(),
2089 when: None,
2090 checkbox: None,
2091 },
2092 MenuItem::Separator { separator: true },
2093 MenuItem::Action {
2094 label: t!("menu.file.switch_project").to_string(),
2095 action: "switch_project".to_string(),
2096 args: HashMap::new(),
2097 when: None,
2098 checkbox: None,
2099 },
2100 MenuItem::Separator { separator: true },
2101 MenuItem::Action {
2102 label: t!("menu.file.detach").to_string(),
2103 action: "detach".to_string(),
2104 args: HashMap::new(),
2105 when: Some(context_keys::SESSION_MODE.to_string()),
2106 checkbox: None,
2107 },
2108 MenuItem::Action {
2109 label: t!("menu.file.quit").to_string(),
2110 action: "quit".to_string(),
2111 args: HashMap::new(),
2112 when: None,
2113 checkbox: None,
2114 },
2115 ],
2116 },
2117 Menu {
2119 id: Some("Edit".to_string()),
2120 label: t!("menu.edit").to_string(),
2121 when: None,
2122 items: vec![
2123 MenuItem::Action {
2124 label: t!("menu.edit.undo").to_string(),
2125 action: "undo".to_string(),
2126 args: HashMap::new(),
2127 when: None,
2128 checkbox: None,
2129 },
2130 MenuItem::Action {
2131 label: t!("menu.edit.redo").to_string(),
2132 action: "redo".to_string(),
2133 args: HashMap::new(),
2134 when: None,
2135 checkbox: None,
2136 },
2137 MenuItem::Separator { separator: true },
2138 MenuItem::Action {
2139 label: t!("menu.edit.cut").to_string(),
2140 action: "cut".to_string(),
2141 args: HashMap::new(),
2142 when: Some(context_keys::HAS_SELECTION.to_string()),
2143 checkbox: None,
2144 },
2145 MenuItem::Action {
2146 label: t!("menu.edit.copy").to_string(),
2147 action: "copy".to_string(),
2148 args: HashMap::new(),
2149 when: Some(context_keys::HAS_SELECTION.to_string()),
2150 checkbox: None,
2151 },
2152 MenuItem::DynamicSubmenu {
2153 label: t!("menu.edit.copy_with_formatting").to_string(),
2154 source: "copy_with_theme".to_string(),
2155 },
2156 MenuItem::Action {
2157 label: t!("menu.edit.paste").to_string(),
2158 action: "paste".to_string(),
2159 args: HashMap::new(),
2160 when: None,
2161 checkbox: None,
2162 },
2163 MenuItem::Separator { separator: true },
2164 MenuItem::Action {
2165 label: t!("menu.edit.select_all").to_string(),
2166 action: "select_all".to_string(),
2167 args: HashMap::new(),
2168 when: None,
2169 checkbox: None,
2170 },
2171 MenuItem::Separator { separator: true },
2172 MenuItem::Action {
2173 label: t!("menu.edit.find").to_string(),
2174 action: "search".to_string(),
2175 args: HashMap::new(),
2176 when: None,
2177 checkbox: None,
2178 },
2179 MenuItem::Action {
2180 label: t!("menu.edit.find_in_selection").to_string(),
2181 action: "find_in_selection".to_string(),
2182 args: HashMap::new(),
2183 when: Some(context_keys::HAS_SELECTION.to_string()),
2184 checkbox: None,
2185 },
2186 MenuItem::Action {
2187 label: t!("menu.edit.find_next").to_string(),
2188 action: "find_next".to_string(),
2189 args: HashMap::new(),
2190 when: None,
2191 checkbox: None,
2192 },
2193 MenuItem::Action {
2194 label: t!("menu.edit.find_previous").to_string(),
2195 action: "find_previous".to_string(),
2196 args: HashMap::new(),
2197 when: None,
2198 checkbox: None,
2199 },
2200 MenuItem::Action {
2201 label: t!("menu.edit.replace").to_string(),
2202 action: "query_replace".to_string(),
2203 args: HashMap::new(),
2204 when: None,
2205 checkbox: None,
2206 },
2207 MenuItem::Separator { separator: true },
2208 MenuItem::Action {
2209 label: t!("menu.edit.delete_line").to_string(),
2210 action: "delete_line".to_string(),
2211 args: HashMap::new(),
2212 when: None,
2213 checkbox: None,
2214 },
2215 MenuItem::Action {
2216 label: t!("menu.edit.format_buffer").to_string(),
2217 action: "format_buffer".to_string(),
2218 args: HashMap::new(),
2219 when: Some(context_keys::FORMATTER_AVAILABLE.to_string()),
2220 checkbox: None,
2221 },
2222 MenuItem::Separator { separator: true },
2223 MenuItem::Action {
2224 label: t!("menu.edit.settings").to_string(),
2225 action: "open_settings".to_string(),
2226 args: HashMap::new(),
2227 when: None,
2228 checkbox: None,
2229 },
2230 MenuItem::Action {
2231 label: t!("menu.edit.keybinding_editor").to_string(),
2232 action: "open_keybinding_editor".to_string(),
2233 args: HashMap::new(),
2234 when: None,
2235 checkbox: None,
2236 },
2237 ],
2238 },
2239 Menu {
2241 id: Some("View".to_string()),
2242 label: t!("menu.view").to_string(),
2243 when: None,
2244 items: vec![
2245 MenuItem::Action {
2246 label: t!("menu.view.file_explorer").to_string(),
2247 action: "toggle_file_explorer".to_string(),
2248 args: HashMap::new(),
2249 when: None,
2250 checkbox: Some(context_keys::FILE_EXPLORER.to_string()),
2251 },
2252 MenuItem::Separator { separator: true },
2253 MenuItem::Action {
2254 label: t!("menu.view.line_numbers").to_string(),
2255 action: "toggle_line_numbers".to_string(),
2256 args: HashMap::new(),
2257 when: None,
2258 checkbox: Some(context_keys::LINE_NUMBERS.to_string()),
2259 },
2260 MenuItem::Action {
2261 label: t!("menu.view.line_wrap").to_string(),
2262 action: "toggle_line_wrap".to_string(),
2263 args: HashMap::new(),
2264 when: None,
2265 checkbox: Some(context_keys::LINE_WRAP.to_string()),
2266 },
2267 MenuItem::Action {
2268 label: t!("menu.view.mouse_support").to_string(),
2269 action: "toggle_mouse_capture".to_string(),
2270 args: HashMap::new(),
2271 when: None,
2272 checkbox: Some(context_keys::MOUSE_CAPTURE.to_string()),
2273 },
2274 MenuItem::Separator { separator: true },
2275 MenuItem::Action {
2276 label: t!("menu.view.vertical_scrollbar").to_string(),
2277 action: "toggle_vertical_scrollbar".to_string(),
2278 args: HashMap::new(),
2279 when: None,
2280 checkbox: Some(context_keys::VERTICAL_SCROLLBAR.to_string()),
2281 },
2282 MenuItem::Action {
2283 label: t!("menu.view.horizontal_scrollbar").to_string(),
2284 action: "toggle_horizontal_scrollbar".to_string(),
2285 args: HashMap::new(),
2286 when: None,
2287 checkbox: Some(context_keys::HORIZONTAL_SCROLLBAR.to_string()),
2288 },
2289 MenuItem::Separator { separator: true },
2290 MenuItem::Action {
2291 label: t!("menu.view.set_background").to_string(),
2292 action: "set_background".to_string(),
2293 args: HashMap::new(),
2294 when: None,
2295 checkbox: None,
2296 },
2297 MenuItem::Action {
2298 label: t!("menu.view.set_background_blend").to_string(),
2299 action: "set_background_blend".to_string(),
2300 args: HashMap::new(),
2301 when: None,
2302 checkbox: None,
2303 },
2304 MenuItem::Action {
2305 label: t!("menu.view.set_page_width").to_string(),
2306 action: "set_page_width".to_string(),
2307 args: HashMap::new(),
2308 when: None,
2309 checkbox: None,
2310 },
2311 MenuItem::Separator { separator: true },
2312 MenuItem::Action {
2313 label: t!("menu.view.select_theme").to_string(),
2314 action: "select_theme".to_string(),
2315 args: HashMap::new(),
2316 when: None,
2317 checkbox: None,
2318 },
2319 MenuItem::Action {
2320 label: t!("menu.view.select_locale").to_string(),
2321 action: "select_locale".to_string(),
2322 args: HashMap::new(),
2323 when: None,
2324 checkbox: None,
2325 },
2326 MenuItem::Action {
2327 label: t!("menu.view.settings").to_string(),
2328 action: "open_settings".to_string(),
2329 args: HashMap::new(),
2330 when: None,
2331 checkbox: None,
2332 },
2333 MenuItem::Action {
2334 label: t!("menu.view.calibrate_input").to_string(),
2335 action: "calibrate_input".to_string(),
2336 args: HashMap::new(),
2337 when: None,
2338 checkbox: None,
2339 },
2340 MenuItem::Separator { separator: true },
2341 MenuItem::Action {
2342 label: t!("menu.view.split_horizontal").to_string(),
2343 action: "split_horizontal".to_string(),
2344 args: HashMap::new(),
2345 when: None,
2346 checkbox: None,
2347 },
2348 MenuItem::Action {
2349 label: t!("menu.view.split_vertical").to_string(),
2350 action: "split_vertical".to_string(),
2351 args: HashMap::new(),
2352 when: None,
2353 checkbox: None,
2354 },
2355 MenuItem::Action {
2356 label: t!("menu.view.close_split").to_string(),
2357 action: "close_split".to_string(),
2358 args: HashMap::new(),
2359 when: None,
2360 checkbox: None,
2361 },
2362 MenuItem::Action {
2363 label: t!("menu.view.scroll_sync").to_string(),
2364 action: "toggle_scroll_sync".to_string(),
2365 args: HashMap::new(),
2366 when: Some(context_keys::HAS_SAME_BUFFER_SPLITS.to_string()),
2367 checkbox: Some(context_keys::SCROLL_SYNC.to_string()),
2368 },
2369 MenuItem::Action {
2370 label: t!("menu.view.focus_next_split").to_string(),
2371 action: "next_split".to_string(),
2372 args: HashMap::new(),
2373 when: None,
2374 checkbox: None,
2375 },
2376 MenuItem::Action {
2377 label: t!("menu.view.focus_prev_split").to_string(),
2378 action: "prev_split".to_string(),
2379 args: HashMap::new(),
2380 when: None,
2381 checkbox: None,
2382 },
2383 MenuItem::Action {
2384 label: t!("menu.view.toggle_maximize_split").to_string(),
2385 action: "toggle_maximize_split".to_string(),
2386 args: HashMap::new(),
2387 when: None,
2388 checkbox: None,
2389 },
2390 MenuItem::Separator { separator: true },
2391 MenuItem::Submenu {
2392 label: t!("menu.terminal").to_string(),
2393 items: vec![
2394 MenuItem::Action {
2395 label: t!("menu.terminal.open").to_string(),
2396 action: "open_terminal".to_string(),
2397 args: HashMap::new(),
2398 when: None,
2399 checkbox: None,
2400 },
2401 MenuItem::Action {
2402 label: t!("menu.terminal.close").to_string(),
2403 action: "close_terminal".to_string(),
2404 args: HashMap::new(),
2405 when: None,
2406 checkbox: None,
2407 },
2408 MenuItem::Separator { separator: true },
2409 MenuItem::Action {
2410 label: t!("menu.terminal.toggle_keyboard_capture").to_string(),
2411 action: "toggle_keyboard_capture".to_string(),
2412 args: HashMap::new(),
2413 when: None,
2414 checkbox: None,
2415 },
2416 ],
2417 },
2418 MenuItem::Separator { separator: true },
2419 MenuItem::Submenu {
2420 label: t!("menu.view.keybinding_style").to_string(),
2421 items: vec![
2422 MenuItem::Action {
2423 label: t!("menu.view.keybinding_default").to_string(),
2424 action: "switch_keybinding_map".to_string(),
2425 args: {
2426 let mut map = HashMap::new();
2427 map.insert("map".to_string(), serde_json::json!("default"));
2428 map
2429 },
2430 when: None,
2431 checkbox: Some(context_keys::KEYMAP_DEFAULT.to_string()),
2432 },
2433 MenuItem::Action {
2434 label: t!("menu.view.keybinding_emacs").to_string(),
2435 action: "switch_keybinding_map".to_string(),
2436 args: {
2437 let mut map = HashMap::new();
2438 map.insert("map".to_string(), serde_json::json!("emacs"));
2439 map
2440 },
2441 when: None,
2442 checkbox: Some(context_keys::KEYMAP_EMACS.to_string()),
2443 },
2444 MenuItem::Action {
2445 label: t!("menu.view.keybinding_vscode").to_string(),
2446 action: "switch_keybinding_map".to_string(),
2447 args: {
2448 let mut map = HashMap::new();
2449 map.insert("map".to_string(), serde_json::json!("vscode"));
2450 map
2451 },
2452 when: None,
2453 checkbox: Some(context_keys::KEYMAP_VSCODE.to_string()),
2454 },
2455 MenuItem::Action {
2456 label: "macOS GUI (⌘)".to_string(),
2457 action: "switch_keybinding_map".to_string(),
2458 args: {
2459 let mut map = HashMap::new();
2460 map.insert("map".to_string(), serde_json::json!("macos-gui"));
2461 map
2462 },
2463 when: None,
2464 checkbox: Some(context_keys::KEYMAP_MACOS_GUI.to_string()),
2465 },
2466 ],
2467 },
2468 ],
2469 },
2470 Menu {
2472 id: Some("Selection".to_string()),
2473 label: t!("menu.selection").to_string(),
2474 when: None,
2475 items: vec![
2476 MenuItem::Action {
2477 label: t!("menu.selection.select_all").to_string(),
2478 action: "select_all".to_string(),
2479 args: HashMap::new(),
2480 when: None,
2481 checkbox: None,
2482 },
2483 MenuItem::Action {
2484 label: t!("menu.selection.select_word").to_string(),
2485 action: "select_word".to_string(),
2486 args: HashMap::new(),
2487 when: None,
2488 checkbox: None,
2489 },
2490 MenuItem::Action {
2491 label: t!("menu.selection.select_line").to_string(),
2492 action: "select_line".to_string(),
2493 args: HashMap::new(),
2494 when: None,
2495 checkbox: None,
2496 },
2497 MenuItem::Action {
2498 label: t!("menu.selection.expand_selection").to_string(),
2499 action: "expand_selection".to_string(),
2500 args: HashMap::new(),
2501 when: None,
2502 checkbox: None,
2503 },
2504 MenuItem::Separator { separator: true },
2505 MenuItem::Action {
2506 label: t!("menu.selection.add_cursor_above").to_string(),
2507 action: "add_cursor_above".to_string(),
2508 args: HashMap::new(),
2509 when: None,
2510 checkbox: None,
2511 },
2512 MenuItem::Action {
2513 label: t!("menu.selection.add_cursor_below").to_string(),
2514 action: "add_cursor_below".to_string(),
2515 args: HashMap::new(),
2516 when: None,
2517 checkbox: None,
2518 },
2519 MenuItem::Action {
2520 label: t!("menu.selection.add_cursor_next_match").to_string(),
2521 action: "add_cursor_next_match".to_string(),
2522 args: HashMap::new(),
2523 when: None,
2524 checkbox: None,
2525 },
2526 MenuItem::Action {
2527 label: t!("menu.selection.remove_secondary_cursors").to_string(),
2528 action: "remove_secondary_cursors".to_string(),
2529 args: HashMap::new(),
2530 when: None,
2531 checkbox: None,
2532 },
2533 ],
2534 },
2535 Menu {
2537 id: Some("Go".to_string()),
2538 label: t!("menu.go").to_string(),
2539 when: None,
2540 items: vec![
2541 MenuItem::Action {
2542 label: t!("menu.go.goto_line").to_string(),
2543 action: "goto_line".to_string(),
2544 args: HashMap::new(),
2545 when: None,
2546 checkbox: None,
2547 },
2548 MenuItem::Action {
2549 label: t!("menu.go.goto_definition").to_string(),
2550 action: "lsp_goto_definition".to_string(),
2551 args: HashMap::new(),
2552 when: None,
2553 checkbox: None,
2554 },
2555 MenuItem::Action {
2556 label: t!("menu.go.find_references").to_string(),
2557 action: "lsp_references".to_string(),
2558 args: HashMap::new(),
2559 when: None,
2560 checkbox: None,
2561 },
2562 MenuItem::Separator { separator: true },
2563 MenuItem::Action {
2564 label: t!("menu.go.next_buffer").to_string(),
2565 action: "next_buffer".to_string(),
2566 args: HashMap::new(),
2567 when: None,
2568 checkbox: None,
2569 },
2570 MenuItem::Action {
2571 label: t!("menu.go.prev_buffer").to_string(),
2572 action: "prev_buffer".to_string(),
2573 args: HashMap::new(),
2574 when: None,
2575 checkbox: None,
2576 },
2577 MenuItem::Separator { separator: true },
2578 MenuItem::Action {
2579 label: t!("menu.go.command_palette").to_string(),
2580 action: "command_palette".to_string(),
2581 args: HashMap::new(),
2582 when: None,
2583 checkbox: None,
2584 },
2585 ],
2586 },
2587 Menu {
2589 id: Some("LSP".to_string()),
2590 label: t!("menu.lsp").to_string(),
2591 when: None,
2592 items: vec![
2593 MenuItem::Action {
2594 label: t!("menu.lsp.show_hover").to_string(),
2595 action: "lsp_hover".to_string(),
2596 args: HashMap::new(),
2597 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2598 checkbox: None,
2599 },
2600 MenuItem::Action {
2601 label: t!("menu.lsp.goto_definition").to_string(),
2602 action: "lsp_goto_definition".to_string(),
2603 args: HashMap::new(),
2604 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2605 checkbox: None,
2606 },
2607 MenuItem::Action {
2608 label: t!("menu.lsp.find_references").to_string(),
2609 action: "lsp_references".to_string(),
2610 args: HashMap::new(),
2611 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2612 checkbox: None,
2613 },
2614 MenuItem::Action {
2615 label: t!("menu.lsp.rename_symbol").to_string(),
2616 action: "lsp_rename".to_string(),
2617 args: HashMap::new(),
2618 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2619 checkbox: None,
2620 },
2621 MenuItem::Separator { separator: true },
2622 MenuItem::Action {
2623 label: t!("menu.lsp.show_completions").to_string(),
2624 action: "lsp_completion".to_string(),
2625 args: HashMap::new(),
2626 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2627 checkbox: None,
2628 },
2629 MenuItem::Action {
2630 label: t!("menu.lsp.show_signature").to_string(),
2631 action: "lsp_signature_help".to_string(),
2632 args: HashMap::new(),
2633 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2634 checkbox: None,
2635 },
2636 MenuItem::Action {
2637 label: t!("menu.lsp.code_actions").to_string(),
2638 action: "lsp_code_actions".to_string(),
2639 args: HashMap::new(),
2640 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2641 checkbox: None,
2642 },
2643 MenuItem::Separator { separator: true },
2644 MenuItem::Action {
2645 label: t!("menu.lsp.toggle_inlay_hints").to_string(),
2646 action: "toggle_inlay_hints".to_string(),
2647 args: HashMap::new(),
2648 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2649 checkbox: Some(context_keys::INLAY_HINTS.to_string()),
2650 },
2651 MenuItem::Action {
2652 label: t!("menu.lsp.toggle_mouse_hover").to_string(),
2653 action: "toggle_mouse_hover".to_string(),
2654 args: HashMap::new(),
2655 when: None,
2656 checkbox: Some(context_keys::MOUSE_HOVER.to_string()),
2657 },
2658 MenuItem::Separator { separator: true },
2659 MenuItem::Action {
2660 label: t!("menu.lsp.restart_server").to_string(),
2661 action: "lsp_restart".to_string(),
2662 args: HashMap::new(),
2663 when: None,
2664 checkbox: None,
2665 },
2666 MenuItem::Action {
2667 label: t!("menu.lsp.stop_server").to_string(),
2668 action: "lsp_stop".to_string(),
2669 args: HashMap::new(),
2670 when: None,
2671 checkbox: None,
2672 },
2673 MenuItem::Separator { separator: true },
2674 MenuItem::Action {
2675 label: t!("menu.lsp.toggle_for_buffer").to_string(),
2676 action: "lsp_toggle_for_buffer".to_string(),
2677 args: HashMap::new(),
2678 when: None,
2679 checkbox: None,
2680 },
2681 ],
2682 },
2683 Menu {
2685 id: Some("Explorer".to_string()),
2686 label: t!("menu.explorer").to_string(),
2687 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
2688 items: vec![
2689 MenuItem::Action {
2690 label: t!("menu.explorer.new_file").to_string(),
2691 action: "file_explorer_new_file".to_string(),
2692 args: HashMap::new(),
2693 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
2694 checkbox: None,
2695 },
2696 MenuItem::Action {
2697 label: t!("menu.explorer.new_folder").to_string(),
2698 action: "file_explorer_new_directory".to_string(),
2699 args: HashMap::new(),
2700 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
2701 checkbox: None,
2702 },
2703 MenuItem::Separator { separator: true },
2704 MenuItem::Action {
2705 label: t!("menu.explorer.open").to_string(),
2706 action: "file_explorer_open".to_string(),
2707 args: HashMap::new(),
2708 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
2709 checkbox: None,
2710 },
2711 MenuItem::Action {
2712 label: t!("menu.explorer.rename").to_string(),
2713 action: "file_explorer_rename".to_string(),
2714 args: HashMap::new(),
2715 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
2716 checkbox: None,
2717 },
2718 MenuItem::Action {
2719 label: t!("menu.explorer.delete").to_string(),
2720 action: "file_explorer_delete".to_string(),
2721 args: HashMap::new(),
2722 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
2723 checkbox: None,
2724 },
2725 MenuItem::Separator { separator: true },
2726 MenuItem::Action {
2727 label: t!("menu.explorer.refresh").to_string(),
2728 action: "file_explorer_refresh".to_string(),
2729 args: HashMap::new(),
2730 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
2731 checkbox: None,
2732 },
2733 MenuItem::Separator { separator: true },
2734 MenuItem::Action {
2735 label: t!("menu.explorer.show_hidden").to_string(),
2736 action: "file_explorer_toggle_hidden".to_string(),
2737 args: HashMap::new(),
2738 when: Some(context_keys::FILE_EXPLORER.to_string()),
2739 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_HIDDEN.to_string()),
2740 },
2741 MenuItem::Action {
2742 label: t!("menu.explorer.show_gitignored").to_string(),
2743 action: "file_explorer_toggle_gitignored".to_string(),
2744 args: HashMap::new(),
2745 when: Some(context_keys::FILE_EXPLORER.to_string()),
2746 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_GITIGNORED.to_string()),
2747 },
2748 ],
2749 },
2750 Menu {
2752 id: Some("Help".to_string()),
2753 label: t!("menu.help").to_string(),
2754 when: None,
2755 items: vec![
2756 MenuItem::Label {
2757 info: format!("Fresh v{}", env!("CARGO_PKG_VERSION")),
2758 },
2759 MenuItem::Separator { separator: true },
2760 MenuItem::Action {
2761 label: t!("menu.help.show_manual").to_string(),
2762 action: "show_help".to_string(),
2763 args: HashMap::new(),
2764 when: None,
2765 checkbox: None,
2766 },
2767 MenuItem::Action {
2768 label: t!("menu.help.keyboard_shortcuts").to_string(),
2769 action: "keyboard_shortcuts".to_string(),
2770 args: HashMap::new(),
2771 when: None,
2772 checkbox: None,
2773 },
2774 MenuItem::Separator { separator: true },
2775 MenuItem::Action {
2776 label: t!("menu.help.event_debug").to_string(),
2777 action: "event_debug".to_string(),
2778 args: HashMap::new(),
2779 when: None,
2780 checkbox: None,
2781 },
2782 ],
2783 },
2784 ]
2785 }
2786}
2787
2788impl Config {
2789 pub(crate) const FILENAME: &'static str = "config.json";
2791
2792 pub(crate) fn local_config_path(working_dir: &Path) -> std::path::PathBuf {
2794 working_dir.join(Self::FILENAME)
2795 }
2796
2797 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
2803 let contents = std::fs::read_to_string(path.as_ref())
2804 .map_err(|e| ConfigError::IoError(e.to_string()))?;
2805
2806 let partial: crate::partial_config::PartialConfig =
2808 serde_json::from_str(&contents).map_err(|e| ConfigError::ParseError(e.to_string()))?;
2809
2810 Ok(partial.resolve())
2811 }
2812
2813 fn load_builtin_keymap(name: &str) -> Option<KeymapConfig> {
2815 let json_content = match name {
2816 "default" => include_str!("../keymaps/default.json"),
2817 "emacs" => include_str!("../keymaps/emacs.json"),
2818 "vscode" => include_str!("../keymaps/vscode.json"),
2819 "macos" => include_str!("../keymaps/macos.json"),
2820 "macos-gui" => include_str!("../keymaps/macos-gui.json"),
2821 _ => return None,
2822 };
2823
2824 match serde_json::from_str(json_content) {
2825 Ok(config) => Some(config),
2826 Err(e) => {
2827 eprintln!("Failed to parse builtin keymap '{}': {}", name, e);
2828 None
2829 }
2830 }
2831 }
2832
2833 pub fn resolve_keymap(&self, map_name: &str) -> Vec<Keybinding> {
2836 let mut visited = std::collections::HashSet::new();
2837 self.resolve_keymap_recursive(map_name, &mut visited)
2838 }
2839
2840 fn resolve_keymap_recursive(
2842 &self,
2843 map_name: &str,
2844 visited: &mut std::collections::HashSet<String>,
2845 ) -> Vec<Keybinding> {
2846 if visited.contains(map_name) {
2848 eprintln!(
2849 "Warning: Circular inheritance detected in keymap '{}'",
2850 map_name
2851 );
2852 return Vec::new();
2853 }
2854 visited.insert(map_name.to_string());
2855
2856 let keymap = self
2858 .keybinding_maps
2859 .get(map_name)
2860 .cloned()
2861 .or_else(|| Self::load_builtin_keymap(map_name));
2862
2863 let Some(keymap) = keymap else {
2864 return Vec::new();
2865 };
2866
2867 let mut all_bindings = if let Some(ref parent_name) = keymap.inherits {
2869 self.resolve_keymap_recursive(parent_name, visited)
2870 } else {
2871 Vec::new()
2872 };
2873
2874 all_bindings.extend(keymap.bindings);
2876
2877 all_bindings
2878 }
2879 fn default_languages() -> HashMap<String, LanguageConfig> {
2881 let mut languages = HashMap::new();
2882
2883 languages.insert(
2884 "rust".to_string(),
2885 LanguageConfig {
2886 extensions: vec!["rs".to_string()],
2887 filenames: vec![],
2888 grammar: "rust".to_string(),
2889 comment_prefix: Some("//".to_string()),
2890 auto_indent: true,
2891 auto_close: None,
2892 auto_surround: None,
2893 textmate_grammar: None,
2894 show_whitespace_tabs: true,
2895 line_wrap: None,
2896 wrap_column: None,
2897 page_view: None,
2898 page_width: None,
2899 use_tabs: None,
2900 tab_size: None,
2901 formatter: Some(FormatterConfig {
2902 command: "rustfmt".to_string(),
2903 args: vec!["--edition".to_string(), "2021".to_string()],
2904 stdin: true,
2905 timeout_ms: 10000,
2906 }),
2907 format_on_save: false,
2908 on_save: vec![],
2909 word_characters: None,
2910 },
2911 );
2912
2913 languages.insert(
2914 "javascript".to_string(),
2915 LanguageConfig {
2916 extensions: vec!["js".to_string(), "jsx".to_string(), "mjs".to_string()],
2917 filenames: vec![],
2918 grammar: "javascript".to_string(),
2919 comment_prefix: Some("//".to_string()),
2920 auto_indent: true,
2921 auto_close: None,
2922 auto_surround: None,
2923 textmate_grammar: None,
2924 show_whitespace_tabs: true,
2925 line_wrap: None,
2926 wrap_column: None,
2927 page_view: None,
2928 page_width: None,
2929 use_tabs: None,
2930 tab_size: None,
2931 formatter: Some(FormatterConfig {
2932 command: "prettier".to_string(),
2933 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
2934 stdin: true,
2935 timeout_ms: 10000,
2936 }),
2937 format_on_save: false,
2938 on_save: vec![],
2939 word_characters: None,
2940 },
2941 );
2942
2943 languages.insert(
2944 "typescript".to_string(),
2945 LanguageConfig {
2946 extensions: vec!["ts".to_string(), "tsx".to_string(), "mts".to_string()],
2947 filenames: vec![],
2948 grammar: "typescript".to_string(),
2949 comment_prefix: Some("//".to_string()),
2950 auto_indent: true,
2951 auto_close: None,
2952 auto_surround: None,
2953 textmate_grammar: None,
2954 show_whitespace_tabs: true,
2955 line_wrap: None,
2956 wrap_column: None,
2957 page_view: None,
2958 page_width: None,
2959 use_tabs: None,
2960 tab_size: None,
2961 formatter: Some(FormatterConfig {
2962 command: "prettier".to_string(),
2963 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
2964 stdin: true,
2965 timeout_ms: 10000,
2966 }),
2967 format_on_save: false,
2968 on_save: vec![],
2969 word_characters: None,
2970 },
2971 );
2972
2973 languages.insert(
2974 "python".to_string(),
2975 LanguageConfig {
2976 extensions: vec!["py".to_string(), "pyi".to_string()],
2977 filenames: vec![],
2978 grammar: "python".to_string(),
2979 comment_prefix: Some("#".to_string()),
2980 auto_indent: true,
2981 auto_close: None,
2982 auto_surround: None,
2983 textmate_grammar: None,
2984 show_whitespace_tabs: true,
2985 line_wrap: None,
2986 wrap_column: None,
2987 page_view: None,
2988 page_width: None,
2989 use_tabs: None,
2990 tab_size: None,
2991 formatter: Some(FormatterConfig {
2992 command: "ruff".to_string(),
2993 args: vec![
2994 "format".to_string(),
2995 "--stdin-filename".to_string(),
2996 "$FILE".to_string(),
2997 ],
2998 stdin: true,
2999 timeout_ms: 10000,
3000 }),
3001 format_on_save: false,
3002 on_save: vec![],
3003 word_characters: None,
3004 },
3005 );
3006
3007 languages.insert(
3008 "c".to_string(),
3009 LanguageConfig {
3010 extensions: vec!["c".to_string(), "h".to_string()],
3011 filenames: vec![],
3012 grammar: "c".to_string(),
3013 comment_prefix: Some("//".to_string()),
3014 auto_indent: true,
3015 auto_close: None,
3016 auto_surround: None,
3017 textmate_grammar: None,
3018 show_whitespace_tabs: true,
3019 line_wrap: None,
3020 wrap_column: None,
3021 page_view: None,
3022 page_width: None,
3023 use_tabs: None,
3024 tab_size: None,
3025 formatter: Some(FormatterConfig {
3026 command: "clang-format".to_string(),
3027 args: vec![],
3028 stdin: true,
3029 timeout_ms: 10000,
3030 }),
3031 format_on_save: false,
3032 on_save: vec![],
3033 word_characters: None,
3034 },
3035 );
3036
3037 languages.insert(
3038 "cpp".to_string(),
3039 LanguageConfig {
3040 extensions: vec![
3041 "cpp".to_string(),
3042 "cc".to_string(),
3043 "cxx".to_string(),
3044 "hpp".to_string(),
3045 "hh".to_string(),
3046 "hxx".to_string(),
3047 ],
3048 filenames: vec![],
3049 grammar: "cpp".to_string(),
3050 comment_prefix: Some("//".to_string()),
3051 auto_indent: true,
3052 auto_close: None,
3053 auto_surround: None,
3054 textmate_grammar: None,
3055 show_whitespace_tabs: true,
3056 line_wrap: None,
3057 wrap_column: None,
3058 page_view: None,
3059 page_width: None,
3060 use_tabs: None,
3061 tab_size: None,
3062 formatter: Some(FormatterConfig {
3063 command: "clang-format".to_string(),
3064 args: vec![],
3065 stdin: true,
3066 timeout_ms: 10000,
3067 }),
3068 format_on_save: false,
3069 on_save: vec![],
3070 word_characters: None,
3071 },
3072 );
3073
3074 languages.insert(
3075 "csharp".to_string(),
3076 LanguageConfig {
3077 extensions: vec!["cs".to_string()],
3078 filenames: vec![],
3079 grammar: "C#".to_string(),
3080 comment_prefix: Some("//".to_string()),
3081 auto_indent: true,
3082 auto_close: None,
3083 auto_surround: None,
3084 textmate_grammar: None,
3085 show_whitespace_tabs: true,
3086 line_wrap: None,
3087 wrap_column: None,
3088 page_view: None,
3089 page_width: None,
3090 use_tabs: None,
3091 tab_size: None,
3092 formatter: None,
3093 format_on_save: false,
3094 on_save: vec![],
3095 word_characters: None,
3096 },
3097 );
3098
3099 languages.insert(
3100 "bash".to_string(),
3101 LanguageConfig {
3102 extensions: vec!["sh".to_string(), "bash".to_string()],
3103 filenames: vec![
3104 ".bash_aliases".to_string(),
3105 ".bash_logout".to_string(),
3106 ".bash_profile".to_string(),
3107 ".bashrc".to_string(),
3108 ".env".to_string(),
3109 ".profile".to_string(),
3110 ".zlogin".to_string(),
3111 ".zlogout".to_string(),
3112 ".zprofile".to_string(),
3113 ".zshenv".to_string(),
3114 ".zshrc".to_string(),
3115 "PKGBUILD".to_string(),
3117 "APKBUILD".to_string(),
3118 ],
3119 grammar: "bash".to_string(),
3120 comment_prefix: Some("#".to_string()),
3121 auto_indent: true,
3122 auto_close: None,
3123 auto_surround: None,
3124 textmate_grammar: None,
3125 show_whitespace_tabs: true,
3126 line_wrap: None,
3127 wrap_column: None,
3128 page_view: None,
3129 page_width: None,
3130 use_tabs: None,
3131 tab_size: None,
3132 formatter: None,
3133 format_on_save: false,
3134 on_save: vec![],
3135 word_characters: None,
3136 },
3137 );
3138
3139 languages.insert(
3140 "makefile".to_string(),
3141 LanguageConfig {
3142 extensions: vec!["mk".to_string()],
3143 filenames: vec![
3144 "Makefile".to_string(),
3145 "makefile".to_string(),
3146 "GNUmakefile".to_string(),
3147 ],
3148 grammar: "Makefile".to_string(),
3149 comment_prefix: Some("#".to_string()),
3150 auto_indent: false,
3151 auto_close: None,
3152 auto_surround: None,
3153 textmate_grammar: None,
3154 show_whitespace_tabs: true,
3155 line_wrap: None,
3156 wrap_column: None,
3157 page_view: None,
3158 page_width: None,
3159 use_tabs: Some(true), tab_size: Some(8), formatter: None,
3162 format_on_save: false,
3163 on_save: vec![],
3164 word_characters: None,
3165 },
3166 );
3167
3168 languages.insert(
3169 "dockerfile".to_string(),
3170 LanguageConfig {
3171 extensions: vec!["dockerfile".to_string()],
3172 filenames: vec!["Dockerfile".to_string(), "Containerfile".to_string()],
3173 grammar: "dockerfile".to_string(),
3174 comment_prefix: Some("#".to_string()),
3175 auto_indent: true,
3176 auto_close: None,
3177 auto_surround: None,
3178 textmate_grammar: None,
3179 show_whitespace_tabs: true,
3180 line_wrap: None,
3181 wrap_column: None,
3182 page_view: None,
3183 page_width: None,
3184 use_tabs: None,
3185 tab_size: None,
3186 formatter: None,
3187 format_on_save: false,
3188 on_save: vec![],
3189 word_characters: None,
3190 },
3191 );
3192
3193 languages.insert(
3194 "json".to_string(),
3195 LanguageConfig {
3196 extensions: vec!["json".to_string(), "jsonc".to_string()],
3197 filenames: vec![],
3198 grammar: "json".to_string(),
3199 comment_prefix: None,
3200 auto_indent: true,
3201 auto_close: None,
3202 auto_surround: None,
3203 textmate_grammar: None,
3204 show_whitespace_tabs: true,
3205 line_wrap: None,
3206 wrap_column: None,
3207 page_view: None,
3208 page_width: None,
3209 use_tabs: None,
3210 tab_size: None,
3211 formatter: Some(FormatterConfig {
3212 command: "prettier".to_string(),
3213 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3214 stdin: true,
3215 timeout_ms: 10000,
3216 }),
3217 format_on_save: false,
3218 on_save: vec![],
3219 word_characters: None,
3220 },
3221 );
3222
3223 languages.insert(
3224 "toml".to_string(),
3225 LanguageConfig {
3226 extensions: vec!["toml".to_string()],
3227 filenames: vec!["Cargo.lock".to_string()],
3228 grammar: "toml".to_string(),
3229 comment_prefix: Some("#".to_string()),
3230 auto_indent: true,
3231 auto_close: None,
3232 auto_surround: None,
3233 textmate_grammar: None,
3234 show_whitespace_tabs: true,
3235 line_wrap: None,
3236 wrap_column: None,
3237 page_view: None,
3238 page_width: None,
3239 use_tabs: None,
3240 tab_size: None,
3241 formatter: None,
3242 format_on_save: false,
3243 on_save: vec![],
3244 word_characters: None,
3245 },
3246 );
3247
3248 languages.insert(
3249 "yaml".to_string(),
3250 LanguageConfig {
3251 extensions: vec!["yml".to_string(), "yaml".to_string()],
3252 filenames: vec![],
3253 grammar: "yaml".to_string(),
3254 comment_prefix: Some("#".to_string()),
3255 auto_indent: true,
3256 auto_close: None,
3257 auto_surround: None,
3258 textmate_grammar: None,
3259 show_whitespace_tabs: true,
3260 line_wrap: None,
3261 wrap_column: None,
3262 page_view: None,
3263 page_width: None,
3264 use_tabs: None,
3265 tab_size: None,
3266 formatter: Some(FormatterConfig {
3267 command: "prettier".to_string(),
3268 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3269 stdin: true,
3270 timeout_ms: 10000,
3271 }),
3272 format_on_save: false,
3273 on_save: vec![],
3274 word_characters: None,
3275 },
3276 );
3277
3278 languages.insert(
3279 "markdown".to_string(),
3280 LanguageConfig {
3281 extensions: vec!["md".to_string(), "markdown".to_string()],
3282 filenames: vec!["README".to_string()],
3283 grammar: "markdown".to_string(),
3284 comment_prefix: None,
3285 auto_indent: false,
3286 auto_close: None,
3287 auto_surround: None,
3288 textmate_grammar: None,
3289 show_whitespace_tabs: true,
3290 line_wrap: None,
3291 wrap_column: None,
3292 page_view: None,
3293 page_width: None,
3294 use_tabs: None,
3295 tab_size: None,
3296 formatter: None,
3297 format_on_save: false,
3298 on_save: vec![],
3299 word_characters: None,
3300 },
3301 );
3302
3303 languages.insert(
3305 "go".to_string(),
3306 LanguageConfig {
3307 extensions: vec!["go".to_string()],
3308 filenames: vec![],
3309 grammar: "go".to_string(),
3310 comment_prefix: Some("//".to_string()),
3311 auto_indent: true,
3312 auto_close: None,
3313 auto_surround: None,
3314 textmate_grammar: None,
3315 show_whitespace_tabs: false,
3316 line_wrap: None,
3317 wrap_column: None,
3318 page_view: None,
3319 page_width: None,
3320 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
3323 command: "gofmt".to_string(),
3324 args: vec![],
3325 stdin: true,
3326 timeout_ms: 10000,
3327 }),
3328 format_on_save: false,
3329 on_save: vec![],
3330 word_characters: None,
3331 },
3332 );
3333
3334 languages.insert(
3335 "odin".to_string(),
3336 LanguageConfig {
3337 extensions: vec!["odin".to_string()],
3338 filenames: vec![],
3339 grammar: "odin".to_string(),
3340 comment_prefix: Some("//".to_string()),
3341 auto_indent: true,
3342 auto_close: None,
3343 auto_surround: None,
3344 textmate_grammar: None,
3345 show_whitespace_tabs: false,
3346 line_wrap: None,
3347 wrap_column: None,
3348 page_view: None,
3349 page_width: None,
3350 use_tabs: Some(true),
3351 tab_size: Some(8),
3352 formatter: None,
3353 format_on_save: false,
3354 on_save: vec![],
3355 word_characters: None,
3356 },
3357 );
3358
3359 languages.insert(
3360 "zig".to_string(),
3361 LanguageConfig {
3362 extensions: vec!["zig".to_string(), "zon".to_string()],
3363 filenames: vec![],
3364 grammar: "zig".to_string(),
3365 comment_prefix: Some("//".to_string()),
3366 auto_indent: true,
3367 auto_close: None,
3368 auto_surround: None,
3369 textmate_grammar: None,
3370 show_whitespace_tabs: true,
3371 line_wrap: None,
3372 wrap_column: None,
3373 page_view: None,
3374 page_width: None,
3375 use_tabs: None,
3376 tab_size: None,
3377 formatter: None,
3378 format_on_save: false,
3379 on_save: vec![],
3380 word_characters: None,
3381 },
3382 );
3383
3384 languages.insert(
3385 "java".to_string(),
3386 LanguageConfig {
3387 extensions: vec!["java".to_string()],
3388 filenames: vec![],
3389 grammar: "java".to_string(),
3390 comment_prefix: Some("//".to_string()),
3391 auto_indent: true,
3392 auto_close: None,
3393 auto_surround: None,
3394 textmate_grammar: None,
3395 show_whitespace_tabs: true,
3396 line_wrap: None,
3397 wrap_column: None,
3398 page_view: None,
3399 page_width: None,
3400 use_tabs: None,
3401 tab_size: None,
3402 formatter: None,
3403 format_on_save: false,
3404 on_save: vec![],
3405 word_characters: None,
3406 },
3407 );
3408
3409 languages.insert(
3410 "latex".to_string(),
3411 LanguageConfig {
3412 extensions: vec![
3413 "tex".to_string(),
3414 "latex".to_string(),
3415 "ltx".to_string(),
3416 "sty".to_string(),
3417 "cls".to_string(),
3418 "bib".to_string(),
3419 ],
3420 filenames: vec![],
3421 grammar: "latex".to_string(),
3422 comment_prefix: Some("%".to_string()),
3423 auto_indent: true,
3424 auto_close: None,
3425 auto_surround: None,
3426 textmate_grammar: None,
3427 show_whitespace_tabs: true,
3428 line_wrap: None,
3429 wrap_column: None,
3430 page_view: None,
3431 page_width: None,
3432 use_tabs: None,
3433 tab_size: None,
3434 formatter: None,
3435 format_on_save: false,
3436 on_save: vec![],
3437 word_characters: None,
3438 },
3439 );
3440
3441 languages.insert(
3442 "templ".to_string(),
3443 LanguageConfig {
3444 extensions: vec!["templ".to_string()],
3445 filenames: vec![],
3446 grammar: "go".to_string(), comment_prefix: Some("//".to_string()),
3448 auto_indent: true,
3449 auto_close: None,
3450 auto_surround: None,
3451 textmate_grammar: None,
3452 show_whitespace_tabs: true,
3453 line_wrap: None,
3454 wrap_column: None,
3455 page_view: None,
3456 page_width: None,
3457 use_tabs: None,
3458 tab_size: None,
3459 formatter: None,
3460 format_on_save: false,
3461 on_save: vec![],
3462 word_characters: None,
3463 },
3464 );
3465
3466 languages.insert(
3468 "git-rebase".to_string(),
3469 LanguageConfig {
3470 extensions: vec![],
3471 filenames: vec!["git-rebase-todo".to_string()],
3472 grammar: "Git Rebase Todo".to_string(),
3473 comment_prefix: Some("#".to_string()),
3474 auto_indent: false,
3475 auto_close: None,
3476 auto_surround: None,
3477 textmate_grammar: None,
3478 show_whitespace_tabs: true,
3479 line_wrap: None,
3480 wrap_column: None,
3481 page_view: None,
3482 page_width: None,
3483 use_tabs: None,
3484 tab_size: None,
3485 formatter: None,
3486 format_on_save: false,
3487 on_save: vec![],
3488 word_characters: None,
3489 },
3490 );
3491
3492 languages.insert(
3493 "git-commit".to_string(),
3494 LanguageConfig {
3495 extensions: vec![],
3496 filenames: vec![
3497 "COMMIT_EDITMSG".to_string(),
3498 "MERGE_MSG".to_string(),
3499 "SQUASH_MSG".to_string(),
3500 "TAG_EDITMSG".to_string(),
3501 ],
3502 grammar: "Git Commit Message".to_string(),
3503 comment_prefix: Some("#".to_string()),
3504 auto_indent: false,
3505 auto_close: None,
3506 auto_surround: None,
3507 textmate_grammar: None,
3508 show_whitespace_tabs: true,
3509 line_wrap: None,
3510 wrap_column: None,
3511 page_view: None,
3512 page_width: None,
3513 use_tabs: None,
3514 tab_size: None,
3515 formatter: None,
3516 format_on_save: false,
3517 on_save: vec![],
3518 word_characters: None,
3519 },
3520 );
3521
3522 languages.insert(
3523 "gitignore".to_string(),
3524 LanguageConfig {
3525 extensions: vec!["gitignore".to_string()],
3526 filenames: vec![
3527 ".gitignore".to_string(),
3528 ".dockerignore".to_string(),
3529 ".npmignore".to_string(),
3530 ".hgignore".to_string(),
3531 ],
3532 grammar: "Gitignore".to_string(),
3533 comment_prefix: Some("#".to_string()),
3534 auto_indent: false,
3535 auto_close: None,
3536 auto_surround: None,
3537 textmate_grammar: None,
3538 show_whitespace_tabs: true,
3539 line_wrap: None,
3540 wrap_column: None,
3541 page_view: None,
3542 page_width: None,
3543 use_tabs: None,
3544 tab_size: None,
3545 formatter: None,
3546 format_on_save: false,
3547 on_save: vec![],
3548 word_characters: None,
3549 },
3550 );
3551
3552 languages.insert(
3553 "gitconfig".to_string(),
3554 LanguageConfig {
3555 extensions: vec!["gitconfig".to_string()],
3556 filenames: vec![".gitconfig".to_string(), ".gitmodules".to_string()],
3557 grammar: "Git Config".to_string(),
3558 comment_prefix: Some("#".to_string()),
3559 auto_indent: true,
3560 auto_close: None,
3561 auto_surround: None,
3562 textmate_grammar: None,
3563 show_whitespace_tabs: true,
3564 line_wrap: None,
3565 wrap_column: None,
3566 page_view: None,
3567 page_width: None,
3568 use_tabs: None,
3569 tab_size: None,
3570 formatter: None,
3571 format_on_save: false,
3572 on_save: vec![],
3573 word_characters: None,
3574 },
3575 );
3576
3577 languages.insert(
3578 "gitattributes".to_string(),
3579 LanguageConfig {
3580 extensions: vec!["gitattributes".to_string()],
3581 filenames: vec![".gitattributes".to_string()],
3582 grammar: "Git Attributes".to_string(),
3583 comment_prefix: Some("#".to_string()),
3584 auto_indent: false,
3585 auto_close: None,
3586 auto_surround: None,
3587 textmate_grammar: None,
3588 show_whitespace_tabs: true,
3589 line_wrap: None,
3590 wrap_column: None,
3591 page_view: None,
3592 page_width: None,
3593 use_tabs: None,
3594 tab_size: None,
3595 formatter: None,
3596 format_on_save: false,
3597 on_save: vec![],
3598 word_characters: None,
3599 },
3600 );
3601
3602 languages.insert(
3603 "typst".to_string(),
3604 LanguageConfig {
3605 extensions: vec!["typ".to_string()],
3606 filenames: vec![],
3607 grammar: "Typst".to_string(),
3608 comment_prefix: Some("//".to_string()),
3609 auto_indent: true,
3610 auto_close: None,
3611 auto_surround: None,
3612 textmate_grammar: None,
3613 show_whitespace_tabs: true,
3614 line_wrap: None,
3615 wrap_column: None,
3616 page_view: None,
3617 page_width: None,
3618 use_tabs: None,
3619 tab_size: None,
3620 formatter: None,
3621 format_on_save: false,
3622 on_save: vec![],
3623 word_characters: None,
3624 },
3625 );
3626
3627 languages.insert(
3632 "kotlin".to_string(),
3633 LanguageConfig {
3634 extensions: vec!["kt".to_string(), "kts".to_string()],
3635 filenames: vec![],
3636 grammar: "Kotlin".to_string(),
3637 comment_prefix: Some("//".to_string()),
3638 auto_indent: true,
3639 auto_close: None,
3640 auto_surround: None,
3641 textmate_grammar: None,
3642 show_whitespace_tabs: true,
3643 line_wrap: None,
3644 wrap_column: None,
3645 page_view: None,
3646 page_width: None,
3647 use_tabs: None,
3648 tab_size: None,
3649 formatter: None,
3650 format_on_save: false,
3651 on_save: vec![],
3652 word_characters: None,
3653 },
3654 );
3655
3656 languages.insert(
3657 "swift".to_string(),
3658 LanguageConfig {
3659 extensions: vec!["swift".to_string()],
3660 filenames: vec![],
3661 grammar: "Swift".to_string(),
3662 comment_prefix: Some("//".to_string()),
3663 auto_indent: true,
3664 auto_close: None,
3665 auto_surround: None,
3666 textmate_grammar: None,
3667 show_whitespace_tabs: true,
3668 line_wrap: None,
3669 wrap_column: None,
3670 page_view: None,
3671 page_width: None,
3672 use_tabs: None,
3673 tab_size: None,
3674 formatter: None,
3675 format_on_save: false,
3676 on_save: vec![],
3677 word_characters: None,
3678 },
3679 );
3680
3681 languages.insert(
3682 "scala".to_string(),
3683 LanguageConfig {
3684 extensions: vec!["scala".to_string(), "sc".to_string()],
3685 filenames: vec![],
3686 grammar: "Scala".to_string(),
3687 comment_prefix: Some("//".to_string()),
3688 auto_indent: true,
3689 auto_close: None,
3690 auto_surround: None,
3691 textmate_grammar: None,
3692 show_whitespace_tabs: true,
3693 line_wrap: None,
3694 wrap_column: None,
3695 page_view: None,
3696 page_width: None,
3697 use_tabs: None,
3698 tab_size: None,
3699 formatter: None,
3700 format_on_save: false,
3701 on_save: vec![],
3702 word_characters: None,
3703 },
3704 );
3705
3706 languages.insert(
3707 "dart".to_string(),
3708 LanguageConfig {
3709 extensions: vec!["dart".to_string()],
3710 filenames: vec![],
3711 grammar: "Dart".to_string(),
3712 comment_prefix: Some("//".to_string()),
3713 auto_indent: true,
3714 auto_close: None,
3715 auto_surround: None,
3716 textmate_grammar: None,
3717 show_whitespace_tabs: true,
3718 line_wrap: None,
3719 wrap_column: None,
3720 page_view: None,
3721 page_width: None,
3722 use_tabs: None,
3723 tab_size: None,
3724 formatter: None,
3725 format_on_save: false,
3726 on_save: vec![],
3727 word_characters: None,
3728 },
3729 );
3730
3731 languages.insert(
3732 "elixir".to_string(),
3733 LanguageConfig {
3734 extensions: vec!["ex".to_string(), "exs".to_string()],
3735 filenames: vec![],
3736 grammar: "Elixir".to_string(),
3737 comment_prefix: Some("#".to_string()),
3738 auto_indent: true,
3739 auto_close: None,
3740 auto_surround: None,
3741 textmate_grammar: None,
3742 show_whitespace_tabs: true,
3743 line_wrap: None,
3744 wrap_column: None,
3745 page_view: None,
3746 page_width: None,
3747 use_tabs: None,
3748 tab_size: None,
3749 formatter: None,
3750 format_on_save: false,
3751 on_save: vec![],
3752 word_characters: None,
3753 },
3754 );
3755
3756 languages.insert(
3757 "erlang".to_string(),
3758 LanguageConfig {
3759 extensions: vec!["erl".to_string(), "hrl".to_string()],
3760 filenames: vec![],
3761 grammar: "Erlang".to_string(),
3762 comment_prefix: Some("%".to_string()),
3763 auto_indent: true,
3764 auto_close: None,
3765 auto_surround: None,
3766 textmate_grammar: None,
3767 show_whitespace_tabs: true,
3768 line_wrap: None,
3769 wrap_column: None,
3770 page_view: None,
3771 page_width: None,
3772 use_tabs: None,
3773 tab_size: None,
3774 formatter: None,
3775 format_on_save: false,
3776 on_save: vec![],
3777 word_characters: None,
3778 },
3779 );
3780
3781 languages.insert(
3782 "haskell".to_string(),
3783 LanguageConfig {
3784 extensions: vec!["hs".to_string(), "lhs".to_string()],
3785 filenames: vec![],
3786 grammar: "Haskell".to_string(),
3787 comment_prefix: Some("--".to_string()),
3788 auto_indent: true,
3789 auto_close: None,
3790 auto_surround: None,
3791 textmate_grammar: None,
3792 show_whitespace_tabs: true,
3793 line_wrap: None,
3794 wrap_column: None,
3795 page_view: None,
3796 page_width: None,
3797 use_tabs: None,
3798 tab_size: None,
3799 formatter: None,
3800 format_on_save: false,
3801 on_save: vec![],
3802 word_characters: None,
3803 },
3804 );
3805
3806 languages.insert(
3807 "ocaml".to_string(),
3808 LanguageConfig {
3809 extensions: vec!["ml".to_string(), "mli".to_string()],
3810 filenames: vec![],
3811 grammar: "OCaml".to_string(),
3812 comment_prefix: None,
3813 auto_indent: true,
3814 auto_close: None,
3815 auto_surround: None,
3816 textmate_grammar: None,
3817 show_whitespace_tabs: true,
3818 line_wrap: None,
3819 wrap_column: None,
3820 page_view: None,
3821 page_width: None,
3822 use_tabs: None,
3823 tab_size: None,
3824 formatter: None,
3825 format_on_save: false,
3826 on_save: vec![],
3827 word_characters: None,
3828 },
3829 );
3830
3831 languages.insert(
3832 "clojure".to_string(),
3833 LanguageConfig {
3834 extensions: vec![
3835 "clj".to_string(),
3836 "cljs".to_string(),
3837 "cljc".to_string(),
3838 "edn".to_string(),
3839 ],
3840 filenames: vec![],
3841 grammar: "Clojure".to_string(),
3842 comment_prefix: Some(";".to_string()),
3843 auto_indent: true,
3844 auto_close: None,
3845 auto_surround: None,
3846 textmate_grammar: None,
3847 show_whitespace_tabs: true,
3848 line_wrap: None,
3849 wrap_column: None,
3850 page_view: None,
3851 page_width: None,
3852 use_tabs: None,
3853 tab_size: None,
3854 formatter: None,
3855 format_on_save: false,
3856 on_save: vec![],
3857 word_characters: None,
3858 },
3859 );
3860
3861 languages.insert(
3862 "r".to_string(),
3863 LanguageConfig {
3864 extensions: vec!["r".to_string(), "R".to_string(), "rmd".to_string()],
3865 filenames: vec![],
3866 grammar: "R".to_string(),
3867 comment_prefix: Some("#".to_string()),
3868 auto_indent: true,
3869 auto_close: None,
3870 auto_surround: None,
3871 textmate_grammar: None,
3872 show_whitespace_tabs: true,
3873 line_wrap: None,
3874 wrap_column: None,
3875 page_view: None,
3876 page_width: None,
3877 use_tabs: None,
3878 tab_size: None,
3879 formatter: None,
3880 format_on_save: false,
3881 on_save: vec![],
3882 word_characters: None,
3883 },
3884 );
3885
3886 languages.insert(
3887 "julia".to_string(),
3888 LanguageConfig {
3889 extensions: vec!["jl".to_string()],
3890 filenames: vec![],
3891 grammar: "Julia".to_string(),
3892 comment_prefix: Some("#".to_string()),
3893 auto_indent: true,
3894 auto_close: None,
3895 auto_surround: None,
3896 textmate_grammar: None,
3897 show_whitespace_tabs: true,
3898 line_wrap: None,
3899 wrap_column: None,
3900 page_view: None,
3901 page_width: None,
3902 use_tabs: None,
3903 tab_size: None,
3904 formatter: None,
3905 format_on_save: false,
3906 on_save: vec![],
3907 word_characters: None,
3908 },
3909 );
3910
3911 languages.insert(
3912 "perl".to_string(),
3913 LanguageConfig {
3914 extensions: vec!["pl".to_string(), "pm".to_string(), "t".to_string()],
3915 filenames: vec![],
3916 grammar: "Perl".to_string(),
3917 comment_prefix: Some("#".to_string()),
3918 auto_indent: true,
3919 auto_close: None,
3920 auto_surround: None,
3921 textmate_grammar: None,
3922 show_whitespace_tabs: true,
3923 line_wrap: None,
3924 wrap_column: None,
3925 page_view: None,
3926 page_width: None,
3927 use_tabs: None,
3928 tab_size: None,
3929 formatter: None,
3930 format_on_save: false,
3931 on_save: vec![],
3932 word_characters: None,
3933 },
3934 );
3935
3936 languages.insert(
3937 "nim".to_string(),
3938 LanguageConfig {
3939 extensions: vec!["nim".to_string(), "nims".to_string(), "nimble".to_string()],
3940 filenames: vec![],
3941 grammar: "Nim".to_string(),
3942 comment_prefix: Some("#".to_string()),
3943 auto_indent: true,
3944 auto_close: None,
3945 auto_surround: None,
3946 textmate_grammar: None,
3947 show_whitespace_tabs: true,
3948 line_wrap: None,
3949 wrap_column: None,
3950 page_view: None,
3951 page_width: None,
3952 use_tabs: None,
3953 tab_size: None,
3954 formatter: None,
3955 format_on_save: false,
3956 on_save: vec![],
3957 word_characters: None,
3958 },
3959 );
3960
3961 languages.insert(
3962 "gleam".to_string(),
3963 LanguageConfig {
3964 extensions: vec!["gleam".to_string()],
3965 filenames: vec![],
3966 grammar: "Gleam".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 },
3984 );
3985
3986 languages.insert(
3987 "fsharp".to_string(),
3988 LanguageConfig {
3989 extensions: vec!["fs".to_string(), "fsi".to_string(), "fsx".to_string()],
3990 filenames: vec![],
3991 grammar: "FSharp".to_string(),
3992 comment_prefix: Some("//".to_string()),
3993 auto_indent: true,
3994 auto_close: None,
3995 auto_surround: None,
3996 textmate_grammar: None,
3997 show_whitespace_tabs: true,
3998 line_wrap: None,
3999 wrap_column: None,
4000 page_view: None,
4001 page_width: None,
4002 use_tabs: None,
4003 tab_size: None,
4004 formatter: None,
4005 format_on_save: false,
4006 on_save: vec![],
4007 word_characters: None,
4008 },
4009 );
4010
4011 languages.insert(
4012 "nix".to_string(),
4013 LanguageConfig {
4014 extensions: vec!["nix".to_string()],
4015 filenames: vec![],
4016 grammar: "Nix".to_string(),
4017 comment_prefix: Some("#".to_string()),
4018 auto_indent: true,
4019 auto_close: None,
4020 auto_surround: None,
4021 textmate_grammar: None,
4022 show_whitespace_tabs: true,
4023 line_wrap: None,
4024 wrap_column: None,
4025 page_view: None,
4026 page_width: None,
4027 use_tabs: None,
4028 tab_size: None,
4029 formatter: None,
4030 format_on_save: false,
4031 on_save: vec![],
4032 word_characters: None,
4033 },
4034 );
4035
4036 languages.insert(
4037 "nushell".to_string(),
4038 LanguageConfig {
4039 extensions: vec!["nu".to_string()],
4040 filenames: vec![],
4041 grammar: "Nushell".to_string(),
4042 comment_prefix: Some("#".to_string()),
4043 auto_indent: true,
4044 auto_close: None,
4045 auto_surround: None,
4046 textmate_grammar: None,
4047 show_whitespace_tabs: true,
4048 line_wrap: None,
4049 wrap_column: None,
4050 page_view: None,
4051 page_width: None,
4052 use_tabs: None,
4053 tab_size: None,
4054 formatter: None,
4055 format_on_save: false,
4056 on_save: vec![],
4057 word_characters: None,
4058 },
4059 );
4060
4061 languages.insert(
4062 "solidity".to_string(),
4063 LanguageConfig {
4064 extensions: vec!["sol".to_string()],
4065 filenames: vec![],
4066 grammar: "Solidity".to_string(),
4067 comment_prefix: Some("//".to_string()),
4068 auto_indent: true,
4069 auto_close: None,
4070 auto_surround: None,
4071 textmate_grammar: None,
4072 show_whitespace_tabs: true,
4073 line_wrap: None,
4074 wrap_column: None,
4075 page_view: None,
4076 page_width: None,
4077 use_tabs: None,
4078 tab_size: None,
4079 formatter: None,
4080 format_on_save: false,
4081 on_save: vec![],
4082 word_characters: None,
4083 },
4084 );
4085
4086 languages.insert(
4087 "ruby".to_string(),
4088 LanguageConfig {
4089 extensions: vec!["rb".to_string(), "rake".to_string(), "gemspec".to_string()],
4090 filenames: vec![
4091 "Gemfile".to_string(),
4092 "Rakefile".to_string(),
4093 "Guardfile".to_string(),
4094 ],
4095 grammar: "Ruby".to_string(),
4096 comment_prefix: Some("#".to_string()),
4097 auto_indent: true,
4098 auto_close: None,
4099 auto_surround: None,
4100 textmate_grammar: None,
4101 show_whitespace_tabs: true,
4102 line_wrap: None,
4103 wrap_column: None,
4104 page_view: None,
4105 page_width: None,
4106 use_tabs: None,
4107 tab_size: None,
4108 formatter: None,
4109 format_on_save: false,
4110 on_save: vec![],
4111 word_characters: None,
4112 },
4113 );
4114
4115 languages.insert(
4116 "php".to_string(),
4117 LanguageConfig {
4118 extensions: vec!["php".to_string(), "phtml".to_string()],
4119 filenames: vec![],
4120 grammar: "PHP".to_string(),
4121 comment_prefix: Some("//".to_string()),
4122 auto_indent: true,
4123 auto_close: None,
4124 auto_surround: None,
4125 textmate_grammar: None,
4126 show_whitespace_tabs: true,
4127 line_wrap: None,
4128 wrap_column: None,
4129 page_view: None,
4130 page_width: None,
4131 use_tabs: None,
4132 tab_size: None,
4133 formatter: None,
4134 format_on_save: false,
4135 on_save: vec![],
4136 word_characters: None,
4137 },
4138 );
4139
4140 languages.insert(
4141 "lua".to_string(),
4142 LanguageConfig {
4143 extensions: vec!["lua".to_string()],
4144 filenames: vec![],
4145 grammar: "Lua".to_string(),
4146 comment_prefix: Some("--".to_string()),
4147 auto_indent: true,
4148 auto_close: None,
4149 auto_surround: None,
4150 textmate_grammar: None,
4151 show_whitespace_tabs: true,
4152 line_wrap: None,
4153 wrap_column: None,
4154 page_view: None,
4155 page_width: None,
4156 use_tabs: None,
4157 tab_size: None,
4158 formatter: None,
4159 format_on_save: false,
4160 on_save: vec![],
4161 word_characters: None,
4162 },
4163 );
4164
4165 languages.insert(
4166 "html".to_string(),
4167 LanguageConfig {
4168 extensions: vec!["html".to_string(), "htm".to_string()],
4169 filenames: vec![],
4170 grammar: "HTML".to_string(),
4171 comment_prefix: None,
4172 auto_indent: true,
4173 auto_close: None,
4174 auto_surround: None,
4175 textmate_grammar: None,
4176 show_whitespace_tabs: true,
4177 line_wrap: None,
4178 wrap_column: None,
4179 page_view: None,
4180 page_width: None,
4181 use_tabs: None,
4182 tab_size: None,
4183 formatter: None,
4184 format_on_save: false,
4185 on_save: vec![],
4186 word_characters: None,
4187 },
4188 );
4189
4190 languages.insert(
4191 "css".to_string(),
4192 LanguageConfig {
4193 extensions: vec!["css".to_string()],
4194 filenames: vec![],
4195 grammar: "CSS".to_string(),
4196 comment_prefix: None,
4197 auto_indent: true,
4198 auto_close: None,
4199 auto_surround: None,
4200 textmate_grammar: None,
4201 show_whitespace_tabs: true,
4202 line_wrap: None,
4203 wrap_column: None,
4204 page_view: None,
4205 page_width: None,
4206 use_tabs: None,
4207 tab_size: None,
4208 formatter: None,
4209 format_on_save: false,
4210 on_save: vec![],
4211 word_characters: None,
4212 },
4213 );
4214
4215 languages.insert(
4216 "sql".to_string(),
4217 LanguageConfig {
4218 extensions: vec!["sql".to_string()],
4219 filenames: vec![],
4220 grammar: "SQL".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 },
4238 );
4239
4240 languages.insert(
4241 "graphql".to_string(),
4242 LanguageConfig {
4243 extensions: vec!["graphql".to_string(), "gql".to_string()],
4244 filenames: vec![],
4245 grammar: "GraphQL".to_string(),
4246 comment_prefix: Some("#".to_string()),
4247 auto_indent: true,
4248 auto_close: None,
4249 auto_surround: None,
4250 textmate_grammar: None,
4251 show_whitespace_tabs: true,
4252 line_wrap: None,
4253 wrap_column: None,
4254 page_view: None,
4255 page_width: None,
4256 use_tabs: None,
4257 tab_size: None,
4258 formatter: None,
4259 format_on_save: false,
4260 on_save: vec![],
4261 word_characters: None,
4262 },
4263 );
4264
4265 languages.insert(
4266 "protobuf".to_string(),
4267 LanguageConfig {
4268 extensions: vec!["proto".to_string()],
4269 filenames: vec![],
4270 grammar: "Protocol Buffers".to_string(),
4271 comment_prefix: Some("//".to_string()),
4272 auto_indent: true,
4273 auto_close: None,
4274 auto_surround: None,
4275 textmate_grammar: None,
4276 show_whitespace_tabs: true,
4277 line_wrap: None,
4278 wrap_column: None,
4279 page_view: None,
4280 page_width: None,
4281 use_tabs: None,
4282 tab_size: None,
4283 formatter: None,
4284 format_on_save: false,
4285 on_save: vec![],
4286 word_characters: None,
4287 },
4288 );
4289
4290 languages.insert(
4291 "cmake".to_string(),
4292 LanguageConfig {
4293 extensions: vec!["cmake".to_string()],
4294 filenames: vec!["CMakeLists.txt".to_string()],
4295 grammar: "CMake".to_string(),
4296 comment_prefix: Some("#".to_string()),
4297 auto_indent: true,
4298 auto_close: None,
4299 auto_surround: None,
4300 textmate_grammar: None,
4301 show_whitespace_tabs: true,
4302 line_wrap: None,
4303 wrap_column: None,
4304 page_view: None,
4305 page_width: None,
4306 use_tabs: None,
4307 tab_size: None,
4308 formatter: None,
4309 format_on_save: false,
4310 on_save: vec![],
4311 word_characters: None,
4312 },
4313 );
4314
4315 languages.insert(
4316 "terraform".to_string(),
4317 LanguageConfig {
4318 extensions: vec!["tf".to_string(), "tfvars".to_string(), "hcl".to_string()],
4319 filenames: vec![],
4320 grammar: "HCL".to_string(),
4321 comment_prefix: Some("#".to_string()),
4322 auto_indent: true,
4323 auto_close: None,
4324 auto_surround: None,
4325 textmate_grammar: None,
4326 show_whitespace_tabs: true,
4327 line_wrap: None,
4328 wrap_column: None,
4329 page_view: None,
4330 page_width: None,
4331 use_tabs: None,
4332 tab_size: None,
4333 formatter: None,
4334 format_on_save: false,
4335 on_save: vec![],
4336 word_characters: None,
4337 },
4338 );
4339
4340 languages.insert(
4341 "vue".to_string(),
4342 LanguageConfig {
4343 extensions: vec!["vue".to_string()],
4344 filenames: vec![],
4345 grammar: "Vue".to_string(),
4346 comment_prefix: None,
4347 auto_indent: true,
4348 auto_close: None,
4349 auto_surround: None,
4350 textmate_grammar: None,
4351 show_whitespace_tabs: true,
4352 line_wrap: None,
4353 wrap_column: None,
4354 page_view: None,
4355 page_width: None,
4356 use_tabs: None,
4357 tab_size: None,
4358 formatter: None,
4359 format_on_save: false,
4360 on_save: vec![],
4361 word_characters: None,
4362 },
4363 );
4364
4365 languages.insert(
4366 "svelte".to_string(),
4367 LanguageConfig {
4368 extensions: vec!["svelte".to_string()],
4369 filenames: vec![],
4370 grammar: "Svelte".to_string(),
4371 comment_prefix: None,
4372 auto_indent: true,
4373 auto_close: None,
4374 auto_surround: None,
4375 textmate_grammar: None,
4376 show_whitespace_tabs: true,
4377 line_wrap: None,
4378 wrap_column: None,
4379 page_view: None,
4380 page_width: None,
4381 use_tabs: None,
4382 tab_size: None,
4383 formatter: None,
4384 format_on_save: false,
4385 on_save: vec![],
4386 word_characters: None,
4387 },
4388 );
4389
4390 languages.insert(
4391 "astro".to_string(),
4392 LanguageConfig {
4393 extensions: vec!["astro".to_string()],
4394 filenames: vec![],
4395 grammar: "Astro".to_string(),
4396 comment_prefix: None,
4397 auto_indent: true,
4398 auto_close: None,
4399 auto_surround: None,
4400 textmate_grammar: None,
4401 show_whitespace_tabs: true,
4402 line_wrap: None,
4403 wrap_column: None,
4404 page_view: None,
4405 page_width: None,
4406 use_tabs: None,
4407 tab_size: None,
4408 formatter: None,
4409 format_on_save: false,
4410 on_save: vec![],
4411 word_characters: None,
4412 },
4413 );
4414
4415 languages.insert(
4418 "scss".to_string(),
4419 LanguageConfig {
4420 extensions: vec!["scss".to_string()],
4421 filenames: vec![],
4422 grammar: "SCSS".to_string(),
4423 comment_prefix: Some("//".to_string()),
4424 auto_indent: true,
4425 auto_close: None,
4426 auto_surround: None,
4427 textmate_grammar: None,
4428 show_whitespace_tabs: true,
4429 line_wrap: None,
4430 wrap_column: None,
4431 page_view: None,
4432 page_width: None,
4433 use_tabs: None,
4434 tab_size: None,
4435 formatter: None,
4436 format_on_save: false,
4437 on_save: vec![],
4438 word_characters: None,
4439 },
4440 );
4441
4442 languages.insert(
4443 "less".to_string(),
4444 LanguageConfig {
4445 extensions: vec!["less".to_string()],
4446 filenames: vec![],
4447 grammar: "LESS".to_string(),
4448 comment_prefix: Some("//".to_string()),
4449 auto_indent: true,
4450 auto_close: None,
4451 auto_surround: None,
4452 textmate_grammar: None,
4453 show_whitespace_tabs: true,
4454 line_wrap: None,
4455 wrap_column: None,
4456 page_view: None,
4457 page_width: None,
4458 use_tabs: None,
4459 tab_size: None,
4460 formatter: None,
4461 format_on_save: false,
4462 on_save: vec![],
4463 word_characters: None,
4464 },
4465 );
4466
4467 languages.insert(
4468 "powershell".to_string(),
4469 LanguageConfig {
4470 extensions: vec!["ps1".to_string(), "psm1".to_string(), "psd1".to_string()],
4471 filenames: vec![],
4472 grammar: "PowerShell".to_string(),
4473 comment_prefix: Some("#".to_string()),
4474 auto_indent: true,
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 },
4490 );
4491
4492 languages.insert(
4493 "kdl".to_string(),
4494 LanguageConfig {
4495 extensions: vec!["kdl".to_string()],
4496 filenames: vec![],
4497 grammar: "KDL".to_string(),
4498 comment_prefix: Some("//".to_string()),
4499 auto_indent: true,
4500 auto_close: None,
4501 auto_surround: None,
4502 textmate_grammar: None,
4503 show_whitespace_tabs: true,
4504 line_wrap: None,
4505 wrap_column: None,
4506 page_view: None,
4507 page_width: None,
4508 use_tabs: None,
4509 tab_size: None,
4510 formatter: None,
4511 format_on_save: false,
4512 on_save: vec![],
4513 word_characters: None,
4514 },
4515 );
4516
4517 languages.insert(
4518 "starlark".to_string(),
4519 LanguageConfig {
4520 extensions: vec!["bzl".to_string(), "star".to_string()],
4521 filenames: vec!["BUILD".to_string(), "WORKSPACE".to_string()],
4522 grammar: "Starlark".to_string(),
4523 comment_prefix: Some("#".to_string()),
4524 auto_indent: true,
4525 auto_close: None,
4526 auto_surround: None,
4527 textmate_grammar: None,
4528 show_whitespace_tabs: true,
4529 line_wrap: None,
4530 wrap_column: None,
4531 page_view: None,
4532 page_width: None,
4533 use_tabs: None,
4534 tab_size: None,
4535 formatter: None,
4536 format_on_save: false,
4537 on_save: vec![],
4538 word_characters: None,
4539 },
4540 );
4541
4542 languages.insert(
4543 "justfile".to_string(),
4544 LanguageConfig {
4545 extensions: vec![],
4546 filenames: vec![
4547 "justfile".to_string(),
4548 "Justfile".to_string(),
4549 ".justfile".to_string(),
4550 ],
4551 grammar: "Justfile".to_string(),
4552 comment_prefix: Some("#".to_string()),
4553 auto_indent: true,
4554 auto_close: None,
4555 auto_surround: None,
4556 textmate_grammar: None,
4557 show_whitespace_tabs: true,
4558 line_wrap: None,
4559 wrap_column: None,
4560 page_view: None,
4561 page_width: None,
4562 use_tabs: Some(true),
4563 tab_size: None,
4564 formatter: None,
4565 format_on_save: false,
4566 on_save: vec![],
4567 word_characters: None,
4568 },
4569 );
4570
4571 languages.insert(
4572 "earthfile".to_string(),
4573 LanguageConfig {
4574 extensions: vec!["earth".to_string()],
4575 filenames: vec!["Earthfile".to_string()],
4576 grammar: "Earthfile".to_string(),
4577 comment_prefix: Some("#".to_string()),
4578 auto_indent: true,
4579 auto_close: None,
4580 auto_surround: None,
4581 textmate_grammar: None,
4582 show_whitespace_tabs: true,
4583 line_wrap: None,
4584 wrap_column: None,
4585 page_view: None,
4586 page_width: None,
4587 use_tabs: None,
4588 tab_size: None,
4589 formatter: None,
4590 format_on_save: false,
4591 on_save: vec![],
4592 word_characters: None,
4593 },
4594 );
4595
4596 languages.insert(
4597 "gomod".to_string(),
4598 LanguageConfig {
4599 extensions: vec![],
4600 filenames: vec!["go.mod".to_string(), "go.sum".to_string()],
4601 grammar: "Go Module".to_string(),
4602 comment_prefix: Some("//".to_string()),
4603 auto_indent: true,
4604 auto_close: None,
4605 auto_surround: None,
4606 textmate_grammar: None,
4607 show_whitespace_tabs: true,
4608 line_wrap: None,
4609 wrap_column: None,
4610 page_view: None,
4611 page_width: None,
4612 use_tabs: Some(true),
4613 tab_size: None,
4614 formatter: None,
4615 format_on_save: false,
4616 on_save: vec![],
4617 word_characters: None,
4618 },
4619 );
4620
4621 languages.insert(
4622 "vlang".to_string(),
4623 LanguageConfig {
4624 extensions: vec!["v".to_string(), "vv".to_string()],
4625 filenames: vec![],
4626 grammar: "V".to_string(),
4627 comment_prefix: Some("//".to_string()),
4628 auto_indent: true,
4629 auto_close: None,
4630 auto_surround: None,
4631 textmate_grammar: None,
4632 show_whitespace_tabs: true,
4633 line_wrap: None,
4634 wrap_column: None,
4635 page_view: None,
4636 page_width: None,
4637 use_tabs: None,
4638 tab_size: None,
4639 formatter: None,
4640 format_on_save: false,
4641 on_save: vec![],
4642 word_characters: None,
4643 },
4644 );
4645
4646 languages.insert(
4647 "ini".to_string(),
4648 LanguageConfig {
4649 extensions: vec!["ini".to_string(), "cfg".to_string()],
4650 filenames: vec![],
4651 grammar: "INI".to_string(),
4652 comment_prefix: Some(";".to_string()),
4653 auto_indent: false,
4654 auto_close: None,
4655 auto_surround: None,
4656 textmate_grammar: None,
4657 show_whitespace_tabs: true,
4658 line_wrap: None,
4659 wrap_column: None,
4660 page_view: None,
4661 page_width: None,
4662 use_tabs: None,
4663 tab_size: None,
4664 formatter: None,
4665 format_on_save: false,
4666 on_save: vec![],
4667 word_characters: None,
4668 },
4669 );
4670
4671 languages.insert(
4672 "hyprlang".to_string(),
4673 LanguageConfig {
4674 extensions: vec!["hl".to_string()],
4675 filenames: vec!["hyprland.conf".to_string()],
4676 grammar: "Hyprlang".to_string(),
4677 comment_prefix: Some("#".to_string()),
4678 auto_indent: true,
4679 auto_close: None,
4680 auto_surround: None,
4681 textmate_grammar: None,
4682 show_whitespace_tabs: true,
4683 line_wrap: None,
4684 wrap_column: None,
4685 page_view: None,
4686 page_width: None,
4687 use_tabs: None,
4688 tab_size: None,
4689 formatter: None,
4690 format_on_save: false,
4691 on_save: vec![],
4692 word_characters: None,
4693 },
4694 );
4695
4696 languages
4697 }
4698
4699 #[cfg(feature = "runtime")]
4701 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
4702 let mut lsp = HashMap::new();
4703
4704 let ra_log_path = crate::services::log_dirs::lsp_log_path("rust-analyzer")
4707 .to_string_lossy()
4708 .to_string();
4709
4710 Self::populate_lsp_config(&mut lsp, ra_log_path);
4711 lsp
4712 }
4713
4714 #[cfg(not(feature = "runtime"))]
4716 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
4717 HashMap::new()
4719 }
4720
4721 #[cfg(feature = "runtime")]
4723 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
4724 let mut universal = HashMap::new();
4725
4726 universal.insert(
4739 "quicklsp".to_string(),
4740 LspLanguageConfig::Multi(vec![LspServerConfig {
4741 command: "quicklsp".to_string(),
4742 args: vec![],
4743 enabled: false,
4744 auto_start: false,
4745 process_limits: ProcessLimits::default(),
4746 initialization_options: None,
4747 env: Default::default(),
4748 language_id_overrides: Default::default(),
4749 name: Some("QuickLSP".to_string()),
4750 only_features: None,
4751 except_features: None,
4752 root_markers: vec![
4753 "Cargo.toml".to_string(),
4754 "package.json".to_string(),
4755 "go.mod".to_string(),
4756 "pyproject.toml".to_string(),
4757 "requirements.txt".to_string(),
4758 ".git".to_string(),
4759 ],
4760 }]),
4761 );
4762
4763 universal
4764 }
4765
4766 #[cfg(not(feature = "runtime"))]
4768 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
4769 HashMap::new()
4770 }
4771
4772 #[cfg(feature = "runtime")]
4773 fn populate_lsp_config(lsp: &mut HashMap<String, LspLanguageConfig>, ra_log_path: String) {
4774 lsp.insert(
4778 "rust".to_string(),
4779 LspLanguageConfig::Multi(vec![LspServerConfig {
4780 command: "rust-analyzer".to_string(),
4781 args: vec!["--log-file".to_string(), ra_log_path],
4782 enabled: true,
4783 auto_start: false,
4784 process_limits: ProcessLimits::unlimited(),
4785 initialization_options: None,
4786 env: Default::default(),
4787 language_id_overrides: Default::default(),
4788 name: None,
4789 only_features: None,
4790 except_features: None,
4791 root_markers: vec![
4792 "Cargo.toml".to_string(),
4793 "rust-project.json".to_string(),
4794 ".git".to_string(),
4795 ],
4796 }]),
4797 );
4798
4799 lsp.insert(
4801 "python".to_string(),
4802 LspLanguageConfig::Multi(vec![LspServerConfig {
4803 command: "pylsp".to_string(),
4804 args: vec![],
4805 enabled: true,
4806 auto_start: false,
4807 process_limits: ProcessLimits::default(),
4808 initialization_options: None,
4809 env: Default::default(),
4810 language_id_overrides: Default::default(),
4811 name: None,
4812 only_features: None,
4813 except_features: None,
4814 root_markers: vec![
4815 "pyproject.toml".to_string(),
4816 "setup.py".to_string(),
4817 "setup.cfg".to_string(),
4818 "pyrightconfig.json".to_string(),
4819 ".git".to_string(),
4820 ],
4821 }]),
4822 );
4823
4824 lsp.insert(
4827 "javascript".to_string(),
4828 LspLanguageConfig::Multi(vec![LspServerConfig {
4829 command: "typescript-language-server".to_string(),
4830 args: vec!["--stdio".to_string()],
4831 enabled: true,
4832 auto_start: false,
4833 process_limits: ProcessLimits::default(),
4834 initialization_options: None,
4835 env: Default::default(),
4836 language_id_overrides: HashMap::from([(
4837 "jsx".to_string(),
4838 "javascriptreact".to_string(),
4839 )]),
4840 name: None,
4841 only_features: None,
4842 except_features: None,
4843 root_markers: vec![
4844 "tsconfig.json".to_string(),
4845 "jsconfig.json".to_string(),
4846 "package.json".to_string(),
4847 ".git".to_string(),
4848 ],
4849 }]),
4850 );
4851 lsp.insert(
4852 "typescript".to_string(),
4853 LspLanguageConfig::Multi(vec![LspServerConfig {
4854 command: "typescript-language-server".to_string(),
4855 args: vec!["--stdio".to_string()],
4856 enabled: true,
4857 auto_start: false,
4858 process_limits: ProcessLimits::default(),
4859 initialization_options: None,
4860 env: Default::default(),
4861 language_id_overrides: HashMap::from([(
4862 "tsx".to_string(),
4863 "typescriptreact".to_string(),
4864 )]),
4865 name: None,
4866 only_features: None,
4867 except_features: None,
4868 root_markers: vec![
4869 "tsconfig.json".to_string(),
4870 "jsconfig.json".to_string(),
4871 "package.json".to_string(),
4872 ".git".to_string(),
4873 ],
4874 }]),
4875 );
4876
4877 lsp.insert(
4879 "html".to_string(),
4880 LspLanguageConfig::Multi(vec![LspServerConfig {
4881 command: "vscode-html-language-server".to_string(),
4882 args: vec!["--stdio".to_string()],
4883 enabled: true,
4884 auto_start: false,
4885 process_limits: ProcessLimits::default(),
4886 initialization_options: None,
4887 env: Default::default(),
4888 language_id_overrides: Default::default(),
4889 name: None,
4890 only_features: None,
4891 except_features: None,
4892 root_markers: Default::default(),
4893 }]),
4894 );
4895
4896 lsp.insert(
4898 "css".to_string(),
4899 LspLanguageConfig::Multi(vec![LspServerConfig {
4900 command: "vscode-css-language-server".to_string(),
4901 args: vec!["--stdio".to_string()],
4902 enabled: true,
4903 auto_start: false,
4904 process_limits: ProcessLimits::default(),
4905 initialization_options: None,
4906 env: Default::default(),
4907 language_id_overrides: Default::default(),
4908 name: None,
4909 only_features: None,
4910 except_features: None,
4911 root_markers: Default::default(),
4912 }]),
4913 );
4914
4915 lsp.insert(
4917 "c".to_string(),
4918 LspLanguageConfig::Multi(vec![LspServerConfig {
4919 command: "clangd".to_string(),
4920 args: vec![],
4921 enabled: true,
4922 auto_start: false,
4923 process_limits: ProcessLimits::default(),
4924 initialization_options: None,
4925 env: Default::default(),
4926 language_id_overrides: Default::default(),
4927 name: None,
4928 only_features: None,
4929 except_features: None,
4930 root_markers: vec![
4931 "compile_commands.json".to_string(),
4932 "CMakeLists.txt".to_string(),
4933 "Makefile".to_string(),
4934 ".git".to_string(),
4935 ],
4936 }]),
4937 );
4938 lsp.insert(
4939 "cpp".to_string(),
4940 LspLanguageConfig::Multi(vec![LspServerConfig {
4941 command: "clangd".to_string(),
4942 args: vec![],
4943 enabled: true,
4944 auto_start: false,
4945 process_limits: ProcessLimits::default(),
4946 initialization_options: None,
4947 env: Default::default(),
4948 language_id_overrides: Default::default(),
4949 name: None,
4950 only_features: None,
4951 except_features: None,
4952 root_markers: vec![
4953 "compile_commands.json".to_string(),
4954 "CMakeLists.txt".to_string(),
4955 "Makefile".to_string(),
4956 ".git".to_string(),
4957 ],
4958 }]),
4959 );
4960
4961 lsp.insert(
4963 "go".to_string(),
4964 LspLanguageConfig::Multi(vec![LspServerConfig {
4965 command: "gopls".to_string(),
4966 args: vec![],
4967 enabled: true,
4968 auto_start: false,
4969 process_limits: ProcessLimits::default(),
4970 initialization_options: None,
4971 env: Default::default(),
4972 language_id_overrides: Default::default(),
4973 name: None,
4974 only_features: None,
4975 except_features: None,
4976 root_markers: vec![
4977 "go.mod".to_string(),
4978 "go.work".to_string(),
4979 ".git".to_string(),
4980 ],
4981 }]),
4982 );
4983
4984 lsp.insert(
4986 "json".to_string(),
4987 LspLanguageConfig::Multi(vec![LspServerConfig {
4988 command: "vscode-json-language-server".to_string(),
4989 args: vec!["--stdio".to_string()],
4990 enabled: true,
4991 auto_start: false,
4992 process_limits: ProcessLimits::default(),
4993 initialization_options: None,
4994 env: Default::default(),
4995 language_id_overrides: Default::default(),
4996 name: None,
4997 only_features: None,
4998 except_features: None,
4999 root_markers: Default::default(),
5000 }]),
5001 );
5002
5003 lsp.insert(
5005 "csharp".to_string(),
5006 LspLanguageConfig::Multi(vec![LspServerConfig {
5007 command: "csharp-ls".to_string(),
5008 args: vec![],
5009 enabled: true,
5010 auto_start: false,
5011 process_limits: ProcessLimits::default(),
5012 initialization_options: None,
5013 env: Default::default(),
5014 language_id_overrides: Default::default(),
5015 name: None,
5016 only_features: None,
5017 except_features: None,
5018 root_markers: vec![
5019 "*.csproj".to_string(),
5020 "*.sln".to_string(),
5021 ".git".to_string(),
5022 ],
5023 }]),
5024 );
5025
5026 lsp.insert(
5029 "odin".to_string(),
5030 LspLanguageConfig::Multi(vec![LspServerConfig {
5031 command: "ols".to_string(),
5032 args: vec![],
5033 enabled: true,
5034 auto_start: false,
5035 process_limits: ProcessLimits::default(),
5036 initialization_options: None,
5037 env: Default::default(),
5038 language_id_overrides: Default::default(),
5039 name: None,
5040 only_features: None,
5041 except_features: None,
5042 root_markers: Default::default(),
5043 }]),
5044 );
5045
5046 lsp.insert(
5049 "zig".to_string(),
5050 LspLanguageConfig::Multi(vec![LspServerConfig {
5051 command: "zls".to_string(),
5052 args: vec![],
5053 enabled: true,
5054 auto_start: false,
5055 process_limits: ProcessLimits::default(),
5056 initialization_options: None,
5057 env: Default::default(),
5058 language_id_overrides: Default::default(),
5059 name: None,
5060 only_features: None,
5061 except_features: None,
5062 root_markers: Default::default(),
5063 }]),
5064 );
5065
5066 lsp.insert(
5069 "java".to_string(),
5070 LspLanguageConfig::Multi(vec![LspServerConfig {
5071 command: "jdtls".to_string(),
5072 args: vec![],
5073 enabled: true,
5074 auto_start: false,
5075 process_limits: ProcessLimits::default(),
5076 initialization_options: None,
5077 env: Default::default(),
5078 language_id_overrides: Default::default(),
5079 name: None,
5080 only_features: None,
5081 except_features: None,
5082 root_markers: vec![
5083 "pom.xml".to_string(),
5084 "build.gradle".to_string(),
5085 "build.gradle.kts".to_string(),
5086 ".git".to_string(),
5087 ],
5088 }]),
5089 );
5090
5091 lsp.insert(
5094 "latex".to_string(),
5095 LspLanguageConfig::Multi(vec![LspServerConfig {
5096 command: "texlab".to_string(),
5097 args: vec![],
5098 enabled: true,
5099 auto_start: false,
5100 process_limits: ProcessLimits::default(),
5101 initialization_options: None,
5102 env: Default::default(),
5103 language_id_overrides: Default::default(),
5104 name: None,
5105 only_features: None,
5106 except_features: None,
5107 root_markers: Default::default(),
5108 }]),
5109 );
5110
5111 lsp.insert(
5114 "markdown".to_string(),
5115 LspLanguageConfig::Multi(vec![LspServerConfig {
5116 command: "marksman".to_string(),
5117 args: vec!["server".to_string()],
5118 enabled: true,
5119 auto_start: false,
5120 process_limits: ProcessLimits::default(),
5121 initialization_options: None,
5122 env: Default::default(),
5123 language_id_overrides: Default::default(),
5124 name: None,
5125 only_features: None,
5126 except_features: None,
5127 root_markers: Default::default(),
5128 }]),
5129 );
5130
5131 lsp.insert(
5134 "templ".to_string(),
5135 LspLanguageConfig::Multi(vec![LspServerConfig {
5136 command: "templ".to_string(),
5137 args: vec!["lsp".to_string()],
5138 enabled: true,
5139 auto_start: false,
5140 process_limits: ProcessLimits::default(),
5141 initialization_options: None,
5142 env: Default::default(),
5143 language_id_overrides: Default::default(),
5144 name: None,
5145 only_features: None,
5146 except_features: None,
5147 root_markers: Default::default(),
5148 }]),
5149 );
5150
5151 lsp.insert(
5154 "typst".to_string(),
5155 LspLanguageConfig::Multi(vec![LspServerConfig {
5156 command: "tinymist".to_string(),
5157 args: vec![],
5158 enabled: true,
5159 auto_start: false,
5160 process_limits: ProcessLimits::default(),
5161 initialization_options: None,
5162 env: Default::default(),
5163 language_id_overrides: Default::default(),
5164 name: None,
5165 only_features: None,
5166 except_features: None,
5167 root_markers: Default::default(),
5168 }]),
5169 );
5170
5171 lsp.insert(
5173 "bash".to_string(),
5174 LspLanguageConfig::Multi(vec![LspServerConfig {
5175 command: "bash-language-server".to_string(),
5176 args: vec!["start".to_string()],
5177 enabled: true,
5178 auto_start: false,
5179 process_limits: ProcessLimits::default(),
5180 initialization_options: None,
5181 env: Default::default(),
5182 language_id_overrides: Default::default(),
5183 name: None,
5184 only_features: None,
5185 except_features: None,
5186 root_markers: Default::default(),
5187 }]),
5188 );
5189
5190 lsp.insert(
5193 "lua".to_string(),
5194 LspLanguageConfig::Multi(vec![LspServerConfig {
5195 command: "lua-language-server".to_string(),
5196 args: vec![],
5197 enabled: true,
5198 auto_start: false,
5199 process_limits: ProcessLimits::default(),
5200 initialization_options: None,
5201 env: Default::default(),
5202 language_id_overrides: Default::default(),
5203 name: None,
5204 only_features: None,
5205 except_features: None,
5206 root_markers: vec![
5207 ".luarc.json".to_string(),
5208 ".luarc.jsonc".to_string(),
5209 ".luacheckrc".to_string(),
5210 ".stylua.toml".to_string(),
5211 ".git".to_string(),
5212 ],
5213 }]),
5214 );
5215
5216 lsp.insert(
5218 "ruby".to_string(),
5219 LspLanguageConfig::Multi(vec![LspServerConfig {
5220 command: "solargraph".to_string(),
5221 args: vec!["stdio".to_string()],
5222 enabled: true,
5223 auto_start: false,
5224 process_limits: ProcessLimits::default(),
5225 initialization_options: None,
5226 env: Default::default(),
5227 language_id_overrides: Default::default(),
5228 name: None,
5229 only_features: None,
5230 except_features: None,
5231 root_markers: vec![
5232 "Gemfile".to_string(),
5233 ".ruby-version".to_string(),
5234 ".git".to_string(),
5235 ],
5236 }]),
5237 );
5238
5239 lsp.insert(
5242 "php".to_string(),
5243 LspLanguageConfig::Multi(vec![LspServerConfig {
5244 command: "phpactor".to_string(),
5245 args: vec!["language-server".to_string()],
5246 enabled: true,
5247 auto_start: false,
5248 process_limits: ProcessLimits::default(),
5249 initialization_options: None,
5250 env: Default::default(),
5251 language_id_overrides: Default::default(),
5252 name: None,
5253 only_features: None,
5254 except_features: None,
5255 root_markers: vec!["composer.json".to_string(), ".git".to_string()],
5256 }]),
5257 );
5258
5259 lsp.insert(
5261 "yaml".to_string(),
5262 LspLanguageConfig::Multi(vec![LspServerConfig {
5263 command: "yaml-language-server".to_string(),
5264 args: vec!["--stdio".to_string()],
5265 enabled: true,
5266 auto_start: false,
5267 process_limits: ProcessLimits::default(),
5268 initialization_options: None,
5269 env: Default::default(),
5270 language_id_overrides: Default::default(),
5271 name: None,
5272 only_features: None,
5273 except_features: None,
5274 root_markers: Default::default(),
5275 }]),
5276 );
5277
5278 lsp.insert(
5281 "toml".to_string(),
5282 LspLanguageConfig::Multi(vec![LspServerConfig {
5283 command: "taplo".to_string(),
5284 args: vec!["lsp".to_string(), "stdio".to_string()],
5285 enabled: true,
5286 auto_start: false,
5287 process_limits: ProcessLimits::default(),
5288 initialization_options: None,
5289 env: Default::default(),
5290 language_id_overrides: Default::default(),
5291 name: None,
5292 only_features: None,
5293 except_features: None,
5294 root_markers: Default::default(),
5295 }]),
5296 );
5297
5298 lsp.insert(
5301 "dart".to_string(),
5302 LspLanguageConfig::Multi(vec![LspServerConfig {
5303 command: "dart".to_string(),
5304 args: vec!["language-server".to_string(), "--protocol=lsp".to_string()],
5305 enabled: true,
5306 auto_start: false,
5307 process_limits: ProcessLimits::default(),
5308 initialization_options: None,
5309 env: Default::default(),
5310 language_id_overrides: Default::default(),
5311 name: None,
5312 only_features: None,
5313 except_features: None,
5314 root_markers: vec!["pubspec.yaml".to_string(), ".git".to_string()],
5315 }]),
5316 );
5317
5318 lsp.insert(
5321 "nushell".to_string(),
5322 LspLanguageConfig::Multi(vec![LspServerConfig {
5323 command: "nu".to_string(),
5324 args: vec!["--lsp".to_string()],
5325 enabled: true,
5326 auto_start: false,
5327 process_limits: ProcessLimits::default(),
5328 initialization_options: None,
5329 env: Default::default(),
5330 language_id_overrides: Default::default(),
5331 name: None,
5332 only_features: None,
5333 except_features: None,
5334 root_markers: Default::default(),
5335 }]),
5336 );
5337
5338 lsp.insert(
5341 "solidity".to_string(),
5342 LspLanguageConfig::Multi(vec![LspServerConfig {
5343 command: "nomicfoundation-solidity-language-server".to_string(),
5344 args: vec!["--stdio".to_string()],
5345 enabled: true,
5346 auto_start: false,
5347 process_limits: ProcessLimits::default(),
5348 initialization_options: None,
5349 env: Default::default(),
5350 language_id_overrides: Default::default(),
5351 name: None,
5352 only_features: None,
5353 except_features: None,
5354 root_markers: Default::default(),
5355 }]),
5356 );
5357
5358 lsp.insert(
5363 "terraform".to_string(),
5364 LspLanguageConfig::Multi(vec![LspServerConfig {
5365 command: "terraform-ls".to_string(),
5366 args: vec!["serve".to_string()],
5367 enabled: true,
5368 auto_start: false,
5369 process_limits: ProcessLimits::default(),
5370 initialization_options: None,
5371 env: Default::default(),
5372 language_id_overrides: Default::default(),
5373 name: None,
5374 only_features: None,
5375 except_features: None,
5376 root_markers: vec![
5377 "*.tf".to_string(),
5378 ".terraform".to_string(),
5379 ".git".to_string(),
5380 ],
5381 }]),
5382 );
5383
5384 lsp.insert(
5387 "cmake".to_string(),
5388 LspLanguageConfig::Multi(vec![LspServerConfig {
5389 command: "cmake-language-server".to_string(),
5390 args: vec![],
5391 enabled: true,
5392 auto_start: false,
5393 process_limits: ProcessLimits::default(),
5394 initialization_options: None,
5395 env: Default::default(),
5396 language_id_overrides: Default::default(),
5397 name: None,
5398 only_features: None,
5399 except_features: None,
5400 root_markers: vec!["CMakeLists.txt".to_string(), ".git".to_string()],
5401 }]),
5402 );
5403
5404 lsp.insert(
5407 "protobuf".to_string(),
5408 LspLanguageConfig::Multi(vec![LspServerConfig {
5409 command: "buf".to_string(),
5410 args: vec!["beta".to_string(), "lsp".to_string()],
5411 enabled: true,
5412 auto_start: false,
5413 process_limits: ProcessLimits::default(),
5414 initialization_options: None,
5415 env: Default::default(),
5416 language_id_overrides: Default::default(),
5417 name: None,
5418 only_features: None,
5419 except_features: None,
5420 root_markers: Default::default(),
5421 }]),
5422 );
5423
5424 lsp.insert(
5427 "graphql".to_string(),
5428 LspLanguageConfig::Multi(vec![LspServerConfig {
5429 command: "graphql-lsp".to_string(),
5430 args: vec!["server".to_string(), "-m".to_string(), "stream".to_string()],
5431 enabled: true,
5432 auto_start: false,
5433 process_limits: ProcessLimits::default(),
5434 initialization_options: None,
5435 env: Default::default(),
5436 language_id_overrides: Default::default(),
5437 name: None,
5438 only_features: None,
5439 except_features: None,
5440 root_markers: Default::default(),
5441 }]),
5442 );
5443
5444 lsp.insert(
5447 "sql".to_string(),
5448 LspLanguageConfig::Multi(vec![LspServerConfig {
5449 command: "sqls".to_string(),
5450 args: vec![],
5451 enabled: true,
5452 auto_start: false,
5453 process_limits: ProcessLimits::default(),
5454 initialization_options: None,
5455 env: Default::default(),
5456 language_id_overrides: Default::default(),
5457 name: None,
5458 only_features: None,
5459 except_features: None,
5460 root_markers: Default::default(),
5461 }]),
5462 );
5463
5464 lsp.insert(
5468 "vue".to_string(),
5469 LspLanguageConfig::Multi(vec![LspServerConfig {
5470 command: "vue-language-server".to_string(),
5471 args: vec!["--stdio".to_string()],
5472 enabled: true,
5473 auto_start: false,
5474 process_limits: ProcessLimits::default(),
5475 initialization_options: None,
5476 env: Default::default(),
5477 language_id_overrides: Default::default(),
5478 name: None,
5479 only_features: None,
5480 except_features: None,
5481 root_markers: Default::default(),
5482 }]),
5483 );
5484
5485 lsp.insert(
5487 "svelte".to_string(),
5488 LspLanguageConfig::Multi(vec![LspServerConfig {
5489 command: "svelteserver".to_string(),
5490 args: vec!["--stdio".to_string()],
5491 enabled: true,
5492 auto_start: false,
5493 process_limits: ProcessLimits::default(),
5494 initialization_options: None,
5495 env: Default::default(),
5496 language_id_overrides: Default::default(),
5497 name: None,
5498 only_features: None,
5499 except_features: None,
5500 root_markers: Default::default(),
5501 }]),
5502 );
5503
5504 lsp.insert(
5506 "astro".to_string(),
5507 LspLanguageConfig::Multi(vec![LspServerConfig {
5508 command: "astro-ls".to_string(),
5509 args: vec!["--stdio".to_string()],
5510 enabled: true,
5511 auto_start: false,
5512 process_limits: ProcessLimits::default(),
5513 initialization_options: None,
5514 env: Default::default(),
5515 language_id_overrides: Default::default(),
5516 name: None,
5517 only_features: None,
5518 except_features: None,
5519 root_markers: Default::default(),
5520 }]),
5521 );
5522
5523 lsp.insert(
5525 "tailwindcss".to_string(),
5526 LspLanguageConfig::Multi(vec![LspServerConfig {
5527 command: "tailwindcss-language-server".to_string(),
5528 args: vec!["--stdio".to_string()],
5529 enabled: true,
5530 auto_start: false,
5531 process_limits: ProcessLimits::default(),
5532 initialization_options: None,
5533 env: Default::default(),
5534 language_id_overrides: Default::default(),
5535 name: None,
5536 only_features: None,
5537 except_features: None,
5538 root_markers: Default::default(),
5539 }]),
5540 );
5541
5542 lsp.insert(
5547 "nix".to_string(),
5548 LspLanguageConfig::Multi(vec![LspServerConfig {
5549 command: "nil".to_string(),
5550 args: vec![],
5551 enabled: true,
5552 auto_start: false,
5553 process_limits: ProcessLimits::default(),
5554 initialization_options: None,
5555 env: Default::default(),
5556 language_id_overrides: Default::default(),
5557 name: None,
5558 only_features: None,
5559 except_features: None,
5560 root_markers: Default::default(),
5561 }]),
5562 );
5563
5564 lsp.insert(
5567 "kotlin".to_string(),
5568 LspLanguageConfig::Multi(vec![LspServerConfig {
5569 command: "kotlin-language-server".to_string(),
5570 args: vec![],
5571 enabled: true,
5572 auto_start: false,
5573 process_limits: ProcessLimits::default(),
5574 initialization_options: None,
5575 env: Default::default(),
5576 language_id_overrides: Default::default(),
5577 name: None,
5578 only_features: None,
5579 except_features: None,
5580 root_markers: Default::default(),
5581 }]),
5582 );
5583
5584 lsp.insert(
5586 "swift".to_string(),
5587 LspLanguageConfig::Multi(vec![LspServerConfig {
5588 command: "sourcekit-lsp".to_string(),
5589 args: vec![],
5590 enabled: true,
5591 auto_start: false,
5592 process_limits: ProcessLimits::default(),
5593 initialization_options: None,
5594 env: Default::default(),
5595 language_id_overrides: Default::default(),
5596 name: None,
5597 only_features: None,
5598 except_features: None,
5599 root_markers: Default::default(),
5600 }]),
5601 );
5602
5603 lsp.insert(
5606 "scala".to_string(),
5607 LspLanguageConfig::Multi(vec![LspServerConfig {
5608 command: "metals".to_string(),
5609 args: vec![],
5610 enabled: true,
5611 auto_start: false,
5612 process_limits: ProcessLimits::default(),
5613 initialization_options: None,
5614 env: Default::default(),
5615 language_id_overrides: Default::default(),
5616 name: None,
5617 only_features: None,
5618 except_features: None,
5619 root_markers: Default::default(),
5620 }]),
5621 );
5622
5623 lsp.insert(
5626 "elixir".to_string(),
5627 LspLanguageConfig::Multi(vec![LspServerConfig {
5628 command: "elixir-ls".to_string(),
5629 args: vec![],
5630 enabled: true,
5631 auto_start: false,
5632 process_limits: ProcessLimits::default(),
5633 initialization_options: None,
5634 env: Default::default(),
5635 language_id_overrides: Default::default(),
5636 name: None,
5637 only_features: None,
5638 except_features: None,
5639 root_markers: Default::default(),
5640 }]),
5641 );
5642
5643 lsp.insert(
5645 "erlang".to_string(),
5646 LspLanguageConfig::Multi(vec![LspServerConfig {
5647 command: "erlang_ls".to_string(),
5648 args: vec![],
5649 enabled: true,
5650 auto_start: false,
5651 process_limits: ProcessLimits::default(),
5652 initialization_options: None,
5653 env: Default::default(),
5654 language_id_overrides: Default::default(),
5655 name: None,
5656 only_features: None,
5657 except_features: None,
5658 root_markers: Default::default(),
5659 }]),
5660 );
5661
5662 lsp.insert(
5665 "haskell".to_string(),
5666 LspLanguageConfig::Multi(vec![LspServerConfig {
5667 command: "haskell-language-server-wrapper".to_string(),
5668 args: vec!["--lsp".to_string()],
5669 enabled: true,
5670 auto_start: false,
5671 process_limits: ProcessLimits::default(),
5672 initialization_options: None,
5673 env: Default::default(),
5674 language_id_overrides: Default::default(),
5675 name: None,
5676 only_features: None,
5677 except_features: None,
5678 root_markers: Default::default(),
5679 }]),
5680 );
5681
5682 lsp.insert(
5685 "ocaml".to_string(),
5686 LspLanguageConfig::Multi(vec![LspServerConfig {
5687 command: "ocamllsp".to_string(),
5688 args: vec![],
5689 enabled: true,
5690 auto_start: false,
5691 process_limits: ProcessLimits::default(),
5692 initialization_options: None,
5693 env: Default::default(),
5694 language_id_overrides: Default::default(),
5695 name: None,
5696 only_features: None,
5697 except_features: None,
5698 root_markers: Default::default(),
5699 }]),
5700 );
5701
5702 lsp.insert(
5705 "clojure".to_string(),
5706 LspLanguageConfig::Multi(vec![LspServerConfig {
5707 command: "clojure-lsp".to_string(),
5708 args: vec![],
5709 enabled: true,
5710 auto_start: false,
5711 process_limits: ProcessLimits::default(),
5712 initialization_options: None,
5713 env: Default::default(),
5714 language_id_overrides: Default::default(),
5715 name: None,
5716 only_features: None,
5717 except_features: None,
5718 root_markers: Default::default(),
5719 }]),
5720 );
5721
5722 lsp.insert(
5725 "r".to_string(),
5726 LspLanguageConfig::Multi(vec![LspServerConfig {
5727 command: "R".to_string(),
5728 args: vec![
5729 "--vanilla".to_string(),
5730 "-e".to_string(),
5731 "languageserver::run()".to_string(),
5732 ],
5733 enabled: true,
5734 auto_start: false,
5735 process_limits: ProcessLimits::default(),
5736 initialization_options: None,
5737 env: Default::default(),
5738 language_id_overrides: Default::default(),
5739 name: None,
5740 only_features: None,
5741 except_features: None,
5742 root_markers: Default::default(),
5743 }]),
5744 );
5745
5746 lsp.insert(
5749 "julia".to_string(),
5750 LspLanguageConfig::Multi(vec![LspServerConfig {
5751 command: "julia".to_string(),
5752 args: vec![
5753 "--startup-file=no".to_string(),
5754 "--history-file=no".to_string(),
5755 "-e".to_string(),
5756 "using LanguageServer; runserver()".to_string(),
5757 ],
5758 enabled: true,
5759 auto_start: false,
5760 process_limits: ProcessLimits::default(),
5761 initialization_options: None,
5762 env: Default::default(),
5763 language_id_overrides: Default::default(),
5764 name: None,
5765 only_features: None,
5766 except_features: None,
5767 root_markers: Default::default(),
5768 }]),
5769 );
5770
5771 lsp.insert(
5774 "perl".to_string(),
5775 LspLanguageConfig::Multi(vec![LspServerConfig {
5776 command: "perlnavigator".to_string(),
5777 args: vec!["--stdio".to_string()],
5778 enabled: true,
5779 auto_start: false,
5780 process_limits: ProcessLimits::default(),
5781 initialization_options: None,
5782 env: Default::default(),
5783 language_id_overrides: Default::default(),
5784 name: None,
5785 only_features: None,
5786 except_features: None,
5787 root_markers: Default::default(),
5788 }]),
5789 );
5790
5791 lsp.insert(
5794 "nim".to_string(),
5795 LspLanguageConfig::Multi(vec![LspServerConfig {
5796 command: "nimlangserver".to_string(),
5797 args: vec![],
5798 enabled: true,
5799 auto_start: false,
5800 process_limits: ProcessLimits::default(),
5801 initialization_options: None,
5802 env: Default::default(),
5803 language_id_overrides: Default::default(),
5804 name: None,
5805 only_features: None,
5806 except_features: None,
5807 root_markers: Default::default(),
5808 }]),
5809 );
5810
5811 lsp.insert(
5813 "gleam".to_string(),
5814 LspLanguageConfig::Multi(vec![LspServerConfig {
5815 command: "gleam".to_string(),
5816 args: vec!["lsp".to_string()],
5817 enabled: true,
5818 auto_start: false,
5819 process_limits: ProcessLimits::default(),
5820 initialization_options: None,
5821 env: Default::default(),
5822 language_id_overrides: Default::default(),
5823 name: None,
5824 only_features: None,
5825 except_features: None,
5826 root_markers: Default::default(),
5827 }]),
5828 );
5829
5830 lsp.insert(
5833 "fsharp".to_string(),
5834 LspLanguageConfig::Multi(vec![LspServerConfig {
5835 command: "fsautocomplete".to_string(),
5836 args: vec!["--adaptive-lsp-server-enabled".to_string()],
5837 enabled: true,
5838 auto_start: false,
5839 process_limits: ProcessLimits::default(),
5840 initialization_options: None,
5841 env: Default::default(),
5842 language_id_overrides: Default::default(),
5843 name: None,
5844 only_features: None,
5845 except_features: None,
5846 root_markers: Default::default(),
5847 }]),
5848 );
5849 }
5850 pub fn validate(&self) -> Result<(), ConfigError> {
5851 if self.editor.tab_size == 0 {
5853 return Err(ConfigError::ValidationError(
5854 "tab_size must be greater than 0".to_string(),
5855 ));
5856 }
5857
5858 if self.editor.scroll_offset > 100 {
5860 return Err(ConfigError::ValidationError(
5861 "scroll_offset must be <= 100".to_string(),
5862 ));
5863 }
5864
5865 for binding in &self.keybindings {
5867 if binding.key.is_empty() {
5868 return Err(ConfigError::ValidationError(
5869 "keybinding key cannot be empty".to_string(),
5870 ));
5871 }
5872 if binding.action.is_empty() {
5873 return Err(ConfigError::ValidationError(
5874 "keybinding action cannot be empty".to_string(),
5875 ));
5876 }
5877 }
5878
5879 Ok(())
5880 }
5881}
5882
5883#[derive(Debug)]
5885pub enum ConfigError {
5886 IoError(String),
5887 ParseError(String),
5888 SerializeError(String),
5889 ValidationError(String),
5890}
5891
5892impl std::fmt::Display for ConfigError {
5893 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5894 match self {
5895 Self::IoError(msg) => write!(f, "IO error: {msg}"),
5896 Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
5897 Self::SerializeError(msg) => write!(f, "Serialize error: {msg}"),
5898 Self::ValidationError(msg) => write!(f, "Validation error: {msg}"),
5899 }
5900 }
5901}
5902
5903impl std::error::Error for ConfigError {}
5904
5905#[cfg(test)]
5906mod tests {
5907 use super::*;
5908
5909 #[test]
5910 fn test_default_config() {
5911 let config = Config::default();
5912 assert_eq!(config.editor.tab_size, 4);
5913 assert!(config.editor.line_numbers);
5914 assert!(config.editor.syntax_highlighting);
5915 assert!(config.keybindings.is_empty());
5918 let resolved = config.resolve_keymap(&config.active_keybinding_map);
5920 assert!(!resolved.is_empty());
5921 }
5922
5923 #[test]
5924 fn test_all_builtin_keymaps_loadable() {
5925 for name in KeybindingMapName::BUILTIN_OPTIONS {
5926 let keymap = Config::load_builtin_keymap(name);
5927 assert!(keymap.is_some(), "Failed to load builtin keymap '{}'", name);
5928 }
5929 }
5930
5931 #[test]
5932 fn test_config_validation() {
5933 let mut config = Config::default();
5934 assert!(config.validate().is_ok());
5935
5936 config.editor.tab_size = 0;
5937 assert!(config.validate().is_err());
5938 }
5939
5940 #[test]
5941 fn test_macos_keymap_inherits_enter_bindings() {
5942 let config = Config::default();
5943 let bindings = config.resolve_keymap("macos");
5944
5945 let enter_bindings: Vec<_> = bindings.iter().filter(|b| b.key == "Enter").collect();
5946 assert!(
5947 !enter_bindings.is_empty(),
5948 "macos keymap should inherit Enter bindings from default, got {} Enter bindings",
5949 enter_bindings.len()
5950 );
5951 let has_insert_newline = enter_bindings.iter().any(|b| b.action == "insert_newline");
5953 assert!(
5954 has_insert_newline,
5955 "macos keymap should have insert_newline action for Enter key"
5956 );
5957 }
5958
5959 #[test]
5960 fn test_config_serialize_deserialize() {
5961 let config = Config::default();
5963
5964 let json = serde_json::to_string_pretty(&config).unwrap();
5966
5967 let loaded: Config = serde_json::from_str(&json).unwrap();
5969
5970 assert_eq!(config.editor.tab_size, loaded.editor.tab_size);
5971 assert_eq!(config.theme, loaded.theme);
5972 }
5973
5974 #[test]
5975 fn test_config_with_custom_keybinding() {
5976 let json = r#"{
5977 "editor": {
5978 "tab_size": 2
5979 },
5980 "keybindings": [
5981 {
5982 "key": "x",
5983 "modifiers": ["ctrl", "shift"],
5984 "action": "custom_action",
5985 "args": {},
5986 "when": null
5987 }
5988 ]
5989 }"#;
5990
5991 let config: Config = serde_json::from_str(json).unwrap();
5992 assert_eq!(config.editor.tab_size, 2);
5993 assert_eq!(config.keybindings.len(), 1);
5994 assert_eq!(config.keybindings[0].key, "x");
5995 assert_eq!(config.keybindings[0].modifiers.len(), 2);
5996 }
5997
5998 #[test]
5999 fn test_sparse_config_merges_with_defaults() {
6000 let temp_dir = tempfile::tempdir().unwrap();
6002 let config_path = temp_dir.path().join("config.json");
6003
6004 let sparse_config = r#"{
6006 "lsp": {
6007 "rust": {
6008 "command": "custom-rust-analyzer",
6009 "args": ["--custom-arg"]
6010 }
6011 }
6012 }"#;
6013 std::fs::write(&config_path, sparse_config).unwrap();
6014
6015 let loaded = Config::load_from_file(&config_path).unwrap();
6017
6018 assert!(loaded.lsp.contains_key("rust"));
6020 assert_eq!(
6021 loaded.lsp["rust"].as_slice()[0].command,
6022 "custom-rust-analyzer".to_string()
6023 );
6024
6025 assert!(
6027 loaded.lsp.contains_key("python"),
6028 "python LSP should be merged from defaults"
6029 );
6030 assert!(
6031 loaded.lsp.contains_key("typescript"),
6032 "typescript LSP should be merged from defaults"
6033 );
6034 assert!(
6035 loaded.lsp.contains_key("javascript"),
6036 "javascript LSP should be merged from defaults"
6037 );
6038
6039 assert!(loaded.languages.contains_key("rust"));
6041 assert!(loaded.languages.contains_key("python"));
6042 assert!(loaded.languages.contains_key("typescript"));
6043 }
6044
6045 #[test]
6046 fn test_empty_config_gets_all_defaults() {
6047 let temp_dir = tempfile::tempdir().unwrap();
6048 let config_path = temp_dir.path().join("config.json");
6049
6050 std::fs::write(&config_path, "{}").unwrap();
6052
6053 let loaded = Config::load_from_file(&config_path).unwrap();
6054 let defaults = Config::default();
6055
6056 assert_eq!(loaded.lsp.len(), defaults.lsp.len());
6058
6059 assert_eq!(loaded.languages.len(), defaults.languages.len());
6061 }
6062
6063 #[test]
6064 fn test_dynamic_submenu_expansion() {
6065 let temp_dir = tempfile::tempdir().unwrap();
6067 let themes_dir = temp_dir.path().to_path_buf();
6068
6069 let dynamic = MenuItem::DynamicSubmenu {
6070 label: "Test".to_string(),
6071 source: "copy_with_theme".to_string(),
6072 };
6073
6074 let expanded = dynamic.expand_dynamic(&themes_dir);
6075
6076 match expanded {
6078 MenuItem::Submenu { label, items } => {
6079 assert_eq!(label, "Test");
6080 let loader = crate::view::theme::ThemeLoader::embedded_only();
6082 let registry = loader.load_all(&[]);
6083 assert_eq!(items.len(), registry.len());
6084
6085 for (item, theme_info) in items.iter().zip(registry.list().iter()) {
6087 match item {
6088 MenuItem::Action {
6089 label,
6090 action,
6091 args,
6092 ..
6093 } => {
6094 assert_eq!(label, &theme_info.name);
6095 assert_eq!(action, "copy_with_theme");
6096 assert_eq!(
6097 args.get("theme").and_then(|v| v.as_str()),
6098 Some(theme_info.name.as_str())
6099 );
6100 }
6101 _ => panic!("Expected Action item"),
6102 }
6103 }
6104 }
6105 _ => panic!("Expected Submenu after expansion"),
6106 }
6107 }
6108
6109 #[test]
6110 fn test_non_dynamic_item_unchanged() {
6111 let temp_dir = tempfile::tempdir().unwrap();
6113 let themes_dir = temp_dir.path();
6114
6115 let action = MenuItem::Action {
6116 label: "Test".to_string(),
6117 action: "test".to_string(),
6118 args: HashMap::new(),
6119 when: None,
6120 checkbox: None,
6121 };
6122
6123 let expanded = action.expand_dynamic(themes_dir);
6124 match expanded {
6125 MenuItem::Action { label, action, .. } => {
6126 assert_eq!(label, "Test");
6127 assert_eq!(action, "test");
6128 }
6129 _ => panic!("Action should remain Action after expand_dynamic"),
6130 }
6131 }
6132
6133 #[test]
6134 fn test_buffer_config_uses_global_defaults() {
6135 let config = Config::default();
6136 let buffer_config = BufferConfig::resolve(&config, None);
6137
6138 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
6139 assert_eq!(buffer_config.auto_indent, config.editor.auto_indent);
6140 assert!(!buffer_config.use_tabs); assert!(buffer_config.whitespace.any_tabs()); assert!(buffer_config.formatter.is_none());
6143 assert!(!buffer_config.format_on_save);
6144 }
6145
6146 #[test]
6147 fn test_buffer_config_applies_language_overrides() {
6148 let mut config = Config::default();
6149
6150 config.languages.insert(
6152 "go".to_string(),
6153 LanguageConfig {
6154 extensions: vec!["go".to_string()],
6155 filenames: vec![],
6156 grammar: "go".to_string(),
6157 comment_prefix: Some("//".to_string()),
6158 auto_indent: true,
6159 auto_close: None,
6160 auto_surround: None,
6161 textmate_grammar: None,
6162 show_whitespace_tabs: false, line_wrap: None,
6164 wrap_column: None,
6165 page_view: None,
6166 page_width: None,
6167 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
6170 command: "gofmt".to_string(),
6171 args: vec![],
6172 stdin: true,
6173 timeout_ms: 10000,
6174 }),
6175 format_on_save: true,
6176 on_save: vec![],
6177 word_characters: None,
6178 },
6179 );
6180
6181 let buffer_config = BufferConfig::resolve(&config, Some("go"));
6182
6183 assert_eq!(buffer_config.tab_size, 8);
6184 assert!(buffer_config.use_tabs);
6185 assert!(!buffer_config.whitespace.any_tabs()); assert!(buffer_config.format_on_save);
6187 assert!(buffer_config.formatter.is_some());
6188 assert_eq!(buffer_config.formatter.as_ref().unwrap().command, "gofmt");
6189 }
6190
6191 #[test]
6192 fn test_buffer_config_unknown_language_uses_global() {
6193 let config = Config::default();
6194 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
6195
6196 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
6198 assert!(!buffer_config.use_tabs);
6199 }
6200
6201 #[test]
6202 fn test_buffer_config_per_language_line_wrap() {
6203 let mut config = Config::default();
6204 config.editor.line_wrap = false;
6205
6206 config.languages.insert(
6208 "markdown".to_string(),
6209 LanguageConfig {
6210 extensions: vec!["md".to_string()],
6211 line_wrap: Some(true),
6212 ..Default::default()
6213 },
6214 );
6215
6216 let md_config = BufferConfig::resolve(&config, Some("markdown"));
6218 assert!(md_config.line_wrap, "Markdown should have line_wrap=true");
6219
6220 let other_config = BufferConfig::resolve(&config, Some("rust"));
6222 assert!(
6223 !other_config.line_wrap,
6224 "Non-configured languages should use global line_wrap=false"
6225 );
6226
6227 let no_lang_config = BufferConfig::resolve(&config, None);
6229 assert!(
6230 !no_lang_config.line_wrap,
6231 "No language should use global line_wrap=false"
6232 );
6233 }
6234
6235 #[test]
6236 fn test_buffer_config_per_language_wrap_column() {
6237 let mut config = Config::default();
6238 config.editor.wrap_column = Some(120);
6239
6240 config.languages.insert(
6242 "markdown".to_string(),
6243 LanguageConfig {
6244 extensions: vec!["md".to_string()],
6245 wrap_column: Some(80),
6246 ..Default::default()
6247 },
6248 );
6249
6250 let md_config = BufferConfig::resolve(&config, Some("markdown"));
6252 assert_eq!(md_config.wrap_column, Some(80));
6253
6254 let other_config = BufferConfig::resolve(&config, Some("rust"));
6256 assert_eq!(other_config.wrap_column, Some(120));
6257
6258 let no_lang_config = BufferConfig::resolve(&config, None);
6260 assert_eq!(no_lang_config.wrap_column, Some(120));
6261 }
6262
6263 #[test]
6264 fn test_buffer_config_indent_string() {
6265 let config = Config::default();
6266
6267 let spaces_config = BufferConfig::resolve(&config, None);
6269 assert_eq!(spaces_config.indent_string(), " "); let mut config_with_tabs = Config::default();
6273 config_with_tabs.languages.insert(
6274 "makefile".to_string(),
6275 LanguageConfig {
6276 use_tabs: Some(true),
6277 tab_size: Some(8),
6278 ..Default::default()
6279 },
6280 );
6281 let tabs_config = BufferConfig::resolve(&config_with_tabs, Some("makefile"));
6282 assert_eq!(tabs_config.indent_string(), "\t");
6283 }
6284
6285 #[test]
6286 fn test_buffer_config_global_use_tabs_inherited() {
6287 let mut config = Config::default();
6290 config.editor.use_tabs = true;
6291
6292 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
6294 assert!(buffer_config.use_tabs);
6295
6296 let buffer_config = BufferConfig::resolve(&config, None);
6298 assert!(buffer_config.use_tabs);
6299
6300 config.languages.insert(
6302 "python".to_string(),
6303 LanguageConfig {
6304 use_tabs: Some(false),
6305 ..Default::default()
6306 },
6307 );
6308 let buffer_config = BufferConfig::resolve(&config, Some("python"));
6309 assert!(!buffer_config.use_tabs);
6310
6311 config.languages.insert(
6313 "rust".to_string(),
6314 LanguageConfig {
6315 use_tabs: None,
6316 ..Default::default()
6317 },
6318 );
6319 let buffer_config = BufferConfig::resolve(&config, Some("rust"));
6320 assert!(buffer_config.use_tabs);
6321 }
6322
6323 #[test]
6329 #[cfg(feature = "runtime")]
6330 fn test_lsp_languages_have_language_config() {
6331 let config = Config::default();
6332 let exceptions = ["tailwindcss"];
6333 for lsp_key in config.lsp.keys() {
6334 if exceptions.contains(&lsp_key.as_str()) {
6335 continue;
6336 }
6337 assert!(
6338 config.languages.contains_key(lsp_key),
6339 "LSP config key '{}' has no matching entry in default_languages(). \
6340 Add a LanguageConfig with the correct file extensions so detect_language() \
6341 can map files to this language.",
6342 lsp_key
6343 );
6344 }
6345 }
6346
6347 #[test]
6348 #[cfg(feature = "runtime")]
6349 fn test_default_config_has_quicklsp_in_universal_lsp() {
6350 let config = Config::default();
6351 assert!(
6352 config.universal_lsp.contains_key("quicklsp"),
6353 "Default config should contain quicklsp in universal_lsp"
6354 );
6355 let quicklsp = &config.universal_lsp["quicklsp"];
6356 let server = &quicklsp.as_slice()[0];
6357 assert_eq!(server.command, "quicklsp");
6358 assert!(!server.enabled, "quicklsp should be disabled by default");
6359 assert_eq!(server.name.as_deref(), Some("QuickLSP"));
6360 assert!(
6364 server.only_features.is_none(),
6365 "quicklsp must not default to a feature whitelist"
6366 );
6367 assert!(server.except_features.is_none());
6368 }
6369
6370 #[test]
6371 fn test_empty_config_preserves_universal_lsp_defaults() {
6372 let temp_dir = tempfile::tempdir().unwrap();
6373 let config_path = temp_dir.path().join("config.json");
6374
6375 std::fs::write(&config_path, "{}").unwrap();
6377
6378 let loaded = Config::load_from_file(&config_path).unwrap();
6379 let defaults = Config::default();
6380
6381 assert_eq!(
6383 loaded.universal_lsp.len(),
6384 defaults.universal_lsp.len(),
6385 "Empty config should preserve all default universal_lsp entries"
6386 );
6387 }
6388
6389 #[test]
6390 fn test_universal_lsp_config_merges_with_defaults() {
6391 let temp_dir = tempfile::tempdir().unwrap();
6392 let config_path = temp_dir.path().join("config.json");
6393
6394 let config_json = r#"{
6396 "universal_lsp": {
6397 "quicklsp": {
6398 "enabled": true
6399 }
6400 }
6401 }"#;
6402 std::fs::write(&config_path, config_json).unwrap();
6403
6404 let loaded = Config::load_from_file(&config_path).unwrap();
6405
6406 assert!(loaded.universal_lsp.contains_key("quicklsp"));
6408 let server = &loaded.universal_lsp["quicklsp"].as_slice()[0];
6409 assert!(server.enabled, "User override should enable quicklsp");
6410 assert_eq!(
6412 server.command, "quicklsp",
6413 "Default command should be merged when not specified by user"
6414 );
6415 }
6416
6417 #[test]
6418 fn test_universal_lsp_custom_server_added() {
6419 let temp_dir = tempfile::tempdir().unwrap();
6420 let config_path = temp_dir.path().join("config.json");
6421
6422 let config_json = r#"{
6424 "universal_lsp": {
6425 "my-custom-server": {
6426 "command": "my-server",
6427 "enabled": true,
6428 "auto_start": true
6429 }
6430 }
6431 }"#;
6432 std::fs::write(&config_path, config_json).unwrap();
6433
6434 let loaded = Config::load_from_file(&config_path).unwrap();
6435
6436 assert!(
6438 loaded.universal_lsp.contains_key("my-custom-server"),
6439 "Custom universal server should be loaded"
6440 );
6441 let server = &loaded.universal_lsp["my-custom-server"].as_slice()[0];
6442 assert_eq!(server.command, "my-server");
6443 assert!(server.enabled);
6444 assert!(server.auto_start);
6445
6446 assert!(
6448 loaded.universal_lsp.contains_key("quicklsp"),
6449 "Default quicklsp should be merged from defaults"
6450 );
6451 }
6452}