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