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 #[serde(default = "default_true")]
1401 pub preview_tabs: bool,
1402}
1403
1404fn default_explorer_width() -> f32 {
1405 0.3 }
1407
1408#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1419pub struct ClipboardConfig {
1420 #[serde(default = "default_true")]
1423 pub use_osc52: bool,
1424
1425 #[serde(default = "default_true")]
1428 pub use_system_clipboard: bool,
1429}
1430
1431impl Default for ClipboardConfig {
1432 fn default() -> Self {
1433 Self {
1434 use_osc52: true,
1435 use_system_clipboard: true,
1436 }
1437 }
1438}
1439
1440#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1442pub struct TerminalConfig {
1443 #[serde(default = "default_true")]
1446 pub jump_to_end_on_output: bool,
1447}
1448
1449impl Default for TerminalConfig {
1450 fn default() -> Self {
1451 Self {
1452 jump_to_end_on_output: true,
1453 }
1454 }
1455}
1456
1457#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1459pub struct WarningsConfig {
1460 #[serde(default = "default_true")]
1463 pub show_status_indicator: bool,
1464}
1465
1466impl Default for WarningsConfig {
1467 fn default() -> Self {
1468 Self {
1469 show_status_indicator: true,
1470 }
1471 }
1472}
1473
1474#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1476pub struct PackagesConfig {
1477 #[serde(default = "default_package_sources")]
1480 pub sources: Vec<String>,
1481}
1482
1483fn default_package_sources() -> Vec<String> {
1484 vec!["https://github.com/sinelaw/fresh-plugins-registry".to_string()]
1485}
1486
1487impl Default for PackagesConfig {
1488 fn default() -> Self {
1489 Self {
1490 sources: default_package_sources(),
1491 }
1492 }
1493}
1494
1495pub use fresh_core::config::PluginConfig;
1497
1498impl Default for FileExplorerConfig {
1499 fn default() -> Self {
1500 Self {
1501 respect_gitignore: true,
1502 show_hidden: false,
1503 show_gitignored: false,
1504 custom_ignore_patterns: Vec::new(),
1505 width: default_explorer_width(),
1506 preview_tabs: true,
1507 }
1508 }
1509}
1510
1511#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
1513pub struct FileBrowserConfig {
1514 #[serde(default = "default_false")]
1516 pub show_hidden: bool,
1517}
1518
1519#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1521pub struct KeyPress {
1522 pub key: String,
1524 #[serde(default)]
1526 pub modifiers: Vec<String>,
1527}
1528
1529#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1531#[schemars(extend("x-display-field" = "/action"))]
1532pub struct Keybinding {
1533 #[serde(default, skip_serializing_if = "String::is_empty")]
1535 pub key: String,
1536
1537 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1539 pub modifiers: Vec<String>,
1540
1541 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1544 pub keys: Vec<KeyPress>,
1545
1546 pub action: String,
1548
1549 #[serde(default)]
1551 pub args: HashMap<String, serde_json::Value>,
1552
1553 #[serde(default)]
1555 pub when: Option<String>,
1556}
1557
1558#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1560#[schemars(extend("x-display-field" = "/inherits"))]
1561pub struct KeymapConfig {
1562 #[serde(default, skip_serializing_if = "Option::is_none")]
1564 pub inherits: Option<String>,
1565
1566 #[serde(default)]
1568 pub bindings: Vec<Keybinding>,
1569}
1570
1571#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1573#[schemars(extend("x-display-field" = "/command"))]
1574pub struct FormatterConfig {
1575 pub command: String,
1577
1578 #[serde(default)]
1581 pub args: Vec<String>,
1582
1583 #[serde(default = "default_true")]
1586 pub stdin: bool,
1587
1588 #[serde(default = "default_on_save_timeout")]
1590 pub timeout_ms: u64,
1591}
1592
1593#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1595#[schemars(extend("x-display-field" = "/command"))]
1596pub struct OnSaveAction {
1597 pub command: String,
1600
1601 #[serde(default)]
1604 pub args: Vec<String>,
1605
1606 #[serde(default)]
1608 pub working_dir: Option<String>,
1609
1610 #[serde(default)]
1612 pub stdin: bool,
1613
1614 #[serde(default = "default_on_save_timeout")]
1616 pub timeout_ms: u64,
1617
1618 #[serde(default = "default_true")]
1621 pub enabled: bool,
1622}
1623
1624fn default_on_save_timeout() -> u64 {
1625 10000
1626}
1627
1628fn default_page_width() -> Option<usize> {
1629 Some(80)
1630}
1631
1632#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1634#[schemars(extend("x-display-field" = "/grammar"))]
1635pub struct LanguageConfig {
1636 #[serde(default)]
1638 pub extensions: Vec<String>,
1639
1640 #[serde(default)]
1642 pub filenames: Vec<String>,
1643
1644 #[serde(default)]
1646 pub grammar: String,
1647
1648 #[serde(default)]
1650 pub comment_prefix: Option<String>,
1651
1652 #[serde(default = "default_true")]
1654 pub auto_indent: bool,
1655
1656 #[serde(default)]
1659 pub auto_close: Option<bool>,
1660
1661 #[serde(default)]
1664 pub auto_surround: Option<bool>,
1665
1666 #[serde(default)]
1669 pub textmate_grammar: Option<std::path::PathBuf>,
1670
1671 #[serde(default = "default_true")]
1674 pub show_whitespace_tabs: bool,
1675
1676 #[serde(default)]
1681 pub line_wrap: Option<bool>,
1682
1683 #[serde(default)]
1686 pub wrap_column: Option<usize>,
1687
1688 #[serde(default)]
1693 pub page_view: Option<bool>,
1694
1695 #[serde(default)]
1699 pub page_width: Option<usize>,
1700
1701 #[serde(default)]
1705 pub use_tabs: Option<bool>,
1706
1707 #[serde(default)]
1710 pub tab_size: Option<usize>,
1711
1712 #[serde(default)]
1714 pub formatter: Option<FormatterConfig>,
1715
1716 #[serde(default)]
1718 pub format_on_save: bool,
1719
1720 #[serde(default)]
1724 pub on_save: Vec<OnSaveAction>,
1725
1726 #[serde(default)]
1736 pub word_characters: Option<String>,
1737}
1738
1739#[derive(Debug, Clone)]
1746pub struct BufferConfig {
1747 pub tab_size: usize,
1749
1750 pub use_tabs: bool,
1752
1753 pub auto_indent: bool,
1755
1756 pub auto_close: bool,
1758
1759 pub auto_surround: bool,
1761
1762 pub line_wrap: bool,
1764
1765 pub wrap_column: Option<usize>,
1767
1768 pub whitespace: WhitespaceVisibility,
1770
1771 pub formatter: Option<FormatterConfig>,
1773
1774 pub format_on_save: bool,
1776
1777 pub on_save: Vec<OnSaveAction>,
1779
1780 pub textmate_grammar: Option<std::path::PathBuf>,
1782
1783 pub word_characters: String,
1786}
1787
1788impl BufferConfig {
1789 pub fn resolve(global_config: &Config, language_id: Option<&str>) -> Self {
1798 let editor = &global_config.editor;
1799
1800 let mut whitespace = WhitespaceVisibility::from_editor_config(editor);
1802 let mut config = BufferConfig {
1803 tab_size: editor.tab_size,
1804 use_tabs: editor.use_tabs,
1805 auto_indent: editor.auto_indent,
1806 auto_close: editor.auto_close,
1807 auto_surround: editor.auto_surround,
1808 line_wrap: editor.line_wrap,
1809 wrap_column: editor.wrap_column,
1810 whitespace,
1811 formatter: None,
1812 format_on_save: false,
1813 on_save: Vec::new(),
1814 textmate_grammar: None,
1815 word_characters: String::new(),
1816 };
1817
1818 let lang_config_ref = language_id
1822 .and_then(|id| global_config.languages.get(id))
1823 .or_else(|| {
1824 match language_id {
1826 None | Some("text") => global_config
1827 .default_language
1828 .as_deref()
1829 .and_then(|lang| global_config.languages.get(lang)),
1830 _ => None,
1831 }
1832 });
1833 if let Some(lang_config) = lang_config_ref {
1834 if let Some(ts) = lang_config.tab_size {
1836 config.tab_size = ts;
1837 }
1838
1839 if let Some(use_tabs) = lang_config.use_tabs {
1841 config.use_tabs = use_tabs;
1842 }
1843
1844 if let Some(line_wrap) = lang_config.line_wrap {
1846 config.line_wrap = line_wrap;
1847 }
1848
1849 if lang_config.wrap_column.is_some() {
1851 config.wrap_column = lang_config.wrap_column;
1852 }
1853
1854 config.auto_indent = lang_config.auto_indent;
1856
1857 if config.auto_close {
1859 if let Some(lang_auto_close) = lang_config.auto_close {
1860 config.auto_close = lang_auto_close;
1861 }
1862 }
1863
1864 if config.auto_surround {
1866 if let Some(lang_auto_surround) = lang_config.auto_surround {
1867 config.auto_surround = lang_auto_surround;
1868 }
1869 }
1870
1871 whitespace = whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
1873 config.whitespace = whitespace;
1874
1875 config.formatter = lang_config.formatter.clone();
1877
1878 config.format_on_save = lang_config.format_on_save;
1880
1881 config.on_save = lang_config.on_save.clone();
1883
1884 config.textmate_grammar = lang_config.textmate_grammar.clone();
1886
1887 if let Some(ref wc) = lang_config.word_characters {
1889 config.word_characters = wc.clone();
1890 }
1891 }
1892
1893 config
1894 }
1895
1896 pub fn indent_string(&self) -> String {
1901 if self.use_tabs {
1902 "\t".to_string()
1903 } else {
1904 " ".repeat(self.tab_size)
1905 }
1906 }
1907}
1908
1909#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
1911pub struct MenuConfig {
1912 #[serde(default)]
1914 pub menus: Vec<Menu>,
1915}
1916
1917pub use fresh_core::menu::{Menu, MenuItem};
1919
1920pub trait MenuExt {
1922 fn match_id(&self) -> &str;
1925
1926 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path);
1929}
1930
1931impl MenuExt for Menu {
1932 fn match_id(&self) -> &str {
1933 self.id.as_deref().unwrap_or(&self.label)
1934 }
1935
1936 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path) {
1937 self.items = self
1938 .items
1939 .iter()
1940 .map(|item| item.expand_dynamic(themes_dir))
1941 .collect();
1942 }
1943}
1944
1945pub trait MenuItemExt {
1947 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem;
1950}
1951
1952impl MenuItemExt for MenuItem {
1953 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem {
1954 match self {
1955 MenuItem::DynamicSubmenu { label, source } => {
1956 let items = generate_dynamic_items(source, themes_dir);
1957 MenuItem::Submenu {
1958 label: label.clone(),
1959 items,
1960 }
1961 }
1962 other => other.clone(),
1963 }
1964 }
1965}
1966
1967#[cfg(feature = "runtime")]
1969pub fn generate_dynamic_items(source: &str, themes_dir: &std::path::Path) -> Vec<MenuItem> {
1970 match source {
1971 "copy_with_theme" => {
1972 let loader = crate::view::theme::ThemeLoader::new(themes_dir.to_path_buf());
1974 let registry = loader.load_all(&[]);
1975 registry
1976 .list()
1977 .iter()
1978 .map(|info| {
1979 let mut args = HashMap::new();
1980 args.insert("theme".to_string(), serde_json::json!(info.key));
1981 MenuItem::Action {
1982 label: info.name.clone(),
1983 action: "copy_with_theme".to_string(),
1984 args,
1985 when: Some(context_keys::HAS_SELECTION.to_string()),
1986 checkbox: None,
1987 }
1988 })
1989 .collect()
1990 }
1991 _ => vec![MenuItem::Label {
1992 info: format!("Unknown source: {}", source),
1993 }],
1994 }
1995}
1996
1997#[cfg(not(feature = "runtime"))]
1999pub fn generate_dynamic_items(_source: &str, _themes_dir: &std::path::Path) -> Vec<MenuItem> {
2000 vec![]
2002}
2003
2004impl Default for Config {
2005 fn default() -> Self {
2006 Self {
2007 version: 0,
2008 theme: default_theme_name(),
2009 locale: LocaleName::default(),
2010 check_for_updates: true,
2011 editor: EditorConfig::default(),
2012 file_explorer: FileExplorerConfig::default(),
2013 file_browser: FileBrowserConfig::default(),
2014 clipboard: ClipboardConfig::default(),
2015 terminal: TerminalConfig::default(),
2016 keybindings: vec![], keybinding_maps: HashMap::new(), active_keybinding_map: default_keybinding_map_name(),
2019 languages: Self::default_languages(),
2020 default_language: None,
2021 lsp: Self::default_lsp_config(),
2022 universal_lsp: Self::default_universal_lsp_config(),
2023 warnings: WarningsConfig::default(),
2024 plugins: HashMap::new(), packages: PackagesConfig::default(),
2026 }
2027 }
2028}
2029
2030impl MenuConfig {
2031 pub fn translated() -> Self {
2033 Self {
2034 menus: Self::translated_menus(),
2035 }
2036 }
2037
2038 pub fn translated_menus() -> Vec<Menu> {
2044 vec![
2045 Menu {
2047 id: Some("File".to_string()),
2048 label: t!("menu.file").to_string(),
2049 when: None,
2050 items: vec![
2051 MenuItem::Action {
2052 label: t!("menu.file.new_file").to_string(),
2053 action: "new".to_string(),
2054 args: HashMap::new(),
2055 when: None,
2056 checkbox: None,
2057 },
2058 MenuItem::Action {
2059 label: t!("menu.file.open_file").to_string(),
2060 action: "open".to_string(),
2061 args: HashMap::new(),
2062 when: None,
2063 checkbox: None,
2064 },
2065 MenuItem::Separator { separator: true },
2066 MenuItem::Action {
2067 label: t!("menu.file.save").to_string(),
2068 action: "save".to_string(),
2069 args: HashMap::new(),
2070 when: None,
2071 checkbox: None,
2072 },
2073 MenuItem::Action {
2074 label: t!("menu.file.save_as").to_string(),
2075 action: "save_as".to_string(),
2076 args: HashMap::new(),
2077 when: None,
2078 checkbox: None,
2079 },
2080 MenuItem::Action {
2081 label: t!("menu.file.revert").to_string(),
2082 action: "revert".to_string(),
2083 args: HashMap::new(),
2084 when: None,
2085 checkbox: None,
2086 },
2087 MenuItem::Action {
2088 label: t!("menu.file.reload_with_encoding").to_string(),
2089 action: "reload_with_encoding".to_string(),
2090 args: HashMap::new(),
2091 when: None,
2092 checkbox: None,
2093 },
2094 MenuItem::Separator { separator: true },
2095 MenuItem::Action {
2096 label: t!("menu.file.close_buffer").to_string(),
2097 action: "close".to_string(),
2098 args: HashMap::new(),
2099 when: None,
2100 checkbox: None,
2101 },
2102 MenuItem::Separator { separator: true },
2103 MenuItem::Action {
2104 label: t!("menu.file.switch_project").to_string(),
2105 action: "switch_project".to_string(),
2106 args: HashMap::new(),
2107 when: None,
2108 checkbox: None,
2109 },
2110 MenuItem::Separator { separator: true },
2111 MenuItem::Action {
2112 label: t!("menu.file.detach").to_string(),
2113 action: "detach".to_string(),
2114 args: HashMap::new(),
2115 when: Some(context_keys::SESSION_MODE.to_string()),
2116 checkbox: None,
2117 },
2118 MenuItem::Action {
2119 label: t!("menu.file.quit").to_string(),
2120 action: "quit".to_string(),
2121 args: HashMap::new(),
2122 when: None,
2123 checkbox: None,
2124 },
2125 ],
2126 },
2127 Menu {
2129 id: Some("Edit".to_string()),
2130 label: t!("menu.edit").to_string(),
2131 when: None,
2132 items: vec![
2133 MenuItem::Action {
2134 label: t!("menu.edit.undo").to_string(),
2135 action: "undo".to_string(),
2136 args: HashMap::new(),
2137 when: None,
2138 checkbox: None,
2139 },
2140 MenuItem::Action {
2141 label: t!("menu.edit.redo").to_string(),
2142 action: "redo".to_string(),
2143 args: HashMap::new(),
2144 when: None,
2145 checkbox: None,
2146 },
2147 MenuItem::Separator { separator: true },
2148 MenuItem::Action {
2149 label: t!("menu.edit.cut").to_string(),
2150 action: "cut".to_string(),
2151 args: HashMap::new(),
2152 when: Some(context_keys::HAS_SELECTION.to_string()),
2153 checkbox: None,
2154 },
2155 MenuItem::Action {
2156 label: t!("menu.edit.copy").to_string(),
2157 action: "copy".to_string(),
2158 args: HashMap::new(),
2159 when: Some(context_keys::HAS_SELECTION.to_string()),
2160 checkbox: None,
2161 },
2162 MenuItem::DynamicSubmenu {
2163 label: t!("menu.edit.copy_with_formatting").to_string(),
2164 source: "copy_with_theme".to_string(),
2165 },
2166 MenuItem::Action {
2167 label: t!("menu.edit.paste").to_string(),
2168 action: "paste".to_string(),
2169 args: HashMap::new(),
2170 when: None,
2171 checkbox: None,
2172 },
2173 MenuItem::Separator { separator: true },
2174 MenuItem::Action {
2175 label: t!("menu.edit.select_all").to_string(),
2176 action: "select_all".to_string(),
2177 args: HashMap::new(),
2178 when: None,
2179 checkbox: None,
2180 },
2181 MenuItem::Separator { separator: true },
2182 MenuItem::Action {
2183 label: t!("menu.edit.find").to_string(),
2184 action: "search".to_string(),
2185 args: HashMap::new(),
2186 when: None,
2187 checkbox: None,
2188 },
2189 MenuItem::Action {
2190 label: t!("menu.edit.find_in_selection").to_string(),
2191 action: "find_in_selection".to_string(),
2192 args: HashMap::new(),
2193 when: Some(context_keys::HAS_SELECTION.to_string()),
2194 checkbox: None,
2195 },
2196 MenuItem::Action {
2197 label: t!("menu.edit.find_next").to_string(),
2198 action: "find_next".to_string(),
2199 args: HashMap::new(),
2200 when: None,
2201 checkbox: None,
2202 },
2203 MenuItem::Action {
2204 label: t!("menu.edit.find_previous").to_string(),
2205 action: "find_previous".to_string(),
2206 args: HashMap::new(),
2207 when: None,
2208 checkbox: None,
2209 },
2210 MenuItem::Action {
2211 label: t!("menu.edit.replace").to_string(),
2212 action: "query_replace".to_string(),
2213 args: HashMap::new(),
2214 when: None,
2215 checkbox: None,
2216 },
2217 MenuItem::Separator { separator: true },
2218 MenuItem::Action {
2219 label: t!("menu.edit.delete_line").to_string(),
2220 action: "delete_line".to_string(),
2221 args: HashMap::new(),
2222 when: None,
2223 checkbox: None,
2224 },
2225 MenuItem::Action {
2226 label: t!("menu.edit.format_buffer").to_string(),
2227 action: "format_buffer".to_string(),
2228 args: HashMap::new(),
2229 when: Some(context_keys::FORMATTER_AVAILABLE.to_string()),
2230 checkbox: None,
2231 },
2232 MenuItem::Separator { separator: true },
2233 MenuItem::Action {
2234 label: t!("menu.edit.settings").to_string(),
2235 action: "open_settings".to_string(),
2236 args: HashMap::new(),
2237 when: None,
2238 checkbox: None,
2239 },
2240 MenuItem::Action {
2241 label: t!("menu.edit.keybinding_editor").to_string(),
2242 action: "open_keybinding_editor".to_string(),
2243 args: HashMap::new(),
2244 when: None,
2245 checkbox: None,
2246 },
2247 ],
2248 },
2249 Menu {
2251 id: Some("View".to_string()),
2252 label: t!("menu.view").to_string(),
2253 when: None,
2254 items: vec![
2255 MenuItem::Action {
2256 label: t!("menu.view.file_explorer").to_string(),
2257 action: "toggle_file_explorer".to_string(),
2258 args: HashMap::new(),
2259 when: None,
2260 checkbox: Some(context_keys::FILE_EXPLORER.to_string()),
2261 },
2262 MenuItem::Separator { separator: true },
2263 MenuItem::Action {
2264 label: t!("menu.view.line_numbers").to_string(),
2265 action: "toggle_line_numbers".to_string(),
2266 args: HashMap::new(),
2267 when: None,
2268 checkbox: Some(context_keys::LINE_NUMBERS.to_string()),
2269 },
2270 MenuItem::Action {
2271 label: t!("menu.view.line_wrap").to_string(),
2272 action: "toggle_line_wrap".to_string(),
2273 args: HashMap::new(),
2274 when: None,
2275 checkbox: Some(context_keys::LINE_WRAP.to_string()),
2276 },
2277 MenuItem::Action {
2278 label: t!("menu.view.mouse_support").to_string(),
2279 action: "toggle_mouse_capture".to_string(),
2280 args: HashMap::new(),
2281 when: None,
2282 checkbox: Some(context_keys::MOUSE_CAPTURE.to_string()),
2283 },
2284 MenuItem::Separator { separator: true },
2285 MenuItem::Action {
2286 label: t!("menu.view.vertical_scrollbar").to_string(),
2287 action: "toggle_vertical_scrollbar".to_string(),
2288 args: HashMap::new(),
2289 when: None,
2290 checkbox: Some(context_keys::VERTICAL_SCROLLBAR.to_string()),
2291 },
2292 MenuItem::Action {
2293 label: t!("menu.view.horizontal_scrollbar").to_string(),
2294 action: "toggle_horizontal_scrollbar".to_string(),
2295 args: HashMap::new(),
2296 when: None,
2297 checkbox: Some(context_keys::HORIZONTAL_SCROLLBAR.to_string()),
2298 },
2299 MenuItem::Separator { separator: true },
2300 MenuItem::Action {
2301 label: t!("menu.view.set_background").to_string(),
2302 action: "set_background".to_string(),
2303 args: HashMap::new(),
2304 when: None,
2305 checkbox: None,
2306 },
2307 MenuItem::Action {
2308 label: t!("menu.view.set_background_blend").to_string(),
2309 action: "set_background_blend".to_string(),
2310 args: HashMap::new(),
2311 when: None,
2312 checkbox: None,
2313 },
2314 MenuItem::Action {
2315 label: t!("menu.view.set_page_width").to_string(),
2316 action: "set_page_width".to_string(),
2317 args: HashMap::new(),
2318 when: None,
2319 checkbox: None,
2320 },
2321 MenuItem::Separator { separator: true },
2322 MenuItem::Action {
2323 label: t!("menu.view.select_theme").to_string(),
2324 action: "select_theme".to_string(),
2325 args: HashMap::new(),
2326 when: None,
2327 checkbox: None,
2328 },
2329 MenuItem::Action {
2330 label: t!("menu.view.select_locale").to_string(),
2331 action: "select_locale".to_string(),
2332 args: HashMap::new(),
2333 when: None,
2334 checkbox: None,
2335 },
2336 MenuItem::Action {
2337 label: t!("menu.view.settings").to_string(),
2338 action: "open_settings".to_string(),
2339 args: HashMap::new(),
2340 when: None,
2341 checkbox: None,
2342 },
2343 MenuItem::Action {
2344 label: t!("menu.view.calibrate_input").to_string(),
2345 action: "calibrate_input".to_string(),
2346 args: HashMap::new(),
2347 when: None,
2348 checkbox: None,
2349 },
2350 MenuItem::Separator { separator: true },
2351 MenuItem::Action {
2352 label: t!("menu.view.split_horizontal").to_string(),
2353 action: "split_horizontal".to_string(),
2354 args: HashMap::new(),
2355 when: None,
2356 checkbox: None,
2357 },
2358 MenuItem::Action {
2359 label: t!("menu.view.split_vertical").to_string(),
2360 action: "split_vertical".to_string(),
2361 args: HashMap::new(),
2362 when: None,
2363 checkbox: None,
2364 },
2365 MenuItem::Action {
2366 label: t!("menu.view.close_split").to_string(),
2367 action: "close_split".to_string(),
2368 args: HashMap::new(),
2369 when: None,
2370 checkbox: None,
2371 },
2372 MenuItem::Action {
2373 label: t!("menu.view.scroll_sync").to_string(),
2374 action: "toggle_scroll_sync".to_string(),
2375 args: HashMap::new(),
2376 when: Some(context_keys::HAS_SAME_BUFFER_SPLITS.to_string()),
2377 checkbox: Some(context_keys::SCROLL_SYNC.to_string()),
2378 },
2379 MenuItem::Action {
2380 label: t!("menu.view.focus_next_split").to_string(),
2381 action: "next_split".to_string(),
2382 args: HashMap::new(),
2383 when: None,
2384 checkbox: None,
2385 },
2386 MenuItem::Action {
2387 label: t!("menu.view.focus_prev_split").to_string(),
2388 action: "prev_split".to_string(),
2389 args: HashMap::new(),
2390 when: None,
2391 checkbox: None,
2392 },
2393 MenuItem::Action {
2394 label: t!("menu.view.toggle_maximize_split").to_string(),
2395 action: "toggle_maximize_split".to_string(),
2396 args: HashMap::new(),
2397 when: None,
2398 checkbox: None,
2399 },
2400 MenuItem::Separator { separator: true },
2401 MenuItem::Submenu {
2402 label: t!("menu.terminal").to_string(),
2403 items: vec![
2404 MenuItem::Action {
2405 label: t!("menu.terminal.open").to_string(),
2406 action: "open_terminal".to_string(),
2407 args: HashMap::new(),
2408 when: None,
2409 checkbox: None,
2410 },
2411 MenuItem::Action {
2412 label: t!("menu.terminal.close").to_string(),
2413 action: "close_terminal".to_string(),
2414 args: HashMap::new(),
2415 when: None,
2416 checkbox: None,
2417 },
2418 MenuItem::Separator { separator: true },
2419 MenuItem::Action {
2420 label: t!("menu.terminal.toggle_keyboard_capture").to_string(),
2421 action: "toggle_keyboard_capture".to_string(),
2422 args: HashMap::new(),
2423 when: None,
2424 checkbox: None,
2425 },
2426 ],
2427 },
2428 MenuItem::Separator { separator: true },
2429 MenuItem::Submenu {
2430 label: t!("menu.view.keybinding_style").to_string(),
2431 items: vec![
2432 MenuItem::Action {
2433 label: t!("menu.view.keybinding_default").to_string(),
2434 action: "switch_keybinding_map".to_string(),
2435 args: {
2436 let mut map = HashMap::new();
2437 map.insert("map".to_string(), serde_json::json!("default"));
2438 map
2439 },
2440 when: None,
2441 checkbox: Some(context_keys::KEYMAP_DEFAULT.to_string()),
2442 },
2443 MenuItem::Action {
2444 label: t!("menu.view.keybinding_emacs").to_string(),
2445 action: "switch_keybinding_map".to_string(),
2446 args: {
2447 let mut map = HashMap::new();
2448 map.insert("map".to_string(), serde_json::json!("emacs"));
2449 map
2450 },
2451 when: None,
2452 checkbox: Some(context_keys::KEYMAP_EMACS.to_string()),
2453 },
2454 MenuItem::Action {
2455 label: t!("menu.view.keybinding_vscode").to_string(),
2456 action: "switch_keybinding_map".to_string(),
2457 args: {
2458 let mut map = HashMap::new();
2459 map.insert("map".to_string(), serde_json::json!("vscode"));
2460 map
2461 },
2462 when: None,
2463 checkbox: Some(context_keys::KEYMAP_VSCODE.to_string()),
2464 },
2465 MenuItem::Action {
2466 label: "macOS GUI (⌘)".to_string(),
2467 action: "switch_keybinding_map".to_string(),
2468 args: {
2469 let mut map = HashMap::new();
2470 map.insert("map".to_string(), serde_json::json!("macos-gui"));
2471 map
2472 },
2473 when: None,
2474 checkbox: Some(context_keys::KEYMAP_MACOS_GUI.to_string()),
2475 },
2476 ],
2477 },
2478 ],
2479 },
2480 Menu {
2482 id: Some("Selection".to_string()),
2483 label: t!("menu.selection").to_string(),
2484 when: None,
2485 items: vec![
2486 MenuItem::Action {
2487 label: t!("menu.selection.select_all").to_string(),
2488 action: "select_all".to_string(),
2489 args: HashMap::new(),
2490 when: None,
2491 checkbox: None,
2492 },
2493 MenuItem::Action {
2494 label: t!("menu.selection.select_word").to_string(),
2495 action: "select_word".to_string(),
2496 args: HashMap::new(),
2497 when: None,
2498 checkbox: None,
2499 },
2500 MenuItem::Action {
2501 label: t!("menu.selection.select_line").to_string(),
2502 action: "select_line".to_string(),
2503 args: HashMap::new(),
2504 when: None,
2505 checkbox: None,
2506 },
2507 MenuItem::Action {
2508 label: t!("menu.selection.expand_selection").to_string(),
2509 action: "expand_selection".to_string(),
2510 args: HashMap::new(),
2511 when: None,
2512 checkbox: None,
2513 },
2514 MenuItem::Separator { separator: true },
2515 MenuItem::Action {
2516 label: t!("menu.selection.add_cursor_above").to_string(),
2517 action: "add_cursor_above".to_string(),
2518 args: HashMap::new(),
2519 when: None,
2520 checkbox: None,
2521 },
2522 MenuItem::Action {
2523 label: t!("menu.selection.add_cursor_below").to_string(),
2524 action: "add_cursor_below".to_string(),
2525 args: HashMap::new(),
2526 when: None,
2527 checkbox: None,
2528 },
2529 MenuItem::Action {
2530 label: t!("menu.selection.add_cursor_next_match").to_string(),
2531 action: "add_cursor_next_match".to_string(),
2532 args: HashMap::new(),
2533 when: None,
2534 checkbox: None,
2535 },
2536 MenuItem::Action {
2537 label: t!("menu.selection.remove_secondary_cursors").to_string(),
2538 action: "remove_secondary_cursors".to_string(),
2539 args: HashMap::new(),
2540 when: None,
2541 checkbox: None,
2542 },
2543 ],
2544 },
2545 Menu {
2547 id: Some("Go".to_string()),
2548 label: t!("menu.go").to_string(),
2549 when: None,
2550 items: vec![
2551 MenuItem::Action {
2552 label: t!("menu.go.goto_line").to_string(),
2553 action: "goto_line".to_string(),
2554 args: HashMap::new(),
2555 when: None,
2556 checkbox: None,
2557 },
2558 MenuItem::Action {
2559 label: t!("menu.go.goto_definition").to_string(),
2560 action: "lsp_goto_definition".to_string(),
2561 args: HashMap::new(),
2562 when: None,
2563 checkbox: None,
2564 },
2565 MenuItem::Action {
2566 label: t!("menu.go.find_references").to_string(),
2567 action: "lsp_references".to_string(),
2568 args: HashMap::new(),
2569 when: None,
2570 checkbox: None,
2571 },
2572 MenuItem::Separator { separator: true },
2573 MenuItem::Action {
2574 label: t!("menu.go.next_buffer").to_string(),
2575 action: "next_buffer".to_string(),
2576 args: HashMap::new(),
2577 when: None,
2578 checkbox: None,
2579 },
2580 MenuItem::Action {
2581 label: t!("menu.go.prev_buffer").to_string(),
2582 action: "prev_buffer".to_string(),
2583 args: HashMap::new(),
2584 when: None,
2585 checkbox: None,
2586 },
2587 MenuItem::Separator { separator: true },
2588 MenuItem::Action {
2589 label: t!("menu.go.command_palette").to_string(),
2590 action: "command_palette".to_string(),
2591 args: HashMap::new(),
2592 when: None,
2593 checkbox: None,
2594 },
2595 ],
2596 },
2597 Menu {
2599 id: Some("LSP".to_string()),
2600 label: t!("menu.lsp").to_string(),
2601 when: None,
2602 items: vec![
2603 MenuItem::Action {
2604 label: t!("menu.lsp.show_hover").to_string(),
2605 action: "lsp_hover".to_string(),
2606 args: HashMap::new(),
2607 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2608 checkbox: None,
2609 },
2610 MenuItem::Action {
2611 label: t!("menu.lsp.goto_definition").to_string(),
2612 action: "lsp_goto_definition".to_string(),
2613 args: HashMap::new(),
2614 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2615 checkbox: None,
2616 },
2617 MenuItem::Action {
2618 label: t!("menu.lsp.find_references").to_string(),
2619 action: "lsp_references".to_string(),
2620 args: HashMap::new(),
2621 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2622 checkbox: None,
2623 },
2624 MenuItem::Action {
2625 label: t!("menu.lsp.rename_symbol").to_string(),
2626 action: "lsp_rename".to_string(),
2627 args: HashMap::new(),
2628 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2629 checkbox: None,
2630 },
2631 MenuItem::Separator { separator: true },
2632 MenuItem::Action {
2633 label: t!("menu.lsp.show_completions").to_string(),
2634 action: "lsp_completion".to_string(),
2635 args: HashMap::new(),
2636 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2637 checkbox: None,
2638 },
2639 MenuItem::Action {
2640 label: t!("menu.lsp.show_signature").to_string(),
2641 action: "lsp_signature_help".to_string(),
2642 args: HashMap::new(),
2643 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2644 checkbox: None,
2645 },
2646 MenuItem::Action {
2647 label: t!("menu.lsp.code_actions").to_string(),
2648 action: "lsp_code_actions".to_string(),
2649 args: HashMap::new(),
2650 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2651 checkbox: None,
2652 },
2653 MenuItem::Separator { separator: true },
2654 MenuItem::Action {
2655 label: t!("menu.lsp.toggle_inlay_hints").to_string(),
2656 action: "toggle_inlay_hints".to_string(),
2657 args: HashMap::new(),
2658 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2659 checkbox: Some(context_keys::INLAY_HINTS.to_string()),
2660 },
2661 MenuItem::Action {
2662 label: t!("menu.lsp.toggle_mouse_hover").to_string(),
2663 action: "toggle_mouse_hover".to_string(),
2664 args: HashMap::new(),
2665 when: None,
2666 checkbox: Some(context_keys::MOUSE_HOVER.to_string()),
2667 },
2668 MenuItem::Separator { separator: true },
2669 MenuItem::Action {
2670 label: t!("menu.lsp.restart_server").to_string(),
2671 action: "lsp_restart".to_string(),
2672 args: HashMap::new(),
2673 when: None,
2674 checkbox: None,
2675 },
2676 MenuItem::Action {
2677 label: t!("menu.lsp.stop_server").to_string(),
2678 action: "lsp_stop".to_string(),
2679 args: HashMap::new(),
2680 when: None,
2681 checkbox: None,
2682 },
2683 MenuItem::Separator { separator: true },
2684 MenuItem::Action {
2685 label: t!("menu.lsp.toggle_for_buffer").to_string(),
2686 action: "lsp_toggle_for_buffer".to_string(),
2687 args: HashMap::new(),
2688 when: None,
2689 checkbox: None,
2690 },
2691 ],
2692 },
2693 Menu {
2695 id: Some("Explorer".to_string()),
2696 label: t!("menu.explorer").to_string(),
2697 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
2698 items: vec![
2699 MenuItem::Action {
2700 label: t!("menu.explorer.new_file").to_string(),
2701 action: "file_explorer_new_file".to_string(),
2702 args: HashMap::new(),
2703 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
2704 checkbox: None,
2705 },
2706 MenuItem::Action {
2707 label: t!("menu.explorer.new_folder").to_string(),
2708 action: "file_explorer_new_directory".to_string(),
2709 args: HashMap::new(),
2710 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
2711 checkbox: None,
2712 },
2713 MenuItem::Separator { separator: true },
2714 MenuItem::Action {
2715 label: t!("menu.explorer.open").to_string(),
2716 action: "file_explorer_open".to_string(),
2717 args: HashMap::new(),
2718 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
2719 checkbox: None,
2720 },
2721 MenuItem::Action {
2722 label: t!("menu.explorer.rename").to_string(),
2723 action: "file_explorer_rename".to_string(),
2724 args: HashMap::new(),
2725 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
2726 checkbox: None,
2727 },
2728 MenuItem::Action {
2729 label: t!("menu.explorer.delete").to_string(),
2730 action: "file_explorer_delete".to_string(),
2731 args: HashMap::new(),
2732 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
2733 checkbox: None,
2734 },
2735 MenuItem::Separator { separator: true },
2736 MenuItem::Action {
2737 label: t!("menu.explorer.refresh").to_string(),
2738 action: "file_explorer_refresh".to_string(),
2739 args: HashMap::new(),
2740 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
2741 checkbox: None,
2742 },
2743 MenuItem::Separator { separator: true },
2744 MenuItem::Action {
2745 label: t!("menu.explorer.show_hidden").to_string(),
2746 action: "file_explorer_toggle_hidden".to_string(),
2747 args: HashMap::new(),
2748 when: Some(context_keys::FILE_EXPLORER.to_string()),
2749 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_HIDDEN.to_string()),
2750 },
2751 MenuItem::Action {
2752 label: t!("menu.explorer.show_gitignored").to_string(),
2753 action: "file_explorer_toggle_gitignored".to_string(),
2754 args: HashMap::new(),
2755 when: Some(context_keys::FILE_EXPLORER.to_string()),
2756 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_GITIGNORED.to_string()),
2757 },
2758 ],
2759 },
2760 Menu {
2762 id: Some("Help".to_string()),
2763 label: t!("menu.help").to_string(),
2764 when: None,
2765 items: vec![
2766 MenuItem::Label {
2767 info: format!("Fresh v{}", env!("CARGO_PKG_VERSION")),
2768 },
2769 MenuItem::Separator { separator: true },
2770 MenuItem::Action {
2771 label: t!("menu.help.show_manual").to_string(),
2772 action: "show_help".to_string(),
2773 args: HashMap::new(),
2774 when: None,
2775 checkbox: None,
2776 },
2777 MenuItem::Action {
2778 label: t!("menu.help.keyboard_shortcuts").to_string(),
2779 action: "keyboard_shortcuts".to_string(),
2780 args: HashMap::new(),
2781 when: None,
2782 checkbox: None,
2783 },
2784 MenuItem::Separator { separator: true },
2785 MenuItem::Action {
2786 label: t!("menu.help.event_debug").to_string(),
2787 action: "event_debug".to_string(),
2788 args: HashMap::new(),
2789 when: None,
2790 checkbox: None,
2791 },
2792 ],
2793 },
2794 ]
2795 }
2796}
2797
2798impl Config {
2799 pub(crate) const FILENAME: &'static str = "config.json";
2801
2802 pub(crate) fn local_config_path(working_dir: &Path) -> std::path::PathBuf {
2804 working_dir.join(Self::FILENAME)
2805 }
2806
2807 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
2813 let contents = std::fs::read_to_string(path.as_ref())
2814 .map_err(|e| ConfigError::IoError(e.to_string()))?;
2815
2816 let partial: crate::partial_config::PartialConfig =
2818 serde_json::from_str(&contents).map_err(|e| ConfigError::ParseError(e.to_string()))?;
2819
2820 Ok(partial.resolve())
2821 }
2822
2823 fn load_builtin_keymap(name: &str) -> Option<KeymapConfig> {
2825 let json_content = match name {
2826 "default" => include_str!("../keymaps/default.json"),
2827 "emacs" => include_str!("../keymaps/emacs.json"),
2828 "vscode" => include_str!("../keymaps/vscode.json"),
2829 "macos" => include_str!("../keymaps/macos.json"),
2830 "macos-gui" => include_str!("../keymaps/macos-gui.json"),
2831 _ => return None,
2832 };
2833
2834 match serde_json::from_str(json_content) {
2835 Ok(config) => Some(config),
2836 Err(e) => {
2837 eprintln!("Failed to parse builtin keymap '{}': {}", name, e);
2838 None
2839 }
2840 }
2841 }
2842
2843 pub fn resolve_keymap(&self, map_name: &str) -> Vec<Keybinding> {
2846 let mut visited = std::collections::HashSet::new();
2847 self.resolve_keymap_recursive(map_name, &mut visited)
2848 }
2849
2850 fn resolve_keymap_recursive(
2852 &self,
2853 map_name: &str,
2854 visited: &mut std::collections::HashSet<String>,
2855 ) -> Vec<Keybinding> {
2856 if visited.contains(map_name) {
2858 eprintln!(
2859 "Warning: Circular inheritance detected in keymap '{}'",
2860 map_name
2861 );
2862 return Vec::new();
2863 }
2864 visited.insert(map_name.to_string());
2865
2866 let keymap = self
2868 .keybinding_maps
2869 .get(map_name)
2870 .cloned()
2871 .or_else(|| Self::load_builtin_keymap(map_name));
2872
2873 let Some(keymap) = keymap else {
2874 return Vec::new();
2875 };
2876
2877 let mut all_bindings = if let Some(ref parent_name) = keymap.inherits {
2879 self.resolve_keymap_recursive(parent_name, visited)
2880 } else {
2881 Vec::new()
2882 };
2883
2884 all_bindings.extend(keymap.bindings);
2886
2887 all_bindings
2888 }
2889 fn default_languages() -> HashMap<String, LanguageConfig> {
2891 let mut languages = HashMap::new();
2892
2893 languages.insert(
2894 "rust".to_string(),
2895 LanguageConfig {
2896 extensions: vec!["rs".to_string()],
2897 filenames: vec![],
2898 grammar: "rust".to_string(),
2899 comment_prefix: Some("//".to_string()),
2900 auto_indent: true,
2901 auto_close: None,
2902 auto_surround: None,
2903 textmate_grammar: None,
2904 show_whitespace_tabs: true,
2905 line_wrap: None,
2906 wrap_column: None,
2907 page_view: None,
2908 page_width: None,
2909 use_tabs: None,
2910 tab_size: None,
2911 formatter: Some(FormatterConfig {
2912 command: "rustfmt".to_string(),
2913 args: vec!["--edition".to_string(), "2021".to_string()],
2914 stdin: true,
2915 timeout_ms: 10000,
2916 }),
2917 format_on_save: false,
2918 on_save: vec![],
2919 word_characters: None,
2920 },
2921 );
2922
2923 languages.insert(
2924 "javascript".to_string(),
2925 LanguageConfig {
2926 extensions: vec!["js".to_string(), "jsx".to_string(), "mjs".to_string()],
2927 filenames: vec![],
2928 grammar: "javascript".to_string(),
2929 comment_prefix: Some("//".to_string()),
2930 auto_indent: true,
2931 auto_close: None,
2932 auto_surround: None,
2933 textmate_grammar: None,
2934 show_whitespace_tabs: true,
2935 line_wrap: None,
2936 wrap_column: None,
2937 page_view: None,
2938 page_width: None,
2939 use_tabs: None,
2940 tab_size: None,
2941 formatter: Some(FormatterConfig {
2942 command: "prettier".to_string(),
2943 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
2944 stdin: true,
2945 timeout_ms: 10000,
2946 }),
2947 format_on_save: false,
2948 on_save: vec![],
2949 word_characters: None,
2950 },
2951 );
2952
2953 languages.insert(
2954 "typescript".to_string(),
2955 LanguageConfig {
2956 extensions: vec!["ts".to_string(), "tsx".to_string(), "mts".to_string()],
2957 filenames: vec![],
2958 grammar: "typescript".to_string(),
2959 comment_prefix: Some("//".to_string()),
2960 auto_indent: true,
2961 auto_close: None,
2962 auto_surround: None,
2963 textmate_grammar: None,
2964 show_whitespace_tabs: true,
2965 line_wrap: None,
2966 wrap_column: None,
2967 page_view: None,
2968 page_width: None,
2969 use_tabs: None,
2970 tab_size: None,
2971 formatter: Some(FormatterConfig {
2972 command: "prettier".to_string(),
2973 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
2974 stdin: true,
2975 timeout_ms: 10000,
2976 }),
2977 format_on_save: false,
2978 on_save: vec![],
2979 word_characters: None,
2980 },
2981 );
2982
2983 languages.insert(
2984 "python".to_string(),
2985 LanguageConfig {
2986 extensions: vec!["py".to_string(), "pyi".to_string()],
2987 filenames: vec![],
2988 grammar: "python".to_string(),
2989 comment_prefix: Some("#".to_string()),
2990 auto_indent: true,
2991 auto_close: None,
2992 auto_surround: None,
2993 textmate_grammar: None,
2994 show_whitespace_tabs: true,
2995 line_wrap: None,
2996 wrap_column: None,
2997 page_view: None,
2998 page_width: None,
2999 use_tabs: None,
3000 tab_size: None,
3001 formatter: Some(FormatterConfig {
3002 command: "ruff".to_string(),
3003 args: vec![
3004 "format".to_string(),
3005 "--stdin-filename".to_string(),
3006 "$FILE".to_string(),
3007 ],
3008 stdin: true,
3009 timeout_ms: 10000,
3010 }),
3011 format_on_save: false,
3012 on_save: vec![],
3013 word_characters: None,
3014 },
3015 );
3016
3017 languages.insert(
3018 "c".to_string(),
3019 LanguageConfig {
3020 extensions: vec!["c".to_string(), "h".to_string()],
3021 filenames: vec![],
3022 grammar: "c".to_string(),
3023 comment_prefix: Some("//".to_string()),
3024 auto_indent: true,
3025 auto_close: None,
3026 auto_surround: None,
3027 textmate_grammar: None,
3028 show_whitespace_tabs: true,
3029 line_wrap: None,
3030 wrap_column: None,
3031 page_view: None,
3032 page_width: None,
3033 use_tabs: None,
3034 tab_size: None,
3035 formatter: Some(FormatterConfig {
3036 command: "clang-format".to_string(),
3037 args: vec![],
3038 stdin: true,
3039 timeout_ms: 10000,
3040 }),
3041 format_on_save: false,
3042 on_save: vec![],
3043 word_characters: None,
3044 },
3045 );
3046
3047 languages.insert(
3048 "cpp".to_string(),
3049 LanguageConfig {
3050 extensions: vec![
3051 "cpp".to_string(),
3052 "cc".to_string(),
3053 "cxx".to_string(),
3054 "hpp".to_string(),
3055 "hh".to_string(),
3056 "hxx".to_string(),
3057 ],
3058 filenames: vec![],
3059 grammar: "cpp".to_string(),
3060 comment_prefix: Some("//".to_string()),
3061 auto_indent: true,
3062 auto_close: None,
3063 auto_surround: None,
3064 textmate_grammar: None,
3065 show_whitespace_tabs: true,
3066 line_wrap: None,
3067 wrap_column: None,
3068 page_view: None,
3069 page_width: None,
3070 use_tabs: None,
3071 tab_size: None,
3072 formatter: Some(FormatterConfig {
3073 command: "clang-format".to_string(),
3074 args: vec![],
3075 stdin: true,
3076 timeout_ms: 10000,
3077 }),
3078 format_on_save: false,
3079 on_save: vec![],
3080 word_characters: None,
3081 },
3082 );
3083
3084 languages.insert(
3085 "csharp".to_string(),
3086 LanguageConfig {
3087 extensions: vec!["cs".to_string()],
3088 filenames: vec![],
3089 grammar: "C#".to_string(),
3090 comment_prefix: Some("//".to_string()),
3091 auto_indent: true,
3092 auto_close: None,
3093 auto_surround: None,
3094 textmate_grammar: None,
3095 show_whitespace_tabs: true,
3096 line_wrap: None,
3097 wrap_column: None,
3098 page_view: None,
3099 page_width: None,
3100 use_tabs: None,
3101 tab_size: None,
3102 formatter: None,
3103 format_on_save: false,
3104 on_save: vec![],
3105 word_characters: None,
3106 },
3107 );
3108
3109 languages.insert(
3110 "bash".to_string(),
3111 LanguageConfig {
3112 extensions: vec!["sh".to_string(), "bash".to_string()],
3113 filenames: vec![
3114 ".bash_aliases".to_string(),
3115 ".bash_logout".to_string(),
3116 ".bash_profile".to_string(),
3117 ".bashrc".to_string(),
3118 ".env".to_string(),
3119 ".profile".to_string(),
3120 ".zlogin".to_string(),
3121 ".zlogout".to_string(),
3122 ".zprofile".to_string(),
3123 ".zshenv".to_string(),
3124 ".zshrc".to_string(),
3125 "PKGBUILD".to_string(),
3127 "APKBUILD".to_string(),
3128 ],
3129 grammar: "bash".to_string(),
3130 comment_prefix: Some("#".to_string()),
3131 auto_indent: true,
3132 auto_close: None,
3133 auto_surround: None,
3134 textmate_grammar: None,
3135 show_whitespace_tabs: true,
3136 line_wrap: None,
3137 wrap_column: None,
3138 page_view: None,
3139 page_width: None,
3140 use_tabs: None,
3141 tab_size: None,
3142 formatter: None,
3143 format_on_save: false,
3144 on_save: vec![],
3145 word_characters: None,
3146 },
3147 );
3148
3149 languages.insert(
3150 "makefile".to_string(),
3151 LanguageConfig {
3152 extensions: vec!["mk".to_string()],
3153 filenames: vec![
3154 "Makefile".to_string(),
3155 "makefile".to_string(),
3156 "GNUmakefile".to_string(),
3157 ],
3158 grammar: "Makefile".to_string(),
3159 comment_prefix: Some("#".to_string()),
3160 auto_indent: false,
3161 auto_close: None,
3162 auto_surround: None,
3163 textmate_grammar: None,
3164 show_whitespace_tabs: true,
3165 line_wrap: None,
3166 wrap_column: None,
3167 page_view: None,
3168 page_width: None,
3169 use_tabs: Some(true), tab_size: Some(8), formatter: None,
3172 format_on_save: false,
3173 on_save: vec![],
3174 word_characters: None,
3175 },
3176 );
3177
3178 languages.insert(
3179 "dockerfile".to_string(),
3180 LanguageConfig {
3181 extensions: vec!["dockerfile".to_string()],
3182 filenames: vec!["Dockerfile".to_string(), "Containerfile".to_string()],
3183 grammar: "dockerfile".to_string(),
3184 comment_prefix: Some("#".to_string()),
3185 auto_indent: true,
3186 auto_close: None,
3187 auto_surround: None,
3188 textmate_grammar: None,
3189 show_whitespace_tabs: true,
3190 line_wrap: None,
3191 wrap_column: None,
3192 page_view: None,
3193 page_width: None,
3194 use_tabs: None,
3195 tab_size: None,
3196 formatter: None,
3197 format_on_save: false,
3198 on_save: vec![],
3199 word_characters: None,
3200 },
3201 );
3202
3203 languages.insert(
3204 "json".to_string(),
3205 LanguageConfig {
3206 extensions: vec!["json".to_string(), "jsonc".to_string()],
3207 filenames: vec![],
3208 grammar: "json".to_string(),
3209 comment_prefix: None,
3210 auto_indent: true,
3211 auto_close: None,
3212 auto_surround: None,
3213 textmate_grammar: None,
3214 show_whitespace_tabs: true,
3215 line_wrap: None,
3216 wrap_column: None,
3217 page_view: None,
3218 page_width: None,
3219 use_tabs: None,
3220 tab_size: None,
3221 formatter: Some(FormatterConfig {
3222 command: "prettier".to_string(),
3223 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3224 stdin: true,
3225 timeout_ms: 10000,
3226 }),
3227 format_on_save: false,
3228 on_save: vec![],
3229 word_characters: None,
3230 },
3231 );
3232
3233 languages.insert(
3234 "toml".to_string(),
3235 LanguageConfig {
3236 extensions: vec!["toml".to_string()],
3237 filenames: vec!["Cargo.lock".to_string()],
3238 grammar: "toml".to_string(),
3239 comment_prefix: Some("#".to_string()),
3240 auto_indent: true,
3241 auto_close: None,
3242 auto_surround: None,
3243 textmate_grammar: None,
3244 show_whitespace_tabs: true,
3245 line_wrap: None,
3246 wrap_column: None,
3247 page_view: None,
3248 page_width: None,
3249 use_tabs: None,
3250 tab_size: None,
3251 formatter: None,
3252 format_on_save: false,
3253 on_save: vec![],
3254 word_characters: None,
3255 },
3256 );
3257
3258 languages.insert(
3259 "yaml".to_string(),
3260 LanguageConfig {
3261 extensions: vec!["yml".to_string(), "yaml".to_string()],
3262 filenames: vec![],
3263 grammar: "yaml".to_string(),
3264 comment_prefix: Some("#".to_string()),
3265 auto_indent: true,
3266 auto_close: None,
3267 auto_surround: None,
3268 textmate_grammar: None,
3269 show_whitespace_tabs: true,
3270 line_wrap: None,
3271 wrap_column: None,
3272 page_view: None,
3273 page_width: None,
3274 use_tabs: None,
3275 tab_size: None,
3276 formatter: Some(FormatterConfig {
3277 command: "prettier".to_string(),
3278 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3279 stdin: true,
3280 timeout_ms: 10000,
3281 }),
3282 format_on_save: false,
3283 on_save: vec![],
3284 word_characters: None,
3285 },
3286 );
3287
3288 languages.insert(
3289 "markdown".to_string(),
3290 LanguageConfig {
3291 extensions: vec!["md".to_string(), "markdown".to_string()],
3292 filenames: vec!["README".to_string()],
3293 grammar: "markdown".to_string(),
3294 comment_prefix: None,
3295 auto_indent: false,
3296 auto_close: None,
3297 auto_surround: None,
3298 textmate_grammar: None,
3299 show_whitespace_tabs: true,
3300 line_wrap: None,
3301 wrap_column: None,
3302 page_view: None,
3303 page_width: None,
3304 use_tabs: None,
3305 tab_size: None,
3306 formatter: None,
3307 format_on_save: false,
3308 on_save: vec![],
3309 word_characters: None,
3310 },
3311 );
3312
3313 languages.insert(
3315 "go".to_string(),
3316 LanguageConfig {
3317 extensions: vec!["go".to_string()],
3318 filenames: vec![],
3319 grammar: "go".to_string(),
3320 comment_prefix: Some("//".to_string()),
3321 auto_indent: true,
3322 auto_close: None,
3323 auto_surround: None,
3324 textmate_grammar: None,
3325 show_whitespace_tabs: false,
3326 line_wrap: None,
3327 wrap_column: None,
3328 page_view: None,
3329 page_width: None,
3330 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
3333 command: "gofmt".to_string(),
3334 args: vec![],
3335 stdin: true,
3336 timeout_ms: 10000,
3337 }),
3338 format_on_save: false,
3339 on_save: vec![],
3340 word_characters: None,
3341 },
3342 );
3343
3344 languages.insert(
3345 "odin".to_string(),
3346 LanguageConfig {
3347 extensions: vec!["odin".to_string()],
3348 filenames: vec![],
3349 grammar: "odin".to_string(),
3350 comment_prefix: Some("//".to_string()),
3351 auto_indent: true,
3352 auto_close: None,
3353 auto_surround: None,
3354 textmate_grammar: None,
3355 show_whitespace_tabs: false,
3356 line_wrap: None,
3357 wrap_column: None,
3358 page_view: None,
3359 page_width: None,
3360 use_tabs: Some(true),
3361 tab_size: Some(8),
3362 formatter: None,
3363 format_on_save: false,
3364 on_save: vec![],
3365 word_characters: None,
3366 },
3367 );
3368
3369 languages.insert(
3370 "zig".to_string(),
3371 LanguageConfig {
3372 extensions: vec!["zig".to_string(), "zon".to_string()],
3373 filenames: vec![],
3374 grammar: "zig".to_string(),
3375 comment_prefix: Some("//".to_string()),
3376 auto_indent: true,
3377 auto_close: None,
3378 auto_surround: None,
3379 textmate_grammar: None,
3380 show_whitespace_tabs: true,
3381 line_wrap: None,
3382 wrap_column: None,
3383 page_view: None,
3384 page_width: None,
3385 use_tabs: None,
3386 tab_size: None,
3387 formatter: None,
3388 format_on_save: false,
3389 on_save: vec![],
3390 word_characters: None,
3391 },
3392 );
3393
3394 languages.insert(
3395 "java".to_string(),
3396 LanguageConfig {
3397 extensions: vec!["java".to_string()],
3398 filenames: vec![],
3399 grammar: "java".to_string(),
3400 comment_prefix: Some("//".to_string()),
3401 auto_indent: true,
3402 auto_close: None,
3403 auto_surround: None,
3404 textmate_grammar: None,
3405 show_whitespace_tabs: true,
3406 line_wrap: None,
3407 wrap_column: None,
3408 page_view: None,
3409 page_width: None,
3410 use_tabs: None,
3411 tab_size: None,
3412 formatter: None,
3413 format_on_save: false,
3414 on_save: vec![],
3415 word_characters: None,
3416 },
3417 );
3418
3419 languages.insert(
3420 "latex".to_string(),
3421 LanguageConfig {
3422 extensions: vec![
3423 "tex".to_string(),
3424 "latex".to_string(),
3425 "ltx".to_string(),
3426 "sty".to_string(),
3427 "cls".to_string(),
3428 "bib".to_string(),
3429 ],
3430 filenames: vec![],
3431 grammar: "latex".to_string(),
3432 comment_prefix: Some("%".to_string()),
3433 auto_indent: true,
3434 auto_close: None,
3435 auto_surround: None,
3436 textmate_grammar: None,
3437 show_whitespace_tabs: true,
3438 line_wrap: None,
3439 wrap_column: None,
3440 page_view: None,
3441 page_width: None,
3442 use_tabs: None,
3443 tab_size: None,
3444 formatter: None,
3445 format_on_save: false,
3446 on_save: vec![],
3447 word_characters: None,
3448 },
3449 );
3450
3451 languages.insert(
3452 "templ".to_string(),
3453 LanguageConfig {
3454 extensions: vec!["templ".to_string()],
3455 filenames: vec![],
3456 grammar: "go".to_string(), comment_prefix: Some("//".to_string()),
3458 auto_indent: true,
3459 auto_close: None,
3460 auto_surround: None,
3461 textmate_grammar: None,
3462 show_whitespace_tabs: true,
3463 line_wrap: None,
3464 wrap_column: None,
3465 page_view: None,
3466 page_width: None,
3467 use_tabs: None,
3468 tab_size: None,
3469 formatter: None,
3470 format_on_save: false,
3471 on_save: vec![],
3472 word_characters: None,
3473 },
3474 );
3475
3476 languages.insert(
3478 "git-rebase".to_string(),
3479 LanguageConfig {
3480 extensions: vec![],
3481 filenames: vec!["git-rebase-todo".to_string()],
3482 grammar: "Git Rebase Todo".to_string(),
3483 comment_prefix: Some("#".to_string()),
3484 auto_indent: false,
3485 auto_close: None,
3486 auto_surround: None,
3487 textmate_grammar: None,
3488 show_whitespace_tabs: true,
3489 line_wrap: None,
3490 wrap_column: None,
3491 page_view: None,
3492 page_width: None,
3493 use_tabs: None,
3494 tab_size: None,
3495 formatter: None,
3496 format_on_save: false,
3497 on_save: vec![],
3498 word_characters: None,
3499 },
3500 );
3501
3502 languages.insert(
3503 "git-commit".to_string(),
3504 LanguageConfig {
3505 extensions: vec![],
3506 filenames: vec![
3507 "COMMIT_EDITMSG".to_string(),
3508 "MERGE_MSG".to_string(),
3509 "SQUASH_MSG".to_string(),
3510 "TAG_EDITMSG".to_string(),
3511 ],
3512 grammar: "Git Commit Message".to_string(),
3513 comment_prefix: Some("#".to_string()),
3514 auto_indent: false,
3515 auto_close: None,
3516 auto_surround: None,
3517 textmate_grammar: None,
3518 show_whitespace_tabs: true,
3519 line_wrap: None,
3520 wrap_column: None,
3521 page_view: None,
3522 page_width: None,
3523 use_tabs: None,
3524 tab_size: None,
3525 formatter: None,
3526 format_on_save: false,
3527 on_save: vec![],
3528 word_characters: None,
3529 },
3530 );
3531
3532 languages.insert(
3533 "gitignore".to_string(),
3534 LanguageConfig {
3535 extensions: vec!["gitignore".to_string()],
3536 filenames: vec![
3537 ".gitignore".to_string(),
3538 ".dockerignore".to_string(),
3539 ".npmignore".to_string(),
3540 ".hgignore".to_string(),
3541 ],
3542 grammar: "Gitignore".to_string(),
3543 comment_prefix: Some("#".to_string()),
3544 auto_indent: false,
3545 auto_close: None,
3546 auto_surround: None,
3547 textmate_grammar: None,
3548 show_whitespace_tabs: true,
3549 line_wrap: None,
3550 wrap_column: None,
3551 page_view: None,
3552 page_width: None,
3553 use_tabs: None,
3554 tab_size: None,
3555 formatter: None,
3556 format_on_save: false,
3557 on_save: vec![],
3558 word_characters: None,
3559 },
3560 );
3561
3562 languages.insert(
3563 "gitconfig".to_string(),
3564 LanguageConfig {
3565 extensions: vec!["gitconfig".to_string()],
3566 filenames: vec![".gitconfig".to_string(), ".gitmodules".to_string()],
3567 grammar: "Git Config".to_string(),
3568 comment_prefix: Some("#".to_string()),
3569 auto_indent: true,
3570 auto_close: None,
3571 auto_surround: None,
3572 textmate_grammar: None,
3573 show_whitespace_tabs: true,
3574 line_wrap: None,
3575 wrap_column: None,
3576 page_view: None,
3577 page_width: None,
3578 use_tabs: None,
3579 tab_size: None,
3580 formatter: None,
3581 format_on_save: false,
3582 on_save: vec![],
3583 word_characters: None,
3584 },
3585 );
3586
3587 languages.insert(
3588 "gitattributes".to_string(),
3589 LanguageConfig {
3590 extensions: vec!["gitattributes".to_string()],
3591 filenames: vec![".gitattributes".to_string()],
3592 grammar: "Git Attributes".to_string(),
3593 comment_prefix: Some("#".to_string()),
3594 auto_indent: false,
3595 auto_close: None,
3596 auto_surround: None,
3597 textmate_grammar: None,
3598 show_whitespace_tabs: true,
3599 line_wrap: None,
3600 wrap_column: None,
3601 page_view: None,
3602 page_width: None,
3603 use_tabs: None,
3604 tab_size: None,
3605 formatter: None,
3606 format_on_save: false,
3607 on_save: vec![],
3608 word_characters: None,
3609 },
3610 );
3611
3612 languages.insert(
3613 "typst".to_string(),
3614 LanguageConfig {
3615 extensions: vec!["typ".to_string()],
3616 filenames: vec![],
3617 grammar: "Typst".to_string(),
3618 comment_prefix: Some("//".to_string()),
3619 auto_indent: true,
3620 auto_close: None,
3621 auto_surround: None,
3622 textmate_grammar: None,
3623 show_whitespace_tabs: true,
3624 line_wrap: None,
3625 wrap_column: None,
3626 page_view: None,
3627 page_width: None,
3628 use_tabs: None,
3629 tab_size: None,
3630 formatter: None,
3631 format_on_save: false,
3632 on_save: vec![],
3633 word_characters: None,
3634 },
3635 );
3636
3637 languages.insert(
3642 "kotlin".to_string(),
3643 LanguageConfig {
3644 extensions: vec!["kt".to_string(), "kts".to_string()],
3645 filenames: vec![],
3646 grammar: "Kotlin".to_string(),
3647 comment_prefix: Some("//".to_string()),
3648 auto_indent: true,
3649 auto_close: None,
3650 auto_surround: None,
3651 textmate_grammar: None,
3652 show_whitespace_tabs: true,
3653 line_wrap: None,
3654 wrap_column: None,
3655 page_view: None,
3656 page_width: None,
3657 use_tabs: None,
3658 tab_size: None,
3659 formatter: None,
3660 format_on_save: false,
3661 on_save: vec![],
3662 word_characters: None,
3663 },
3664 );
3665
3666 languages.insert(
3667 "swift".to_string(),
3668 LanguageConfig {
3669 extensions: vec!["swift".to_string()],
3670 filenames: vec![],
3671 grammar: "Swift".to_string(),
3672 comment_prefix: Some("//".to_string()),
3673 auto_indent: true,
3674 auto_close: None,
3675 auto_surround: None,
3676 textmate_grammar: None,
3677 show_whitespace_tabs: true,
3678 line_wrap: None,
3679 wrap_column: None,
3680 page_view: None,
3681 page_width: None,
3682 use_tabs: None,
3683 tab_size: None,
3684 formatter: None,
3685 format_on_save: false,
3686 on_save: vec![],
3687 word_characters: None,
3688 },
3689 );
3690
3691 languages.insert(
3692 "scala".to_string(),
3693 LanguageConfig {
3694 extensions: vec!["scala".to_string(), "sc".to_string()],
3695 filenames: vec![],
3696 grammar: "Scala".to_string(),
3697 comment_prefix: Some("//".to_string()),
3698 auto_indent: true,
3699 auto_close: None,
3700 auto_surround: None,
3701 textmate_grammar: None,
3702 show_whitespace_tabs: true,
3703 line_wrap: None,
3704 wrap_column: None,
3705 page_view: None,
3706 page_width: None,
3707 use_tabs: None,
3708 tab_size: None,
3709 formatter: None,
3710 format_on_save: false,
3711 on_save: vec![],
3712 word_characters: None,
3713 },
3714 );
3715
3716 languages.insert(
3717 "dart".to_string(),
3718 LanguageConfig {
3719 extensions: vec!["dart".to_string()],
3720 filenames: vec![],
3721 grammar: "Dart".to_string(),
3722 comment_prefix: Some("//".to_string()),
3723 auto_indent: true,
3724 auto_close: None,
3725 auto_surround: None,
3726 textmate_grammar: None,
3727 show_whitespace_tabs: true,
3728 line_wrap: None,
3729 wrap_column: None,
3730 page_view: None,
3731 page_width: None,
3732 use_tabs: None,
3733 tab_size: None,
3734 formatter: None,
3735 format_on_save: false,
3736 on_save: vec![],
3737 word_characters: None,
3738 },
3739 );
3740
3741 languages.insert(
3742 "elixir".to_string(),
3743 LanguageConfig {
3744 extensions: vec!["ex".to_string(), "exs".to_string()],
3745 filenames: vec![],
3746 grammar: "Elixir".to_string(),
3747 comment_prefix: Some("#".to_string()),
3748 auto_indent: true,
3749 auto_close: None,
3750 auto_surround: None,
3751 textmate_grammar: None,
3752 show_whitespace_tabs: true,
3753 line_wrap: None,
3754 wrap_column: None,
3755 page_view: None,
3756 page_width: None,
3757 use_tabs: None,
3758 tab_size: None,
3759 formatter: None,
3760 format_on_save: false,
3761 on_save: vec![],
3762 word_characters: None,
3763 },
3764 );
3765
3766 languages.insert(
3767 "erlang".to_string(),
3768 LanguageConfig {
3769 extensions: vec!["erl".to_string(), "hrl".to_string()],
3770 filenames: vec![],
3771 grammar: "Erlang".to_string(),
3772 comment_prefix: Some("%".to_string()),
3773 auto_indent: true,
3774 auto_close: None,
3775 auto_surround: None,
3776 textmate_grammar: None,
3777 show_whitespace_tabs: true,
3778 line_wrap: None,
3779 wrap_column: None,
3780 page_view: None,
3781 page_width: None,
3782 use_tabs: None,
3783 tab_size: None,
3784 formatter: None,
3785 format_on_save: false,
3786 on_save: vec![],
3787 word_characters: None,
3788 },
3789 );
3790
3791 languages.insert(
3792 "haskell".to_string(),
3793 LanguageConfig {
3794 extensions: vec!["hs".to_string(), "lhs".to_string()],
3795 filenames: vec![],
3796 grammar: "Haskell".to_string(),
3797 comment_prefix: Some("--".to_string()),
3798 auto_indent: true,
3799 auto_close: None,
3800 auto_surround: None,
3801 textmate_grammar: None,
3802 show_whitespace_tabs: true,
3803 line_wrap: None,
3804 wrap_column: None,
3805 page_view: None,
3806 page_width: None,
3807 use_tabs: None,
3808 tab_size: None,
3809 formatter: None,
3810 format_on_save: false,
3811 on_save: vec![],
3812 word_characters: None,
3813 },
3814 );
3815
3816 languages.insert(
3817 "ocaml".to_string(),
3818 LanguageConfig {
3819 extensions: vec!["ml".to_string(), "mli".to_string()],
3820 filenames: vec![],
3821 grammar: "OCaml".to_string(),
3822 comment_prefix: None,
3823 auto_indent: true,
3824 auto_close: None,
3825 auto_surround: None,
3826 textmate_grammar: None,
3827 show_whitespace_tabs: true,
3828 line_wrap: None,
3829 wrap_column: None,
3830 page_view: None,
3831 page_width: None,
3832 use_tabs: None,
3833 tab_size: None,
3834 formatter: None,
3835 format_on_save: false,
3836 on_save: vec![],
3837 word_characters: None,
3838 },
3839 );
3840
3841 languages.insert(
3842 "clojure".to_string(),
3843 LanguageConfig {
3844 extensions: vec![
3845 "clj".to_string(),
3846 "cljs".to_string(),
3847 "cljc".to_string(),
3848 "edn".to_string(),
3849 ],
3850 filenames: vec![],
3851 grammar: "Clojure".to_string(),
3852 comment_prefix: Some(";".to_string()),
3853 auto_indent: true,
3854 auto_close: None,
3855 auto_surround: None,
3856 textmate_grammar: None,
3857 show_whitespace_tabs: true,
3858 line_wrap: None,
3859 wrap_column: None,
3860 page_view: None,
3861 page_width: None,
3862 use_tabs: None,
3863 tab_size: None,
3864 formatter: None,
3865 format_on_save: false,
3866 on_save: vec![],
3867 word_characters: None,
3868 },
3869 );
3870
3871 languages.insert(
3872 "r".to_string(),
3873 LanguageConfig {
3874 extensions: vec!["r".to_string(), "R".to_string(), "rmd".to_string()],
3875 filenames: vec![],
3876 grammar: "R".to_string(),
3877 comment_prefix: Some("#".to_string()),
3878 auto_indent: true,
3879 auto_close: None,
3880 auto_surround: None,
3881 textmate_grammar: None,
3882 show_whitespace_tabs: true,
3883 line_wrap: None,
3884 wrap_column: None,
3885 page_view: None,
3886 page_width: None,
3887 use_tabs: None,
3888 tab_size: None,
3889 formatter: None,
3890 format_on_save: false,
3891 on_save: vec![],
3892 word_characters: None,
3893 },
3894 );
3895
3896 languages.insert(
3897 "julia".to_string(),
3898 LanguageConfig {
3899 extensions: vec!["jl".to_string()],
3900 filenames: vec![],
3901 grammar: "Julia".to_string(),
3902 comment_prefix: Some("#".to_string()),
3903 auto_indent: true,
3904 auto_close: None,
3905 auto_surround: None,
3906 textmate_grammar: None,
3907 show_whitespace_tabs: true,
3908 line_wrap: None,
3909 wrap_column: None,
3910 page_view: None,
3911 page_width: None,
3912 use_tabs: None,
3913 tab_size: None,
3914 formatter: None,
3915 format_on_save: false,
3916 on_save: vec![],
3917 word_characters: None,
3918 },
3919 );
3920
3921 languages.insert(
3922 "perl".to_string(),
3923 LanguageConfig {
3924 extensions: vec!["pl".to_string(), "pm".to_string(), "t".to_string()],
3925 filenames: vec![],
3926 grammar: "Perl".to_string(),
3927 comment_prefix: Some("#".to_string()),
3928 auto_indent: true,
3929 auto_close: None,
3930 auto_surround: None,
3931 textmate_grammar: None,
3932 show_whitespace_tabs: true,
3933 line_wrap: None,
3934 wrap_column: None,
3935 page_view: None,
3936 page_width: None,
3937 use_tabs: None,
3938 tab_size: None,
3939 formatter: None,
3940 format_on_save: false,
3941 on_save: vec![],
3942 word_characters: None,
3943 },
3944 );
3945
3946 languages.insert(
3947 "nim".to_string(),
3948 LanguageConfig {
3949 extensions: vec!["nim".to_string(), "nims".to_string(), "nimble".to_string()],
3950 filenames: vec![],
3951 grammar: "Nim".to_string(),
3952 comment_prefix: Some("#".to_string()),
3953 auto_indent: true,
3954 auto_close: None,
3955 auto_surround: None,
3956 textmate_grammar: None,
3957 show_whitespace_tabs: true,
3958 line_wrap: None,
3959 wrap_column: None,
3960 page_view: None,
3961 page_width: None,
3962 use_tabs: None,
3963 tab_size: None,
3964 formatter: None,
3965 format_on_save: false,
3966 on_save: vec![],
3967 word_characters: None,
3968 },
3969 );
3970
3971 languages.insert(
3972 "gleam".to_string(),
3973 LanguageConfig {
3974 extensions: vec!["gleam".to_string()],
3975 filenames: vec![],
3976 grammar: "Gleam".to_string(),
3977 comment_prefix: Some("//".to_string()),
3978 auto_indent: true,
3979 auto_close: None,
3980 auto_surround: None,
3981 textmate_grammar: None,
3982 show_whitespace_tabs: true,
3983 line_wrap: None,
3984 wrap_column: None,
3985 page_view: None,
3986 page_width: None,
3987 use_tabs: None,
3988 tab_size: None,
3989 formatter: None,
3990 format_on_save: false,
3991 on_save: vec![],
3992 word_characters: None,
3993 },
3994 );
3995
3996 languages.insert(
3997 "fsharp".to_string(),
3998 LanguageConfig {
3999 extensions: vec!["fs".to_string(), "fsi".to_string(), "fsx".to_string()],
4000 filenames: vec![],
4001 grammar: "FSharp".to_string(),
4002 comment_prefix: Some("//".to_string()),
4003 auto_indent: true,
4004 auto_close: None,
4005 auto_surround: None,
4006 textmate_grammar: None,
4007 show_whitespace_tabs: true,
4008 line_wrap: None,
4009 wrap_column: None,
4010 page_view: None,
4011 page_width: None,
4012 use_tabs: None,
4013 tab_size: None,
4014 formatter: None,
4015 format_on_save: false,
4016 on_save: vec![],
4017 word_characters: None,
4018 },
4019 );
4020
4021 languages.insert(
4022 "nix".to_string(),
4023 LanguageConfig {
4024 extensions: vec!["nix".to_string()],
4025 filenames: vec![],
4026 grammar: "Nix".to_string(),
4027 comment_prefix: Some("#".to_string()),
4028 auto_indent: true,
4029 auto_close: None,
4030 auto_surround: None,
4031 textmate_grammar: None,
4032 show_whitespace_tabs: true,
4033 line_wrap: None,
4034 wrap_column: None,
4035 page_view: None,
4036 page_width: None,
4037 use_tabs: None,
4038 tab_size: None,
4039 formatter: None,
4040 format_on_save: false,
4041 on_save: vec![],
4042 word_characters: None,
4043 },
4044 );
4045
4046 languages.insert(
4047 "nushell".to_string(),
4048 LanguageConfig {
4049 extensions: vec!["nu".to_string()],
4050 filenames: vec![],
4051 grammar: "Nushell".to_string(),
4052 comment_prefix: Some("#".to_string()),
4053 auto_indent: true,
4054 auto_close: None,
4055 auto_surround: None,
4056 textmate_grammar: None,
4057 show_whitespace_tabs: true,
4058 line_wrap: None,
4059 wrap_column: None,
4060 page_view: None,
4061 page_width: None,
4062 use_tabs: None,
4063 tab_size: None,
4064 formatter: None,
4065 format_on_save: false,
4066 on_save: vec![],
4067 word_characters: None,
4068 },
4069 );
4070
4071 languages.insert(
4072 "solidity".to_string(),
4073 LanguageConfig {
4074 extensions: vec!["sol".to_string()],
4075 filenames: vec![],
4076 grammar: "Solidity".to_string(),
4077 comment_prefix: Some("//".to_string()),
4078 auto_indent: true,
4079 auto_close: None,
4080 auto_surround: None,
4081 textmate_grammar: None,
4082 show_whitespace_tabs: true,
4083 line_wrap: None,
4084 wrap_column: None,
4085 page_view: None,
4086 page_width: None,
4087 use_tabs: None,
4088 tab_size: None,
4089 formatter: None,
4090 format_on_save: false,
4091 on_save: vec![],
4092 word_characters: None,
4093 },
4094 );
4095
4096 languages.insert(
4097 "ruby".to_string(),
4098 LanguageConfig {
4099 extensions: vec!["rb".to_string(), "rake".to_string(), "gemspec".to_string()],
4100 filenames: vec![
4101 "Gemfile".to_string(),
4102 "Rakefile".to_string(),
4103 "Guardfile".to_string(),
4104 ],
4105 grammar: "Ruby".to_string(),
4106 comment_prefix: Some("#".to_string()),
4107 auto_indent: true,
4108 auto_close: None,
4109 auto_surround: None,
4110 textmate_grammar: None,
4111 show_whitespace_tabs: true,
4112 line_wrap: None,
4113 wrap_column: None,
4114 page_view: None,
4115 page_width: None,
4116 use_tabs: None,
4117 tab_size: None,
4118 formatter: None,
4119 format_on_save: false,
4120 on_save: vec![],
4121 word_characters: None,
4122 },
4123 );
4124
4125 languages.insert(
4126 "php".to_string(),
4127 LanguageConfig {
4128 extensions: vec!["php".to_string(), "phtml".to_string()],
4129 filenames: vec![],
4130 grammar: "PHP".to_string(),
4131 comment_prefix: Some("//".to_string()),
4132 auto_indent: true,
4133 auto_close: None,
4134 auto_surround: None,
4135 textmate_grammar: None,
4136 show_whitespace_tabs: true,
4137 line_wrap: None,
4138 wrap_column: None,
4139 page_view: None,
4140 page_width: None,
4141 use_tabs: None,
4142 tab_size: None,
4143 formatter: None,
4144 format_on_save: false,
4145 on_save: vec![],
4146 word_characters: None,
4147 },
4148 );
4149
4150 languages.insert(
4151 "lua".to_string(),
4152 LanguageConfig {
4153 extensions: vec!["lua".to_string()],
4154 filenames: vec![],
4155 grammar: "Lua".to_string(),
4156 comment_prefix: Some("--".to_string()),
4157 auto_indent: true,
4158 auto_close: None,
4159 auto_surround: None,
4160 textmate_grammar: None,
4161 show_whitespace_tabs: true,
4162 line_wrap: None,
4163 wrap_column: None,
4164 page_view: None,
4165 page_width: None,
4166 use_tabs: None,
4167 tab_size: None,
4168 formatter: None,
4169 format_on_save: false,
4170 on_save: vec![],
4171 word_characters: None,
4172 },
4173 );
4174
4175 languages.insert(
4176 "html".to_string(),
4177 LanguageConfig {
4178 extensions: vec!["html".to_string(), "htm".to_string()],
4179 filenames: vec![],
4180 grammar: "HTML".to_string(),
4181 comment_prefix: None,
4182 auto_indent: true,
4183 auto_close: None,
4184 auto_surround: None,
4185 textmate_grammar: None,
4186 show_whitespace_tabs: true,
4187 line_wrap: None,
4188 wrap_column: None,
4189 page_view: None,
4190 page_width: None,
4191 use_tabs: None,
4192 tab_size: None,
4193 formatter: None,
4194 format_on_save: false,
4195 on_save: vec![],
4196 word_characters: None,
4197 },
4198 );
4199
4200 languages.insert(
4201 "css".to_string(),
4202 LanguageConfig {
4203 extensions: vec!["css".to_string()],
4204 filenames: vec![],
4205 grammar: "CSS".to_string(),
4206 comment_prefix: None,
4207 auto_indent: true,
4208 auto_close: None,
4209 auto_surround: None,
4210 textmate_grammar: None,
4211 show_whitespace_tabs: true,
4212 line_wrap: None,
4213 wrap_column: None,
4214 page_view: None,
4215 page_width: None,
4216 use_tabs: None,
4217 tab_size: None,
4218 formatter: None,
4219 format_on_save: false,
4220 on_save: vec![],
4221 word_characters: None,
4222 },
4223 );
4224
4225 languages.insert(
4226 "sql".to_string(),
4227 LanguageConfig {
4228 extensions: vec!["sql".to_string()],
4229 filenames: vec![],
4230 grammar: "SQL".to_string(),
4231 comment_prefix: Some("--".to_string()),
4232 auto_indent: true,
4233 auto_close: None,
4234 auto_surround: None,
4235 textmate_grammar: None,
4236 show_whitespace_tabs: true,
4237 line_wrap: None,
4238 wrap_column: None,
4239 page_view: None,
4240 page_width: None,
4241 use_tabs: None,
4242 tab_size: None,
4243 formatter: None,
4244 format_on_save: false,
4245 on_save: vec![],
4246 word_characters: None,
4247 },
4248 );
4249
4250 languages.insert(
4251 "graphql".to_string(),
4252 LanguageConfig {
4253 extensions: vec!["graphql".to_string(), "gql".to_string()],
4254 filenames: vec![],
4255 grammar: "GraphQL".to_string(),
4256 comment_prefix: Some("#".to_string()),
4257 auto_indent: true,
4258 auto_close: None,
4259 auto_surround: None,
4260 textmate_grammar: None,
4261 show_whitespace_tabs: true,
4262 line_wrap: None,
4263 wrap_column: None,
4264 page_view: None,
4265 page_width: None,
4266 use_tabs: None,
4267 tab_size: None,
4268 formatter: None,
4269 format_on_save: false,
4270 on_save: vec![],
4271 word_characters: None,
4272 },
4273 );
4274
4275 languages.insert(
4276 "protobuf".to_string(),
4277 LanguageConfig {
4278 extensions: vec!["proto".to_string()],
4279 filenames: vec![],
4280 grammar: "Protocol Buffers".to_string(),
4281 comment_prefix: Some("//".to_string()),
4282 auto_indent: true,
4283 auto_close: None,
4284 auto_surround: None,
4285 textmate_grammar: None,
4286 show_whitespace_tabs: true,
4287 line_wrap: None,
4288 wrap_column: None,
4289 page_view: None,
4290 page_width: None,
4291 use_tabs: None,
4292 tab_size: None,
4293 formatter: None,
4294 format_on_save: false,
4295 on_save: vec![],
4296 word_characters: None,
4297 },
4298 );
4299
4300 languages.insert(
4301 "cmake".to_string(),
4302 LanguageConfig {
4303 extensions: vec!["cmake".to_string()],
4304 filenames: vec!["CMakeLists.txt".to_string()],
4305 grammar: "CMake".to_string(),
4306 comment_prefix: Some("#".to_string()),
4307 auto_indent: true,
4308 auto_close: None,
4309 auto_surround: None,
4310 textmate_grammar: None,
4311 show_whitespace_tabs: true,
4312 line_wrap: None,
4313 wrap_column: None,
4314 page_view: None,
4315 page_width: None,
4316 use_tabs: None,
4317 tab_size: None,
4318 formatter: None,
4319 format_on_save: false,
4320 on_save: vec![],
4321 word_characters: None,
4322 },
4323 );
4324
4325 languages.insert(
4326 "terraform".to_string(),
4327 LanguageConfig {
4328 extensions: vec!["tf".to_string(), "tfvars".to_string(), "hcl".to_string()],
4329 filenames: vec![],
4330 grammar: "HCL".to_string(),
4331 comment_prefix: Some("#".to_string()),
4332 auto_indent: true,
4333 auto_close: None,
4334 auto_surround: None,
4335 textmate_grammar: None,
4336 show_whitespace_tabs: true,
4337 line_wrap: None,
4338 wrap_column: None,
4339 page_view: None,
4340 page_width: None,
4341 use_tabs: None,
4342 tab_size: None,
4343 formatter: None,
4344 format_on_save: false,
4345 on_save: vec![],
4346 word_characters: None,
4347 },
4348 );
4349
4350 languages.insert(
4351 "vue".to_string(),
4352 LanguageConfig {
4353 extensions: vec!["vue".to_string()],
4354 filenames: vec![],
4355 grammar: "Vue".to_string(),
4356 comment_prefix: None,
4357 auto_indent: true,
4358 auto_close: None,
4359 auto_surround: None,
4360 textmate_grammar: None,
4361 show_whitespace_tabs: true,
4362 line_wrap: None,
4363 wrap_column: None,
4364 page_view: None,
4365 page_width: None,
4366 use_tabs: None,
4367 tab_size: None,
4368 formatter: None,
4369 format_on_save: false,
4370 on_save: vec![],
4371 word_characters: None,
4372 },
4373 );
4374
4375 languages.insert(
4376 "svelte".to_string(),
4377 LanguageConfig {
4378 extensions: vec!["svelte".to_string()],
4379 filenames: vec![],
4380 grammar: "Svelte".to_string(),
4381 comment_prefix: None,
4382 auto_indent: true,
4383 auto_close: None,
4384 auto_surround: None,
4385 textmate_grammar: None,
4386 show_whitespace_tabs: true,
4387 line_wrap: None,
4388 wrap_column: None,
4389 page_view: None,
4390 page_width: None,
4391 use_tabs: None,
4392 tab_size: None,
4393 formatter: None,
4394 format_on_save: false,
4395 on_save: vec![],
4396 word_characters: None,
4397 },
4398 );
4399
4400 languages.insert(
4401 "astro".to_string(),
4402 LanguageConfig {
4403 extensions: vec!["astro".to_string()],
4404 filenames: vec![],
4405 grammar: "Astro".to_string(),
4406 comment_prefix: None,
4407 auto_indent: true,
4408 auto_close: None,
4409 auto_surround: None,
4410 textmate_grammar: None,
4411 show_whitespace_tabs: true,
4412 line_wrap: None,
4413 wrap_column: None,
4414 page_view: None,
4415 page_width: None,
4416 use_tabs: None,
4417 tab_size: None,
4418 formatter: None,
4419 format_on_save: false,
4420 on_save: vec![],
4421 word_characters: None,
4422 },
4423 );
4424
4425 languages.insert(
4428 "scss".to_string(),
4429 LanguageConfig {
4430 extensions: vec!["scss".to_string()],
4431 filenames: vec![],
4432 grammar: "SCSS".to_string(),
4433 comment_prefix: Some("//".to_string()),
4434 auto_indent: true,
4435 auto_close: None,
4436 auto_surround: None,
4437 textmate_grammar: None,
4438 show_whitespace_tabs: true,
4439 line_wrap: None,
4440 wrap_column: None,
4441 page_view: None,
4442 page_width: None,
4443 use_tabs: None,
4444 tab_size: None,
4445 formatter: None,
4446 format_on_save: false,
4447 on_save: vec![],
4448 word_characters: None,
4449 },
4450 );
4451
4452 languages.insert(
4453 "less".to_string(),
4454 LanguageConfig {
4455 extensions: vec!["less".to_string()],
4456 filenames: vec![],
4457 grammar: "LESS".to_string(),
4458 comment_prefix: Some("//".to_string()),
4459 auto_indent: true,
4460 auto_close: None,
4461 auto_surround: None,
4462 textmate_grammar: None,
4463 show_whitespace_tabs: true,
4464 line_wrap: None,
4465 wrap_column: None,
4466 page_view: None,
4467 page_width: None,
4468 use_tabs: None,
4469 tab_size: None,
4470 formatter: None,
4471 format_on_save: false,
4472 on_save: vec![],
4473 word_characters: None,
4474 },
4475 );
4476
4477 languages.insert(
4478 "powershell".to_string(),
4479 LanguageConfig {
4480 extensions: vec!["ps1".to_string(), "psm1".to_string(), "psd1".to_string()],
4481 filenames: vec![],
4482 grammar: "PowerShell".to_string(),
4483 comment_prefix: Some("#".to_string()),
4484 auto_indent: true,
4485 auto_close: None,
4486 auto_surround: None,
4487 textmate_grammar: None,
4488 show_whitespace_tabs: true,
4489 line_wrap: None,
4490 wrap_column: None,
4491 page_view: None,
4492 page_width: None,
4493 use_tabs: None,
4494 tab_size: None,
4495 formatter: None,
4496 format_on_save: false,
4497 on_save: vec![],
4498 word_characters: None,
4499 },
4500 );
4501
4502 languages.insert(
4503 "kdl".to_string(),
4504 LanguageConfig {
4505 extensions: vec!["kdl".to_string()],
4506 filenames: vec![],
4507 grammar: "KDL".to_string(),
4508 comment_prefix: Some("//".to_string()),
4509 auto_indent: true,
4510 auto_close: None,
4511 auto_surround: None,
4512 textmate_grammar: None,
4513 show_whitespace_tabs: true,
4514 line_wrap: None,
4515 wrap_column: None,
4516 page_view: None,
4517 page_width: None,
4518 use_tabs: None,
4519 tab_size: None,
4520 formatter: None,
4521 format_on_save: false,
4522 on_save: vec![],
4523 word_characters: None,
4524 },
4525 );
4526
4527 languages.insert(
4528 "starlark".to_string(),
4529 LanguageConfig {
4530 extensions: vec!["bzl".to_string(), "star".to_string()],
4531 filenames: vec!["BUILD".to_string(), "WORKSPACE".to_string()],
4532 grammar: "Starlark".to_string(),
4533 comment_prefix: Some("#".to_string()),
4534 auto_indent: true,
4535 auto_close: None,
4536 auto_surround: None,
4537 textmate_grammar: None,
4538 show_whitespace_tabs: true,
4539 line_wrap: None,
4540 wrap_column: None,
4541 page_view: None,
4542 page_width: None,
4543 use_tabs: None,
4544 tab_size: None,
4545 formatter: None,
4546 format_on_save: false,
4547 on_save: vec![],
4548 word_characters: None,
4549 },
4550 );
4551
4552 languages.insert(
4553 "justfile".to_string(),
4554 LanguageConfig {
4555 extensions: vec![],
4556 filenames: vec![
4557 "justfile".to_string(),
4558 "Justfile".to_string(),
4559 ".justfile".to_string(),
4560 ],
4561 grammar: "Justfile".to_string(),
4562 comment_prefix: Some("#".to_string()),
4563 auto_indent: true,
4564 auto_close: None,
4565 auto_surround: None,
4566 textmate_grammar: None,
4567 show_whitespace_tabs: true,
4568 line_wrap: None,
4569 wrap_column: None,
4570 page_view: None,
4571 page_width: None,
4572 use_tabs: Some(true),
4573 tab_size: None,
4574 formatter: None,
4575 format_on_save: false,
4576 on_save: vec![],
4577 word_characters: None,
4578 },
4579 );
4580
4581 languages.insert(
4582 "earthfile".to_string(),
4583 LanguageConfig {
4584 extensions: vec!["earth".to_string()],
4585 filenames: vec!["Earthfile".to_string()],
4586 grammar: "Earthfile".to_string(),
4587 comment_prefix: Some("#".to_string()),
4588 auto_indent: true,
4589 auto_close: None,
4590 auto_surround: None,
4591 textmate_grammar: None,
4592 show_whitespace_tabs: true,
4593 line_wrap: None,
4594 wrap_column: None,
4595 page_view: None,
4596 page_width: None,
4597 use_tabs: None,
4598 tab_size: None,
4599 formatter: None,
4600 format_on_save: false,
4601 on_save: vec![],
4602 word_characters: None,
4603 },
4604 );
4605
4606 languages.insert(
4607 "gomod".to_string(),
4608 LanguageConfig {
4609 extensions: vec![],
4610 filenames: vec!["go.mod".to_string(), "go.sum".to_string()],
4611 grammar: "Go Module".to_string(),
4612 comment_prefix: Some("//".to_string()),
4613 auto_indent: true,
4614 auto_close: None,
4615 auto_surround: None,
4616 textmate_grammar: None,
4617 show_whitespace_tabs: true,
4618 line_wrap: None,
4619 wrap_column: None,
4620 page_view: None,
4621 page_width: None,
4622 use_tabs: Some(true),
4623 tab_size: None,
4624 formatter: None,
4625 format_on_save: false,
4626 on_save: vec![],
4627 word_characters: None,
4628 },
4629 );
4630
4631 languages.insert(
4632 "vlang".to_string(),
4633 LanguageConfig {
4634 extensions: vec!["v".to_string(), "vv".to_string()],
4635 filenames: vec![],
4636 grammar: "V".to_string(),
4637 comment_prefix: Some("//".to_string()),
4638 auto_indent: true,
4639 auto_close: None,
4640 auto_surround: None,
4641 textmate_grammar: None,
4642 show_whitespace_tabs: true,
4643 line_wrap: None,
4644 wrap_column: None,
4645 page_view: None,
4646 page_width: None,
4647 use_tabs: None,
4648 tab_size: None,
4649 formatter: None,
4650 format_on_save: false,
4651 on_save: vec![],
4652 word_characters: None,
4653 },
4654 );
4655
4656 languages.insert(
4657 "ini".to_string(),
4658 LanguageConfig {
4659 extensions: vec!["ini".to_string(), "cfg".to_string()],
4660 filenames: vec![],
4661 grammar: "INI".to_string(),
4662 comment_prefix: Some(";".to_string()),
4663 auto_indent: false,
4664 auto_close: None,
4665 auto_surround: None,
4666 textmate_grammar: None,
4667 show_whitespace_tabs: true,
4668 line_wrap: None,
4669 wrap_column: None,
4670 page_view: None,
4671 page_width: None,
4672 use_tabs: None,
4673 tab_size: None,
4674 formatter: None,
4675 format_on_save: false,
4676 on_save: vec![],
4677 word_characters: None,
4678 },
4679 );
4680
4681 languages.insert(
4682 "hyprlang".to_string(),
4683 LanguageConfig {
4684 extensions: vec!["hl".to_string()],
4685 filenames: vec!["hyprland.conf".to_string()],
4686 grammar: "Hyprlang".to_string(),
4687 comment_prefix: Some("#".to_string()),
4688 auto_indent: true,
4689 auto_close: None,
4690 auto_surround: None,
4691 textmate_grammar: None,
4692 show_whitespace_tabs: true,
4693 line_wrap: None,
4694 wrap_column: None,
4695 page_view: None,
4696 page_width: None,
4697 use_tabs: None,
4698 tab_size: None,
4699 formatter: None,
4700 format_on_save: false,
4701 on_save: vec![],
4702 word_characters: None,
4703 },
4704 );
4705
4706 languages
4707 }
4708
4709 #[cfg(feature = "runtime")]
4711 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
4712 let mut lsp = HashMap::new();
4713
4714 let ra_log_path = crate::services::log_dirs::lsp_log_path("rust-analyzer")
4717 .to_string_lossy()
4718 .to_string();
4719
4720 Self::populate_lsp_config(&mut lsp, ra_log_path);
4721 lsp
4722 }
4723
4724 #[cfg(not(feature = "runtime"))]
4726 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
4727 HashMap::new()
4729 }
4730
4731 #[cfg(feature = "runtime")]
4733 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
4734 let mut universal = HashMap::new();
4735
4736 universal.insert(
4749 "quicklsp".to_string(),
4750 LspLanguageConfig::Multi(vec![LspServerConfig {
4751 command: "quicklsp".to_string(),
4752 args: vec![],
4753 enabled: false,
4754 auto_start: false,
4755 process_limits: ProcessLimits::default(),
4756 initialization_options: None,
4757 env: Default::default(),
4758 language_id_overrides: Default::default(),
4759 name: Some("QuickLSP".to_string()),
4760 only_features: None,
4761 except_features: None,
4762 root_markers: vec![
4763 "Cargo.toml".to_string(),
4764 "package.json".to_string(),
4765 "go.mod".to_string(),
4766 "pyproject.toml".to_string(),
4767 "requirements.txt".to_string(),
4768 ".git".to_string(),
4769 ],
4770 }]),
4771 );
4772
4773 universal
4774 }
4775
4776 #[cfg(not(feature = "runtime"))]
4778 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
4779 HashMap::new()
4780 }
4781
4782 #[cfg(feature = "runtime")]
4783 fn populate_lsp_config(lsp: &mut HashMap<String, LspLanguageConfig>, ra_log_path: String) {
4784 lsp.insert(
4788 "rust".to_string(),
4789 LspLanguageConfig::Multi(vec![LspServerConfig {
4790 command: "rust-analyzer".to_string(),
4791 args: vec!["--log-file".to_string(), ra_log_path],
4792 enabled: true,
4793 auto_start: false,
4794 process_limits: ProcessLimits::unlimited(),
4795 initialization_options: None,
4796 env: Default::default(),
4797 language_id_overrides: Default::default(),
4798 name: None,
4799 only_features: None,
4800 except_features: None,
4801 root_markers: vec![
4802 "Cargo.toml".to_string(),
4803 "rust-project.json".to_string(),
4804 ".git".to_string(),
4805 ],
4806 }]),
4807 );
4808
4809 lsp.insert(
4811 "python".to_string(),
4812 LspLanguageConfig::Multi(vec![LspServerConfig {
4813 command: "pylsp".to_string(),
4814 args: vec![],
4815 enabled: true,
4816 auto_start: false,
4817 process_limits: ProcessLimits::default(),
4818 initialization_options: None,
4819 env: Default::default(),
4820 language_id_overrides: Default::default(),
4821 name: None,
4822 only_features: None,
4823 except_features: None,
4824 root_markers: vec![
4825 "pyproject.toml".to_string(),
4826 "setup.py".to_string(),
4827 "setup.cfg".to_string(),
4828 "pyrightconfig.json".to_string(),
4829 ".git".to_string(),
4830 ],
4831 }]),
4832 );
4833
4834 lsp.insert(
4837 "javascript".to_string(),
4838 LspLanguageConfig::Multi(vec![LspServerConfig {
4839 command: "typescript-language-server".to_string(),
4840 args: vec!["--stdio".to_string()],
4841 enabled: true,
4842 auto_start: false,
4843 process_limits: ProcessLimits::default(),
4844 initialization_options: None,
4845 env: Default::default(),
4846 language_id_overrides: HashMap::from([(
4847 "jsx".to_string(),
4848 "javascriptreact".to_string(),
4849 )]),
4850 name: None,
4851 only_features: None,
4852 except_features: None,
4853 root_markers: vec![
4854 "tsconfig.json".to_string(),
4855 "jsconfig.json".to_string(),
4856 "package.json".to_string(),
4857 ".git".to_string(),
4858 ],
4859 }]),
4860 );
4861 lsp.insert(
4862 "typescript".to_string(),
4863 LspLanguageConfig::Multi(vec![LspServerConfig {
4864 command: "typescript-language-server".to_string(),
4865 args: vec!["--stdio".to_string()],
4866 enabled: true,
4867 auto_start: false,
4868 process_limits: ProcessLimits::default(),
4869 initialization_options: None,
4870 env: Default::default(),
4871 language_id_overrides: HashMap::from([(
4872 "tsx".to_string(),
4873 "typescriptreact".to_string(),
4874 )]),
4875 name: None,
4876 only_features: None,
4877 except_features: None,
4878 root_markers: vec![
4879 "tsconfig.json".to_string(),
4880 "jsconfig.json".to_string(),
4881 "package.json".to_string(),
4882 ".git".to_string(),
4883 ],
4884 }]),
4885 );
4886
4887 lsp.insert(
4889 "html".to_string(),
4890 LspLanguageConfig::Multi(vec![LspServerConfig {
4891 command: "vscode-html-language-server".to_string(),
4892 args: vec!["--stdio".to_string()],
4893 enabled: true,
4894 auto_start: false,
4895 process_limits: ProcessLimits::default(),
4896 initialization_options: None,
4897 env: Default::default(),
4898 language_id_overrides: Default::default(),
4899 name: None,
4900 only_features: None,
4901 except_features: None,
4902 root_markers: Default::default(),
4903 }]),
4904 );
4905
4906 lsp.insert(
4908 "css".to_string(),
4909 LspLanguageConfig::Multi(vec![LspServerConfig {
4910 command: "vscode-css-language-server".to_string(),
4911 args: vec!["--stdio".to_string()],
4912 enabled: true,
4913 auto_start: false,
4914 process_limits: ProcessLimits::default(),
4915 initialization_options: None,
4916 env: Default::default(),
4917 language_id_overrides: Default::default(),
4918 name: None,
4919 only_features: None,
4920 except_features: None,
4921 root_markers: Default::default(),
4922 }]),
4923 );
4924
4925 lsp.insert(
4927 "c".to_string(),
4928 LspLanguageConfig::Multi(vec![LspServerConfig {
4929 command: "clangd".to_string(),
4930 args: vec![],
4931 enabled: true,
4932 auto_start: false,
4933 process_limits: ProcessLimits::default(),
4934 initialization_options: None,
4935 env: Default::default(),
4936 language_id_overrides: Default::default(),
4937 name: None,
4938 only_features: None,
4939 except_features: None,
4940 root_markers: vec![
4941 "compile_commands.json".to_string(),
4942 "CMakeLists.txt".to_string(),
4943 "Makefile".to_string(),
4944 ".git".to_string(),
4945 ],
4946 }]),
4947 );
4948 lsp.insert(
4949 "cpp".to_string(),
4950 LspLanguageConfig::Multi(vec![LspServerConfig {
4951 command: "clangd".to_string(),
4952 args: vec![],
4953 enabled: true,
4954 auto_start: false,
4955 process_limits: ProcessLimits::default(),
4956 initialization_options: None,
4957 env: Default::default(),
4958 language_id_overrides: Default::default(),
4959 name: None,
4960 only_features: None,
4961 except_features: None,
4962 root_markers: vec![
4963 "compile_commands.json".to_string(),
4964 "CMakeLists.txt".to_string(),
4965 "Makefile".to_string(),
4966 ".git".to_string(),
4967 ],
4968 }]),
4969 );
4970
4971 lsp.insert(
4973 "go".to_string(),
4974 LspLanguageConfig::Multi(vec![LspServerConfig {
4975 command: "gopls".to_string(),
4976 args: vec![],
4977 enabled: true,
4978 auto_start: false,
4979 process_limits: ProcessLimits::default(),
4980 initialization_options: None,
4981 env: Default::default(),
4982 language_id_overrides: Default::default(),
4983 name: None,
4984 only_features: None,
4985 except_features: None,
4986 root_markers: vec![
4987 "go.mod".to_string(),
4988 "go.work".to_string(),
4989 ".git".to_string(),
4990 ],
4991 }]),
4992 );
4993
4994 lsp.insert(
4996 "json".to_string(),
4997 LspLanguageConfig::Multi(vec![LspServerConfig {
4998 command: "vscode-json-language-server".to_string(),
4999 args: vec!["--stdio".to_string()],
5000 enabled: true,
5001 auto_start: false,
5002 process_limits: ProcessLimits::default(),
5003 initialization_options: None,
5004 env: Default::default(),
5005 language_id_overrides: Default::default(),
5006 name: None,
5007 only_features: None,
5008 except_features: None,
5009 root_markers: Default::default(),
5010 }]),
5011 );
5012
5013 lsp.insert(
5015 "csharp".to_string(),
5016 LspLanguageConfig::Multi(vec![LspServerConfig {
5017 command: "csharp-ls".to_string(),
5018 args: vec![],
5019 enabled: true,
5020 auto_start: false,
5021 process_limits: ProcessLimits::default(),
5022 initialization_options: None,
5023 env: Default::default(),
5024 language_id_overrides: Default::default(),
5025 name: None,
5026 only_features: None,
5027 except_features: None,
5028 root_markers: vec![
5029 "*.csproj".to_string(),
5030 "*.sln".to_string(),
5031 ".git".to_string(),
5032 ],
5033 }]),
5034 );
5035
5036 lsp.insert(
5039 "odin".to_string(),
5040 LspLanguageConfig::Multi(vec![LspServerConfig {
5041 command: "ols".to_string(),
5042 args: vec![],
5043 enabled: true,
5044 auto_start: false,
5045 process_limits: ProcessLimits::default(),
5046 initialization_options: None,
5047 env: Default::default(),
5048 language_id_overrides: Default::default(),
5049 name: None,
5050 only_features: None,
5051 except_features: None,
5052 root_markers: Default::default(),
5053 }]),
5054 );
5055
5056 lsp.insert(
5059 "zig".to_string(),
5060 LspLanguageConfig::Multi(vec![LspServerConfig {
5061 command: "zls".to_string(),
5062 args: vec![],
5063 enabled: true,
5064 auto_start: false,
5065 process_limits: ProcessLimits::default(),
5066 initialization_options: None,
5067 env: Default::default(),
5068 language_id_overrides: Default::default(),
5069 name: None,
5070 only_features: None,
5071 except_features: None,
5072 root_markers: Default::default(),
5073 }]),
5074 );
5075
5076 lsp.insert(
5079 "java".to_string(),
5080 LspLanguageConfig::Multi(vec![LspServerConfig {
5081 command: "jdtls".to_string(),
5082 args: vec![],
5083 enabled: true,
5084 auto_start: false,
5085 process_limits: ProcessLimits::default(),
5086 initialization_options: None,
5087 env: Default::default(),
5088 language_id_overrides: Default::default(),
5089 name: None,
5090 only_features: None,
5091 except_features: None,
5092 root_markers: vec![
5093 "pom.xml".to_string(),
5094 "build.gradle".to_string(),
5095 "build.gradle.kts".to_string(),
5096 ".git".to_string(),
5097 ],
5098 }]),
5099 );
5100
5101 lsp.insert(
5104 "latex".to_string(),
5105 LspLanguageConfig::Multi(vec![LspServerConfig {
5106 command: "texlab".to_string(),
5107 args: vec![],
5108 enabled: true,
5109 auto_start: false,
5110 process_limits: ProcessLimits::default(),
5111 initialization_options: None,
5112 env: Default::default(),
5113 language_id_overrides: Default::default(),
5114 name: None,
5115 only_features: None,
5116 except_features: None,
5117 root_markers: Default::default(),
5118 }]),
5119 );
5120
5121 lsp.insert(
5124 "markdown".to_string(),
5125 LspLanguageConfig::Multi(vec![LspServerConfig {
5126 command: "marksman".to_string(),
5127 args: vec!["server".to_string()],
5128 enabled: true,
5129 auto_start: false,
5130 process_limits: ProcessLimits::default(),
5131 initialization_options: None,
5132 env: Default::default(),
5133 language_id_overrides: Default::default(),
5134 name: None,
5135 only_features: None,
5136 except_features: None,
5137 root_markers: Default::default(),
5138 }]),
5139 );
5140
5141 lsp.insert(
5144 "templ".to_string(),
5145 LspLanguageConfig::Multi(vec![LspServerConfig {
5146 command: "templ".to_string(),
5147 args: vec!["lsp".to_string()],
5148 enabled: true,
5149 auto_start: false,
5150 process_limits: ProcessLimits::default(),
5151 initialization_options: None,
5152 env: Default::default(),
5153 language_id_overrides: Default::default(),
5154 name: None,
5155 only_features: None,
5156 except_features: None,
5157 root_markers: Default::default(),
5158 }]),
5159 );
5160
5161 lsp.insert(
5164 "typst".to_string(),
5165 LspLanguageConfig::Multi(vec![LspServerConfig {
5166 command: "tinymist".to_string(),
5167 args: vec![],
5168 enabled: true,
5169 auto_start: false,
5170 process_limits: ProcessLimits::default(),
5171 initialization_options: None,
5172 env: Default::default(),
5173 language_id_overrides: Default::default(),
5174 name: None,
5175 only_features: None,
5176 except_features: None,
5177 root_markers: Default::default(),
5178 }]),
5179 );
5180
5181 lsp.insert(
5183 "bash".to_string(),
5184 LspLanguageConfig::Multi(vec![LspServerConfig {
5185 command: "bash-language-server".to_string(),
5186 args: vec!["start".to_string()],
5187 enabled: true,
5188 auto_start: false,
5189 process_limits: ProcessLimits::default(),
5190 initialization_options: None,
5191 env: Default::default(),
5192 language_id_overrides: Default::default(),
5193 name: None,
5194 only_features: None,
5195 except_features: None,
5196 root_markers: Default::default(),
5197 }]),
5198 );
5199
5200 lsp.insert(
5203 "lua".to_string(),
5204 LspLanguageConfig::Multi(vec![LspServerConfig {
5205 command: "lua-language-server".to_string(),
5206 args: vec![],
5207 enabled: true,
5208 auto_start: false,
5209 process_limits: ProcessLimits::default(),
5210 initialization_options: None,
5211 env: Default::default(),
5212 language_id_overrides: Default::default(),
5213 name: None,
5214 only_features: None,
5215 except_features: None,
5216 root_markers: vec![
5217 ".luarc.json".to_string(),
5218 ".luarc.jsonc".to_string(),
5219 ".luacheckrc".to_string(),
5220 ".stylua.toml".to_string(),
5221 ".git".to_string(),
5222 ],
5223 }]),
5224 );
5225
5226 lsp.insert(
5228 "ruby".to_string(),
5229 LspLanguageConfig::Multi(vec![LspServerConfig {
5230 command: "solargraph".to_string(),
5231 args: vec!["stdio".to_string()],
5232 enabled: true,
5233 auto_start: false,
5234 process_limits: ProcessLimits::default(),
5235 initialization_options: None,
5236 env: Default::default(),
5237 language_id_overrides: Default::default(),
5238 name: None,
5239 only_features: None,
5240 except_features: None,
5241 root_markers: vec![
5242 "Gemfile".to_string(),
5243 ".ruby-version".to_string(),
5244 ".git".to_string(),
5245 ],
5246 }]),
5247 );
5248
5249 lsp.insert(
5252 "php".to_string(),
5253 LspLanguageConfig::Multi(vec![LspServerConfig {
5254 command: "phpactor".to_string(),
5255 args: vec!["language-server".to_string()],
5256 enabled: true,
5257 auto_start: false,
5258 process_limits: ProcessLimits::default(),
5259 initialization_options: None,
5260 env: Default::default(),
5261 language_id_overrides: Default::default(),
5262 name: None,
5263 only_features: None,
5264 except_features: None,
5265 root_markers: vec!["composer.json".to_string(), ".git".to_string()],
5266 }]),
5267 );
5268
5269 lsp.insert(
5271 "yaml".to_string(),
5272 LspLanguageConfig::Multi(vec![LspServerConfig {
5273 command: "yaml-language-server".to_string(),
5274 args: vec!["--stdio".to_string()],
5275 enabled: true,
5276 auto_start: false,
5277 process_limits: ProcessLimits::default(),
5278 initialization_options: None,
5279 env: Default::default(),
5280 language_id_overrides: Default::default(),
5281 name: None,
5282 only_features: None,
5283 except_features: None,
5284 root_markers: Default::default(),
5285 }]),
5286 );
5287
5288 lsp.insert(
5291 "toml".to_string(),
5292 LspLanguageConfig::Multi(vec![LspServerConfig {
5293 command: "taplo".to_string(),
5294 args: vec!["lsp".to_string(), "stdio".to_string()],
5295 enabled: true,
5296 auto_start: false,
5297 process_limits: ProcessLimits::default(),
5298 initialization_options: None,
5299 env: Default::default(),
5300 language_id_overrides: Default::default(),
5301 name: None,
5302 only_features: None,
5303 except_features: None,
5304 root_markers: Default::default(),
5305 }]),
5306 );
5307
5308 lsp.insert(
5311 "dart".to_string(),
5312 LspLanguageConfig::Multi(vec![LspServerConfig {
5313 command: "dart".to_string(),
5314 args: vec!["language-server".to_string(), "--protocol=lsp".to_string()],
5315 enabled: true,
5316 auto_start: false,
5317 process_limits: ProcessLimits::default(),
5318 initialization_options: None,
5319 env: Default::default(),
5320 language_id_overrides: Default::default(),
5321 name: None,
5322 only_features: None,
5323 except_features: None,
5324 root_markers: vec!["pubspec.yaml".to_string(), ".git".to_string()],
5325 }]),
5326 );
5327
5328 lsp.insert(
5331 "nushell".to_string(),
5332 LspLanguageConfig::Multi(vec![LspServerConfig {
5333 command: "nu".to_string(),
5334 args: vec!["--lsp".to_string()],
5335 enabled: true,
5336 auto_start: false,
5337 process_limits: ProcessLimits::default(),
5338 initialization_options: None,
5339 env: Default::default(),
5340 language_id_overrides: Default::default(),
5341 name: None,
5342 only_features: None,
5343 except_features: None,
5344 root_markers: Default::default(),
5345 }]),
5346 );
5347
5348 lsp.insert(
5351 "solidity".to_string(),
5352 LspLanguageConfig::Multi(vec![LspServerConfig {
5353 command: "nomicfoundation-solidity-language-server".to_string(),
5354 args: vec!["--stdio".to_string()],
5355 enabled: true,
5356 auto_start: false,
5357 process_limits: ProcessLimits::default(),
5358 initialization_options: None,
5359 env: Default::default(),
5360 language_id_overrides: Default::default(),
5361 name: None,
5362 only_features: None,
5363 except_features: None,
5364 root_markers: Default::default(),
5365 }]),
5366 );
5367
5368 lsp.insert(
5373 "terraform".to_string(),
5374 LspLanguageConfig::Multi(vec![LspServerConfig {
5375 command: "terraform-ls".to_string(),
5376 args: vec!["serve".to_string()],
5377 enabled: true,
5378 auto_start: false,
5379 process_limits: ProcessLimits::default(),
5380 initialization_options: None,
5381 env: Default::default(),
5382 language_id_overrides: Default::default(),
5383 name: None,
5384 only_features: None,
5385 except_features: None,
5386 root_markers: vec![
5387 "*.tf".to_string(),
5388 ".terraform".to_string(),
5389 ".git".to_string(),
5390 ],
5391 }]),
5392 );
5393
5394 lsp.insert(
5397 "cmake".to_string(),
5398 LspLanguageConfig::Multi(vec![LspServerConfig {
5399 command: "cmake-language-server".to_string(),
5400 args: vec![],
5401 enabled: true,
5402 auto_start: false,
5403 process_limits: ProcessLimits::default(),
5404 initialization_options: None,
5405 env: Default::default(),
5406 language_id_overrides: Default::default(),
5407 name: None,
5408 only_features: None,
5409 except_features: None,
5410 root_markers: vec!["CMakeLists.txt".to_string(), ".git".to_string()],
5411 }]),
5412 );
5413
5414 lsp.insert(
5417 "protobuf".to_string(),
5418 LspLanguageConfig::Multi(vec![LspServerConfig {
5419 command: "buf".to_string(),
5420 args: vec!["beta".to_string(), "lsp".to_string()],
5421 enabled: true,
5422 auto_start: false,
5423 process_limits: ProcessLimits::default(),
5424 initialization_options: None,
5425 env: Default::default(),
5426 language_id_overrides: Default::default(),
5427 name: None,
5428 only_features: None,
5429 except_features: None,
5430 root_markers: Default::default(),
5431 }]),
5432 );
5433
5434 lsp.insert(
5437 "graphql".to_string(),
5438 LspLanguageConfig::Multi(vec![LspServerConfig {
5439 command: "graphql-lsp".to_string(),
5440 args: vec!["server".to_string(), "-m".to_string(), "stream".to_string()],
5441 enabled: true,
5442 auto_start: false,
5443 process_limits: ProcessLimits::default(),
5444 initialization_options: None,
5445 env: Default::default(),
5446 language_id_overrides: Default::default(),
5447 name: None,
5448 only_features: None,
5449 except_features: None,
5450 root_markers: Default::default(),
5451 }]),
5452 );
5453
5454 lsp.insert(
5457 "sql".to_string(),
5458 LspLanguageConfig::Multi(vec![LspServerConfig {
5459 command: "sqls".to_string(),
5460 args: vec![],
5461 enabled: true,
5462 auto_start: false,
5463 process_limits: ProcessLimits::default(),
5464 initialization_options: None,
5465 env: Default::default(),
5466 language_id_overrides: Default::default(),
5467 name: None,
5468 only_features: None,
5469 except_features: None,
5470 root_markers: Default::default(),
5471 }]),
5472 );
5473
5474 lsp.insert(
5478 "vue".to_string(),
5479 LspLanguageConfig::Multi(vec![LspServerConfig {
5480 command: "vue-language-server".to_string(),
5481 args: vec!["--stdio".to_string()],
5482 enabled: true,
5483 auto_start: false,
5484 process_limits: ProcessLimits::default(),
5485 initialization_options: None,
5486 env: Default::default(),
5487 language_id_overrides: Default::default(),
5488 name: None,
5489 only_features: None,
5490 except_features: None,
5491 root_markers: Default::default(),
5492 }]),
5493 );
5494
5495 lsp.insert(
5497 "svelte".to_string(),
5498 LspLanguageConfig::Multi(vec![LspServerConfig {
5499 command: "svelteserver".to_string(),
5500 args: vec!["--stdio".to_string()],
5501 enabled: true,
5502 auto_start: false,
5503 process_limits: ProcessLimits::default(),
5504 initialization_options: None,
5505 env: Default::default(),
5506 language_id_overrides: Default::default(),
5507 name: None,
5508 only_features: None,
5509 except_features: None,
5510 root_markers: Default::default(),
5511 }]),
5512 );
5513
5514 lsp.insert(
5516 "astro".to_string(),
5517 LspLanguageConfig::Multi(vec![LspServerConfig {
5518 command: "astro-ls".to_string(),
5519 args: vec!["--stdio".to_string()],
5520 enabled: true,
5521 auto_start: false,
5522 process_limits: ProcessLimits::default(),
5523 initialization_options: None,
5524 env: Default::default(),
5525 language_id_overrides: Default::default(),
5526 name: None,
5527 only_features: None,
5528 except_features: None,
5529 root_markers: Default::default(),
5530 }]),
5531 );
5532
5533 lsp.insert(
5535 "tailwindcss".to_string(),
5536 LspLanguageConfig::Multi(vec![LspServerConfig {
5537 command: "tailwindcss-language-server".to_string(),
5538 args: vec!["--stdio".to_string()],
5539 enabled: true,
5540 auto_start: false,
5541 process_limits: ProcessLimits::default(),
5542 initialization_options: None,
5543 env: Default::default(),
5544 language_id_overrides: Default::default(),
5545 name: None,
5546 only_features: None,
5547 except_features: None,
5548 root_markers: Default::default(),
5549 }]),
5550 );
5551
5552 lsp.insert(
5557 "nix".to_string(),
5558 LspLanguageConfig::Multi(vec![LspServerConfig {
5559 command: "nil".to_string(),
5560 args: vec![],
5561 enabled: true,
5562 auto_start: false,
5563 process_limits: ProcessLimits::default(),
5564 initialization_options: None,
5565 env: Default::default(),
5566 language_id_overrides: Default::default(),
5567 name: None,
5568 only_features: None,
5569 except_features: None,
5570 root_markers: Default::default(),
5571 }]),
5572 );
5573
5574 lsp.insert(
5577 "kotlin".to_string(),
5578 LspLanguageConfig::Multi(vec![LspServerConfig {
5579 command: "kotlin-language-server".to_string(),
5580 args: vec![],
5581 enabled: true,
5582 auto_start: false,
5583 process_limits: ProcessLimits::default(),
5584 initialization_options: None,
5585 env: Default::default(),
5586 language_id_overrides: Default::default(),
5587 name: None,
5588 only_features: None,
5589 except_features: None,
5590 root_markers: Default::default(),
5591 }]),
5592 );
5593
5594 lsp.insert(
5596 "swift".to_string(),
5597 LspLanguageConfig::Multi(vec![LspServerConfig {
5598 command: "sourcekit-lsp".to_string(),
5599 args: vec![],
5600 enabled: true,
5601 auto_start: false,
5602 process_limits: ProcessLimits::default(),
5603 initialization_options: None,
5604 env: Default::default(),
5605 language_id_overrides: Default::default(),
5606 name: None,
5607 only_features: None,
5608 except_features: None,
5609 root_markers: Default::default(),
5610 }]),
5611 );
5612
5613 lsp.insert(
5616 "scala".to_string(),
5617 LspLanguageConfig::Multi(vec![LspServerConfig {
5618 command: "metals".to_string(),
5619 args: vec![],
5620 enabled: true,
5621 auto_start: false,
5622 process_limits: ProcessLimits::default(),
5623 initialization_options: None,
5624 env: Default::default(),
5625 language_id_overrides: Default::default(),
5626 name: None,
5627 only_features: None,
5628 except_features: None,
5629 root_markers: Default::default(),
5630 }]),
5631 );
5632
5633 lsp.insert(
5636 "elixir".to_string(),
5637 LspLanguageConfig::Multi(vec![LspServerConfig {
5638 command: "elixir-ls".to_string(),
5639 args: vec![],
5640 enabled: true,
5641 auto_start: false,
5642 process_limits: ProcessLimits::default(),
5643 initialization_options: None,
5644 env: Default::default(),
5645 language_id_overrides: Default::default(),
5646 name: None,
5647 only_features: None,
5648 except_features: None,
5649 root_markers: Default::default(),
5650 }]),
5651 );
5652
5653 lsp.insert(
5655 "erlang".to_string(),
5656 LspLanguageConfig::Multi(vec![LspServerConfig {
5657 command: "erlang_ls".to_string(),
5658 args: vec![],
5659 enabled: true,
5660 auto_start: false,
5661 process_limits: ProcessLimits::default(),
5662 initialization_options: None,
5663 env: Default::default(),
5664 language_id_overrides: Default::default(),
5665 name: None,
5666 only_features: None,
5667 except_features: None,
5668 root_markers: Default::default(),
5669 }]),
5670 );
5671
5672 lsp.insert(
5675 "haskell".to_string(),
5676 LspLanguageConfig::Multi(vec![LspServerConfig {
5677 command: "haskell-language-server-wrapper".to_string(),
5678 args: vec!["--lsp".to_string()],
5679 enabled: true,
5680 auto_start: false,
5681 process_limits: ProcessLimits::default(),
5682 initialization_options: None,
5683 env: Default::default(),
5684 language_id_overrides: Default::default(),
5685 name: None,
5686 only_features: None,
5687 except_features: None,
5688 root_markers: Default::default(),
5689 }]),
5690 );
5691
5692 lsp.insert(
5695 "ocaml".to_string(),
5696 LspLanguageConfig::Multi(vec![LspServerConfig {
5697 command: "ocamllsp".to_string(),
5698 args: vec![],
5699 enabled: true,
5700 auto_start: false,
5701 process_limits: ProcessLimits::default(),
5702 initialization_options: None,
5703 env: Default::default(),
5704 language_id_overrides: Default::default(),
5705 name: None,
5706 only_features: None,
5707 except_features: None,
5708 root_markers: Default::default(),
5709 }]),
5710 );
5711
5712 lsp.insert(
5715 "clojure".to_string(),
5716 LspLanguageConfig::Multi(vec![LspServerConfig {
5717 command: "clojure-lsp".to_string(),
5718 args: vec![],
5719 enabled: true,
5720 auto_start: false,
5721 process_limits: ProcessLimits::default(),
5722 initialization_options: None,
5723 env: Default::default(),
5724 language_id_overrides: Default::default(),
5725 name: None,
5726 only_features: None,
5727 except_features: None,
5728 root_markers: Default::default(),
5729 }]),
5730 );
5731
5732 lsp.insert(
5735 "r".to_string(),
5736 LspLanguageConfig::Multi(vec![LspServerConfig {
5737 command: "R".to_string(),
5738 args: vec![
5739 "--vanilla".to_string(),
5740 "-e".to_string(),
5741 "languageserver::run()".to_string(),
5742 ],
5743 enabled: true,
5744 auto_start: false,
5745 process_limits: ProcessLimits::default(),
5746 initialization_options: None,
5747 env: Default::default(),
5748 language_id_overrides: Default::default(),
5749 name: None,
5750 only_features: None,
5751 except_features: None,
5752 root_markers: Default::default(),
5753 }]),
5754 );
5755
5756 lsp.insert(
5759 "julia".to_string(),
5760 LspLanguageConfig::Multi(vec![LspServerConfig {
5761 command: "julia".to_string(),
5762 args: vec![
5763 "--startup-file=no".to_string(),
5764 "--history-file=no".to_string(),
5765 "-e".to_string(),
5766 "using LanguageServer; runserver()".to_string(),
5767 ],
5768 enabled: true,
5769 auto_start: false,
5770 process_limits: ProcessLimits::default(),
5771 initialization_options: None,
5772 env: Default::default(),
5773 language_id_overrides: Default::default(),
5774 name: None,
5775 only_features: None,
5776 except_features: None,
5777 root_markers: Default::default(),
5778 }]),
5779 );
5780
5781 lsp.insert(
5784 "perl".to_string(),
5785 LspLanguageConfig::Multi(vec![LspServerConfig {
5786 command: "perlnavigator".to_string(),
5787 args: vec!["--stdio".to_string()],
5788 enabled: true,
5789 auto_start: false,
5790 process_limits: ProcessLimits::default(),
5791 initialization_options: None,
5792 env: Default::default(),
5793 language_id_overrides: Default::default(),
5794 name: None,
5795 only_features: None,
5796 except_features: None,
5797 root_markers: Default::default(),
5798 }]),
5799 );
5800
5801 lsp.insert(
5804 "nim".to_string(),
5805 LspLanguageConfig::Multi(vec![LspServerConfig {
5806 command: "nimlangserver".to_string(),
5807 args: vec![],
5808 enabled: true,
5809 auto_start: false,
5810 process_limits: ProcessLimits::default(),
5811 initialization_options: None,
5812 env: Default::default(),
5813 language_id_overrides: Default::default(),
5814 name: None,
5815 only_features: None,
5816 except_features: None,
5817 root_markers: Default::default(),
5818 }]),
5819 );
5820
5821 lsp.insert(
5823 "gleam".to_string(),
5824 LspLanguageConfig::Multi(vec![LspServerConfig {
5825 command: "gleam".to_string(),
5826 args: vec!["lsp".to_string()],
5827 enabled: true,
5828 auto_start: false,
5829 process_limits: ProcessLimits::default(),
5830 initialization_options: None,
5831 env: Default::default(),
5832 language_id_overrides: Default::default(),
5833 name: None,
5834 only_features: None,
5835 except_features: None,
5836 root_markers: Default::default(),
5837 }]),
5838 );
5839
5840 lsp.insert(
5843 "fsharp".to_string(),
5844 LspLanguageConfig::Multi(vec![LspServerConfig {
5845 command: "fsautocomplete".to_string(),
5846 args: vec!["--adaptive-lsp-server-enabled".to_string()],
5847 enabled: true,
5848 auto_start: false,
5849 process_limits: ProcessLimits::default(),
5850 initialization_options: None,
5851 env: Default::default(),
5852 language_id_overrides: Default::default(),
5853 name: None,
5854 only_features: None,
5855 except_features: None,
5856 root_markers: Default::default(),
5857 }]),
5858 );
5859 }
5860 pub fn validate(&self) -> Result<(), ConfigError> {
5861 if self.editor.tab_size == 0 {
5863 return Err(ConfigError::ValidationError(
5864 "tab_size must be greater than 0".to_string(),
5865 ));
5866 }
5867
5868 if self.editor.scroll_offset > 100 {
5870 return Err(ConfigError::ValidationError(
5871 "scroll_offset must be <= 100".to_string(),
5872 ));
5873 }
5874
5875 for binding in &self.keybindings {
5877 if binding.key.is_empty() {
5878 return Err(ConfigError::ValidationError(
5879 "keybinding key cannot be empty".to_string(),
5880 ));
5881 }
5882 if binding.action.is_empty() {
5883 return Err(ConfigError::ValidationError(
5884 "keybinding action cannot be empty".to_string(),
5885 ));
5886 }
5887 }
5888
5889 Ok(())
5890 }
5891}
5892
5893#[derive(Debug)]
5895pub enum ConfigError {
5896 IoError(String),
5897 ParseError(String),
5898 SerializeError(String),
5899 ValidationError(String),
5900}
5901
5902impl std::fmt::Display for ConfigError {
5903 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5904 match self {
5905 Self::IoError(msg) => write!(f, "IO error: {msg}"),
5906 Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
5907 Self::SerializeError(msg) => write!(f, "Serialize error: {msg}"),
5908 Self::ValidationError(msg) => write!(f, "Validation error: {msg}"),
5909 }
5910 }
5911}
5912
5913impl std::error::Error for ConfigError {}
5914
5915#[cfg(test)]
5916mod tests {
5917 use super::*;
5918
5919 #[test]
5920 fn test_default_config() {
5921 let config = Config::default();
5922 assert_eq!(config.editor.tab_size, 4);
5923 assert!(config.editor.line_numbers);
5924 assert!(config.editor.syntax_highlighting);
5925 assert!(config.keybindings.is_empty());
5928 let resolved = config.resolve_keymap(&config.active_keybinding_map);
5930 assert!(!resolved.is_empty());
5931 }
5932
5933 #[test]
5934 fn test_all_builtin_keymaps_loadable() {
5935 for name in KeybindingMapName::BUILTIN_OPTIONS {
5936 let keymap = Config::load_builtin_keymap(name);
5937 assert!(keymap.is_some(), "Failed to load builtin keymap '{}'", name);
5938 }
5939 }
5940
5941 #[test]
5942 fn test_config_validation() {
5943 let mut config = Config::default();
5944 assert!(config.validate().is_ok());
5945
5946 config.editor.tab_size = 0;
5947 assert!(config.validate().is_err());
5948 }
5949
5950 #[test]
5951 fn test_macos_keymap_inherits_enter_bindings() {
5952 let config = Config::default();
5953 let bindings = config.resolve_keymap("macos");
5954
5955 let enter_bindings: Vec<_> = bindings.iter().filter(|b| b.key == "Enter").collect();
5956 assert!(
5957 !enter_bindings.is_empty(),
5958 "macos keymap should inherit Enter bindings from default, got {} Enter bindings",
5959 enter_bindings.len()
5960 );
5961 let has_insert_newline = enter_bindings.iter().any(|b| b.action == "insert_newline");
5963 assert!(
5964 has_insert_newline,
5965 "macos keymap should have insert_newline action for Enter key"
5966 );
5967 }
5968
5969 #[test]
5970 fn test_config_serialize_deserialize() {
5971 let config = Config::default();
5973
5974 let json = serde_json::to_string_pretty(&config).unwrap();
5976
5977 let loaded: Config = serde_json::from_str(&json).unwrap();
5979
5980 assert_eq!(config.editor.tab_size, loaded.editor.tab_size);
5981 assert_eq!(config.theme, loaded.theme);
5982 }
5983
5984 #[test]
5985 fn test_config_with_custom_keybinding() {
5986 let json = r#"{
5987 "editor": {
5988 "tab_size": 2
5989 },
5990 "keybindings": [
5991 {
5992 "key": "x",
5993 "modifiers": ["ctrl", "shift"],
5994 "action": "custom_action",
5995 "args": {},
5996 "when": null
5997 }
5998 ]
5999 }"#;
6000
6001 let config: Config = serde_json::from_str(json).unwrap();
6002 assert_eq!(config.editor.tab_size, 2);
6003 assert_eq!(config.keybindings.len(), 1);
6004 assert_eq!(config.keybindings[0].key, "x");
6005 assert_eq!(config.keybindings[0].modifiers.len(), 2);
6006 }
6007
6008 #[test]
6009 fn test_sparse_config_merges_with_defaults() {
6010 let temp_dir = tempfile::tempdir().unwrap();
6012 let config_path = temp_dir.path().join("config.json");
6013
6014 let sparse_config = r#"{
6016 "lsp": {
6017 "rust": {
6018 "command": "custom-rust-analyzer",
6019 "args": ["--custom-arg"]
6020 }
6021 }
6022 }"#;
6023 std::fs::write(&config_path, sparse_config).unwrap();
6024
6025 let loaded = Config::load_from_file(&config_path).unwrap();
6027
6028 assert!(loaded.lsp.contains_key("rust"));
6030 assert_eq!(
6031 loaded.lsp["rust"].as_slice()[0].command,
6032 "custom-rust-analyzer".to_string()
6033 );
6034
6035 assert!(
6037 loaded.lsp.contains_key("python"),
6038 "python LSP should be merged from defaults"
6039 );
6040 assert!(
6041 loaded.lsp.contains_key("typescript"),
6042 "typescript LSP should be merged from defaults"
6043 );
6044 assert!(
6045 loaded.lsp.contains_key("javascript"),
6046 "javascript LSP should be merged from defaults"
6047 );
6048
6049 assert!(loaded.languages.contains_key("rust"));
6051 assert!(loaded.languages.contains_key("python"));
6052 assert!(loaded.languages.contains_key("typescript"));
6053 }
6054
6055 #[test]
6056 fn test_empty_config_gets_all_defaults() {
6057 let temp_dir = tempfile::tempdir().unwrap();
6058 let config_path = temp_dir.path().join("config.json");
6059
6060 std::fs::write(&config_path, "{}").unwrap();
6062
6063 let loaded = Config::load_from_file(&config_path).unwrap();
6064 let defaults = Config::default();
6065
6066 assert_eq!(loaded.lsp.len(), defaults.lsp.len());
6068
6069 assert_eq!(loaded.languages.len(), defaults.languages.len());
6071 }
6072
6073 #[test]
6074 fn test_dynamic_submenu_expansion() {
6075 let temp_dir = tempfile::tempdir().unwrap();
6077 let themes_dir = temp_dir.path().to_path_buf();
6078
6079 let dynamic = MenuItem::DynamicSubmenu {
6080 label: "Test".to_string(),
6081 source: "copy_with_theme".to_string(),
6082 };
6083
6084 let expanded = dynamic.expand_dynamic(&themes_dir);
6085
6086 match expanded {
6088 MenuItem::Submenu { label, items } => {
6089 assert_eq!(label, "Test");
6090 let loader = crate::view::theme::ThemeLoader::embedded_only();
6092 let registry = loader.load_all(&[]);
6093 assert_eq!(items.len(), registry.len());
6094
6095 for (item, theme_info) in items.iter().zip(registry.list().iter()) {
6097 match item {
6098 MenuItem::Action {
6099 label,
6100 action,
6101 args,
6102 ..
6103 } => {
6104 assert_eq!(label, &theme_info.name);
6105 assert_eq!(action, "copy_with_theme");
6106 assert_eq!(
6107 args.get("theme").and_then(|v| v.as_str()),
6108 Some(theme_info.name.as_str())
6109 );
6110 }
6111 _ => panic!("Expected Action item"),
6112 }
6113 }
6114 }
6115 _ => panic!("Expected Submenu after expansion"),
6116 }
6117 }
6118
6119 #[test]
6120 fn test_non_dynamic_item_unchanged() {
6121 let temp_dir = tempfile::tempdir().unwrap();
6123 let themes_dir = temp_dir.path();
6124
6125 let action = MenuItem::Action {
6126 label: "Test".to_string(),
6127 action: "test".to_string(),
6128 args: HashMap::new(),
6129 when: None,
6130 checkbox: None,
6131 };
6132
6133 let expanded = action.expand_dynamic(themes_dir);
6134 match expanded {
6135 MenuItem::Action { label, action, .. } => {
6136 assert_eq!(label, "Test");
6137 assert_eq!(action, "test");
6138 }
6139 _ => panic!("Action should remain Action after expand_dynamic"),
6140 }
6141 }
6142
6143 #[test]
6144 fn test_buffer_config_uses_global_defaults() {
6145 let config = Config::default();
6146 let buffer_config = BufferConfig::resolve(&config, None);
6147
6148 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
6149 assert_eq!(buffer_config.auto_indent, config.editor.auto_indent);
6150 assert!(!buffer_config.use_tabs); assert!(buffer_config.whitespace.any_tabs()); assert!(buffer_config.formatter.is_none());
6153 assert!(!buffer_config.format_on_save);
6154 }
6155
6156 #[test]
6157 fn test_buffer_config_applies_language_overrides() {
6158 let mut config = Config::default();
6159
6160 config.languages.insert(
6162 "go".to_string(),
6163 LanguageConfig {
6164 extensions: vec!["go".to_string()],
6165 filenames: vec![],
6166 grammar: "go".to_string(),
6167 comment_prefix: Some("//".to_string()),
6168 auto_indent: true,
6169 auto_close: None,
6170 auto_surround: None,
6171 textmate_grammar: None,
6172 show_whitespace_tabs: false, line_wrap: None,
6174 wrap_column: None,
6175 page_view: None,
6176 page_width: None,
6177 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
6180 command: "gofmt".to_string(),
6181 args: vec![],
6182 stdin: true,
6183 timeout_ms: 10000,
6184 }),
6185 format_on_save: true,
6186 on_save: vec![],
6187 word_characters: None,
6188 },
6189 );
6190
6191 let buffer_config = BufferConfig::resolve(&config, Some("go"));
6192
6193 assert_eq!(buffer_config.tab_size, 8);
6194 assert!(buffer_config.use_tabs);
6195 assert!(!buffer_config.whitespace.any_tabs()); assert!(buffer_config.format_on_save);
6197 assert!(buffer_config.formatter.is_some());
6198 assert_eq!(buffer_config.formatter.as_ref().unwrap().command, "gofmt");
6199 }
6200
6201 #[test]
6202 fn test_buffer_config_unknown_language_uses_global() {
6203 let config = Config::default();
6204 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
6205
6206 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
6208 assert!(!buffer_config.use_tabs);
6209 }
6210
6211 #[test]
6212 fn test_buffer_config_per_language_line_wrap() {
6213 let mut config = Config::default();
6214 config.editor.line_wrap = false;
6215
6216 config.languages.insert(
6218 "markdown".to_string(),
6219 LanguageConfig {
6220 extensions: vec!["md".to_string()],
6221 line_wrap: Some(true),
6222 ..Default::default()
6223 },
6224 );
6225
6226 let md_config = BufferConfig::resolve(&config, Some("markdown"));
6228 assert!(md_config.line_wrap, "Markdown should have line_wrap=true");
6229
6230 let other_config = BufferConfig::resolve(&config, Some("rust"));
6232 assert!(
6233 !other_config.line_wrap,
6234 "Non-configured languages should use global line_wrap=false"
6235 );
6236
6237 let no_lang_config = BufferConfig::resolve(&config, None);
6239 assert!(
6240 !no_lang_config.line_wrap,
6241 "No language should use global line_wrap=false"
6242 );
6243 }
6244
6245 #[test]
6246 fn test_buffer_config_per_language_wrap_column() {
6247 let mut config = Config::default();
6248 config.editor.wrap_column = Some(120);
6249
6250 config.languages.insert(
6252 "markdown".to_string(),
6253 LanguageConfig {
6254 extensions: vec!["md".to_string()],
6255 wrap_column: Some(80),
6256 ..Default::default()
6257 },
6258 );
6259
6260 let md_config = BufferConfig::resolve(&config, Some("markdown"));
6262 assert_eq!(md_config.wrap_column, Some(80));
6263
6264 let other_config = BufferConfig::resolve(&config, Some("rust"));
6266 assert_eq!(other_config.wrap_column, Some(120));
6267
6268 let no_lang_config = BufferConfig::resolve(&config, None);
6270 assert_eq!(no_lang_config.wrap_column, Some(120));
6271 }
6272
6273 #[test]
6274 fn test_buffer_config_indent_string() {
6275 let config = Config::default();
6276
6277 let spaces_config = BufferConfig::resolve(&config, None);
6279 assert_eq!(spaces_config.indent_string(), " "); let mut config_with_tabs = Config::default();
6283 config_with_tabs.languages.insert(
6284 "makefile".to_string(),
6285 LanguageConfig {
6286 use_tabs: Some(true),
6287 tab_size: Some(8),
6288 ..Default::default()
6289 },
6290 );
6291 let tabs_config = BufferConfig::resolve(&config_with_tabs, Some("makefile"));
6292 assert_eq!(tabs_config.indent_string(), "\t");
6293 }
6294
6295 #[test]
6296 fn test_buffer_config_global_use_tabs_inherited() {
6297 let mut config = Config::default();
6300 config.editor.use_tabs = true;
6301
6302 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
6304 assert!(buffer_config.use_tabs);
6305
6306 let buffer_config = BufferConfig::resolve(&config, None);
6308 assert!(buffer_config.use_tabs);
6309
6310 config.languages.insert(
6312 "python".to_string(),
6313 LanguageConfig {
6314 use_tabs: Some(false),
6315 ..Default::default()
6316 },
6317 );
6318 let buffer_config = BufferConfig::resolve(&config, Some("python"));
6319 assert!(!buffer_config.use_tabs);
6320
6321 config.languages.insert(
6323 "rust".to_string(),
6324 LanguageConfig {
6325 use_tabs: None,
6326 ..Default::default()
6327 },
6328 );
6329 let buffer_config = BufferConfig::resolve(&config, Some("rust"));
6330 assert!(buffer_config.use_tabs);
6331 }
6332
6333 #[test]
6339 #[cfg(feature = "runtime")]
6340 fn test_lsp_languages_have_language_config() {
6341 let config = Config::default();
6342 let exceptions = ["tailwindcss"];
6343 for lsp_key in config.lsp.keys() {
6344 if exceptions.contains(&lsp_key.as_str()) {
6345 continue;
6346 }
6347 assert!(
6348 config.languages.contains_key(lsp_key),
6349 "LSP config key '{}' has no matching entry in default_languages(). \
6350 Add a LanguageConfig with the correct file extensions so detect_language() \
6351 can map files to this language.",
6352 lsp_key
6353 );
6354 }
6355 }
6356
6357 #[test]
6358 #[cfg(feature = "runtime")]
6359 fn test_default_config_has_quicklsp_in_universal_lsp() {
6360 let config = Config::default();
6361 assert!(
6362 config.universal_lsp.contains_key("quicklsp"),
6363 "Default config should contain quicklsp in universal_lsp"
6364 );
6365 let quicklsp = &config.universal_lsp["quicklsp"];
6366 let server = &quicklsp.as_slice()[0];
6367 assert_eq!(server.command, "quicklsp");
6368 assert!(!server.enabled, "quicklsp should be disabled by default");
6369 assert_eq!(server.name.as_deref(), Some("QuickLSP"));
6370 assert!(
6374 server.only_features.is_none(),
6375 "quicklsp must not default to a feature whitelist"
6376 );
6377 assert!(server.except_features.is_none());
6378 }
6379
6380 #[test]
6381 fn test_empty_config_preserves_universal_lsp_defaults() {
6382 let temp_dir = tempfile::tempdir().unwrap();
6383 let config_path = temp_dir.path().join("config.json");
6384
6385 std::fs::write(&config_path, "{}").unwrap();
6387
6388 let loaded = Config::load_from_file(&config_path).unwrap();
6389 let defaults = Config::default();
6390
6391 assert_eq!(
6393 loaded.universal_lsp.len(),
6394 defaults.universal_lsp.len(),
6395 "Empty config should preserve all default universal_lsp entries"
6396 );
6397 }
6398
6399 #[test]
6400 fn test_universal_lsp_config_merges_with_defaults() {
6401 let temp_dir = tempfile::tempdir().unwrap();
6402 let config_path = temp_dir.path().join("config.json");
6403
6404 let config_json = r#"{
6406 "universal_lsp": {
6407 "quicklsp": {
6408 "enabled": true
6409 }
6410 }
6411 }"#;
6412 std::fs::write(&config_path, config_json).unwrap();
6413
6414 let loaded = Config::load_from_file(&config_path).unwrap();
6415
6416 assert!(loaded.universal_lsp.contains_key("quicklsp"));
6418 let server = &loaded.universal_lsp["quicklsp"].as_slice()[0];
6419 assert!(server.enabled, "User override should enable quicklsp");
6420 assert_eq!(
6422 server.command, "quicklsp",
6423 "Default command should be merged when not specified by user"
6424 );
6425 }
6426
6427 #[test]
6428 fn test_universal_lsp_custom_server_added() {
6429 let temp_dir = tempfile::tempdir().unwrap();
6430 let config_path = temp_dir.path().join("config.json");
6431
6432 let config_json = r#"{
6434 "universal_lsp": {
6435 "my-custom-server": {
6436 "command": "my-server",
6437 "enabled": true,
6438 "auto_start": true
6439 }
6440 }
6441 }"#;
6442 std::fs::write(&config_path, config_json).unwrap();
6443
6444 let loaded = Config::load_from_file(&config_path).unwrap();
6445
6446 assert!(
6448 loaded.universal_lsp.contains_key("my-custom-server"),
6449 "Custom universal server should be loaded"
6450 );
6451 let server = &loaded.universal_lsp["my-custom-server"].as_slice()[0];
6452 assert_eq!(server.command, "my-server");
6453 assert!(server.enabled);
6454 assert!(server.auto_start);
6455
6456 assert!(
6458 loaded.universal_lsp.contains_key("quicklsp"),
6459 "Default quicklsp should be merged from defaults"
6460 );
6461 }
6462}