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![
683 StatusBarElement::RemoteIndicator,
684 StatusBarElement::Filename,
685 StatusBarElement::Cursor,
686 StatusBarElement::Diagnostics,
687 StatusBarElement::CursorCount,
688 StatusBarElement::Messages,
689 ]
690}
691
692fn default_status_bar_right() -> Vec<StatusBarElement> {
693 vec![
694 StatusBarElement::LineEnding,
695 StatusBarElement::Encoding,
696 StatusBarElement::Language,
697 StatusBarElement::Lsp,
698 StatusBarElement::Warnings,
699 StatusBarElement::Update,
700 StatusBarElement::Palette,
701 ]
702}
703
704#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
719pub struct StatusBarConfig {
720 #[serde(default = "default_status_bar_left")]
723 #[schemars(extend("x-section" = "Status Bar", "x-dual-list-sibling" = "/editor/status_bar/right"))]
724 pub left: Vec<StatusBarElement>,
725
726 #[serde(default = "default_status_bar_right")]
729 #[schemars(extend("x-section" = "Status Bar", "x-dual-list-sibling" = "/editor/status_bar/left"))]
730 pub right: Vec<StatusBarElement>,
731}
732
733impl Default for StatusBarConfig {
734 fn default() -> Self {
735 Self {
736 left: default_status_bar_left(),
737 right: default_status_bar_right(),
738 }
739 }
740}
741
742#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
744pub struct EditorConfig {
745 #[serde(default = "default_true")]
752 #[schemars(extend("x-section" = "Display"))]
753 pub animations: bool,
754
755 #[serde(default = "default_true")]
757 #[schemars(extend("x-section" = "Display"))]
758 pub line_numbers: bool,
759
760 #[serde(default = "default_false")]
762 #[schemars(extend("x-section" = "Display"))]
763 pub relative_line_numbers: bool,
764
765 #[serde(default = "default_true")]
767 #[schemars(extend("x-section" = "Display"))]
768 pub highlight_current_line: bool,
769
770 #[serde(default = "default_false")]
772 #[schemars(extend("x-section" = "Display"))]
773 pub highlight_current_column: bool,
774
775 #[serde(default = "default_true")]
777 #[schemars(extend("x-section" = "Display"))]
778 pub line_wrap: bool,
779
780 #[serde(default = "default_true")]
782 #[schemars(extend("x-section" = "Display"))]
783 pub wrap_indent: bool,
784
785 #[serde(default)]
790 #[schemars(extend("x-section" = "Display"))]
791 pub wrap_column: Option<usize>,
792
793 #[serde(default = "default_page_width")]
797 #[schemars(extend("x-section" = "Display"))]
798 pub page_width: Option<usize>,
799
800 #[serde(default = "default_true")]
802 #[schemars(extend("x-section" = "Display"))]
803 pub syntax_highlighting: bool,
804
805 #[serde(default = "default_true")]
810 #[schemars(extend("x-section" = "Display"))]
811 pub show_menu_bar: bool,
812
813 #[serde(default = "default_true")]
818 #[schemars(extend("x-section" = "Display"))]
819 pub menu_bar_mnemonics: bool,
820
821 #[serde(default = "default_true")]
826 #[schemars(extend("x-section" = "Display"))]
827 pub show_tab_bar: bool,
828
829 #[serde(default = "default_true")]
834 #[schemars(extend("x-section" = "Display"))]
835 pub show_status_bar: bool,
836
837 #[serde(default)]
840 #[schemars(extend("x-section" = "Status Bar"))]
841 pub status_bar: StatusBarConfig,
842
843 #[serde(default = "default_false")]
850 #[schemars(extend("x-section" = "Display"))]
851 pub show_prompt_line: bool,
852
853 #[serde(default = "default_true")]
857 #[schemars(extend("x-section" = "Display"))]
858 pub show_vertical_scrollbar: bool,
859
860 #[serde(default = "default_false")]
865 #[schemars(extend("x-section" = "Display"))]
866 pub show_horizontal_scrollbar: bool,
867
868 #[serde(default = "default_true")]
872 #[schemars(extend("x-section" = "Display"))]
873 pub show_tilde: bool,
874
875 #[serde(default = "default_false")]
880 #[schemars(extend("x-section" = "Display"))]
881 pub use_terminal_bg: bool,
882
883 #[serde(default = "default_true")]
889 #[schemars(extend("x-section" = "Display"))]
890 pub set_window_title: bool,
891
892 #[serde(default)]
896 #[schemars(extend("x-section" = "Display"))]
897 pub cursor_style: CursorStyle,
898
899 #[serde(default)]
904 #[schemars(extend("x-section" = "Display"))]
905 pub rulers: Vec<usize>,
906
907 #[serde(default = "default_true")]
913 #[schemars(extend("x-section" = "Whitespace"))]
914 pub whitespace_show: bool,
915
916 #[serde(default = "default_false")]
920 #[schemars(extend("x-section" = "Whitespace"))]
921 pub whitespace_spaces_leading: bool,
922
923 #[serde(default = "default_false")]
927 #[schemars(extend("x-section" = "Whitespace"))]
928 pub whitespace_spaces_inner: bool,
929
930 #[serde(default = "default_false")]
934 #[schemars(extend("x-section" = "Whitespace"))]
935 pub whitespace_spaces_trailing: bool,
936
937 #[serde(default = "default_true")]
941 #[schemars(extend("x-section" = "Whitespace"))]
942 pub whitespace_tabs_leading: bool,
943
944 #[serde(default = "default_true")]
948 #[schemars(extend("x-section" = "Whitespace"))]
949 pub whitespace_tabs_inner: bool,
950
951 #[serde(default = "default_true")]
955 #[schemars(extend("x-section" = "Whitespace"))]
956 pub whitespace_tabs_trailing: bool,
957
958 #[serde(default = "default_false")]
964 #[schemars(extend("x-section" = "Editing"))]
965 pub use_tabs: bool,
966
967 #[serde(default = "default_tab_size")]
969 #[schemars(extend("x-section" = "Editing"))]
970 pub tab_size: usize,
971
972 #[serde(default = "default_true")]
974 #[schemars(extend("x-section" = "Editing"))]
975 pub auto_indent: bool,
976
977 #[serde(default = "default_true")]
984 #[schemars(extend("x-section" = "Editing"))]
985 pub auto_close: bool,
986
987 #[serde(default = "default_true")]
992 #[schemars(extend("x-section" = "Editing"))]
993 pub auto_surround: bool,
994
995 #[serde(default = "default_scroll_offset")]
997 #[schemars(extend("x-section" = "Editing"))]
998 pub scroll_offset: usize,
999
1000 #[serde(default)]
1005 #[schemars(extend("x-section" = "Editing"))]
1006 pub default_line_ending: LineEndingOption,
1007
1008 #[serde(default = "default_false")]
1011 #[schemars(extend("x-section" = "Editing"))]
1012 pub trim_trailing_whitespace_on_save: bool,
1013
1014 #[serde(default = "default_false")]
1017 #[schemars(extend("x-section" = "Editing"))]
1018 pub ensure_final_newline_on_save: bool,
1019
1020 #[serde(default = "default_true")]
1024 #[schemars(extend("x-section" = "Bracket Matching"))]
1025 pub highlight_matching_brackets: bool,
1026
1027 #[serde(default = "default_true")]
1031 #[schemars(extend("x-section" = "Bracket Matching"))]
1032 pub rainbow_brackets: bool,
1033
1034 #[serde(default = "default_false")]
1041 #[schemars(extend("x-section" = "Completion"))]
1042 pub completion_popup_auto_show: bool,
1043
1044 #[serde(default = "default_true")]
1050 #[schemars(extend("x-section" = "Completion"))]
1051 pub quick_suggestions: bool,
1052
1053 #[serde(default = "default_quick_suggestions_delay")]
1059 #[schemars(extend("x-section" = "Completion"))]
1060 pub quick_suggestions_delay_ms: u64,
1061
1062 #[serde(default = "default_true")]
1066 #[schemars(extend("x-section" = "Completion"))]
1067 pub suggest_on_trigger_characters: bool,
1068
1069 #[serde(default = "default_true")]
1072 #[schemars(extend("x-section" = "LSP"))]
1073 pub enable_inlay_hints: bool,
1074
1075 #[serde(default = "default_false")]
1079 #[schemars(extend("x-section" = "LSP"))]
1080 pub enable_semantic_tokens_full: bool,
1081
1082 #[serde(default = "default_false")]
1087 #[schemars(extend("x-section" = "Diagnostics"))]
1088 pub diagnostics_inline_text: bool,
1089
1090 #[serde(default = "default_mouse_hover_enabled")]
1101 #[schemars(extend("x-section" = "Mouse"))]
1102 pub mouse_hover_enabled: bool,
1103
1104 #[serde(default = "default_mouse_hover_delay")]
1108 #[schemars(extend("x-section" = "Mouse"))]
1109 pub mouse_hover_delay_ms: u64,
1110
1111 #[serde(default = "default_double_click_time")]
1115 #[schemars(extend("x-section" = "Mouse"))]
1116 pub double_click_time_ms: u64,
1117
1118 #[serde(default = "default_false")]
1123 #[schemars(extend("x-section" = "Recovery"))]
1124 pub auto_save_enabled: bool,
1125
1126 #[serde(default = "default_auto_save_interval")]
1131 #[schemars(extend("x-section" = "Recovery"))]
1132 pub auto_save_interval_secs: u32,
1133
1134 #[serde(default = "default_true", alias = "persist_unnamed_buffers")]
1141 #[schemars(extend("x-section" = "Recovery"))]
1142 pub hot_exit: bool,
1143
1144 #[serde(default = "default_true")]
1155 #[schemars(extend("x-section" = "Startup"))]
1156 pub restore_previous_session: bool,
1157
1158 #[serde(default = "default_true")]
1169 #[schemars(extend("x-section" = "Startup"))]
1170 pub skip_session_restore_when_files_passed: bool,
1171
1172 #[serde(default = "default_true")]
1180 #[schemars(extend("x-section" = "Startup"))]
1181 pub auto_create_empty_buffer_on_last_buffer_close: bool,
1182
1183 #[serde(default = "default_true")]
1188 #[schemars(extend("x-section" = "Recovery"))]
1189 pub recovery_enabled: bool,
1190
1191 #[serde(default = "default_auto_recovery_save_interval")]
1196 #[schemars(extend("x-section" = "Recovery"))]
1197 pub auto_recovery_save_interval_secs: u32,
1198
1199 #[serde(default = "default_auto_revert_poll_interval")]
1204 #[schemars(extend("x-section" = "Recovery"))]
1205 pub auto_revert_poll_interval_ms: u64,
1206
1207 #[serde(default = "default_true")]
1213 #[schemars(extend("x-section" = "Keyboard"))]
1214 pub keyboard_disambiguate_escape_codes: bool,
1215
1216 #[serde(default = "default_false")]
1221 #[schemars(extend("x-section" = "Keyboard"))]
1222 pub keyboard_report_event_types: bool,
1223
1224 #[serde(default = "default_true")]
1229 #[schemars(extend("x-section" = "Keyboard"))]
1230 pub keyboard_report_alternate_keys: bool,
1231
1232 #[serde(default = "default_false")]
1238 #[schemars(extend("x-section" = "Keyboard"))]
1239 pub keyboard_report_all_keys_as_escape_codes: bool,
1240
1241 #[serde(default = "default_highlight_timeout")]
1244 #[schemars(extend("x-section" = "Performance"))]
1245 pub highlight_timeout_ms: u64,
1246
1247 #[serde(default = "default_snapshot_interval")]
1249 #[schemars(extend("x-section" = "Performance"))]
1250 pub snapshot_interval: usize,
1251
1252 #[serde(default = "default_highlight_context_bytes")]
1257 #[schemars(extend("x-section" = "Performance"))]
1258 pub highlight_context_bytes: usize,
1259
1260 #[serde(default = "default_large_file_threshold")]
1267 #[schemars(extend("x-section" = "Performance"))]
1268 pub large_file_threshold_bytes: u64,
1269
1270 #[serde(default = "default_estimated_line_length")]
1274 #[schemars(extend("x-section" = "Performance"))]
1275 pub estimated_line_length: usize,
1276
1277 #[serde(default = "default_read_concurrency")]
1282 #[schemars(extend("x-section" = "Performance"))]
1283 pub read_concurrency: usize,
1284
1285 #[serde(default = "default_file_tree_poll_interval")]
1290 #[schemars(extend("x-section" = "Performance"))]
1291 pub file_tree_poll_interval_ms: u64,
1292}
1293
1294fn default_tab_size() -> usize {
1295 4
1296}
1297
1298pub const LARGE_FILE_THRESHOLD_BYTES: u64 = 1024 * 1024; fn default_large_file_threshold() -> u64 {
1304 LARGE_FILE_THRESHOLD_BYTES
1305}
1306
1307pub const INDENT_FOLD_MAX_SCAN_LINES: usize = 10_000;
1310
1311pub const INDENT_FOLD_INDICATOR_MAX_SCAN: usize = 50;
1314
1315pub const INDENT_FOLD_MAX_UPWARD_SCAN: usize = 200;
1318
1319fn default_read_concurrency() -> usize {
1320 64
1321}
1322
1323fn default_true() -> bool {
1324 true
1325}
1326
1327fn default_false() -> bool {
1328 false
1329}
1330
1331fn default_quick_suggestions_delay() -> u64 {
1332 150 }
1334
1335fn default_scroll_offset() -> usize {
1336 3
1337}
1338
1339fn default_highlight_timeout() -> u64 {
1340 5
1341}
1342
1343fn default_snapshot_interval() -> usize {
1344 100
1345}
1346
1347fn default_estimated_line_length() -> usize {
1348 80
1349}
1350
1351fn default_auto_save_interval() -> u32 {
1352 30 }
1354
1355fn default_auto_recovery_save_interval() -> u32 {
1356 2 }
1358
1359fn default_highlight_context_bytes() -> usize {
1360 10_000 }
1362
1363fn default_mouse_hover_enabled() -> bool {
1364 !cfg!(windows)
1365}
1366
1367fn default_mouse_hover_delay() -> u64 {
1368 500 }
1370
1371fn default_double_click_time() -> u64 {
1372 500 }
1374
1375fn default_auto_revert_poll_interval() -> u64 {
1376 2000 }
1378
1379fn default_file_tree_poll_interval() -> u64 {
1380 3000 }
1382
1383impl Default for EditorConfig {
1384 fn default() -> Self {
1385 Self {
1386 use_tabs: false,
1387 tab_size: default_tab_size(),
1388 auto_indent: true,
1389 auto_close: true,
1390 auto_surround: true,
1391 animations: true,
1392 line_numbers: true,
1393 relative_line_numbers: false,
1394 scroll_offset: default_scroll_offset(),
1395 syntax_highlighting: true,
1396 highlight_current_line: true,
1397 highlight_current_column: false,
1398 line_wrap: true,
1399 wrap_indent: true,
1400 wrap_column: None,
1401 page_width: default_page_width(),
1402 highlight_timeout_ms: default_highlight_timeout(),
1403 snapshot_interval: default_snapshot_interval(),
1404 large_file_threshold_bytes: default_large_file_threshold(),
1405 estimated_line_length: default_estimated_line_length(),
1406 enable_inlay_hints: true,
1407 enable_semantic_tokens_full: false,
1408 diagnostics_inline_text: false,
1409 auto_save_enabled: false,
1410 auto_save_interval_secs: default_auto_save_interval(),
1411 hot_exit: true,
1412 restore_previous_session: true,
1413 skip_session_restore_when_files_passed: true,
1414 auto_create_empty_buffer_on_last_buffer_close: true,
1415 recovery_enabled: true,
1416 auto_recovery_save_interval_secs: default_auto_recovery_save_interval(),
1417 highlight_context_bytes: default_highlight_context_bytes(),
1418 mouse_hover_enabled: default_mouse_hover_enabled(),
1419 mouse_hover_delay_ms: default_mouse_hover_delay(),
1420 double_click_time_ms: default_double_click_time(),
1421 auto_revert_poll_interval_ms: default_auto_revert_poll_interval(),
1422 read_concurrency: default_read_concurrency(),
1423 file_tree_poll_interval_ms: default_file_tree_poll_interval(),
1424 default_line_ending: LineEndingOption::default(),
1425 trim_trailing_whitespace_on_save: false,
1426 ensure_final_newline_on_save: false,
1427 highlight_matching_brackets: true,
1428 rainbow_brackets: true,
1429 cursor_style: CursorStyle::default(),
1430 keyboard_disambiguate_escape_codes: true,
1431 keyboard_report_event_types: false,
1432 keyboard_report_alternate_keys: true,
1433 keyboard_report_all_keys_as_escape_codes: false,
1434 completion_popup_auto_show: false,
1435 quick_suggestions: true,
1436 quick_suggestions_delay_ms: default_quick_suggestions_delay(),
1437 suggest_on_trigger_characters: true,
1438 show_menu_bar: true,
1439 menu_bar_mnemonics: true,
1440 show_tab_bar: true,
1441 show_status_bar: true,
1442 status_bar: StatusBarConfig::default(),
1443 show_prompt_line: false,
1444 show_vertical_scrollbar: true,
1445 show_horizontal_scrollbar: false,
1446 show_tilde: true,
1447 use_terminal_bg: false,
1448 set_window_title: true,
1449 rulers: Vec::new(),
1450 whitespace_show: true,
1451 whitespace_spaces_leading: false,
1452 whitespace_spaces_inner: false,
1453 whitespace_spaces_trailing: false,
1454 whitespace_tabs_leading: true,
1455 whitespace_tabs_inner: true,
1456 whitespace_tabs_trailing: true,
1457 }
1458 }
1459}
1460
1461#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
1463#[serde(rename_all = "snake_case")]
1464pub enum FileExplorerSide {
1465 #[default]
1466 Left,
1467 Right,
1468}
1469
1470#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1472pub struct FileExplorerConfig {
1473 #[serde(default = "default_true")]
1475 pub respect_gitignore: bool,
1476
1477 #[serde(default = "default_false")]
1479 pub show_hidden: bool,
1480
1481 #[serde(default = "default_false")]
1483 pub show_gitignored: bool,
1484
1485 #[serde(default)]
1487 pub custom_ignore_patterns: Vec<String>,
1488
1489 #[serde(default = "default_explorer_width")]
1495 pub width: ExplorerWidth,
1496
1497 #[serde(default = "default_true")]
1504 pub preview_tabs: bool,
1505
1506 #[serde(default = "default_explorer_side")]
1509 pub side: FileExplorerSide,
1510
1511 #[serde(default = "default_true")]
1517 pub auto_open_on_last_buffer_close: bool,
1518}
1519
1520#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1548pub enum ExplorerWidth {
1549 Percent(u8),
1550 Columns(u16),
1551}
1552
1553impl ExplorerWidth {
1554 pub const DEFAULT: Self = Self::Percent(30);
1556
1557 pub const MIN_COLS: u16 = 5;
1563
1564 pub fn to_cols(self, terminal_width: u16) -> u16 {
1572 let raw = match self {
1573 Self::Percent(pct) => ((terminal_width as u32 * pct as u32) / 100) as u16,
1574 Self::Columns(cols) => cols,
1575 };
1576 raw.max(Self::MIN_COLS).min(terminal_width)
1577 }
1578}
1579
1580impl Default for ExplorerWidth {
1581 fn default() -> Self {
1582 Self::DEFAULT
1583 }
1584}
1585
1586impl std::fmt::Display for ExplorerWidth {
1587 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1588 match self {
1589 Self::Percent(n) => write!(f, "{}%", n),
1590 Self::Columns(n) => write!(f, "{}", n),
1591 }
1592 }
1593}
1594
1595#[derive(Debug)]
1597pub struct ExplorerWidthParseError(String);
1598
1599impl std::fmt::Display for ExplorerWidthParseError {
1600 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1601 write!(f, "{}", self.0)
1602 }
1603}
1604
1605impl std::error::Error for ExplorerWidthParseError {}
1606
1607impl std::str::FromStr for ExplorerWidth {
1608 type Err = ExplorerWidthParseError;
1609
1610 fn from_str(s: &str) -> Result<Self, Self::Err> {
1611 let s = s.trim();
1612 if s.is_empty() {
1613 return Err(ExplorerWidthParseError(
1614 "explorer width: empty string".into(),
1615 ));
1616 }
1617 if let Some(rest) = s.strip_suffix('%') {
1618 let n: u16 = rest.trim().parse().map_err(|_| {
1619 ExplorerWidthParseError(format!("explorer width: {:?} is not a valid percent", s))
1620 })?;
1621 if n > 100 {
1622 return Err(ExplorerWidthParseError(format!(
1623 "explorer width: {}% exceeds 100%",
1624 n
1625 )));
1626 }
1627 Ok(Self::Percent(n as u8))
1628 } else {
1629 let n: u16 = s.parse().map_err(|_| {
1630 ExplorerWidthParseError(format!(
1631 "explorer width: {:?} is neither a percent (e.g. \"30%\") nor a column count (e.g. \"24\")",
1632 s
1633 ))
1634 })?;
1635 Ok(Self::Columns(n))
1636 }
1637 }
1638}
1639
1640impl serde::Serialize for ExplorerWidth {
1641 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1642 s.collect_str(self)
1643 }
1644}
1645
1646impl<'de> serde::Deserialize<'de> for ExplorerWidth {
1647 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
1648 let raw = serde_json::Value::deserialize(d)?;
1649 explorer_width::from_value(&raw)
1650 }
1651}
1652
1653impl schemars::JsonSchema for ExplorerWidth {
1654 fn schema_name() -> std::borrow::Cow<'static, str> {
1655 std::borrow::Cow::Borrowed("ExplorerWidth")
1656 }
1657
1658 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
1659 schemars::json_schema!({
1663 "type": "string",
1664 "pattern": r"^(100%|[1-9]?[0-9]%|\d+)$",
1665 "description": "Either a percent like \"30%\" (0–100) or an absolute column count like \"24\".",
1666 })
1667 }
1668}
1669
1670fn default_explorer_width() -> ExplorerWidth {
1671 ExplorerWidth::DEFAULT
1672}
1673
1674fn default_explorer_side() -> FileExplorerSide {
1675 FileExplorerSide::default()
1676}
1677
1678pub fn default_explorer_width_value() -> ExplorerWidth {
1680 ExplorerWidth::DEFAULT
1681}
1682
1683pub(crate) mod explorer_width {
1687 use super::ExplorerWidth;
1688 use serde::de::{self, Deserialize, Deserializer};
1689 use std::str::FromStr;
1690
1691 pub fn deserialize_optional<'de, D>(d: D) -> Result<Option<ExplorerWidth>, D::Error>
1696 where
1697 D: Deserializer<'de>,
1698 {
1699 let raw = Option::<serde_json::Value>::deserialize(d)?;
1700 match raw {
1701 None | Some(serde_json::Value::Null) => Ok(None),
1702 Some(v) => from_value(&v).map(Some),
1703 }
1704 }
1705
1706 pub(super) fn from_value<E: de::Error>(v: &serde_json::Value) -> Result<ExplorerWidth, E> {
1707 match v {
1708 serde_json::Value::String(s) => ExplorerWidth::from_str(s).map_err(E::custom),
1709 serde_json::Value::Number(n) => {
1710 if let Some(u) = n.as_u64() {
1711 if u > 100 {
1715 return Err(E::custom(format!(
1716 "explorer width: {} exceeds 100 (percent). Use \"{}\" for columns.",
1717 u, u
1718 )));
1719 }
1720 Ok(ExplorerWidth::Percent(u as u8))
1721 } else if let Some(f) = n.as_f64() {
1722 let pct = if (0.0..=1.0).contains(&f) {
1724 f * 100.0
1725 } else {
1726 f
1727 };
1728 if !(0.0..=100.0).contains(&pct) {
1729 return Err(E::custom(format!(
1730 "explorer width: percent {} out of range 0..=100",
1731 pct
1732 )));
1733 }
1734 Ok(ExplorerWidth::Percent(pct.round() as u8))
1735 } else {
1736 Err(E::custom("explorer width: unsupported number"))
1737 }
1738 }
1739 _ => Err(E::custom(
1740 "explorer width: expected \"30%\", \"24\" (columns), or a number",
1741 )),
1742 }
1743 }
1744}
1745
1746#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1757pub struct ClipboardConfig {
1758 #[serde(default = "default_true")]
1761 pub use_osc52: bool,
1762
1763 #[serde(default = "default_true")]
1766 pub use_system_clipboard: bool,
1767}
1768
1769impl Default for ClipboardConfig {
1770 fn default() -> Self {
1771 Self {
1772 use_osc52: true,
1773 use_system_clipboard: true,
1774 }
1775 }
1776}
1777
1778#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1780pub struct TerminalConfig {
1781 #[serde(default = "default_true")]
1784 pub jump_to_end_on_output: bool,
1785
1786 #[serde(default)]
1798 pub shell: Option<TerminalShellConfig>,
1799}
1800
1801impl Default for TerminalConfig {
1802 fn default() -> Self {
1803 Self {
1804 jump_to_end_on_output: true,
1805 shell: None,
1806 }
1807 }
1808}
1809
1810#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1812pub struct TerminalShellConfig {
1813 pub command: String,
1816
1817 #[serde(default)]
1819 pub args: Vec<String>,
1820}
1821
1822#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1824pub struct WarningsConfig {
1825 #[serde(default = "default_true")]
1828 pub show_status_indicator: bool,
1829}
1830
1831impl Default for WarningsConfig {
1832 fn default() -> Self {
1833 Self {
1834 show_status_indicator: true,
1835 }
1836 }
1837}
1838
1839#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1841pub struct PackagesConfig {
1842 #[serde(default = "default_package_sources")]
1845 pub sources: Vec<String>,
1846}
1847
1848fn default_package_sources() -> Vec<String> {
1849 vec!["https://github.com/sinelaw/fresh-plugins-registry".to_string()]
1850}
1851
1852impl Default for PackagesConfig {
1853 fn default() -> Self {
1854 Self {
1855 sources: default_package_sources(),
1856 }
1857 }
1858}
1859
1860pub use fresh_core::config::PluginConfig;
1862
1863impl Default for FileExplorerConfig {
1864 fn default() -> Self {
1865 Self {
1866 respect_gitignore: true,
1867 show_hidden: false,
1868 show_gitignored: false,
1869 custom_ignore_patterns: Vec::new(),
1870 width: default_explorer_width(),
1871 preview_tabs: true,
1872 side: default_explorer_side(),
1873 auto_open_on_last_buffer_close: true,
1874 }
1875 }
1876}
1877
1878#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
1880pub struct FileBrowserConfig {
1881 #[serde(default = "default_false")]
1883 pub show_hidden: bool,
1884}
1885
1886#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1888pub struct KeyPress {
1889 pub key: String,
1891 #[serde(default)]
1893 pub modifiers: Vec<String>,
1894}
1895
1896#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1898#[schemars(extend("x-display-field" = "/action"))]
1899pub struct Keybinding {
1900 #[serde(default, skip_serializing_if = "String::is_empty")]
1902 pub key: String,
1903
1904 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1906 pub modifiers: Vec<String>,
1907
1908 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1911 pub keys: Vec<KeyPress>,
1912
1913 pub action: String,
1915
1916 #[serde(default)]
1918 pub args: HashMap<String, serde_json::Value>,
1919
1920 #[serde(default)]
1922 pub when: Option<String>,
1923}
1924
1925#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1927#[schemars(extend("x-display-field" = "/inherits"))]
1928pub struct KeymapConfig {
1929 #[serde(default, skip_serializing_if = "Option::is_none")]
1931 pub inherits: Option<String>,
1932
1933 #[serde(default)]
1935 pub bindings: Vec<Keybinding>,
1936}
1937
1938#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1940#[schemars(extend("x-display-field" = "/command"))]
1941pub struct FormatterConfig {
1942 pub command: String,
1944
1945 #[serde(default)]
1948 pub args: Vec<String>,
1949
1950 #[serde(default = "default_true")]
1953 pub stdin: bool,
1954
1955 #[serde(default = "default_on_save_timeout")]
1957 pub timeout_ms: u64,
1958}
1959
1960#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1962#[schemars(extend("x-display-field" = "/command"))]
1963pub struct OnSaveAction {
1964 pub command: String,
1967
1968 #[serde(default)]
1971 pub args: Vec<String>,
1972
1973 #[serde(default)]
1975 pub working_dir: Option<String>,
1976
1977 #[serde(default)]
1979 pub stdin: bool,
1980
1981 #[serde(default = "default_on_save_timeout")]
1983 pub timeout_ms: u64,
1984
1985 #[serde(default = "default_true")]
1988 pub enabled: bool,
1989}
1990
1991fn default_on_save_timeout() -> u64 {
1992 10000
1993}
1994
1995fn default_page_width() -> Option<usize> {
1996 Some(80)
1997}
1998
1999#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2001#[schemars(extend("x-display-field" = "/grammar"))]
2002pub struct LanguageConfig {
2003 #[serde(default)]
2005 pub extensions: Vec<String>,
2006
2007 #[serde(default)]
2009 pub filenames: Vec<String>,
2010
2011 #[serde(default)]
2013 pub grammar: String,
2014
2015 #[serde(default)]
2017 pub comment_prefix: Option<String>,
2018
2019 #[serde(default = "default_true")]
2021 pub auto_indent: bool,
2022
2023 #[serde(default)]
2026 pub auto_close: Option<bool>,
2027
2028 #[serde(default)]
2031 pub auto_surround: Option<bool>,
2032
2033 #[serde(default)]
2036 pub textmate_grammar: Option<std::path::PathBuf>,
2037
2038 #[serde(default = "default_true")]
2041 pub show_whitespace_tabs: bool,
2042
2043 #[serde(default)]
2048 pub line_wrap: Option<bool>,
2049
2050 #[serde(default)]
2053 pub wrap_column: Option<usize>,
2054
2055 #[serde(default)]
2060 pub page_view: Option<bool>,
2061
2062 #[serde(default)]
2066 pub page_width: Option<usize>,
2067
2068 #[serde(default)]
2072 pub use_tabs: Option<bool>,
2073
2074 #[serde(default)]
2077 pub tab_size: Option<usize>,
2078
2079 #[serde(default)]
2081 pub formatter: Option<FormatterConfig>,
2082
2083 #[serde(default)]
2085 pub format_on_save: bool,
2086
2087 #[serde(default)]
2091 pub on_save: Vec<OnSaveAction>,
2092
2093 #[serde(default)]
2103 pub word_characters: Option<String>,
2104}
2105
2106#[derive(Debug, Clone)]
2113pub struct BufferConfig {
2114 pub tab_size: usize,
2116
2117 pub use_tabs: bool,
2119
2120 pub auto_indent: bool,
2122
2123 pub auto_close: bool,
2125
2126 pub auto_surround: bool,
2128
2129 pub line_wrap: bool,
2131
2132 pub wrap_column: Option<usize>,
2134
2135 pub whitespace: WhitespaceVisibility,
2137
2138 pub formatter: Option<FormatterConfig>,
2140
2141 pub format_on_save: bool,
2143
2144 pub on_save: Vec<OnSaveAction>,
2146
2147 pub textmate_grammar: Option<std::path::PathBuf>,
2149
2150 pub word_characters: String,
2153}
2154
2155impl BufferConfig {
2156 pub fn resolve(global_config: &Config, language_id: Option<&str>) -> Self {
2165 let editor = &global_config.editor;
2166
2167 let mut whitespace = WhitespaceVisibility::from_editor_config(editor);
2169 let mut config = BufferConfig {
2170 tab_size: editor.tab_size,
2171 use_tabs: editor.use_tabs,
2172 auto_indent: editor.auto_indent,
2173 auto_close: editor.auto_close,
2174 auto_surround: editor.auto_surround,
2175 line_wrap: editor.line_wrap,
2176 wrap_column: editor.wrap_column,
2177 whitespace,
2178 formatter: None,
2179 format_on_save: false,
2180 on_save: Vec::new(),
2181 textmate_grammar: None,
2182 word_characters: String::new(),
2183 };
2184
2185 let lang_config_ref = language_id
2189 .and_then(|id| global_config.languages.get(id))
2190 .or_else(|| {
2191 match language_id {
2193 None | Some("text") => global_config
2194 .default_language
2195 .as_deref()
2196 .and_then(|lang| global_config.languages.get(lang)),
2197 _ => None,
2198 }
2199 });
2200 if let Some(lang_config) = lang_config_ref {
2201 if let Some(ts) = lang_config.tab_size {
2203 config.tab_size = ts;
2204 }
2205
2206 if let Some(use_tabs) = lang_config.use_tabs {
2208 config.use_tabs = use_tabs;
2209 }
2210
2211 if let Some(line_wrap) = lang_config.line_wrap {
2213 config.line_wrap = line_wrap;
2214 }
2215
2216 if lang_config.wrap_column.is_some() {
2218 config.wrap_column = lang_config.wrap_column;
2219 }
2220
2221 config.auto_indent = lang_config.auto_indent;
2223
2224 if config.auto_close {
2226 if let Some(lang_auto_close) = lang_config.auto_close {
2227 config.auto_close = lang_auto_close;
2228 }
2229 }
2230
2231 if config.auto_surround {
2233 if let Some(lang_auto_surround) = lang_config.auto_surround {
2234 config.auto_surround = lang_auto_surround;
2235 }
2236 }
2237
2238 whitespace = whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
2240 config.whitespace = whitespace;
2241
2242 config.formatter = lang_config.formatter.clone();
2244
2245 config.format_on_save = lang_config.format_on_save;
2247
2248 config.on_save = lang_config.on_save.clone();
2250
2251 config.textmate_grammar = lang_config.textmate_grammar.clone();
2253
2254 if let Some(ref wc) = lang_config.word_characters {
2256 config.word_characters = wc.clone();
2257 }
2258 }
2259
2260 config
2261 }
2262
2263 pub fn indent_string(&self) -> String {
2268 if self.use_tabs {
2269 "\t".to_string()
2270 } else {
2271 " ".repeat(self.tab_size)
2272 }
2273 }
2274}
2275
2276#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
2278pub struct MenuConfig {
2279 #[serde(default)]
2281 pub menus: Vec<Menu>,
2282}
2283
2284pub use fresh_core::menu::{Menu, MenuItem};
2286
2287pub trait MenuExt {
2289 fn match_id(&self) -> &str;
2292
2293 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path);
2296}
2297
2298impl MenuExt for Menu {
2299 fn match_id(&self) -> &str {
2300 self.id.as_deref().unwrap_or(&self.label)
2301 }
2302
2303 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path) {
2304 self.items = self
2305 .items
2306 .iter()
2307 .map(|item| item.expand_dynamic(themes_dir))
2308 .collect();
2309 }
2310}
2311
2312pub trait MenuItemExt {
2314 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem;
2317}
2318
2319impl MenuItemExt for MenuItem {
2320 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem {
2321 match self {
2322 MenuItem::DynamicSubmenu { label, source } => {
2323 let items = generate_dynamic_items(source, themes_dir);
2324 MenuItem::Submenu {
2325 label: label.clone(),
2326 items,
2327 }
2328 }
2329 other => other.clone(),
2330 }
2331 }
2332}
2333
2334#[cfg(feature = "runtime")]
2336pub fn generate_dynamic_items(source: &str, themes_dir: &std::path::Path) -> Vec<MenuItem> {
2337 match source {
2338 "copy_with_theme" => {
2339 let loader = crate::view::theme::ThemeLoader::new(themes_dir.to_path_buf());
2341 let registry = loader.load_all(&[]);
2342 registry
2343 .list()
2344 .iter()
2345 .map(|info| {
2346 let mut args = HashMap::new();
2347 args.insert("theme".to_string(), serde_json::json!(info.key));
2348 MenuItem::Action {
2349 label: info.name.clone(),
2350 action: "copy_with_theme".to_string(),
2351 args,
2352 when: Some(context_keys::HAS_SELECTION.to_string()),
2353 checkbox: None,
2354 }
2355 })
2356 .collect()
2357 }
2358 _ => vec![MenuItem::Label {
2359 info: format!("Unknown source: {}", source),
2360 }],
2361 }
2362}
2363
2364#[cfg(not(feature = "runtime"))]
2366pub fn generate_dynamic_items(_source: &str, _themes_dir: &std::path::Path) -> Vec<MenuItem> {
2367 vec![]
2369}
2370
2371impl Default for Config {
2372 fn default() -> Self {
2373 Self {
2374 version: 0,
2375 theme: default_theme_name(),
2376 locale: LocaleName::default(),
2377 check_for_updates: true,
2378 editor: EditorConfig::default(),
2379 file_explorer: FileExplorerConfig::default(),
2380 file_browser: FileBrowserConfig::default(),
2381 clipboard: ClipboardConfig::default(),
2382 terminal: TerminalConfig::default(),
2383 keybindings: vec![], keybinding_maps: HashMap::new(), active_keybinding_map: default_keybinding_map_name(),
2386 languages: Self::default_languages(),
2387 default_language: None,
2388 lsp: Self::default_lsp_config(),
2389 universal_lsp: Self::default_universal_lsp_config(),
2390 warnings: WarningsConfig::default(),
2391 plugins: HashMap::new(), packages: PackagesConfig::default(),
2393 }
2394 }
2395}
2396
2397impl MenuConfig {
2398 pub fn translated() -> Self {
2400 Self {
2401 menus: Self::translated_menus(),
2402 }
2403 }
2404
2405 pub fn translated_menus() -> Vec<Menu> {
2411 vec![
2412 Menu {
2414 id: Some("File".to_string()),
2415 label: t!("menu.file").to_string(),
2416 when: None,
2417 items: vec![
2418 MenuItem::Action {
2419 label: t!("menu.file.new_file").to_string(),
2420 action: "new".to_string(),
2421 args: HashMap::new(),
2422 when: None,
2423 checkbox: None,
2424 },
2425 MenuItem::Action {
2426 label: t!("menu.file.open_file").to_string(),
2427 action: "open".to_string(),
2428 args: HashMap::new(),
2429 when: None,
2430 checkbox: None,
2431 },
2432 MenuItem::Separator { separator: true },
2433 MenuItem::Action {
2434 label: t!("menu.file.save").to_string(),
2435 action: "save".to_string(),
2436 args: HashMap::new(),
2437 when: Some(context_keys::HAS_BUFFER.to_string()),
2438 checkbox: None,
2439 },
2440 MenuItem::Action {
2441 label: t!("menu.file.save_as").to_string(),
2442 action: "save_as".to_string(),
2443 args: HashMap::new(),
2444 when: Some(context_keys::HAS_BUFFER.to_string()),
2445 checkbox: None,
2446 },
2447 MenuItem::Action {
2448 label: t!("menu.file.revert").to_string(),
2449 action: "revert".to_string(),
2450 args: HashMap::new(),
2451 when: Some(context_keys::HAS_BUFFER.to_string()),
2452 checkbox: None,
2453 },
2454 MenuItem::Action {
2455 label: t!("menu.file.reload_with_encoding").to_string(),
2456 action: "reload_with_encoding".to_string(),
2457 args: HashMap::new(),
2458 when: Some(context_keys::HAS_BUFFER.to_string()),
2459 checkbox: None,
2460 },
2461 MenuItem::Separator { separator: true },
2462 MenuItem::Action {
2463 label: t!("menu.file.close_buffer").to_string(),
2464 action: "close".to_string(),
2465 args: HashMap::new(),
2466 when: Some(context_keys::HAS_BUFFER.to_string()),
2467 checkbox: None,
2468 },
2469 MenuItem::Separator { separator: true },
2470 MenuItem::Action {
2471 label: t!("menu.file.switch_project").to_string(),
2472 action: "switch_project".to_string(),
2473 args: HashMap::new(),
2474 when: None,
2475 checkbox: None,
2476 },
2477 MenuItem::Separator { separator: true },
2478 MenuItem::Action {
2479 label: t!("menu.file.detach").to_string(),
2480 action: "detach".to_string(),
2481 args: HashMap::new(),
2482 when: Some(context_keys::SESSION_MODE.to_string()),
2483 checkbox: None,
2484 },
2485 MenuItem::Action {
2486 label: t!("menu.file.quit").to_string(),
2487 action: "quit".to_string(),
2488 args: HashMap::new(),
2489 when: None,
2490 checkbox: None,
2491 },
2492 ],
2493 },
2494 Menu {
2496 id: Some("Edit".to_string()),
2497 label: t!("menu.edit").to_string(),
2498 when: None,
2499 items: vec![
2500 MenuItem::Action {
2501 label: t!("menu.edit.undo").to_string(),
2502 action: "undo".to_string(),
2503 args: HashMap::new(),
2504 when: Some(context_keys::HAS_BUFFER.to_string()),
2505 checkbox: None,
2506 },
2507 MenuItem::Action {
2508 label: t!("menu.edit.redo").to_string(),
2509 action: "redo".to_string(),
2510 args: HashMap::new(),
2511 when: Some(context_keys::HAS_BUFFER.to_string()),
2512 checkbox: None,
2513 },
2514 MenuItem::Separator { separator: true },
2515 MenuItem::Action {
2516 label: t!("menu.edit.cut").to_string(),
2517 action: "cut".to_string(),
2518 args: HashMap::new(),
2519 when: Some(context_keys::CAN_COPY.to_string()),
2520 checkbox: None,
2521 },
2522 MenuItem::Action {
2523 label: t!("menu.edit.copy").to_string(),
2524 action: "copy".to_string(),
2525 args: HashMap::new(),
2526 when: Some(context_keys::CAN_COPY.to_string()),
2527 checkbox: None,
2528 },
2529 MenuItem::DynamicSubmenu {
2530 label: t!("menu.edit.copy_with_formatting").to_string(),
2531 source: "copy_with_theme".to_string(),
2532 },
2533 MenuItem::Action {
2534 label: t!("menu.edit.paste").to_string(),
2535 action: "paste".to_string(),
2536 args: HashMap::new(),
2537 when: Some(context_keys::CAN_PASTE.to_string()),
2538 checkbox: None,
2539 },
2540 MenuItem::Separator { separator: true },
2541 MenuItem::Action {
2542 label: t!("menu.edit.select_all").to_string(),
2543 action: "select_all".to_string(),
2544 args: HashMap::new(),
2545 when: Some(context_keys::HAS_BUFFER.to_string()),
2546 checkbox: None,
2547 },
2548 MenuItem::Separator { separator: true },
2549 MenuItem::Action {
2550 label: t!("menu.edit.find").to_string(),
2551 action: "search".to_string(),
2552 args: HashMap::new(),
2553 when: Some(context_keys::HAS_BUFFER.to_string()),
2554 checkbox: None,
2555 },
2556 MenuItem::Action {
2557 label: t!("menu.edit.find_in_selection").to_string(),
2558 action: "find_in_selection".to_string(),
2559 args: HashMap::new(),
2560 when: Some(context_keys::HAS_SELECTION.to_string()),
2561 checkbox: None,
2562 },
2563 MenuItem::Action {
2564 label: t!("menu.edit.find_next").to_string(),
2565 action: "find_next".to_string(),
2566 args: HashMap::new(),
2567 when: Some(context_keys::HAS_BUFFER.to_string()),
2568 checkbox: None,
2569 },
2570 MenuItem::Action {
2571 label: t!("menu.edit.find_previous").to_string(),
2572 action: "find_previous".to_string(),
2573 args: HashMap::new(),
2574 when: Some(context_keys::HAS_BUFFER.to_string()),
2575 checkbox: None,
2576 },
2577 MenuItem::Action {
2578 label: t!("menu.edit.replace").to_string(),
2579 action: "query_replace".to_string(),
2580 args: HashMap::new(),
2581 when: Some(context_keys::HAS_BUFFER.to_string()),
2582 checkbox: None,
2583 },
2584 MenuItem::Separator { separator: true },
2585 MenuItem::Action {
2586 label: t!("menu.edit.delete_line").to_string(),
2587 action: "delete_line".to_string(),
2588 args: HashMap::new(),
2589 when: Some(context_keys::HAS_BUFFER.to_string()),
2590 checkbox: None,
2591 },
2592 MenuItem::Action {
2593 label: t!("menu.edit.format_buffer").to_string(),
2594 action: "format_buffer".to_string(),
2595 args: HashMap::new(),
2596 when: Some(context_keys::FORMATTER_AVAILABLE.to_string()),
2597 checkbox: None,
2598 },
2599 MenuItem::Separator { separator: true },
2600 MenuItem::Action {
2601 label: t!("menu.edit.settings").to_string(),
2602 action: "open_settings".to_string(),
2603 args: HashMap::new(),
2604 when: None,
2605 checkbox: None,
2606 },
2607 MenuItem::Action {
2608 label: t!("menu.edit.keybinding_editor").to_string(),
2609 action: "open_keybinding_editor".to_string(),
2610 args: HashMap::new(),
2611 when: None,
2612 checkbox: None,
2613 },
2614 ],
2615 },
2616 Menu {
2618 id: Some("View".to_string()),
2619 label: t!("menu.view").to_string(),
2620 when: None,
2621 items: vec![
2622 MenuItem::Action {
2623 label: t!("menu.view.file_explorer").to_string(),
2624 action: "toggle_file_explorer".to_string(),
2625 args: HashMap::new(),
2626 when: None,
2627 checkbox: Some(context_keys::FILE_EXPLORER.to_string()),
2628 },
2629 MenuItem::Separator { separator: true },
2630 MenuItem::Action {
2631 label: t!("menu.view.line_numbers").to_string(),
2632 action: "toggle_line_numbers".to_string(),
2633 args: HashMap::new(),
2634 when: None,
2635 checkbox: Some(context_keys::LINE_NUMBERS.to_string()),
2636 },
2637 MenuItem::Action {
2638 label: t!("menu.view.line_wrap").to_string(),
2639 action: "toggle_line_wrap".to_string(),
2640 args: HashMap::new(),
2641 when: None,
2642 checkbox: Some(context_keys::LINE_WRAP.to_string()),
2643 },
2644 MenuItem::Action {
2645 label: t!("menu.view.mouse_support").to_string(),
2646 action: "toggle_mouse_capture".to_string(),
2647 args: HashMap::new(),
2648 when: None,
2649 checkbox: Some(context_keys::MOUSE_CAPTURE.to_string()),
2650 },
2651 MenuItem::Separator { separator: true },
2652 MenuItem::Action {
2653 label: t!("menu.view.vertical_scrollbar").to_string(),
2654 action: "toggle_vertical_scrollbar".to_string(),
2655 args: HashMap::new(),
2656 when: None,
2657 checkbox: Some(context_keys::VERTICAL_SCROLLBAR.to_string()),
2658 },
2659 MenuItem::Action {
2660 label: t!("menu.view.horizontal_scrollbar").to_string(),
2661 action: "toggle_horizontal_scrollbar".to_string(),
2662 args: HashMap::new(),
2663 when: None,
2664 checkbox: Some(context_keys::HORIZONTAL_SCROLLBAR.to_string()),
2665 },
2666 MenuItem::Separator { separator: true },
2667 MenuItem::Action {
2668 label: t!("menu.view.set_background").to_string(),
2669 action: "set_background".to_string(),
2670 args: HashMap::new(),
2671 when: None,
2672 checkbox: None,
2673 },
2674 MenuItem::Action {
2675 label: t!("menu.view.set_background_blend").to_string(),
2676 action: "set_background_blend".to_string(),
2677 args: HashMap::new(),
2678 when: None,
2679 checkbox: None,
2680 },
2681 MenuItem::Action {
2682 label: t!("menu.view.set_page_width").to_string(),
2683 action: "set_page_width".to_string(),
2684 args: HashMap::new(),
2685 when: None,
2686 checkbox: None,
2687 },
2688 MenuItem::Separator { separator: true },
2689 MenuItem::Action {
2690 label: t!("menu.view.select_theme").to_string(),
2691 action: "select_theme".to_string(),
2692 args: HashMap::new(),
2693 when: None,
2694 checkbox: None,
2695 },
2696 MenuItem::Action {
2697 label: t!("menu.view.select_locale").to_string(),
2698 action: "select_locale".to_string(),
2699 args: HashMap::new(),
2700 when: None,
2701 checkbox: None,
2702 },
2703 MenuItem::Action {
2704 label: t!("menu.view.settings").to_string(),
2705 action: "open_settings".to_string(),
2706 args: HashMap::new(),
2707 when: None,
2708 checkbox: None,
2709 },
2710 MenuItem::Action {
2711 label: t!("menu.view.calibrate_input").to_string(),
2712 action: "calibrate_input".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.view.split_horizontal").to_string(),
2720 action: "split_horizontal".to_string(),
2721 args: HashMap::new(),
2722 when: Some(context_keys::HAS_BUFFER.to_string()),
2723 checkbox: None,
2724 },
2725 MenuItem::Action {
2726 label: t!("menu.view.split_vertical").to_string(),
2727 action: "split_vertical".to_string(),
2728 args: HashMap::new(),
2729 when: Some(context_keys::HAS_BUFFER.to_string()),
2730 checkbox: None,
2731 },
2732 MenuItem::Action {
2733 label: t!("menu.view.close_split").to_string(),
2734 action: "close_split".to_string(),
2735 args: HashMap::new(),
2736 when: Some(context_keys::HAS_BUFFER.to_string()),
2737 checkbox: None,
2738 },
2739 MenuItem::Action {
2740 label: t!("menu.view.scroll_sync").to_string(),
2741 action: "toggle_scroll_sync".to_string(),
2742 args: HashMap::new(),
2743 when: Some(context_keys::HAS_SAME_BUFFER_SPLITS.to_string()),
2744 checkbox: Some(context_keys::SCROLL_SYNC.to_string()),
2745 },
2746 MenuItem::Action {
2747 label: t!("menu.view.focus_next_split").to_string(),
2748 action: "next_split".to_string(),
2749 args: HashMap::new(),
2750 when: None,
2751 checkbox: None,
2752 },
2753 MenuItem::Action {
2754 label: t!("menu.view.focus_prev_split").to_string(),
2755 action: "prev_split".to_string(),
2756 args: HashMap::new(),
2757 when: None,
2758 checkbox: None,
2759 },
2760 MenuItem::Action {
2761 label: t!("menu.view.toggle_maximize_split").to_string(),
2762 action: "toggle_maximize_split".to_string(),
2763 args: HashMap::new(),
2764 when: None,
2765 checkbox: None,
2766 },
2767 MenuItem::Separator { separator: true },
2768 MenuItem::Submenu {
2769 label: t!("menu.terminal").to_string(),
2770 items: vec![
2771 MenuItem::Action {
2772 label: t!("menu.terminal.open").to_string(),
2773 action: "open_terminal".to_string(),
2774 args: HashMap::new(),
2775 when: None,
2776 checkbox: None,
2777 },
2778 MenuItem::Action {
2779 label: t!("menu.terminal.close").to_string(),
2780 action: "close_terminal".to_string(),
2781 args: HashMap::new(),
2782 when: None,
2783 checkbox: None,
2784 },
2785 MenuItem::Separator { separator: true },
2786 MenuItem::Action {
2787 label: t!("menu.terminal.toggle_keyboard_capture").to_string(),
2788 action: "toggle_keyboard_capture".to_string(),
2789 args: HashMap::new(),
2790 when: None,
2791 checkbox: None,
2792 },
2793 ],
2794 },
2795 MenuItem::Separator { separator: true },
2796 MenuItem::Submenu {
2797 label: t!("menu.view.keybinding_style").to_string(),
2798 items: vec![
2799 MenuItem::Action {
2800 label: t!("menu.view.keybinding_default").to_string(),
2801 action: "switch_keybinding_map".to_string(),
2802 args: {
2803 let mut map = HashMap::new();
2804 map.insert("map".to_string(), serde_json::json!("default"));
2805 map
2806 },
2807 when: None,
2808 checkbox: Some(context_keys::KEYMAP_DEFAULT.to_string()),
2809 },
2810 MenuItem::Action {
2811 label: t!("menu.view.keybinding_emacs").to_string(),
2812 action: "switch_keybinding_map".to_string(),
2813 args: {
2814 let mut map = HashMap::new();
2815 map.insert("map".to_string(), serde_json::json!("emacs"));
2816 map
2817 },
2818 when: None,
2819 checkbox: Some(context_keys::KEYMAP_EMACS.to_string()),
2820 },
2821 MenuItem::Action {
2822 label: t!("menu.view.keybinding_vscode").to_string(),
2823 action: "switch_keybinding_map".to_string(),
2824 args: {
2825 let mut map = HashMap::new();
2826 map.insert("map".to_string(), serde_json::json!("vscode"));
2827 map
2828 },
2829 when: None,
2830 checkbox: Some(context_keys::KEYMAP_VSCODE.to_string()),
2831 },
2832 MenuItem::Action {
2833 label: "macOS GUI (⌘)".to_string(),
2834 action: "switch_keybinding_map".to_string(),
2835 args: {
2836 let mut map = HashMap::new();
2837 map.insert("map".to_string(), serde_json::json!("macos-gui"));
2838 map
2839 },
2840 when: None,
2841 checkbox: Some(context_keys::KEYMAP_MACOS_GUI.to_string()),
2842 },
2843 ],
2844 },
2845 ],
2846 },
2847 Menu {
2849 id: Some("Selection".to_string()),
2850 label: t!("menu.selection").to_string(),
2851 when: Some(context_keys::HAS_BUFFER.to_string()),
2852 items: vec![
2853 MenuItem::Action {
2854 label: t!("menu.selection.select_all").to_string(),
2855 action: "select_all".to_string(),
2856 args: HashMap::new(),
2857 when: None,
2858 checkbox: None,
2859 },
2860 MenuItem::Action {
2861 label: t!("menu.selection.select_word").to_string(),
2862 action: "select_word".to_string(),
2863 args: HashMap::new(),
2864 when: None,
2865 checkbox: None,
2866 },
2867 MenuItem::Action {
2868 label: t!("menu.selection.select_line").to_string(),
2869 action: "select_line".to_string(),
2870 args: HashMap::new(),
2871 when: None,
2872 checkbox: None,
2873 },
2874 MenuItem::Action {
2875 label: t!("menu.selection.expand_selection").to_string(),
2876 action: "expand_selection".to_string(),
2877 args: HashMap::new(),
2878 when: None,
2879 checkbox: None,
2880 },
2881 MenuItem::Separator { separator: true },
2882 MenuItem::Action {
2883 label: t!("menu.selection.add_cursor_above").to_string(),
2884 action: "add_cursor_above".to_string(),
2885 args: HashMap::new(),
2886 when: None,
2887 checkbox: None,
2888 },
2889 MenuItem::Action {
2890 label: t!("menu.selection.add_cursor_below").to_string(),
2891 action: "add_cursor_below".to_string(),
2892 args: HashMap::new(),
2893 when: None,
2894 checkbox: None,
2895 },
2896 MenuItem::Action {
2897 label: t!("menu.selection.add_cursor_next_match").to_string(),
2898 action: "add_cursor_next_match".to_string(),
2899 args: HashMap::new(),
2900 when: None,
2901 checkbox: None,
2902 },
2903 MenuItem::Action {
2904 label: t!("menu.selection.remove_secondary_cursors").to_string(),
2905 action: "remove_secondary_cursors".to_string(),
2906 args: HashMap::new(),
2907 when: None,
2908 checkbox: None,
2909 },
2910 ],
2911 },
2912 Menu {
2914 id: Some("Go".to_string()),
2915 label: t!("menu.go").to_string(),
2916 when: None,
2917 items: vec![
2918 MenuItem::Action {
2919 label: t!("menu.go.goto_line").to_string(),
2920 action: "goto_line".to_string(),
2921 args: HashMap::new(),
2922 when: Some(context_keys::HAS_BUFFER.to_string()),
2923 checkbox: None,
2924 },
2925 MenuItem::Action {
2926 label: t!("menu.go.goto_definition").to_string(),
2927 action: "lsp_goto_definition".to_string(),
2928 args: HashMap::new(),
2929 when: Some(context_keys::HAS_BUFFER.to_string()),
2930 checkbox: None,
2931 },
2932 MenuItem::Action {
2933 label: t!("menu.go.find_references").to_string(),
2934 action: "lsp_references".to_string(),
2935 args: HashMap::new(),
2936 when: Some(context_keys::HAS_BUFFER.to_string()),
2937 checkbox: None,
2938 },
2939 MenuItem::Separator { separator: true },
2940 MenuItem::Action {
2941 label: t!("menu.go.next_buffer").to_string(),
2942 action: "next_buffer".to_string(),
2943 args: HashMap::new(),
2944 when: Some(context_keys::HAS_BUFFER.to_string()),
2945 checkbox: None,
2946 },
2947 MenuItem::Action {
2948 label: t!("menu.go.prev_buffer").to_string(),
2949 action: "prev_buffer".to_string(),
2950 args: HashMap::new(),
2951 when: Some(context_keys::HAS_BUFFER.to_string()),
2952 checkbox: None,
2953 },
2954 MenuItem::Separator { separator: true },
2955 MenuItem::Action {
2956 label: t!("menu.go.command_palette").to_string(),
2957 action: "command_palette".to_string(),
2958 args: HashMap::new(),
2959 when: None,
2960 checkbox: None,
2961 },
2962 ],
2963 },
2964 Menu {
2966 id: Some("LSP".to_string()),
2967 label: t!("menu.lsp").to_string(),
2968 when: None,
2969 items: vec![
2970 MenuItem::Action {
2971 label: t!("menu.lsp.show_hover").to_string(),
2972 action: "lsp_hover".to_string(),
2973 args: HashMap::new(),
2974 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2975 checkbox: None,
2976 },
2977 MenuItem::Action {
2978 label: t!("menu.lsp.goto_definition").to_string(),
2979 action: "lsp_goto_definition".to_string(),
2980 args: HashMap::new(),
2981 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2982 checkbox: None,
2983 },
2984 MenuItem::Action {
2985 label: t!("menu.lsp.find_references").to_string(),
2986 action: "lsp_references".to_string(),
2987 args: HashMap::new(),
2988 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2989 checkbox: None,
2990 },
2991 MenuItem::Action {
2992 label: t!("menu.lsp.rename_symbol").to_string(),
2993 action: "lsp_rename".to_string(),
2994 args: HashMap::new(),
2995 when: Some(context_keys::LSP_AVAILABLE.to_string()),
2996 checkbox: None,
2997 },
2998 MenuItem::Separator { separator: true },
2999 MenuItem::Action {
3000 label: t!("menu.lsp.show_completions").to_string(),
3001 action: "lsp_completion".to_string(),
3002 args: HashMap::new(),
3003 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3004 checkbox: None,
3005 },
3006 MenuItem::Action {
3007 label: t!("menu.lsp.show_signature").to_string(),
3008 action: "lsp_signature_help".to_string(),
3009 args: HashMap::new(),
3010 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3011 checkbox: None,
3012 },
3013 MenuItem::Action {
3014 label: t!("menu.lsp.code_actions").to_string(),
3015 action: "lsp_code_actions".to_string(),
3016 args: HashMap::new(),
3017 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3018 checkbox: None,
3019 },
3020 MenuItem::Separator { separator: true },
3021 MenuItem::Action {
3022 label: t!("menu.lsp.toggle_inlay_hints").to_string(),
3023 action: "toggle_inlay_hints".to_string(),
3024 args: HashMap::new(),
3025 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3026 checkbox: Some(context_keys::INLAY_HINTS.to_string()),
3027 },
3028 MenuItem::Action {
3029 label: t!("menu.lsp.toggle_mouse_hover").to_string(),
3030 action: "toggle_mouse_hover".to_string(),
3031 args: HashMap::new(),
3032 when: None,
3033 checkbox: Some(context_keys::MOUSE_HOVER.to_string()),
3034 },
3035 MenuItem::Separator { separator: true },
3036 MenuItem::Action {
3037 label: t!("menu.lsp.show_status").to_string(),
3038 action: "show_lsp_status".to_string(),
3039 args: HashMap::new(),
3040 when: None,
3041 checkbox: None,
3042 },
3043 MenuItem::Action {
3044 label: t!("menu.lsp.restart_server").to_string(),
3045 action: "lsp_restart".to_string(),
3046 args: HashMap::new(),
3047 when: None,
3048 checkbox: None,
3049 },
3050 MenuItem::Action {
3051 label: t!("menu.lsp.stop_server").to_string(),
3052 action: "lsp_stop".to_string(),
3053 args: HashMap::new(),
3054 when: None,
3055 checkbox: None,
3056 },
3057 MenuItem::Separator { separator: true },
3058 MenuItem::Action {
3059 label: t!("menu.lsp.toggle_for_buffer").to_string(),
3060 action: "lsp_toggle_for_buffer".to_string(),
3061 args: HashMap::new(),
3062 when: Some(context_keys::HAS_BUFFER.to_string()),
3063 checkbox: None,
3064 },
3065 ],
3066 },
3067 Menu {
3069 id: Some("Explorer".to_string()),
3070 label: t!("menu.explorer").to_string(),
3071 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3072 items: vec![
3073 MenuItem::Action {
3074 label: t!("menu.explorer.new_file").to_string(),
3075 action: "file_explorer_new_file".to_string(),
3076 args: HashMap::new(),
3077 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3078 checkbox: None,
3079 },
3080 MenuItem::Action {
3081 label: t!("menu.explorer.new_folder").to_string(),
3082 action: "file_explorer_new_directory".to_string(),
3083 args: HashMap::new(),
3084 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3085 checkbox: None,
3086 },
3087 MenuItem::Separator { separator: true },
3088 MenuItem::Action {
3089 label: t!("menu.explorer.open").to_string(),
3090 action: "file_explorer_open".to_string(),
3091 args: HashMap::new(),
3092 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3093 checkbox: None,
3094 },
3095 MenuItem::Action {
3096 label: t!("menu.explorer.rename").to_string(),
3097 action: "file_explorer_rename".to_string(),
3098 args: HashMap::new(),
3099 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3100 checkbox: None,
3101 },
3102 MenuItem::Action {
3103 label: t!("menu.explorer.delete").to_string(),
3104 action: "file_explorer_delete".to_string(),
3105 args: HashMap::new(),
3106 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3107 checkbox: None,
3108 },
3109 MenuItem::Separator { separator: true },
3110 MenuItem::Action {
3111 label: t!("menu.explorer.cut").to_string(),
3112 action: "cut".to_string(),
3113 args: HashMap::new(),
3114 when: Some(context_keys::CAN_COPY.to_string()),
3115 checkbox: None,
3116 },
3117 MenuItem::Action {
3118 label: t!("menu.explorer.copy").to_string(),
3119 action: "copy".to_string(),
3120 args: HashMap::new(),
3121 when: Some(context_keys::CAN_COPY.to_string()),
3122 checkbox: None,
3123 },
3124 MenuItem::Action {
3125 label: t!("menu.explorer.paste").to_string(),
3126 action: "paste".to_string(),
3127 args: HashMap::new(),
3128 when: Some(context_keys::CAN_PASTE.to_string()),
3129 checkbox: None,
3130 },
3131 MenuItem::Separator { separator: true },
3132 MenuItem::Action {
3133 label: t!("menu.explorer.refresh").to_string(),
3134 action: "file_explorer_refresh".to_string(),
3135 args: HashMap::new(),
3136 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3137 checkbox: None,
3138 },
3139 MenuItem::Separator { separator: true },
3140 MenuItem::Action {
3141 label: t!("menu.explorer.show_hidden").to_string(),
3142 action: "file_explorer_toggle_hidden".to_string(),
3143 args: HashMap::new(),
3144 when: Some(context_keys::FILE_EXPLORER.to_string()),
3145 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_HIDDEN.to_string()),
3146 },
3147 MenuItem::Action {
3148 label: t!("menu.explorer.show_gitignored").to_string(),
3149 action: "file_explorer_toggle_gitignored".to_string(),
3150 args: HashMap::new(),
3151 when: Some(context_keys::FILE_EXPLORER.to_string()),
3152 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_GITIGNORED.to_string()),
3153 },
3154 ],
3155 },
3156 Menu {
3158 id: Some("Help".to_string()),
3159 label: t!("menu.help").to_string(),
3160 when: None,
3161 items: vec![
3162 MenuItem::Label {
3163 info: format!("Fresh v{}", env!("CARGO_PKG_VERSION")),
3164 },
3165 MenuItem::Separator { separator: true },
3166 MenuItem::Action {
3167 label: t!("menu.help.show_manual").to_string(),
3168 action: "show_help".to_string(),
3169 args: HashMap::new(),
3170 when: None,
3171 checkbox: None,
3172 },
3173 MenuItem::Action {
3174 label: t!("menu.help.keyboard_shortcuts").to_string(),
3175 action: "keyboard_shortcuts".to_string(),
3176 args: HashMap::new(),
3177 when: None,
3178 checkbox: None,
3179 },
3180 MenuItem::Separator { separator: true },
3181 MenuItem::Action {
3182 label: t!("menu.help.event_debug").to_string(),
3183 action: "event_debug".to_string(),
3184 args: HashMap::new(),
3185 when: None,
3186 checkbox: None,
3187 },
3188 ],
3189 },
3190 ]
3191 }
3192}
3193
3194impl Config {
3195 pub(crate) const FILENAME: &'static str = "config.json";
3197
3198 pub(crate) fn local_config_path(working_dir: &Path) -> std::path::PathBuf {
3200 working_dir.join(Self::FILENAME)
3201 }
3202
3203 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
3209 let contents = std::fs::read_to_string(path.as_ref())
3210 .map_err(|e| ConfigError::IoError(e.to_string()))?;
3211
3212 let partial: crate::partial_config::PartialConfig =
3214 serde_json::from_str(&contents).map_err(|e| ConfigError::ParseError(e.to_string()))?;
3215
3216 Ok(partial.resolve())
3217 }
3218
3219 fn load_builtin_keymap(name: &str) -> Option<KeymapConfig> {
3221 let json_content = match name {
3222 "default" => include_str!("../keymaps/default.json"),
3223 "emacs" => include_str!("../keymaps/emacs.json"),
3224 "vscode" => include_str!("../keymaps/vscode.json"),
3225 "macos" => include_str!("../keymaps/macos.json"),
3226 "macos-gui" => include_str!("../keymaps/macos-gui.json"),
3227 _ => return None,
3228 };
3229
3230 match serde_json::from_str(json_content) {
3231 Ok(config) => Some(config),
3232 Err(e) => {
3233 eprintln!("Failed to parse builtin keymap '{}': {}", name, e);
3234 None
3235 }
3236 }
3237 }
3238
3239 pub fn resolve_keymap(&self, map_name: &str) -> Vec<Keybinding> {
3242 let mut visited = std::collections::HashSet::new();
3243 self.resolve_keymap_recursive(map_name, &mut visited)
3244 }
3245
3246 fn resolve_keymap_recursive(
3248 &self,
3249 map_name: &str,
3250 visited: &mut std::collections::HashSet<String>,
3251 ) -> Vec<Keybinding> {
3252 if visited.contains(map_name) {
3254 eprintln!(
3255 "Warning: Circular inheritance detected in keymap '{}'",
3256 map_name
3257 );
3258 return Vec::new();
3259 }
3260 visited.insert(map_name.to_string());
3261
3262 let keymap = self
3264 .keybinding_maps
3265 .get(map_name)
3266 .cloned()
3267 .or_else(|| Self::load_builtin_keymap(map_name));
3268
3269 let Some(keymap) = keymap else {
3270 return Vec::new();
3271 };
3272
3273 let mut all_bindings = if let Some(ref parent_name) = keymap.inherits {
3275 self.resolve_keymap_recursive(parent_name, visited)
3276 } else {
3277 Vec::new()
3278 };
3279
3280 all_bindings.extend(keymap.bindings);
3282
3283 all_bindings
3284 }
3285 fn default_languages() -> HashMap<String, LanguageConfig> {
3287 let mut languages = HashMap::new();
3288
3289 languages.insert(
3290 "rust".to_string(),
3291 LanguageConfig {
3292 extensions: vec!["rs".to_string()],
3293 filenames: vec![],
3294 grammar: "rust".to_string(),
3295 comment_prefix: Some("//".to_string()),
3296 auto_indent: true,
3297 auto_close: None,
3298 auto_surround: None,
3299 textmate_grammar: None,
3300 show_whitespace_tabs: true,
3301 line_wrap: None,
3302 wrap_column: None,
3303 page_view: None,
3304 page_width: None,
3305 use_tabs: None,
3306 tab_size: None,
3307 formatter: Some(FormatterConfig {
3308 command: "rustfmt".to_string(),
3309 args: vec!["--edition".to_string(), "2021".to_string()],
3310 stdin: true,
3311 timeout_ms: 10000,
3312 }),
3313 format_on_save: false,
3314 on_save: vec![],
3315 word_characters: None,
3316 },
3317 );
3318
3319 languages.insert(
3320 "javascript".to_string(),
3321 LanguageConfig {
3322 extensions: vec!["js".to_string(), "jsx".to_string(), "mjs".to_string()],
3323 filenames: vec![],
3324 grammar: "javascript".to_string(),
3325 comment_prefix: Some("//".to_string()),
3326 auto_indent: true,
3327 auto_close: None,
3328 auto_surround: None,
3329 textmate_grammar: None,
3330 show_whitespace_tabs: true,
3331 line_wrap: None,
3332 wrap_column: None,
3333 page_view: None,
3334 page_width: None,
3335 use_tabs: None,
3336 tab_size: None,
3337 formatter: Some(FormatterConfig {
3338 command: "prettier".to_string(),
3339 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3340 stdin: true,
3341 timeout_ms: 10000,
3342 }),
3343 format_on_save: false,
3344 on_save: vec![],
3345 word_characters: None,
3346 },
3347 );
3348
3349 languages.insert(
3350 "typescript".to_string(),
3351 LanguageConfig {
3352 extensions: vec!["ts".to_string(), "tsx".to_string(), "mts".to_string()],
3353 filenames: vec![],
3354 grammar: "typescript".to_string(),
3355 comment_prefix: Some("//".to_string()),
3356 auto_indent: true,
3357 auto_close: None,
3358 auto_surround: None,
3359 textmate_grammar: None,
3360 show_whitespace_tabs: true,
3361 line_wrap: None,
3362 wrap_column: None,
3363 page_view: None,
3364 page_width: None,
3365 use_tabs: None,
3366 tab_size: None,
3367 formatter: Some(FormatterConfig {
3368 command: "prettier".to_string(),
3369 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3370 stdin: true,
3371 timeout_ms: 10000,
3372 }),
3373 format_on_save: false,
3374 on_save: vec![],
3375 word_characters: None,
3376 },
3377 );
3378
3379 languages.insert(
3380 "python".to_string(),
3381 LanguageConfig {
3382 extensions: vec!["py".to_string(), "pyi".to_string()],
3383 filenames: vec![],
3384 grammar: "python".to_string(),
3385 comment_prefix: Some("#".to_string()),
3386 auto_indent: true,
3387 auto_close: None,
3388 auto_surround: None,
3389 textmate_grammar: None,
3390 show_whitespace_tabs: true,
3391 line_wrap: None,
3392 wrap_column: None,
3393 page_view: None,
3394 page_width: None,
3395 use_tabs: None,
3396 tab_size: None,
3397 formatter: Some(FormatterConfig {
3398 command: "ruff".to_string(),
3399 args: vec![
3400 "format".to_string(),
3401 "--stdin-filename".to_string(),
3402 "$FILE".to_string(),
3403 ],
3404 stdin: true,
3405 timeout_ms: 10000,
3406 }),
3407 format_on_save: false,
3408 on_save: vec![],
3409 word_characters: None,
3410 },
3411 );
3412
3413 languages.insert(
3414 "c".to_string(),
3415 LanguageConfig {
3416 extensions: vec!["c".to_string(), "h".to_string()],
3417 filenames: vec![],
3418 grammar: "c".to_string(),
3419 comment_prefix: Some("//".to_string()),
3420 auto_indent: true,
3421 auto_close: None,
3422 auto_surround: None,
3423 textmate_grammar: None,
3424 show_whitespace_tabs: true,
3425 line_wrap: None,
3426 wrap_column: None,
3427 page_view: None,
3428 page_width: None,
3429 use_tabs: None,
3430 tab_size: None,
3431 formatter: Some(FormatterConfig {
3432 command: "clang-format".to_string(),
3433 args: vec![],
3434 stdin: true,
3435 timeout_ms: 10000,
3436 }),
3437 format_on_save: false,
3438 on_save: vec![],
3439 word_characters: None,
3440 },
3441 );
3442
3443 languages.insert(
3444 "cpp".to_string(),
3445 LanguageConfig {
3446 extensions: vec![
3447 "cpp".to_string(),
3448 "cc".to_string(),
3449 "cxx".to_string(),
3450 "hpp".to_string(),
3451 "hh".to_string(),
3452 "hxx".to_string(),
3453 ],
3454 filenames: vec![],
3455 grammar: "cpp".to_string(),
3456 comment_prefix: Some("//".to_string()),
3457 auto_indent: true,
3458 auto_close: None,
3459 auto_surround: None,
3460 textmate_grammar: None,
3461 show_whitespace_tabs: true,
3462 line_wrap: None,
3463 wrap_column: None,
3464 page_view: None,
3465 page_width: None,
3466 use_tabs: None,
3467 tab_size: None,
3468 formatter: Some(FormatterConfig {
3469 command: "clang-format".to_string(),
3470 args: vec![],
3471 stdin: true,
3472 timeout_ms: 10000,
3473 }),
3474 format_on_save: false,
3475 on_save: vec![],
3476 word_characters: None,
3477 },
3478 );
3479
3480 languages.insert(
3481 "csharp".to_string(),
3482 LanguageConfig {
3483 extensions: vec!["cs".to_string()],
3484 filenames: vec![],
3485 grammar: "C#".to_string(),
3486 comment_prefix: Some("//".to_string()),
3487 auto_indent: true,
3488 auto_close: None,
3489 auto_surround: None,
3490 textmate_grammar: None,
3491 show_whitespace_tabs: true,
3492 line_wrap: None,
3493 wrap_column: None,
3494 page_view: None,
3495 page_width: None,
3496 use_tabs: None,
3497 tab_size: None,
3498 formatter: None,
3499 format_on_save: false,
3500 on_save: vec![],
3501 word_characters: None,
3502 },
3503 );
3504
3505 languages.insert(
3506 "bash".to_string(),
3507 LanguageConfig {
3508 extensions: vec!["sh".to_string(), "bash".to_string()],
3509 filenames: vec![
3510 ".bash_aliases".to_string(),
3511 ".bash_logout".to_string(),
3512 ".bash_profile".to_string(),
3513 ".bashrc".to_string(),
3514 ".env".to_string(),
3515 ".profile".to_string(),
3516 ".zlogin".to_string(),
3517 ".zlogout".to_string(),
3518 ".zprofile".to_string(),
3519 ".zshenv".to_string(),
3520 ".zshrc".to_string(),
3521 "PKGBUILD".to_string(),
3523 "APKBUILD".to_string(),
3524 ],
3525 grammar: "bash".to_string(),
3526 comment_prefix: Some("#".to_string()),
3527 auto_indent: true,
3528 auto_close: None,
3529 auto_surround: None,
3530 textmate_grammar: None,
3531 show_whitespace_tabs: true,
3532 line_wrap: None,
3533 wrap_column: None,
3534 page_view: None,
3535 page_width: None,
3536 use_tabs: None,
3537 tab_size: None,
3538 formatter: None,
3539 format_on_save: false,
3540 on_save: vec![],
3541 word_characters: None,
3542 },
3543 );
3544
3545 languages.insert(
3546 "makefile".to_string(),
3547 LanguageConfig {
3548 extensions: vec!["mk".to_string()],
3549 filenames: vec![
3550 "Makefile".to_string(),
3551 "makefile".to_string(),
3552 "GNUmakefile".to_string(),
3553 ],
3554 grammar: "Makefile".to_string(),
3555 comment_prefix: Some("#".to_string()),
3556 auto_indent: false,
3557 auto_close: None,
3558 auto_surround: None,
3559 textmate_grammar: None,
3560 show_whitespace_tabs: true,
3561 line_wrap: None,
3562 wrap_column: None,
3563 page_view: None,
3564 page_width: None,
3565 use_tabs: Some(true), tab_size: Some(8), formatter: None,
3568 format_on_save: false,
3569 on_save: vec![],
3570 word_characters: None,
3571 },
3572 );
3573
3574 languages.insert(
3575 "dockerfile".to_string(),
3576 LanguageConfig {
3577 extensions: vec!["dockerfile".to_string()],
3578 filenames: vec!["Dockerfile".to_string(), "Containerfile".to_string()],
3579 grammar: "dockerfile".to_string(),
3580 comment_prefix: Some("#".to_string()),
3581 auto_indent: true,
3582 auto_close: None,
3583 auto_surround: None,
3584 textmate_grammar: None,
3585 show_whitespace_tabs: true,
3586 line_wrap: None,
3587 wrap_column: None,
3588 page_view: None,
3589 page_width: None,
3590 use_tabs: None,
3591 tab_size: None,
3592 formatter: None,
3593 format_on_save: false,
3594 on_save: vec![],
3595 word_characters: None,
3596 },
3597 );
3598
3599 languages.insert(
3600 "json".to_string(),
3601 LanguageConfig {
3602 extensions: vec!["json".to_string()],
3603 filenames: vec![],
3604 grammar: "json".to_string(),
3605 comment_prefix: None,
3606 auto_indent: true,
3607 auto_close: None,
3608 auto_surround: None,
3609 textmate_grammar: None,
3610 show_whitespace_tabs: true,
3611 line_wrap: None,
3612 wrap_column: None,
3613 page_view: None,
3614 page_width: None,
3615 use_tabs: None,
3616 tab_size: None,
3617 formatter: Some(FormatterConfig {
3618 command: "prettier".to_string(),
3619 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3620 stdin: true,
3621 timeout_ms: 10000,
3622 }),
3623 format_on_save: false,
3624 on_save: vec![],
3625 word_characters: None,
3626 },
3627 );
3628
3629 languages.insert(
3636 "jsonc".to_string(),
3637 LanguageConfig {
3638 extensions: vec!["jsonc".to_string()],
3639 filenames: vec![
3640 "devcontainer.json".to_string(),
3641 ".devcontainer.json".to_string(),
3642 "tsconfig.json".to_string(),
3643 "tsconfig.*.json".to_string(),
3644 "jsconfig.json".to_string(),
3645 "jsconfig.*.json".to_string(),
3646 ".eslintrc.json".to_string(),
3647 ".babelrc".to_string(),
3648 ".babelrc.json".to_string(),
3649 ".swcrc".to_string(),
3650 ".jshintrc".to_string(),
3651 ".hintrc".to_string(),
3652 "settings.json".to_string(),
3653 "keybindings.json".to_string(),
3654 "tasks.json".to_string(),
3655 "launch.json".to_string(),
3656 "extensions.json".to_string(),
3657 "argv.json".to_string(),
3658 ],
3659 grammar: "jsonc".to_string(),
3660 comment_prefix: Some("//".to_string()),
3661 auto_indent: true,
3662 auto_close: None,
3663 auto_surround: None,
3664 textmate_grammar: None,
3665 show_whitespace_tabs: true,
3666 line_wrap: None,
3667 wrap_column: None,
3668 page_view: None,
3669 page_width: None,
3670 use_tabs: None,
3671 tab_size: None,
3672 formatter: Some(FormatterConfig {
3673 command: "prettier".to_string(),
3674 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3675 stdin: true,
3676 timeout_ms: 10000,
3677 }),
3678 format_on_save: false,
3679 on_save: vec![],
3680 word_characters: None,
3681 },
3682 );
3683
3684 languages.insert(
3685 "toml".to_string(),
3686 LanguageConfig {
3687 extensions: vec!["toml".to_string()],
3688 filenames: vec!["Cargo.lock".to_string()],
3689 grammar: "toml".to_string(),
3690 comment_prefix: Some("#".to_string()),
3691 auto_indent: true,
3692 auto_close: None,
3693 auto_surround: None,
3694 textmate_grammar: None,
3695 show_whitespace_tabs: true,
3696 line_wrap: None,
3697 wrap_column: None,
3698 page_view: None,
3699 page_width: None,
3700 use_tabs: None,
3701 tab_size: None,
3702 formatter: None,
3703 format_on_save: false,
3704 on_save: vec![],
3705 word_characters: None,
3706 },
3707 );
3708
3709 languages.insert(
3710 "yaml".to_string(),
3711 LanguageConfig {
3712 extensions: vec!["yml".to_string(), "yaml".to_string()],
3713 filenames: vec![],
3714 grammar: "yaml".to_string(),
3715 comment_prefix: Some("#".to_string()),
3716 auto_indent: true,
3717 auto_close: None,
3718 auto_surround: None,
3719 textmate_grammar: None,
3720 show_whitespace_tabs: true,
3721 line_wrap: None,
3722 wrap_column: None,
3723 page_view: None,
3724 page_width: None,
3725 use_tabs: None,
3726 tab_size: None,
3727 formatter: Some(FormatterConfig {
3728 command: "prettier".to_string(),
3729 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3730 stdin: true,
3731 timeout_ms: 10000,
3732 }),
3733 format_on_save: false,
3734 on_save: vec![],
3735 word_characters: None,
3736 },
3737 );
3738
3739 languages.insert(
3740 "markdown".to_string(),
3741 LanguageConfig {
3742 extensions: vec!["md".to_string(), "markdown".to_string()],
3743 filenames: vec!["README".to_string()],
3744 grammar: "markdown".to_string(),
3745 comment_prefix: None,
3746 auto_indent: false,
3747 auto_close: None,
3748 auto_surround: None,
3749 textmate_grammar: None,
3750 show_whitespace_tabs: true,
3751 line_wrap: None,
3752 wrap_column: None,
3753 page_view: None,
3754 page_width: None,
3755 use_tabs: None,
3756 tab_size: None,
3757 formatter: None,
3758 format_on_save: false,
3759 on_save: vec![],
3760 word_characters: None,
3761 },
3762 );
3763
3764 languages.insert(
3766 "go".to_string(),
3767 LanguageConfig {
3768 extensions: vec!["go".to_string()],
3769 filenames: vec![],
3770 grammar: "go".to_string(),
3771 comment_prefix: Some("//".to_string()),
3772 auto_indent: true,
3773 auto_close: None,
3774 auto_surround: None,
3775 textmate_grammar: None,
3776 show_whitespace_tabs: false,
3777 line_wrap: None,
3778 wrap_column: None,
3779 page_view: None,
3780 page_width: None,
3781 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
3784 command: "gofmt".to_string(),
3785 args: vec![],
3786 stdin: true,
3787 timeout_ms: 10000,
3788 }),
3789 format_on_save: false,
3790 on_save: vec![],
3791 word_characters: None,
3792 },
3793 );
3794
3795 languages.insert(
3796 "odin".to_string(),
3797 LanguageConfig {
3798 extensions: vec!["odin".to_string()],
3799 filenames: vec![],
3800 grammar: "odin".to_string(),
3801 comment_prefix: Some("//".to_string()),
3802 auto_indent: true,
3803 auto_close: None,
3804 auto_surround: None,
3805 textmate_grammar: None,
3806 show_whitespace_tabs: false,
3807 line_wrap: None,
3808 wrap_column: None,
3809 page_view: None,
3810 page_width: None,
3811 use_tabs: Some(true),
3812 tab_size: Some(8),
3813 formatter: None,
3814 format_on_save: false,
3815 on_save: vec![],
3816 word_characters: None,
3817 },
3818 );
3819
3820 languages.insert(
3821 "zig".to_string(),
3822 LanguageConfig {
3823 extensions: vec!["zig".to_string(), "zon".to_string()],
3824 filenames: vec![],
3825 grammar: "zig".to_string(),
3826 comment_prefix: Some("//".to_string()),
3827 auto_indent: true,
3828 auto_close: None,
3829 auto_surround: None,
3830 textmate_grammar: None,
3831 show_whitespace_tabs: true,
3832 line_wrap: None,
3833 wrap_column: None,
3834 page_view: None,
3835 page_width: None,
3836 use_tabs: None,
3837 tab_size: None,
3838 formatter: None,
3839 format_on_save: false,
3840 on_save: vec![],
3841 word_characters: None,
3842 },
3843 );
3844
3845 languages.insert(
3846 "java".to_string(),
3847 LanguageConfig {
3848 extensions: vec!["java".to_string()],
3849 filenames: vec![],
3850 grammar: "java".to_string(),
3851 comment_prefix: Some("//".to_string()),
3852 auto_indent: true,
3853 auto_close: None,
3854 auto_surround: None,
3855 textmate_grammar: None,
3856 show_whitespace_tabs: true,
3857 line_wrap: None,
3858 wrap_column: None,
3859 page_view: None,
3860 page_width: None,
3861 use_tabs: None,
3862 tab_size: None,
3863 formatter: None,
3864 format_on_save: false,
3865 on_save: vec![],
3866 word_characters: None,
3867 },
3868 );
3869
3870 languages.insert(
3871 "latex".to_string(),
3872 LanguageConfig {
3873 extensions: vec![
3874 "tex".to_string(),
3875 "latex".to_string(),
3876 "ltx".to_string(),
3877 "sty".to_string(),
3878 "cls".to_string(),
3879 "bib".to_string(),
3880 ],
3881 filenames: vec![],
3882 grammar: "latex".to_string(),
3883 comment_prefix: Some("%".to_string()),
3884 auto_indent: true,
3885 auto_close: None,
3886 auto_surround: None,
3887 textmate_grammar: None,
3888 show_whitespace_tabs: true,
3889 line_wrap: None,
3890 wrap_column: None,
3891 page_view: None,
3892 page_width: None,
3893 use_tabs: None,
3894 tab_size: None,
3895 formatter: None,
3896 format_on_save: false,
3897 on_save: vec![],
3898 word_characters: None,
3899 },
3900 );
3901
3902 languages.insert(
3903 "templ".to_string(),
3904 LanguageConfig {
3905 extensions: vec!["templ".to_string()],
3906 filenames: vec![],
3907 grammar: "go".to_string(), comment_prefix: Some("//".to_string()),
3909 auto_indent: true,
3910 auto_close: None,
3911 auto_surround: None,
3912 textmate_grammar: None,
3913 show_whitespace_tabs: true,
3914 line_wrap: None,
3915 wrap_column: None,
3916 page_view: None,
3917 page_width: None,
3918 use_tabs: None,
3919 tab_size: None,
3920 formatter: None,
3921 format_on_save: false,
3922 on_save: vec![],
3923 word_characters: None,
3924 },
3925 );
3926
3927 languages.insert(
3929 "git-rebase".to_string(),
3930 LanguageConfig {
3931 extensions: vec![],
3932 filenames: vec!["git-rebase-todo".to_string()],
3933 grammar: "Git Rebase Todo".to_string(),
3934 comment_prefix: Some("#".to_string()),
3935 auto_indent: false,
3936 auto_close: None,
3937 auto_surround: None,
3938 textmate_grammar: None,
3939 show_whitespace_tabs: true,
3940 line_wrap: None,
3941 wrap_column: None,
3942 page_view: None,
3943 page_width: None,
3944 use_tabs: None,
3945 tab_size: None,
3946 formatter: None,
3947 format_on_save: false,
3948 on_save: vec![],
3949 word_characters: None,
3950 },
3951 );
3952
3953 languages.insert(
3954 "git-commit".to_string(),
3955 LanguageConfig {
3956 extensions: vec![],
3957 filenames: vec![
3958 "COMMIT_EDITMSG".to_string(),
3959 "MERGE_MSG".to_string(),
3960 "SQUASH_MSG".to_string(),
3961 "TAG_EDITMSG".to_string(),
3962 ],
3963 grammar: "Git Commit Message".to_string(),
3964 comment_prefix: Some("#".to_string()),
3965 auto_indent: false,
3966 auto_close: None,
3967 auto_surround: None,
3968 textmate_grammar: None,
3969 show_whitespace_tabs: true,
3970 line_wrap: None,
3971 wrap_column: None,
3972 page_view: None,
3973 page_width: None,
3974 use_tabs: None,
3975 tab_size: None,
3976 formatter: None,
3977 format_on_save: false,
3978 on_save: vec![],
3979 word_characters: None,
3980 },
3981 );
3982
3983 languages.insert(
3984 "gitignore".to_string(),
3985 LanguageConfig {
3986 extensions: vec!["gitignore".to_string()],
3987 filenames: vec![
3988 ".gitignore".to_string(),
3989 ".dockerignore".to_string(),
3990 ".npmignore".to_string(),
3991 ".hgignore".to_string(),
3992 ],
3993 grammar: "Gitignore".to_string(),
3994 comment_prefix: Some("#".to_string()),
3995 auto_indent: false,
3996 auto_close: None,
3997 auto_surround: None,
3998 textmate_grammar: None,
3999 show_whitespace_tabs: true,
4000 line_wrap: None,
4001 wrap_column: None,
4002 page_view: None,
4003 page_width: None,
4004 use_tabs: None,
4005 tab_size: None,
4006 formatter: None,
4007 format_on_save: false,
4008 on_save: vec![],
4009 word_characters: None,
4010 },
4011 );
4012
4013 languages.insert(
4014 "gitconfig".to_string(),
4015 LanguageConfig {
4016 extensions: vec!["gitconfig".to_string()],
4017 filenames: vec![".gitconfig".to_string(), ".gitmodules".to_string()],
4018 grammar: "Git Config".to_string(),
4019 comment_prefix: Some("#".to_string()),
4020 auto_indent: true,
4021 auto_close: None,
4022 auto_surround: None,
4023 textmate_grammar: None,
4024 show_whitespace_tabs: true,
4025 line_wrap: None,
4026 wrap_column: None,
4027 page_view: None,
4028 page_width: None,
4029 use_tabs: None,
4030 tab_size: None,
4031 formatter: None,
4032 format_on_save: false,
4033 on_save: vec![],
4034 word_characters: None,
4035 },
4036 );
4037
4038 languages.insert(
4039 "gitattributes".to_string(),
4040 LanguageConfig {
4041 extensions: vec!["gitattributes".to_string()],
4042 filenames: vec![".gitattributes".to_string()],
4043 grammar: "Git Attributes".to_string(),
4044 comment_prefix: Some("#".to_string()),
4045 auto_indent: false,
4046 auto_close: None,
4047 auto_surround: None,
4048 textmate_grammar: None,
4049 show_whitespace_tabs: true,
4050 line_wrap: None,
4051 wrap_column: None,
4052 page_view: None,
4053 page_width: None,
4054 use_tabs: None,
4055 tab_size: None,
4056 formatter: None,
4057 format_on_save: false,
4058 on_save: vec![],
4059 word_characters: None,
4060 },
4061 );
4062
4063 languages.insert(
4064 "typst".to_string(),
4065 LanguageConfig {
4066 extensions: vec!["typ".to_string()],
4067 filenames: vec![],
4068 grammar: "Typst".to_string(),
4069 comment_prefix: Some("//".to_string()),
4070 auto_indent: true,
4071 auto_close: None,
4072 auto_surround: None,
4073 textmate_grammar: None,
4074 show_whitespace_tabs: true,
4075 line_wrap: None,
4076 wrap_column: None,
4077 page_view: None,
4078 page_width: None,
4079 use_tabs: None,
4080 tab_size: None,
4081 formatter: None,
4082 format_on_save: false,
4083 on_save: vec![],
4084 word_characters: None,
4085 },
4086 );
4087
4088 languages.insert(
4093 "kotlin".to_string(),
4094 LanguageConfig {
4095 extensions: vec!["kt".to_string(), "kts".to_string()],
4096 filenames: vec![],
4097 grammar: "Kotlin".to_string(),
4098 comment_prefix: Some("//".to_string()),
4099 auto_indent: true,
4100 auto_close: None,
4101 auto_surround: None,
4102 textmate_grammar: None,
4103 show_whitespace_tabs: true,
4104 line_wrap: None,
4105 wrap_column: None,
4106 page_view: None,
4107 page_width: None,
4108 use_tabs: None,
4109 tab_size: None,
4110 formatter: None,
4111 format_on_save: false,
4112 on_save: vec![],
4113 word_characters: None,
4114 },
4115 );
4116
4117 languages.insert(
4118 "swift".to_string(),
4119 LanguageConfig {
4120 extensions: vec!["swift".to_string()],
4121 filenames: vec![],
4122 grammar: "Swift".to_string(),
4123 comment_prefix: Some("//".to_string()),
4124 auto_indent: true,
4125 auto_close: None,
4126 auto_surround: None,
4127 textmate_grammar: None,
4128 show_whitespace_tabs: true,
4129 line_wrap: None,
4130 wrap_column: None,
4131 page_view: None,
4132 page_width: None,
4133 use_tabs: None,
4134 tab_size: None,
4135 formatter: None,
4136 format_on_save: false,
4137 on_save: vec![],
4138 word_characters: None,
4139 },
4140 );
4141
4142 languages.insert(
4143 "scala".to_string(),
4144 LanguageConfig {
4145 extensions: vec!["scala".to_string(), "sc".to_string()],
4146 filenames: vec![],
4147 grammar: "Scala".to_string(),
4148 comment_prefix: Some("//".to_string()),
4149 auto_indent: true,
4150 auto_close: None,
4151 auto_surround: None,
4152 textmate_grammar: None,
4153 show_whitespace_tabs: true,
4154 line_wrap: None,
4155 wrap_column: None,
4156 page_view: None,
4157 page_width: None,
4158 use_tabs: None,
4159 tab_size: None,
4160 formatter: None,
4161 format_on_save: false,
4162 on_save: vec![],
4163 word_characters: None,
4164 },
4165 );
4166
4167 languages.insert(
4168 "dart".to_string(),
4169 LanguageConfig {
4170 extensions: vec!["dart".to_string()],
4171 filenames: vec![],
4172 grammar: "Dart".to_string(),
4173 comment_prefix: Some("//".to_string()),
4174 auto_indent: true,
4175 auto_close: None,
4176 auto_surround: None,
4177 textmate_grammar: None,
4178 show_whitespace_tabs: true,
4179 line_wrap: None,
4180 wrap_column: None,
4181 page_view: None,
4182 page_width: None,
4183 use_tabs: None,
4184 tab_size: None,
4185 formatter: None,
4186 format_on_save: false,
4187 on_save: vec![],
4188 word_characters: None,
4189 },
4190 );
4191
4192 languages.insert(
4193 "elixir".to_string(),
4194 LanguageConfig {
4195 extensions: vec!["ex".to_string(), "exs".to_string()],
4196 filenames: vec![],
4197 grammar: "Elixir".to_string(),
4198 comment_prefix: Some("#".to_string()),
4199 auto_indent: true,
4200 auto_close: None,
4201 auto_surround: None,
4202 textmate_grammar: None,
4203 show_whitespace_tabs: true,
4204 line_wrap: None,
4205 wrap_column: None,
4206 page_view: None,
4207 page_width: None,
4208 use_tabs: None,
4209 tab_size: None,
4210 formatter: None,
4211 format_on_save: false,
4212 on_save: vec![],
4213 word_characters: None,
4214 },
4215 );
4216
4217 languages.insert(
4218 "erlang".to_string(),
4219 LanguageConfig {
4220 extensions: vec!["erl".to_string(), "hrl".to_string()],
4221 filenames: vec![],
4222 grammar: "Erlang".to_string(),
4223 comment_prefix: Some("%".to_string()),
4224 auto_indent: true,
4225 auto_close: None,
4226 auto_surround: None,
4227 textmate_grammar: None,
4228 show_whitespace_tabs: true,
4229 line_wrap: None,
4230 wrap_column: None,
4231 page_view: None,
4232 page_width: None,
4233 use_tabs: None,
4234 tab_size: None,
4235 formatter: None,
4236 format_on_save: false,
4237 on_save: vec![],
4238 word_characters: None,
4239 },
4240 );
4241
4242 languages.insert(
4243 "haskell".to_string(),
4244 LanguageConfig {
4245 extensions: vec!["hs".to_string(), "lhs".to_string()],
4246 filenames: vec![],
4247 grammar: "Haskell".to_string(),
4248 comment_prefix: Some("--".to_string()),
4249 auto_indent: true,
4250 auto_close: None,
4251 auto_surround: None,
4252 textmate_grammar: None,
4253 show_whitespace_tabs: true,
4254 line_wrap: None,
4255 wrap_column: None,
4256 page_view: None,
4257 page_width: None,
4258 use_tabs: None,
4259 tab_size: None,
4260 formatter: None,
4261 format_on_save: false,
4262 on_save: vec![],
4263 word_characters: None,
4264 },
4265 );
4266
4267 languages.insert(
4268 "ocaml".to_string(),
4269 LanguageConfig {
4270 extensions: vec!["ml".to_string(), "mli".to_string()],
4271 filenames: vec![],
4272 grammar: "OCaml".to_string(),
4273 comment_prefix: None,
4274 auto_indent: true,
4275 auto_close: None,
4276 auto_surround: None,
4277 textmate_grammar: None,
4278 show_whitespace_tabs: true,
4279 line_wrap: None,
4280 wrap_column: None,
4281 page_view: None,
4282 page_width: None,
4283 use_tabs: None,
4284 tab_size: None,
4285 formatter: None,
4286 format_on_save: false,
4287 on_save: vec![],
4288 word_characters: None,
4289 },
4290 );
4291
4292 languages.insert(
4293 "clojure".to_string(),
4294 LanguageConfig {
4295 extensions: vec![
4296 "clj".to_string(),
4297 "cljs".to_string(),
4298 "cljc".to_string(),
4299 "edn".to_string(),
4300 ],
4301 filenames: vec![],
4302 grammar: "Clojure".to_string(),
4303 comment_prefix: Some(";".to_string()),
4304 auto_indent: true,
4305 auto_close: None,
4306 auto_surround: None,
4307 textmate_grammar: None,
4308 show_whitespace_tabs: true,
4309 line_wrap: None,
4310 wrap_column: None,
4311 page_view: None,
4312 page_width: None,
4313 use_tabs: None,
4314 tab_size: None,
4315 formatter: None,
4316 format_on_save: false,
4317 on_save: vec![],
4318 word_characters: None,
4319 },
4320 );
4321
4322 languages.insert(
4323 "r".to_string(),
4324 LanguageConfig {
4325 extensions: vec!["r".to_string(), "R".to_string(), "rmd".to_string()],
4326 filenames: vec![],
4327 grammar: "R".to_string(),
4328 comment_prefix: Some("#".to_string()),
4329 auto_indent: true,
4330 auto_close: None,
4331 auto_surround: None,
4332 textmate_grammar: None,
4333 show_whitespace_tabs: true,
4334 line_wrap: None,
4335 wrap_column: None,
4336 page_view: None,
4337 page_width: None,
4338 use_tabs: None,
4339 tab_size: None,
4340 formatter: None,
4341 format_on_save: false,
4342 on_save: vec![],
4343 word_characters: None,
4344 },
4345 );
4346
4347 languages.insert(
4348 "julia".to_string(),
4349 LanguageConfig {
4350 extensions: vec!["jl".to_string()],
4351 filenames: vec![],
4352 grammar: "Julia".to_string(),
4353 comment_prefix: Some("#".to_string()),
4354 auto_indent: true,
4355 auto_close: None,
4356 auto_surround: None,
4357 textmate_grammar: None,
4358 show_whitespace_tabs: true,
4359 line_wrap: None,
4360 wrap_column: None,
4361 page_view: None,
4362 page_width: None,
4363 use_tabs: None,
4364 tab_size: None,
4365 formatter: None,
4366 format_on_save: false,
4367 on_save: vec![],
4368 word_characters: None,
4369 },
4370 );
4371
4372 languages.insert(
4373 "perl".to_string(),
4374 LanguageConfig {
4375 extensions: vec!["pl".to_string(), "pm".to_string(), "t".to_string()],
4376 filenames: vec![],
4377 grammar: "Perl".to_string(),
4378 comment_prefix: Some("#".to_string()),
4379 auto_indent: true,
4380 auto_close: None,
4381 auto_surround: None,
4382 textmate_grammar: None,
4383 show_whitespace_tabs: true,
4384 line_wrap: None,
4385 wrap_column: None,
4386 page_view: None,
4387 page_width: None,
4388 use_tabs: None,
4389 tab_size: None,
4390 formatter: None,
4391 format_on_save: false,
4392 on_save: vec![],
4393 word_characters: None,
4394 },
4395 );
4396
4397 languages.insert(
4398 "nim".to_string(),
4399 LanguageConfig {
4400 extensions: vec!["nim".to_string(), "nims".to_string(), "nimble".to_string()],
4401 filenames: vec![],
4402 grammar: "Nim".to_string(),
4403 comment_prefix: Some("#".to_string()),
4404 auto_indent: true,
4405 auto_close: None,
4406 auto_surround: None,
4407 textmate_grammar: None,
4408 show_whitespace_tabs: true,
4409 line_wrap: None,
4410 wrap_column: None,
4411 page_view: None,
4412 page_width: None,
4413 use_tabs: None,
4414 tab_size: None,
4415 formatter: None,
4416 format_on_save: false,
4417 on_save: vec![],
4418 word_characters: None,
4419 },
4420 );
4421
4422 languages.insert(
4423 "gleam".to_string(),
4424 LanguageConfig {
4425 extensions: vec!["gleam".to_string()],
4426 filenames: vec![],
4427 grammar: "Gleam".to_string(),
4428 comment_prefix: Some("//".to_string()),
4429 auto_indent: true,
4430 auto_close: None,
4431 auto_surround: None,
4432 textmate_grammar: None,
4433 show_whitespace_tabs: true,
4434 line_wrap: None,
4435 wrap_column: None,
4436 page_view: None,
4437 page_width: None,
4438 use_tabs: None,
4439 tab_size: None,
4440 formatter: None,
4441 format_on_save: false,
4442 on_save: vec![],
4443 word_characters: None,
4444 },
4445 );
4446
4447 languages.insert(
4448 "racket".to_string(),
4449 LanguageConfig {
4450 extensions: vec![
4451 "rkt".to_string(),
4452 "rktd".to_string(),
4453 "rktl".to_string(),
4454 "scrbl".to_string(),
4455 ],
4456 filenames: vec![],
4457 grammar: "Racket".to_string(),
4458 comment_prefix: Some(";".to_string()),
4459 auto_indent: true,
4460 auto_close: None,
4461 auto_surround: None,
4462 textmate_grammar: None,
4463 show_whitespace_tabs: true,
4464 line_wrap: None,
4465 wrap_column: None,
4466 page_view: None,
4467 page_width: None,
4468 use_tabs: None,
4469 tab_size: None,
4470 formatter: None,
4471 format_on_save: false,
4472 on_save: vec![],
4473 word_characters: None,
4474 },
4475 );
4476
4477 languages.insert(
4478 "fsharp".to_string(),
4479 LanguageConfig {
4480 extensions: vec!["fs".to_string(), "fsi".to_string(), "fsx".to_string()],
4481 filenames: vec![],
4482 grammar: "FSharp".to_string(),
4483 comment_prefix: Some("//".to_string()),
4484 auto_indent: true,
4485 auto_close: None,
4486 auto_surround: None,
4487 textmate_grammar: None,
4488 show_whitespace_tabs: true,
4489 line_wrap: None,
4490 wrap_column: None,
4491 page_view: None,
4492 page_width: None,
4493 use_tabs: None,
4494 tab_size: None,
4495 formatter: None,
4496 format_on_save: false,
4497 on_save: vec![],
4498 word_characters: None,
4499 },
4500 );
4501
4502 languages.insert(
4503 "nix".to_string(),
4504 LanguageConfig {
4505 extensions: vec!["nix".to_string()],
4506 filenames: vec![],
4507 grammar: "Nix".to_string(),
4508 comment_prefix: Some("#".to_string()),
4509 auto_indent: true,
4510 auto_close: None,
4511 auto_surround: None,
4512 textmate_grammar: None,
4513 show_whitespace_tabs: true,
4514 line_wrap: None,
4515 wrap_column: None,
4516 page_view: None,
4517 page_width: None,
4518 use_tabs: None,
4519 tab_size: None,
4520 formatter: None,
4521 format_on_save: false,
4522 on_save: vec![],
4523 word_characters: None,
4524 },
4525 );
4526
4527 languages.insert(
4528 "nushell".to_string(),
4529 LanguageConfig {
4530 extensions: vec!["nu".to_string()],
4531 filenames: vec![],
4532 grammar: "Nushell".to_string(),
4533 comment_prefix: Some("#".to_string()),
4534 auto_indent: true,
4535 auto_close: None,
4536 auto_surround: None,
4537 textmate_grammar: None,
4538 show_whitespace_tabs: true,
4539 line_wrap: None,
4540 wrap_column: None,
4541 page_view: None,
4542 page_width: None,
4543 use_tabs: None,
4544 tab_size: None,
4545 formatter: None,
4546 format_on_save: false,
4547 on_save: vec![],
4548 word_characters: None,
4549 },
4550 );
4551
4552 languages.insert(
4553 "solidity".to_string(),
4554 LanguageConfig {
4555 extensions: vec!["sol".to_string()],
4556 filenames: vec![],
4557 grammar: "Solidity".to_string(),
4558 comment_prefix: Some("//".to_string()),
4559 auto_indent: true,
4560 auto_close: None,
4561 auto_surround: None,
4562 textmate_grammar: None,
4563 show_whitespace_tabs: true,
4564 line_wrap: None,
4565 wrap_column: None,
4566 page_view: None,
4567 page_width: None,
4568 use_tabs: None,
4569 tab_size: None,
4570 formatter: None,
4571 format_on_save: false,
4572 on_save: vec![],
4573 word_characters: None,
4574 },
4575 );
4576
4577 languages.insert(
4578 "ruby".to_string(),
4579 LanguageConfig {
4580 extensions: vec!["rb".to_string(), "rake".to_string(), "gemspec".to_string()],
4581 filenames: vec![
4582 "Gemfile".to_string(),
4583 "Rakefile".to_string(),
4584 "Guardfile".to_string(),
4585 ],
4586 grammar: "Ruby".to_string(),
4587 comment_prefix: Some("#".to_string()),
4588 auto_indent: true,
4589 auto_close: None,
4590 auto_surround: None,
4591 textmate_grammar: None,
4592 show_whitespace_tabs: true,
4593 line_wrap: None,
4594 wrap_column: None,
4595 page_view: None,
4596 page_width: None,
4597 use_tabs: None,
4598 tab_size: None,
4599 formatter: None,
4600 format_on_save: false,
4601 on_save: vec![],
4602 word_characters: None,
4603 },
4604 );
4605
4606 languages.insert(
4607 "php".to_string(),
4608 LanguageConfig {
4609 extensions: vec!["php".to_string(), "phtml".to_string()],
4610 filenames: vec![],
4611 grammar: "PHP".to_string(),
4612 comment_prefix: Some("//".to_string()),
4613 auto_indent: true,
4614 auto_close: None,
4615 auto_surround: None,
4616 textmate_grammar: None,
4617 show_whitespace_tabs: true,
4618 line_wrap: None,
4619 wrap_column: None,
4620 page_view: None,
4621 page_width: None,
4622 use_tabs: None,
4623 tab_size: None,
4624 formatter: None,
4625 format_on_save: false,
4626 on_save: vec![],
4627 word_characters: None,
4628 },
4629 );
4630
4631 languages.insert(
4632 "lua".to_string(),
4633 LanguageConfig {
4634 extensions: vec!["lua".to_string()],
4635 filenames: vec![],
4636 grammar: "Lua".to_string(),
4637 comment_prefix: Some("--".to_string()),
4638 auto_indent: true,
4639 auto_close: None,
4640 auto_surround: None,
4641 textmate_grammar: None,
4642 show_whitespace_tabs: true,
4643 line_wrap: None,
4644 wrap_column: None,
4645 page_view: None,
4646 page_width: None,
4647 use_tabs: None,
4648 tab_size: None,
4649 formatter: None,
4650 format_on_save: false,
4651 on_save: vec![],
4652 word_characters: None,
4653 },
4654 );
4655
4656 languages.insert(
4657 "html".to_string(),
4658 LanguageConfig {
4659 extensions: vec!["html".to_string(), "htm".to_string()],
4660 filenames: vec![],
4661 grammar: "HTML".to_string(),
4662 comment_prefix: None,
4663 auto_indent: true,
4664 auto_close: None,
4665 auto_surround: None,
4666 textmate_grammar: None,
4667 show_whitespace_tabs: true,
4668 line_wrap: None,
4669 wrap_column: None,
4670 page_view: None,
4671 page_width: None,
4672 use_tabs: None,
4673 tab_size: None,
4674 formatter: None,
4675 format_on_save: false,
4676 on_save: vec![],
4677 word_characters: None,
4678 },
4679 );
4680
4681 languages.insert(
4682 "css".to_string(),
4683 LanguageConfig {
4684 extensions: vec!["css".to_string()],
4685 filenames: vec![],
4686 grammar: "CSS".to_string(),
4687 comment_prefix: None,
4688 auto_indent: true,
4689 auto_close: None,
4690 auto_surround: None,
4691 textmate_grammar: None,
4692 show_whitespace_tabs: true,
4693 line_wrap: None,
4694 wrap_column: None,
4695 page_view: None,
4696 page_width: None,
4697 use_tabs: None,
4698 tab_size: None,
4699 formatter: None,
4700 format_on_save: false,
4701 on_save: vec![],
4702 word_characters: None,
4703 },
4704 );
4705
4706 languages.insert(
4707 "sql".to_string(),
4708 LanguageConfig {
4709 extensions: vec!["sql".to_string()],
4710 filenames: vec![],
4711 grammar: "SQL".to_string(),
4712 comment_prefix: Some("--".to_string()),
4713 auto_indent: true,
4714 auto_close: None,
4715 auto_surround: None,
4716 textmate_grammar: None,
4717 show_whitespace_tabs: true,
4718 line_wrap: None,
4719 wrap_column: None,
4720 page_view: None,
4721 page_width: None,
4722 use_tabs: None,
4723 tab_size: None,
4724 formatter: None,
4725 format_on_save: false,
4726 on_save: vec![],
4727 word_characters: None,
4728 },
4729 );
4730
4731 languages.insert(
4732 "graphql".to_string(),
4733 LanguageConfig {
4734 extensions: vec!["graphql".to_string(), "gql".to_string()],
4735 filenames: vec![],
4736 grammar: "GraphQL".to_string(),
4737 comment_prefix: Some("#".to_string()),
4738 auto_indent: true,
4739 auto_close: None,
4740 auto_surround: None,
4741 textmate_grammar: None,
4742 show_whitespace_tabs: true,
4743 line_wrap: None,
4744 wrap_column: None,
4745 page_view: None,
4746 page_width: None,
4747 use_tabs: None,
4748 tab_size: None,
4749 formatter: None,
4750 format_on_save: false,
4751 on_save: vec![],
4752 word_characters: None,
4753 },
4754 );
4755
4756 languages.insert(
4757 "protobuf".to_string(),
4758 LanguageConfig {
4759 extensions: vec!["proto".to_string()],
4760 filenames: vec![],
4761 grammar: "Protocol Buffers".to_string(),
4762 comment_prefix: Some("//".to_string()),
4763 auto_indent: true,
4764 auto_close: None,
4765 auto_surround: None,
4766 textmate_grammar: None,
4767 show_whitespace_tabs: true,
4768 line_wrap: None,
4769 wrap_column: None,
4770 page_view: None,
4771 page_width: None,
4772 use_tabs: None,
4773 tab_size: None,
4774 formatter: None,
4775 format_on_save: false,
4776 on_save: vec![],
4777 word_characters: None,
4778 },
4779 );
4780
4781 languages.insert(
4782 "cmake".to_string(),
4783 LanguageConfig {
4784 extensions: vec!["cmake".to_string()],
4785 filenames: vec!["CMakeLists.txt".to_string()],
4786 grammar: "CMake".to_string(),
4787 comment_prefix: Some("#".to_string()),
4788 auto_indent: true,
4789 auto_close: None,
4790 auto_surround: None,
4791 textmate_grammar: None,
4792 show_whitespace_tabs: true,
4793 line_wrap: None,
4794 wrap_column: None,
4795 page_view: None,
4796 page_width: None,
4797 use_tabs: None,
4798 tab_size: None,
4799 formatter: None,
4800 format_on_save: false,
4801 on_save: vec![],
4802 word_characters: None,
4803 },
4804 );
4805
4806 languages.insert(
4807 "terraform".to_string(),
4808 LanguageConfig {
4809 extensions: vec!["tf".to_string(), "tfvars".to_string(), "hcl".to_string()],
4810 filenames: vec![],
4811 grammar: "HCL".to_string(),
4812 comment_prefix: Some("#".to_string()),
4813 auto_indent: true,
4814 auto_close: None,
4815 auto_surround: None,
4816 textmate_grammar: None,
4817 show_whitespace_tabs: true,
4818 line_wrap: None,
4819 wrap_column: None,
4820 page_view: None,
4821 page_width: None,
4822 use_tabs: None,
4823 tab_size: None,
4824 formatter: None,
4825 format_on_save: false,
4826 on_save: vec![],
4827 word_characters: None,
4828 },
4829 );
4830
4831 languages.insert(
4832 "vue".to_string(),
4833 LanguageConfig {
4834 extensions: vec!["vue".to_string()],
4835 filenames: vec![],
4836 grammar: "Vue".to_string(),
4837 comment_prefix: None,
4838 auto_indent: true,
4839 auto_close: None,
4840 auto_surround: None,
4841 textmate_grammar: None,
4842 show_whitespace_tabs: true,
4843 line_wrap: None,
4844 wrap_column: None,
4845 page_view: None,
4846 page_width: None,
4847 use_tabs: None,
4848 tab_size: None,
4849 formatter: None,
4850 format_on_save: false,
4851 on_save: vec![],
4852 word_characters: None,
4853 },
4854 );
4855
4856 languages.insert(
4857 "svelte".to_string(),
4858 LanguageConfig {
4859 extensions: vec!["svelte".to_string()],
4860 filenames: vec![],
4861 grammar: "Svelte".to_string(),
4862 comment_prefix: None,
4863 auto_indent: true,
4864 auto_close: None,
4865 auto_surround: None,
4866 textmate_grammar: None,
4867 show_whitespace_tabs: true,
4868 line_wrap: None,
4869 wrap_column: None,
4870 page_view: None,
4871 page_width: None,
4872 use_tabs: None,
4873 tab_size: None,
4874 formatter: None,
4875 format_on_save: false,
4876 on_save: vec![],
4877 word_characters: None,
4878 },
4879 );
4880
4881 languages.insert(
4882 "astro".to_string(),
4883 LanguageConfig {
4884 extensions: vec!["astro".to_string()],
4885 filenames: vec![],
4886 grammar: "Astro".to_string(),
4887 comment_prefix: None,
4888 auto_indent: true,
4889 auto_close: None,
4890 auto_surround: None,
4891 textmate_grammar: None,
4892 show_whitespace_tabs: true,
4893 line_wrap: None,
4894 wrap_column: None,
4895 page_view: None,
4896 page_width: None,
4897 use_tabs: None,
4898 tab_size: None,
4899 formatter: None,
4900 format_on_save: false,
4901 on_save: vec![],
4902 word_characters: None,
4903 },
4904 );
4905
4906 languages.insert(
4909 "scss".to_string(),
4910 LanguageConfig {
4911 extensions: vec!["scss".to_string()],
4912 filenames: vec![],
4913 grammar: "SCSS".to_string(),
4914 comment_prefix: Some("//".to_string()),
4915 auto_indent: true,
4916 auto_close: None,
4917 auto_surround: None,
4918 textmate_grammar: None,
4919 show_whitespace_tabs: true,
4920 line_wrap: None,
4921 wrap_column: None,
4922 page_view: None,
4923 page_width: None,
4924 use_tabs: None,
4925 tab_size: None,
4926 formatter: None,
4927 format_on_save: false,
4928 on_save: vec![],
4929 word_characters: None,
4930 },
4931 );
4932
4933 languages.insert(
4934 "less".to_string(),
4935 LanguageConfig {
4936 extensions: vec!["less".to_string()],
4937 filenames: vec![],
4938 grammar: "LESS".to_string(),
4939 comment_prefix: Some("//".to_string()),
4940 auto_indent: true,
4941 auto_close: None,
4942 auto_surround: None,
4943 textmate_grammar: None,
4944 show_whitespace_tabs: true,
4945 line_wrap: None,
4946 wrap_column: None,
4947 page_view: None,
4948 page_width: None,
4949 use_tabs: None,
4950 tab_size: None,
4951 formatter: None,
4952 format_on_save: false,
4953 on_save: vec![],
4954 word_characters: None,
4955 },
4956 );
4957
4958 languages.insert(
4959 "powershell".to_string(),
4960 LanguageConfig {
4961 extensions: vec!["ps1".to_string(), "psm1".to_string(), "psd1".to_string()],
4962 filenames: vec![],
4963 grammar: "PowerShell".to_string(),
4964 comment_prefix: Some("#".to_string()),
4965 auto_indent: true,
4966 auto_close: None,
4967 auto_surround: None,
4968 textmate_grammar: None,
4969 show_whitespace_tabs: true,
4970 line_wrap: None,
4971 wrap_column: None,
4972 page_view: None,
4973 page_width: None,
4974 use_tabs: None,
4975 tab_size: None,
4976 formatter: None,
4977 format_on_save: false,
4978 on_save: vec![],
4979 word_characters: None,
4980 },
4981 );
4982
4983 languages.insert(
4984 "kdl".to_string(),
4985 LanguageConfig {
4986 extensions: vec!["kdl".to_string()],
4987 filenames: vec![],
4988 grammar: "KDL".to_string(),
4989 comment_prefix: Some("//".to_string()),
4990 auto_indent: true,
4991 auto_close: None,
4992 auto_surround: None,
4993 textmate_grammar: None,
4994 show_whitespace_tabs: true,
4995 line_wrap: None,
4996 wrap_column: None,
4997 page_view: None,
4998 page_width: None,
4999 use_tabs: None,
5000 tab_size: None,
5001 formatter: None,
5002 format_on_save: false,
5003 on_save: vec![],
5004 word_characters: None,
5005 },
5006 );
5007
5008 languages.insert(
5009 "starlark".to_string(),
5010 LanguageConfig {
5011 extensions: vec!["bzl".to_string(), "star".to_string()],
5012 filenames: vec!["BUILD".to_string(), "WORKSPACE".to_string()],
5013 grammar: "Starlark".to_string(),
5014 comment_prefix: Some("#".to_string()),
5015 auto_indent: true,
5016 auto_close: None,
5017 auto_surround: None,
5018 textmate_grammar: None,
5019 show_whitespace_tabs: true,
5020 line_wrap: None,
5021 wrap_column: None,
5022 page_view: None,
5023 page_width: None,
5024 use_tabs: None,
5025 tab_size: None,
5026 formatter: None,
5027 format_on_save: false,
5028 on_save: vec![],
5029 word_characters: None,
5030 },
5031 );
5032
5033 languages.insert(
5034 "justfile".to_string(),
5035 LanguageConfig {
5036 extensions: vec![],
5037 filenames: vec![
5038 "justfile".to_string(),
5039 "Justfile".to_string(),
5040 ".justfile".to_string(),
5041 ],
5042 grammar: "Justfile".to_string(),
5043 comment_prefix: Some("#".to_string()),
5044 auto_indent: true,
5045 auto_close: None,
5046 auto_surround: None,
5047 textmate_grammar: None,
5048 show_whitespace_tabs: true,
5049 line_wrap: None,
5050 wrap_column: None,
5051 page_view: None,
5052 page_width: None,
5053 use_tabs: Some(true),
5054 tab_size: None,
5055 formatter: None,
5056 format_on_save: false,
5057 on_save: vec![],
5058 word_characters: None,
5059 },
5060 );
5061
5062 languages.insert(
5063 "earthfile".to_string(),
5064 LanguageConfig {
5065 extensions: vec!["earth".to_string()],
5066 filenames: vec!["Earthfile".to_string()],
5067 grammar: "Earthfile".to_string(),
5068 comment_prefix: Some("#".to_string()),
5069 auto_indent: true,
5070 auto_close: None,
5071 auto_surround: None,
5072 textmate_grammar: None,
5073 show_whitespace_tabs: true,
5074 line_wrap: None,
5075 wrap_column: None,
5076 page_view: None,
5077 page_width: None,
5078 use_tabs: None,
5079 tab_size: None,
5080 formatter: None,
5081 format_on_save: false,
5082 on_save: vec![],
5083 word_characters: None,
5084 },
5085 );
5086
5087 languages.insert(
5088 "gomod".to_string(),
5089 LanguageConfig {
5090 extensions: vec![],
5091 filenames: vec!["go.mod".to_string(), "go.sum".to_string()],
5092 grammar: "Go Module".to_string(),
5093 comment_prefix: Some("//".to_string()),
5094 auto_indent: true,
5095 auto_close: None,
5096 auto_surround: None,
5097 textmate_grammar: None,
5098 show_whitespace_tabs: true,
5099 line_wrap: None,
5100 wrap_column: None,
5101 page_view: None,
5102 page_width: None,
5103 use_tabs: Some(true),
5104 tab_size: None,
5105 formatter: None,
5106 format_on_save: false,
5107 on_save: vec![],
5108 word_characters: None,
5109 },
5110 );
5111
5112 languages.insert(
5113 "vlang".to_string(),
5114 LanguageConfig {
5115 extensions: vec!["v".to_string(), "vv".to_string()],
5116 filenames: vec![],
5117 grammar: "V".to_string(),
5118 comment_prefix: Some("//".to_string()),
5119 auto_indent: true,
5120 auto_close: None,
5121 auto_surround: None,
5122 textmate_grammar: None,
5123 show_whitespace_tabs: true,
5124 line_wrap: None,
5125 wrap_column: None,
5126 page_view: None,
5127 page_width: None,
5128 use_tabs: None,
5129 tab_size: None,
5130 formatter: None,
5131 format_on_save: false,
5132 on_save: vec![],
5133 word_characters: None,
5134 },
5135 );
5136
5137 languages.insert(
5138 "ini".to_string(),
5139 LanguageConfig {
5140 extensions: vec!["ini".to_string(), "cfg".to_string()],
5141 filenames: vec![],
5142 grammar: "INI".to_string(),
5143 comment_prefix: Some(";".to_string()),
5144 auto_indent: false,
5145 auto_close: None,
5146 auto_surround: None,
5147 textmate_grammar: None,
5148 show_whitespace_tabs: true,
5149 line_wrap: None,
5150 wrap_column: None,
5151 page_view: None,
5152 page_width: None,
5153 use_tabs: None,
5154 tab_size: None,
5155 formatter: None,
5156 format_on_save: false,
5157 on_save: vec![],
5158 word_characters: None,
5159 },
5160 );
5161
5162 languages.insert(
5163 "hyprlang".to_string(),
5164 LanguageConfig {
5165 extensions: vec!["hl".to_string()],
5166 filenames: vec!["hyprland.conf".to_string()],
5167 grammar: "Hyprlang".to_string(),
5168 comment_prefix: Some("#".to_string()),
5169 auto_indent: true,
5170 auto_close: None,
5171 auto_surround: None,
5172 textmate_grammar: None,
5173 show_whitespace_tabs: true,
5174 line_wrap: None,
5175 wrap_column: None,
5176 page_view: None,
5177 page_width: None,
5178 use_tabs: None,
5179 tab_size: None,
5180 formatter: None,
5181 format_on_save: false,
5182 on_save: vec![],
5183 word_characters: None,
5184 },
5185 );
5186
5187 languages
5188 }
5189
5190 #[cfg(feature = "runtime")]
5192 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
5193 let mut lsp = HashMap::new();
5194
5195 let ra_log_path = crate::services::log_dirs::lsp_log_path("rust-analyzer")
5198 .to_string_lossy()
5199 .to_string();
5200
5201 Self::populate_lsp_config(&mut lsp, ra_log_path);
5202 lsp
5203 }
5204
5205 #[cfg(not(feature = "runtime"))]
5207 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
5208 HashMap::new()
5210 }
5211
5212 #[cfg(feature = "runtime")]
5214 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
5215 let mut universal = HashMap::new();
5216
5217 universal.insert(
5230 "quicklsp".to_string(),
5231 LspLanguageConfig::Multi(vec![LspServerConfig {
5232 command: "quicklsp".to_string(),
5233 args: vec![],
5234 enabled: false,
5235 auto_start: false,
5236 process_limits: ProcessLimits::default(),
5237 initialization_options: None,
5238 env: Default::default(),
5239 language_id_overrides: Default::default(),
5240 name: Some("QuickLSP".to_string()),
5241 only_features: None,
5242 except_features: None,
5243 root_markers: vec![
5244 "Cargo.toml".to_string(),
5245 "package.json".to_string(),
5246 "go.mod".to_string(),
5247 "pyproject.toml".to_string(),
5248 "requirements.txt".to_string(),
5249 ".git".to_string(),
5250 ],
5251 }]),
5252 );
5253
5254 universal
5255 }
5256
5257 #[cfg(not(feature = "runtime"))]
5259 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
5260 HashMap::new()
5261 }
5262
5263 #[cfg(feature = "runtime")]
5264 fn populate_lsp_config(lsp: &mut HashMap<String, LspLanguageConfig>, ra_log_path: String) {
5265 lsp.insert(
5269 "rust".to_string(),
5270 LspLanguageConfig::Multi(vec![LspServerConfig {
5271 command: "rust-analyzer".to_string(),
5272 args: vec!["--log-file".to_string(), ra_log_path],
5273 enabled: true,
5274 auto_start: false,
5275 process_limits: ProcessLimits::unlimited(),
5276 initialization_options: None,
5277 env: Default::default(),
5278 language_id_overrides: Default::default(),
5279 name: None,
5280 only_features: None,
5281 except_features: None,
5282 root_markers: vec![
5283 "Cargo.toml".to_string(),
5284 "rust-project.json".to_string(),
5285 ".git".to_string(),
5286 ],
5287 }]),
5288 );
5289
5290 lsp.insert(
5292 "python".to_string(),
5293 LspLanguageConfig::Multi(vec![LspServerConfig {
5294 command: "pylsp".to_string(),
5295 args: vec![],
5296 enabled: true,
5297 auto_start: false,
5298 process_limits: ProcessLimits::default(),
5299 initialization_options: None,
5300 env: Default::default(),
5301 language_id_overrides: Default::default(),
5302 name: None,
5303 only_features: None,
5304 except_features: None,
5305 root_markers: vec![
5306 "pyproject.toml".to_string(),
5307 "setup.py".to_string(),
5308 "setup.cfg".to_string(),
5309 "pyrightconfig.json".to_string(),
5310 ".git".to_string(),
5311 ],
5312 }]),
5313 );
5314
5315 lsp.insert(
5318 "javascript".to_string(),
5319 LspLanguageConfig::Multi(vec![LspServerConfig {
5320 command: "typescript-language-server".to_string(),
5321 args: vec!["--stdio".to_string()],
5322 enabled: true,
5323 auto_start: false,
5324 process_limits: ProcessLimits::default(),
5325 initialization_options: None,
5326 env: Default::default(),
5327 language_id_overrides: HashMap::from([(
5328 "jsx".to_string(),
5329 "javascriptreact".to_string(),
5330 )]),
5331 name: None,
5332 only_features: None,
5333 except_features: None,
5334 root_markers: vec![
5335 "tsconfig.json".to_string(),
5336 "jsconfig.json".to_string(),
5337 "package.json".to_string(),
5338 ".git".to_string(),
5339 ],
5340 }]),
5341 );
5342 lsp.insert(
5343 "typescript".to_string(),
5344 LspLanguageConfig::Multi(vec![LspServerConfig {
5345 command: "typescript-language-server".to_string(),
5346 args: vec!["--stdio".to_string()],
5347 enabled: true,
5348 auto_start: false,
5349 process_limits: ProcessLimits::default(),
5350 initialization_options: None,
5351 env: Default::default(),
5352 language_id_overrides: HashMap::from([(
5353 "tsx".to_string(),
5354 "typescriptreact".to_string(),
5355 )]),
5356 name: None,
5357 only_features: None,
5358 except_features: None,
5359 root_markers: vec![
5360 "tsconfig.json".to_string(),
5361 "jsconfig.json".to_string(),
5362 "package.json".to_string(),
5363 ".git".to_string(),
5364 ],
5365 }]),
5366 );
5367
5368 lsp.insert(
5370 "html".to_string(),
5371 LspLanguageConfig::Multi(vec![LspServerConfig {
5372 command: "vscode-html-language-server".to_string(),
5373 args: vec!["--stdio".to_string()],
5374 enabled: true,
5375 auto_start: false,
5376 process_limits: ProcessLimits::default(),
5377 initialization_options: None,
5378 env: Default::default(),
5379 language_id_overrides: Default::default(),
5380 name: None,
5381 only_features: None,
5382 except_features: None,
5383 root_markers: Default::default(),
5384 }]),
5385 );
5386
5387 lsp.insert(
5389 "css".to_string(),
5390 LspLanguageConfig::Multi(vec![LspServerConfig {
5391 command: "vscode-css-language-server".to_string(),
5392 args: vec!["--stdio".to_string()],
5393 enabled: true,
5394 auto_start: false,
5395 process_limits: ProcessLimits::default(),
5396 initialization_options: None,
5397 env: Default::default(),
5398 language_id_overrides: Default::default(),
5399 name: None,
5400 only_features: None,
5401 except_features: None,
5402 root_markers: Default::default(),
5403 }]),
5404 );
5405
5406 lsp.insert(
5408 "c".to_string(),
5409 LspLanguageConfig::Multi(vec![LspServerConfig {
5410 command: "clangd".to_string(),
5411 args: vec![],
5412 enabled: true,
5413 auto_start: false,
5414 process_limits: ProcessLimits::default(),
5415 initialization_options: None,
5416 env: Default::default(),
5417 language_id_overrides: Default::default(),
5418 name: None,
5419 only_features: None,
5420 except_features: None,
5421 root_markers: vec![
5422 "compile_commands.json".to_string(),
5423 "CMakeLists.txt".to_string(),
5424 "Makefile".to_string(),
5425 ".git".to_string(),
5426 ],
5427 }]),
5428 );
5429 lsp.insert(
5430 "cpp".to_string(),
5431 LspLanguageConfig::Multi(vec![LspServerConfig {
5432 command: "clangd".to_string(),
5433 args: vec![],
5434 enabled: true,
5435 auto_start: false,
5436 process_limits: ProcessLimits::default(),
5437 initialization_options: None,
5438 env: Default::default(),
5439 language_id_overrides: Default::default(),
5440 name: None,
5441 only_features: None,
5442 except_features: None,
5443 root_markers: vec![
5444 "compile_commands.json".to_string(),
5445 "CMakeLists.txt".to_string(),
5446 "Makefile".to_string(),
5447 ".git".to_string(),
5448 ],
5449 }]),
5450 );
5451
5452 lsp.insert(
5454 "go".to_string(),
5455 LspLanguageConfig::Multi(vec![LspServerConfig {
5456 command: "gopls".to_string(),
5457 args: vec![],
5458 enabled: true,
5459 auto_start: false,
5460 process_limits: ProcessLimits::default(),
5461 initialization_options: None,
5462 env: Default::default(),
5463 language_id_overrides: Default::default(),
5464 name: None,
5465 only_features: None,
5466 except_features: None,
5467 root_markers: vec![
5468 "go.mod".to_string(),
5469 "go.work".to_string(),
5470 ".git".to_string(),
5471 ],
5472 }]),
5473 );
5474
5475 lsp.insert(
5477 "json".to_string(),
5478 LspLanguageConfig::Multi(vec![LspServerConfig {
5479 command: "vscode-json-language-server".to_string(),
5480 args: vec!["--stdio".to_string()],
5481 enabled: true,
5482 auto_start: false,
5483 process_limits: ProcessLimits::default(),
5484 initialization_options: None,
5485 env: Default::default(),
5486 language_id_overrides: Default::default(),
5487 name: None,
5488 only_features: None,
5489 except_features: None,
5490 root_markers: Default::default(),
5491 }]),
5492 );
5493
5494 lsp.insert(
5498 "jsonc".to_string(),
5499 LspLanguageConfig::Multi(vec![LspServerConfig {
5500 command: "vscode-json-language-server".to_string(),
5501 args: vec!["--stdio".to_string()],
5502 enabled: true,
5503 auto_start: false,
5504 process_limits: ProcessLimits::default(),
5505 initialization_options: None,
5506 env: Default::default(),
5507 language_id_overrides: Default::default(),
5508 name: None,
5509 only_features: None,
5510 except_features: None,
5511 root_markers: Default::default(),
5512 }]),
5513 );
5514
5515 lsp.insert(
5517 "csharp".to_string(),
5518 LspLanguageConfig::Multi(vec![LspServerConfig {
5519 command: "csharp-ls".to_string(),
5520 args: vec![],
5521 enabled: true,
5522 auto_start: false,
5523 process_limits: ProcessLimits::default(),
5524 initialization_options: None,
5525 env: Default::default(),
5526 language_id_overrides: Default::default(),
5527 name: None,
5528 only_features: None,
5529 except_features: None,
5530 root_markers: vec![
5531 "*.csproj".to_string(),
5532 "*.sln".to_string(),
5533 ".git".to_string(),
5534 ],
5535 }]),
5536 );
5537
5538 lsp.insert(
5541 "odin".to_string(),
5542 LspLanguageConfig::Multi(vec![LspServerConfig {
5543 command: "ols".to_string(),
5544 args: vec![],
5545 enabled: true,
5546 auto_start: false,
5547 process_limits: ProcessLimits::default(),
5548 initialization_options: None,
5549 env: Default::default(),
5550 language_id_overrides: Default::default(),
5551 name: None,
5552 only_features: None,
5553 except_features: None,
5554 root_markers: Default::default(),
5555 }]),
5556 );
5557
5558 lsp.insert(
5561 "zig".to_string(),
5562 LspLanguageConfig::Multi(vec![LspServerConfig {
5563 command: "zls".to_string(),
5564 args: vec![],
5565 enabled: true,
5566 auto_start: false,
5567 process_limits: ProcessLimits::default(),
5568 initialization_options: None,
5569 env: Default::default(),
5570 language_id_overrides: Default::default(),
5571 name: None,
5572 only_features: None,
5573 except_features: None,
5574 root_markers: Default::default(),
5575 }]),
5576 );
5577
5578 lsp.insert(
5581 "java".to_string(),
5582 LspLanguageConfig::Multi(vec![LspServerConfig {
5583 command: "jdtls".to_string(),
5584 args: vec![],
5585 enabled: true,
5586 auto_start: false,
5587 process_limits: ProcessLimits::default(),
5588 initialization_options: None,
5589 env: Default::default(),
5590 language_id_overrides: Default::default(),
5591 name: None,
5592 only_features: None,
5593 except_features: None,
5594 root_markers: vec![
5595 "pom.xml".to_string(),
5596 "build.gradle".to_string(),
5597 "build.gradle.kts".to_string(),
5598 ".git".to_string(),
5599 ],
5600 }]),
5601 );
5602
5603 lsp.insert(
5606 "latex".to_string(),
5607 LspLanguageConfig::Multi(vec![LspServerConfig {
5608 command: "texlab".to_string(),
5609 args: vec![],
5610 enabled: true,
5611 auto_start: false,
5612 process_limits: ProcessLimits::default(),
5613 initialization_options: None,
5614 env: Default::default(),
5615 language_id_overrides: Default::default(),
5616 name: None,
5617 only_features: None,
5618 except_features: None,
5619 root_markers: Default::default(),
5620 }]),
5621 );
5622
5623 lsp.insert(
5626 "markdown".to_string(),
5627 LspLanguageConfig::Multi(vec![LspServerConfig {
5628 command: "marksman".to_string(),
5629 args: vec!["server".to_string()],
5630 enabled: true,
5631 auto_start: false,
5632 process_limits: ProcessLimits::default(),
5633 initialization_options: None,
5634 env: Default::default(),
5635 language_id_overrides: Default::default(),
5636 name: None,
5637 only_features: None,
5638 except_features: None,
5639 root_markers: Default::default(),
5640 }]),
5641 );
5642
5643 lsp.insert(
5646 "templ".to_string(),
5647 LspLanguageConfig::Multi(vec![LspServerConfig {
5648 command: "templ".to_string(),
5649 args: vec!["lsp".to_string()],
5650 enabled: true,
5651 auto_start: false,
5652 process_limits: ProcessLimits::default(),
5653 initialization_options: None,
5654 env: Default::default(),
5655 language_id_overrides: Default::default(),
5656 name: None,
5657 only_features: None,
5658 except_features: None,
5659 root_markers: Default::default(),
5660 }]),
5661 );
5662
5663 lsp.insert(
5666 "typst".to_string(),
5667 LspLanguageConfig::Multi(vec![LspServerConfig {
5668 command: "tinymist".to_string(),
5669 args: vec![],
5670 enabled: true,
5671 auto_start: false,
5672 process_limits: ProcessLimits::default(),
5673 initialization_options: None,
5674 env: Default::default(),
5675 language_id_overrides: Default::default(),
5676 name: None,
5677 only_features: None,
5678 except_features: None,
5679 root_markers: Default::default(),
5680 }]),
5681 );
5682
5683 lsp.insert(
5685 "bash".to_string(),
5686 LspLanguageConfig::Multi(vec![LspServerConfig {
5687 command: "bash-language-server".to_string(),
5688 args: vec!["start".to_string()],
5689 enabled: true,
5690 auto_start: false,
5691 process_limits: ProcessLimits::default(),
5692 initialization_options: None,
5693 env: Default::default(),
5694 language_id_overrides: Default::default(),
5695 name: None,
5696 only_features: None,
5697 except_features: None,
5698 root_markers: Default::default(),
5699 }]),
5700 );
5701
5702 lsp.insert(
5705 "lua".to_string(),
5706 LspLanguageConfig::Multi(vec![LspServerConfig {
5707 command: "lua-language-server".to_string(),
5708 args: vec![],
5709 enabled: true,
5710 auto_start: false,
5711 process_limits: ProcessLimits::default(),
5712 initialization_options: None,
5713 env: Default::default(),
5714 language_id_overrides: Default::default(),
5715 name: None,
5716 only_features: None,
5717 except_features: None,
5718 root_markers: vec![
5719 ".luarc.json".to_string(),
5720 ".luarc.jsonc".to_string(),
5721 ".luacheckrc".to_string(),
5722 ".stylua.toml".to_string(),
5723 ".git".to_string(),
5724 ],
5725 }]),
5726 );
5727
5728 lsp.insert(
5730 "ruby".to_string(),
5731 LspLanguageConfig::Multi(vec![LspServerConfig {
5732 command: "solargraph".to_string(),
5733 args: vec!["stdio".to_string()],
5734 enabled: true,
5735 auto_start: false,
5736 process_limits: ProcessLimits::default(),
5737 initialization_options: None,
5738 env: Default::default(),
5739 language_id_overrides: Default::default(),
5740 name: None,
5741 only_features: None,
5742 except_features: None,
5743 root_markers: vec![
5744 "Gemfile".to_string(),
5745 ".ruby-version".to_string(),
5746 ".git".to_string(),
5747 ],
5748 }]),
5749 );
5750
5751 lsp.insert(
5754 "php".to_string(),
5755 LspLanguageConfig::Multi(vec![LspServerConfig {
5756 command: "phpactor".to_string(),
5757 args: vec!["language-server".to_string()],
5758 enabled: true,
5759 auto_start: false,
5760 process_limits: ProcessLimits::default(),
5761 initialization_options: None,
5762 env: Default::default(),
5763 language_id_overrides: Default::default(),
5764 name: None,
5765 only_features: None,
5766 except_features: None,
5767 root_markers: vec!["composer.json".to_string(), ".git".to_string()],
5768 }]),
5769 );
5770
5771 lsp.insert(
5773 "yaml".to_string(),
5774 LspLanguageConfig::Multi(vec![LspServerConfig {
5775 command: "yaml-language-server".to_string(),
5776 args: vec!["--stdio".to_string()],
5777 enabled: true,
5778 auto_start: false,
5779 process_limits: ProcessLimits::default(),
5780 initialization_options: None,
5781 env: Default::default(),
5782 language_id_overrides: Default::default(),
5783 name: None,
5784 only_features: None,
5785 except_features: None,
5786 root_markers: Default::default(),
5787 }]),
5788 );
5789
5790 lsp.insert(
5793 "toml".to_string(),
5794 LspLanguageConfig::Multi(vec![LspServerConfig {
5795 command: "taplo".to_string(),
5796 args: vec!["lsp".to_string(), "stdio".to_string()],
5797 enabled: true,
5798 auto_start: false,
5799 process_limits: ProcessLimits::default(),
5800 initialization_options: None,
5801 env: Default::default(),
5802 language_id_overrides: Default::default(),
5803 name: None,
5804 only_features: None,
5805 except_features: None,
5806 root_markers: Default::default(),
5807 }]),
5808 );
5809
5810 lsp.insert(
5813 "dart".to_string(),
5814 LspLanguageConfig::Multi(vec![LspServerConfig {
5815 command: "dart".to_string(),
5816 args: vec!["language-server".to_string(), "--protocol=lsp".to_string()],
5817 enabled: true,
5818 auto_start: false,
5819 process_limits: ProcessLimits::default(),
5820 initialization_options: None,
5821 env: Default::default(),
5822 language_id_overrides: Default::default(),
5823 name: None,
5824 only_features: None,
5825 except_features: None,
5826 root_markers: vec!["pubspec.yaml".to_string(), ".git".to_string()],
5827 }]),
5828 );
5829
5830 lsp.insert(
5833 "nushell".to_string(),
5834 LspLanguageConfig::Multi(vec![LspServerConfig {
5835 command: "nu".to_string(),
5836 args: vec!["--lsp".to_string()],
5837 enabled: true,
5838 auto_start: false,
5839 process_limits: ProcessLimits::default(),
5840 initialization_options: None,
5841 env: Default::default(),
5842 language_id_overrides: Default::default(),
5843 name: None,
5844 only_features: None,
5845 except_features: None,
5846 root_markers: Default::default(),
5847 }]),
5848 );
5849
5850 lsp.insert(
5853 "solidity".to_string(),
5854 LspLanguageConfig::Multi(vec![LspServerConfig {
5855 command: "nomicfoundation-solidity-language-server".to_string(),
5856 args: vec!["--stdio".to_string()],
5857 enabled: true,
5858 auto_start: false,
5859 process_limits: ProcessLimits::default(),
5860 initialization_options: None,
5861 env: Default::default(),
5862 language_id_overrides: Default::default(),
5863 name: None,
5864 only_features: None,
5865 except_features: None,
5866 root_markers: Default::default(),
5867 }]),
5868 );
5869
5870 lsp.insert(
5875 "terraform".to_string(),
5876 LspLanguageConfig::Multi(vec![LspServerConfig {
5877 command: "terraform-ls".to_string(),
5878 args: vec!["serve".to_string()],
5879 enabled: true,
5880 auto_start: false,
5881 process_limits: ProcessLimits::default(),
5882 initialization_options: None,
5883 env: Default::default(),
5884 language_id_overrides: Default::default(),
5885 name: None,
5886 only_features: None,
5887 except_features: None,
5888 root_markers: vec![
5889 "*.tf".to_string(),
5890 ".terraform".to_string(),
5891 ".git".to_string(),
5892 ],
5893 }]),
5894 );
5895
5896 lsp.insert(
5899 "cmake".to_string(),
5900 LspLanguageConfig::Multi(vec![LspServerConfig {
5901 command: "cmake-language-server".to_string(),
5902 args: vec![],
5903 enabled: true,
5904 auto_start: false,
5905 process_limits: ProcessLimits::default(),
5906 initialization_options: None,
5907 env: Default::default(),
5908 language_id_overrides: Default::default(),
5909 name: None,
5910 only_features: None,
5911 except_features: None,
5912 root_markers: vec!["CMakeLists.txt".to_string(), ".git".to_string()],
5913 }]),
5914 );
5915
5916 lsp.insert(
5919 "protobuf".to_string(),
5920 LspLanguageConfig::Multi(vec![LspServerConfig {
5921 command: "buf".to_string(),
5922 args: vec!["beta".to_string(), "lsp".to_string()],
5923 enabled: true,
5924 auto_start: false,
5925 process_limits: ProcessLimits::default(),
5926 initialization_options: None,
5927 env: Default::default(),
5928 language_id_overrides: Default::default(),
5929 name: None,
5930 only_features: None,
5931 except_features: None,
5932 root_markers: Default::default(),
5933 }]),
5934 );
5935
5936 lsp.insert(
5939 "graphql".to_string(),
5940 LspLanguageConfig::Multi(vec![LspServerConfig {
5941 command: "graphql-lsp".to_string(),
5942 args: vec!["server".to_string(), "-m".to_string(), "stream".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(
5959 "sql".to_string(),
5960 LspLanguageConfig::Multi(vec![LspServerConfig {
5961 command: "sqls".to_string(),
5962 args: vec![],
5963 enabled: true,
5964 auto_start: false,
5965 process_limits: ProcessLimits::default(),
5966 initialization_options: None,
5967 env: Default::default(),
5968 language_id_overrides: Default::default(),
5969 name: None,
5970 only_features: None,
5971 except_features: None,
5972 root_markers: Default::default(),
5973 }]),
5974 );
5975
5976 lsp.insert(
5980 "vue".to_string(),
5981 LspLanguageConfig::Multi(vec![LspServerConfig {
5982 command: "vue-language-server".to_string(),
5983 args: vec!["--stdio".to_string()],
5984 enabled: true,
5985 auto_start: false,
5986 process_limits: ProcessLimits::default(),
5987 initialization_options: None,
5988 env: Default::default(),
5989 language_id_overrides: Default::default(),
5990 name: None,
5991 only_features: None,
5992 except_features: None,
5993 root_markers: Default::default(),
5994 }]),
5995 );
5996
5997 lsp.insert(
5999 "svelte".to_string(),
6000 LspLanguageConfig::Multi(vec![LspServerConfig {
6001 command: "svelteserver".to_string(),
6002 args: vec!["--stdio".to_string()],
6003 enabled: true,
6004 auto_start: false,
6005 process_limits: ProcessLimits::default(),
6006 initialization_options: None,
6007 env: Default::default(),
6008 language_id_overrides: Default::default(),
6009 name: None,
6010 only_features: None,
6011 except_features: None,
6012 root_markers: Default::default(),
6013 }]),
6014 );
6015
6016 lsp.insert(
6018 "astro".to_string(),
6019 LspLanguageConfig::Multi(vec![LspServerConfig {
6020 command: "astro-ls".to_string(),
6021 args: vec!["--stdio".to_string()],
6022 enabled: true,
6023 auto_start: false,
6024 process_limits: ProcessLimits::default(),
6025 initialization_options: None,
6026 env: Default::default(),
6027 language_id_overrides: Default::default(),
6028 name: None,
6029 only_features: None,
6030 except_features: None,
6031 root_markers: Default::default(),
6032 }]),
6033 );
6034
6035 lsp.insert(
6037 "tailwindcss".to_string(),
6038 LspLanguageConfig::Multi(vec![LspServerConfig {
6039 command: "tailwindcss-language-server".to_string(),
6040 args: vec!["--stdio".to_string()],
6041 enabled: true,
6042 auto_start: false,
6043 process_limits: ProcessLimits::default(),
6044 initialization_options: None,
6045 env: Default::default(),
6046 language_id_overrides: Default::default(),
6047 name: None,
6048 only_features: None,
6049 except_features: None,
6050 root_markers: Default::default(),
6051 }]),
6052 );
6053
6054 lsp.insert(
6059 "nix".to_string(),
6060 LspLanguageConfig::Multi(vec![LspServerConfig {
6061 command: "nil".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 "kotlin".to_string(),
6080 LspLanguageConfig::Multi(vec![LspServerConfig {
6081 command: "kotlin-language-server".to_string(),
6082 args: vec![],
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(
6098 "swift".to_string(),
6099 LspLanguageConfig::Multi(vec![LspServerConfig {
6100 command: "sourcekit-lsp".to_string(),
6101 args: vec![],
6102 enabled: true,
6103 auto_start: false,
6104 process_limits: ProcessLimits::default(),
6105 initialization_options: None,
6106 env: Default::default(),
6107 language_id_overrides: Default::default(),
6108 name: None,
6109 only_features: None,
6110 except_features: None,
6111 root_markers: Default::default(),
6112 }]),
6113 );
6114
6115 lsp.insert(
6118 "scala".to_string(),
6119 LspLanguageConfig::Multi(vec![LspServerConfig {
6120 command: "metals".to_string(),
6121 args: vec![],
6122 enabled: true,
6123 auto_start: false,
6124 process_limits: ProcessLimits::default(),
6125 initialization_options: None,
6126 env: Default::default(),
6127 language_id_overrides: Default::default(),
6128 name: None,
6129 only_features: None,
6130 except_features: None,
6131 root_markers: Default::default(),
6132 }]),
6133 );
6134
6135 lsp.insert(
6138 "elixir".to_string(),
6139 LspLanguageConfig::Multi(vec![LspServerConfig {
6140 command: "elixir-ls".to_string(),
6141 args: vec![],
6142 enabled: true,
6143 auto_start: false,
6144 process_limits: ProcessLimits::default(),
6145 initialization_options: None,
6146 env: Default::default(),
6147 language_id_overrides: Default::default(),
6148 name: None,
6149 only_features: None,
6150 except_features: None,
6151 root_markers: Default::default(),
6152 }]),
6153 );
6154
6155 lsp.insert(
6157 "erlang".to_string(),
6158 LspLanguageConfig::Multi(vec![LspServerConfig {
6159 command: "erlang_ls".to_string(),
6160 args: vec![],
6161 enabled: true,
6162 auto_start: false,
6163 process_limits: ProcessLimits::default(),
6164 initialization_options: None,
6165 env: Default::default(),
6166 language_id_overrides: Default::default(),
6167 name: None,
6168 only_features: None,
6169 except_features: None,
6170 root_markers: Default::default(),
6171 }]),
6172 );
6173
6174 lsp.insert(
6177 "haskell".to_string(),
6178 LspLanguageConfig::Multi(vec![LspServerConfig {
6179 command: "haskell-language-server-wrapper".to_string(),
6180 args: vec!["--lsp".to_string()],
6181 enabled: true,
6182 auto_start: false,
6183 process_limits: ProcessLimits::default(),
6184 initialization_options: None,
6185 env: Default::default(),
6186 language_id_overrides: Default::default(),
6187 name: None,
6188 only_features: None,
6189 except_features: None,
6190 root_markers: Default::default(),
6191 }]),
6192 );
6193
6194 lsp.insert(
6197 "ocaml".to_string(),
6198 LspLanguageConfig::Multi(vec![LspServerConfig {
6199 command: "ocamllsp".to_string(),
6200 args: vec![],
6201 enabled: true,
6202 auto_start: false,
6203 process_limits: ProcessLimits::default(),
6204 initialization_options: None,
6205 env: Default::default(),
6206 language_id_overrides: Default::default(),
6207 name: None,
6208 only_features: None,
6209 except_features: None,
6210 root_markers: Default::default(),
6211 }]),
6212 );
6213
6214 lsp.insert(
6217 "clojure".to_string(),
6218 LspLanguageConfig::Multi(vec![LspServerConfig {
6219 command: "clojure-lsp".to_string(),
6220 args: vec![],
6221 enabled: true,
6222 auto_start: false,
6223 process_limits: ProcessLimits::default(),
6224 initialization_options: None,
6225 env: Default::default(),
6226 language_id_overrides: Default::default(),
6227 name: None,
6228 only_features: None,
6229 except_features: None,
6230 root_markers: Default::default(),
6231 }]),
6232 );
6233
6234 lsp.insert(
6237 "r".to_string(),
6238 LspLanguageConfig::Multi(vec![LspServerConfig {
6239 command: "R".to_string(),
6240 args: vec![
6241 "--vanilla".to_string(),
6242 "-e".to_string(),
6243 "languageserver::run()".to_string(),
6244 ],
6245 enabled: true,
6246 auto_start: false,
6247 process_limits: ProcessLimits::default(),
6248 initialization_options: None,
6249 env: Default::default(),
6250 language_id_overrides: Default::default(),
6251 name: None,
6252 only_features: None,
6253 except_features: None,
6254 root_markers: Default::default(),
6255 }]),
6256 );
6257
6258 lsp.insert(
6261 "julia".to_string(),
6262 LspLanguageConfig::Multi(vec![LspServerConfig {
6263 command: "julia".to_string(),
6264 args: vec![
6265 "--startup-file=no".to_string(),
6266 "--history-file=no".to_string(),
6267 "-e".to_string(),
6268 "using LanguageServer; runserver()".to_string(),
6269 ],
6270 enabled: true,
6271 auto_start: false,
6272 process_limits: ProcessLimits::default(),
6273 initialization_options: None,
6274 env: Default::default(),
6275 language_id_overrides: Default::default(),
6276 name: None,
6277 only_features: None,
6278 except_features: None,
6279 root_markers: Default::default(),
6280 }]),
6281 );
6282
6283 lsp.insert(
6286 "perl".to_string(),
6287 LspLanguageConfig::Multi(vec![LspServerConfig {
6288 command: "perlnavigator".to_string(),
6289 args: vec!["--stdio".to_string()],
6290 enabled: true,
6291 auto_start: false,
6292 process_limits: ProcessLimits::default(),
6293 initialization_options: None,
6294 env: Default::default(),
6295 language_id_overrides: Default::default(),
6296 name: None,
6297 only_features: None,
6298 except_features: None,
6299 root_markers: Default::default(),
6300 }]),
6301 );
6302
6303 lsp.insert(
6306 "nim".to_string(),
6307 LspLanguageConfig::Multi(vec![LspServerConfig {
6308 command: "nimlangserver".to_string(),
6309 args: vec![],
6310 enabled: true,
6311 auto_start: false,
6312 process_limits: ProcessLimits::default(),
6313 initialization_options: None,
6314 env: Default::default(),
6315 language_id_overrides: Default::default(),
6316 name: None,
6317 only_features: None,
6318 except_features: None,
6319 root_markers: Default::default(),
6320 }]),
6321 );
6322
6323 lsp.insert(
6325 "gleam".to_string(),
6326 LspLanguageConfig::Multi(vec![LspServerConfig {
6327 command: "gleam".to_string(),
6328 args: vec!["lsp".to_string()],
6329 enabled: true,
6330 auto_start: false,
6331 process_limits: ProcessLimits::default(),
6332 initialization_options: None,
6333 env: Default::default(),
6334 language_id_overrides: Default::default(),
6335 name: None,
6336 only_features: None,
6337 except_features: None,
6338 root_markers: Default::default(),
6339 }]),
6340 );
6341
6342 lsp.insert(
6345 "racket".to_string(),
6346 LspLanguageConfig::Multi(vec![LspServerConfig {
6347 command: "racket-langserver".to_string(),
6348 args: vec![],
6349 enabled: true,
6350 auto_start: false,
6351 process_limits: ProcessLimits::default(),
6352 initialization_options: None,
6353 env: Default::default(),
6354 language_id_overrides: Default::default(),
6355 name: None,
6356 only_features: None,
6357 except_features: None,
6358 root_markers: vec!["info.rkt".to_string(), ".git".to_string()],
6359 }]),
6360 );
6361
6362 lsp.insert(
6365 "fsharp".to_string(),
6366 LspLanguageConfig::Multi(vec![LspServerConfig {
6367 command: "fsautocomplete".to_string(),
6368 args: vec!["--adaptive-lsp-server-enabled".to_string()],
6369 enabled: true,
6370 auto_start: false,
6371 process_limits: ProcessLimits::default(),
6372 initialization_options: None,
6373 env: Default::default(),
6374 language_id_overrides: Default::default(),
6375 name: None,
6376 only_features: None,
6377 except_features: None,
6378 root_markers: Default::default(),
6379 }]),
6380 );
6381 }
6382 pub fn validate(&self) -> Result<(), ConfigError> {
6383 if self.editor.tab_size == 0 {
6385 return Err(ConfigError::ValidationError(
6386 "tab_size must be greater than 0".to_string(),
6387 ));
6388 }
6389
6390 if self.editor.scroll_offset > 100 {
6392 return Err(ConfigError::ValidationError(
6393 "scroll_offset must be <= 100".to_string(),
6394 ));
6395 }
6396
6397 for binding in &self.keybindings {
6399 if binding.key.is_empty() {
6400 return Err(ConfigError::ValidationError(
6401 "keybinding key cannot be empty".to_string(),
6402 ));
6403 }
6404 if binding.action.is_empty() {
6405 return Err(ConfigError::ValidationError(
6406 "keybinding action cannot be empty".to_string(),
6407 ));
6408 }
6409 }
6410
6411 Ok(())
6412 }
6413}
6414
6415#[derive(Debug)]
6417pub enum ConfigError {
6418 IoError(String),
6419 ParseError(String),
6420 SerializeError(String),
6421 ValidationError(String),
6422}
6423
6424impl std::fmt::Display for ConfigError {
6425 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6426 match self {
6427 Self::IoError(msg) => write!(f, "IO error: {msg}"),
6428 Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
6429 Self::SerializeError(msg) => write!(f, "Serialize error: {msg}"),
6430 Self::ValidationError(msg) => write!(f, "Validation error: {msg}"),
6431 }
6432 }
6433}
6434
6435impl std::error::Error for ConfigError {}
6436
6437#[cfg(test)]
6438mod tests {
6439 use super::*;
6440
6441 #[test]
6442 fn test_file_explorer_width_default_is_percent_30() {
6443 let cfg = FileExplorerConfig::default();
6444 assert_eq!(cfg.width, ExplorerWidth::Percent(30));
6445 }
6446
6447 #[test]
6450 fn test_width_accepts_legacy_float_fraction() {
6451 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": 0.3}"#).unwrap();
6452 assert_eq!(cfg.width, ExplorerWidth::Percent(30));
6453 }
6454
6455 #[test]
6456 fn test_width_accepts_bare_integer_as_percent() {
6457 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": 42}"#).unwrap();
6459 assert_eq!(cfg.width, ExplorerWidth::Percent(42));
6460 }
6461
6462 #[test]
6463 fn test_width_accepts_percent_string() {
6464 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "75%"}"#).unwrap();
6465 assert_eq!(cfg.width, ExplorerWidth::Percent(75));
6466 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "42 %"}"#).unwrap();
6468 assert_eq!(cfg.width, ExplorerWidth::Percent(42));
6469 }
6470
6471 #[test]
6472 fn test_width_accepts_columns_string() {
6473 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "24"}"#).unwrap();
6474 assert_eq!(cfg.width, ExplorerWidth::Columns(24));
6475 }
6476
6477 #[test]
6478 fn test_width_rejects_percent_over_100() {
6479 let err = serde_json::from_str::<FileExplorerConfig>(r#"{"width": "150%"}"#)
6480 .expect_err("percent > 100 should be rejected");
6481 assert!(err.to_string().contains("100"), "{err}");
6482 }
6483
6484 #[test]
6485 fn test_width_rejects_integer_over_100() {
6486 let err = serde_json::from_str::<FileExplorerConfig>(r#"{"width": 150}"#)
6489 .expect_err("bare integer > 100 should be rejected as percent");
6490 assert!(err.to_string().contains("percent") || err.to_string().contains("100"));
6491 }
6492
6493 #[test]
6494 fn test_width_rejects_garbage_string() {
6495 serde_json::from_str::<FileExplorerConfig>(r#"{"width": "big"}"#)
6496 .expect_err("non-numeric string should be rejected");
6497 }
6498
6499 #[test]
6502 fn test_width_serializes_percent_as_string_with_suffix() {
6503 let cfg = FileExplorerConfig {
6504 width: ExplorerWidth::Percent(30),
6505 ..Default::default()
6506 };
6507 let json = serde_json::to_value(&cfg).unwrap();
6508 assert_eq!(json["width"], serde_json::json!("30%"));
6509 }
6510
6511 #[test]
6512 fn test_width_serializes_columns_as_string_without_suffix() {
6513 let cfg = FileExplorerConfig {
6514 width: ExplorerWidth::Columns(24),
6515 ..Default::default()
6516 };
6517 let json = serde_json::to_value(&cfg).unwrap();
6518 assert_eq!(json["width"], serde_json::json!("24"));
6519 }
6520
6521 #[test]
6522 fn test_width_round_trip_both_variants() {
6523 for value in [ExplorerWidth::Percent(17), ExplorerWidth::Columns(42)] {
6524 let cfg = FileExplorerConfig {
6525 width: value,
6526 ..Default::default()
6527 };
6528 let json = serde_json::to_string(&cfg).unwrap();
6529 let restored: FileExplorerConfig = serde_json::from_str(&json).unwrap();
6530 assert_eq!(restored.width, value, "round trip failed for {:?}", value);
6531 }
6532 }
6533
6534 #[test]
6537 fn test_to_cols_percent() {
6538 assert_eq!(ExplorerWidth::Percent(30).to_cols(100), 30);
6539 assert_eq!(ExplorerWidth::Percent(25).to_cols(120), 30);
6540 assert_eq!(ExplorerWidth::Percent(30).to_cols(0), 0);
6542 assert_eq!(ExplorerWidth::Percent(100).to_cols(200), 200);
6543 }
6544
6545 #[test]
6546 fn test_to_cols_columns_clamps_to_terminal() {
6547 assert_eq!(ExplorerWidth::Columns(24).to_cols(100), 24);
6548 assert_eq!(ExplorerWidth::Columns(999).to_cols(80), 80);
6549 }
6550
6551 #[test]
6554 fn test_to_cols_enforces_min_width() {
6555 assert_eq!(
6557 ExplorerWidth::Columns(0).to_cols(100),
6558 ExplorerWidth::MIN_COLS
6559 );
6560 assert_eq!(
6561 ExplorerWidth::Columns(1).to_cols(100),
6562 ExplorerWidth::MIN_COLS
6563 );
6564 assert_eq!(
6565 ExplorerWidth::Columns(4).to_cols(100),
6566 ExplorerWidth::MIN_COLS
6567 );
6568 assert_eq!(
6569 ExplorerWidth::Percent(0).to_cols(100),
6570 ExplorerWidth::MIN_COLS
6571 );
6572 assert_eq!(
6574 ExplorerWidth::Percent(3).to_cols(100),
6575 ExplorerWidth::MIN_COLS
6576 );
6577 assert_eq!(
6579 ExplorerWidth::Columns(ExplorerWidth::MIN_COLS + 1).to_cols(100),
6580 ExplorerWidth::MIN_COLS + 1
6581 );
6582 }
6583
6584 #[test]
6587 fn test_to_cols_min_floor_yields_to_narrow_terminal() {
6588 assert_eq!(ExplorerWidth::Columns(10).to_cols(3), 3);
6589 assert_eq!(ExplorerWidth::Percent(100).to_cols(2), 2);
6590 assert_eq!(ExplorerWidth::Columns(1).to_cols(0), 0);
6591 }
6592
6593 #[test]
6596 fn test_load_from_file_accepts_legacy_float_fraction_width() {
6597 let dir = tempfile::tempdir().unwrap();
6598 let path = dir.path().join("config.json");
6599 std::fs::write(&path, r#"{"file_explorer":{"width":0.25}}"#).unwrap();
6600 let cfg = Config::load_from_file(&path).expect("legacy float fraction must still load");
6601 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Percent(25));
6602 }
6603
6604 #[test]
6605 fn test_load_from_file_accepts_columns_string_width() {
6606 let dir = tempfile::tempdir().unwrap();
6607 let path = dir.path().join("config.json");
6608 std::fs::write(&path, r#"{"file_explorer":{"width":"42"}}"#).unwrap();
6609 let cfg = Config::load_from_file(&path).unwrap();
6610 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Columns(42));
6611 }
6612
6613 #[test]
6614 fn test_load_from_file_accepts_percent_string_width() {
6615 let dir = tempfile::tempdir().unwrap();
6616 let path = dir.path().join("config.json");
6617 std::fs::write(&path, r#"{"file_explorer":{"width":"55%"}}"#).unwrap();
6618 let cfg = Config::load_from_file(&path).unwrap();
6619 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Percent(55));
6620 }
6621
6622 #[test]
6623 fn test_default_config() {
6624 let config = Config::default();
6625 assert_eq!(config.editor.tab_size, 4);
6626 assert!(config.editor.line_numbers);
6627 assert!(config.editor.syntax_highlighting);
6628 assert!(config.keybindings.is_empty());
6631 let resolved = config.resolve_keymap(&config.active_keybinding_map);
6633 assert!(!resolved.is_empty());
6634 }
6635
6636 #[test]
6637 fn test_all_builtin_keymaps_loadable() {
6638 for name in KeybindingMapName::BUILTIN_OPTIONS {
6639 let keymap = Config::load_builtin_keymap(name);
6640 assert!(keymap.is_some(), "Failed to load builtin keymap '{}'", name);
6641 }
6642 }
6643
6644 #[test]
6645 fn test_config_validation() {
6646 let mut config = Config::default();
6647 assert!(config.validate().is_ok());
6648
6649 config.editor.tab_size = 0;
6650 assert!(config.validate().is_err());
6651 }
6652
6653 #[test]
6654 fn test_macos_keymap_inherits_enter_bindings() {
6655 let config = Config::default();
6656 let bindings = config.resolve_keymap("macos");
6657
6658 let enter_bindings: Vec<_> = bindings.iter().filter(|b| b.key == "Enter").collect();
6659 assert!(
6660 !enter_bindings.is_empty(),
6661 "macos keymap should inherit Enter bindings from default, got {} Enter bindings",
6662 enter_bindings.len()
6663 );
6664 let has_insert_newline = enter_bindings.iter().any(|b| b.action == "insert_newline");
6666 assert!(
6667 has_insert_newline,
6668 "macos keymap should have insert_newline action for Enter key"
6669 );
6670 }
6671
6672 #[test]
6673 fn test_config_serialize_deserialize() {
6674 let config = Config::default();
6676
6677 let json = serde_json::to_string_pretty(&config).unwrap();
6679
6680 let loaded: Config = serde_json::from_str(&json).unwrap();
6682
6683 assert_eq!(config.editor.tab_size, loaded.editor.tab_size);
6684 assert_eq!(config.theme, loaded.theme);
6685 }
6686
6687 #[test]
6688 fn test_config_with_custom_keybinding() {
6689 let json = r#"{
6690 "editor": {
6691 "tab_size": 2
6692 },
6693 "keybindings": [
6694 {
6695 "key": "x",
6696 "modifiers": ["ctrl", "shift"],
6697 "action": "custom_action",
6698 "args": {},
6699 "when": null
6700 }
6701 ]
6702 }"#;
6703
6704 let config: Config = serde_json::from_str(json).unwrap();
6705 assert_eq!(config.editor.tab_size, 2);
6706 assert_eq!(config.keybindings.len(), 1);
6707 assert_eq!(config.keybindings[0].key, "x");
6708 assert_eq!(config.keybindings[0].modifiers.len(), 2);
6709 }
6710
6711 #[test]
6712 fn test_sparse_config_merges_with_defaults() {
6713 let temp_dir = tempfile::tempdir().unwrap();
6715 let config_path = temp_dir.path().join("config.json");
6716
6717 let sparse_config = r#"{
6719 "lsp": {
6720 "rust": {
6721 "command": "custom-rust-analyzer",
6722 "args": ["--custom-arg"]
6723 }
6724 }
6725 }"#;
6726 std::fs::write(&config_path, sparse_config).unwrap();
6727
6728 let loaded = Config::load_from_file(&config_path).unwrap();
6730
6731 assert!(loaded.lsp.contains_key("rust"));
6733 assert_eq!(
6734 loaded.lsp["rust"].as_slice()[0].command,
6735 "custom-rust-analyzer".to_string()
6736 );
6737
6738 assert!(
6740 loaded.lsp.contains_key("python"),
6741 "python LSP should be merged from defaults"
6742 );
6743 assert!(
6744 loaded.lsp.contains_key("typescript"),
6745 "typescript LSP should be merged from defaults"
6746 );
6747 assert!(
6748 loaded.lsp.contains_key("javascript"),
6749 "javascript LSP should be merged from defaults"
6750 );
6751
6752 assert!(loaded.languages.contains_key("rust"));
6754 assert!(loaded.languages.contains_key("python"));
6755 assert!(loaded.languages.contains_key("typescript"));
6756 }
6757
6758 #[test]
6759 fn test_empty_config_gets_all_defaults() {
6760 let temp_dir = tempfile::tempdir().unwrap();
6761 let config_path = temp_dir.path().join("config.json");
6762
6763 std::fs::write(&config_path, "{}").unwrap();
6765
6766 let loaded = Config::load_from_file(&config_path).unwrap();
6767 let defaults = Config::default();
6768
6769 assert_eq!(loaded.lsp.len(), defaults.lsp.len());
6771
6772 assert_eq!(loaded.languages.len(), defaults.languages.len());
6774 }
6775
6776 #[test]
6777 fn test_dynamic_submenu_expansion() {
6778 let temp_dir = tempfile::tempdir().unwrap();
6780 let themes_dir = temp_dir.path().to_path_buf();
6781
6782 let dynamic = MenuItem::DynamicSubmenu {
6783 label: "Test".to_string(),
6784 source: "copy_with_theme".to_string(),
6785 };
6786
6787 let expanded = dynamic.expand_dynamic(&themes_dir);
6788
6789 match expanded {
6791 MenuItem::Submenu { label, items } => {
6792 assert_eq!(label, "Test");
6793 let loader = crate::view::theme::ThemeLoader::embedded_only();
6795 let registry = loader.load_all(&[]);
6796 assert_eq!(items.len(), registry.len());
6797
6798 for (item, theme_info) in items.iter().zip(registry.list().iter()) {
6800 match item {
6801 MenuItem::Action {
6802 label,
6803 action,
6804 args,
6805 ..
6806 } => {
6807 assert_eq!(label, &theme_info.name);
6808 assert_eq!(action, "copy_with_theme");
6809 assert_eq!(
6810 args.get("theme").and_then(|v| v.as_str()),
6811 Some(theme_info.name.as_str())
6812 );
6813 }
6814 _ => panic!("Expected Action item"),
6815 }
6816 }
6817 }
6818 _ => panic!("Expected Submenu after expansion"),
6819 }
6820 }
6821
6822 #[test]
6823 fn test_non_dynamic_item_unchanged() {
6824 let temp_dir = tempfile::tempdir().unwrap();
6826 let themes_dir = temp_dir.path();
6827
6828 let action = MenuItem::Action {
6829 label: "Test".to_string(),
6830 action: "test".to_string(),
6831 args: HashMap::new(),
6832 when: None,
6833 checkbox: None,
6834 };
6835
6836 let expanded = action.expand_dynamic(themes_dir);
6837 match expanded {
6838 MenuItem::Action { label, action, .. } => {
6839 assert_eq!(label, "Test");
6840 assert_eq!(action, "test");
6841 }
6842 _ => panic!("Action should remain Action after expand_dynamic"),
6843 }
6844 }
6845
6846 #[test]
6847 fn test_buffer_config_uses_global_defaults() {
6848 let config = Config::default();
6849 let buffer_config = BufferConfig::resolve(&config, None);
6850
6851 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
6852 assert_eq!(buffer_config.auto_indent, config.editor.auto_indent);
6853 assert!(!buffer_config.use_tabs); assert!(buffer_config.whitespace.any_tabs()); assert!(buffer_config.formatter.is_none());
6856 assert!(!buffer_config.format_on_save);
6857 }
6858
6859 #[test]
6860 fn test_buffer_config_applies_language_overrides() {
6861 let mut config = Config::default();
6862
6863 config.languages.insert(
6865 "go".to_string(),
6866 LanguageConfig {
6867 extensions: vec!["go".to_string()],
6868 filenames: vec![],
6869 grammar: "go".to_string(),
6870 comment_prefix: Some("//".to_string()),
6871 auto_indent: true,
6872 auto_close: None,
6873 auto_surround: None,
6874 textmate_grammar: None,
6875 show_whitespace_tabs: false, line_wrap: None,
6877 wrap_column: None,
6878 page_view: None,
6879 page_width: None,
6880 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
6883 command: "gofmt".to_string(),
6884 args: vec![],
6885 stdin: true,
6886 timeout_ms: 10000,
6887 }),
6888 format_on_save: true,
6889 on_save: vec![],
6890 word_characters: None,
6891 },
6892 );
6893
6894 let buffer_config = BufferConfig::resolve(&config, Some("go"));
6895
6896 assert_eq!(buffer_config.tab_size, 8);
6897 assert!(buffer_config.use_tabs);
6898 assert!(!buffer_config.whitespace.any_tabs()); assert!(buffer_config.format_on_save);
6900 assert!(buffer_config.formatter.is_some());
6901 assert_eq!(buffer_config.formatter.as_ref().unwrap().command, "gofmt");
6902 }
6903
6904 #[test]
6905 fn test_buffer_config_unknown_language_uses_global() {
6906 let config = Config::default();
6907 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
6908
6909 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
6911 assert!(!buffer_config.use_tabs);
6912 }
6913
6914 #[test]
6915 fn test_buffer_config_per_language_line_wrap() {
6916 let mut config = Config::default();
6917 config.editor.line_wrap = false;
6918
6919 config.languages.insert(
6921 "markdown".to_string(),
6922 LanguageConfig {
6923 extensions: vec!["md".to_string()],
6924 line_wrap: Some(true),
6925 ..Default::default()
6926 },
6927 );
6928
6929 let md_config = BufferConfig::resolve(&config, Some("markdown"));
6931 assert!(md_config.line_wrap, "Markdown should have line_wrap=true");
6932
6933 let other_config = BufferConfig::resolve(&config, Some("rust"));
6935 assert!(
6936 !other_config.line_wrap,
6937 "Non-configured languages should use global line_wrap=false"
6938 );
6939
6940 let no_lang_config = BufferConfig::resolve(&config, None);
6942 assert!(
6943 !no_lang_config.line_wrap,
6944 "No language should use global line_wrap=false"
6945 );
6946 }
6947
6948 #[test]
6949 fn test_buffer_config_per_language_wrap_column() {
6950 let mut config = Config::default();
6951 config.editor.wrap_column = Some(120);
6952
6953 config.languages.insert(
6955 "markdown".to_string(),
6956 LanguageConfig {
6957 extensions: vec!["md".to_string()],
6958 wrap_column: Some(80),
6959 ..Default::default()
6960 },
6961 );
6962
6963 let md_config = BufferConfig::resolve(&config, Some("markdown"));
6965 assert_eq!(md_config.wrap_column, Some(80));
6966
6967 let other_config = BufferConfig::resolve(&config, Some("rust"));
6969 assert_eq!(other_config.wrap_column, Some(120));
6970
6971 let no_lang_config = BufferConfig::resolve(&config, None);
6973 assert_eq!(no_lang_config.wrap_column, Some(120));
6974 }
6975
6976 #[test]
6977 fn test_buffer_config_indent_string() {
6978 let config = Config::default();
6979
6980 let spaces_config = BufferConfig::resolve(&config, None);
6982 assert_eq!(spaces_config.indent_string(), " "); let mut config_with_tabs = Config::default();
6986 config_with_tabs.languages.insert(
6987 "makefile".to_string(),
6988 LanguageConfig {
6989 use_tabs: Some(true),
6990 tab_size: Some(8),
6991 ..Default::default()
6992 },
6993 );
6994 let tabs_config = BufferConfig::resolve(&config_with_tabs, Some("makefile"));
6995 assert_eq!(tabs_config.indent_string(), "\t");
6996 }
6997
6998 #[test]
6999 fn test_buffer_config_global_use_tabs_inherited() {
7000 let mut config = Config::default();
7003 config.editor.use_tabs = true;
7004
7005 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
7007 assert!(buffer_config.use_tabs);
7008
7009 let buffer_config = BufferConfig::resolve(&config, None);
7011 assert!(buffer_config.use_tabs);
7012
7013 config.languages.insert(
7015 "python".to_string(),
7016 LanguageConfig {
7017 use_tabs: Some(false),
7018 ..Default::default()
7019 },
7020 );
7021 let buffer_config = BufferConfig::resolve(&config, Some("python"));
7022 assert!(!buffer_config.use_tabs);
7023
7024 config.languages.insert(
7026 "rust".to_string(),
7027 LanguageConfig {
7028 use_tabs: None,
7029 ..Default::default()
7030 },
7031 );
7032 let buffer_config = BufferConfig::resolve(&config, Some("rust"));
7033 assert!(buffer_config.use_tabs);
7034 }
7035
7036 #[test]
7042 #[cfg(feature = "runtime")]
7043 fn test_lsp_languages_have_language_config() {
7044 let config = Config::default();
7045 let exceptions = ["tailwindcss"];
7046 for lsp_key in config.lsp.keys() {
7047 if exceptions.contains(&lsp_key.as_str()) {
7048 continue;
7049 }
7050 assert!(
7051 config.languages.contains_key(lsp_key),
7052 "LSP config key '{}' has no matching entry in default_languages(). \
7053 Add a LanguageConfig with the correct file extensions so detect_language() \
7054 can map files to this language.",
7055 lsp_key
7056 );
7057 }
7058 }
7059
7060 #[test]
7061 #[cfg(feature = "runtime")]
7062 fn test_default_config_has_quicklsp_in_universal_lsp() {
7063 let config = Config::default();
7064 assert!(
7065 config.universal_lsp.contains_key("quicklsp"),
7066 "Default config should contain quicklsp in universal_lsp"
7067 );
7068 let quicklsp = &config.universal_lsp["quicklsp"];
7069 let server = &quicklsp.as_slice()[0];
7070 assert_eq!(server.command, "quicklsp");
7071 assert!(!server.enabled, "quicklsp should be disabled by default");
7072 assert_eq!(server.name.as_deref(), Some("QuickLSP"));
7073 assert!(
7077 server.only_features.is_none(),
7078 "quicklsp must not default to a feature whitelist"
7079 );
7080 assert!(server.except_features.is_none());
7081 }
7082
7083 #[test]
7084 fn test_empty_config_preserves_universal_lsp_defaults() {
7085 let temp_dir = tempfile::tempdir().unwrap();
7086 let config_path = temp_dir.path().join("config.json");
7087
7088 std::fs::write(&config_path, "{}").unwrap();
7090
7091 let loaded = Config::load_from_file(&config_path).unwrap();
7092 let defaults = Config::default();
7093
7094 assert_eq!(
7096 loaded.universal_lsp.len(),
7097 defaults.universal_lsp.len(),
7098 "Empty config should preserve all default universal_lsp entries"
7099 );
7100 }
7101
7102 #[test]
7103 fn test_universal_lsp_config_merges_with_defaults() {
7104 let temp_dir = tempfile::tempdir().unwrap();
7105 let config_path = temp_dir.path().join("config.json");
7106
7107 let config_json = r#"{
7109 "universal_lsp": {
7110 "quicklsp": {
7111 "enabled": true
7112 }
7113 }
7114 }"#;
7115 std::fs::write(&config_path, config_json).unwrap();
7116
7117 let loaded = Config::load_from_file(&config_path).unwrap();
7118
7119 assert!(loaded.universal_lsp.contains_key("quicklsp"));
7121 let server = &loaded.universal_lsp["quicklsp"].as_slice()[0];
7122 assert!(server.enabled, "User override should enable quicklsp");
7123 assert_eq!(
7125 server.command, "quicklsp",
7126 "Default command should be merged when not specified by user"
7127 );
7128 }
7129
7130 #[test]
7131 fn test_universal_lsp_custom_server_added() {
7132 let temp_dir = tempfile::tempdir().unwrap();
7133 let config_path = temp_dir.path().join("config.json");
7134
7135 let config_json = r#"{
7137 "universal_lsp": {
7138 "my-custom-server": {
7139 "command": "my-server",
7140 "enabled": true,
7141 "auto_start": true
7142 }
7143 }
7144 }"#;
7145 std::fs::write(&config_path, config_json).unwrap();
7146
7147 let loaded = Config::load_from_file(&config_path).unwrap();
7148
7149 assert!(
7151 loaded.universal_lsp.contains_key("my-custom-server"),
7152 "Custom universal server should be loaded"
7153 );
7154 let server = &loaded.universal_lsp["my-custom-server"].as_slice()[0];
7155 assert_eq!(server.command, "my-server");
7156 assert!(server.enabled);
7157 assert!(server.auto_start);
7158
7159 assert!(
7161 loaded.universal_lsp.contains_key("quicklsp"),
7162 "Default quicklsp should be merged from defaults"
7163 );
7164 }
7165}