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