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)]
549#[serde(try_from = "String", into = "String")]
550pub enum StatusBarElement {
551 Filename,
553 Cursor,
555 CursorCompact,
557 Diagnostics,
559 CursorCount,
561 Messages,
563 Chord,
565 LineEnding,
567 Encoding,
569 Language,
571 Lsp,
573 Warnings,
575 Update,
577 Palette,
579 Clock,
581 RemoteIndicator,
586}
587
588impl TryFrom<String> for StatusBarElement {
589 type Error = String;
590 fn try_from(s: String) -> Result<Self, String> {
591 let inner = s
593 .strip_prefix('{')
594 .and_then(|s| s.strip_suffix('}'))
595 .unwrap_or(&s);
596 match inner {
597 "filename" => Ok(Self::Filename),
598 "cursor" => Ok(Self::Cursor),
599 "cursor:compact" => Ok(Self::CursorCompact),
600 "diagnostics" => Ok(Self::Diagnostics),
601 "cursor_count" => Ok(Self::CursorCount),
602 "messages" => Ok(Self::Messages),
603 "chord" => Ok(Self::Chord),
604 "line_ending" => Ok(Self::LineEnding),
605 "encoding" => Ok(Self::Encoding),
606 "language" => Ok(Self::Language),
607 "lsp" => Ok(Self::Lsp),
608 "warnings" => Ok(Self::Warnings),
609 "update" => Ok(Self::Update),
610 "palette" => Ok(Self::Palette),
611 "clock" => Ok(Self::Clock),
612 "remote" => Ok(Self::RemoteIndicator),
613 _ => Err(format!("Unknown status bar element: {}", s)),
614 }
615 }
616}
617
618impl From<StatusBarElement> for String {
619 fn from(e: StatusBarElement) -> String {
620 match e {
621 StatusBarElement::Filename => "{filename}".to_string(),
622 StatusBarElement::Cursor => "{cursor}".to_string(),
623 StatusBarElement::CursorCompact => "{cursor:compact}".to_string(),
624 StatusBarElement::Diagnostics => "{diagnostics}".to_string(),
625 StatusBarElement::CursorCount => "{cursor_count}".to_string(),
626 StatusBarElement::Messages => "{messages}".to_string(),
627 StatusBarElement::Chord => "{chord}".to_string(),
628 StatusBarElement::LineEnding => "{line_ending}".to_string(),
629 StatusBarElement::Encoding => "{encoding}".to_string(),
630 StatusBarElement::Language => "{language}".to_string(),
631 StatusBarElement::Lsp => "{lsp}".to_string(),
632 StatusBarElement::Warnings => "{warnings}".to_string(),
633 StatusBarElement::Update => "{update}".to_string(),
634 StatusBarElement::Palette => "{palette}".to_string(),
635 StatusBarElement::Clock => "{clock}".to_string(),
636 StatusBarElement::RemoteIndicator => "{remote}".to_string(),
637 }
638 }
639}
640
641impl schemars::JsonSchema for StatusBarElement {
642 fn schema_name() -> std::borrow::Cow<'static, str> {
643 std::borrow::Cow::Borrowed("StatusBarElement")
644 }
645 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
646 schemars::json_schema!({
647 "type": "string",
648 "x-dual-list-options": [
649 {"value": "{filename}", "name": "Filename"},
650 {"value": "{cursor}", "name": "Cursor"},
651 {"value": "{cursor:compact}", "name": "Cursor (compact)"},
652 {"value": "{diagnostics}", "name": "Diagnostics"},
653 {"value": "{cursor_count}", "name": "Cursor Count"},
654 {"value": "{messages}", "name": "Messages"},
655 {"value": "{chord}", "name": "Chord"},
656 {"value": "{line_ending}", "name": "Line Ending"},
657 {"value": "{encoding}", "name": "Encoding"},
658 {"value": "{language}", "name": "Language"},
659 {"value": "{lsp}", "name": "LSP"},
660 {"value": "{warnings}", "name": "Warnings"},
661 {"value": "{update}", "name": "Update"},
662 {"value": "{palette}", "name": "Palette"},
663 {"value": "{clock}", "name": "Clock"},
664 {"value": "{remote}", "name": "Remote Indicator"}
665 ]
666 })
667 }
668}
669
670fn default_status_bar_left() -> Vec<StatusBarElement> {
671 vec![
682 StatusBarElement::Filename,
683 StatusBarElement::Cursor,
684 StatusBarElement::Diagnostics,
685 StatusBarElement::CursorCount,
686 StatusBarElement::Messages,
687 ]
688}
689
690fn default_status_bar_right() -> Vec<StatusBarElement> {
691 vec![
692 StatusBarElement::LineEnding,
693 StatusBarElement::Encoding,
694 StatusBarElement::Language,
695 StatusBarElement::Lsp,
696 StatusBarElement::Warnings,
697 StatusBarElement::Update,
698 StatusBarElement::Palette,
699 ]
700}
701
702#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
717pub struct StatusBarConfig {
718 #[serde(default = "default_status_bar_left")]
721 #[schemars(extend("x-section" = "Status Bar", "x-dual-list-sibling" = "/editor/status_bar/right"))]
722 pub left: Vec<StatusBarElement>,
723
724 #[serde(default = "default_status_bar_right")]
727 #[schemars(extend("x-section" = "Status Bar", "x-dual-list-sibling" = "/editor/status_bar/left"))]
728 pub right: Vec<StatusBarElement>,
729}
730
731impl Default for StatusBarConfig {
732 fn default() -> Self {
733 Self {
734 left: default_status_bar_left(),
735 right: default_status_bar_right(),
736 }
737 }
738}
739
740#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
742pub struct EditorConfig {
743 #[serde(default = "default_true")]
746 #[schemars(extend("x-section" = "Display"))]
747 pub line_numbers: bool,
748
749 #[serde(default = "default_false")]
751 #[schemars(extend("x-section" = "Display"))]
752 pub relative_line_numbers: bool,
753
754 #[serde(default = "default_true")]
756 #[schemars(extend("x-section" = "Display"))]
757 pub highlight_current_line: bool,
758
759 #[serde(default = "default_false")]
761 #[schemars(extend("x-section" = "Display"))]
762 pub highlight_current_column: bool,
763
764 #[serde(default = "default_true")]
766 #[schemars(extend("x-section" = "Display"))]
767 pub line_wrap: bool,
768
769 #[serde(default = "default_true")]
771 #[schemars(extend("x-section" = "Display"))]
772 pub wrap_indent: bool,
773
774 #[serde(default)]
779 #[schemars(extend("x-section" = "Display"))]
780 pub wrap_column: Option<usize>,
781
782 #[serde(default = "default_page_width")]
786 #[schemars(extend("x-section" = "Display"))]
787 pub page_width: Option<usize>,
788
789 #[serde(default = "default_true")]
791 #[schemars(extend("x-section" = "Display"))]
792 pub syntax_highlighting: bool,
793
794 #[serde(default = "default_true")]
799 #[schemars(extend("x-section" = "Display"))]
800 pub show_menu_bar: bool,
801
802 #[serde(default = "default_true")]
807 #[schemars(extend("x-section" = "Display"))]
808 pub menu_bar_mnemonics: bool,
809
810 #[serde(default = "default_true")]
815 #[schemars(extend("x-section" = "Display"))]
816 pub show_tab_bar: bool,
817
818 #[serde(default = "default_true")]
823 #[schemars(extend("x-section" = "Display"))]
824 pub show_status_bar: bool,
825
826 #[serde(default)]
829 #[schemars(extend("x-section" = "Status Bar"))]
830 pub status_bar: StatusBarConfig,
831
832 #[serde(default = "default_true")]
838 #[schemars(extend("x-section" = "Display"))]
839 pub show_prompt_line: bool,
840
841 #[serde(default = "default_true")]
845 #[schemars(extend("x-section" = "Display"))]
846 pub show_vertical_scrollbar: bool,
847
848 #[serde(default = "default_false")]
853 #[schemars(extend("x-section" = "Display"))]
854 pub show_horizontal_scrollbar: bool,
855
856 #[serde(default = "default_true")]
860 #[schemars(extend("x-section" = "Display"))]
861 pub show_tilde: bool,
862
863 #[serde(default = "default_false")]
868 #[schemars(extend("x-section" = "Display"))]
869 pub use_terminal_bg: bool,
870
871 #[serde(default = "default_true")]
877 #[schemars(extend("x-section" = "Display"))]
878 pub set_window_title: bool,
879
880 #[serde(default)]
884 #[schemars(extend("x-section" = "Display"))]
885 pub cursor_style: CursorStyle,
886
887 #[serde(default)]
892 #[schemars(extend("x-section" = "Display"))]
893 pub rulers: Vec<usize>,
894
895 #[serde(default = "default_true")]
901 #[schemars(extend("x-section" = "Whitespace"))]
902 pub whitespace_show: bool,
903
904 #[serde(default = "default_false")]
908 #[schemars(extend("x-section" = "Whitespace"))]
909 pub whitespace_spaces_leading: bool,
910
911 #[serde(default = "default_false")]
915 #[schemars(extend("x-section" = "Whitespace"))]
916 pub whitespace_spaces_inner: bool,
917
918 #[serde(default = "default_false")]
922 #[schemars(extend("x-section" = "Whitespace"))]
923 pub whitespace_spaces_trailing: bool,
924
925 #[serde(default = "default_true")]
929 #[schemars(extend("x-section" = "Whitespace"))]
930 pub whitespace_tabs_leading: bool,
931
932 #[serde(default = "default_true")]
936 #[schemars(extend("x-section" = "Whitespace"))]
937 pub whitespace_tabs_inner: bool,
938
939 #[serde(default = "default_true")]
943 #[schemars(extend("x-section" = "Whitespace"))]
944 pub whitespace_tabs_trailing: bool,
945
946 #[serde(default = "default_false")]
952 #[schemars(extend("x-section" = "Editing"))]
953 pub use_tabs: bool,
954
955 #[serde(default = "default_tab_size")]
957 #[schemars(extend("x-section" = "Editing"))]
958 pub tab_size: usize,
959
960 #[serde(default = "default_true")]
962 #[schemars(extend("x-section" = "Editing"))]
963 pub auto_indent: bool,
964
965 #[serde(default = "default_true")]
972 #[schemars(extend("x-section" = "Editing"))]
973 pub auto_close: bool,
974
975 #[serde(default = "default_true")]
980 #[schemars(extend("x-section" = "Editing"))]
981 pub auto_surround: bool,
982
983 #[serde(default = "default_scroll_offset")]
985 #[schemars(extend("x-section" = "Editing"))]
986 pub scroll_offset: usize,
987
988 #[serde(default)]
993 #[schemars(extend("x-section" = "Editing"))]
994 pub default_line_ending: LineEndingOption,
995
996 #[serde(default = "default_false")]
999 #[schemars(extend("x-section" = "Editing"))]
1000 pub trim_trailing_whitespace_on_save: bool,
1001
1002 #[serde(default = "default_false")]
1005 #[schemars(extend("x-section" = "Editing"))]
1006 pub ensure_final_newline_on_save: bool,
1007
1008 #[serde(default = "default_true")]
1012 #[schemars(extend("x-section" = "Bracket Matching"))]
1013 pub highlight_matching_brackets: bool,
1014
1015 #[serde(default = "default_true")]
1019 #[schemars(extend("x-section" = "Bracket Matching"))]
1020 pub rainbow_brackets: bool,
1021
1022 #[serde(default = "default_false")]
1029 #[schemars(extend("x-section" = "Completion"))]
1030 pub completion_popup_auto_show: bool,
1031
1032 #[serde(default = "default_true")]
1038 #[schemars(extend("x-section" = "Completion"))]
1039 pub quick_suggestions: bool,
1040
1041 #[serde(default = "default_quick_suggestions_delay")]
1047 #[schemars(extend("x-section" = "Completion"))]
1048 pub quick_suggestions_delay_ms: u64,
1049
1050 #[serde(default = "default_true")]
1054 #[schemars(extend("x-section" = "Completion"))]
1055 pub suggest_on_trigger_characters: bool,
1056
1057 #[serde(default = "default_true")]
1060 #[schemars(extend("x-section" = "LSP"))]
1061 pub enable_inlay_hints: bool,
1062
1063 #[serde(default = "default_false")]
1067 #[schemars(extend("x-section" = "LSP"))]
1068 pub enable_semantic_tokens_full: bool,
1069
1070 #[serde(default = "default_false")]
1075 #[schemars(extend("x-section" = "Diagnostics"))]
1076 pub diagnostics_inline_text: bool,
1077
1078 #[serde(default = "default_mouse_hover_enabled")]
1089 #[schemars(extend("x-section" = "Mouse"))]
1090 pub mouse_hover_enabled: bool,
1091
1092 #[serde(default = "default_mouse_hover_delay")]
1096 #[schemars(extend("x-section" = "Mouse"))]
1097 pub mouse_hover_delay_ms: u64,
1098
1099 #[serde(default = "default_double_click_time")]
1103 #[schemars(extend("x-section" = "Mouse"))]
1104 pub double_click_time_ms: u64,
1105
1106 #[serde(default = "default_false")]
1111 #[schemars(extend("x-section" = "Recovery"))]
1112 pub auto_save_enabled: bool,
1113
1114 #[serde(default = "default_auto_save_interval")]
1119 #[schemars(extend("x-section" = "Recovery"))]
1120 pub auto_save_interval_secs: u32,
1121
1122 #[serde(default = "default_true", alias = "persist_unnamed_buffers")]
1129 #[schemars(extend("x-section" = "Recovery"))]
1130 pub hot_exit: bool,
1131
1132 #[serde(default = "default_true")]
1143 #[schemars(extend("x-section" = "Recovery"))]
1144 pub restore_previous_session: bool,
1145
1146 #[serde(default = "default_true")]
1151 #[schemars(extend("x-section" = "Recovery"))]
1152 pub recovery_enabled: bool,
1153
1154 #[serde(default = "default_auto_recovery_save_interval")]
1159 #[schemars(extend("x-section" = "Recovery"))]
1160 pub auto_recovery_save_interval_secs: u32,
1161
1162 #[serde(default = "default_auto_revert_poll_interval")]
1167 #[schemars(extend("x-section" = "Recovery"))]
1168 pub auto_revert_poll_interval_ms: u64,
1169
1170 #[serde(default = "default_true")]
1176 #[schemars(extend("x-section" = "Keyboard"))]
1177 pub keyboard_disambiguate_escape_codes: bool,
1178
1179 #[serde(default = "default_false")]
1184 #[schemars(extend("x-section" = "Keyboard"))]
1185 pub keyboard_report_event_types: bool,
1186
1187 #[serde(default = "default_true")]
1192 #[schemars(extend("x-section" = "Keyboard"))]
1193 pub keyboard_report_alternate_keys: bool,
1194
1195 #[serde(default = "default_false")]
1201 #[schemars(extend("x-section" = "Keyboard"))]
1202 pub keyboard_report_all_keys_as_escape_codes: bool,
1203
1204 #[serde(default = "default_highlight_timeout")]
1207 #[schemars(extend("x-section" = "Performance"))]
1208 pub highlight_timeout_ms: u64,
1209
1210 #[serde(default = "default_snapshot_interval")]
1212 #[schemars(extend("x-section" = "Performance"))]
1213 pub snapshot_interval: usize,
1214
1215 #[serde(default = "default_highlight_context_bytes")]
1220 #[schemars(extend("x-section" = "Performance"))]
1221 pub highlight_context_bytes: usize,
1222
1223 #[serde(default = "default_large_file_threshold")]
1230 #[schemars(extend("x-section" = "Performance"))]
1231 pub large_file_threshold_bytes: u64,
1232
1233 #[serde(default = "default_estimated_line_length")]
1237 #[schemars(extend("x-section" = "Performance"))]
1238 pub estimated_line_length: usize,
1239
1240 #[serde(default = "default_read_concurrency")]
1245 #[schemars(extend("x-section" = "Performance"))]
1246 pub read_concurrency: usize,
1247
1248 #[serde(default = "default_file_tree_poll_interval")]
1253 #[schemars(extend("x-section" = "Performance"))]
1254 pub file_tree_poll_interval_ms: u64,
1255}
1256
1257fn default_tab_size() -> usize {
1258 4
1259}
1260
1261pub const LARGE_FILE_THRESHOLD_BYTES: u64 = 1024 * 1024; fn default_large_file_threshold() -> u64 {
1267 LARGE_FILE_THRESHOLD_BYTES
1268}
1269
1270pub const INDENT_FOLD_MAX_SCAN_LINES: usize = 10_000;
1273
1274pub const INDENT_FOLD_INDICATOR_MAX_SCAN: usize = 50;
1277
1278pub const INDENT_FOLD_MAX_UPWARD_SCAN: usize = 200;
1281
1282fn default_read_concurrency() -> usize {
1283 64
1284}
1285
1286fn default_true() -> bool {
1287 true
1288}
1289
1290fn default_false() -> bool {
1291 false
1292}
1293
1294fn default_quick_suggestions_delay() -> u64 {
1295 150 }
1297
1298fn default_scroll_offset() -> usize {
1299 3
1300}
1301
1302fn default_highlight_timeout() -> u64 {
1303 5
1304}
1305
1306fn default_snapshot_interval() -> usize {
1307 100
1308}
1309
1310fn default_estimated_line_length() -> usize {
1311 80
1312}
1313
1314fn default_auto_save_interval() -> u32 {
1315 30 }
1317
1318fn default_auto_recovery_save_interval() -> u32 {
1319 2 }
1321
1322fn default_highlight_context_bytes() -> usize {
1323 10_000 }
1325
1326fn default_mouse_hover_enabled() -> bool {
1327 !cfg!(windows)
1328}
1329
1330fn default_mouse_hover_delay() -> u64 {
1331 500 }
1333
1334fn default_double_click_time() -> u64 {
1335 500 }
1337
1338fn default_auto_revert_poll_interval() -> u64 {
1339 2000 }
1341
1342fn default_file_tree_poll_interval() -> u64 {
1343 3000 }
1345
1346impl Default for EditorConfig {
1347 fn default() -> Self {
1348 Self {
1349 use_tabs: false,
1350 tab_size: default_tab_size(),
1351 auto_indent: true,
1352 auto_close: true,
1353 auto_surround: true,
1354 line_numbers: true,
1355 relative_line_numbers: false,
1356 scroll_offset: default_scroll_offset(),
1357 syntax_highlighting: true,
1358 highlight_current_line: true,
1359 highlight_current_column: false,
1360 line_wrap: true,
1361 wrap_indent: true,
1362 wrap_column: None,
1363 page_width: default_page_width(),
1364 highlight_timeout_ms: default_highlight_timeout(),
1365 snapshot_interval: default_snapshot_interval(),
1366 large_file_threshold_bytes: default_large_file_threshold(),
1367 estimated_line_length: default_estimated_line_length(),
1368 enable_inlay_hints: true,
1369 enable_semantic_tokens_full: false,
1370 diagnostics_inline_text: false,
1371 auto_save_enabled: false,
1372 auto_save_interval_secs: default_auto_save_interval(),
1373 hot_exit: true,
1374 restore_previous_session: true,
1375 recovery_enabled: true,
1376 auto_recovery_save_interval_secs: default_auto_recovery_save_interval(),
1377 highlight_context_bytes: default_highlight_context_bytes(),
1378 mouse_hover_enabled: default_mouse_hover_enabled(),
1379 mouse_hover_delay_ms: default_mouse_hover_delay(),
1380 double_click_time_ms: default_double_click_time(),
1381 auto_revert_poll_interval_ms: default_auto_revert_poll_interval(),
1382 read_concurrency: default_read_concurrency(),
1383 file_tree_poll_interval_ms: default_file_tree_poll_interval(),
1384 default_line_ending: LineEndingOption::default(),
1385 trim_trailing_whitespace_on_save: false,
1386 ensure_final_newline_on_save: false,
1387 highlight_matching_brackets: true,
1388 rainbow_brackets: true,
1389 cursor_style: CursorStyle::default(),
1390 keyboard_disambiguate_escape_codes: true,
1391 keyboard_report_event_types: false,
1392 keyboard_report_alternate_keys: true,
1393 keyboard_report_all_keys_as_escape_codes: false,
1394 completion_popup_auto_show: false,
1395 quick_suggestions: true,
1396 quick_suggestions_delay_ms: default_quick_suggestions_delay(),
1397 suggest_on_trigger_characters: true,
1398 show_menu_bar: true,
1399 menu_bar_mnemonics: true,
1400 show_tab_bar: true,
1401 show_status_bar: true,
1402 status_bar: StatusBarConfig::default(),
1403 show_prompt_line: true,
1404 show_vertical_scrollbar: true,
1405 show_horizontal_scrollbar: false,
1406 show_tilde: true,
1407 use_terminal_bg: false,
1408 set_window_title: true,
1409 rulers: Vec::new(),
1410 whitespace_show: true,
1411 whitespace_spaces_leading: false,
1412 whitespace_spaces_inner: false,
1413 whitespace_spaces_trailing: false,
1414 whitespace_tabs_leading: true,
1415 whitespace_tabs_inner: true,
1416 whitespace_tabs_trailing: true,
1417 }
1418 }
1419}
1420
1421#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1423pub struct FileExplorerConfig {
1424 #[serde(default = "default_true")]
1426 pub respect_gitignore: bool,
1427
1428 #[serde(default = "default_false")]
1430 pub show_hidden: bool,
1431
1432 #[serde(default = "default_false")]
1434 pub show_gitignored: bool,
1435
1436 #[serde(default)]
1438 pub custom_ignore_patterns: Vec<String>,
1439
1440 #[serde(default = "default_explorer_width")]
1446 pub width: ExplorerWidth,
1447
1448 #[serde(default = "default_true")]
1455 pub preview_tabs: bool,
1456}
1457
1458#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1486pub enum ExplorerWidth {
1487 Percent(u8),
1488 Columns(u16),
1489}
1490
1491impl ExplorerWidth {
1492 pub const DEFAULT: Self = Self::Percent(30);
1494
1495 pub const MIN_COLS: u16 = 5;
1501
1502 pub fn to_cols(self, terminal_width: u16) -> u16 {
1510 let raw = match self {
1511 Self::Percent(pct) => ((terminal_width as u32 * pct as u32) / 100) as u16,
1512 Self::Columns(cols) => cols,
1513 };
1514 raw.max(Self::MIN_COLS).min(terminal_width)
1515 }
1516}
1517
1518impl Default for ExplorerWidth {
1519 fn default() -> Self {
1520 Self::DEFAULT
1521 }
1522}
1523
1524impl std::fmt::Display for ExplorerWidth {
1525 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1526 match self {
1527 Self::Percent(n) => write!(f, "{}%", n),
1528 Self::Columns(n) => write!(f, "{}", n),
1529 }
1530 }
1531}
1532
1533#[derive(Debug)]
1535pub struct ExplorerWidthParseError(String);
1536
1537impl std::fmt::Display for ExplorerWidthParseError {
1538 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1539 write!(f, "{}", self.0)
1540 }
1541}
1542
1543impl std::error::Error for ExplorerWidthParseError {}
1544
1545impl std::str::FromStr for ExplorerWidth {
1546 type Err = ExplorerWidthParseError;
1547
1548 fn from_str(s: &str) -> Result<Self, Self::Err> {
1549 let s = s.trim();
1550 if s.is_empty() {
1551 return Err(ExplorerWidthParseError(
1552 "explorer width: empty string".into(),
1553 ));
1554 }
1555 if let Some(rest) = s.strip_suffix('%') {
1556 let n: u16 = rest.trim().parse().map_err(|_| {
1557 ExplorerWidthParseError(format!("explorer width: {:?} is not a valid percent", s))
1558 })?;
1559 if n > 100 {
1560 return Err(ExplorerWidthParseError(format!(
1561 "explorer width: {}% exceeds 100%",
1562 n
1563 )));
1564 }
1565 Ok(Self::Percent(n as u8))
1566 } else {
1567 let n: u16 = s.parse().map_err(|_| {
1568 ExplorerWidthParseError(format!(
1569 "explorer width: {:?} is neither a percent (e.g. \"30%\") nor a column count (e.g. \"24\")",
1570 s
1571 ))
1572 })?;
1573 Ok(Self::Columns(n))
1574 }
1575 }
1576}
1577
1578impl serde::Serialize for ExplorerWidth {
1579 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1580 s.collect_str(self)
1581 }
1582}
1583
1584impl<'de> serde::Deserialize<'de> for ExplorerWidth {
1585 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
1586 let raw = serde_json::Value::deserialize(d)?;
1587 explorer_width::from_value(&raw)
1588 }
1589}
1590
1591impl schemars::JsonSchema for ExplorerWidth {
1592 fn schema_name() -> std::borrow::Cow<'static, str> {
1593 std::borrow::Cow::Borrowed("ExplorerWidth")
1594 }
1595
1596 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
1597 schemars::json_schema!({
1601 "type": "string",
1602 "pattern": r"^(100%|[1-9]?[0-9]%|\d+)$",
1603 "description": "Either a percent like \"30%\" (0–100) or an absolute column count like \"24\".",
1604 })
1605 }
1606}
1607
1608fn default_explorer_width() -> ExplorerWidth {
1609 ExplorerWidth::DEFAULT
1610}
1611
1612pub fn default_explorer_width_value() -> ExplorerWidth {
1614 ExplorerWidth::DEFAULT
1615}
1616
1617pub(crate) mod explorer_width {
1621 use super::ExplorerWidth;
1622 use serde::de::{self, Deserialize, Deserializer};
1623 use std::str::FromStr;
1624
1625 pub fn deserialize_optional<'de, D>(d: D) -> Result<Option<ExplorerWidth>, D::Error>
1630 where
1631 D: Deserializer<'de>,
1632 {
1633 let raw = Option::<serde_json::Value>::deserialize(d)?;
1634 match raw {
1635 None | Some(serde_json::Value::Null) => Ok(None),
1636 Some(v) => from_value(&v).map(Some),
1637 }
1638 }
1639
1640 pub(super) fn from_value<E: de::Error>(v: &serde_json::Value) -> Result<ExplorerWidth, E> {
1641 match v {
1642 serde_json::Value::String(s) => ExplorerWidth::from_str(s).map_err(E::custom),
1643 serde_json::Value::Number(n) => {
1644 if let Some(u) = n.as_u64() {
1645 if u > 100 {
1649 return Err(E::custom(format!(
1650 "explorer width: {} exceeds 100 (percent). Use \"{}\" for columns.",
1651 u, u
1652 )));
1653 }
1654 Ok(ExplorerWidth::Percent(u as u8))
1655 } else if let Some(f) = n.as_f64() {
1656 let pct = if (0.0..=1.0).contains(&f) {
1658 f * 100.0
1659 } else {
1660 f
1661 };
1662 if !(0.0..=100.0).contains(&pct) {
1663 return Err(E::custom(format!(
1664 "explorer width: percent {} out of range 0..=100",
1665 pct
1666 )));
1667 }
1668 Ok(ExplorerWidth::Percent(pct.round() as u8))
1669 } else {
1670 Err(E::custom("explorer width: unsupported number"))
1671 }
1672 }
1673 _ => Err(E::custom(
1674 "explorer width: expected \"30%\", \"24\" (columns), or a number",
1675 )),
1676 }
1677 }
1678}
1679
1680#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1691pub struct ClipboardConfig {
1692 #[serde(default = "default_true")]
1695 pub use_osc52: bool,
1696
1697 #[serde(default = "default_true")]
1700 pub use_system_clipboard: bool,
1701}
1702
1703impl Default for ClipboardConfig {
1704 fn default() -> Self {
1705 Self {
1706 use_osc52: true,
1707 use_system_clipboard: true,
1708 }
1709 }
1710}
1711
1712#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1714pub struct TerminalConfig {
1715 #[serde(default = "default_true")]
1718 pub jump_to_end_on_output: bool,
1719
1720 #[serde(default)]
1732 pub shell: Option<TerminalShellConfig>,
1733}
1734
1735impl Default for TerminalConfig {
1736 fn default() -> Self {
1737 Self {
1738 jump_to_end_on_output: true,
1739 shell: None,
1740 }
1741 }
1742}
1743
1744#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1746pub struct TerminalShellConfig {
1747 pub command: String,
1750
1751 #[serde(default)]
1753 pub args: Vec<String>,
1754}
1755
1756#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1758pub struct WarningsConfig {
1759 #[serde(default = "default_true")]
1762 pub show_status_indicator: bool,
1763}
1764
1765impl Default for WarningsConfig {
1766 fn default() -> Self {
1767 Self {
1768 show_status_indicator: true,
1769 }
1770 }
1771}
1772
1773#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1775pub struct PackagesConfig {
1776 #[serde(default = "default_package_sources")]
1779 pub sources: Vec<String>,
1780}
1781
1782fn default_package_sources() -> Vec<String> {
1783 vec!["https://github.com/sinelaw/fresh-plugins-registry".to_string()]
1784}
1785
1786impl Default for PackagesConfig {
1787 fn default() -> Self {
1788 Self {
1789 sources: default_package_sources(),
1790 }
1791 }
1792}
1793
1794pub use fresh_core::config::PluginConfig;
1796
1797impl Default for FileExplorerConfig {
1798 fn default() -> Self {
1799 Self {
1800 respect_gitignore: true,
1801 show_hidden: false,
1802 show_gitignored: false,
1803 custom_ignore_patterns: Vec::new(),
1804 width: default_explorer_width(),
1805 preview_tabs: true,
1806 }
1807 }
1808}
1809
1810#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
1812pub struct FileBrowserConfig {
1813 #[serde(default = "default_false")]
1815 pub show_hidden: bool,
1816}
1817
1818#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1820pub struct KeyPress {
1821 pub key: String,
1823 #[serde(default)]
1825 pub modifiers: Vec<String>,
1826}
1827
1828#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1830#[schemars(extend("x-display-field" = "/action"))]
1831pub struct Keybinding {
1832 #[serde(default, skip_serializing_if = "String::is_empty")]
1834 pub key: String,
1835
1836 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1838 pub modifiers: Vec<String>,
1839
1840 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1843 pub keys: Vec<KeyPress>,
1844
1845 pub action: String,
1847
1848 #[serde(default)]
1850 pub args: HashMap<String, serde_json::Value>,
1851
1852 #[serde(default)]
1854 pub when: Option<String>,
1855}
1856
1857#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1859#[schemars(extend("x-display-field" = "/inherits"))]
1860pub struct KeymapConfig {
1861 #[serde(default, skip_serializing_if = "Option::is_none")]
1863 pub inherits: Option<String>,
1864
1865 #[serde(default)]
1867 pub bindings: Vec<Keybinding>,
1868}
1869
1870#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1872#[schemars(extend("x-display-field" = "/command"))]
1873pub struct FormatterConfig {
1874 pub command: String,
1876
1877 #[serde(default)]
1880 pub args: Vec<String>,
1881
1882 #[serde(default = "default_true")]
1885 pub stdin: bool,
1886
1887 #[serde(default = "default_on_save_timeout")]
1889 pub timeout_ms: u64,
1890}
1891
1892#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1894#[schemars(extend("x-display-field" = "/command"))]
1895pub struct OnSaveAction {
1896 pub command: String,
1899
1900 #[serde(default)]
1903 pub args: Vec<String>,
1904
1905 #[serde(default)]
1907 pub working_dir: Option<String>,
1908
1909 #[serde(default)]
1911 pub stdin: bool,
1912
1913 #[serde(default = "default_on_save_timeout")]
1915 pub timeout_ms: u64,
1916
1917 #[serde(default = "default_true")]
1920 pub enabled: bool,
1921}
1922
1923fn default_on_save_timeout() -> u64 {
1924 10000
1925}
1926
1927fn default_page_width() -> Option<usize> {
1928 Some(80)
1929}
1930
1931#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1933#[schemars(extend("x-display-field" = "/grammar"))]
1934pub struct LanguageConfig {
1935 #[serde(default)]
1937 pub extensions: Vec<String>,
1938
1939 #[serde(default)]
1941 pub filenames: Vec<String>,
1942
1943 #[serde(default)]
1945 pub grammar: String,
1946
1947 #[serde(default)]
1949 pub comment_prefix: Option<String>,
1950
1951 #[serde(default = "default_true")]
1953 pub auto_indent: bool,
1954
1955 #[serde(default)]
1958 pub auto_close: Option<bool>,
1959
1960 #[serde(default)]
1963 pub auto_surround: Option<bool>,
1964
1965 #[serde(default)]
1968 pub textmate_grammar: Option<std::path::PathBuf>,
1969
1970 #[serde(default = "default_true")]
1973 pub show_whitespace_tabs: bool,
1974
1975 #[serde(default)]
1980 pub line_wrap: Option<bool>,
1981
1982 #[serde(default)]
1985 pub wrap_column: Option<usize>,
1986
1987 #[serde(default)]
1992 pub page_view: Option<bool>,
1993
1994 #[serde(default)]
1998 pub page_width: Option<usize>,
1999
2000 #[serde(default)]
2004 pub use_tabs: Option<bool>,
2005
2006 #[serde(default)]
2009 pub tab_size: Option<usize>,
2010
2011 #[serde(default)]
2013 pub formatter: Option<FormatterConfig>,
2014
2015 #[serde(default)]
2017 pub format_on_save: bool,
2018
2019 #[serde(default)]
2023 pub on_save: Vec<OnSaveAction>,
2024
2025 #[serde(default)]
2035 pub word_characters: Option<String>,
2036}
2037
2038#[derive(Debug, Clone)]
2045pub struct BufferConfig {
2046 pub tab_size: usize,
2048
2049 pub use_tabs: bool,
2051
2052 pub auto_indent: bool,
2054
2055 pub auto_close: bool,
2057
2058 pub auto_surround: bool,
2060
2061 pub line_wrap: bool,
2063
2064 pub wrap_column: Option<usize>,
2066
2067 pub whitespace: WhitespaceVisibility,
2069
2070 pub formatter: Option<FormatterConfig>,
2072
2073 pub format_on_save: bool,
2075
2076 pub on_save: Vec<OnSaveAction>,
2078
2079 pub textmate_grammar: Option<std::path::PathBuf>,
2081
2082 pub word_characters: String,
2085}
2086
2087impl BufferConfig {
2088 pub fn resolve(global_config: &Config, language_id: Option<&str>) -> Self {
2097 let editor = &global_config.editor;
2098
2099 let mut whitespace = WhitespaceVisibility::from_editor_config(editor);
2101 let mut config = BufferConfig {
2102 tab_size: editor.tab_size,
2103 use_tabs: editor.use_tabs,
2104 auto_indent: editor.auto_indent,
2105 auto_close: editor.auto_close,
2106 auto_surround: editor.auto_surround,
2107 line_wrap: editor.line_wrap,
2108 wrap_column: editor.wrap_column,
2109 whitespace,
2110 formatter: None,
2111 format_on_save: false,
2112 on_save: Vec::new(),
2113 textmate_grammar: None,
2114 word_characters: String::new(),
2115 };
2116
2117 let lang_config_ref = language_id
2121 .and_then(|id| global_config.languages.get(id))
2122 .or_else(|| {
2123 match language_id {
2125 None | Some("text") => global_config
2126 .default_language
2127 .as_deref()
2128 .and_then(|lang| global_config.languages.get(lang)),
2129 _ => None,
2130 }
2131 });
2132 if let Some(lang_config) = lang_config_ref {
2133 if let Some(ts) = lang_config.tab_size {
2135 config.tab_size = ts;
2136 }
2137
2138 if let Some(use_tabs) = lang_config.use_tabs {
2140 config.use_tabs = use_tabs;
2141 }
2142
2143 if let Some(line_wrap) = lang_config.line_wrap {
2145 config.line_wrap = line_wrap;
2146 }
2147
2148 if lang_config.wrap_column.is_some() {
2150 config.wrap_column = lang_config.wrap_column;
2151 }
2152
2153 config.auto_indent = lang_config.auto_indent;
2155
2156 if config.auto_close {
2158 if let Some(lang_auto_close) = lang_config.auto_close {
2159 config.auto_close = lang_auto_close;
2160 }
2161 }
2162
2163 if config.auto_surround {
2165 if let Some(lang_auto_surround) = lang_config.auto_surround {
2166 config.auto_surround = lang_auto_surround;
2167 }
2168 }
2169
2170 whitespace = whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
2172 config.whitespace = whitespace;
2173
2174 config.formatter = lang_config.formatter.clone();
2176
2177 config.format_on_save = lang_config.format_on_save;
2179
2180 config.on_save = lang_config.on_save.clone();
2182
2183 config.textmate_grammar = lang_config.textmate_grammar.clone();
2185
2186 if let Some(ref wc) = lang_config.word_characters {
2188 config.word_characters = wc.clone();
2189 }
2190 }
2191
2192 config
2193 }
2194
2195 pub fn indent_string(&self) -> String {
2200 if self.use_tabs {
2201 "\t".to_string()
2202 } else {
2203 " ".repeat(self.tab_size)
2204 }
2205 }
2206}
2207
2208#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
2210pub struct MenuConfig {
2211 #[serde(default)]
2213 pub menus: Vec<Menu>,
2214}
2215
2216pub use fresh_core::menu::{Menu, MenuItem};
2218
2219pub trait MenuExt {
2221 fn match_id(&self) -> &str;
2224
2225 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path);
2228}
2229
2230impl MenuExt for Menu {
2231 fn match_id(&self) -> &str {
2232 self.id.as_deref().unwrap_or(&self.label)
2233 }
2234
2235 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path) {
2236 self.items = self
2237 .items
2238 .iter()
2239 .map(|item| item.expand_dynamic(themes_dir))
2240 .collect();
2241 }
2242}
2243
2244pub trait MenuItemExt {
2246 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem;
2249}
2250
2251impl MenuItemExt for MenuItem {
2252 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem {
2253 match self {
2254 MenuItem::DynamicSubmenu { label, source } => {
2255 let items = generate_dynamic_items(source, themes_dir);
2256 MenuItem::Submenu {
2257 label: label.clone(),
2258 items,
2259 }
2260 }
2261 other => other.clone(),
2262 }
2263 }
2264}
2265
2266#[cfg(feature = "runtime")]
2268pub fn generate_dynamic_items(source: &str, themes_dir: &std::path::Path) -> Vec<MenuItem> {
2269 match source {
2270 "copy_with_theme" => {
2271 let loader = crate::view::theme::ThemeLoader::new(themes_dir.to_path_buf());
2273 let registry = loader.load_all(&[]);
2274 registry
2275 .list()
2276 .iter()
2277 .map(|info| {
2278 let mut args = HashMap::new();
2279 args.insert("theme".to_string(), serde_json::json!(info.key));
2280 MenuItem::Action {
2281 label: info.name.clone(),
2282 action: "copy_with_theme".to_string(),
2283 args,
2284 when: Some(context_keys::HAS_SELECTION.to_string()),
2285 checkbox: None,
2286 }
2287 })
2288 .collect()
2289 }
2290 _ => vec![MenuItem::Label {
2291 info: format!("Unknown source: {}", source),
2292 }],
2293 }
2294}
2295
2296#[cfg(not(feature = "runtime"))]
2298pub fn generate_dynamic_items(_source: &str, _themes_dir: &std::path::Path) -> Vec<MenuItem> {
2299 vec![]
2301}
2302
2303impl Default for Config {
2304 fn default() -> Self {
2305 Self {
2306 version: 0,
2307 theme: default_theme_name(),
2308 locale: LocaleName::default(),
2309 check_for_updates: true,
2310 editor: EditorConfig::default(),
2311 file_explorer: FileExplorerConfig::default(),
2312 file_browser: FileBrowserConfig::default(),
2313 clipboard: ClipboardConfig::default(),
2314 terminal: TerminalConfig::default(),
2315 keybindings: vec![], keybinding_maps: HashMap::new(), active_keybinding_map: default_keybinding_map_name(),
2318 languages: Self::default_languages(),
2319 default_language: None,
2320 lsp: Self::default_lsp_config(),
2321 universal_lsp: Self::default_universal_lsp_config(),
2322 warnings: WarningsConfig::default(),
2323 plugins: HashMap::new(), packages: PackagesConfig::default(),
2325 }
2326 }
2327}
2328
2329impl MenuConfig {
2330 pub fn translated() -> Self {
2332 Self {
2333 menus: Self::translated_menus(),
2334 }
2335 }
2336
2337 pub fn translated_menus() -> Vec<Menu> {
2343 vec![
2344 Menu {
2346 id: Some("File".to_string()),
2347 label: t!("menu.file").to_string(),
2348 when: None,
2349 items: vec![
2350 MenuItem::Action {
2351 label: t!("menu.file.new_file").to_string(),
2352 action: "new".to_string(),
2353 args: HashMap::new(),
2354 when: None,
2355 checkbox: None,
2356 },
2357 MenuItem::Action {
2358 label: t!("menu.file.open_file").to_string(),
2359 action: "open".to_string(),
2360 args: HashMap::new(),
2361 when: None,
2362 checkbox: None,
2363 },
2364 MenuItem::Separator { separator: true },
2365 MenuItem::Action {
2366 label: t!("menu.file.save").to_string(),
2367 action: "save".to_string(),
2368 args: HashMap::new(),
2369 when: None,
2370 checkbox: None,
2371 },
2372 MenuItem::Action {
2373 label: t!("menu.file.save_as").to_string(),
2374 action: "save_as".to_string(),
2375 args: HashMap::new(),
2376 when: None,
2377 checkbox: None,
2378 },
2379 MenuItem::Action {
2380 label: t!("menu.file.revert").to_string(),
2381 action: "revert".to_string(),
2382 args: HashMap::new(),
2383 when: None,
2384 checkbox: None,
2385 },
2386 MenuItem::Action {
2387 label: t!("menu.file.reload_with_encoding").to_string(),
2388 action: "reload_with_encoding".to_string(),
2389 args: HashMap::new(),
2390 when: None,
2391 checkbox: None,
2392 },
2393 MenuItem::Separator { separator: true },
2394 MenuItem::Action {
2395 label: t!("menu.file.close_buffer").to_string(),
2396 action: "close".to_string(),
2397 args: HashMap::new(),
2398 when: None,
2399 checkbox: None,
2400 },
2401 MenuItem::Separator { separator: true },
2402 MenuItem::Action {
2403 label: t!("menu.file.switch_project").to_string(),
2404 action: "switch_project".to_string(),
2405 args: HashMap::new(),
2406 when: None,
2407 checkbox: None,
2408 },
2409 MenuItem::Separator { separator: true },
2410 MenuItem::Action {
2411 label: t!("menu.file.detach").to_string(),
2412 action: "detach".to_string(),
2413 args: HashMap::new(),
2414 when: Some(context_keys::SESSION_MODE.to_string()),
2415 checkbox: None,
2416 },
2417 MenuItem::Action {
2418 label: t!("menu.file.quit").to_string(),
2419 action: "quit".to_string(),
2420 args: HashMap::new(),
2421 when: None,
2422 checkbox: None,
2423 },
2424 ],
2425 },
2426 Menu {
2428 id: Some("Edit".to_string()),
2429 label: t!("menu.edit").to_string(),
2430 when: None,
2431 items: vec![
2432 MenuItem::Action {
2433 label: t!("menu.edit.undo").to_string(),
2434 action: "undo".to_string(),
2435 args: HashMap::new(),
2436 when: None,
2437 checkbox: None,
2438 },
2439 MenuItem::Action {
2440 label: t!("menu.edit.redo").to_string(),
2441 action: "redo".to_string(),
2442 args: HashMap::new(),
2443 when: None,
2444 checkbox: None,
2445 },
2446 MenuItem::Separator { separator: true },
2447 MenuItem::Action {
2448 label: t!("menu.edit.cut").to_string(),
2449 action: "cut".to_string(),
2450 args: HashMap::new(),
2451 when: Some(context_keys::CAN_COPY.to_string()),
2452 checkbox: None,
2453 },
2454 MenuItem::Action {
2455 label: t!("menu.edit.copy").to_string(),
2456 action: "copy".to_string(),
2457 args: HashMap::new(),
2458 when: Some(context_keys::CAN_COPY.to_string()),
2459 checkbox: None,
2460 },
2461 MenuItem::DynamicSubmenu {
2462 label: t!("menu.edit.copy_with_formatting").to_string(),
2463 source: "copy_with_theme".to_string(),
2464 },
2465 MenuItem::Action {
2466 label: t!("menu.edit.paste").to_string(),
2467 action: "paste".to_string(),
2468 args: HashMap::new(),
2469 when: Some(context_keys::CAN_PASTE.to_string()),
2470 checkbox: None,
2471 },
2472 MenuItem::Separator { separator: true },
2473 MenuItem::Action {
2474 label: t!("menu.edit.select_all").to_string(),
2475 action: "select_all".to_string(),
2476 args: HashMap::new(),
2477 when: None,
2478 checkbox: None,
2479 },
2480 MenuItem::Separator { separator: true },
2481 MenuItem::Action {
2482 label: t!("menu.edit.find").to_string(),
2483 action: "search".to_string(),
2484 args: HashMap::new(),
2485 when: None,
2486 checkbox: None,
2487 },
2488 MenuItem::Action {
2489 label: t!("menu.edit.find_in_selection").to_string(),
2490 action: "find_in_selection".to_string(),
2491 args: HashMap::new(),
2492 when: Some(context_keys::HAS_SELECTION.to_string()),
2493 checkbox: None,
2494 },
2495 MenuItem::Action {
2496 label: t!("menu.edit.find_next").to_string(),
2497 action: "find_next".to_string(),
2498 args: HashMap::new(),
2499 when: None,
2500 checkbox: None,
2501 },
2502 MenuItem::Action {
2503 label: t!("menu.edit.find_previous").to_string(),
2504 action: "find_previous".to_string(),
2505 args: HashMap::new(),
2506 when: None,
2507 checkbox: None,
2508 },
2509 MenuItem::Action {
2510 label: t!("menu.edit.replace").to_string(),
2511 action: "query_replace".to_string(),
2512 args: HashMap::new(),
2513 when: None,
2514 checkbox: None,
2515 },
2516 MenuItem::Separator { separator: true },
2517 MenuItem::Action {
2518 label: t!("menu.edit.delete_line").to_string(),
2519 action: "delete_line".to_string(),
2520 args: HashMap::new(),
2521 when: None,
2522 checkbox: None,
2523 },
2524 MenuItem::Action {
2525 label: t!("menu.edit.format_buffer").to_string(),
2526 action: "format_buffer".to_string(),
2527 args: HashMap::new(),
2528 when: Some(context_keys::FORMATTER_AVAILABLE.to_string()),
2529 checkbox: None,
2530 },
2531 MenuItem::Separator { separator: true },
2532 MenuItem::Action {
2533 label: t!("menu.edit.settings").to_string(),
2534 action: "open_settings".to_string(),
2535 args: HashMap::new(),
2536 when: None,
2537 checkbox: None,
2538 },
2539 MenuItem::Action {
2540 label: t!("menu.edit.keybinding_editor").to_string(),
2541 action: "open_keybinding_editor".to_string(),
2542 args: HashMap::new(),
2543 when: None,
2544 checkbox: None,
2545 },
2546 ],
2547 },
2548 Menu {
2550 id: Some("View".to_string()),
2551 label: t!("menu.view").to_string(),
2552 when: None,
2553 items: vec![
2554 MenuItem::Action {
2555 label: t!("menu.view.file_explorer").to_string(),
2556 action: "toggle_file_explorer".to_string(),
2557 args: HashMap::new(),
2558 when: None,
2559 checkbox: Some(context_keys::FILE_EXPLORER.to_string()),
2560 },
2561 MenuItem::Separator { separator: true },
2562 MenuItem::Action {
2563 label: t!("menu.view.line_numbers").to_string(),
2564 action: "toggle_line_numbers".to_string(),
2565 args: HashMap::new(),
2566 when: None,
2567 checkbox: Some(context_keys::LINE_NUMBERS.to_string()),
2568 },
2569 MenuItem::Action {
2570 label: t!("menu.view.line_wrap").to_string(),
2571 action: "toggle_line_wrap".to_string(),
2572 args: HashMap::new(),
2573 when: None,
2574 checkbox: Some(context_keys::LINE_WRAP.to_string()),
2575 },
2576 MenuItem::Action {
2577 label: t!("menu.view.mouse_support").to_string(),
2578 action: "toggle_mouse_capture".to_string(),
2579 args: HashMap::new(),
2580 when: None,
2581 checkbox: Some(context_keys::MOUSE_CAPTURE.to_string()),
2582 },
2583 MenuItem::Separator { separator: true },
2584 MenuItem::Action {
2585 label: t!("menu.view.vertical_scrollbar").to_string(),
2586 action: "toggle_vertical_scrollbar".to_string(),
2587 args: HashMap::new(),
2588 when: None,
2589 checkbox: Some(context_keys::VERTICAL_SCROLLBAR.to_string()),
2590 },
2591 MenuItem::Action {
2592 label: t!("menu.view.horizontal_scrollbar").to_string(),
2593 action: "toggle_horizontal_scrollbar".to_string(),
2594 args: HashMap::new(),
2595 when: None,
2596 checkbox: Some(context_keys::HORIZONTAL_SCROLLBAR.to_string()),
2597 },
2598 MenuItem::Separator { separator: true },
2599 MenuItem::Action {
2600 label: t!("menu.view.set_background").to_string(),
2601 action: "set_background".to_string(),
2602 args: HashMap::new(),
2603 when: None,
2604 checkbox: None,
2605 },
2606 MenuItem::Action {
2607 label: t!("menu.view.set_background_blend").to_string(),
2608 action: "set_background_blend".to_string(),
2609 args: HashMap::new(),
2610 when: None,
2611 checkbox: None,
2612 },
2613 MenuItem::Action {
2614 label: t!("menu.view.set_page_width").to_string(),
2615 action: "set_page_width".to_string(),
2616 args: HashMap::new(),
2617 when: None,
2618 checkbox: None,
2619 },
2620 MenuItem::Separator { separator: true },
2621 MenuItem::Action {
2622 label: t!("menu.view.select_theme").to_string(),
2623 action: "select_theme".to_string(),
2624 args: HashMap::new(),
2625 when: None,
2626 checkbox: None,
2627 },
2628 MenuItem::Action {
2629 label: t!("menu.view.select_locale").to_string(),
2630 action: "select_locale".to_string(),
2631 args: HashMap::new(),
2632 when: None,
2633 checkbox: None,
2634 },
2635 MenuItem::Action {
2636 label: t!("menu.view.settings").to_string(),
2637 action: "open_settings".to_string(),
2638 args: HashMap::new(),
2639 when: None,
2640 checkbox: None,
2641 },
2642 MenuItem::Action {
2643 label: t!("menu.view.calibrate_input").to_string(),
2644 action: "calibrate_input".to_string(),
2645 args: HashMap::new(),
2646 when: None,
2647 checkbox: None,
2648 },
2649 MenuItem::Separator { separator: true },
2650 MenuItem::Action {
2651 label: t!("menu.view.split_horizontal").to_string(),
2652 action: "split_horizontal".to_string(),
2653 args: HashMap::new(),
2654 when: None,
2655 checkbox: None,
2656 },
2657 MenuItem::Action {
2658 label: t!("menu.view.split_vertical").to_string(),
2659 action: "split_vertical".to_string(),
2660 args: HashMap::new(),
2661 when: None,
2662 checkbox: None,
2663 },
2664 MenuItem::Action {
2665 label: t!("menu.view.close_split").to_string(),
2666 action: "close_split".to_string(),
2667 args: HashMap::new(),
2668 when: None,
2669 checkbox: None,
2670 },
2671 MenuItem::Action {
2672 label: t!("menu.view.scroll_sync").to_string(),
2673 action: "toggle_scroll_sync".to_string(),
2674 args: HashMap::new(),
2675 when: Some(context_keys::HAS_SAME_BUFFER_SPLITS.to_string()),
2676 checkbox: Some(context_keys::SCROLL_SYNC.to_string()),
2677 },
2678 MenuItem::Action {
2679 label: t!("menu.view.focus_next_split").to_string(),
2680 action: "next_split".to_string(),
2681 args: HashMap::new(),
2682 when: None,
2683 checkbox: None,
2684 },
2685 MenuItem::Action {
2686 label: t!("menu.view.focus_prev_split").to_string(),
2687 action: "prev_split".to_string(),
2688 args: HashMap::new(),
2689 when: None,
2690 checkbox: None,
2691 },
2692 MenuItem::Action {
2693 label: t!("menu.view.toggle_maximize_split").to_string(),
2694 action: "toggle_maximize_split".to_string(),
2695 args: HashMap::new(),
2696 when: None,
2697 checkbox: None,
2698 },
2699 MenuItem::Separator { separator: true },
2700 MenuItem::Submenu {
2701 label: t!("menu.terminal").to_string(),
2702 items: vec![
2703 MenuItem::Action {
2704 label: t!("menu.terminal.open").to_string(),
2705 action: "open_terminal".to_string(),
2706 args: HashMap::new(),
2707 when: None,
2708 checkbox: None,
2709 },
2710 MenuItem::Action {
2711 label: t!("menu.terminal.close").to_string(),
2712 action: "close_terminal".to_string(),
2713 args: HashMap::new(),
2714 when: None,
2715 checkbox: None,
2716 },
2717 MenuItem::Separator { separator: true },
2718 MenuItem::Action {
2719 label: t!("menu.terminal.toggle_keyboard_capture").to_string(),
2720 action: "toggle_keyboard_capture".to_string(),
2721 args: HashMap::new(),
2722 when: None,
2723 checkbox: None,
2724 },
2725 ],
2726 },
2727 MenuItem::Separator { separator: true },
2728 MenuItem::Submenu {
2729 label: t!("menu.view.keybinding_style").to_string(),
2730 items: vec![
2731 MenuItem::Action {
2732 label: t!("menu.view.keybinding_default").to_string(),
2733 action: "switch_keybinding_map".to_string(),
2734 args: {
2735 let mut map = HashMap::new();
2736 map.insert("map".to_string(), serde_json::json!("default"));
2737 map
2738 },
2739 when: None,
2740 checkbox: Some(context_keys::KEYMAP_DEFAULT.to_string()),
2741 },
2742 MenuItem::Action {
2743 label: t!("menu.view.keybinding_emacs").to_string(),
2744 action: "switch_keybinding_map".to_string(),
2745 args: {
2746 let mut map = HashMap::new();
2747 map.insert("map".to_string(), serde_json::json!("emacs"));
2748 map
2749 },
2750 when: None,
2751 checkbox: Some(context_keys::KEYMAP_EMACS.to_string()),
2752 },
2753 MenuItem::Action {
2754 label: t!("menu.view.keybinding_vscode").to_string(),
2755 action: "switch_keybinding_map".to_string(),
2756 args: {
2757 let mut map = HashMap::new();
2758 map.insert("map".to_string(), serde_json::json!("vscode"));
2759 map
2760 },
2761 when: None,
2762 checkbox: Some(context_keys::KEYMAP_VSCODE.to_string()),
2763 },
2764 MenuItem::Action {
2765 label: "macOS GUI (⌘)".to_string(),
2766 action: "switch_keybinding_map".to_string(),
2767 args: {
2768 let mut map = HashMap::new();
2769 map.insert("map".to_string(), serde_json::json!("macos-gui"));
2770 map
2771 },
2772 when: None,
2773 checkbox: Some(context_keys::KEYMAP_MACOS_GUI.to_string()),
2774 },
2775 ],
2776 },
2777 ],
2778 },
2779 Menu {
2781 id: Some("Selection".to_string()),
2782 label: t!("menu.selection").to_string(),
2783 when: None,
2784 items: vec![
2785 MenuItem::Action {
2786 label: t!("menu.selection.select_all").to_string(),
2787 action: "select_all".to_string(),
2788 args: HashMap::new(),
2789 when: None,
2790 checkbox: None,
2791 },
2792 MenuItem::Action {
2793 label: t!("menu.selection.select_word").to_string(),
2794 action: "select_word".to_string(),
2795 args: HashMap::new(),
2796 when: None,
2797 checkbox: None,
2798 },
2799 MenuItem::Action {
2800 label: t!("menu.selection.select_line").to_string(),
2801 action: "select_line".to_string(),
2802 args: HashMap::new(),
2803 when: None,
2804 checkbox: None,
2805 },
2806 MenuItem::Action {
2807 label: t!("menu.selection.expand_selection").to_string(),
2808 action: "expand_selection".to_string(),
2809 args: HashMap::new(),
2810 when: None,
2811 checkbox: None,
2812 },
2813 MenuItem::Separator { separator: true },
2814 MenuItem::Action {
2815 label: t!("menu.selection.add_cursor_above").to_string(),
2816 action: "add_cursor_above".to_string(),
2817 args: HashMap::new(),
2818 when: None,
2819 checkbox: None,
2820 },
2821 MenuItem::Action {
2822 label: t!("menu.selection.add_cursor_below").to_string(),
2823 action: "add_cursor_below".to_string(),
2824 args: HashMap::new(),
2825 when: None,
2826 checkbox: None,
2827 },
2828 MenuItem::Action {
2829 label: t!("menu.selection.add_cursor_next_match").to_string(),
2830 action: "add_cursor_next_match".to_string(),
2831 args: HashMap::new(),
2832 when: None,
2833 checkbox: None,
2834 },
2835 MenuItem::Action {
2836 label: t!("menu.selection.remove_secondary_cursors").to_string(),
2837 action: "remove_secondary_cursors".to_string(),
2838 args: HashMap::new(),
2839 when: None,
2840 checkbox: None,
2841 },
2842 ],
2843 },
2844 Menu {
2846 id: Some("Go".to_string()),
2847 label: t!("menu.go").to_string(),
2848 when: None,
2849 items: vec![
2850 MenuItem::Action {
2851 label: t!("menu.go.goto_line").to_string(),
2852 action: "goto_line".to_string(),
2853 args: HashMap::new(),
2854 when: None,
2855 checkbox: None,
2856 },
2857 MenuItem::Action {
2858 label: t!("menu.go.goto_definition").to_string(),
2859 action: "lsp_goto_definition".to_string(),
2860 args: HashMap::new(),
2861 when: None,
2862 checkbox: None,
2863 },
2864 MenuItem::Action {
2865 label: t!("menu.go.find_references").to_string(),
2866 action: "lsp_references".to_string(),
2867 args: HashMap::new(),
2868 when: None,
2869 checkbox: None,
2870 },
2871 MenuItem::Separator { separator: true },
2872 MenuItem::Action {
2873 label: t!("menu.go.next_buffer").to_string(),
2874 action: "next_buffer".to_string(),
2875 args: HashMap::new(),
2876 when: None,
2877 checkbox: None,
2878 },
2879 MenuItem::Action {
2880 label: t!("menu.go.prev_buffer").to_string(),
2881 action: "prev_buffer".to_string(),
2882 args: HashMap::new(),
2883 when: None,
2884 checkbox: None,
2885 },
2886 MenuItem::Separator { separator: true },
2887 MenuItem::Action {
2888 label: t!("menu.go.command_palette").to_string(),
2889 action: "command_palette".to_string(),
2890 args: HashMap::new(),
2891 when: None,
2892 checkbox: None,
2893 },
2894 ],
2895 },
2896 Menu {
2898 id: Some("LSP".to_string()),
2899 label: t!("menu.lsp").to_string(),
2900 when: None,
2901 items: vec![
2902 MenuItem::Action {
2903 label: t!("menu.lsp.show_hover").to_string(),
2904 action: "lsp_hover".to_string(),
2905 args: HashMap::new(),
2906 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2907 checkbox: None,
2908 },
2909 MenuItem::Action {
2910 label: t!("menu.lsp.goto_definition").to_string(),
2911 action: "lsp_goto_definition".to_string(),
2912 args: HashMap::new(),
2913 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2914 checkbox: None,
2915 },
2916 MenuItem::Action {
2917 label: t!("menu.lsp.find_references").to_string(),
2918 action: "lsp_references".to_string(),
2919 args: HashMap::new(),
2920 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2921 checkbox: None,
2922 },
2923 MenuItem::Action {
2924 label: t!("menu.lsp.rename_symbol").to_string(),
2925 action: "lsp_rename".to_string(),
2926 args: HashMap::new(),
2927 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2928 checkbox: None,
2929 },
2930 MenuItem::Separator { separator: true },
2931 MenuItem::Action {
2932 label: t!("menu.lsp.show_completions").to_string(),
2933 action: "lsp_completion".to_string(),
2934 args: HashMap::new(),
2935 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2936 checkbox: None,
2937 },
2938 MenuItem::Action {
2939 label: t!("menu.lsp.show_signature").to_string(),
2940 action: "lsp_signature_help".to_string(),
2941 args: HashMap::new(),
2942 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2943 checkbox: None,
2944 },
2945 MenuItem::Action {
2946 label: t!("menu.lsp.code_actions").to_string(),
2947 action: "lsp_code_actions".to_string(),
2948 args: HashMap::new(),
2949 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2950 checkbox: None,
2951 },
2952 MenuItem::Separator { separator: true },
2953 MenuItem::Action {
2954 label: t!("menu.lsp.toggle_inlay_hints").to_string(),
2955 action: "toggle_inlay_hints".to_string(),
2956 args: HashMap::new(),
2957 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2958 checkbox: Some(context_keys::INLAY_HINTS.to_string()),
2959 },
2960 MenuItem::Action {
2961 label: t!("menu.lsp.toggle_mouse_hover").to_string(),
2962 action: "toggle_mouse_hover".to_string(),
2963 args: HashMap::new(),
2964 when: None,
2965 checkbox: Some(context_keys::MOUSE_HOVER.to_string()),
2966 },
2967 MenuItem::Separator { separator: true },
2968 MenuItem::Action {
2969 label: t!("menu.lsp.show_status").to_string(),
2970 action: "show_lsp_status".to_string(),
2971 args: HashMap::new(),
2972 when: None,
2973 checkbox: None,
2974 },
2975 MenuItem::Action {
2976 label: t!("menu.lsp.restart_server").to_string(),
2977 action: "lsp_restart".to_string(),
2978 args: HashMap::new(),
2979 when: None,
2980 checkbox: None,
2981 },
2982 MenuItem::Action {
2983 label: t!("menu.lsp.stop_server").to_string(),
2984 action: "lsp_stop".to_string(),
2985 args: HashMap::new(),
2986 when: None,
2987 checkbox: None,
2988 },
2989 MenuItem::Separator { separator: true },
2990 MenuItem::Action {
2991 label: t!("menu.lsp.toggle_for_buffer").to_string(),
2992 action: "lsp_toggle_for_buffer".to_string(),
2993 args: HashMap::new(),
2994 when: None,
2995 checkbox: None,
2996 },
2997 ],
2998 },
2999 Menu {
3001 id: Some("Explorer".to_string()),
3002 label: t!("menu.explorer").to_string(),
3003 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3004 items: vec![
3005 MenuItem::Action {
3006 label: t!("menu.explorer.new_file").to_string(),
3007 action: "file_explorer_new_file".to_string(),
3008 args: HashMap::new(),
3009 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3010 checkbox: None,
3011 },
3012 MenuItem::Action {
3013 label: t!("menu.explorer.new_folder").to_string(),
3014 action: "file_explorer_new_directory".to_string(),
3015 args: HashMap::new(),
3016 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3017 checkbox: None,
3018 },
3019 MenuItem::Separator { separator: true },
3020 MenuItem::Action {
3021 label: t!("menu.explorer.open").to_string(),
3022 action: "file_explorer_open".to_string(),
3023 args: HashMap::new(),
3024 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3025 checkbox: None,
3026 },
3027 MenuItem::Action {
3028 label: t!("menu.explorer.rename").to_string(),
3029 action: "file_explorer_rename".to_string(),
3030 args: HashMap::new(),
3031 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3032 checkbox: None,
3033 },
3034 MenuItem::Action {
3035 label: t!("menu.explorer.delete").to_string(),
3036 action: "file_explorer_delete".to_string(),
3037 args: HashMap::new(),
3038 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3039 checkbox: None,
3040 },
3041 MenuItem::Separator { separator: true },
3042 MenuItem::Action {
3043 label: t!("menu.explorer.cut").to_string(),
3044 action: "cut".to_string(),
3045 args: HashMap::new(),
3046 when: Some(context_keys::CAN_COPY.to_string()),
3047 checkbox: None,
3048 },
3049 MenuItem::Action {
3050 label: t!("menu.explorer.copy").to_string(),
3051 action: "copy".to_string(),
3052 args: HashMap::new(),
3053 when: Some(context_keys::CAN_COPY.to_string()),
3054 checkbox: None,
3055 },
3056 MenuItem::Action {
3057 label: t!("menu.explorer.paste").to_string(),
3058 action: "paste".to_string(),
3059 args: HashMap::new(),
3060 when: Some(context_keys::CAN_PASTE.to_string()),
3061 checkbox: None,
3062 },
3063 MenuItem::Separator { separator: true },
3064 MenuItem::Action {
3065 label: t!("menu.explorer.refresh").to_string(),
3066 action: "file_explorer_refresh".to_string(),
3067 args: HashMap::new(),
3068 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3069 checkbox: None,
3070 },
3071 MenuItem::Separator { separator: true },
3072 MenuItem::Action {
3073 label: t!("menu.explorer.show_hidden").to_string(),
3074 action: "file_explorer_toggle_hidden".to_string(),
3075 args: HashMap::new(),
3076 when: Some(context_keys::FILE_EXPLORER.to_string()),
3077 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_HIDDEN.to_string()),
3078 },
3079 MenuItem::Action {
3080 label: t!("menu.explorer.show_gitignored").to_string(),
3081 action: "file_explorer_toggle_gitignored".to_string(),
3082 args: HashMap::new(),
3083 when: Some(context_keys::FILE_EXPLORER.to_string()),
3084 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_GITIGNORED.to_string()),
3085 },
3086 ],
3087 },
3088 Menu {
3090 id: Some("Help".to_string()),
3091 label: t!("menu.help").to_string(),
3092 when: None,
3093 items: vec![
3094 MenuItem::Label {
3095 info: format!("Fresh v{}", env!("CARGO_PKG_VERSION")),
3096 },
3097 MenuItem::Separator { separator: true },
3098 MenuItem::Action {
3099 label: t!("menu.help.show_manual").to_string(),
3100 action: "show_help".to_string(),
3101 args: HashMap::new(),
3102 when: None,
3103 checkbox: None,
3104 },
3105 MenuItem::Action {
3106 label: t!("menu.help.keyboard_shortcuts").to_string(),
3107 action: "keyboard_shortcuts".to_string(),
3108 args: HashMap::new(),
3109 when: None,
3110 checkbox: None,
3111 },
3112 MenuItem::Separator { separator: true },
3113 MenuItem::Action {
3114 label: t!("menu.help.event_debug").to_string(),
3115 action: "event_debug".to_string(),
3116 args: HashMap::new(),
3117 when: None,
3118 checkbox: None,
3119 },
3120 ],
3121 },
3122 ]
3123 }
3124}
3125
3126impl Config {
3127 pub(crate) const FILENAME: &'static str = "config.json";
3129
3130 pub(crate) fn local_config_path(working_dir: &Path) -> std::path::PathBuf {
3132 working_dir.join(Self::FILENAME)
3133 }
3134
3135 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
3141 let contents = std::fs::read_to_string(path.as_ref())
3142 .map_err(|e| ConfigError::IoError(e.to_string()))?;
3143
3144 let partial: crate::partial_config::PartialConfig =
3146 serde_json::from_str(&contents).map_err(|e| ConfigError::ParseError(e.to_string()))?;
3147
3148 Ok(partial.resolve())
3149 }
3150
3151 fn load_builtin_keymap(name: &str) -> Option<KeymapConfig> {
3153 let json_content = match name {
3154 "default" => include_str!("../keymaps/default.json"),
3155 "emacs" => include_str!("../keymaps/emacs.json"),
3156 "vscode" => include_str!("../keymaps/vscode.json"),
3157 "macos" => include_str!("../keymaps/macos.json"),
3158 "macos-gui" => include_str!("../keymaps/macos-gui.json"),
3159 _ => return None,
3160 };
3161
3162 match serde_json::from_str(json_content) {
3163 Ok(config) => Some(config),
3164 Err(e) => {
3165 eprintln!("Failed to parse builtin keymap '{}': {}", name, e);
3166 None
3167 }
3168 }
3169 }
3170
3171 pub fn resolve_keymap(&self, map_name: &str) -> Vec<Keybinding> {
3174 let mut visited = std::collections::HashSet::new();
3175 self.resolve_keymap_recursive(map_name, &mut visited)
3176 }
3177
3178 fn resolve_keymap_recursive(
3180 &self,
3181 map_name: &str,
3182 visited: &mut std::collections::HashSet<String>,
3183 ) -> Vec<Keybinding> {
3184 if visited.contains(map_name) {
3186 eprintln!(
3187 "Warning: Circular inheritance detected in keymap '{}'",
3188 map_name
3189 );
3190 return Vec::new();
3191 }
3192 visited.insert(map_name.to_string());
3193
3194 let keymap = self
3196 .keybinding_maps
3197 .get(map_name)
3198 .cloned()
3199 .or_else(|| Self::load_builtin_keymap(map_name));
3200
3201 let Some(keymap) = keymap else {
3202 return Vec::new();
3203 };
3204
3205 let mut all_bindings = if let Some(ref parent_name) = keymap.inherits {
3207 self.resolve_keymap_recursive(parent_name, visited)
3208 } else {
3209 Vec::new()
3210 };
3211
3212 all_bindings.extend(keymap.bindings);
3214
3215 all_bindings
3216 }
3217 fn default_languages() -> HashMap<String, LanguageConfig> {
3219 let mut languages = HashMap::new();
3220
3221 languages.insert(
3222 "rust".to_string(),
3223 LanguageConfig {
3224 extensions: vec!["rs".to_string()],
3225 filenames: vec![],
3226 grammar: "rust".to_string(),
3227 comment_prefix: Some("//".to_string()),
3228 auto_indent: true,
3229 auto_close: None,
3230 auto_surround: None,
3231 textmate_grammar: None,
3232 show_whitespace_tabs: true,
3233 line_wrap: None,
3234 wrap_column: None,
3235 page_view: None,
3236 page_width: None,
3237 use_tabs: None,
3238 tab_size: None,
3239 formatter: Some(FormatterConfig {
3240 command: "rustfmt".to_string(),
3241 args: vec!["--edition".to_string(), "2021".to_string()],
3242 stdin: true,
3243 timeout_ms: 10000,
3244 }),
3245 format_on_save: false,
3246 on_save: vec![],
3247 word_characters: None,
3248 },
3249 );
3250
3251 languages.insert(
3252 "javascript".to_string(),
3253 LanguageConfig {
3254 extensions: vec!["js".to_string(), "jsx".to_string(), "mjs".to_string()],
3255 filenames: vec![],
3256 grammar: "javascript".to_string(),
3257 comment_prefix: Some("//".to_string()),
3258 auto_indent: true,
3259 auto_close: None,
3260 auto_surround: None,
3261 textmate_grammar: None,
3262 show_whitespace_tabs: true,
3263 line_wrap: None,
3264 wrap_column: None,
3265 page_view: None,
3266 page_width: None,
3267 use_tabs: None,
3268 tab_size: None,
3269 formatter: Some(FormatterConfig {
3270 command: "prettier".to_string(),
3271 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3272 stdin: true,
3273 timeout_ms: 10000,
3274 }),
3275 format_on_save: false,
3276 on_save: vec![],
3277 word_characters: None,
3278 },
3279 );
3280
3281 languages.insert(
3282 "typescript".to_string(),
3283 LanguageConfig {
3284 extensions: vec!["ts".to_string(), "tsx".to_string(), "mts".to_string()],
3285 filenames: vec![],
3286 grammar: "typescript".to_string(),
3287 comment_prefix: Some("//".to_string()),
3288 auto_indent: true,
3289 auto_close: None,
3290 auto_surround: None,
3291 textmate_grammar: None,
3292 show_whitespace_tabs: true,
3293 line_wrap: None,
3294 wrap_column: None,
3295 page_view: None,
3296 page_width: None,
3297 use_tabs: None,
3298 tab_size: None,
3299 formatter: Some(FormatterConfig {
3300 command: "prettier".to_string(),
3301 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3302 stdin: true,
3303 timeout_ms: 10000,
3304 }),
3305 format_on_save: false,
3306 on_save: vec![],
3307 word_characters: None,
3308 },
3309 );
3310
3311 languages.insert(
3312 "python".to_string(),
3313 LanguageConfig {
3314 extensions: vec!["py".to_string(), "pyi".to_string()],
3315 filenames: vec![],
3316 grammar: "python".to_string(),
3317 comment_prefix: Some("#".to_string()),
3318 auto_indent: true,
3319 auto_close: None,
3320 auto_surround: None,
3321 textmate_grammar: None,
3322 show_whitespace_tabs: true,
3323 line_wrap: None,
3324 wrap_column: None,
3325 page_view: None,
3326 page_width: None,
3327 use_tabs: None,
3328 tab_size: None,
3329 formatter: Some(FormatterConfig {
3330 command: "ruff".to_string(),
3331 args: vec![
3332 "format".to_string(),
3333 "--stdin-filename".to_string(),
3334 "$FILE".to_string(),
3335 ],
3336 stdin: true,
3337 timeout_ms: 10000,
3338 }),
3339 format_on_save: false,
3340 on_save: vec![],
3341 word_characters: None,
3342 },
3343 );
3344
3345 languages.insert(
3346 "c".to_string(),
3347 LanguageConfig {
3348 extensions: vec!["c".to_string(), "h".to_string()],
3349 filenames: vec![],
3350 grammar: "c".to_string(),
3351 comment_prefix: Some("//".to_string()),
3352 auto_indent: true,
3353 auto_close: None,
3354 auto_surround: None,
3355 textmate_grammar: None,
3356 show_whitespace_tabs: true,
3357 line_wrap: None,
3358 wrap_column: None,
3359 page_view: None,
3360 page_width: None,
3361 use_tabs: None,
3362 tab_size: None,
3363 formatter: Some(FormatterConfig {
3364 command: "clang-format".to_string(),
3365 args: vec![],
3366 stdin: true,
3367 timeout_ms: 10000,
3368 }),
3369 format_on_save: false,
3370 on_save: vec![],
3371 word_characters: None,
3372 },
3373 );
3374
3375 languages.insert(
3376 "cpp".to_string(),
3377 LanguageConfig {
3378 extensions: vec![
3379 "cpp".to_string(),
3380 "cc".to_string(),
3381 "cxx".to_string(),
3382 "hpp".to_string(),
3383 "hh".to_string(),
3384 "hxx".to_string(),
3385 ],
3386 filenames: vec![],
3387 grammar: "cpp".to_string(),
3388 comment_prefix: Some("//".to_string()),
3389 auto_indent: true,
3390 auto_close: None,
3391 auto_surround: None,
3392 textmate_grammar: None,
3393 show_whitespace_tabs: true,
3394 line_wrap: None,
3395 wrap_column: None,
3396 page_view: None,
3397 page_width: None,
3398 use_tabs: None,
3399 tab_size: None,
3400 formatter: Some(FormatterConfig {
3401 command: "clang-format".to_string(),
3402 args: vec![],
3403 stdin: true,
3404 timeout_ms: 10000,
3405 }),
3406 format_on_save: false,
3407 on_save: vec![],
3408 word_characters: None,
3409 },
3410 );
3411
3412 languages.insert(
3413 "csharp".to_string(),
3414 LanguageConfig {
3415 extensions: vec!["cs".to_string()],
3416 filenames: vec![],
3417 grammar: "C#".to_string(),
3418 comment_prefix: Some("//".to_string()),
3419 auto_indent: true,
3420 auto_close: None,
3421 auto_surround: None,
3422 textmate_grammar: None,
3423 show_whitespace_tabs: true,
3424 line_wrap: None,
3425 wrap_column: None,
3426 page_view: None,
3427 page_width: None,
3428 use_tabs: None,
3429 tab_size: None,
3430 formatter: None,
3431 format_on_save: false,
3432 on_save: vec![],
3433 word_characters: None,
3434 },
3435 );
3436
3437 languages.insert(
3438 "bash".to_string(),
3439 LanguageConfig {
3440 extensions: vec!["sh".to_string(), "bash".to_string()],
3441 filenames: vec![
3442 ".bash_aliases".to_string(),
3443 ".bash_logout".to_string(),
3444 ".bash_profile".to_string(),
3445 ".bashrc".to_string(),
3446 ".env".to_string(),
3447 ".profile".to_string(),
3448 ".zlogin".to_string(),
3449 ".zlogout".to_string(),
3450 ".zprofile".to_string(),
3451 ".zshenv".to_string(),
3452 ".zshrc".to_string(),
3453 "PKGBUILD".to_string(),
3455 "APKBUILD".to_string(),
3456 ],
3457 grammar: "bash".to_string(),
3458 comment_prefix: Some("#".to_string()),
3459 auto_indent: true,
3460 auto_close: None,
3461 auto_surround: None,
3462 textmate_grammar: None,
3463 show_whitespace_tabs: true,
3464 line_wrap: None,
3465 wrap_column: None,
3466 page_view: None,
3467 page_width: None,
3468 use_tabs: None,
3469 tab_size: None,
3470 formatter: None,
3471 format_on_save: false,
3472 on_save: vec![],
3473 word_characters: None,
3474 },
3475 );
3476
3477 languages.insert(
3478 "makefile".to_string(),
3479 LanguageConfig {
3480 extensions: vec!["mk".to_string()],
3481 filenames: vec![
3482 "Makefile".to_string(),
3483 "makefile".to_string(),
3484 "GNUmakefile".to_string(),
3485 ],
3486 grammar: "Makefile".to_string(),
3487 comment_prefix: Some("#".to_string()),
3488 auto_indent: false,
3489 auto_close: None,
3490 auto_surround: None,
3491 textmate_grammar: None,
3492 show_whitespace_tabs: true,
3493 line_wrap: None,
3494 wrap_column: None,
3495 page_view: None,
3496 page_width: None,
3497 use_tabs: Some(true), tab_size: Some(8), formatter: None,
3500 format_on_save: false,
3501 on_save: vec![],
3502 word_characters: None,
3503 },
3504 );
3505
3506 languages.insert(
3507 "dockerfile".to_string(),
3508 LanguageConfig {
3509 extensions: vec!["dockerfile".to_string()],
3510 filenames: vec!["Dockerfile".to_string(), "Containerfile".to_string()],
3511 grammar: "dockerfile".to_string(),
3512 comment_prefix: Some("#".to_string()),
3513 auto_indent: true,
3514 auto_close: None,
3515 auto_surround: None,
3516 textmate_grammar: None,
3517 show_whitespace_tabs: true,
3518 line_wrap: None,
3519 wrap_column: None,
3520 page_view: None,
3521 page_width: None,
3522 use_tabs: None,
3523 tab_size: None,
3524 formatter: None,
3525 format_on_save: false,
3526 on_save: vec![],
3527 word_characters: None,
3528 },
3529 );
3530
3531 languages.insert(
3532 "json".to_string(),
3533 LanguageConfig {
3534 extensions: vec!["json".to_string()],
3535 filenames: vec![],
3536 grammar: "json".to_string(),
3537 comment_prefix: None,
3538 auto_indent: true,
3539 auto_close: None,
3540 auto_surround: None,
3541 textmate_grammar: None,
3542 show_whitespace_tabs: true,
3543 line_wrap: None,
3544 wrap_column: None,
3545 page_view: None,
3546 page_width: None,
3547 use_tabs: None,
3548 tab_size: None,
3549 formatter: Some(FormatterConfig {
3550 command: "prettier".to_string(),
3551 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3552 stdin: true,
3553 timeout_ms: 10000,
3554 }),
3555 format_on_save: false,
3556 on_save: vec![],
3557 word_characters: None,
3558 },
3559 );
3560
3561 languages.insert(
3568 "jsonc".to_string(),
3569 LanguageConfig {
3570 extensions: vec!["jsonc".to_string()],
3571 filenames: vec![
3572 "devcontainer.json".to_string(),
3573 ".devcontainer.json".to_string(),
3574 "tsconfig.json".to_string(),
3575 "tsconfig.*.json".to_string(),
3576 "jsconfig.json".to_string(),
3577 "jsconfig.*.json".to_string(),
3578 ".eslintrc.json".to_string(),
3579 ".babelrc".to_string(),
3580 ".babelrc.json".to_string(),
3581 ".swcrc".to_string(),
3582 ".jshintrc".to_string(),
3583 ".hintrc".to_string(),
3584 "settings.json".to_string(),
3585 "keybindings.json".to_string(),
3586 "tasks.json".to_string(),
3587 "launch.json".to_string(),
3588 "extensions.json".to_string(),
3589 "argv.json".to_string(),
3590 ],
3591 grammar: "jsonc".to_string(),
3592 comment_prefix: Some("//".to_string()),
3593 auto_indent: true,
3594 auto_close: None,
3595 auto_surround: None,
3596 textmate_grammar: None,
3597 show_whitespace_tabs: true,
3598 line_wrap: None,
3599 wrap_column: None,
3600 page_view: None,
3601 page_width: None,
3602 use_tabs: None,
3603 tab_size: None,
3604 formatter: Some(FormatterConfig {
3605 command: "prettier".to_string(),
3606 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3607 stdin: true,
3608 timeout_ms: 10000,
3609 }),
3610 format_on_save: false,
3611 on_save: vec![],
3612 word_characters: None,
3613 },
3614 );
3615
3616 languages.insert(
3617 "toml".to_string(),
3618 LanguageConfig {
3619 extensions: vec!["toml".to_string()],
3620 filenames: vec!["Cargo.lock".to_string()],
3621 grammar: "toml".to_string(),
3622 comment_prefix: Some("#".to_string()),
3623 auto_indent: true,
3624 auto_close: None,
3625 auto_surround: None,
3626 textmate_grammar: None,
3627 show_whitespace_tabs: true,
3628 line_wrap: None,
3629 wrap_column: None,
3630 page_view: None,
3631 page_width: None,
3632 use_tabs: None,
3633 tab_size: None,
3634 formatter: None,
3635 format_on_save: false,
3636 on_save: vec![],
3637 word_characters: None,
3638 },
3639 );
3640
3641 languages.insert(
3642 "yaml".to_string(),
3643 LanguageConfig {
3644 extensions: vec!["yml".to_string(), "yaml".to_string()],
3645 filenames: vec![],
3646 grammar: "yaml".to_string(),
3647 comment_prefix: Some("#".to_string()),
3648 auto_indent: true,
3649 auto_close: None,
3650 auto_surround: None,
3651 textmate_grammar: None,
3652 show_whitespace_tabs: true,
3653 line_wrap: None,
3654 wrap_column: None,
3655 page_view: None,
3656 page_width: None,
3657 use_tabs: None,
3658 tab_size: None,
3659 formatter: Some(FormatterConfig {
3660 command: "prettier".to_string(),
3661 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3662 stdin: true,
3663 timeout_ms: 10000,
3664 }),
3665 format_on_save: false,
3666 on_save: vec![],
3667 word_characters: None,
3668 },
3669 );
3670
3671 languages.insert(
3672 "markdown".to_string(),
3673 LanguageConfig {
3674 extensions: vec!["md".to_string(), "markdown".to_string()],
3675 filenames: vec!["README".to_string()],
3676 grammar: "markdown".to_string(),
3677 comment_prefix: None,
3678 auto_indent: false,
3679 auto_close: None,
3680 auto_surround: None,
3681 textmate_grammar: None,
3682 show_whitespace_tabs: true,
3683 line_wrap: None,
3684 wrap_column: None,
3685 page_view: None,
3686 page_width: None,
3687 use_tabs: None,
3688 tab_size: None,
3689 formatter: None,
3690 format_on_save: false,
3691 on_save: vec![],
3692 word_characters: None,
3693 },
3694 );
3695
3696 languages.insert(
3698 "go".to_string(),
3699 LanguageConfig {
3700 extensions: vec!["go".to_string()],
3701 filenames: vec![],
3702 grammar: "go".to_string(),
3703 comment_prefix: Some("//".to_string()),
3704 auto_indent: true,
3705 auto_close: None,
3706 auto_surround: None,
3707 textmate_grammar: None,
3708 show_whitespace_tabs: false,
3709 line_wrap: None,
3710 wrap_column: None,
3711 page_view: None,
3712 page_width: None,
3713 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
3716 command: "gofmt".to_string(),
3717 args: vec![],
3718 stdin: true,
3719 timeout_ms: 10000,
3720 }),
3721 format_on_save: false,
3722 on_save: vec![],
3723 word_characters: None,
3724 },
3725 );
3726
3727 languages.insert(
3728 "odin".to_string(),
3729 LanguageConfig {
3730 extensions: vec!["odin".to_string()],
3731 filenames: vec![],
3732 grammar: "odin".to_string(),
3733 comment_prefix: Some("//".to_string()),
3734 auto_indent: true,
3735 auto_close: None,
3736 auto_surround: None,
3737 textmate_grammar: None,
3738 show_whitespace_tabs: false,
3739 line_wrap: None,
3740 wrap_column: None,
3741 page_view: None,
3742 page_width: None,
3743 use_tabs: Some(true),
3744 tab_size: Some(8),
3745 formatter: None,
3746 format_on_save: false,
3747 on_save: vec![],
3748 word_characters: None,
3749 },
3750 );
3751
3752 languages.insert(
3753 "zig".to_string(),
3754 LanguageConfig {
3755 extensions: vec!["zig".to_string(), "zon".to_string()],
3756 filenames: vec![],
3757 grammar: "zig".to_string(),
3758 comment_prefix: Some("//".to_string()),
3759 auto_indent: true,
3760 auto_close: None,
3761 auto_surround: None,
3762 textmate_grammar: None,
3763 show_whitespace_tabs: true,
3764 line_wrap: None,
3765 wrap_column: None,
3766 page_view: None,
3767 page_width: None,
3768 use_tabs: None,
3769 tab_size: None,
3770 formatter: None,
3771 format_on_save: false,
3772 on_save: vec![],
3773 word_characters: None,
3774 },
3775 );
3776
3777 languages.insert(
3778 "java".to_string(),
3779 LanguageConfig {
3780 extensions: vec!["java".to_string()],
3781 filenames: vec![],
3782 grammar: "java".to_string(),
3783 comment_prefix: Some("//".to_string()),
3784 auto_indent: true,
3785 auto_close: None,
3786 auto_surround: None,
3787 textmate_grammar: None,
3788 show_whitespace_tabs: true,
3789 line_wrap: None,
3790 wrap_column: None,
3791 page_view: None,
3792 page_width: None,
3793 use_tabs: None,
3794 tab_size: None,
3795 formatter: None,
3796 format_on_save: false,
3797 on_save: vec![],
3798 word_characters: None,
3799 },
3800 );
3801
3802 languages.insert(
3803 "latex".to_string(),
3804 LanguageConfig {
3805 extensions: vec![
3806 "tex".to_string(),
3807 "latex".to_string(),
3808 "ltx".to_string(),
3809 "sty".to_string(),
3810 "cls".to_string(),
3811 "bib".to_string(),
3812 ],
3813 filenames: vec![],
3814 grammar: "latex".to_string(),
3815 comment_prefix: Some("%".to_string()),
3816 auto_indent: true,
3817 auto_close: None,
3818 auto_surround: None,
3819 textmate_grammar: None,
3820 show_whitespace_tabs: true,
3821 line_wrap: None,
3822 wrap_column: None,
3823 page_view: None,
3824 page_width: None,
3825 use_tabs: None,
3826 tab_size: None,
3827 formatter: None,
3828 format_on_save: false,
3829 on_save: vec![],
3830 word_characters: None,
3831 },
3832 );
3833
3834 languages.insert(
3835 "templ".to_string(),
3836 LanguageConfig {
3837 extensions: vec!["templ".to_string()],
3838 filenames: vec![],
3839 grammar: "go".to_string(), comment_prefix: Some("//".to_string()),
3841 auto_indent: true,
3842 auto_close: None,
3843 auto_surround: None,
3844 textmate_grammar: None,
3845 show_whitespace_tabs: true,
3846 line_wrap: None,
3847 wrap_column: None,
3848 page_view: None,
3849 page_width: None,
3850 use_tabs: None,
3851 tab_size: None,
3852 formatter: None,
3853 format_on_save: false,
3854 on_save: vec![],
3855 word_characters: None,
3856 },
3857 );
3858
3859 languages.insert(
3861 "git-rebase".to_string(),
3862 LanguageConfig {
3863 extensions: vec![],
3864 filenames: vec!["git-rebase-todo".to_string()],
3865 grammar: "Git Rebase Todo".to_string(),
3866 comment_prefix: Some("#".to_string()),
3867 auto_indent: false,
3868 auto_close: None,
3869 auto_surround: None,
3870 textmate_grammar: None,
3871 show_whitespace_tabs: true,
3872 line_wrap: None,
3873 wrap_column: None,
3874 page_view: None,
3875 page_width: None,
3876 use_tabs: None,
3877 tab_size: None,
3878 formatter: None,
3879 format_on_save: false,
3880 on_save: vec![],
3881 word_characters: None,
3882 },
3883 );
3884
3885 languages.insert(
3886 "git-commit".to_string(),
3887 LanguageConfig {
3888 extensions: vec![],
3889 filenames: vec![
3890 "COMMIT_EDITMSG".to_string(),
3891 "MERGE_MSG".to_string(),
3892 "SQUASH_MSG".to_string(),
3893 "TAG_EDITMSG".to_string(),
3894 ],
3895 grammar: "Git Commit Message".to_string(),
3896 comment_prefix: Some("#".to_string()),
3897 auto_indent: false,
3898 auto_close: None,
3899 auto_surround: None,
3900 textmate_grammar: None,
3901 show_whitespace_tabs: true,
3902 line_wrap: None,
3903 wrap_column: None,
3904 page_view: None,
3905 page_width: None,
3906 use_tabs: None,
3907 tab_size: None,
3908 formatter: None,
3909 format_on_save: false,
3910 on_save: vec![],
3911 word_characters: None,
3912 },
3913 );
3914
3915 languages.insert(
3916 "gitignore".to_string(),
3917 LanguageConfig {
3918 extensions: vec!["gitignore".to_string()],
3919 filenames: vec![
3920 ".gitignore".to_string(),
3921 ".dockerignore".to_string(),
3922 ".npmignore".to_string(),
3923 ".hgignore".to_string(),
3924 ],
3925 grammar: "Gitignore".to_string(),
3926 comment_prefix: Some("#".to_string()),
3927 auto_indent: false,
3928 auto_close: None,
3929 auto_surround: None,
3930 textmate_grammar: None,
3931 show_whitespace_tabs: true,
3932 line_wrap: None,
3933 wrap_column: None,
3934 page_view: None,
3935 page_width: None,
3936 use_tabs: None,
3937 tab_size: None,
3938 formatter: None,
3939 format_on_save: false,
3940 on_save: vec![],
3941 word_characters: None,
3942 },
3943 );
3944
3945 languages.insert(
3946 "gitconfig".to_string(),
3947 LanguageConfig {
3948 extensions: vec!["gitconfig".to_string()],
3949 filenames: vec![".gitconfig".to_string(), ".gitmodules".to_string()],
3950 grammar: "Git Config".to_string(),
3951 comment_prefix: Some("#".to_string()),
3952 auto_indent: true,
3953 auto_close: None,
3954 auto_surround: None,
3955 textmate_grammar: None,
3956 show_whitespace_tabs: true,
3957 line_wrap: None,
3958 wrap_column: None,
3959 page_view: None,
3960 page_width: None,
3961 use_tabs: None,
3962 tab_size: None,
3963 formatter: None,
3964 format_on_save: false,
3965 on_save: vec![],
3966 word_characters: None,
3967 },
3968 );
3969
3970 languages.insert(
3971 "gitattributes".to_string(),
3972 LanguageConfig {
3973 extensions: vec!["gitattributes".to_string()],
3974 filenames: vec![".gitattributes".to_string()],
3975 grammar: "Git Attributes".to_string(),
3976 comment_prefix: Some("#".to_string()),
3977 auto_indent: false,
3978 auto_close: None,
3979 auto_surround: None,
3980 textmate_grammar: None,
3981 show_whitespace_tabs: true,
3982 line_wrap: None,
3983 wrap_column: None,
3984 page_view: None,
3985 page_width: None,
3986 use_tabs: None,
3987 tab_size: None,
3988 formatter: None,
3989 format_on_save: false,
3990 on_save: vec![],
3991 word_characters: None,
3992 },
3993 );
3994
3995 languages.insert(
3996 "typst".to_string(),
3997 LanguageConfig {
3998 extensions: vec!["typ".to_string()],
3999 filenames: vec![],
4000 grammar: "Typst".to_string(),
4001 comment_prefix: Some("//".to_string()),
4002 auto_indent: true,
4003 auto_close: None,
4004 auto_surround: None,
4005 textmate_grammar: None,
4006 show_whitespace_tabs: true,
4007 line_wrap: None,
4008 wrap_column: None,
4009 page_view: None,
4010 page_width: None,
4011 use_tabs: None,
4012 tab_size: None,
4013 formatter: None,
4014 format_on_save: false,
4015 on_save: vec![],
4016 word_characters: None,
4017 },
4018 );
4019
4020 languages.insert(
4025 "kotlin".to_string(),
4026 LanguageConfig {
4027 extensions: vec!["kt".to_string(), "kts".to_string()],
4028 filenames: vec![],
4029 grammar: "Kotlin".to_string(),
4030 comment_prefix: Some("//".to_string()),
4031 auto_indent: true,
4032 auto_close: None,
4033 auto_surround: None,
4034 textmate_grammar: None,
4035 show_whitespace_tabs: true,
4036 line_wrap: None,
4037 wrap_column: None,
4038 page_view: None,
4039 page_width: None,
4040 use_tabs: None,
4041 tab_size: None,
4042 formatter: None,
4043 format_on_save: false,
4044 on_save: vec![],
4045 word_characters: None,
4046 },
4047 );
4048
4049 languages.insert(
4050 "swift".to_string(),
4051 LanguageConfig {
4052 extensions: vec!["swift".to_string()],
4053 filenames: vec![],
4054 grammar: "Swift".to_string(),
4055 comment_prefix: Some("//".to_string()),
4056 auto_indent: true,
4057 auto_close: None,
4058 auto_surround: None,
4059 textmate_grammar: None,
4060 show_whitespace_tabs: true,
4061 line_wrap: None,
4062 wrap_column: None,
4063 page_view: None,
4064 page_width: None,
4065 use_tabs: None,
4066 tab_size: None,
4067 formatter: None,
4068 format_on_save: false,
4069 on_save: vec![],
4070 word_characters: None,
4071 },
4072 );
4073
4074 languages.insert(
4075 "scala".to_string(),
4076 LanguageConfig {
4077 extensions: vec!["scala".to_string(), "sc".to_string()],
4078 filenames: vec![],
4079 grammar: "Scala".to_string(),
4080 comment_prefix: Some("//".to_string()),
4081 auto_indent: true,
4082 auto_close: None,
4083 auto_surround: None,
4084 textmate_grammar: None,
4085 show_whitespace_tabs: true,
4086 line_wrap: None,
4087 wrap_column: None,
4088 page_view: None,
4089 page_width: None,
4090 use_tabs: None,
4091 tab_size: None,
4092 formatter: None,
4093 format_on_save: false,
4094 on_save: vec![],
4095 word_characters: None,
4096 },
4097 );
4098
4099 languages.insert(
4100 "dart".to_string(),
4101 LanguageConfig {
4102 extensions: vec!["dart".to_string()],
4103 filenames: vec![],
4104 grammar: "Dart".to_string(),
4105 comment_prefix: Some("//".to_string()),
4106 auto_indent: true,
4107 auto_close: None,
4108 auto_surround: None,
4109 textmate_grammar: None,
4110 show_whitespace_tabs: true,
4111 line_wrap: None,
4112 wrap_column: None,
4113 page_view: None,
4114 page_width: None,
4115 use_tabs: None,
4116 tab_size: None,
4117 formatter: None,
4118 format_on_save: false,
4119 on_save: vec![],
4120 word_characters: None,
4121 },
4122 );
4123
4124 languages.insert(
4125 "elixir".to_string(),
4126 LanguageConfig {
4127 extensions: vec!["ex".to_string(), "exs".to_string()],
4128 filenames: vec![],
4129 grammar: "Elixir".to_string(),
4130 comment_prefix: Some("#".to_string()),
4131 auto_indent: true,
4132 auto_close: None,
4133 auto_surround: None,
4134 textmate_grammar: None,
4135 show_whitespace_tabs: true,
4136 line_wrap: None,
4137 wrap_column: None,
4138 page_view: None,
4139 page_width: None,
4140 use_tabs: None,
4141 tab_size: None,
4142 formatter: None,
4143 format_on_save: false,
4144 on_save: vec![],
4145 word_characters: None,
4146 },
4147 );
4148
4149 languages.insert(
4150 "erlang".to_string(),
4151 LanguageConfig {
4152 extensions: vec!["erl".to_string(), "hrl".to_string()],
4153 filenames: vec![],
4154 grammar: "Erlang".to_string(),
4155 comment_prefix: Some("%".to_string()),
4156 auto_indent: true,
4157 auto_close: None,
4158 auto_surround: None,
4159 textmate_grammar: None,
4160 show_whitespace_tabs: true,
4161 line_wrap: None,
4162 wrap_column: None,
4163 page_view: None,
4164 page_width: None,
4165 use_tabs: None,
4166 tab_size: None,
4167 formatter: None,
4168 format_on_save: false,
4169 on_save: vec![],
4170 word_characters: None,
4171 },
4172 );
4173
4174 languages.insert(
4175 "haskell".to_string(),
4176 LanguageConfig {
4177 extensions: vec!["hs".to_string(), "lhs".to_string()],
4178 filenames: vec![],
4179 grammar: "Haskell".to_string(),
4180 comment_prefix: Some("--".to_string()),
4181 auto_indent: true,
4182 auto_close: None,
4183 auto_surround: None,
4184 textmate_grammar: None,
4185 show_whitespace_tabs: true,
4186 line_wrap: None,
4187 wrap_column: None,
4188 page_view: None,
4189 page_width: None,
4190 use_tabs: None,
4191 tab_size: None,
4192 formatter: None,
4193 format_on_save: false,
4194 on_save: vec![],
4195 word_characters: None,
4196 },
4197 );
4198
4199 languages.insert(
4200 "ocaml".to_string(),
4201 LanguageConfig {
4202 extensions: vec!["ml".to_string(), "mli".to_string()],
4203 filenames: vec![],
4204 grammar: "OCaml".to_string(),
4205 comment_prefix: None,
4206 auto_indent: true,
4207 auto_close: None,
4208 auto_surround: None,
4209 textmate_grammar: None,
4210 show_whitespace_tabs: true,
4211 line_wrap: None,
4212 wrap_column: None,
4213 page_view: None,
4214 page_width: None,
4215 use_tabs: None,
4216 tab_size: None,
4217 formatter: None,
4218 format_on_save: false,
4219 on_save: vec![],
4220 word_characters: None,
4221 },
4222 );
4223
4224 languages.insert(
4225 "clojure".to_string(),
4226 LanguageConfig {
4227 extensions: vec![
4228 "clj".to_string(),
4229 "cljs".to_string(),
4230 "cljc".to_string(),
4231 "edn".to_string(),
4232 ],
4233 filenames: vec![],
4234 grammar: "Clojure".to_string(),
4235 comment_prefix: Some(";".to_string()),
4236 auto_indent: true,
4237 auto_close: None,
4238 auto_surround: None,
4239 textmate_grammar: None,
4240 show_whitespace_tabs: true,
4241 line_wrap: None,
4242 wrap_column: None,
4243 page_view: None,
4244 page_width: None,
4245 use_tabs: None,
4246 tab_size: None,
4247 formatter: None,
4248 format_on_save: false,
4249 on_save: vec![],
4250 word_characters: None,
4251 },
4252 );
4253
4254 languages.insert(
4255 "r".to_string(),
4256 LanguageConfig {
4257 extensions: vec!["r".to_string(), "R".to_string(), "rmd".to_string()],
4258 filenames: vec![],
4259 grammar: "R".to_string(),
4260 comment_prefix: Some("#".to_string()),
4261 auto_indent: true,
4262 auto_close: None,
4263 auto_surround: None,
4264 textmate_grammar: None,
4265 show_whitespace_tabs: true,
4266 line_wrap: None,
4267 wrap_column: None,
4268 page_view: None,
4269 page_width: None,
4270 use_tabs: None,
4271 tab_size: None,
4272 formatter: None,
4273 format_on_save: false,
4274 on_save: vec![],
4275 word_characters: None,
4276 },
4277 );
4278
4279 languages.insert(
4280 "julia".to_string(),
4281 LanguageConfig {
4282 extensions: vec!["jl".to_string()],
4283 filenames: vec![],
4284 grammar: "Julia".to_string(),
4285 comment_prefix: Some("#".to_string()),
4286 auto_indent: true,
4287 auto_close: None,
4288 auto_surround: None,
4289 textmate_grammar: None,
4290 show_whitespace_tabs: true,
4291 line_wrap: None,
4292 wrap_column: None,
4293 page_view: None,
4294 page_width: None,
4295 use_tabs: None,
4296 tab_size: None,
4297 formatter: None,
4298 format_on_save: false,
4299 on_save: vec![],
4300 word_characters: None,
4301 },
4302 );
4303
4304 languages.insert(
4305 "perl".to_string(),
4306 LanguageConfig {
4307 extensions: vec!["pl".to_string(), "pm".to_string(), "t".to_string()],
4308 filenames: vec![],
4309 grammar: "Perl".to_string(),
4310 comment_prefix: Some("#".to_string()),
4311 auto_indent: true,
4312 auto_close: None,
4313 auto_surround: None,
4314 textmate_grammar: None,
4315 show_whitespace_tabs: true,
4316 line_wrap: None,
4317 wrap_column: None,
4318 page_view: None,
4319 page_width: None,
4320 use_tabs: None,
4321 tab_size: None,
4322 formatter: None,
4323 format_on_save: false,
4324 on_save: vec![],
4325 word_characters: None,
4326 },
4327 );
4328
4329 languages.insert(
4330 "nim".to_string(),
4331 LanguageConfig {
4332 extensions: vec!["nim".to_string(), "nims".to_string(), "nimble".to_string()],
4333 filenames: vec![],
4334 grammar: "Nim".to_string(),
4335 comment_prefix: Some("#".to_string()),
4336 auto_indent: true,
4337 auto_close: None,
4338 auto_surround: None,
4339 textmate_grammar: None,
4340 show_whitespace_tabs: true,
4341 line_wrap: None,
4342 wrap_column: None,
4343 page_view: None,
4344 page_width: None,
4345 use_tabs: None,
4346 tab_size: None,
4347 formatter: None,
4348 format_on_save: false,
4349 on_save: vec![],
4350 word_characters: None,
4351 },
4352 );
4353
4354 languages.insert(
4355 "gleam".to_string(),
4356 LanguageConfig {
4357 extensions: vec!["gleam".to_string()],
4358 filenames: vec![],
4359 grammar: "Gleam".to_string(),
4360 comment_prefix: Some("//".to_string()),
4361 auto_indent: true,
4362 auto_close: None,
4363 auto_surround: None,
4364 textmate_grammar: None,
4365 show_whitespace_tabs: true,
4366 line_wrap: None,
4367 wrap_column: None,
4368 page_view: None,
4369 page_width: None,
4370 use_tabs: None,
4371 tab_size: None,
4372 formatter: None,
4373 format_on_save: false,
4374 on_save: vec![],
4375 word_characters: None,
4376 },
4377 );
4378
4379 languages.insert(
4380 "fsharp".to_string(),
4381 LanguageConfig {
4382 extensions: vec!["fs".to_string(), "fsi".to_string(), "fsx".to_string()],
4383 filenames: vec![],
4384 grammar: "FSharp".to_string(),
4385 comment_prefix: Some("//".to_string()),
4386 auto_indent: true,
4387 auto_close: None,
4388 auto_surround: None,
4389 textmate_grammar: None,
4390 show_whitespace_tabs: true,
4391 line_wrap: None,
4392 wrap_column: None,
4393 page_view: None,
4394 page_width: None,
4395 use_tabs: None,
4396 tab_size: None,
4397 formatter: None,
4398 format_on_save: false,
4399 on_save: vec![],
4400 word_characters: None,
4401 },
4402 );
4403
4404 languages.insert(
4405 "nix".to_string(),
4406 LanguageConfig {
4407 extensions: vec!["nix".to_string()],
4408 filenames: vec![],
4409 grammar: "Nix".to_string(),
4410 comment_prefix: Some("#".to_string()),
4411 auto_indent: true,
4412 auto_close: None,
4413 auto_surround: None,
4414 textmate_grammar: None,
4415 show_whitespace_tabs: true,
4416 line_wrap: None,
4417 wrap_column: None,
4418 page_view: None,
4419 page_width: None,
4420 use_tabs: None,
4421 tab_size: None,
4422 formatter: None,
4423 format_on_save: false,
4424 on_save: vec![],
4425 word_characters: None,
4426 },
4427 );
4428
4429 languages.insert(
4430 "nushell".to_string(),
4431 LanguageConfig {
4432 extensions: vec!["nu".to_string()],
4433 filenames: vec![],
4434 grammar: "Nushell".to_string(),
4435 comment_prefix: Some("#".to_string()),
4436 auto_indent: true,
4437 auto_close: None,
4438 auto_surround: None,
4439 textmate_grammar: None,
4440 show_whitespace_tabs: true,
4441 line_wrap: None,
4442 wrap_column: None,
4443 page_view: None,
4444 page_width: None,
4445 use_tabs: None,
4446 tab_size: None,
4447 formatter: None,
4448 format_on_save: false,
4449 on_save: vec![],
4450 word_characters: None,
4451 },
4452 );
4453
4454 languages.insert(
4455 "solidity".to_string(),
4456 LanguageConfig {
4457 extensions: vec!["sol".to_string()],
4458 filenames: vec![],
4459 grammar: "Solidity".to_string(),
4460 comment_prefix: Some("//".to_string()),
4461 auto_indent: true,
4462 auto_close: None,
4463 auto_surround: None,
4464 textmate_grammar: None,
4465 show_whitespace_tabs: true,
4466 line_wrap: None,
4467 wrap_column: None,
4468 page_view: None,
4469 page_width: None,
4470 use_tabs: None,
4471 tab_size: None,
4472 formatter: None,
4473 format_on_save: false,
4474 on_save: vec![],
4475 word_characters: None,
4476 },
4477 );
4478
4479 languages.insert(
4480 "ruby".to_string(),
4481 LanguageConfig {
4482 extensions: vec!["rb".to_string(), "rake".to_string(), "gemspec".to_string()],
4483 filenames: vec![
4484 "Gemfile".to_string(),
4485 "Rakefile".to_string(),
4486 "Guardfile".to_string(),
4487 ],
4488 grammar: "Ruby".to_string(),
4489 comment_prefix: Some("#".to_string()),
4490 auto_indent: true,
4491 auto_close: None,
4492 auto_surround: None,
4493 textmate_grammar: None,
4494 show_whitespace_tabs: true,
4495 line_wrap: None,
4496 wrap_column: None,
4497 page_view: None,
4498 page_width: None,
4499 use_tabs: None,
4500 tab_size: None,
4501 formatter: None,
4502 format_on_save: false,
4503 on_save: vec![],
4504 word_characters: None,
4505 },
4506 );
4507
4508 languages.insert(
4509 "php".to_string(),
4510 LanguageConfig {
4511 extensions: vec!["php".to_string(), "phtml".to_string()],
4512 filenames: vec![],
4513 grammar: "PHP".to_string(),
4514 comment_prefix: Some("//".to_string()),
4515 auto_indent: true,
4516 auto_close: None,
4517 auto_surround: None,
4518 textmate_grammar: None,
4519 show_whitespace_tabs: true,
4520 line_wrap: None,
4521 wrap_column: None,
4522 page_view: None,
4523 page_width: None,
4524 use_tabs: None,
4525 tab_size: None,
4526 formatter: None,
4527 format_on_save: false,
4528 on_save: vec![],
4529 word_characters: None,
4530 },
4531 );
4532
4533 languages.insert(
4534 "lua".to_string(),
4535 LanguageConfig {
4536 extensions: vec!["lua".to_string()],
4537 filenames: vec![],
4538 grammar: "Lua".to_string(),
4539 comment_prefix: Some("--".to_string()),
4540 auto_indent: true,
4541 auto_close: None,
4542 auto_surround: None,
4543 textmate_grammar: None,
4544 show_whitespace_tabs: true,
4545 line_wrap: None,
4546 wrap_column: None,
4547 page_view: None,
4548 page_width: None,
4549 use_tabs: None,
4550 tab_size: None,
4551 formatter: None,
4552 format_on_save: false,
4553 on_save: vec![],
4554 word_characters: None,
4555 },
4556 );
4557
4558 languages.insert(
4559 "html".to_string(),
4560 LanguageConfig {
4561 extensions: vec!["html".to_string(), "htm".to_string()],
4562 filenames: vec![],
4563 grammar: "HTML".to_string(),
4564 comment_prefix: None,
4565 auto_indent: true,
4566 auto_close: None,
4567 auto_surround: None,
4568 textmate_grammar: None,
4569 show_whitespace_tabs: true,
4570 line_wrap: None,
4571 wrap_column: None,
4572 page_view: None,
4573 page_width: None,
4574 use_tabs: None,
4575 tab_size: None,
4576 formatter: None,
4577 format_on_save: false,
4578 on_save: vec![],
4579 word_characters: None,
4580 },
4581 );
4582
4583 languages.insert(
4584 "css".to_string(),
4585 LanguageConfig {
4586 extensions: vec!["css".to_string()],
4587 filenames: vec![],
4588 grammar: "CSS".to_string(),
4589 comment_prefix: None,
4590 auto_indent: true,
4591 auto_close: None,
4592 auto_surround: None,
4593 textmate_grammar: None,
4594 show_whitespace_tabs: true,
4595 line_wrap: None,
4596 wrap_column: None,
4597 page_view: None,
4598 page_width: None,
4599 use_tabs: None,
4600 tab_size: None,
4601 formatter: None,
4602 format_on_save: false,
4603 on_save: vec![],
4604 word_characters: None,
4605 },
4606 );
4607
4608 languages.insert(
4609 "sql".to_string(),
4610 LanguageConfig {
4611 extensions: vec!["sql".to_string()],
4612 filenames: vec![],
4613 grammar: "SQL".to_string(),
4614 comment_prefix: Some("--".to_string()),
4615 auto_indent: true,
4616 auto_close: None,
4617 auto_surround: None,
4618 textmate_grammar: None,
4619 show_whitespace_tabs: true,
4620 line_wrap: None,
4621 wrap_column: None,
4622 page_view: None,
4623 page_width: None,
4624 use_tabs: None,
4625 tab_size: None,
4626 formatter: None,
4627 format_on_save: false,
4628 on_save: vec![],
4629 word_characters: None,
4630 },
4631 );
4632
4633 languages.insert(
4634 "graphql".to_string(),
4635 LanguageConfig {
4636 extensions: vec!["graphql".to_string(), "gql".to_string()],
4637 filenames: vec![],
4638 grammar: "GraphQL".to_string(),
4639 comment_prefix: Some("#".to_string()),
4640 auto_indent: true,
4641 auto_close: None,
4642 auto_surround: None,
4643 textmate_grammar: None,
4644 show_whitespace_tabs: true,
4645 line_wrap: None,
4646 wrap_column: None,
4647 page_view: None,
4648 page_width: None,
4649 use_tabs: None,
4650 tab_size: None,
4651 formatter: None,
4652 format_on_save: false,
4653 on_save: vec![],
4654 word_characters: None,
4655 },
4656 );
4657
4658 languages.insert(
4659 "protobuf".to_string(),
4660 LanguageConfig {
4661 extensions: vec!["proto".to_string()],
4662 filenames: vec![],
4663 grammar: "Protocol Buffers".to_string(),
4664 comment_prefix: Some("//".to_string()),
4665 auto_indent: true,
4666 auto_close: None,
4667 auto_surround: None,
4668 textmate_grammar: None,
4669 show_whitespace_tabs: true,
4670 line_wrap: None,
4671 wrap_column: None,
4672 page_view: None,
4673 page_width: None,
4674 use_tabs: None,
4675 tab_size: None,
4676 formatter: None,
4677 format_on_save: false,
4678 on_save: vec![],
4679 word_characters: None,
4680 },
4681 );
4682
4683 languages.insert(
4684 "cmake".to_string(),
4685 LanguageConfig {
4686 extensions: vec!["cmake".to_string()],
4687 filenames: vec!["CMakeLists.txt".to_string()],
4688 grammar: "CMake".to_string(),
4689 comment_prefix: Some("#".to_string()),
4690 auto_indent: true,
4691 auto_close: None,
4692 auto_surround: None,
4693 textmate_grammar: None,
4694 show_whitespace_tabs: true,
4695 line_wrap: None,
4696 wrap_column: None,
4697 page_view: None,
4698 page_width: None,
4699 use_tabs: None,
4700 tab_size: None,
4701 formatter: None,
4702 format_on_save: false,
4703 on_save: vec![],
4704 word_characters: None,
4705 },
4706 );
4707
4708 languages.insert(
4709 "terraform".to_string(),
4710 LanguageConfig {
4711 extensions: vec!["tf".to_string(), "tfvars".to_string(), "hcl".to_string()],
4712 filenames: vec![],
4713 grammar: "HCL".to_string(),
4714 comment_prefix: Some("#".to_string()),
4715 auto_indent: true,
4716 auto_close: None,
4717 auto_surround: None,
4718 textmate_grammar: None,
4719 show_whitespace_tabs: true,
4720 line_wrap: None,
4721 wrap_column: None,
4722 page_view: None,
4723 page_width: None,
4724 use_tabs: None,
4725 tab_size: None,
4726 formatter: None,
4727 format_on_save: false,
4728 on_save: vec![],
4729 word_characters: None,
4730 },
4731 );
4732
4733 languages.insert(
4734 "vue".to_string(),
4735 LanguageConfig {
4736 extensions: vec!["vue".to_string()],
4737 filenames: vec![],
4738 grammar: "Vue".to_string(),
4739 comment_prefix: None,
4740 auto_indent: true,
4741 auto_close: None,
4742 auto_surround: None,
4743 textmate_grammar: None,
4744 show_whitespace_tabs: true,
4745 line_wrap: None,
4746 wrap_column: None,
4747 page_view: None,
4748 page_width: None,
4749 use_tabs: None,
4750 tab_size: None,
4751 formatter: None,
4752 format_on_save: false,
4753 on_save: vec![],
4754 word_characters: None,
4755 },
4756 );
4757
4758 languages.insert(
4759 "svelte".to_string(),
4760 LanguageConfig {
4761 extensions: vec!["svelte".to_string()],
4762 filenames: vec![],
4763 grammar: "Svelte".to_string(),
4764 comment_prefix: None,
4765 auto_indent: true,
4766 auto_close: None,
4767 auto_surround: None,
4768 textmate_grammar: None,
4769 show_whitespace_tabs: true,
4770 line_wrap: None,
4771 wrap_column: None,
4772 page_view: None,
4773 page_width: None,
4774 use_tabs: None,
4775 tab_size: None,
4776 formatter: None,
4777 format_on_save: false,
4778 on_save: vec![],
4779 word_characters: None,
4780 },
4781 );
4782
4783 languages.insert(
4784 "astro".to_string(),
4785 LanguageConfig {
4786 extensions: vec!["astro".to_string()],
4787 filenames: vec![],
4788 grammar: "Astro".to_string(),
4789 comment_prefix: None,
4790 auto_indent: true,
4791 auto_close: None,
4792 auto_surround: None,
4793 textmate_grammar: None,
4794 show_whitespace_tabs: true,
4795 line_wrap: None,
4796 wrap_column: None,
4797 page_view: None,
4798 page_width: None,
4799 use_tabs: None,
4800 tab_size: None,
4801 formatter: None,
4802 format_on_save: false,
4803 on_save: vec![],
4804 word_characters: None,
4805 },
4806 );
4807
4808 languages.insert(
4811 "scss".to_string(),
4812 LanguageConfig {
4813 extensions: vec!["scss".to_string()],
4814 filenames: vec![],
4815 grammar: "SCSS".to_string(),
4816 comment_prefix: Some("//".to_string()),
4817 auto_indent: true,
4818 auto_close: None,
4819 auto_surround: None,
4820 textmate_grammar: None,
4821 show_whitespace_tabs: true,
4822 line_wrap: None,
4823 wrap_column: None,
4824 page_view: None,
4825 page_width: None,
4826 use_tabs: None,
4827 tab_size: None,
4828 formatter: None,
4829 format_on_save: false,
4830 on_save: vec![],
4831 word_characters: None,
4832 },
4833 );
4834
4835 languages.insert(
4836 "less".to_string(),
4837 LanguageConfig {
4838 extensions: vec!["less".to_string()],
4839 filenames: vec![],
4840 grammar: "LESS".to_string(),
4841 comment_prefix: Some("//".to_string()),
4842 auto_indent: true,
4843 auto_close: None,
4844 auto_surround: None,
4845 textmate_grammar: None,
4846 show_whitespace_tabs: true,
4847 line_wrap: None,
4848 wrap_column: None,
4849 page_view: None,
4850 page_width: None,
4851 use_tabs: None,
4852 tab_size: None,
4853 formatter: None,
4854 format_on_save: false,
4855 on_save: vec![],
4856 word_characters: None,
4857 },
4858 );
4859
4860 languages.insert(
4861 "powershell".to_string(),
4862 LanguageConfig {
4863 extensions: vec!["ps1".to_string(), "psm1".to_string(), "psd1".to_string()],
4864 filenames: vec![],
4865 grammar: "PowerShell".to_string(),
4866 comment_prefix: Some("#".to_string()),
4867 auto_indent: true,
4868 auto_close: None,
4869 auto_surround: None,
4870 textmate_grammar: None,
4871 show_whitespace_tabs: true,
4872 line_wrap: None,
4873 wrap_column: None,
4874 page_view: None,
4875 page_width: None,
4876 use_tabs: None,
4877 tab_size: None,
4878 formatter: None,
4879 format_on_save: false,
4880 on_save: vec![],
4881 word_characters: None,
4882 },
4883 );
4884
4885 languages.insert(
4886 "kdl".to_string(),
4887 LanguageConfig {
4888 extensions: vec!["kdl".to_string()],
4889 filenames: vec![],
4890 grammar: "KDL".to_string(),
4891 comment_prefix: Some("//".to_string()),
4892 auto_indent: true,
4893 auto_close: None,
4894 auto_surround: None,
4895 textmate_grammar: None,
4896 show_whitespace_tabs: true,
4897 line_wrap: None,
4898 wrap_column: None,
4899 page_view: None,
4900 page_width: None,
4901 use_tabs: None,
4902 tab_size: None,
4903 formatter: None,
4904 format_on_save: false,
4905 on_save: vec![],
4906 word_characters: None,
4907 },
4908 );
4909
4910 languages.insert(
4911 "starlark".to_string(),
4912 LanguageConfig {
4913 extensions: vec!["bzl".to_string(), "star".to_string()],
4914 filenames: vec!["BUILD".to_string(), "WORKSPACE".to_string()],
4915 grammar: "Starlark".to_string(),
4916 comment_prefix: Some("#".to_string()),
4917 auto_indent: true,
4918 auto_close: None,
4919 auto_surround: None,
4920 textmate_grammar: None,
4921 show_whitespace_tabs: true,
4922 line_wrap: None,
4923 wrap_column: None,
4924 page_view: None,
4925 page_width: None,
4926 use_tabs: None,
4927 tab_size: None,
4928 formatter: None,
4929 format_on_save: false,
4930 on_save: vec![],
4931 word_characters: None,
4932 },
4933 );
4934
4935 languages.insert(
4936 "justfile".to_string(),
4937 LanguageConfig {
4938 extensions: vec![],
4939 filenames: vec![
4940 "justfile".to_string(),
4941 "Justfile".to_string(),
4942 ".justfile".to_string(),
4943 ],
4944 grammar: "Justfile".to_string(),
4945 comment_prefix: Some("#".to_string()),
4946 auto_indent: true,
4947 auto_close: None,
4948 auto_surround: None,
4949 textmate_grammar: None,
4950 show_whitespace_tabs: true,
4951 line_wrap: None,
4952 wrap_column: None,
4953 page_view: None,
4954 page_width: None,
4955 use_tabs: Some(true),
4956 tab_size: None,
4957 formatter: None,
4958 format_on_save: false,
4959 on_save: vec![],
4960 word_characters: None,
4961 },
4962 );
4963
4964 languages.insert(
4965 "earthfile".to_string(),
4966 LanguageConfig {
4967 extensions: vec!["earth".to_string()],
4968 filenames: vec!["Earthfile".to_string()],
4969 grammar: "Earthfile".to_string(),
4970 comment_prefix: Some("#".to_string()),
4971 auto_indent: true,
4972 auto_close: None,
4973 auto_surround: None,
4974 textmate_grammar: None,
4975 show_whitespace_tabs: true,
4976 line_wrap: None,
4977 wrap_column: None,
4978 page_view: None,
4979 page_width: None,
4980 use_tabs: None,
4981 tab_size: None,
4982 formatter: None,
4983 format_on_save: false,
4984 on_save: vec![],
4985 word_characters: None,
4986 },
4987 );
4988
4989 languages.insert(
4990 "gomod".to_string(),
4991 LanguageConfig {
4992 extensions: vec![],
4993 filenames: vec!["go.mod".to_string(), "go.sum".to_string()],
4994 grammar: "Go Module".to_string(),
4995 comment_prefix: Some("//".to_string()),
4996 auto_indent: true,
4997 auto_close: None,
4998 auto_surround: None,
4999 textmate_grammar: None,
5000 show_whitespace_tabs: true,
5001 line_wrap: None,
5002 wrap_column: None,
5003 page_view: None,
5004 page_width: None,
5005 use_tabs: Some(true),
5006 tab_size: None,
5007 formatter: None,
5008 format_on_save: false,
5009 on_save: vec![],
5010 word_characters: None,
5011 },
5012 );
5013
5014 languages.insert(
5015 "vlang".to_string(),
5016 LanguageConfig {
5017 extensions: vec!["v".to_string(), "vv".to_string()],
5018 filenames: vec![],
5019 grammar: "V".to_string(),
5020 comment_prefix: Some("//".to_string()),
5021 auto_indent: true,
5022 auto_close: None,
5023 auto_surround: None,
5024 textmate_grammar: None,
5025 show_whitespace_tabs: true,
5026 line_wrap: None,
5027 wrap_column: None,
5028 page_view: None,
5029 page_width: None,
5030 use_tabs: None,
5031 tab_size: None,
5032 formatter: None,
5033 format_on_save: false,
5034 on_save: vec![],
5035 word_characters: None,
5036 },
5037 );
5038
5039 languages.insert(
5040 "ini".to_string(),
5041 LanguageConfig {
5042 extensions: vec!["ini".to_string(), "cfg".to_string()],
5043 filenames: vec![],
5044 grammar: "INI".to_string(),
5045 comment_prefix: Some(";".to_string()),
5046 auto_indent: false,
5047 auto_close: None,
5048 auto_surround: None,
5049 textmate_grammar: None,
5050 show_whitespace_tabs: true,
5051 line_wrap: None,
5052 wrap_column: None,
5053 page_view: None,
5054 page_width: None,
5055 use_tabs: None,
5056 tab_size: None,
5057 formatter: None,
5058 format_on_save: false,
5059 on_save: vec![],
5060 word_characters: None,
5061 },
5062 );
5063
5064 languages.insert(
5065 "hyprlang".to_string(),
5066 LanguageConfig {
5067 extensions: vec!["hl".to_string()],
5068 filenames: vec!["hyprland.conf".to_string()],
5069 grammar: "Hyprlang".to_string(),
5070 comment_prefix: Some("#".to_string()),
5071 auto_indent: true,
5072 auto_close: None,
5073 auto_surround: None,
5074 textmate_grammar: None,
5075 show_whitespace_tabs: true,
5076 line_wrap: None,
5077 wrap_column: None,
5078 page_view: None,
5079 page_width: None,
5080 use_tabs: None,
5081 tab_size: None,
5082 formatter: None,
5083 format_on_save: false,
5084 on_save: vec![],
5085 word_characters: None,
5086 },
5087 );
5088
5089 languages
5090 }
5091
5092 #[cfg(feature = "runtime")]
5094 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
5095 let mut lsp = HashMap::new();
5096
5097 let ra_log_path = crate::services::log_dirs::lsp_log_path("rust-analyzer")
5100 .to_string_lossy()
5101 .to_string();
5102
5103 Self::populate_lsp_config(&mut lsp, ra_log_path);
5104 lsp
5105 }
5106
5107 #[cfg(not(feature = "runtime"))]
5109 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
5110 HashMap::new()
5112 }
5113
5114 #[cfg(feature = "runtime")]
5116 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
5117 let mut universal = HashMap::new();
5118
5119 universal.insert(
5132 "quicklsp".to_string(),
5133 LspLanguageConfig::Multi(vec![LspServerConfig {
5134 command: "quicklsp".to_string(),
5135 args: vec![],
5136 enabled: false,
5137 auto_start: false,
5138 process_limits: ProcessLimits::default(),
5139 initialization_options: None,
5140 env: Default::default(),
5141 language_id_overrides: Default::default(),
5142 name: Some("QuickLSP".to_string()),
5143 only_features: None,
5144 except_features: None,
5145 root_markers: vec![
5146 "Cargo.toml".to_string(),
5147 "package.json".to_string(),
5148 "go.mod".to_string(),
5149 "pyproject.toml".to_string(),
5150 "requirements.txt".to_string(),
5151 ".git".to_string(),
5152 ],
5153 }]),
5154 );
5155
5156 universal
5157 }
5158
5159 #[cfg(not(feature = "runtime"))]
5161 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
5162 HashMap::new()
5163 }
5164
5165 #[cfg(feature = "runtime")]
5166 fn populate_lsp_config(lsp: &mut HashMap<String, LspLanguageConfig>, ra_log_path: String) {
5167 lsp.insert(
5171 "rust".to_string(),
5172 LspLanguageConfig::Multi(vec![LspServerConfig {
5173 command: "rust-analyzer".to_string(),
5174 args: vec!["--log-file".to_string(), ra_log_path],
5175 enabled: true,
5176 auto_start: false,
5177 process_limits: ProcessLimits::unlimited(),
5178 initialization_options: None,
5179 env: Default::default(),
5180 language_id_overrides: Default::default(),
5181 name: None,
5182 only_features: None,
5183 except_features: None,
5184 root_markers: vec![
5185 "Cargo.toml".to_string(),
5186 "rust-project.json".to_string(),
5187 ".git".to_string(),
5188 ],
5189 }]),
5190 );
5191
5192 lsp.insert(
5194 "python".to_string(),
5195 LspLanguageConfig::Multi(vec![LspServerConfig {
5196 command: "pylsp".to_string(),
5197 args: vec![],
5198 enabled: true,
5199 auto_start: false,
5200 process_limits: ProcessLimits::default(),
5201 initialization_options: None,
5202 env: Default::default(),
5203 language_id_overrides: Default::default(),
5204 name: None,
5205 only_features: None,
5206 except_features: None,
5207 root_markers: vec![
5208 "pyproject.toml".to_string(),
5209 "setup.py".to_string(),
5210 "setup.cfg".to_string(),
5211 "pyrightconfig.json".to_string(),
5212 ".git".to_string(),
5213 ],
5214 }]),
5215 );
5216
5217 lsp.insert(
5220 "javascript".to_string(),
5221 LspLanguageConfig::Multi(vec![LspServerConfig {
5222 command: "typescript-language-server".to_string(),
5223 args: vec!["--stdio".to_string()],
5224 enabled: true,
5225 auto_start: false,
5226 process_limits: ProcessLimits::default(),
5227 initialization_options: None,
5228 env: Default::default(),
5229 language_id_overrides: HashMap::from([(
5230 "jsx".to_string(),
5231 "javascriptreact".to_string(),
5232 )]),
5233 name: None,
5234 only_features: None,
5235 except_features: None,
5236 root_markers: vec![
5237 "tsconfig.json".to_string(),
5238 "jsconfig.json".to_string(),
5239 "package.json".to_string(),
5240 ".git".to_string(),
5241 ],
5242 }]),
5243 );
5244 lsp.insert(
5245 "typescript".to_string(),
5246 LspLanguageConfig::Multi(vec![LspServerConfig {
5247 command: "typescript-language-server".to_string(),
5248 args: vec!["--stdio".to_string()],
5249 enabled: true,
5250 auto_start: false,
5251 process_limits: ProcessLimits::default(),
5252 initialization_options: None,
5253 env: Default::default(),
5254 language_id_overrides: HashMap::from([(
5255 "tsx".to_string(),
5256 "typescriptreact".to_string(),
5257 )]),
5258 name: None,
5259 only_features: None,
5260 except_features: None,
5261 root_markers: vec![
5262 "tsconfig.json".to_string(),
5263 "jsconfig.json".to_string(),
5264 "package.json".to_string(),
5265 ".git".to_string(),
5266 ],
5267 }]),
5268 );
5269
5270 lsp.insert(
5272 "html".to_string(),
5273 LspLanguageConfig::Multi(vec![LspServerConfig {
5274 command: "vscode-html-language-server".to_string(),
5275 args: vec!["--stdio".to_string()],
5276 enabled: true,
5277 auto_start: false,
5278 process_limits: ProcessLimits::default(),
5279 initialization_options: None,
5280 env: Default::default(),
5281 language_id_overrides: Default::default(),
5282 name: None,
5283 only_features: None,
5284 except_features: None,
5285 root_markers: Default::default(),
5286 }]),
5287 );
5288
5289 lsp.insert(
5291 "css".to_string(),
5292 LspLanguageConfig::Multi(vec![LspServerConfig {
5293 command: "vscode-css-language-server".to_string(),
5294 args: vec!["--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(
5310 "c".to_string(),
5311 LspLanguageConfig::Multi(vec![LspServerConfig {
5312 command: "clangd".to_string(),
5313 args: vec![],
5314 enabled: true,
5315 auto_start: false,
5316 process_limits: ProcessLimits::default(),
5317 initialization_options: None,
5318 env: Default::default(),
5319 language_id_overrides: Default::default(),
5320 name: None,
5321 only_features: None,
5322 except_features: None,
5323 root_markers: vec![
5324 "compile_commands.json".to_string(),
5325 "CMakeLists.txt".to_string(),
5326 "Makefile".to_string(),
5327 ".git".to_string(),
5328 ],
5329 }]),
5330 );
5331 lsp.insert(
5332 "cpp".to_string(),
5333 LspLanguageConfig::Multi(vec![LspServerConfig {
5334 command: "clangd".to_string(),
5335 args: vec![],
5336 enabled: true,
5337 auto_start: false,
5338 process_limits: ProcessLimits::default(),
5339 initialization_options: None,
5340 env: Default::default(),
5341 language_id_overrides: Default::default(),
5342 name: None,
5343 only_features: None,
5344 except_features: None,
5345 root_markers: vec![
5346 "compile_commands.json".to_string(),
5347 "CMakeLists.txt".to_string(),
5348 "Makefile".to_string(),
5349 ".git".to_string(),
5350 ],
5351 }]),
5352 );
5353
5354 lsp.insert(
5356 "go".to_string(),
5357 LspLanguageConfig::Multi(vec![LspServerConfig {
5358 command: "gopls".to_string(),
5359 args: vec![],
5360 enabled: true,
5361 auto_start: false,
5362 process_limits: ProcessLimits::default(),
5363 initialization_options: None,
5364 env: Default::default(),
5365 language_id_overrides: Default::default(),
5366 name: None,
5367 only_features: None,
5368 except_features: None,
5369 root_markers: vec![
5370 "go.mod".to_string(),
5371 "go.work".to_string(),
5372 ".git".to_string(),
5373 ],
5374 }]),
5375 );
5376
5377 lsp.insert(
5379 "json".to_string(),
5380 LspLanguageConfig::Multi(vec![LspServerConfig {
5381 command: "vscode-json-language-server".to_string(),
5382 args: vec!["--stdio".to_string()],
5383 enabled: true,
5384 auto_start: false,
5385 process_limits: ProcessLimits::default(),
5386 initialization_options: None,
5387 env: Default::default(),
5388 language_id_overrides: Default::default(),
5389 name: None,
5390 only_features: None,
5391 except_features: None,
5392 root_markers: Default::default(),
5393 }]),
5394 );
5395
5396 lsp.insert(
5400 "jsonc".to_string(),
5401 LspLanguageConfig::Multi(vec![LspServerConfig {
5402 command: "vscode-json-language-server".to_string(),
5403 args: vec!["--stdio".to_string()],
5404 enabled: true,
5405 auto_start: false,
5406 process_limits: ProcessLimits::default(),
5407 initialization_options: None,
5408 env: Default::default(),
5409 language_id_overrides: Default::default(),
5410 name: None,
5411 only_features: None,
5412 except_features: None,
5413 root_markers: Default::default(),
5414 }]),
5415 );
5416
5417 lsp.insert(
5419 "csharp".to_string(),
5420 LspLanguageConfig::Multi(vec![LspServerConfig {
5421 command: "csharp-ls".to_string(),
5422 args: vec![],
5423 enabled: true,
5424 auto_start: false,
5425 process_limits: ProcessLimits::default(),
5426 initialization_options: None,
5427 env: Default::default(),
5428 language_id_overrides: Default::default(),
5429 name: None,
5430 only_features: None,
5431 except_features: None,
5432 root_markers: vec![
5433 "*.csproj".to_string(),
5434 "*.sln".to_string(),
5435 ".git".to_string(),
5436 ],
5437 }]),
5438 );
5439
5440 lsp.insert(
5443 "odin".to_string(),
5444 LspLanguageConfig::Multi(vec![LspServerConfig {
5445 command: "ols".to_string(),
5446 args: vec![],
5447 enabled: true,
5448 auto_start: false,
5449 process_limits: ProcessLimits::default(),
5450 initialization_options: None,
5451 env: Default::default(),
5452 language_id_overrides: Default::default(),
5453 name: None,
5454 only_features: None,
5455 except_features: None,
5456 root_markers: Default::default(),
5457 }]),
5458 );
5459
5460 lsp.insert(
5463 "zig".to_string(),
5464 LspLanguageConfig::Multi(vec![LspServerConfig {
5465 command: "zls".to_string(),
5466 args: vec![],
5467 enabled: true,
5468 auto_start: false,
5469 process_limits: ProcessLimits::default(),
5470 initialization_options: None,
5471 env: Default::default(),
5472 language_id_overrides: Default::default(),
5473 name: None,
5474 only_features: None,
5475 except_features: None,
5476 root_markers: Default::default(),
5477 }]),
5478 );
5479
5480 lsp.insert(
5483 "java".to_string(),
5484 LspLanguageConfig::Multi(vec![LspServerConfig {
5485 command: "jdtls".to_string(),
5486 args: vec![],
5487 enabled: true,
5488 auto_start: false,
5489 process_limits: ProcessLimits::default(),
5490 initialization_options: None,
5491 env: Default::default(),
5492 language_id_overrides: Default::default(),
5493 name: None,
5494 only_features: None,
5495 except_features: None,
5496 root_markers: vec![
5497 "pom.xml".to_string(),
5498 "build.gradle".to_string(),
5499 "build.gradle.kts".to_string(),
5500 ".git".to_string(),
5501 ],
5502 }]),
5503 );
5504
5505 lsp.insert(
5508 "latex".to_string(),
5509 LspLanguageConfig::Multi(vec![LspServerConfig {
5510 command: "texlab".to_string(),
5511 args: vec![],
5512 enabled: true,
5513 auto_start: false,
5514 process_limits: ProcessLimits::default(),
5515 initialization_options: None,
5516 env: Default::default(),
5517 language_id_overrides: Default::default(),
5518 name: None,
5519 only_features: None,
5520 except_features: None,
5521 root_markers: Default::default(),
5522 }]),
5523 );
5524
5525 lsp.insert(
5528 "markdown".to_string(),
5529 LspLanguageConfig::Multi(vec![LspServerConfig {
5530 command: "marksman".to_string(),
5531 args: vec!["server".to_string()],
5532 enabled: true,
5533 auto_start: false,
5534 process_limits: ProcessLimits::default(),
5535 initialization_options: None,
5536 env: Default::default(),
5537 language_id_overrides: Default::default(),
5538 name: None,
5539 only_features: None,
5540 except_features: None,
5541 root_markers: Default::default(),
5542 }]),
5543 );
5544
5545 lsp.insert(
5548 "templ".to_string(),
5549 LspLanguageConfig::Multi(vec![LspServerConfig {
5550 command: "templ".to_string(),
5551 args: vec!["lsp".to_string()],
5552 enabled: true,
5553 auto_start: false,
5554 process_limits: ProcessLimits::default(),
5555 initialization_options: None,
5556 env: Default::default(),
5557 language_id_overrides: Default::default(),
5558 name: None,
5559 only_features: None,
5560 except_features: None,
5561 root_markers: Default::default(),
5562 }]),
5563 );
5564
5565 lsp.insert(
5568 "typst".to_string(),
5569 LspLanguageConfig::Multi(vec![LspServerConfig {
5570 command: "tinymist".to_string(),
5571 args: vec![],
5572 enabled: true,
5573 auto_start: false,
5574 process_limits: ProcessLimits::default(),
5575 initialization_options: None,
5576 env: Default::default(),
5577 language_id_overrides: Default::default(),
5578 name: None,
5579 only_features: None,
5580 except_features: None,
5581 root_markers: Default::default(),
5582 }]),
5583 );
5584
5585 lsp.insert(
5587 "bash".to_string(),
5588 LspLanguageConfig::Multi(vec![LspServerConfig {
5589 command: "bash-language-server".to_string(),
5590 args: vec!["start".to_string()],
5591 enabled: true,
5592 auto_start: false,
5593 process_limits: ProcessLimits::default(),
5594 initialization_options: None,
5595 env: Default::default(),
5596 language_id_overrides: Default::default(),
5597 name: None,
5598 only_features: None,
5599 except_features: None,
5600 root_markers: Default::default(),
5601 }]),
5602 );
5603
5604 lsp.insert(
5607 "lua".to_string(),
5608 LspLanguageConfig::Multi(vec![LspServerConfig {
5609 command: "lua-language-server".to_string(),
5610 args: vec![],
5611 enabled: true,
5612 auto_start: false,
5613 process_limits: ProcessLimits::default(),
5614 initialization_options: None,
5615 env: Default::default(),
5616 language_id_overrides: Default::default(),
5617 name: None,
5618 only_features: None,
5619 except_features: None,
5620 root_markers: vec![
5621 ".luarc.json".to_string(),
5622 ".luarc.jsonc".to_string(),
5623 ".luacheckrc".to_string(),
5624 ".stylua.toml".to_string(),
5625 ".git".to_string(),
5626 ],
5627 }]),
5628 );
5629
5630 lsp.insert(
5632 "ruby".to_string(),
5633 LspLanguageConfig::Multi(vec![LspServerConfig {
5634 command: "solargraph".to_string(),
5635 args: vec!["stdio".to_string()],
5636 enabled: true,
5637 auto_start: false,
5638 process_limits: ProcessLimits::default(),
5639 initialization_options: None,
5640 env: Default::default(),
5641 language_id_overrides: Default::default(),
5642 name: None,
5643 only_features: None,
5644 except_features: None,
5645 root_markers: vec![
5646 "Gemfile".to_string(),
5647 ".ruby-version".to_string(),
5648 ".git".to_string(),
5649 ],
5650 }]),
5651 );
5652
5653 lsp.insert(
5656 "php".to_string(),
5657 LspLanguageConfig::Multi(vec![LspServerConfig {
5658 command: "phpactor".to_string(),
5659 args: vec!["language-server".to_string()],
5660 enabled: true,
5661 auto_start: false,
5662 process_limits: ProcessLimits::default(),
5663 initialization_options: None,
5664 env: Default::default(),
5665 language_id_overrides: Default::default(),
5666 name: None,
5667 only_features: None,
5668 except_features: None,
5669 root_markers: vec!["composer.json".to_string(), ".git".to_string()],
5670 }]),
5671 );
5672
5673 lsp.insert(
5675 "yaml".to_string(),
5676 LspLanguageConfig::Multi(vec![LspServerConfig {
5677 command: "yaml-language-server".to_string(),
5678 args: vec!["--stdio".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 "toml".to_string(),
5696 LspLanguageConfig::Multi(vec![LspServerConfig {
5697 command: "taplo".to_string(),
5698 args: vec!["lsp".to_string(), "stdio".to_string()],
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 "dart".to_string(),
5716 LspLanguageConfig::Multi(vec![LspServerConfig {
5717 command: "dart".to_string(),
5718 args: vec!["language-server".to_string(), "--protocol=lsp".to_string()],
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: vec!["pubspec.yaml".to_string(), ".git".to_string()],
5729 }]),
5730 );
5731
5732 lsp.insert(
5735 "nushell".to_string(),
5736 LspLanguageConfig::Multi(vec![LspServerConfig {
5737 command: "nu".to_string(),
5738 args: vec!["--lsp".to_string()],
5739 enabled: true,
5740 auto_start: false,
5741 process_limits: ProcessLimits::default(),
5742 initialization_options: None,
5743 env: Default::default(),
5744 language_id_overrides: Default::default(),
5745 name: None,
5746 only_features: None,
5747 except_features: None,
5748 root_markers: Default::default(),
5749 }]),
5750 );
5751
5752 lsp.insert(
5755 "solidity".to_string(),
5756 LspLanguageConfig::Multi(vec![LspServerConfig {
5757 command: "nomicfoundation-solidity-language-server".to_string(),
5758 args: vec!["--stdio".to_string()],
5759 enabled: true,
5760 auto_start: false,
5761 process_limits: ProcessLimits::default(),
5762 initialization_options: None,
5763 env: Default::default(),
5764 language_id_overrides: Default::default(),
5765 name: None,
5766 only_features: None,
5767 except_features: None,
5768 root_markers: Default::default(),
5769 }]),
5770 );
5771
5772 lsp.insert(
5777 "terraform".to_string(),
5778 LspLanguageConfig::Multi(vec![LspServerConfig {
5779 command: "terraform-ls".to_string(),
5780 args: vec!["serve".to_string()],
5781 enabled: true,
5782 auto_start: false,
5783 process_limits: ProcessLimits::default(),
5784 initialization_options: None,
5785 env: Default::default(),
5786 language_id_overrides: Default::default(),
5787 name: None,
5788 only_features: None,
5789 except_features: None,
5790 root_markers: vec![
5791 "*.tf".to_string(),
5792 ".terraform".to_string(),
5793 ".git".to_string(),
5794 ],
5795 }]),
5796 );
5797
5798 lsp.insert(
5801 "cmake".to_string(),
5802 LspLanguageConfig::Multi(vec![LspServerConfig {
5803 command: "cmake-language-server".to_string(),
5804 args: vec![],
5805 enabled: true,
5806 auto_start: false,
5807 process_limits: ProcessLimits::default(),
5808 initialization_options: None,
5809 env: Default::default(),
5810 language_id_overrides: Default::default(),
5811 name: None,
5812 only_features: None,
5813 except_features: None,
5814 root_markers: vec!["CMakeLists.txt".to_string(), ".git".to_string()],
5815 }]),
5816 );
5817
5818 lsp.insert(
5821 "protobuf".to_string(),
5822 LspLanguageConfig::Multi(vec![LspServerConfig {
5823 command: "buf".to_string(),
5824 args: vec!["beta".to_string(), "lsp".to_string()],
5825 enabled: true,
5826 auto_start: false,
5827 process_limits: ProcessLimits::default(),
5828 initialization_options: None,
5829 env: Default::default(),
5830 language_id_overrides: Default::default(),
5831 name: None,
5832 only_features: None,
5833 except_features: None,
5834 root_markers: Default::default(),
5835 }]),
5836 );
5837
5838 lsp.insert(
5841 "graphql".to_string(),
5842 LspLanguageConfig::Multi(vec![LspServerConfig {
5843 command: "graphql-lsp".to_string(),
5844 args: vec!["server".to_string(), "-m".to_string(), "stream".to_string()],
5845 enabled: true,
5846 auto_start: false,
5847 process_limits: ProcessLimits::default(),
5848 initialization_options: None,
5849 env: Default::default(),
5850 language_id_overrides: Default::default(),
5851 name: None,
5852 only_features: None,
5853 except_features: None,
5854 root_markers: Default::default(),
5855 }]),
5856 );
5857
5858 lsp.insert(
5861 "sql".to_string(),
5862 LspLanguageConfig::Multi(vec![LspServerConfig {
5863 command: "sqls".to_string(),
5864 args: vec![],
5865 enabled: true,
5866 auto_start: false,
5867 process_limits: ProcessLimits::default(),
5868 initialization_options: None,
5869 env: Default::default(),
5870 language_id_overrides: Default::default(),
5871 name: None,
5872 only_features: None,
5873 except_features: None,
5874 root_markers: Default::default(),
5875 }]),
5876 );
5877
5878 lsp.insert(
5882 "vue".to_string(),
5883 LspLanguageConfig::Multi(vec![LspServerConfig {
5884 command: "vue-language-server".to_string(),
5885 args: vec!["--stdio".to_string()],
5886 enabled: true,
5887 auto_start: false,
5888 process_limits: ProcessLimits::default(),
5889 initialization_options: None,
5890 env: Default::default(),
5891 language_id_overrides: Default::default(),
5892 name: None,
5893 only_features: None,
5894 except_features: None,
5895 root_markers: Default::default(),
5896 }]),
5897 );
5898
5899 lsp.insert(
5901 "svelte".to_string(),
5902 LspLanguageConfig::Multi(vec![LspServerConfig {
5903 command: "svelteserver".to_string(),
5904 args: vec!["--stdio".to_string()],
5905 enabled: true,
5906 auto_start: false,
5907 process_limits: ProcessLimits::default(),
5908 initialization_options: None,
5909 env: Default::default(),
5910 language_id_overrides: Default::default(),
5911 name: None,
5912 only_features: None,
5913 except_features: None,
5914 root_markers: Default::default(),
5915 }]),
5916 );
5917
5918 lsp.insert(
5920 "astro".to_string(),
5921 LspLanguageConfig::Multi(vec![LspServerConfig {
5922 command: "astro-ls".to_string(),
5923 args: vec!["--stdio".to_string()],
5924 enabled: true,
5925 auto_start: false,
5926 process_limits: ProcessLimits::default(),
5927 initialization_options: None,
5928 env: Default::default(),
5929 language_id_overrides: Default::default(),
5930 name: None,
5931 only_features: None,
5932 except_features: None,
5933 root_markers: Default::default(),
5934 }]),
5935 );
5936
5937 lsp.insert(
5939 "tailwindcss".to_string(),
5940 LspLanguageConfig::Multi(vec![LspServerConfig {
5941 command: "tailwindcss-language-server".to_string(),
5942 args: vec!["--stdio".to_string()],
5943 enabled: true,
5944 auto_start: false,
5945 process_limits: ProcessLimits::default(),
5946 initialization_options: None,
5947 env: Default::default(),
5948 language_id_overrides: Default::default(),
5949 name: None,
5950 only_features: None,
5951 except_features: None,
5952 root_markers: Default::default(),
5953 }]),
5954 );
5955
5956 lsp.insert(
5961 "nix".to_string(),
5962 LspLanguageConfig::Multi(vec![LspServerConfig {
5963 command: "nil".to_string(),
5964 args: vec![],
5965 enabled: true,
5966 auto_start: false,
5967 process_limits: ProcessLimits::default(),
5968 initialization_options: None,
5969 env: Default::default(),
5970 language_id_overrides: Default::default(),
5971 name: None,
5972 only_features: None,
5973 except_features: None,
5974 root_markers: Default::default(),
5975 }]),
5976 );
5977
5978 lsp.insert(
5981 "kotlin".to_string(),
5982 LspLanguageConfig::Multi(vec![LspServerConfig {
5983 command: "kotlin-language-server".to_string(),
5984 args: vec![],
5985 enabled: true,
5986 auto_start: false,
5987 process_limits: ProcessLimits::default(),
5988 initialization_options: None,
5989 env: Default::default(),
5990 language_id_overrides: Default::default(),
5991 name: None,
5992 only_features: None,
5993 except_features: None,
5994 root_markers: Default::default(),
5995 }]),
5996 );
5997
5998 lsp.insert(
6000 "swift".to_string(),
6001 LspLanguageConfig::Multi(vec![LspServerConfig {
6002 command: "sourcekit-lsp".to_string(),
6003 args: vec![],
6004 enabled: true,
6005 auto_start: false,
6006 process_limits: ProcessLimits::default(),
6007 initialization_options: None,
6008 env: Default::default(),
6009 language_id_overrides: Default::default(),
6010 name: None,
6011 only_features: None,
6012 except_features: None,
6013 root_markers: Default::default(),
6014 }]),
6015 );
6016
6017 lsp.insert(
6020 "scala".to_string(),
6021 LspLanguageConfig::Multi(vec![LspServerConfig {
6022 command: "metals".to_string(),
6023 args: vec![],
6024 enabled: true,
6025 auto_start: false,
6026 process_limits: ProcessLimits::default(),
6027 initialization_options: None,
6028 env: Default::default(),
6029 language_id_overrides: Default::default(),
6030 name: None,
6031 only_features: None,
6032 except_features: None,
6033 root_markers: Default::default(),
6034 }]),
6035 );
6036
6037 lsp.insert(
6040 "elixir".to_string(),
6041 LspLanguageConfig::Multi(vec![LspServerConfig {
6042 command: "elixir-ls".to_string(),
6043 args: vec![],
6044 enabled: true,
6045 auto_start: false,
6046 process_limits: ProcessLimits::default(),
6047 initialization_options: None,
6048 env: Default::default(),
6049 language_id_overrides: Default::default(),
6050 name: None,
6051 only_features: None,
6052 except_features: None,
6053 root_markers: Default::default(),
6054 }]),
6055 );
6056
6057 lsp.insert(
6059 "erlang".to_string(),
6060 LspLanguageConfig::Multi(vec![LspServerConfig {
6061 command: "erlang_ls".to_string(),
6062 args: vec![],
6063 enabled: true,
6064 auto_start: false,
6065 process_limits: ProcessLimits::default(),
6066 initialization_options: None,
6067 env: Default::default(),
6068 language_id_overrides: Default::default(),
6069 name: None,
6070 only_features: None,
6071 except_features: None,
6072 root_markers: Default::default(),
6073 }]),
6074 );
6075
6076 lsp.insert(
6079 "haskell".to_string(),
6080 LspLanguageConfig::Multi(vec![LspServerConfig {
6081 command: "haskell-language-server-wrapper".to_string(),
6082 args: vec!["--lsp".to_string()],
6083 enabled: true,
6084 auto_start: false,
6085 process_limits: ProcessLimits::default(),
6086 initialization_options: None,
6087 env: Default::default(),
6088 language_id_overrides: Default::default(),
6089 name: None,
6090 only_features: None,
6091 except_features: None,
6092 root_markers: Default::default(),
6093 }]),
6094 );
6095
6096 lsp.insert(
6099 "ocaml".to_string(),
6100 LspLanguageConfig::Multi(vec![LspServerConfig {
6101 command: "ocamllsp".to_string(),
6102 args: vec![],
6103 enabled: true,
6104 auto_start: false,
6105 process_limits: ProcessLimits::default(),
6106 initialization_options: None,
6107 env: Default::default(),
6108 language_id_overrides: Default::default(),
6109 name: None,
6110 only_features: None,
6111 except_features: None,
6112 root_markers: Default::default(),
6113 }]),
6114 );
6115
6116 lsp.insert(
6119 "clojure".to_string(),
6120 LspLanguageConfig::Multi(vec![LspServerConfig {
6121 command: "clojure-lsp".to_string(),
6122 args: vec![],
6123 enabled: true,
6124 auto_start: false,
6125 process_limits: ProcessLimits::default(),
6126 initialization_options: None,
6127 env: Default::default(),
6128 language_id_overrides: Default::default(),
6129 name: None,
6130 only_features: None,
6131 except_features: None,
6132 root_markers: Default::default(),
6133 }]),
6134 );
6135
6136 lsp.insert(
6139 "r".to_string(),
6140 LspLanguageConfig::Multi(vec![LspServerConfig {
6141 command: "R".to_string(),
6142 args: vec![
6143 "--vanilla".to_string(),
6144 "-e".to_string(),
6145 "languageserver::run()".to_string(),
6146 ],
6147 enabled: true,
6148 auto_start: false,
6149 process_limits: ProcessLimits::default(),
6150 initialization_options: None,
6151 env: Default::default(),
6152 language_id_overrides: Default::default(),
6153 name: None,
6154 only_features: None,
6155 except_features: None,
6156 root_markers: Default::default(),
6157 }]),
6158 );
6159
6160 lsp.insert(
6163 "julia".to_string(),
6164 LspLanguageConfig::Multi(vec![LspServerConfig {
6165 command: "julia".to_string(),
6166 args: vec![
6167 "--startup-file=no".to_string(),
6168 "--history-file=no".to_string(),
6169 "-e".to_string(),
6170 "using LanguageServer; runserver()".to_string(),
6171 ],
6172 enabled: true,
6173 auto_start: false,
6174 process_limits: ProcessLimits::default(),
6175 initialization_options: None,
6176 env: Default::default(),
6177 language_id_overrides: Default::default(),
6178 name: None,
6179 only_features: None,
6180 except_features: None,
6181 root_markers: Default::default(),
6182 }]),
6183 );
6184
6185 lsp.insert(
6188 "perl".to_string(),
6189 LspLanguageConfig::Multi(vec![LspServerConfig {
6190 command: "perlnavigator".to_string(),
6191 args: vec!["--stdio".to_string()],
6192 enabled: true,
6193 auto_start: false,
6194 process_limits: ProcessLimits::default(),
6195 initialization_options: None,
6196 env: Default::default(),
6197 language_id_overrides: Default::default(),
6198 name: None,
6199 only_features: None,
6200 except_features: None,
6201 root_markers: Default::default(),
6202 }]),
6203 );
6204
6205 lsp.insert(
6208 "nim".to_string(),
6209 LspLanguageConfig::Multi(vec![LspServerConfig {
6210 command: "nimlangserver".to_string(),
6211 args: vec![],
6212 enabled: true,
6213 auto_start: false,
6214 process_limits: ProcessLimits::default(),
6215 initialization_options: None,
6216 env: Default::default(),
6217 language_id_overrides: Default::default(),
6218 name: None,
6219 only_features: None,
6220 except_features: None,
6221 root_markers: Default::default(),
6222 }]),
6223 );
6224
6225 lsp.insert(
6227 "gleam".to_string(),
6228 LspLanguageConfig::Multi(vec![LspServerConfig {
6229 command: "gleam".to_string(),
6230 args: vec!["lsp".to_string()],
6231 enabled: true,
6232 auto_start: false,
6233 process_limits: ProcessLimits::default(),
6234 initialization_options: None,
6235 env: Default::default(),
6236 language_id_overrides: Default::default(),
6237 name: None,
6238 only_features: None,
6239 except_features: None,
6240 root_markers: Default::default(),
6241 }]),
6242 );
6243
6244 lsp.insert(
6247 "fsharp".to_string(),
6248 LspLanguageConfig::Multi(vec![LspServerConfig {
6249 command: "fsautocomplete".to_string(),
6250 args: vec!["--adaptive-lsp-server-enabled".to_string()],
6251 enabled: true,
6252 auto_start: false,
6253 process_limits: ProcessLimits::default(),
6254 initialization_options: None,
6255 env: Default::default(),
6256 language_id_overrides: Default::default(),
6257 name: None,
6258 only_features: None,
6259 except_features: None,
6260 root_markers: Default::default(),
6261 }]),
6262 );
6263 }
6264 pub fn validate(&self) -> Result<(), ConfigError> {
6265 if self.editor.tab_size == 0 {
6267 return Err(ConfigError::ValidationError(
6268 "tab_size must be greater than 0".to_string(),
6269 ));
6270 }
6271
6272 if self.editor.scroll_offset > 100 {
6274 return Err(ConfigError::ValidationError(
6275 "scroll_offset must be <= 100".to_string(),
6276 ));
6277 }
6278
6279 for binding in &self.keybindings {
6281 if binding.key.is_empty() {
6282 return Err(ConfigError::ValidationError(
6283 "keybinding key cannot be empty".to_string(),
6284 ));
6285 }
6286 if binding.action.is_empty() {
6287 return Err(ConfigError::ValidationError(
6288 "keybinding action cannot be empty".to_string(),
6289 ));
6290 }
6291 }
6292
6293 Ok(())
6294 }
6295}
6296
6297#[derive(Debug)]
6299pub enum ConfigError {
6300 IoError(String),
6301 ParseError(String),
6302 SerializeError(String),
6303 ValidationError(String),
6304}
6305
6306impl std::fmt::Display for ConfigError {
6307 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6308 match self {
6309 Self::IoError(msg) => write!(f, "IO error: {msg}"),
6310 Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
6311 Self::SerializeError(msg) => write!(f, "Serialize error: {msg}"),
6312 Self::ValidationError(msg) => write!(f, "Validation error: {msg}"),
6313 }
6314 }
6315}
6316
6317impl std::error::Error for ConfigError {}
6318
6319#[cfg(test)]
6320mod tests {
6321 use super::*;
6322
6323 #[test]
6324 fn test_file_explorer_width_default_is_percent_30() {
6325 let cfg = FileExplorerConfig::default();
6326 assert_eq!(cfg.width, ExplorerWidth::Percent(30));
6327 }
6328
6329 #[test]
6332 fn test_width_accepts_legacy_float_fraction() {
6333 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": 0.3}"#).unwrap();
6334 assert_eq!(cfg.width, ExplorerWidth::Percent(30));
6335 }
6336
6337 #[test]
6338 fn test_width_accepts_bare_integer_as_percent() {
6339 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": 42}"#).unwrap();
6341 assert_eq!(cfg.width, ExplorerWidth::Percent(42));
6342 }
6343
6344 #[test]
6345 fn test_width_accepts_percent_string() {
6346 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "75%"}"#).unwrap();
6347 assert_eq!(cfg.width, ExplorerWidth::Percent(75));
6348 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "42 %"}"#).unwrap();
6350 assert_eq!(cfg.width, ExplorerWidth::Percent(42));
6351 }
6352
6353 #[test]
6354 fn test_width_accepts_columns_string() {
6355 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "24"}"#).unwrap();
6356 assert_eq!(cfg.width, ExplorerWidth::Columns(24));
6357 }
6358
6359 #[test]
6360 fn test_width_rejects_percent_over_100() {
6361 let err = serde_json::from_str::<FileExplorerConfig>(r#"{"width": "150%"}"#)
6362 .expect_err("percent > 100 should be rejected");
6363 assert!(err.to_string().contains("100"), "{err}");
6364 }
6365
6366 #[test]
6367 fn test_width_rejects_integer_over_100() {
6368 let err = serde_json::from_str::<FileExplorerConfig>(r#"{"width": 150}"#)
6371 .expect_err("bare integer > 100 should be rejected as percent");
6372 assert!(err.to_string().contains("percent") || err.to_string().contains("100"));
6373 }
6374
6375 #[test]
6376 fn test_width_rejects_garbage_string() {
6377 serde_json::from_str::<FileExplorerConfig>(r#"{"width": "big"}"#)
6378 .expect_err("non-numeric string should be rejected");
6379 }
6380
6381 #[test]
6384 fn test_width_serializes_percent_as_string_with_suffix() {
6385 let cfg = FileExplorerConfig {
6386 width: ExplorerWidth::Percent(30),
6387 ..Default::default()
6388 };
6389 let json = serde_json::to_value(&cfg).unwrap();
6390 assert_eq!(json["width"], serde_json::json!("30%"));
6391 }
6392
6393 #[test]
6394 fn test_width_serializes_columns_as_string_without_suffix() {
6395 let cfg = FileExplorerConfig {
6396 width: ExplorerWidth::Columns(24),
6397 ..Default::default()
6398 };
6399 let json = serde_json::to_value(&cfg).unwrap();
6400 assert_eq!(json["width"], serde_json::json!("24"));
6401 }
6402
6403 #[test]
6404 fn test_width_round_trip_both_variants() {
6405 for value in [ExplorerWidth::Percent(17), ExplorerWidth::Columns(42)] {
6406 let cfg = FileExplorerConfig {
6407 width: value,
6408 ..Default::default()
6409 };
6410 let json = serde_json::to_string(&cfg).unwrap();
6411 let restored: FileExplorerConfig = serde_json::from_str(&json).unwrap();
6412 assert_eq!(restored.width, value, "round trip failed for {:?}", value);
6413 }
6414 }
6415
6416 #[test]
6419 fn test_to_cols_percent() {
6420 assert_eq!(ExplorerWidth::Percent(30).to_cols(100), 30);
6421 assert_eq!(ExplorerWidth::Percent(25).to_cols(120), 30);
6422 assert_eq!(ExplorerWidth::Percent(30).to_cols(0), 0);
6424 assert_eq!(ExplorerWidth::Percent(100).to_cols(200), 200);
6425 }
6426
6427 #[test]
6428 fn test_to_cols_columns_clamps_to_terminal() {
6429 assert_eq!(ExplorerWidth::Columns(24).to_cols(100), 24);
6430 assert_eq!(ExplorerWidth::Columns(999).to_cols(80), 80);
6431 }
6432
6433 #[test]
6436 fn test_to_cols_enforces_min_width() {
6437 assert_eq!(
6439 ExplorerWidth::Columns(0).to_cols(100),
6440 ExplorerWidth::MIN_COLS
6441 );
6442 assert_eq!(
6443 ExplorerWidth::Columns(1).to_cols(100),
6444 ExplorerWidth::MIN_COLS
6445 );
6446 assert_eq!(
6447 ExplorerWidth::Columns(4).to_cols(100),
6448 ExplorerWidth::MIN_COLS
6449 );
6450 assert_eq!(
6451 ExplorerWidth::Percent(0).to_cols(100),
6452 ExplorerWidth::MIN_COLS
6453 );
6454 assert_eq!(
6456 ExplorerWidth::Percent(3).to_cols(100),
6457 ExplorerWidth::MIN_COLS
6458 );
6459 assert_eq!(
6461 ExplorerWidth::Columns(ExplorerWidth::MIN_COLS + 1).to_cols(100),
6462 ExplorerWidth::MIN_COLS + 1
6463 );
6464 }
6465
6466 #[test]
6469 fn test_to_cols_min_floor_yields_to_narrow_terminal() {
6470 assert_eq!(ExplorerWidth::Columns(10).to_cols(3), 3);
6471 assert_eq!(ExplorerWidth::Percent(100).to_cols(2), 2);
6472 assert_eq!(ExplorerWidth::Columns(1).to_cols(0), 0);
6473 }
6474
6475 #[test]
6478 fn test_load_from_file_accepts_legacy_float_fraction_width() {
6479 let dir = tempfile::tempdir().unwrap();
6480 let path = dir.path().join("config.json");
6481 std::fs::write(&path, r#"{"file_explorer":{"width":0.25}}"#).unwrap();
6482 let cfg = Config::load_from_file(&path).expect("legacy float fraction must still load");
6483 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Percent(25));
6484 }
6485
6486 #[test]
6487 fn test_load_from_file_accepts_columns_string_width() {
6488 let dir = tempfile::tempdir().unwrap();
6489 let path = dir.path().join("config.json");
6490 std::fs::write(&path, r#"{"file_explorer":{"width":"42"}}"#).unwrap();
6491 let cfg = Config::load_from_file(&path).unwrap();
6492 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Columns(42));
6493 }
6494
6495 #[test]
6496 fn test_load_from_file_accepts_percent_string_width() {
6497 let dir = tempfile::tempdir().unwrap();
6498 let path = dir.path().join("config.json");
6499 std::fs::write(&path, r#"{"file_explorer":{"width":"55%"}}"#).unwrap();
6500 let cfg = Config::load_from_file(&path).unwrap();
6501 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Percent(55));
6502 }
6503
6504 #[test]
6505 fn test_default_config() {
6506 let config = Config::default();
6507 assert_eq!(config.editor.tab_size, 4);
6508 assert!(config.editor.line_numbers);
6509 assert!(config.editor.syntax_highlighting);
6510 assert!(config.keybindings.is_empty());
6513 let resolved = config.resolve_keymap(&config.active_keybinding_map);
6515 assert!(!resolved.is_empty());
6516 }
6517
6518 #[test]
6519 fn test_all_builtin_keymaps_loadable() {
6520 for name in KeybindingMapName::BUILTIN_OPTIONS {
6521 let keymap = Config::load_builtin_keymap(name);
6522 assert!(keymap.is_some(), "Failed to load builtin keymap '{}'", name);
6523 }
6524 }
6525
6526 #[test]
6527 fn test_config_validation() {
6528 let mut config = Config::default();
6529 assert!(config.validate().is_ok());
6530
6531 config.editor.tab_size = 0;
6532 assert!(config.validate().is_err());
6533 }
6534
6535 #[test]
6536 fn test_macos_keymap_inherits_enter_bindings() {
6537 let config = Config::default();
6538 let bindings = config.resolve_keymap("macos");
6539
6540 let enter_bindings: Vec<_> = bindings.iter().filter(|b| b.key == "Enter").collect();
6541 assert!(
6542 !enter_bindings.is_empty(),
6543 "macos keymap should inherit Enter bindings from default, got {} Enter bindings",
6544 enter_bindings.len()
6545 );
6546 let has_insert_newline = enter_bindings.iter().any(|b| b.action == "insert_newline");
6548 assert!(
6549 has_insert_newline,
6550 "macos keymap should have insert_newline action for Enter key"
6551 );
6552 }
6553
6554 #[test]
6555 fn test_config_serialize_deserialize() {
6556 let config = Config::default();
6558
6559 let json = serde_json::to_string_pretty(&config).unwrap();
6561
6562 let loaded: Config = serde_json::from_str(&json).unwrap();
6564
6565 assert_eq!(config.editor.tab_size, loaded.editor.tab_size);
6566 assert_eq!(config.theme, loaded.theme);
6567 }
6568
6569 #[test]
6570 fn test_config_with_custom_keybinding() {
6571 let json = r#"{
6572 "editor": {
6573 "tab_size": 2
6574 },
6575 "keybindings": [
6576 {
6577 "key": "x",
6578 "modifiers": ["ctrl", "shift"],
6579 "action": "custom_action",
6580 "args": {},
6581 "when": null
6582 }
6583 ]
6584 }"#;
6585
6586 let config: Config = serde_json::from_str(json).unwrap();
6587 assert_eq!(config.editor.tab_size, 2);
6588 assert_eq!(config.keybindings.len(), 1);
6589 assert_eq!(config.keybindings[0].key, "x");
6590 assert_eq!(config.keybindings[0].modifiers.len(), 2);
6591 }
6592
6593 #[test]
6594 fn test_sparse_config_merges_with_defaults() {
6595 let temp_dir = tempfile::tempdir().unwrap();
6597 let config_path = temp_dir.path().join("config.json");
6598
6599 let sparse_config = r#"{
6601 "lsp": {
6602 "rust": {
6603 "command": "custom-rust-analyzer",
6604 "args": ["--custom-arg"]
6605 }
6606 }
6607 }"#;
6608 std::fs::write(&config_path, sparse_config).unwrap();
6609
6610 let loaded = Config::load_from_file(&config_path).unwrap();
6612
6613 assert!(loaded.lsp.contains_key("rust"));
6615 assert_eq!(
6616 loaded.lsp["rust"].as_slice()[0].command,
6617 "custom-rust-analyzer".to_string()
6618 );
6619
6620 assert!(
6622 loaded.lsp.contains_key("python"),
6623 "python LSP should be merged from defaults"
6624 );
6625 assert!(
6626 loaded.lsp.contains_key("typescript"),
6627 "typescript LSP should be merged from defaults"
6628 );
6629 assert!(
6630 loaded.lsp.contains_key("javascript"),
6631 "javascript LSP should be merged from defaults"
6632 );
6633
6634 assert!(loaded.languages.contains_key("rust"));
6636 assert!(loaded.languages.contains_key("python"));
6637 assert!(loaded.languages.contains_key("typescript"));
6638 }
6639
6640 #[test]
6641 fn test_empty_config_gets_all_defaults() {
6642 let temp_dir = tempfile::tempdir().unwrap();
6643 let config_path = temp_dir.path().join("config.json");
6644
6645 std::fs::write(&config_path, "{}").unwrap();
6647
6648 let loaded = Config::load_from_file(&config_path).unwrap();
6649 let defaults = Config::default();
6650
6651 assert_eq!(loaded.lsp.len(), defaults.lsp.len());
6653
6654 assert_eq!(loaded.languages.len(), defaults.languages.len());
6656 }
6657
6658 #[test]
6659 fn test_dynamic_submenu_expansion() {
6660 let temp_dir = tempfile::tempdir().unwrap();
6662 let themes_dir = temp_dir.path().to_path_buf();
6663
6664 let dynamic = MenuItem::DynamicSubmenu {
6665 label: "Test".to_string(),
6666 source: "copy_with_theme".to_string(),
6667 };
6668
6669 let expanded = dynamic.expand_dynamic(&themes_dir);
6670
6671 match expanded {
6673 MenuItem::Submenu { label, items } => {
6674 assert_eq!(label, "Test");
6675 let loader = crate::view::theme::ThemeLoader::embedded_only();
6677 let registry = loader.load_all(&[]);
6678 assert_eq!(items.len(), registry.len());
6679
6680 for (item, theme_info) in items.iter().zip(registry.list().iter()) {
6682 match item {
6683 MenuItem::Action {
6684 label,
6685 action,
6686 args,
6687 ..
6688 } => {
6689 assert_eq!(label, &theme_info.name);
6690 assert_eq!(action, "copy_with_theme");
6691 assert_eq!(
6692 args.get("theme").and_then(|v| v.as_str()),
6693 Some(theme_info.name.as_str())
6694 );
6695 }
6696 _ => panic!("Expected Action item"),
6697 }
6698 }
6699 }
6700 _ => panic!("Expected Submenu after expansion"),
6701 }
6702 }
6703
6704 #[test]
6705 fn test_non_dynamic_item_unchanged() {
6706 let temp_dir = tempfile::tempdir().unwrap();
6708 let themes_dir = temp_dir.path();
6709
6710 let action = MenuItem::Action {
6711 label: "Test".to_string(),
6712 action: "test".to_string(),
6713 args: HashMap::new(),
6714 when: None,
6715 checkbox: None,
6716 };
6717
6718 let expanded = action.expand_dynamic(themes_dir);
6719 match expanded {
6720 MenuItem::Action { label, action, .. } => {
6721 assert_eq!(label, "Test");
6722 assert_eq!(action, "test");
6723 }
6724 _ => panic!("Action should remain Action after expand_dynamic"),
6725 }
6726 }
6727
6728 #[test]
6729 fn test_buffer_config_uses_global_defaults() {
6730 let config = Config::default();
6731 let buffer_config = BufferConfig::resolve(&config, None);
6732
6733 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
6734 assert_eq!(buffer_config.auto_indent, config.editor.auto_indent);
6735 assert!(!buffer_config.use_tabs); assert!(buffer_config.whitespace.any_tabs()); assert!(buffer_config.formatter.is_none());
6738 assert!(!buffer_config.format_on_save);
6739 }
6740
6741 #[test]
6742 fn test_buffer_config_applies_language_overrides() {
6743 let mut config = Config::default();
6744
6745 config.languages.insert(
6747 "go".to_string(),
6748 LanguageConfig {
6749 extensions: vec!["go".to_string()],
6750 filenames: vec![],
6751 grammar: "go".to_string(),
6752 comment_prefix: Some("//".to_string()),
6753 auto_indent: true,
6754 auto_close: None,
6755 auto_surround: None,
6756 textmate_grammar: None,
6757 show_whitespace_tabs: false, line_wrap: None,
6759 wrap_column: None,
6760 page_view: None,
6761 page_width: None,
6762 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
6765 command: "gofmt".to_string(),
6766 args: vec![],
6767 stdin: true,
6768 timeout_ms: 10000,
6769 }),
6770 format_on_save: true,
6771 on_save: vec![],
6772 word_characters: None,
6773 },
6774 );
6775
6776 let buffer_config = BufferConfig::resolve(&config, Some("go"));
6777
6778 assert_eq!(buffer_config.tab_size, 8);
6779 assert!(buffer_config.use_tabs);
6780 assert!(!buffer_config.whitespace.any_tabs()); assert!(buffer_config.format_on_save);
6782 assert!(buffer_config.formatter.is_some());
6783 assert_eq!(buffer_config.formatter.as_ref().unwrap().command, "gofmt");
6784 }
6785
6786 #[test]
6787 fn test_buffer_config_unknown_language_uses_global() {
6788 let config = Config::default();
6789 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
6790
6791 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
6793 assert!(!buffer_config.use_tabs);
6794 }
6795
6796 #[test]
6797 fn test_buffer_config_per_language_line_wrap() {
6798 let mut config = Config::default();
6799 config.editor.line_wrap = false;
6800
6801 config.languages.insert(
6803 "markdown".to_string(),
6804 LanguageConfig {
6805 extensions: vec!["md".to_string()],
6806 line_wrap: Some(true),
6807 ..Default::default()
6808 },
6809 );
6810
6811 let md_config = BufferConfig::resolve(&config, Some("markdown"));
6813 assert!(md_config.line_wrap, "Markdown should have line_wrap=true");
6814
6815 let other_config = BufferConfig::resolve(&config, Some("rust"));
6817 assert!(
6818 !other_config.line_wrap,
6819 "Non-configured languages should use global line_wrap=false"
6820 );
6821
6822 let no_lang_config = BufferConfig::resolve(&config, None);
6824 assert!(
6825 !no_lang_config.line_wrap,
6826 "No language should use global line_wrap=false"
6827 );
6828 }
6829
6830 #[test]
6831 fn test_buffer_config_per_language_wrap_column() {
6832 let mut config = Config::default();
6833 config.editor.wrap_column = Some(120);
6834
6835 config.languages.insert(
6837 "markdown".to_string(),
6838 LanguageConfig {
6839 extensions: vec!["md".to_string()],
6840 wrap_column: Some(80),
6841 ..Default::default()
6842 },
6843 );
6844
6845 let md_config = BufferConfig::resolve(&config, Some("markdown"));
6847 assert_eq!(md_config.wrap_column, Some(80));
6848
6849 let other_config = BufferConfig::resolve(&config, Some("rust"));
6851 assert_eq!(other_config.wrap_column, Some(120));
6852
6853 let no_lang_config = BufferConfig::resolve(&config, None);
6855 assert_eq!(no_lang_config.wrap_column, Some(120));
6856 }
6857
6858 #[test]
6859 fn test_buffer_config_indent_string() {
6860 let config = Config::default();
6861
6862 let spaces_config = BufferConfig::resolve(&config, None);
6864 assert_eq!(spaces_config.indent_string(), " "); let mut config_with_tabs = Config::default();
6868 config_with_tabs.languages.insert(
6869 "makefile".to_string(),
6870 LanguageConfig {
6871 use_tabs: Some(true),
6872 tab_size: Some(8),
6873 ..Default::default()
6874 },
6875 );
6876 let tabs_config = BufferConfig::resolve(&config_with_tabs, Some("makefile"));
6877 assert_eq!(tabs_config.indent_string(), "\t");
6878 }
6879
6880 #[test]
6881 fn test_buffer_config_global_use_tabs_inherited() {
6882 let mut config = Config::default();
6885 config.editor.use_tabs = true;
6886
6887 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
6889 assert!(buffer_config.use_tabs);
6890
6891 let buffer_config = BufferConfig::resolve(&config, None);
6893 assert!(buffer_config.use_tabs);
6894
6895 config.languages.insert(
6897 "python".to_string(),
6898 LanguageConfig {
6899 use_tabs: Some(false),
6900 ..Default::default()
6901 },
6902 );
6903 let buffer_config = BufferConfig::resolve(&config, Some("python"));
6904 assert!(!buffer_config.use_tabs);
6905
6906 config.languages.insert(
6908 "rust".to_string(),
6909 LanguageConfig {
6910 use_tabs: None,
6911 ..Default::default()
6912 },
6913 );
6914 let buffer_config = BufferConfig::resolve(&config, Some("rust"));
6915 assert!(buffer_config.use_tabs);
6916 }
6917
6918 #[test]
6924 #[cfg(feature = "runtime")]
6925 fn test_lsp_languages_have_language_config() {
6926 let config = Config::default();
6927 let exceptions = ["tailwindcss"];
6928 for lsp_key in config.lsp.keys() {
6929 if exceptions.contains(&lsp_key.as_str()) {
6930 continue;
6931 }
6932 assert!(
6933 config.languages.contains_key(lsp_key),
6934 "LSP config key '{}' has no matching entry in default_languages(). \
6935 Add a LanguageConfig with the correct file extensions so detect_language() \
6936 can map files to this language.",
6937 lsp_key
6938 );
6939 }
6940 }
6941
6942 #[test]
6943 #[cfg(feature = "runtime")]
6944 fn test_default_config_has_quicklsp_in_universal_lsp() {
6945 let config = Config::default();
6946 assert!(
6947 config.universal_lsp.contains_key("quicklsp"),
6948 "Default config should contain quicklsp in universal_lsp"
6949 );
6950 let quicklsp = &config.universal_lsp["quicklsp"];
6951 let server = &quicklsp.as_slice()[0];
6952 assert_eq!(server.command, "quicklsp");
6953 assert!(!server.enabled, "quicklsp should be disabled by default");
6954 assert_eq!(server.name.as_deref(), Some("QuickLSP"));
6955 assert!(
6959 server.only_features.is_none(),
6960 "quicklsp must not default to a feature whitelist"
6961 );
6962 assert!(server.except_features.is_none());
6963 }
6964
6965 #[test]
6966 fn test_empty_config_preserves_universal_lsp_defaults() {
6967 let temp_dir = tempfile::tempdir().unwrap();
6968 let config_path = temp_dir.path().join("config.json");
6969
6970 std::fs::write(&config_path, "{}").unwrap();
6972
6973 let loaded = Config::load_from_file(&config_path).unwrap();
6974 let defaults = Config::default();
6975
6976 assert_eq!(
6978 loaded.universal_lsp.len(),
6979 defaults.universal_lsp.len(),
6980 "Empty config should preserve all default universal_lsp entries"
6981 );
6982 }
6983
6984 #[test]
6985 fn test_universal_lsp_config_merges_with_defaults() {
6986 let temp_dir = tempfile::tempdir().unwrap();
6987 let config_path = temp_dir.path().join("config.json");
6988
6989 let config_json = r#"{
6991 "universal_lsp": {
6992 "quicklsp": {
6993 "enabled": true
6994 }
6995 }
6996 }"#;
6997 std::fs::write(&config_path, config_json).unwrap();
6998
6999 let loaded = Config::load_from_file(&config_path).unwrap();
7000
7001 assert!(loaded.universal_lsp.contains_key("quicklsp"));
7003 let server = &loaded.universal_lsp["quicklsp"].as_slice()[0];
7004 assert!(server.enabled, "User override should enable quicklsp");
7005 assert_eq!(
7007 server.command, "quicklsp",
7008 "Default command should be merged when not specified by user"
7009 );
7010 }
7011
7012 #[test]
7013 fn test_universal_lsp_custom_server_added() {
7014 let temp_dir = tempfile::tempdir().unwrap();
7015 let config_path = temp_dir.path().join("config.json");
7016
7017 let config_json = r#"{
7019 "universal_lsp": {
7020 "my-custom-server": {
7021 "command": "my-server",
7022 "enabled": true,
7023 "auto_start": true
7024 }
7025 }
7026 }"#;
7027 std::fs::write(&config_path, config_json).unwrap();
7028
7029 let loaded = Config::load_from_file(&config_path).unwrap();
7030
7031 assert!(
7033 loaded.universal_lsp.contains_key("my-custom-server"),
7034 "Custom universal server should be loaded"
7035 );
7036 let server = &loaded.universal_lsp["my-custom-server"].as_slice()[0];
7037 assert_eq!(server.command, "my-server");
7038 assert!(server.enabled);
7039 assert!(server.auto_start);
7040
7041 assert!(
7043 loaded.universal_lsp.contains_key("quicklsp"),
7044 "Default quicklsp should be merged from defaults"
7045 );
7046 }
7047}