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 CustomToken(String),
588}
589
590impl TryFrom<String> for StatusBarElement {
591 type Error = String;
592 fn try_from(s: String) -> Result<Self, String> {
593 let inner = s
595 .strip_prefix('{')
596 .and_then(|s| s.strip_suffix('}'))
597 .unwrap_or(&s);
598 match inner {
599 "filename" => Ok(Self::Filename),
600 "cursor" => Ok(Self::Cursor),
601 "cursor:compact" => Ok(Self::CursorCompact),
602 "diagnostics" => Ok(Self::Diagnostics),
603 "cursor_count" => Ok(Self::CursorCount),
604 "messages" => Ok(Self::Messages),
605 "chord" => Ok(Self::Chord),
606 "line_ending" => Ok(Self::LineEnding),
607 "encoding" => Ok(Self::Encoding),
608 "language" => Ok(Self::Language),
609 "lsp" => Ok(Self::Lsp),
610 "warnings" => Ok(Self::Warnings),
611 "update" => Ok(Self::Update),
612 "palette" => Ok(Self::Palette),
613 "clock" => Ok(Self::Clock),
614 "remote" => Ok(Self::RemoteIndicator),
615 _ => {
616 if inner.contains(':') {
618 Ok(Self::CustomToken(inner.to_string()))
619 } else {
620 Err(format!("Unknown status bar element: {}", s))
621 }
622 }
623 }
624 }
625}
626
627impl From<StatusBarElement> for String {
628 fn from(e: StatusBarElement) -> String {
629 match e {
630 StatusBarElement::Filename => "{filename}".to_string(),
631 StatusBarElement::Cursor => "{cursor}".to_string(),
632 StatusBarElement::CursorCompact => "{cursor:compact}".to_string(),
633 StatusBarElement::Diagnostics => "{diagnostics}".to_string(),
634 StatusBarElement::CursorCount => "{cursor_count}".to_string(),
635 StatusBarElement::Messages => "{messages}".to_string(),
636 StatusBarElement::Chord => "{chord}".to_string(),
637 StatusBarElement::LineEnding => "{line_ending}".to_string(),
638 StatusBarElement::Encoding => "{encoding}".to_string(),
639 StatusBarElement::Language => "{language}".to_string(),
640 StatusBarElement::Lsp => "{lsp}".to_string(),
641 StatusBarElement::Warnings => "{warnings}".to_string(),
642 StatusBarElement::Update => "{update}".to_string(),
643 StatusBarElement::Palette => "{palette}".to_string(),
644 StatusBarElement::Clock => "{clock}".to_string(),
645 StatusBarElement::RemoteIndicator => "{remote}".to_string(),
646 StatusBarElement::CustomToken(name) => format!("{{{}}}", name),
647 }
648 }
649}
650
651impl schemars::JsonSchema for StatusBarElement {
652 fn schema_name() -> std::borrow::Cow<'static, str> {
653 std::borrow::Cow::Borrowed("StatusBarElement")
654 }
655 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
656 schemars::json_schema!({
657 "type": "string",
658 "x-dual-list-options": [
659 {"value": "{filename}", "name": "Filename"},
660 {"value": "{cursor}", "name": "Cursor"},
661 {"value": "{cursor:compact}", "name": "Cursor (compact)"},
662 {"value": "{diagnostics}", "name": "Diagnostics"},
663 {"value": "{cursor_count}", "name": "Cursor Count"},
664 {"value": "{messages}", "name": "Messages"},
665 {"value": "{chord}", "name": "Chord"},
666 {"value": "{line_ending}", "name": "Line Ending"},
667 {"value": "{encoding}", "name": "Encoding"},
668 {"value": "{language}", "name": "Language"},
669 {"value": "{lsp}", "name": "LSP"},
670 {"value": "{warnings}", "name": "Warnings"},
671 {"value": "{update}", "name": "Update"},
672 {"value": "{palette}", "name": "Palette"},
673 {"value": "{clock}", "name": "Clock"},
674 {"value": "{remote}", "name": "Remote Indicator"}
675 ]
676 })
677 }
678}
679
680fn default_status_bar_left() -> Vec<StatusBarElement> {
681 vec![
693 StatusBarElement::RemoteIndicator,
694 StatusBarElement::Filename,
695 StatusBarElement::Cursor,
696 StatusBarElement::Diagnostics,
697 StatusBarElement::CursorCount,
698 StatusBarElement::Messages,
699 ]
700}
701
702fn default_status_bar_right() -> Vec<StatusBarElement> {
703 vec![
704 StatusBarElement::LineEnding,
705 StatusBarElement::Encoding,
706 StatusBarElement::Language,
707 StatusBarElement::Lsp,
708 StatusBarElement::Warnings,
709 StatusBarElement::Update,
710 StatusBarElement::Palette,
711 ]
712}
713
714#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
729pub struct StatusBarConfig {
730 #[serde(default = "default_status_bar_left")]
733 #[schemars(extend("x-section" = "Status Bar", "x-dual-list-sibling" = "/editor/status_bar/right", "x-dynamically-extendable-status-bar-elements" = true))]
734 pub left: Vec<StatusBarElement>,
735
736 #[serde(default = "default_status_bar_right")]
739 #[schemars(extend("x-section" = "Status Bar", "x-dual-list-sibling" = "/editor/status_bar/left", "x-dynamically-extendable-status-bar-elements" = true))]
740 pub right: Vec<StatusBarElement>,
741}
742
743impl Default for StatusBarConfig {
744 fn default() -> Self {
745 Self {
746 left: default_status_bar_left(),
747 right: default_status_bar_right(),
748 }
749 }
750}
751
752#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
754pub struct EditorConfig {
755 #[serde(default = "default_true")]
762 #[schemars(extend("x-section" = "Display"))]
763 pub animations: bool,
764
765 #[serde(default = "default_true")]
769 #[schemars(extend("x-section" = "Display"))]
770 pub cursor_jump_animation: bool,
771
772 #[serde(default = "default_true")]
774 #[schemars(extend("x-section" = "Display"))]
775 pub line_numbers: bool,
776
777 #[serde(default = "default_false")]
779 #[schemars(extend("x-section" = "Display"))]
780 pub relative_line_numbers: bool,
781
782 #[serde(default = "default_true")]
784 #[schemars(extend("x-section" = "Display"))]
785 pub highlight_current_line: bool,
786
787 #[serde(default = "default_false")]
789 #[schemars(extend("x-section" = "Display"))]
790 pub highlight_current_column: bool,
791
792 #[serde(default = "default_true")]
794 #[schemars(extend("x-section" = "Display"))]
795 pub line_wrap: bool,
796
797 #[serde(default = "default_true")]
799 #[schemars(extend("x-section" = "Display"))]
800 pub wrap_indent: bool,
801
802 #[serde(default)]
808 #[schemars(extend("x-section" = "Display"))]
809 pub wrap_column: Option<usize>,
810
811 #[serde(default = "default_page_width")]
815 #[schemars(extend("x-section" = "Display"))]
816 pub page_width: Option<usize>,
817
818 #[serde(default = "default_true")]
820 #[schemars(extend("x-section" = "Display"))]
821 pub syntax_highlighting: bool,
822
823 #[serde(default = "default_true")]
828 #[schemars(extend("x-section" = "Display"))]
829 pub show_menu_bar: bool,
830
831 #[serde(default = "default_true")]
836 #[schemars(extend("x-section" = "Display"))]
837 pub menu_bar_mnemonics: bool,
838
839 #[serde(default = "default_true")]
844 #[schemars(extend("x-section" = "Display"))]
845 pub show_tab_bar: bool,
846
847 #[serde(default = "default_true")]
852 #[schemars(extend("x-section" = "Display"))]
853 pub show_status_bar: bool,
854
855 #[serde(default)]
858 #[schemars(extend("x-section" = "Status Bar"))]
859 pub status_bar: StatusBarConfig,
860
861 #[serde(default = "default_false")]
868 #[schemars(extend("x-section" = "Display"))]
869 pub show_prompt_line: bool,
870
871 #[serde(default = "default_true")]
875 #[schemars(extend("x-section" = "Display"))]
876 pub show_vertical_scrollbar: bool,
877
878 #[serde(default = "default_false")]
883 #[schemars(extend("x-section" = "Display"))]
884 pub show_horizontal_scrollbar: bool,
885
886 #[serde(default = "default_true")]
890 #[schemars(extend("x-section" = "Display"))]
891 pub show_tilde: bool,
892
893 #[serde(default = "default_false")]
898 #[schemars(extend("x-section" = "Display"))]
899 pub use_terminal_bg: bool,
900
901 #[serde(default = "default_true")]
907 #[schemars(extend("x-section" = "Display"))]
908 pub set_window_title: bool,
909
910 #[serde(default = "default_true")]
917 #[schemars(extend("x-section" = "Display"))]
918 pub terminal_auto_title: bool,
919
920 #[serde(default)]
924 #[schemars(extend("x-section" = "Display"))]
925 pub cursor_style: CursorStyle,
926
927 #[serde(default)]
932 #[schemars(extend("x-section" = "Display"))]
933 pub rulers: Vec<usize>,
934
935 #[serde(default = "default_true")]
941 #[schemars(extend("x-section" = "Whitespace"))]
942 pub whitespace_show: bool,
943
944 #[serde(default = "default_false")]
948 #[schemars(extend("x-section" = "Whitespace"))]
949 pub whitespace_spaces_leading: bool,
950
951 #[serde(default = "default_false")]
955 #[schemars(extend("x-section" = "Whitespace"))]
956 pub whitespace_spaces_inner: bool,
957
958 #[serde(default = "default_false")]
962 #[schemars(extend("x-section" = "Whitespace"))]
963 pub whitespace_spaces_trailing: bool,
964
965 #[serde(default = "default_true")]
969 #[schemars(extend("x-section" = "Whitespace"))]
970 pub whitespace_tabs_leading: bool,
971
972 #[serde(default = "default_true")]
976 #[schemars(extend("x-section" = "Whitespace"))]
977 pub whitespace_tabs_inner: bool,
978
979 #[serde(default = "default_true")]
983 #[schemars(extend("x-section" = "Whitespace"))]
984 pub whitespace_tabs_trailing: bool,
985
986 #[serde(default = "default_false")]
992 #[schemars(extend("x-section" = "Editing"))]
993 pub use_tabs: bool,
994
995 #[serde(default = "default_tab_size")]
998 #[schemars(extend("x-section" = "Editing"))]
999 pub tab_size: usize,
1000
1001 #[serde(default = "default_true")]
1003 #[schemars(extend("x-section" = "Editing"))]
1004 pub auto_indent: bool,
1005
1006 #[serde(default = "default_true")]
1013 #[schemars(extend("x-section" = "Editing"))]
1014 pub auto_close: bool,
1015
1016 #[serde(default = "default_true")]
1021 #[schemars(extend("x-section" = "Editing"))]
1022 pub auto_surround: bool,
1023
1024 #[serde(default = "default_scroll_offset")]
1026 #[schemars(extend("x-section" = "Editing"))]
1027 pub scroll_offset: usize,
1028
1029 #[serde(default)]
1034 #[schemars(extend("x-section" = "Editing"))]
1035 pub default_line_ending: LineEndingOption,
1036
1037 #[serde(default = "default_false")]
1040 #[schemars(extend("x-section" = "Editing"))]
1041 pub trim_trailing_whitespace_on_save: bool,
1042
1043 #[serde(default = "default_false")]
1046 #[schemars(extend("x-section" = "Editing"))]
1047 pub ensure_final_newline_on_save: bool,
1048
1049 #[serde(default = "default_true")]
1053 #[schemars(extend("x-section" = "Bracket Matching"))]
1054 pub highlight_matching_brackets: bool,
1055
1056 #[serde(default = "default_true")]
1060 #[schemars(extend("x-section" = "Bracket Matching"))]
1061 pub rainbow_brackets: bool,
1062
1063 #[serde(default = "default_false")]
1070 #[schemars(extend("x-section" = "Completion"))]
1071 pub completion_popup_auto_show: bool,
1072
1073 #[serde(default = "default_true")]
1079 #[schemars(extend("x-section" = "Completion"))]
1080 pub quick_suggestions: bool,
1081
1082 #[serde(default = "default_quick_suggestions_delay")]
1088 #[schemars(extend("x-section" = "Completion"))]
1089 pub quick_suggestions_delay_ms: u64,
1090
1091 #[serde(default = "default_true")]
1095 #[schemars(extend("x-section" = "Completion"))]
1096 pub suggest_on_trigger_characters: bool,
1097
1098 #[serde(default = "default_true")]
1101 #[schemars(extend("x-section" = "LSP"))]
1102 pub enable_inlay_hints: bool,
1103
1104 #[serde(default = "default_false")]
1108 #[schemars(extend("x-section" = "LSP"))]
1109 pub enable_semantic_tokens_full: bool,
1110
1111 #[serde(default = "default_false")]
1116 #[schemars(extend("x-section" = "Diagnostics"))]
1117 pub diagnostics_inline_text: bool,
1118
1119 #[serde(default = "default_mouse_hover_enabled")]
1130 #[schemars(extend("x-section" = "Mouse"))]
1131 pub mouse_hover_enabled: bool,
1132
1133 #[serde(default = "default_mouse_hover_delay")]
1137 #[schemars(extend("x-section" = "Mouse"))]
1138 pub mouse_hover_delay_ms: u64,
1139
1140 #[serde(default = "default_double_click_time")]
1144 #[schemars(extend("x-section" = "Mouse"))]
1145 pub double_click_time_ms: u64,
1146
1147 #[serde(default = "default_false")]
1152 #[schemars(extend("x-section" = "Recovery"))]
1153 pub auto_save_enabled: bool,
1154
1155 #[serde(default = "default_auto_save_interval")]
1160 #[schemars(extend("x-section" = "Recovery"))]
1161 pub auto_save_interval_secs: u32,
1162
1163 #[serde(default = "default_true", alias = "persist_unnamed_buffers")]
1170 #[schemars(extend("x-section" = "Recovery"))]
1171 pub hot_exit: bool,
1172
1173 #[serde(default)]
1180 #[schemars(extend("x-section" = "Startup"))]
1181 pub confirm_quit: bool,
1182
1183 #[serde(default = "default_true")]
1194 #[schemars(extend("x-section" = "Startup"))]
1195 pub restore_previous_session: bool,
1196
1197 #[serde(default = "default_true")]
1208 #[schemars(extend("x-section" = "Startup"))]
1209 pub skip_session_restore_when_files_passed: bool,
1210
1211 #[serde(default = "default_true")]
1219 #[schemars(extend("x-section" = "Startup"))]
1220 pub auto_create_empty_buffer_on_last_buffer_close: bool,
1221
1222 #[serde(default = "default_true")]
1227 #[schemars(extend("x-section" = "Recovery"))]
1228 pub recovery_enabled: bool,
1229
1230 #[serde(default = "default_auto_recovery_save_interval")]
1235 #[schemars(extend("x-section" = "Recovery"))]
1236 pub auto_recovery_save_interval_secs: u32,
1237
1238 #[serde(default = "default_auto_revert_poll_interval")]
1243 #[schemars(extend("x-section" = "Recovery"))]
1244 pub auto_revert_poll_interval_ms: u64,
1245
1246 #[serde(default = "default_true")]
1252 #[schemars(extend("x-section" = "Keyboard"))]
1253 pub keyboard_disambiguate_escape_codes: bool,
1254
1255 #[serde(default = "default_false")]
1260 #[schemars(extend("x-section" = "Keyboard"))]
1261 pub keyboard_report_event_types: bool,
1262
1263 #[serde(default = "default_true")]
1268 #[schemars(extend("x-section" = "Keyboard"))]
1269 pub keyboard_report_alternate_keys: bool,
1270
1271 #[serde(default = "default_false")]
1277 #[schemars(extend("x-section" = "Keyboard"))]
1278 pub keyboard_report_all_keys_as_escape_codes: bool,
1279
1280 #[serde(default = "default_highlight_timeout")]
1283 #[schemars(extend("x-section" = "Performance"))]
1284 pub highlight_timeout_ms: u64,
1285
1286 #[serde(default = "default_snapshot_interval")]
1288 #[schemars(extend("x-section" = "Performance"))]
1289 pub snapshot_interval: usize,
1290
1291 #[serde(default = "default_highlight_context_bytes")]
1296 #[schemars(extend("x-section" = "Performance"))]
1297 pub highlight_context_bytes: usize,
1298
1299 #[serde(default = "default_large_file_threshold")]
1306 #[schemars(extend("x-section" = "Performance"))]
1307 pub large_file_threshold_bytes: u64,
1308
1309 #[serde(default = "default_estimated_line_length")]
1313 #[schemars(extend("x-section" = "Performance"))]
1314 pub estimated_line_length: usize,
1315
1316 #[serde(default = "default_read_concurrency")]
1321 #[schemars(extend("x-section" = "Performance"))]
1322 pub read_concurrency: usize,
1323
1324 #[serde(default = "default_file_tree_poll_interval")]
1329 #[schemars(extend("x-section" = "Performance"))]
1330 pub file_tree_poll_interval_ms: u64,
1331}
1332
1333fn default_tab_size() -> usize {
1334 4
1335}
1336
1337pub const LARGE_FILE_THRESHOLD_BYTES: u64 = 1024 * 1024; fn default_large_file_threshold() -> u64 {
1343 LARGE_FILE_THRESHOLD_BYTES
1344}
1345
1346pub const INDENT_FOLD_MAX_SCAN_LINES: usize = 10_000;
1349
1350pub const INDENT_FOLD_INDICATOR_MAX_SCAN: usize = 50;
1353
1354pub const INDENT_FOLD_MAX_UPWARD_SCAN: usize = 200;
1357
1358fn default_read_concurrency() -> usize {
1359 64
1360}
1361
1362fn default_true() -> bool {
1363 true
1364}
1365
1366fn default_false() -> bool {
1367 false
1368}
1369
1370fn default_quick_suggestions_delay() -> u64 {
1371 150 }
1373
1374fn default_scroll_offset() -> usize {
1375 3
1376}
1377
1378fn default_highlight_timeout() -> u64 {
1379 5
1380}
1381
1382fn default_snapshot_interval() -> usize {
1383 100
1384}
1385
1386fn default_estimated_line_length() -> usize {
1387 80
1388}
1389
1390fn default_auto_save_interval() -> u32 {
1391 30 }
1393
1394fn default_auto_recovery_save_interval() -> u32 {
1395 2 }
1397
1398fn default_highlight_context_bytes() -> usize {
1399 10_000 }
1401
1402fn default_mouse_hover_enabled() -> bool {
1403 !cfg!(windows)
1404}
1405
1406fn default_mouse_hover_delay() -> u64 {
1407 500 }
1409
1410fn default_double_click_time() -> u64 {
1411 500 }
1413
1414fn default_auto_revert_poll_interval() -> u64 {
1415 2000 }
1417
1418fn default_file_tree_poll_interval() -> u64 {
1419 3000 }
1421
1422impl Default for EditorConfig {
1423 fn default() -> Self {
1424 Self {
1425 use_tabs: false,
1426 tab_size: default_tab_size(),
1427 auto_indent: true,
1428 auto_close: true,
1429 auto_surround: true,
1430 animations: true,
1431 cursor_jump_animation: true,
1432 line_numbers: true,
1433 relative_line_numbers: false,
1434 scroll_offset: default_scroll_offset(),
1435 syntax_highlighting: true,
1436 highlight_current_line: true,
1437 highlight_current_column: false,
1438 line_wrap: true,
1439 wrap_indent: true,
1440 wrap_column: None,
1441 page_width: default_page_width(),
1442 highlight_timeout_ms: default_highlight_timeout(),
1443 snapshot_interval: default_snapshot_interval(),
1444 large_file_threshold_bytes: default_large_file_threshold(),
1445 estimated_line_length: default_estimated_line_length(),
1446 enable_inlay_hints: true,
1447 enable_semantic_tokens_full: false,
1448 diagnostics_inline_text: false,
1449 auto_save_enabled: false,
1450 auto_save_interval_secs: default_auto_save_interval(),
1451 hot_exit: true,
1452 confirm_quit: false,
1453 restore_previous_session: true,
1454 skip_session_restore_when_files_passed: true,
1455 auto_create_empty_buffer_on_last_buffer_close: true,
1456 recovery_enabled: true,
1457 auto_recovery_save_interval_secs: default_auto_recovery_save_interval(),
1458 highlight_context_bytes: default_highlight_context_bytes(),
1459 mouse_hover_enabled: default_mouse_hover_enabled(),
1460 mouse_hover_delay_ms: default_mouse_hover_delay(),
1461 double_click_time_ms: default_double_click_time(),
1462 auto_revert_poll_interval_ms: default_auto_revert_poll_interval(),
1463 read_concurrency: default_read_concurrency(),
1464 file_tree_poll_interval_ms: default_file_tree_poll_interval(),
1465 default_line_ending: LineEndingOption::default(),
1466 trim_trailing_whitespace_on_save: false,
1467 ensure_final_newline_on_save: false,
1468 highlight_matching_brackets: true,
1469 rainbow_brackets: true,
1470 cursor_style: CursorStyle::default(),
1471 keyboard_disambiguate_escape_codes: true,
1472 keyboard_report_event_types: false,
1473 keyboard_report_alternate_keys: true,
1474 keyboard_report_all_keys_as_escape_codes: false,
1475 completion_popup_auto_show: false,
1476 quick_suggestions: true,
1477 quick_suggestions_delay_ms: default_quick_suggestions_delay(),
1478 suggest_on_trigger_characters: true,
1479 show_menu_bar: true,
1480 menu_bar_mnemonics: true,
1481 show_tab_bar: true,
1482 show_status_bar: true,
1483 status_bar: StatusBarConfig::default(),
1484 show_prompt_line: false,
1485 show_vertical_scrollbar: true,
1486 show_horizontal_scrollbar: false,
1487 show_tilde: true,
1488 use_terminal_bg: false,
1489 set_window_title: true,
1490 terminal_auto_title: true,
1491 rulers: Vec::new(),
1492 whitespace_show: true,
1493 whitespace_spaces_leading: false,
1494 whitespace_spaces_inner: false,
1495 whitespace_spaces_trailing: false,
1496 whitespace_tabs_leading: true,
1497 whitespace_tabs_inner: true,
1498 whitespace_tabs_trailing: true,
1499 }
1500 }
1501}
1502
1503#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
1505#[serde(rename_all = "snake_case")]
1506pub enum FileExplorerSide {
1507 #[default]
1508 Left,
1509 Right,
1510}
1511
1512#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1514pub struct FileExplorerConfig {
1515 #[serde(default = "default_true")]
1517 pub respect_gitignore: bool,
1518
1519 #[serde(default = "default_false")]
1521 pub show_hidden: bool,
1522
1523 #[serde(default = "default_false")]
1525 pub show_gitignored: bool,
1526
1527 #[serde(default)]
1529 pub custom_ignore_patterns: Vec<String>,
1530
1531 #[serde(default = "default_explorer_width")]
1537 pub width: ExplorerWidth,
1538
1539 #[serde(default = "default_true")]
1546 pub preview_tabs: bool,
1547
1548 #[serde(default = "default_explorer_side")]
1551 pub side: FileExplorerSide,
1552
1553 #[serde(default = "default_true")]
1559 pub auto_open_on_last_buffer_close: bool,
1560
1561 #[serde(default = "default_false")]
1567 pub follow_active_buffer: bool,
1568
1569 #[serde(default = "default_true")]
1576 pub compact_directories: bool,
1577
1578 #[serde(default = "default_tree_indicator_collapsed")]
1584 pub tree_indicator_collapsed: String,
1585
1586 #[serde(default = "default_tree_indicator_expanded")]
1592 pub tree_indicator_expanded: String,
1593}
1594
1595#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1623pub enum ExplorerWidth {
1624 Percent(u8),
1625 Columns(u16),
1626}
1627
1628impl ExplorerWidth {
1629 pub const DEFAULT: Self = Self::Percent(30);
1631
1632 pub const MIN_COLS: u16 = 5;
1638
1639 pub fn to_cols(self, terminal_width: u16) -> u16 {
1647 let raw = match self {
1648 Self::Percent(pct) => ((terminal_width as u32 * pct as u32) / 100) as u16,
1649 Self::Columns(cols) => cols,
1650 };
1651 raw.max(Self::MIN_COLS).min(terminal_width)
1652 }
1653}
1654
1655impl Default for ExplorerWidth {
1656 fn default() -> Self {
1657 Self::DEFAULT
1658 }
1659}
1660
1661impl std::fmt::Display for ExplorerWidth {
1662 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1663 match self {
1664 Self::Percent(n) => write!(f, "{}%", n),
1665 Self::Columns(n) => write!(f, "{}", n),
1666 }
1667 }
1668}
1669
1670#[derive(Debug)]
1672pub struct ExplorerWidthParseError(String);
1673
1674impl std::fmt::Display for ExplorerWidthParseError {
1675 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1676 write!(f, "{}", self.0)
1677 }
1678}
1679
1680impl std::error::Error for ExplorerWidthParseError {}
1681
1682impl std::str::FromStr for ExplorerWidth {
1683 type Err = ExplorerWidthParseError;
1684
1685 fn from_str(s: &str) -> Result<Self, Self::Err> {
1686 let s = s.trim();
1687 if s.is_empty() {
1688 return Err(ExplorerWidthParseError(
1689 "explorer width: empty string".into(),
1690 ));
1691 }
1692 if let Some(rest) = s.strip_suffix('%') {
1693 let n: u16 = rest.trim().parse().map_err(|_| {
1694 ExplorerWidthParseError(format!("explorer width: {:?} is not a valid percent", s))
1695 })?;
1696 if n > 100 {
1697 return Err(ExplorerWidthParseError(format!(
1698 "explorer width: {}% exceeds 100%",
1699 n
1700 )));
1701 }
1702 Ok(Self::Percent(n as u8))
1703 } else {
1704 let n: u16 = s.parse().map_err(|_| {
1705 ExplorerWidthParseError(format!(
1706 "explorer width: {:?} is neither a percent (e.g. \"30%\") nor a column count (e.g. \"24\")",
1707 s
1708 ))
1709 })?;
1710 Ok(Self::Columns(n))
1711 }
1712 }
1713}
1714
1715impl serde::Serialize for ExplorerWidth {
1716 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1717 s.collect_str(self)
1718 }
1719}
1720
1721impl<'de> serde::Deserialize<'de> for ExplorerWidth {
1722 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
1723 let raw = serde_json::Value::deserialize(d)?;
1724 explorer_width::from_value(&raw)
1725 }
1726}
1727
1728impl schemars::JsonSchema for ExplorerWidth {
1729 fn schema_name() -> std::borrow::Cow<'static, str> {
1730 std::borrow::Cow::Borrowed("ExplorerWidth")
1731 }
1732
1733 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
1734 schemars::json_schema!({
1738 "type": "string",
1739 "pattern": r"^(100%|[1-9]?[0-9]%|\d+)$",
1740 "description": "Either a percent like \"30%\" (0–100) or an absolute column count like \"24\".",
1741 })
1742 }
1743}
1744
1745fn default_explorer_width() -> ExplorerWidth {
1746 ExplorerWidth::DEFAULT
1747}
1748
1749fn default_explorer_side() -> FileExplorerSide {
1750 FileExplorerSide::default()
1751}
1752
1753fn default_tree_indicator_collapsed() -> String {
1754 ">".to_string()
1755}
1756
1757fn default_tree_indicator_expanded() -> String {
1758 "▼".to_string()
1759}
1760
1761pub fn default_explorer_width_value() -> ExplorerWidth {
1763 ExplorerWidth::DEFAULT
1764}
1765
1766pub(crate) mod explorer_width {
1770 use super::ExplorerWidth;
1771 use serde::de::{self, Deserialize, Deserializer};
1772 use std::str::FromStr;
1773
1774 pub fn deserialize_optional<'de, D>(d: D) -> Result<Option<ExplorerWidth>, D::Error>
1779 where
1780 D: Deserializer<'de>,
1781 {
1782 let raw = Option::<serde_json::Value>::deserialize(d)?;
1783 match raw {
1784 None | Some(serde_json::Value::Null) => Ok(None),
1785 Some(v) => from_value(&v).map(Some),
1786 }
1787 }
1788
1789 pub(super) fn from_value<E: de::Error>(v: &serde_json::Value) -> Result<ExplorerWidth, E> {
1790 match v {
1791 serde_json::Value::String(s) => ExplorerWidth::from_str(s).map_err(E::custom),
1792 serde_json::Value::Number(n) => {
1793 if let Some(u) = n.as_u64() {
1794 if u > 100 {
1798 return Err(E::custom(format!(
1799 "explorer width: {} exceeds 100 (percent). Use \"{}\" for columns.",
1800 u, u
1801 )));
1802 }
1803 Ok(ExplorerWidth::Percent(u as u8))
1804 } else if let Some(f) = n.as_f64() {
1805 let pct = if (0.0..=1.0).contains(&f) {
1807 f * 100.0
1808 } else {
1809 f
1810 };
1811 if !(0.0..=100.0).contains(&pct) {
1812 return Err(E::custom(format!(
1813 "explorer width: percent {} out of range 0..=100",
1814 pct
1815 )));
1816 }
1817 Ok(ExplorerWidth::Percent(pct.round() as u8))
1818 } else {
1819 Err(E::custom("explorer width: unsupported number"))
1820 }
1821 }
1822 _ => Err(E::custom(
1823 "explorer width: expected \"30%\", \"24\" (columns), or a number",
1824 )),
1825 }
1826 }
1827}
1828
1829#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1840pub struct ClipboardConfig {
1841 #[serde(default = "default_true")]
1844 pub use_osc52: bool,
1845
1846 #[serde(default = "default_true")]
1849 pub use_system_clipboard: bool,
1850}
1851
1852impl Default for ClipboardConfig {
1853 fn default() -> Self {
1854 Self {
1855 use_osc52: true,
1856 use_system_clipboard: true,
1857 }
1858 }
1859}
1860
1861#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1863pub struct TerminalConfig {
1864 #[serde(default = "default_true")]
1867 pub jump_to_end_on_output: bool,
1868
1869 #[serde(default)]
1881 pub shell: Option<TerminalShellConfig>,
1882
1883 #[serde(default = "default_true")]
1895 pub skip_app_execution_alias: bool,
1896}
1897
1898impl Default for TerminalConfig {
1899 fn default() -> Self {
1900 Self {
1901 jump_to_end_on_output: true,
1902 shell: None,
1903 skip_app_execution_alias: true,
1904 }
1905 }
1906}
1907
1908#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1910pub struct TerminalShellConfig {
1911 pub command: String,
1914
1915 #[serde(default)]
1917 pub args: Vec<String>,
1918}
1919
1920#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1922pub struct WarningsConfig {
1923 #[serde(default = "default_true")]
1926 pub show_status_indicator: bool,
1927}
1928
1929impl Default for WarningsConfig {
1930 fn default() -> Self {
1931 Self {
1932 show_status_indicator: true,
1933 }
1934 }
1935}
1936
1937#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1939pub struct PackagesConfig {
1940 #[serde(default = "default_package_sources")]
1943 pub sources: Vec<String>,
1944}
1945
1946fn default_package_sources() -> Vec<String> {
1947 vec!["https://github.com/sinelaw/fresh-plugins-registry".to_string()]
1948}
1949
1950impl Default for PackagesConfig {
1951 fn default() -> Self {
1952 Self {
1953 sources: default_package_sources(),
1954 }
1955 }
1956}
1957
1958pub use fresh_core::config::PluginConfig;
1960
1961impl Default for FileExplorerConfig {
1962 fn default() -> Self {
1963 Self {
1964 respect_gitignore: true,
1965 show_hidden: false,
1966 show_gitignored: false,
1967 custom_ignore_patterns: Vec::new(),
1968 width: default_explorer_width(),
1969 preview_tabs: true,
1970 side: default_explorer_side(),
1971 auto_open_on_last_buffer_close: true,
1972 follow_active_buffer: false,
1973 compact_directories: true,
1974 tree_indicator_collapsed: default_tree_indicator_collapsed(),
1975 tree_indicator_expanded: default_tree_indicator_expanded(),
1976 }
1977 }
1978}
1979
1980#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
1982pub struct FileBrowserConfig {
1983 #[serde(default = "default_false")]
1985 pub show_hidden: bool,
1986}
1987
1988#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1990pub struct KeyPress {
1991 pub key: String,
1993 #[serde(default)]
1995 pub modifiers: Vec<String>,
1996}
1997
1998#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2000#[schemars(extend("x-display-field" = "/action"))]
2001pub struct Keybinding {
2002 #[serde(default, skip_serializing_if = "String::is_empty")]
2004 pub key: String,
2005
2006 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2008 pub modifiers: Vec<String>,
2009
2010 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2013 pub keys: Vec<KeyPress>,
2014
2015 pub action: String,
2017
2018 #[serde(default)]
2020 pub args: HashMap<String, serde_json::Value>,
2021
2022 #[serde(default)]
2024 pub when: Option<String>,
2025}
2026
2027#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2029#[schemars(extend("x-display-field" = "/inherits"))]
2030pub struct KeymapConfig {
2031 #[serde(default, skip_serializing_if = "Option::is_none")]
2033 pub inherits: Option<String>,
2034
2035 #[serde(default)]
2037 pub bindings: Vec<Keybinding>,
2038}
2039
2040#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2042#[schemars(extend("x-display-field" = "/command"))]
2043pub struct FormatterConfig {
2044 pub command: String,
2046
2047 #[serde(default)]
2050 pub args: Vec<String>,
2051
2052 #[serde(default = "default_true")]
2055 pub stdin: bool,
2056
2057 #[serde(default = "default_on_save_timeout")]
2059 pub timeout_ms: u64,
2060}
2061
2062#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2064#[schemars(extend("x-display-field" = "/command"))]
2065pub struct OnSaveAction {
2066 pub command: String,
2069
2070 #[serde(default)]
2073 pub args: Vec<String>,
2074
2075 #[serde(default)]
2077 pub working_dir: Option<String>,
2078
2079 #[serde(default)]
2081 pub stdin: bool,
2082
2083 #[serde(default = "default_on_save_timeout")]
2085 pub timeout_ms: u64,
2086
2087 #[serde(default = "default_true")]
2090 pub enabled: bool,
2091}
2092
2093fn default_on_save_timeout() -> u64 {
2094 10000
2095}
2096
2097fn default_page_width() -> Option<usize> {
2098 Some(80)
2099}
2100
2101#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2103#[schemars(extend("x-display-field" = "/grammar"))]
2104pub struct LanguageConfig {
2105 #[serde(default)]
2107 pub extensions: Vec<String>,
2108
2109 #[serde(default)]
2111 pub filenames: Vec<String>,
2112
2113 #[serde(default)]
2115 pub grammar: String,
2116
2117 #[serde(default)]
2119 pub comment_prefix: Option<String>,
2120
2121 #[serde(default = "default_true")]
2123 pub auto_indent: bool,
2124
2125 #[serde(default)]
2128 pub auto_close: Option<bool>,
2129
2130 #[serde(default)]
2133 pub auto_surround: Option<bool>,
2134
2135 #[serde(default)]
2138 pub textmate_grammar: Option<std::path::PathBuf>,
2139
2140 #[serde(default = "default_true")]
2143 pub show_whitespace_tabs: bool,
2144
2145 #[serde(default)]
2150 pub line_wrap: Option<bool>,
2151
2152 #[serde(default)]
2156 pub wrap_column: Option<usize>,
2157
2158 #[serde(default)]
2163 pub page_view: Option<bool>,
2164
2165 #[serde(default)]
2170 pub page_width: Option<usize>,
2171
2172 #[serde(default)]
2176 pub use_tabs: Option<bool>,
2177
2178 #[serde(default)]
2182 pub tab_size: Option<usize>,
2183
2184 #[serde(default)]
2186 pub formatter: Option<FormatterConfig>,
2187
2188 #[serde(default)]
2190 pub format_on_save: bool,
2191
2192 #[serde(default)]
2196 pub on_save: Vec<OnSaveAction>,
2197
2198 #[serde(default)]
2208 pub word_characters: Option<String>,
2209}
2210
2211#[derive(Debug, Clone)]
2218pub struct BufferConfig {
2219 pub tab_size: usize,
2221
2222 pub use_tabs: bool,
2224
2225 pub auto_indent: bool,
2227
2228 pub auto_close: bool,
2230
2231 pub auto_surround: bool,
2233
2234 pub line_wrap: bool,
2236
2237 pub wrap_column: Option<usize>,
2239
2240 pub whitespace: WhitespaceVisibility,
2242
2243 pub formatter: Option<FormatterConfig>,
2245
2246 pub format_on_save: bool,
2248
2249 pub on_save: Vec<OnSaveAction>,
2251
2252 pub textmate_grammar: Option<std::path::PathBuf>,
2254
2255 pub word_characters: String,
2258}
2259
2260impl BufferConfig {
2261 pub fn resolve(global_config: &Config, language_id: Option<&str>) -> Self {
2270 let editor = &global_config.editor;
2271
2272 let mut whitespace = WhitespaceVisibility::from_editor_config(editor);
2274 let mut config = BufferConfig {
2275 tab_size: editor.tab_size,
2276 use_tabs: editor.use_tabs,
2277 auto_indent: editor.auto_indent,
2278 auto_close: editor.auto_close,
2279 auto_surround: editor.auto_surround,
2280 line_wrap: editor.line_wrap,
2281 wrap_column: editor.wrap_column,
2282 whitespace,
2283 formatter: None,
2284 format_on_save: false,
2285 on_save: Vec::new(),
2286 textmate_grammar: None,
2287 word_characters: String::new(),
2288 };
2289
2290 let lang_config_ref = language_id
2294 .and_then(|id| global_config.languages.get(id))
2295 .or_else(|| {
2296 match language_id {
2298 None | Some("text") => global_config
2299 .default_language
2300 .as_deref()
2301 .and_then(|lang| global_config.languages.get(lang)),
2302 _ => None,
2303 }
2304 });
2305 if let Some(lang_config) = lang_config_ref {
2306 if let Some(ts) = lang_config.tab_size {
2308 config.tab_size = ts;
2309 }
2310
2311 if let Some(use_tabs) = lang_config.use_tabs {
2313 config.use_tabs = use_tabs;
2314 }
2315
2316 if let Some(line_wrap) = lang_config.line_wrap {
2318 config.line_wrap = line_wrap;
2319 }
2320
2321 if lang_config.wrap_column.is_some() {
2323 config.wrap_column = lang_config.wrap_column;
2324 }
2325
2326 config.auto_indent = lang_config.auto_indent;
2328
2329 if config.auto_close {
2331 if let Some(lang_auto_close) = lang_config.auto_close {
2332 config.auto_close = lang_auto_close;
2333 }
2334 }
2335
2336 if config.auto_surround {
2338 if let Some(lang_auto_surround) = lang_config.auto_surround {
2339 config.auto_surround = lang_auto_surround;
2340 }
2341 }
2342
2343 whitespace = whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
2345 config.whitespace = whitespace;
2346
2347 config.formatter = lang_config.formatter.clone();
2349
2350 config.format_on_save = lang_config.format_on_save;
2352
2353 config.on_save = lang_config.on_save.clone();
2355
2356 config.textmate_grammar = lang_config.textmate_grammar.clone();
2358
2359 if let Some(ref wc) = lang_config.word_characters {
2361 config.word_characters = wc.clone();
2362 }
2363 }
2364
2365 config
2366 }
2367
2368 pub fn indent_string(&self) -> String {
2373 if self.use_tabs {
2374 "\t".to_string()
2375 } else {
2376 " ".repeat(self.tab_size)
2377 }
2378 }
2379}
2380
2381#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
2383pub struct MenuConfig {
2384 #[serde(default)]
2386 pub menus: Vec<Menu>,
2387}
2388
2389pub use fresh_core::menu::{Menu, MenuItem};
2391
2392pub trait MenuExt {
2394 fn match_id(&self) -> &str;
2397
2398 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path);
2401}
2402
2403impl MenuExt for Menu {
2404 fn match_id(&self) -> &str {
2405 self.id.as_deref().unwrap_or(&self.label)
2406 }
2407
2408 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path) {
2409 self.items = self
2410 .items
2411 .iter()
2412 .map(|item| item.expand_dynamic(themes_dir))
2413 .collect();
2414 }
2415}
2416
2417pub trait MenuItemExt {
2419 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem;
2422}
2423
2424impl MenuItemExt for MenuItem {
2425 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem {
2426 match self {
2427 MenuItem::DynamicSubmenu { label, source } => {
2428 let items = generate_dynamic_items(source, themes_dir);
2429 MenuItem::Submenu {
2430 label: label.clone(),
2431 items,
2432 }
2433 }
2434 other => other.clone(),
2435 }
2436 }
2437}
2438
2439#[cfg(feature = "runtime")]
2441pub fn generate_dynamic_items(source: &str, themes_dir: &std::path::Path) -> Vec<MenuItem> {
2442 match source {
2443 "copy_with_theme" => {
2444 let loader = crate::view::theme::ThemeLoader::new(themes_dir.to_path_buf());
2446 let registry = loader.load_all(&[]);
2447 registry
2448 .list()
2449 .iter()
2450 .map(|info| {
2451 let mut args = HashMap::new();
2452 args.insert("theme".to_string(), serde_json::json!(info.key));
2453 MenuItem::Action {
2454 label: info.name.clone(),
2455 action: "copy_with_theme".to_string(),
2456 args,
2457 when: Some(context_keys::HAS_SELECTION.to_string()),
2458 checkbox: None,
2459 }
2460 })
2461 .collect()
2462 }
2463 _ => vec![MenuItem::Label {
2464 info: format!("Unknown source: {}", source),
2465 }],
2466 }
2467}
2468
2469#[cfg(not(feature = "runtime"))]
2471pub fn generate_dynamic_items(_source: &str, _themes_dir: &std::path::Path) -> Vec<MenuItem> {
2472 vec![]
2474}
2475
2476impl Default for Config {
2477 fn default() -> Self {
2478 Self {
2479 version: 0,
2480 theme: default_theme_name(),
2481 locale: LocaleName::default(),
2482 check_for_updates: true,
2483 editor: EditorConfig::default(),
2484 file_explorer: FileExplorerConfig::default(),
2485 file_browser: FileBrowserConfig::default(),
2486 clipboard: ClipboardConfig::default(),
2487 terminal: TerminalConfig::default(),
2488 keybindings: vec![], keybinding_maps: HashMap::new(), active_keybinding_map: default_keybinding_map_name(),
2491 languages: Self::default_languages(),
2492 default_language: None,
2493 lsp: Self::default_lsp_config(),
2494 universal_lsp: Self::default_universal_lsp_config(),
2495 warnings: WarningsConfig::default(),
2496 plugins: HashMap::new(), packages: PackagesConfig::default(),
2498 }
2499 }
2500}
2501
2502impl MenuConfig {
2503 pub fn translated() -> Self {
2505 Self {
2506 menus: Self::translated_menus(),
2507 }
2508 }
2509
2510 pub fn translated_menus() -> Vec<Menu> {
2516 vec![
2517 Menu {
2519 id: Some("File".to_string()),
2520 label: t!("menu.file").to_string(),
2521 when: None,
2522 items: vec![
2523 MenuItem::Action {
2524 label: t!("menu.file.new_file").to_string(),
2525 action: "new".to_string(),
2526 args: HashMap::new(),
2527 when: None,
2528 checkbox: None,
2529 },
2530 MenuItem::Action {
2531 label: t!("menu.file.open_file").to_string(),
2532 action: "open".to_string(),
2533 args: HashMap::new(),
2534 when: None,
2535 checkbox: None,
2536 },
2537 MenuItem::Separator { separator: true },
2538 MenuItem::Action {
2539 label: t!("menu.file.save").to_string(),
2540 action: "save".to_string(),
2541 args: HashMap::new(),
2542 when: Some(context_keys::HAS_BUFFER.to_string()),
2543 checkbox: None,
2544 },
2545 MenuItem::Action {
2546 label: t!("menu.file.save_as").to_string(),
2547 action: "save_as".to_string(),
2548 args: HashMap::new(),
2549 when: Some(context_keys::HAS_BUFFER.to_string()),
2550 checkbox: None,
2551 },
2552 MenuItem::Action {
2553 label: t!("menu.file.revert").to_string(),
2554 action: "revert".to_string(),
2555 args: HashMap::new(),
2556 when: Some(context_keys::HAS_BUFFER.to_string()),
2557 checkbox: None,
2558 },
2559 MenuItem::Action {
2560 label: t!("menu.file.reload_with_encoding").to_string(),
2561 action: "reload_with_encoding".to_string(),
2562 args: HashMap::new(),
2563 when: Some(context_keys::HAS_BUFFER.to_string()),
2564 checkbox: None,
2565 },
2566 MenuItem::Separator { separator: true },
2567 MenuItem::Action {
2568 label: t!("menu.file.close_buffer").to_string(),
2569 action: "close".to_string(),
2570 args: HashMap::new(),
2571 when: Some(context_keys::HAS_BUFFER.to_string()),
2572 checkbox: None,
2573 },
2574 MenuItem::Separator { separator: true },
2575 MenuItem::Action {
2576 label: t!("menu.file.switch_project").to_string(),
2577 action: "switch_project".to_string(),
2578 args: HashMap::new(),
2579 when: None,
2580 checkbox: None,
2581 },
2582 MenuItem::Separator { separator: true },
2583 MenuItem::Action {
2584 label: t!("menu.file.detach").to_string(),
2585 action: "detach".to_string(),
2586 args: HashMap::new(),
2587 when: Some(context_keys::SESSION_MODE.to_string()),
2588 checkbox: None,
2589 },
2590 MenuItem::Action {
2591 label: t!("menu.file.quit").to_string(),
2592 action: "quit".to_string(),
2593 args: HashMap::new(),
2594 when: None,
2595 checkbox: None,
2596 },
2597 ],
2598 },
2599 Menu {
2601 id: Some("Edit".to_string()),
2602 label: t!("menu.edit").to_string(),
2603 when: None,
2604 items: vec![
2605 MenuItem::Action {
2606 label: t!("menu.edit.undo").to_string(),
2607 action: "undo".to_string(),
2608 args: HashMap::new(),
2609 when: Some(context_keys::HAS_BUFFER.to_string()),
2610 checkbox: None,
2611 },
2612 MenuItem::Action {
2613 label: t!("menu.edit.redo").to_string(),
2614 action: "redo".to_string(),
2615 args: HashMap::new(),
2616 when: Some(context_keys::HAS_BUFFER.to_string()),
2617 checkbox: None,
2618 },
2619 MenuItem::Separator { separator: true },
2620 MenuItem::Action {
2621 label: t!("menu.edit.cut").to_string(),
2622 action: "cut".to_string(),
2623 args: HashMap::new(),
2624 when: Some(context_keys::CAN_COPY.to_string()),
2625 checkbox: None,
2626 },
2627 MenuItem::Action {
2628 label: t!("menu.edit.copy").to_string(),
2629 action: "copy".to_string(),
2630 args: HashMap::new(),
2631 when: Some(context_keys::CAN_COPY.to_string()),
2632 checkbox: None,
2633 },
2634 MenuItem::DynamicSubmenu {
2635 label: t!("menu.edit.copy_with_formatting").to_string(),
2636 source: "copy_with_theme".to_string(),
2637 },
2638 MenuItem::Action {
2639 label: t!("menu.edit.paste").to_string(),
2640 action: "paste".to_string(),
2641 args: HashMap::new(),
2642 when: Some(context_keys::CAN_PASTE.to_string()),
2643 checkbox: None,
2644 },
2645 MenuItem::Separator { separator: true },
2646 MenuItem::Action {
2647 label: t!("menu.edit.select_all").to_string(),
2648 action: "select_all".to_string(),
2649 args: HashMap::new(),
2650 when: Some(context_keys::HAS_BUFFER.to_string()),
2651 checkbox: None,
2652 },
2653 MenuItem::Separator { separator: true },
2654 MenuItem::Action {
2655 label: t!("menu.edit.find").to_string(),
2656 action: "search".to_string(),
2657 args: HashMap::new(),
2658 when: Some(context_keys::HAS_BUFFER.to_string()),
2659 checkbox: None,
2660 },
2661 MenuItem::Action {
2662 label: t!("menu.edit.find_in_selection").to_string(),
2663 action: "find_in_selection".to_string(),
2664 args: HashMap::new(),
2665 when: Some(context_keys::HAS_SELECTION.to_string()),
2666 checkbox: None,
2667 },
2668 MenuItem::Action {
2669 label: t!("menu.edit.find_next").to_string(),
2670 action: "find_next".to_string(),
2671 args: HashMap::new(),
2672 when: Some(context_keys::HAS_BUFFER.to_string()),
2673 checkbox: None,
2674 },
2675 MenuItem::Action {
2676 label: t!("menu.edit.find_previous").to_string(),
2677 action: "find_previous".to_string(),
2678 args: HashMap::new(),
2679 when: Some(context_keys::HAS_BUFFER.to_string()),
2680 checkbox: None,
2681 },
2682 MenuItem::Action {
2683 label: t!("menu.edit.replace").to_string(),
2684 action: "query_replace".to_string(),
2685 args: HashMap::new(),
2686 when: Some(context_keys::HAS_BUFFER.to_string()),
2687 checkbox: None,
2688 },
2689 MenuItem::Separator { separator: true },
2690 MenuItem::Action {
2691 label: t!("menu.edit.delete_line").to_string(),
2692 action: "delete_line".to_string(),
2693 args: HashMap::new(),
2694 when: Some(context_keys::HAS_BUFFER.to_string()),
2695 checkbox: None,
2696 },
2697 MenuItem::Action {
2698 label: t!("menu.edit.format_buffer").to_string(),
2699 action: "format_buffer".to_string(),
2700 args: HashMap::new(),
2701 when: Some(context_keys::FORMATTER_AVAILABLE.to_string()),
2702 checkbox: None,
2703 },
2704 MenuItem::Separator { separator: true },
2705 MenuItem::Action {
2706 label: t!("menu.edit.settings").to_string(),
2707 action: "open_settings".to_string(),
2708 args: HashMap::new(),
2709 when: None,
2710 checkbox: None,
2711 },
2712 MenuItem::Action {
2713 label: t!("menu.edit.keybinding_editor").to_string(),
2714 action: "open_keybinding_editor".to_string(),
2715 args: HashMap::new(),
2716 when: None,
2717 checkbox: None,
2718 },
2719 ],
2720 },
2721 Menu {
2723 id: Some("View".to_string()),
2724 label: t!("menu.view").to_string(),
2725 when: None,
2726 items: vec![
2727 MenuItem::Action {
2728 label: t!("menu.view.file_explorer").to_string(),
2729 action: "toggle_file_explorer".to_string(),
2730 args: HashMap::new(),
2731 when: None,
2732 checkbox: Some(context_keys::FILE_EXPLORER.to_string()),
2733 },
2734 MenuItem::Separator { separator: true },
2735 MenuItem::Action {
2736 label: t!("menu.view.line_numbers").to_string(),
2737 action: "toggle_line_numbers".to_string(),
2738 args: HashMap::new(),
2739 when: None,
2740 checkbox: Some(context_keys::LINE_NUMBERS.to_string()),
2741 },
2742 MenuItem::Action {
2743 label: t!("menu.view.line_wrap").to_string(),
2744 action: "toggle_line_wrap".to_string(),
2745 args: HashMap::new(),
2746 when: None,
2747 checkbox: Some(context_keys::LINE_WRAP.to_string()),
2748 },
2749 MenuItem::Action {
2750 label: t!("menu.view.mouse_support").to_string(),
2751 action: "toggle_mouse_capture".to_string(),
2752 args: HashMap::new(),
2753 when: None,
2754 checkbox: Some(context_keys::MOUSE_CAPTURE.to_string()),
2755 },
2756 MenuItem::Separator { separator: true },
2757 MenuItem::Action {
2758 label: t!("menu.view.vertical_scrollbar").to_string(),
2759 action: "toggle_vertical_scrollbar".to_string(),
2760 args: HashMap::new(),
2761 when: None,
2762 checkbox: Some(context_keys::VERTICAL_SCROLLBAR.to_string()),
2763 },
2764 MenuItem::Action {
2765 label: t!("menu.view.horizontal_scrollbar").to_string(),
2766 action: "toggle_horizontal_scrollbar".to_string(),
2767 args: HashMap::new(),
2768 when: None,
2769 checkbox: Some(context_keys::HORIZONTAL_SCROLLBAR.to_string()),
2770 },
2771 MenuItem::Separator { separator: true },
2772 MenuItem::Action {
2773 label: t!("menu.view.set_background").to_string(),
2774 action: "set_background".to_string(),
2775 args: HashMap::new(),
2776 when: None,
2777 checkbox: None,
2778 },
2779 MenuItem::Action {
2780 label: t!("menu.view.set_background_blend").to_string(),
2781 action: "set_background_blend".to_string(),
2782 args: HashMap::new(),
2783 when: None,
2784 checkbox: None,
2785 },
2786 MenuItem::Action {
2787 label: t!("menu.view.set_page_width").to_string(),
2788 action: "set_page_width".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.view.select_theme").to_string(),
2796 action: "select_theme".to_string(),
2797 args: HashMap::new(),
2798 when: None,
2799 checkbox: None,
2800 },
2801 MenuItem::Action {
2802 label: t!("menu.view.select_locale").to_string(),
2803 action: "select_locale".to_string(),
2804 args: HashMap::new(),
2805 when: None,
2806 checkbox: None,
2807 },
2808 MenuItem::Action {
2809 label: t!("menu.view.settings").to_string(),
2810 action: "open_settings".to_string(),
2811 args: HashMap::new(),
2812 when: None,
2813 checkbox: None,
2814 },
2815 MenuItem::Action {
2816 label: t!("menu.view.calibrate_input").to_string(),
2817 action: "calibrate_input".to_string(),
2818 args: HashMap::new(),
2819 when: None,
2820 checkbox: None,
2821 },
2822 MenuItem::Separator { separator: true },
2823 MenuItem::Action {
2824 label: t!("menu.view.split_horizontal").to_string(),
2825 action: "split_horizontal".to_string(),
2826 args: HashMap::new(),
2827 when: Some(context_keys::HAS_BUFFER.to_string()),
2828 checkbox: None,
2829 },
2830 MenuItem::Action {
2831 label: t!("menu.view.split_vertical").to_string(),
2832 action: "split_vertical".to_string(),
2833 args: HashMap::new(),
2834 when: Some(context_keys::HAS_BUFFER.to_string()),
2835 checkbox: None,
2836 },
2837 MenuItem::Action {
2838 label: t!("menu.view.close_split").to_string(),
2839 action: "close_split".to_string(),
2840 args: HashMap::new(),
2841 when: Some(context_keys::HAS_BUFFER.to_string()),
2842 checkbox: None,
2843 },
2844 MenuItem::Action {
2845 label: t!("menu.view.scroll_sync").to_string(),
2846 action: "toggle_scroll_sync".to_string(),
2847 args: HashMap::new(),
2848 when: Some(context_keys::HAS_SAME_BUFFER_SPLITS.to_string()),
2849 checkbox: Some(context_keys::SCROLL_SYNC.to_string()),
2850 },
2851 MenuItem::Action {
2852 label: t!("menu.view.focus_next_split").to_string(),
2853 action: "next_split".to_string(),
2854 args: HashMap::new(),
2855 when: None,
2856 checkbox: None,
2857 },
2858 MenuItem::Action {
2859 label: t!("menu.view.focus_prev_split").to_string(),
2860 action: "prev_split".to_string(),
2861 args: HashMap::new(),
2862 when: None,
2863 checkbox: None,
2864 },
2865 MenuItem::Action {
2866 label: t!("menu.view.toggle_maximize_split").to_string(),
2867 action: "toggle_maximize_split".to_string(),
2868 args: HashMap::new(),
2869 when: None,
2870 checkbox: None,
2871 },
2872 MenuItem::Separator { separator: true },
2873 MenuItem::Submenu {
2874 label: t!("menu.terminal").to_string(),
2875 items: vec![
2876 MenuItem::Action {
2877 label: t!("menu.terminal.open").to_string(),
2878 action: "open_terminal".to_string(),
2879 args: HashMap::new(),
2880 when: None,
2881 checkbox: None,
2882 },
2883 MenuItem::Action {
2884 label: t!("menu.terminal.close").to_string(),
2885 action: "close_terminal".to_string(),
2886 args: HashMap::new(),
2887 when: None,
2888 checkbox: None,
2889 },
2890 MenuItem::Separator { separator: true },
2891 MenuItem::Action {
2892 label: t!("menu.terminal.toggle_keyboard_capture").to_string(),
2893 action: "toggle_keyboard_capture".to_string(),
2894 args: HashMap::new(),
2895 when: None,
2896 checkbox: None,
2897 },
2898 ],
2899 },
2900 MenuItem::Separator { separator: true },
2901 MenuItem::Submenu {
2902 label: t!("menu.view.keybinding_style").to_string(),
2903 items: vec![
2904 MenuItem::Action {
2905 label: t!("menu.view.keybinding_default").to_string(),
2906 action: "switch_keybinding_map".to_string(),
2907 args: {
2908 let mut map = HashMap::new();
2909 map.insert("map".to_string(), serde_json::json!("default"));
2910 map
2911 },
2912 when: None,
2913 checkbox: Some(context_keys::KEYMAP_DEFAULT.to_string()),
2914 },
2915 MenuItem::Action {
2916 label: t!("menu.view.keybinding_emacs").to_string(),
2917 action: "switch_keybinding_map".to_string(),
2918 args: {
2919 let mut map = HashMap::new();
2920 map.insert("map".to_string(), serde_json::json!("emacs"));
2921 map
2922 },
2923 when: None,
2924 checkbox: Some(context_keys::KEYMAP_EMACS.to_string()),
2925 },
2926 MenuItem::Action {
2927 label: t!("menu.view.keybinding_vscode").to_string(),
2928 action: "switch_keybinding_map".to_string(),
2929 args: {
2930 let mut map = HashMap::new();
2931 map.insert("map".to_string(), serde_json::json!("vscode"));
2932 map
2933 },
2934 when: None,
2935 checkbox: Some(context_keys::KEYMAP_VSCODE.to_string()),
2936 },
2937 MenuItem::Action {
2938 label: "macOS GUI (⌘)".to_string(),
2939 action: "switch_keybinding_map".to_string(),
2940 args: {
2941 let mut map = HashMap::new();
2942 map.insert("map".to_string(), serde_json::json!("macos-gui"));
2943 map
2944 },
2945 when: None,
2946 checkbox: Some(context_keys::KEYMAP_MACOS_GUI.to_string()),
2947 },
2948 ],
2949 },
2950 ],
2951 },
2952 Menu {
2954 id: Some("Selection".to_string()),
2955 label: t!("menu.selection").to_string(),
2956 when: Some(context_keys::HAS_BUFFER.to_string()),
2957 items: vec![
2958 MenuItem::Action {
2959 label: t!("menu.selection.select_all").to_string(),
2960 action: "select_all".to_string(),
2961 args: HashMap::new(),
2962 when: None,
2963 checkbox: None,
2964 },
2965 MenuItem::Action {
2966 label: t!("menu.selection.select_word").to_string(),
2967 action: "select_word".to_string(),
2968 args: HashMap::new(),
2969 when: None,
2970 checkbox: None,
2971 },
2972 MenuItem::Action {
2973 label: t!("menu.selection.select_line").to_string(),
2974 action: "select_line".to_string(),
2975 args: HashMap::new(),
2976 when: None,
2977 checkbox: None,
2978 },
2979 MenuItem::Action {
2980 label: t!("menu.selection.expand_selection").to_string(),
2981 action: "expand_selection".to_string(),
2982 args: HashMap::new(),
2983 when: None,
2984 checkbox: None,
2985 },
2986 MenuItem::Separator { separator: true },
2987 MenuItem::Action {
2988 label: t!("menu.selection.add_cursor_above").to_string(),
2989 action: "add_cursor_above".to_string(),
2990 args: HashMap::new(),
2991 when: None,
2992 checkbox: None,
2993 },
2994 MenuItem::Action {
2995 label: t!("menu.selection.add_cursor_below").to_string(),
2996 action: "add_cursor_below".to_string(),
2997 args: HashMap::new(),
2998 when: None,
2999 checkbox: None,
3000 },
3001 MenuItem::Action {
3002 label: t!("menu.selection.add_cursor_next_match").to_string(),
3003 action: "add_cursor_next_match".to_string(),
3004 args: HashMap::new(),
3005 when: None,
3006 checkbox: None,
3007 },
3008 MenuItem::Action {
3009 label: t!("menu.selection.add_cursors_to_line_ends").to_string(),
3010 action: "add_cursors_to_line_ends".to_string(),
3011 args: HashMap::new(),
3012 when: None,
3013 checkbox: None,
3014 },
3015 MenuItem::Action {
3016 label: t!("menu.selection.remove_secondary_cursors").to_string(),
3017 action: "remove_secondary_cursors".to_string(),
3018 args: HashMap::new(),
3019 when: None,
3020 checkbox: None,
3021 },
3022 ],
3023 },
3024 Menu {
3026 id: Some("Go".to_string()),
3027 label: t!("menu.go").to_string(),
3028 when: None,
3029 items: vec![
3030 MenuItem::Action {
3031 label: t!("menu.go.goto_line").to_string(),
3032 action: "goto_line".to_string(),
3033 args: HashMap::new(),
3034 when: Some(context_keys::HAS_BUFFER.to_string()),
3035 checkbox: None,
3036 },
3037 MenuItem::Action {
3038 label: t!("menu.go.goto_definition").to_string(),
3039 action: "lsp_goto_definition".to_string(),
3040 args: HashMap::new(),
3041 when: Some(context_keys::HAS_BUFFER.to_string()),
3042 checkbox: None,
3043 },
3044 MenuItem::Action {
3045 label: t!("menu.go.find_references").to_string(),
3046 action: "lsp_references".to_string(),
3047 args: HashMap::new(),
3048 when: Some(context_keys::HAS_BUFFER.to_string()),
3049 checkbox: None,
3050 },
3051 MenuItem::Separator { separator: true },
3052 MenuItem::Action {
3053 label: t!("menu.go.next_buffer").to_string(),
3054 action: "next_buffer".to_string(),
3055 args: HashMap::new(),
3056 when: Some(context_keys::HAS_BUFFER.to_string()),
3057 checkbox: None,
3058 },
3059 MenuItem::Action {
3060 label: t!("menu.go.prev_buffer").to_string(),
3061 action: "prev_buffer".to_string(),
3062 args: HashMap::new(),
3063 when: Some(context_keys::HAS_BUFFER.to_string()),
3064 checkbox: None,
3065 },
3066 MenuItem::Separator { separator: true },
3067 MenuItem::Action {
3068 label: t!("menu.go.command_palette").to_string(),
3069 action: "command_palette".to_string(),
3070 args: HashMap::new(),
3071 when: None,
3072 checkbox: None,
3073 },
3074 ],
3075 },
3076 Menu {
3078 id: Some("LSP".to_string()),
3079 label: t!("menu.lsp").to_string(),
3080 when: None,
3081 items: vec![
3082 MenuItem::Action {
3083 label: t!("menu.lsp.show_hover").to_string(),
3084 action: "lsp_hover".to_string(),
3085 args: HashMap::new(),
3086 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3087 checkbox: None,
3088 },
3089 MenuItem::Action {
3090 label: t!("menu.lsp.goto_definition").to_string(),
3091 action: "lsp_goto_definition".to_string(),
3092 args: HashMap::new(),
3093 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3094 checkbox: None,
3095 },
3096 MenuItem::Action {
3097 label: t!("menu.lsp.find_references").to_string(),
3098 action: "lsp_references".to_string(),
3099 args: HashMap::new(),
3100 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3101 checkbox: None,
3102 },
3103 MenuItem::Action {
3104 label: t!("menu.lsp.rename_symbol").to_string(),
3105 action: "lsp_rename".to_string(),
3106 args: HashMap::new(),
3107 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3108 checkbox: None,
3109 },
3110 MenuItem::Separator { separator: true },
3111 MenuItem::Action {
3112 label: t!("menu.lsp.show_completions").to_string(),
3113 action: "lsp_completion".to_string(),
3114 args: HashMap::new(),
3115 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3116 checkbox: None,
3117 },
3118 MenuItem::Action {
3119 label: t!("menu.lsp.show_signature").to_string(),
3120 action: "lsp_signature_help".to_string(),
3121 args: HashMap::new(),
3122 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3123 checkbox: None,
3124 },
3125 MenuItem::Action {
3126 label: t!("menu.lsp.code_actions").to_string(),
3127 action: "lsp_code_actions".to_string(),
3128 args: HashMap::new(),
3129 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3130 checkbox: None,
3131 },
3132 MenuItem::Separator { separator: true },
3133 MenuItem::Action {
3134 label: t!("menu.lsp.toggle_inlay_hints").to_string(),
3135 action: "toggle_inlay_hints".to_string(),
3136 args: HashMap::new(),
3137 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3138 checkbox: Some(context_keys::INLAY_HINTS.to_string()),
3139 },
3140 MenuItem::Action {
3141 label: t!("menu.lsp.toggle_mouse_hover").to_string(),
3142 action: "toggle_mouse_hover".to_string(),
3143 args: HashMap::new(),
3144 when: None,
3145 checkbox: Some(context_keys::MOUSE_HOVER.to_string()),
3146 },
3147 MenuItem::Separator { separator: true },
3148 MenuItem::Action {
3149 label: t!("menu.lsp.show_status").to_string(),
3150 action: "show_lsp_status".to_string(),
3151 args: HashMap::new(),
3152 when: None,
3153 checkbox: None,
3154 },
3155 MenuItem::Action {
3156 label: t!("menu.lsp.restart_server").to_string(),
3157 action: "lsp_restart".to_string(),
3158 args: HashMap::new(),
3159 when: None,
3160 checkbox: None,
3161 },
3162 MenuItem::Action {
3163 label: t!("menu.lsp.stop_server").to_string(),
3164 action: "lsp_stop".to_string(),
3165 args: HashMap::new(),
3166 when: None,
3167 checkbox: None,
3168 },
3169 MenuItem::Separator { separator: true },
3170 MenuItem::Action {
3171 label: t!("menu.lsp.toggle_for_buffer").to_string(),
3172 action: "lsp_toggle_for_buffer".to_string(),
3173 args: HashMap::new(),
3174 when: Some(context_keys::HAS_BUFFER.to_string()),
3175 checkbox: None,
3176 },
3177 ],
3178 },
3179 Menu {
3181 id: Some("Explorer".to_string()),
3182 label: t!("menu.explorer").to_string(),
3183 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3184 items: vec![
3185 MenuItem::Action {
3186 label: t!("menu.explorer.new_file").to_string(),
3187 action: "file_explorer_new_file".to_string(),
3188 args: HashMap::new(),
3189 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3190 checkbox: None,
3191 },
3192 MenuItem::Action {
3193 label: t!("menu.explorer.new_folder").to_string(),
3194 action: "file_explorer_new_directory".to_string(),
3195 args: HashMap::new(),
3196 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3197 checkbox: None,
3198 },
3199 MenuItem::Separator { separator: true },
3200 MenuItem::Action {
3201 label: t!("menu.explorer.open").to_string(),
3202 action: "file_explorer_open".to_string(),
3203 args: HashMap::new(),
3204 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3205 checkbox: None,
3206 },
3207 MenuItem::Action {
3208 label: t!("menu.explorer.rename").to_string(),
3209 action: "file_explorer_rename".to_string(),
3210 args: HashMap::new(),
3211 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3212 checkbox: None,
3213 },
3214 MenuItem::Action {
3215 label: t!("menu.explorer.delete").to_string(),
3216 action: "file_explorer_delete".to_string(),
3217 args: HashMap::new(),
3218 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3219 checkbox: None,
3220 },
3221 MenuItem::Separator { separator: true },
3222 MenuItem::Action {
3223 label: t!("menu.explorer.cut").to_string(),
3224 action: "cut".to_string(),
3225 args: HashMap::new(),
3226 when: Some(context_keys::CAN_COPY.to_string()),
3227 checkbox: None,
3228 },
3229 MenuItem::Action {
3230 label: t!("menu.explorer.copy").to_string(),
3231 action: "copy".to_string(),
3232 args: HashMap::new(),
3233 when: Some(context_keys::CAN_COPY.to_string()),
3234 checkbox: None,
3235 },
3236 MenuItem::Action {
3237 label: t!("menu.explorer.paste").to_string(),
3238 action: "paste".to_string(),
3239 args: HashMap::new(),
3240 when: Some(context_keys::CAN_PASTE.to_string()),
3241 checkbox: None,
3242 },
3243 MenuItem::Separator { separator: true },
3244 MenuItem::Action {
3245 label: t!("menu.explorer.refresh").to_string(),
3246 action: "file_explorer_refresh".to_string(),
3247 args: HashMap::new(),
3248 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3249 checkbox: None,
3250 },
3251 MenuItem::Separator { separator: true },
3252 MenuItem::Action {
3253 label: t!("menu.explorer.show_hidden").to_string(),
3254 action: "file_explorer_toggle_hidden".to_string(),
3255 args: HashMap::new(),
3256 when: Some(context_keys::FILE_EXPLORER.to_string()),
3257 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_HIDDEN.to_string()),
3258 },
3259 MenuItem::Action {
3260 label: t!("menu.explorer.show_gitignored").to_string(),
3261 action: "file_explorer_toggle_gitignored".to_string(),
3262 args: HashMap::new(),
3263 when: Some(context_keys::FILE_EXPLORER.to_string()),
3264 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_GITIGNORED.to_string()),
3265 },
3266 ],
3267 },
3268 Menu {
3270 id: Some("Help".to_string()),
3271 label: t!("menu.help").to_string(),
3272 when: None,
3273 items: vec![
3274 MenuItem::Label {
3275 info: format!("Fresh v{}", env!("CARGO_PKG_VERSION")),
3276 },
3277 MenuItem::Separator { separator: true },
3278 MenuItem::Action {
3279 label: t!("menu.help.show_manual").to_string(),
3280 action: "show_help".to_string(),
3281 args: HashMap::new(),
3282 when: None,
3283 checkbox: None,
3284 },
3285 MenuItem::Action {
3286 label: t!("menu.help.keyboard_shortcuts").to_string(),
3287 action: "keyboard_shortcuts".to_string(),
3288 args: HashMap::new(),
3289 when: None,
3290 checkbox: None,
3291 },
3292 MenuItem::Separator { separator: true },
3293 MenuItem::Action {
3294 label: t!("menu.help.event_debug").to_string(),
3295 action: "event_debug".to_string(),
3296 args: HashMap::new(),
3297 when: None,
3298 checkbox: None,
3299 },
3300 ],
3301 },
3302 ]
3303 }
3304}
3305
3306impl Config {
3307 pub(crate) const FILENAME: &'static str = "config.json";
3309
3310 pub(crate) fn normalize_zero_sentinels(&mut self) {
3324 if self.editor.wrap_column == Some(0) {
3325 self.editor.wrap_column = None;
3326 }
3327 if self.editor.page_width == Some(0) {
3328 self.editor.page_width = None;
3329 }
3330 if self.editor.tab_size == 0 {
3331 self.editor.tab_size = default_tab_size();
3332 }
3333 for lang in self.languages.values_mut() {
3334 if lang.wrap_column == Some(0) {
3335 lang.wrap_column = None;
3336 }
3337 if lang.page_width == Some(0) {
3338 lang.page_width = None;
3339 }
3340 if lang.tab_size == Some(0) {
3341 lang.tab_size = None;
3342 }
3343 }
3344 }
3345
3346 pub fn apply_runtime_flags(&self) {
3351 #[cfg(windows)]
3352 {
3353 crate::services::terminal::set_skip_app_execution_alias(
3354 self.terminal.skip_app_execution_alias,
3355 );
3356 }
3357 }
3358
3359 pub(crate) fn local_config_path(working_dir: &Path) -> std::path::PathBuf {
3361 working_dir.join(Self::FILENAME)
3362 }
3363
3364 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
3370 let contents = std::fs::read_to_string(path.as_ref())
3371 .map_err(|e| ConfigError::IoError(e.to_string()))?;
3372
3373 let partial: crate::partial_config::PartialConfig =
3375 serde_json::from_str(&contents).map_err(|e| ConfigError::ParseError(e.to_string()))?;
3376
3377 Ok(partial.resolve())
3378 }
3379
3380 fn load_builtin_keymap(name: &str) -> Option<KeymapConfig> {
3382 let json_content = match name {
3383 "default" => include_str!("../keymaps/default.json"),
3384 "emacs" => include_str!("../keymaps/emacs.json"),
3385 "vscode" => include_str!("../keymaps/vscode.json"),
3386 "macos" => include_str!("../keymaps/macos.json"),
3387 "macos-gui" => include_str!("../keymaps/macos-gui.json"),
3388 _ => return None,
3389 };
3390
3391 match serde_json::from_str(json_content) {
3392 Ok(config) => Some(config),
3393 Err(e) => {
3394 eprintln!("Failed to parse builtin keymap '{}': {}", name, e);
3395 None
3396 }
3397 }
3398 }
3399
3400 pub fn resolve_keymap(&self, map_name: &str) -> Vec<Keybinding> {
3403 let mut visited = std::collections::HashSet::new();
3404 self.resolve_keymap_recursive(map_name, &mut visited)
3405 }
3406
3407 fn resolve_keymap_recursive(
3409 &self,
3410 map_name: &str,
3411 visited: &mut std::collections::HashSet<String>,
3412 ) -> Vec<Keybinding> {
3413 if visited.contains(map_name) {
3415 eprintln!(
3416 "Warning: Circular inheritance detected in keymap '{}'",
3417 map_name
3418 );
3419 return Vec::new();
3420 }
3421 visited.insert(map_name.to_string());
3422
3423 let keymap = self
3425 .keybinding_maps
3426 .get(map_name)
3427 .cloned()
3428 .or_else(|| Self::load_builtin_keymap(map_name));
3429
3430 let Some(keymap) = keymap else {
3431 return Vec::new();
3432 };
3433
3434 let mut all_bindings = if let Some(ref parent_name) = keymap.inherits {
3436 self.resolve_keymap_recursive(parent_name, visited)
3437 } else {
3438 Vec::new()
3439 };
3440
3441 all_bindings.extend(keymap.bindings);
3443
3444 all_bindings
3445 }
3446 fn default_languages() -> HashMap<String, LanguageConfig> {
3448 let mut languages = HashMap::new();
3449
3450 languages.insert(
3451 "rust".to_string(),
3452 LanguageConfig {
3453 extensions: vec!["rs".to_string()],
3454 filenames: vec![],
3455 grammar: "rust".to_string(),
3456 comment_prefix: Some("//".to_string()),
3457 auto_indent: true,
3458 auto_close: None,
3459 auto_surround: None,
3460 textmate_grammar: None,
3461 show_whitespace_tabs: true,
3462 line_wrap: None,
3463 wrap_column: None,
3464 page_view: None,
3465 page_width: None,
3466 use_tabs: None,
3467 tab_size: None,
3468 formatter: Some(FormatterConfig {
3469 command: "rustfmt".to_string(),
3470 args: vec!["--edition".to_string(), "2021".to_string()],
3471 stdin: true,
3472 timeout_ms: 10000,
3473 }),
3474 format_on_save: false,
3475 on_save: vec![],
3476 word_characters: None,
3477 },
3478 );
3479
3480 languages.insert(
3481 "javascript".to_string(),
3482 LanguageConfig {
3483 extensions: vec!["js".to_string(), "jsx".to_string(), "mjs".to_string()],
3484 filenames: vec![],
3485 grammar: "javascript".to_string(),
3486 comment_prefix: Some("//".to_string()),
3487 auto_indent: true,
3488 auto_close: None,
3489 auto_surround: None,
3490 textmate_grammar: None,
3491 show_whitespace_tabs: true,
3492 line_wrap: None,
3493 wrap_column: None,
3494 page_view: None,
3495 page_width: None,
3496 use_tabs: None,
3497 tab_size: None,
3498 formatter: Some(FormatterConfig {
3499 command: "prettier".to_string(),
3500 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3501 stdin: true,
3502 timeout_ms: 10000,
3503 }),
3504 format_on_save: false,
3505 on_save: vec![],
3506 word_characters: None,
3507 },
3508 );
3509
3510 languages.insert(
3511 "typescript".to_string(),
3512 LanguageConfig {
3513 extensions: vec!["ts".to_string(), "tsx".to_string(), "mts".to_string()],
3514 filenames: vec![],
3515 grammar: "typescript".to_string(),
3516 comment_prefix: Some("//".to_string()),
3517 auto_indent: true,
3518 auto_close: None,
3519 auto_surround: None,
3520 textmate_grammar: None,
3521 show_whitespace_tabs: true,
3522 line_wrap: None,
3523 wrap_column: None,
3524 page_view: None,
3525 page_width: None,
3526 use_tabs: None,
3527 tab_size: None,
3528 formatter: Some(FormatterConfig {
3529 command: "prettier".to_string(),
3530 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3531 stdin: true,
3532 timeout_ms: 10000,
3533 }),
3534 format_on_save: false,
3535 on_save: vec![],
3536 word_characters: None,
3537 },
3538 );
3539
3540 languages.insert(
3541 "python".to_string(),
3542 LanguageConfig {
3543 extensions: vec!["py".to_string(), "pyi".to_string()],
3544 filenames: vec![],
3545 grammar: "python".to_string(),
3546 comment_prefix: Some("#".to_string()),
3547 auto_indent: true,
3548 auto_close: None,
3549 auto_surround: None,
3550 textmate_grammar: None,
3551 show_whitespace_tabs: true,
3552 line_wrap: None,
3553 wrap_column: None,
3554 page_view: None,
3555 page_width: None,
3556 use_tabs: None,
3557 tab_size: None,
3558 formatter: Some(FormatterConfig {
3559 command: "ruff".to_string(),
3560 args: vec![
3561 "format".to_string(),
3562 "--stdin-filename".to_string(),
3563 "$FILE".to_string(),
3564 ],
3565 stdin: true,
3566 timeout_ms: 10000,
3567 }),
3568 format_on_save: false,
3569 on_save: vec![],
3570 word_characters: None,
3571 },
3572 );
3573
3574 languages.insert(
3575 "c".to_string(),
3576 LanguageConfig {
3577 extensions: vec!["c".to_string(), "h".to_string()],
3578 filenames: vec![],
3579 grammar: "c".to_string(),
3580 comment_prefix: Some("//".to_string()),
3581 auto_indent: true,
3582 auto_close: None,
3583 auto_surround: None,
3584 textmate_grammar: None,
3585 show_whitespace_tabs: true,
3586 line_wrap: None,
3587 wrap_column: None,
3588 page_view: None,
3589 page_width: None,
3590 use_tabs: None,
3591 tab_size: None,
3592 formatter: Some(FormatterConfig {
3593 command: "clang-format".to_string(),
3594 args: vec![],
3595 stdin: true,
3596 timeout_ms: 10000,
3597 }),
3598 format_on_save: false,
3599 on_save: vec![],
3600 word_characters: None,
3601 },
3602 );
3603
3604 languages.insert(
3605 "cpp".to_string(),
3606 LanguageConfig {
3607 extensions: vec![
3608 "cpp".to_string(),
3609 "cc".to_string(),
3610 "cxx".to_string(),
3611 "hpp".to_string(),
3612 "hh".to_string(),
3613 "hxx".to_string(),
3614 ],
3615 filenames: vec![],
3616 grammar: "cpp".to_string(),
3617 comment_prefix: Some("//".to_string()),
3618 auto_indent: true,
3619 auto_close: None,
3620 auto_surround: None,
3621 textmate_grammar: None,
3622 show_whitespace_tabs: true,
3623 line_wrap: None,
3624 wrap_column: None,
3625 page_view: None,
3626 page_width: None,
3627 use_tabs: None,
3628 tab_size: None,
3629 formatter: Some(FormatterConfig {
3630 command: "clang-format".to_string(),
3631 args: vec![],
3632 stdin: true,
3633 timeout_ms: 10000,
3634 }),
3635 format_on_save: false,
3636 on_save: vec![],
3637 word_characters: None,
3638 },
3639 );
3640
3641 languages.insert(
3642 "csharp".to_string(),
3643 LanguageConfig {
3644 extensions: vec!["cs".to_string()],
3645 filenames: vec![],
3646 grammar: "C#".to_string(),
3647 comment_prefix: Some("//".to_string()),
3648 auto_indent: true,
3649 auto_close: None,
3650 auto_surround: None,
3651 textmate_grammar: None,
3652 show_whitespace_tabs: true,
3653 line_wrap: None,
3654 wrap_column: None,
3655 page_view: None,
3656 page_width: None,
3657 use_tabs: None,
3658 tab_size: None,
3659 formatter: None,
3660 format_on_save: false,
3661 on_save: vec![],
3662 word_characters: None,
3663 },
3664 );
3665
3666 languages.insert(
3667 "bash".to_string(),
3668 LanguageConfig {
3669 extensions: vec!["sh".to_string(), "bash".to_string()],
3670 filenames: vec![
3671 ".bash_aliases".to_string(),
3672 ".bash_logout".to_string(),
3673 ".bash_profile".to_string(),
3674 ".bashrc".to_string(),
3675 ".env".to_string(),
3676 ".profile".to_string(),
3677 ".zlogin".to_string(),
3678 ".zlogout".to_string(),
3679 ".zprofile".to_string(),
3680 ".zshenv".to_string(),
3681 ".zshrc".to_string(),
3682 "PKGBUILD".to_string(),
3684 "APKBUILD".to_string(),
3685 ],
3686 grammar: "bash".to_string(),
3687 comment_prefix: Some("#".to_string()),
3688 auto_indent: true,
3689 auto_close: None,
3690 auto_surround: None,
3691 textmate_grammar: None,
3692 show_whitespace_tabs: true,
3693 line_wrap: None,
3694 wrap_column: None,
3695 page_view: None,
3696 page_width: None,
3697 use_tabs: None,
3698 tab_size: None,
3699 formatter: None,
3700 format_on_save: false,
3701 on_save: vec![],
3702 word_characters: None,
3703 },
3704 );
3705
3706 languages.insert(
3707 "makefile".to_string(),
3708 LanguageConfig {
3709 extensions: vec!["mk".to_string()],
3710 filenames: vec![
3711 "Makefile".to_string(),
3712 "makefile".to_string(),
3713 "GNUmakefile".to_string(),
3714 ],
3715 grammar: "Makefile".to_string(),
3716 comment_prefix: Some("#".to_string()),
3717 auto_indent: false,
3718 auto_close: None,
3719 auto_surround: None,
3720 textmate_grammar: None,
3721 show_whitespace_tabs: true,
3722 line_wrap: None,
3723 wrap_column: None,
3724 page_view: None,
3725 page_width: None,
3726 use_tabs: Some(true), tab_size: Some(8), formatter: None,
3729 format_on_save: false,
3730 on_save: vec![],
3731 word_characters: None,
3732 },
3733 );
3734
3735 languages.insert(
3736 "dockerfile".to_string(),
3737 LanguageConfig {
3738 extensions: vec!["dockerfile".to_string()],
3739 filenames: vec!["Dockerfile".to_string(), "Containerfile".to_string()],
3740 grammar: "dockerfile".to_string(),
3741 comment_prefix: Some("#".to_string()),
3742 auto_indent: true,
3743 auto_close: None,
3744 auto_surround: None,
3745 textmate_grammar: None,
3746 show_whitespace_tabs: true,
3747 line_wrap: None,
3748 wrap_column: None,
3749 page_view: None,
3750 page_width: None,
3751 use_tabs: None,
3752 tab_size: None,
3753 formatter: None,
3754 format_on_save: false,
3755 on_save: vec![],
3756 word_characters: None,
3757 },
3758 );
3759
3760 languages.insert(
3761 "json".to_string(),
3762 LanguageConfig {
3763 extensions: vec!["json".to_string()],
3764 filenames: vec![],
3765 grammar: "json".to_string(),
3766 comment_prefix: None,
3767 auto_indent: true,
3768 auto_close: None,
3769 auto_surround: None,
3770 textmate_grammar: None,
3771 show_whitespace_tabs: true,
3772 line_wrap: None,
3773 wrap_column: None,
3774 page_view: None,
3775 page_width: None,
3776 use_tabs: None,
3777 tab_size: None,
3778 formatter: Some(FormatterConfig {
3779 command: "prettier".to_string(),
3780 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3781 stdin: true,
3782 timeout_ms: 10000,
3783 }),
3784 format_on_save: false,
3785 on_save: vec![],
3786 word_characters: None,
3787 },
3788 );
3789
3790 languages.insert(
3797 "jsonc".to_string(),
3798 LanguageConfig {
3799 extensions: vec!["jsonc".to_string()],
3800 filenames: vec![
3801 "devcontainer.json".to_string(),
3802 ".devcontainer.json".to_string(),
3803 "tsconfig.json".to_string(),
3804 "tsconfig.*.json".to_string(),
3805 "jsconfig.json".to_string(),
3806 "jsconfig.*.json".to_string(),
3807 ".eslintrc.json".to_string(),
3808 ".babelrc".to_string(),
3809 ".babelrc.json".to_string(),
3810 ".swcrc".to_string(),
3811 ".jshintrc".to_string(),
3812 ".hintrc".to_string(),
3813 "settings.json".to_string(),
3814 "keybindings.json".to_string(),
3815 "tasks.json".to_string(),
3816 "launch.json".to_string(),
3817 "extensions.json".to_string(),
3818 "argv.json".to_string(),
3819 ],
3820 grammar: "jsonc".to_string(),
3821 comment_prefix: Some("//".to_string()),
3822 auto_indent: true,
3823 auto_close: None,
3824 auto_surround: None,
3825 textmate_grammar: None,
3826 show_whitespace_tabs: true,
3827 line_wrap: None,
3828 wrap_column: None,
3829 page_view: None,
3830 page_width: None,
3831 use_tabs: None,
3832 tab_size: None,
3833 formatter: Some(FormatterConfig {
3834 command: "prettier".to_string(),
3835 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3836 stdin: true,
3837 timeout_ms: 10000,
3838 }),
3839 format_on_save: false,
3840 on_save: vec![],
3841 word_characters: None,
3842 },
3843 );
3844
3845 languages.insert(
3846 "toml".to_string(),
3847 LanguageConfig {
3848 extensions: vec!["toml".to_string()],
3849 filenames: vec!["Cargo.lock".to_string()],
3850 grammar: "toml".to_string(),
3851 comment_prefix: Some("#".to_string()),
3852 auto_indent: true,
3853 auto_close: None,
3854 auto_surround: None,
3855 textmate_grammar: None,
3856 show_whitespace_tabs: true,
3857 line_wrap: None,
3858 wrap_column: None,
3859 page_view: None,
3860 page_width: None,
3861 use_tabs: None,
3862 tab_size: None,
3863 formatter: None,
3864 format_on_save: false,
3865 on_save: vec![],
3866 word_characters: None,
3867 },
3868 );
3869
3870 languages.insert(
3871 "yaml".to_string(),
3872 LanguageConfig {
3873 extensions: vec!["yml".to_string(), "yaml".to_string()],
3874 filenames: vec![],
3875 grammar: "yaml".to_string(),
3876 comment_prefix: Some("#".to_string()),
3877 auto_indent: true,
3878 auto_close: None,
3879 auto_surround: None,
3880 textmate_grammar: None,
3881 show_whitespace_tabs: true,
3882 line_wrap: None,
3883 wrap_column: None,
3884 page_view: None,
3885 page_width: None,
3886 use_tabs: None,
3887 tab_size: None,
3888 formatter: Some(FormatterConfig {
3889 command: "prettier".to_string(),
3890 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3891 stdin: true,
3892 timeout_ms: 10000,
3893 }),
3894 format_on_save: false,
3895 on_save: vec![],
3896 word_characters: None,
3897 },
3898 );
3899
3900 languages.insert(
3901 "markdown".to_string(),
3902 LanguageConfig {
3903 extensions: vec!["md".to_string(), "markdown".to_string()],
3904 filenames: vec!["README".to_string()],
3905 grammar: "markdown".to_string(),
3906 comment_prefix: None,
3907 auto_indent: false,
3908 auto_close: None,
3909 auto_surround: None,
3910 textmate_grammar: None,
3911 show_whitespace_tabs: true,
3912 line_wrap: None,
3913 wrap_column: None,
3914 page_view: None,
3915 page_width: None,
3916 use_tabs: None,
3917 tab_size: None,
3918 formatter: None,
3919 format_on_save: false,
3920 on_save: vec![],
3921 word_characters: None,
3922 },
3923 );
3924
3925 languages.insert(
3927 "go".to_string(),
3928 LanguageConfig {
3929 extensions: vec!["go".to_string()],
3930 filenames: vec![],
3931 grammar: "go".to_string(),
3932 comment_prefix: Some("//".to_string()),
3933 auto_indent: true,
3934 auto_close: None,
3935 auto_surround: None,
3936 textmate_grammar: None,
3937 show_whitespace_tabs: false,
3938 line_wrap: None,
3939 wrap_column: None,
3940 page_view: None,
3941 page_width: None,
3942 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
3945 command: "gofmt".to_string(),
3946 args: vec![],
3947 stdin: true,
3948 timeout_ms: 10000,
3949 }),
3950 format_on_save: false,
3951 on_save: vec![],
3952 word_characters: None,
3953 },
3954 );
3955
3956 languages.insert(
3957 "odin".to_string(),
3958 LanguageConfig {
3959 extensions: vec!["odin".to_string()],
3960 filenames: vec![],
3961 grammar: "odin".to_string(),
3962 comment_prefix: Some("//".to_string()),
3963 auto_indent: true,
3964 auto_close: None,
3965 auto_surround: None,
3966 textmate_grammar: None,
3967 show_whitespace_tabs: false,
3968 line_wrap: None,
3969 wrap_column: None,
3970 page_view: None,
3971 page_width: None,
3972 use_tabs: Some(true),
3973 tab_size: Some(8),
3974 formatter: None,
3975 format_on_save: false,
3976 on_save: vec![],
3977 word_characters: None,
3978 },
3979 );
3980
3981 languages.insert(
3982 "zig".to_string(),
3983 LanguageConfig {
3984 extensions: vec!["zig".to_string(), "zon".to_string()],
3985 filenames: vec![],
3986 grammar: "zig".to_string(),
3987 comment_prefix: Some("//".to_string()),
3988 auto_indent: true,
3989 auto_close: None,
3990 auto_surround: None,
3991 textmate_grammar: None,
3992 show_whitespace_tabs: true,
3993 line_wrap: None,
3994 wrap_column: None,
3995 page_view: None,
3996 page_width: None,
3997 use_tabs: None,
3998 tab_size: None,
3999 formatter: None,
4000 format_on_save: false,
4001 on_save: vec![],
4002 word_characters: None,
4003 },
4004 );
4005
4006 languages.insert(
4007 "c3".to_string(),
4008 LanguageConfig {
4009 extensions: vec!["c3".to_string(), "c3i".to_string(), "c3t".to_string()],
4010 filenames: vec![],
4011 grammar: "c3".to_string(),
4012 comment_prefix: Some("//".to_string()),
4013 auto_indent: true,
4014 auto_close: None,
4015 auto_surround: None,
4016 textmate_grammar: None,
4017 show_whitespace_tabs: true,
4018 line_wrap: None,
4019 wrap_column: None,
4020 page_view: None,
4021 page_width: None,
4022 use_tabs: None,
4023 tab_size: None,
4024 formatter: None,
4025 format_on_save: false,
4026 on_save: vec![],
4027 word_characters: None,
4028 },
4029 );
4030
4031 languages.insert(
4032 "java".to_string(),
4033 LanguageConfig {
4034 extensions: vec!["java".to_string()],
4035 filenames: vec![],
4036 grammar: "java".to_string(),
4037 comment_prefix: Some("//".to_string()),
4038 auto_indent: true,
4039 auto_close: None,
4040 auto_surround: None,
4041 textmate_grammar: None,
4042 show_whitespace_tabs: true,
4043 line_wrap: None,
4044 wrap_column: None,
4045 page_view: None,
4046 page_width: None,
4047 use_tabs: None,
4048 tab_size: None,
4049 formatter: None,
4050 format_on_save: false,
4051 on_save: vec![],
4052 word_characters: None,
4053 },
4054 );
4055
4056 languages.insert(
4057 "latex".to_string(),
4058 LanguageConfig {
4059 extensions: vec![
4060 "tex".to_string(),
4061 "latex".to_string(),
4062 "ltx".to_string(),
4063 "sty".to_string(),
4064 "cls".to_string(),
4065 "bib".to_string(),
4066 ],
4067 filenames: vec![],
4068 grammar: "latex".to_string(),
4069 comment_prefix: Some("%".to_string()),
4070 auto_indent: true,
4071 auto_close: None,
4072 auto_surround: None,
4073 textmate_grammar: None,
4074 show_whitespace_tabs: true,
4075 line_wrap: None,
4076 wrap_column: None,
4077 page_view: None,
4078 page_width: None,
4079 use_tabs: None,
4080 tab_size: None,
4081 formatter: None,
4082 format_on_save: false,
4083 on_save: vec![],
4084 word_characters: None,
4085 },
4086 );
4087
4088 languages.insert(
4089 "templ".to_string(),
4090 LanguageConfig {
4091 extensions: vec!["templ".to_string()],
4092 filenames: vec![],
4093 grammar: "go".to_string(), comment_prefix: Some("//".to_string()),
4095 auto_indent: true,
4096 auto_close: None,
4097 auto_surround: None,
4098 textmate_grammar: None,
4099 show_whitespace_tabs: true,
4100 line_wrap: None,
4101 wrap_column: None,
4102 page_view: None,
4103 page_width: None,
4104 use_tabs: None,
4105 tab_size: None,
4106 formatter: None,
4107 format_on_save: false,
4108 on_save: vec![],
4109 word_characters: None,
4110 },
4111 );
4112
4113 languages.insert(
4115 "git-rebase".to_string(),
4116 LanguageConfig {
4117 extensions: vec![],
4118 filenames: vec!["git-rebase-todo".to_string()],
4119 grammar: "Git Rebase Todo".to_string(),
4120 comment_prefix: Some("#".to_string()),
4121 auto_indent: false,
4122 auto_close: None,
4123 auto_surround: None,
4124 textmate_grammar: None,
4125 show_whitespace_tabs: true,
4126 line_wrap: None,
4127 wrap_column: None,
4128 page_view: None,
4129 page_width: None,
4130 use_tabs: None,
4131 tab_size: None,
4132 formatter: None,
4133 format_on_save: false,
4134 on_save: vec![],
4135 word_characters: None,
4136 },
4137 );
4138
4139 languages.insert(
4140 "git-commit".to_string(),
4141 LanguageConfig {
4142 extensions: vec![],
4143 filenames: vec![
4144 "COMMIT_EDITMSG".to_string(),
4145 "MERGE_MSG".to_string(),
4146 "SQUASH_MSG".to_string(),
4147 "TAG_EDITMSG".to_string(),
4148 ],
4149 grammar: "Git Commit Message".to_string(),
4150 comment_prefix: Some("#".to_string()),
4151 auto_indent: false,
4152 auto_close: None,
4153 auto_surround: None,
4154 textmate_grammar: None,
4155 show_whitespace_tabs: true,
4156 line_wrap: None,
4157 wrap_column: None,
4158 page_view: None,
4159 page_width: None,
4160 use_tabs: None,
4161 tab_size: None,
4162 formatter: None,
4163 format_on_save: false,
4164 on_save: vec![],
4165 word_characters: None,
4166 },
4167 );
4168
4169 languages.insert(
4170 "gitignore".to_string(),
4171 LanguageConfig {
4172 extensions: vec!["gitignore".to_string()],
4173 filenames: vec![
4174 ".gitignore".to_string(),
4175 ".dockerignore".to_string(),
4176 ".npmignore".to_string(),
4177 ".hgignore".to_string(),
4178 ],
4179 grammar: "Gitignore".to_string(),
4180 comment_prefix: Some("#".to_string()),
4181 auto_indent: false,
4182 auto_close: None,
4183 auto_surround: None,
4184 textmate_grammar: None,
4185 show_whitespace_tabs: true,
4186 line_wrap: None,
4187 wrap_column: None,
4188 page_view: None,
4189 page_width: None,
4190 use_tabs: None,
4191 tab_size: None,
4192 formatter: None,
4193 format_on_save: false,
4194 on_save: vec![],
4195 word_characters: None,
4196 },
4197 );
4198
4199 languages.insert(
4200 "gitconfig".to_string(),
4201 LanguageConfig {
4202 extensions: vec!["gitconfig".to_string()],
4203 filenames: vec![".gitconfig".to_string(), ".gitmodules".to_string()],
4204 grammar: "Git Config".to_string(),
4205 comment_prefix: Some("#".to_string()),
4206 auto_indent: true,
4207 auto_close: None,
4208 auto_surround: None,
4209 textmate_grammar: None,
4210 show_whitespace_tabs: true,
4211 line_wrap: None,
4212 wrap_column: None,
4213 page_view: None,
4214 page_width: None,
4215 use_tabs: None,
4216 tab_size: None,
4217 formatter: None,
4218 format_on_save: false,
4219 on_save: vec![],
4220 word_characters: None,
4221 },
4222 );
4223
4224 languages.insert(
4225 "gitattributes".to_string(),
4226 LanguageConfig {
4227 extensions: vec!["gitattributes".to_string()],
4228 filenames: vec![".gitattributes".to_string()],
4229 grammar: "Git Attributes".to_string(),
4230 comment_prefix: Some("#".to_string()),
4231 auto_indent: false,
4232 auto_close: None,
4233 auto_surround: None,
4234 textmate_grammar: None,
4235 show_whitespace_tabs: true,
4236 line_wrap: None,
4237 wrap_column: None,
4238 page_view: None,
4239 page_width: None,
4240 use_tabs: None,
4241 tab_size: None,
4242 formatter: None,
4243 format_on_save: false,
4244 on_save: vec![],
4245 word_characters: None,
4246 },
4247 );
4248
4249 languages.insert(
4250 "typst".to_string(),
4251 LanguageConfig {
4252 extensions: vec!["typ".to_string()],
4253 filenames: vec![],
4254 grammar: "Typst".to_string(),
4255 comment_prefix: Some("//".to_string()),
4256 auto_indent: true,
4257 auto_close: None,
4258 auto_surround: None,
4259 textmate_grammar: None,
4260 show_whitespace_tabs: true,
4261 line_wrap: None,
4262 wrap_column: None,
4263 page_view: None,
4264 page_width: None,
4265 use_tabs: None,
4266 tab_size: None,
4267 formatter: None,
4268 format_on_save: false,
4269 on_save: vec![],
4270 word_characters: None,
4271 },
4272 );
4273
4274 languages.insert(
4279 "kotlin".to_string(),
4280 LanguageConfig {
4281 extensions: vec!["kt".to_string(), "kts".to_string()],
4282 filenames: vec![],
4283 grammar: "Kotlin".to_string(),
4284 comment_prefix: Some("//".to_string()),
4285 auto_indent: true,
4286 auto_close: None,
4287 auto_surround: None,
4288 textmate_grammar: None,
4289 show_whitespace_tabs: true,
4290 line_wrap: None,
4291 wrap_column: None,
4292 page_view: None,
4293 page_width: None,
4294 use_tabs: None,
4295 tab_size: None,
4296 formatter: None,
4297 format_on_save: false,
4298 on_save: vec![],
4299 word_characters: None,
4300 },
4301 );
4302
4303 languages.insert(
4304 "swift".to_string(),
4305 LanguageConfig {
4306 extensions: vec!["swift".to_string()],
4307 filenames: vec![],
4308 grammar: "Swift".to_string(),
4309 comment_prefix: Some("//".to_string()),
4310 auto_indent: true,
4311 auto_close: None,
4312 auto_surround: None,
4313 textmate_grammar: None,
4314 show_whitespace_tabs: true,
4315 line_wrap: None,
4316 wrap_column: None,
4317 page_view: None,
4318 page_width: None,
4319 use_tabs: None,
4320 tab_size: None,
4321 formatter: None,
4322 format_on_save: false,
4323 on_save: vec![],
4324 word_characters: None,
4325 },
4326 );
4327
4328 languages.insert(
4329 "scala".to_string(),
4330 LanguageConfig {
4331 extensions: vec!["scala".to_string(), "sc".to_string()],
4332 filenames: vec![],
4333 grammar: "Scala".to_string(),
4334 comment_prefix: Some("//".to_string()),
4335 auto_indent: true,
4336 auto_close: None,
4337 auto_surround: None,
4338 textmate_grammar: None,
4339 show_whitespace_tabs: true,
4340 line_wrap: None,
4341 wrap_column: None,
4342 page_view: None,
4343 page_width: None,
4344 use_tabs: None,
4345 tab_size: None,
4346 formatter: None,
4347 format_on_save: false,
4348 on_save: vec![],
4349 word_characters: None,
4350 },
4351 );
4352
4353 languages.insert(
4354 "dart".to_string(),
4355 LanguageConfig {
4356 extensions: vec!["dart".to_string()],
4357 filenames: vec![],
4358 grammar: "Dart".to_string(),
4359 comment_prefix: Some("//".to_string()),
4360 auto_indent: true,
4361 auto_close: None,
4362 auto_surround: None,
4363 textmate_grammar: None,
4364 show_whitespace_tabs: true,
4365 line_wrap: None,
4366 wrap_column: None,
4367 page_view: None,
4368 page_width: None,
4369 use_tabs: None,
4370 tab_size: None,
4371 formatter: None,
4372 format_on_save: false,
4373 on_save: vec![],
4374 word_characters: None,
4375 },
4376 );
4377
4378 languages.insert(
4379 "elixir".to_string(),
4380 LanguageConfig {
4381 extensions: vec!["ex".to_string(), "exs".to_string()],
4382 filenames: vec![],
4383 grammar: "Elixir".to_string(),
4384 comment_prefix: Some("#".to_string()),
4385 auto_indent: true,
4386 auto_close: None,
4387 auto_surround: None,
4388 textmate_grammar: None,
4389 show_whitespace_tabs: true,
4390 line_wrap: None,
4391 wrap_column: None,
4392 page_view: None,
4393 page_width: None,
4394 use_tabs: None,
4395 tab_size: None,
4396 formatter: None,
4397 format_on_save: false,
4398 on_save: vec![],
4399 word_characters: None,
4400 },
4401 );
4402
4403 languages.insert(
4404 "erlang".to_string(),
4405 LanguageConfig {
4406 extensions: vec!["erl".to_string(), "hrl".to_string()],
4407 filenames: vec![],
4408 grammar: "Erlang".to_string(),
4409 comment_prefix: Some("%".to_string()),
4410 auto_indent: true,
4411 auto_close: None,
4412 auto_surround: None,
4413 textmate_grammar: None,
4414 show_whitespace_tabs: true,
4415 line_wrap: None,
4416 wrap_column: None,
4417 page_view: None,
4418 page_width: None,
4419 use_tabs: None,
4420 tab_size: None,
4421 formatter: None,
4422 format_on_save: false,
4423 on_save: vec![],
4424 word_characters: None,
4425 },
4426 );
4427
4428 languages.insert(
4429 "haskell".to_string(),
4430 LanguageConfig {
4431 extensions: vec!["hs".to_string(), "lhs".to_string()],
4432 filenames: vec![],
4433 grammar: "Haskell".to_string(),
4434 comment_prefix: Some("--".to_string()),
4435 auto_indent: true,
4436 auto_close: None,
4437 auto_surround: None,
4438 textmate_grammar: None,
4439 show_whitespace_tabs: true,
4440 line_wrap: None,
4441 wrap_column: None,
4442 page_view: None,
4443 page_width: None,
4444 use_tabs: None,
4445 tab_size: None,
4446 formatter: None,
4447 format_on_save: false,
4448 on_save: vec![],
4449 word_characters: None,
4450 },
4451 );
4452
4453 languages.insert(
4454 "ocaml".to_string(),
4455 LanguageConfig {
4456 extensions: vec!["ml".to_string(), "mli".to_string()],
4457 filenames: vec![],
4458 grammar: "OCaml".to_string(),
4459 comment_prefix: None,
4460 auto_indent: true,
4461 auto_close: None,
4462 auto_surround: None,
4463 textmate_grammar: None,
4464 show_whitespace_tabs: true,
4465 line_wrap: None,
4466 wrap_column: None,
4467 page_view: None,
4468 page_width: None,
4469 use_tabs: None,
4470 tab_size: None,
4471 formatter: None,
4472 format_on_save: false,
4473 on_save: vec![],
4474 word_characters: None,
4475 },
4476 );
4477
4478 languages.insert(
4479 "clojure".to_string(),
4480 LanguageConfig {
4481 extensions: vec![
4482 "clj".to_string(),
4483 "cljs".to_string(),
4484 "cljc".to_string(),
4485 "edn".to_string(),
4486 ],
4487 filenames: vec![],
4488 grammar: "Clojure".to_string(),
4489 comment_prefix: Some(";".to_string()),
4490 auto_indent: true,
4491 auto_close: None,
4492 auto_surround: None,
4493 textmate_grammar: None,
4494 show_whitespace_tabs: true,
4495 line_wrap: None,
4496 wrap_column: None,
4497 page_view: None,
4498 page_width: None,
4499 use_tabs: None,
4500 tab_size: None,
4501 formatter: None,
4502 format_on_save: false,
4503 on_save: vec![],
4504 word_characters: None,
4505 },
4506 );
4507
4508 languages.insert(
4509 "r".to_string(),
4510 LanguageConfig {
4511 extensions: vec!["r".to_string(), "R".to_string(), "rmd".to_string()],
4512 filenames: vec![],
4513 grammar: "R".to_string(),
4514 comment_prefix: Some("#".to_string()),
4515 auto_indent: true,
4516 auto_close: None,
4517 auto_surround: None,
4518 textmate_grammar: None,
4519 show_whitespace_tabs: true,
4520 line_wrap: None,
4521 wrap_column: None,
4522 page_view: None,
4523 page_width: None,
4524 use_tabs: None,
4525 tab_size: None,
4526 formatter: None,
4527 format_on_save: false,
4528 on_save: vec![],
4529 word_characters: None,
4530 },
4531 );
4532
4533 languages.insert(
4534 "julia".to_string(),
4535 LanguageConfig {
4536 extensions: vec!["jl".to_string()],
4537 filenames: vec![],
4538 grammar: "Julia".to_string(),
4539 comment_prefix: Some("#".to_string()),
4540 auto_indent: true,
4541 auto_close: None,
4542 auto_surround: None,
4543 textmate_grammar: None,
4544 show_whitespace_tabs: true,
4545 line_wrap: None,
4546 wrap_column: None,
4547 page_view: None,
4548 page_width: None,
4549 use_tabs: None,
4550 tab_size: None,
4551 formatter: None,
4552 format_on_save: false,
4553 on_save: vec![],
4554 word_characters: None,
4555 },
4556 );
4557
4558 languages.insert(
4559 "perl".to_string(),
4560 LanguageConfig {
4561 extensions: vec!["pl".to_string(), "pm".to_string(), "t".to_string()],
4562 filenames: vec![],
4563 grammar: "Perl".to_string(),
4564 comment_prefix: Some("#".to_string()),
4565 auto_indent: true,
4566 auto_close: None,
4567 auto_surround: None,
4568 textmate_grammar: None,
4569 show_whitespace_tabs: true,
4570 line_wrap: None,
4571 wrap_column: None,
4572 page_view: None,
4573 page_width: None,
4574 use_tabs: None,
4575 tab_size: None,
4576 formatter: None,
4577 format_on_save: false,
4578 on_save: vec![],
4579 word_characters: None,
4580 },
4581 );
4582
4583 languages.insert(
4584 "nim".to_string(),
4585 LanguageConfig {
4586 extensions: vec!["nim".to_string(), "nims".to_string(), "nimble".to_string()],
4587 filenames: vec![],
4588 grammar: "Nim".to_string(),
4589 comment_prefix: Some("#".to_string()),
4590 auto_indent: true,
4591 auto_close: None,
4592 auto_surround: None,
4593 textmate_grammar: None,
4594 show_whitespace_tabs: true,
4595 line_wrap: None,
4596 wrap_column: None,
4597 page_view: None,
4598 page_width: None,
4599 use_tabs: None,
4600 tab_size: None,
4601 formatter: None,
4602 format_on_save: false,
4603 on_save: vec![],
4604 word_characters: None,
4605 },
4606 );
4607
4608 languages.insert(
4609 "gleam".to_string(),
4610 LanguageConfig {
4611 extensions: vec!["gleam".to_string()],
4612 filenames: vec![],
4613 grammar: "Gleam".to_string(),
4614 comment_prefix: Some("//".to_string()),
4615 auto_indent: true,
4616 auto_close: None,
4617 auto_surround: None,
4618 textmate_grammar: None,
4619 show_whitespace_tabs: true,
4620 line_wrap: None,
4621 wrap_column: None,
4622 page_view: None,
4623 page_width: None,
4624 use_tabs: None,
4625 tab_size: None,
4626 formatter: None,
4627 format_on_save: false,
4628 on_save: vec![],
4629 word_characters: None,
4630 },
4631 );
4632
4633 languages.insert(
4634 "racket".to_string(),
4635 LanguageConfig {
4636 extensions: vec![
4637 "rkt".to_string(),
4638 "rktd".to_string(),
4639 "rktl".to_string(),
4640 "scrbl".to_string(),
4641 ],
4642 filenames: vec![],
4643 grammar: "Racket".to_string(),
4644 comment_prefix: Some(";".to_string()),
4645 auto_indent: true,
4646 auto_close: None,
4647 auto_surround: None,
4648 textmate_grammar: None,
4649 show_whitespace_tabs: true,
4650 line_wrap: None,
4651 wrap_column: None,
4652 page_view: None,
4653 page_width: None,
4654 use_tabs: None,
4655 tab_size: None,
4656 formatter: None,
4657 format_on_save: false,
4658 on_save: vec![],
4659 word_characters: None,
4660 },
4661 );
4662
4663 languages.insert(
4664 "fsharp".to_string(),
4665 LanguageConfig {
4666 extensions: vec!["fs".to_string(), "fsi".to_string(), "fsx".to_string()],
4667 filenames: vec![],
4668 grammar: "FSharp".to_string(),
4669 comment_prefix: Some("//".to_string()),
4670 auto_indent: true,
4671 auto_close: None,
4672 auto_surround: None,
4673 textmate_grammar: None,
4674 show_whitespace_tabs: true,
4675 line_wrap: None,
4676 wrap_column: None,
4677 page_view: None,
4678 page_width: None,
4679 use_tabs: None,
4680 tab_size: None,
4681 formatter: None,
4682 format_on_save: false,
4683 on_save: vec![],
4684 word_characters: None,
4685 },
4686 );
4687
4688 languages.insert(
4689 "nix".to_string(),
4690 LanguageConfig {
4691 extensions: vec!["nix".to_string()],
4692 filenames: vec![],
4693 grammar: "Nix".to_string(),
4694 comment_prefix: Some("#".to_string()),
4695 auto_indent: true,
4696 auto_close: None,
4697 auto_surround: None,
4698 textmate_grammar: None,
4699 show_whitespace_tabs: true,
4700 line_wrap: None,
4701 wrap_column: None,
4702 page_view: None,
4703 page_width: None,
4704 use_tabs: None,
4705 tab_size: None,
4706 formatter: None,
4707 format_on_save: false,
4708 on_save: vec![],
4709 word_characters: None,
4710 },
4711 );
4712
4713 languages.insert(
4714 "nushell".to_string(),
4715 LanguageConfig {
4716 extensions: vec!["nu".to_string()],
4717 filenames: vec![],
4718 grammar: "Nushell".to_string(),
4719 comment_prefix: Some("#".to_string()),
4720 auto_indent: true,
4721 auto_close: None,
4722 auto_surround: None,
4723 textmate_grammar: None,
4724 show_whitespace_tabs: true,
4725 line_wrap: None,
4726 wrap_column: None,
4727 page_view: None,
4728 page_width: None,
4729 use_tabs: None,
4730 tab_size: None,
4731 formatter: None,
4732 format_on_save: false,
4733 on_save: vec![],
4734 word_characters: None,
4735 },
4736 );
4737
4738 languages.insert(
4739 "solidity".to_string(),
4740 LanguageConfig {
4741 extensions: vec!["sol".to_string()],
4742 filenames: vec![],
4743 grammar: "Solidity".to_string(),
4744 comment_prefix: Some("//".to_string()),
4745 auto_indent: true,
4746 auto_close: None,
4747 auto_surround: None,
4748 textmate_grammar: None,
4749 show_whitespace_tabs: true,
4750 line_wrap: None,
4751 wrap_column: None,
4752 page_view: None,
4753 page_width: None,
4754 use_tabs: None,
4755 tab_size: None,
4756 formatter: None,
4757 format_on_save: false,
4758 on_save: vec![],
4759 word_characters: None,
4760 },
4761 );
4762
4763 languages.insert(
4764 "verilog".to_string(),
4765 LanguageConfig {
4766 extensions: vec!["vh".to_string(), "verilog".to_string()],
4767 filenames: vec![],
4768 grammar: "Verilog".to_string(),
4769 comment_prefix: Some("//".to_string()),
4770 auto_indent: true,
4771 auto_close: None,
4772 auto_surround: None,
4773 textmate_grammar: None,
4774 show_whitespace_tabs: true,
4775 line_wrap: None,
4776 wrap_column: None,
4777 page_view: None,
4778 page_width: None,
4779 use_tabs: None,
4780 tab_size: None,
4781 formatter: None,
4782 format_on_save: false,
4783 on_save: vec![],
4784 word_characters: None,
4785 },
4786 );
4787
4788 languages.insert(
4789 "systemverilog".to_string(),
4790 LanguageConfig {
4791 extensions: vec![
4792 "sv".to_string(),
4793 "svh".to_string(),
4794 "svi".to_string(),
4795 "svp".to_string(),
4796 ],
4797 filenames: vec![],
4798 grammar: "SystemVerilog".to_string(),
4799 comment_prefix: Some("//".to_string()),
4800 auto_indent: true,
4801 auto_close: None,
4802 auto_surround: None,
4803 textmate_grammar: None,
4804 show_whitespace_tabs: true,
4805 line_wrap: None,
4806 wrap_column: None,
4807 page_view: None,
4808 page_width: None,
4809 use_tabs: None,
4810 tab_size: None,
4811 formatter: None,
4812 format_on_save: false,
4813 on_save: vec![],
4814 word_characters: None,
4815 },
4816 );
4817
4818 languages.insert(
4819 "vhdl".to_string(),
4820 LanguageConfig {
4821 extensions: vec!["vhd".to_string(), "vhdl".to_string(), "vho".to_string()],
4822 filenames: vec![],
4823 grammar: "VHDL".to_string(),
4824 comment_prefix: Some("--".to_string()),
4825 auto_indent: true,
4826 auto_close: None,
4827 auto_surround: None,
4828 textmate_grammar: None,
4829 show_whitespace_tabs: true,
4830 line_wrap: None,
4831 wrap_column: None,
4832 page_view: None,
4833 page_width: None,
4834 use_tabs: None,
4835 tab_size: None,
4836 formatter: None,
4837 format_on_save: false,
4838 on_save: vec![],
4839 word_characters: None,
4840 },
4841 );
4842
4843 languages.insert(
4844 "ruby".to_string(),
4845 LanguageConfig {
4846 extensions: vec!["rb".to_string(), "rake".to_string(), "gemspec".to_string()],
4847 filenames: vec![
4848 "Gemfile".to_string(),
4849 "Rakefile".to_string(),
4850 "Guardfile".to_string(),
4851 ],
4852 grammar: "Ruby".to_string(),
4853 comment_prefix: Some("#".to_string()),
4854 auto_indent: true,
4855 auto_close: None,
4856 auto_surround: None,
4857 textmate_grammar: None,
4858 show_whitespace_tabs: true,
4859 line_wrap: None,
4860 wrap_column: None,
4861 page_view: None,
4862 page_width: None,
4863 use_tabs: None,
4864 tab_size: None,
4865 formatter: None,
4866 format_on_save: false,
4867 on_save: vec![],
4868 word_characters: None,
4869 },
4870 );
4871
4872 languages.insert(
4873 "php".to_string(),
4874 LanguageConfig {
4875 extensions: vec!["php".to_string(), "phtml".to_string()],
4876 filenames: vec![],
4877 grammar: "PHP".to_string(),
4878 comment_prefix: Some("//".to_string()),
4879 auto_indent: true,
4880 auto_close: None,
4881 auto_surround: None,
4882 textmate_grammar: None,
4883 show_whitespace_tabs: true,
4884 line_wrap: None,
4885 wrap_column: None,
4886 page_view: None,
4887 page_width: None,
4888 use_tabs: None,
4889 tab_size: None,
4890 formatter: None,
4891 format_on_save: false,
4892 on_save: vec![],
4893 word_characters: None,
4894 },
4895 );
4896
4897 languages.insert(
4898 "lua".to_string(),
4899 LanguageConfig {
4900 extensions: vec!["lua".to_string()],
4901 filenames: vec![],
4902 grammar: "Lua".to_string(),
4903 comment_prefix: Some("--".to_string()),
4904 auto_indent: true,
4905 auto_close: None,
4906 auto_surround: None,
4907 textmate_grammar: None,
4908 show_whitespace_tabs: true,
4909 line_wrap: None,
4910 wrap_column: None,
4911 page_view: None,
4912 page_width: None,
4913 use_tabs: None,
4914 tab_size: None,
4915 formatter: None,
4916 format_on_save: false,
4917 on_save: vec![],
4918 word_characters: None,
4919 },
4920 );
4921
4922 languages.insert(
4923 "html".to_string(),
4924 LanguageConfig {
4925 extensions: vec!["html".to_string(), "htm".to_string()],
4926 filenames: vec![],
4927 grammar: "HTML".to_string(),
4928 comment_prefix: None,
4929 auto_indent: true,
4930 auto_close: None,
4931 auto_surround: None,
4932 textmate_grammar: None,
4933 show_whitespace_tabs: true,
4934 line_wrap: None,
4935 wrap_column: None,
4936 page_view: None,
4937 page_width: None,
4938 use_tabs: None,
4939 tab_size: None,
4940 formatter: None,
4941 format_on_save: false,
4942 on_save: vec![],
4943 word_characters: None,
4944 },
4945 );
4946
4947 languages.insert(
4948 "css".to_string(),
4949 LanguageConfig {
4950 extensions: vec!["css".to_string()],
4951 filenames: vec![],
4952 grammar: "CSS".to_string(),
4953 comment_prefix: None,
4954 auto_indent: true,
4955 auto_close: None,
4956 auto_surround: None,
4957 textmate_grammar: None,
4958 show_whitespace_tabs: true,
4959 line_wrap: None,
4960 wrap_column: None,
4961 page_view: None,
4962 page_width: None,
4963 use_tabs: None,
4964 tab_size: None,
4965 formatter: None,
4966 format_on_save: false,
4967 on_save: vec![],
4968 word_characters: None,
4969 },
4970 );
4971
4972 languages.insert(
4973 "sql".to_string(),
4974 LanguageConfig {
4975 extensions: vec!["sql".to_string()],
4976 filenames: vec![],
4977 grammar: "SQL".to_string(),
4978 comment_prefix: Some("--".to_string()),
4979 auto_indent: true,
4980 auto_close: None,
4981 auto_surround: None,
4982 textmate_grammar: None,
4983 show_whitespace_tabs: true,
4984 line_wrap: None,
4985 wrap_column: None,
4986 page_view: None,
4987 page_width: None,
4988 use_tabs: None,
4989 tab_size: None,
4990 formatter: None,
4991 format_on_save: false,
4992 on_save: vec![],
4993 word_characters: None,
4994 },
4995 );
4996
4997 languages.insert(
4998 "graphql".to_string(),
4999 LanguageConfig {
5000 extensions: vec!["graphql".to_string(), "gql".to_string()],
5001 filenames: vec![],
5002 grammar: "GraphQL".to_string(),
5003 comment_prefix: Some("#".to_string()),
5004 auto_indent: true,
5005 auto_close: None,
5006 auto_surround: None,
5007 textmate_grammar: None,
5008 show_whitespace_tabs: true,
5009 line_wrap: None,
5010 wrap_column: None,
5011 page_view: None,
5012 page_width: None,
5013 use_tabs: None,
5014 tab_size: None,
5015 formatter: None,
5016 format_on_save: false,
5017 on_save: vec![],
5018 word_characters: None,
5019 },
5020 );
5021
5022 languages.insert(
5023 "protobuf".to_string(),
5024 LanguageConfig {
5025 extensions: vec!["proto".to_string()],
5026 filenames: vec![],
5027 grammar: "Protocol Buffers".to_string(),
5028 comment_prefix: Some("//".to_string()),
5029 auto_indent: true,
5030 auto_close: None,
5031 auto_surround: None,
5032 textmate_grammar: None,
5033 show_whitespace_tabs: true,
5034 line_wrap: None,
5035 wrap_column: None,
5036 page_view: None,
5037 page_width: None,
5038 use_tabs: None,
5039 tab_size: None,
5040 formatter: None,
5041 format_on_save: false,
5042 on_save: vec![],
5043 word_characters: None,
5044 },
5045 );
5046
5047 languages.insert(
5048 "cmake".to_string(),
5049 LanguageConfig {
5050 extensions: vec!["cmake".to_string()],
5051 filenames: vec!["CMakeLists.txt".to_string()],
5052 grammar: "CMake".to_string(),
5053 comment_prefix: Some("#".to_string()),
5054 auto_indent: true,
5055 auto_close: None,
5056 auto_surround: None,
5057 textmate_grammar: None,
5058 show_whitespace_tabs: true,
5059 line_wrap: None,
5060 wrap_column: None,
5061 page_view: None,
5062 page_width: None,
5063 use_tabs: None,
5064 tab_size: None,
5065 formatter: None,
5066 format_on_save: false,
5067 on_save: vec![],
5068 word_characters: None,
5069 },
5070 );
5071
5072 languages.insert(
5073 "terraform".to_string(),
5074 LanguageConfig {
5075 extensions: vec!["tf".to_string(), "tfvars".to_string(), "hcl".to_string()],
5076 filenames: vec![],
5077 grammar: "HCL".to_string(),
5078 comment_prefix: Some("#".to_string()),
5079 auto_indent: true,
5080 auto_close: None,
5081 auto_surround: None,
5082 textmate_grammar: None,
5083 show_whitespace_tabs: true,
5084 line_wrap: None,
5085 wrap_column: None,
5086 page_view: None,
5087 page_width: None,
5088 use_tabs: None,
5089 tab_size: None,
5090 formatter: None,
5091 format_on_save: false,
5092 on_save: vec![],
5093 word_characters: None,
5094 },
5095 );
5096
5097 languages.insert(
5098 "vue".to_string(),
5099 LanguageConfig {
5100 extensions: vec!["vue".to_string()],
5101 filenames: vec![],
5102 grammar: "Vue".to_string(),
5103 comment_prefix: None,
5104 auto_indent: true,
5105 auto_close: None,
5106 auto_surround: None,
5107 textmate_grammar: None,
5108 show_whitespace_tabs: true,
5109 line_wrap: None,
5110 wrap_column: None,
5111 page_view: None,
5112 page_width: None,
5113 use_tabs: None,
5114 tab_size: None,
5115 formatter: None,
5116 format_on_save: false,
5117 on_save: vec![],
5118 word_characters: None,
5119 },
5120 );
5121
5122 languages.insert(
5123 "svelte".to_string(),
5124 LanguageConfig {
5125 extensions: vec!["svelte".to_string()],
5126 filenames: vec![],
5127 grammar: "Svelte".to_string(),
5128 comment_prefix: None,
5129 auto_indent: true,
5130 auto_close: None,
5131 auto_surround: None,
5132 textmate_grammar: None,
5133 show_whitespace_tabs: true,
5134 line_wrap: None,
5135 wrap_column: None,
5136 page_view: None,
5137 page_width: None,
5138 use_tabs: None,
5139 tab_size: None,
5140 formatter: None,
5141 format_on_save: false,
5142 on_save: vec![],
5143 word_characters: None,
5144 },
5145 );
5146
5147 languages.insert(
5148 "astro".to_string(),
5149 LanguageConfig {
5150 extensions: vec!["astro".to_string()],
5151 filenames: vec![],
5152 grammar: "Astro".to_string(),
5153 comment_prefix: None,
5154 auto_indent: true,
5155 auto_close: None,
5156 auto_surround: None,
5157 textmate_grammar: None,
5158 show_whitespace_tabs: true,
5159 line_wrap: None,
5160 wrap_column: None,
5161 page_view: None,
5162 page_width: None,
5163 use_tabs: None,
5164 tab_size: None,
5165 formatter: None,
5166 format_on_save: false,
5167 on_save: vec![],
5168 word_characters: None,
5169 },
5170 );
5171
5172 languages.insert(
5175 "scss".to_string(),
5176 LanguageConfig {
5177 extensions: vec!["scss".to_string()],
5178 filenames: vec![],
5179 grammar: "SCSS".to_string(),
5180 comment_prefix: Some("//".to_string()),
5181 auto_indent: true,
5182 auto_close: None,
5183 auto_surround: None,
5184 textmate_grammar: None,
5185 show_whitespace_tabs: true,
5186 line_wrap: None,
5187 wrap_column: None,
5188 page_view: None,
5189 page_width: None,
5190 use_tabs: None,
5191 tab_size: None,
5192 formatter: None,
5193 format_on_save: false,
5194 on_save: vec![],
5195 word_characters: None,
5196 },
5197 );
5198
5199 languages.insert(
5200 "less".to_string(),
5201 LanguageConfig {
5202 extensions: vec!["less".to_string()],
5203 filenames: vec![],
5204 grammar: "LESS".to_string(),
5205 comment_prefix: Some("//".to_string()),
5206 auto_indent: true,
5207 auto_close: None,
5208 auto_surround: None,
5209 textmate_grammar: None,
5210 show_whitespace_tabs: true,
5211 line_wrap: None,
5212 wrap_column: None,
5213 page_view: None,
5214 page_width: None,
5215 use_tabs: None,
5216 tab_size: None,
5217 formatter: None,
5218 format_on_save: false,
5219 on_save: vec![],
5220 word_characters: None,
5221 },
5222 );
5223
5224 languages.insert(
5225 "powershell".to_string(),
5226 LanguageConfig {
5227 extensions: vec!["ps1".to_string(), "psm1".to_string(), "psd1".to_string()],
5228 filenames: vec![],
5229 grammar: "PowerShell".to_string(),
5230 comment_prefix: Some("#".to_string()),
5231 auto_indent: true,
5232 auto_close: None,
5233 auto_surround: None,
5234 textmate_grammar: None,
5235 show_whitespace_tabs: true,
5236 line_wrap: None,
5237 wrap_column: None,
5238 page_view: None,
5239 page_width: None,
5240 use_tabs: None,
5241 tab_size: None,
5242 formatter: None,
5243 format_on_save: false,
5244 on_save: vec![],
5245 word_characters: None,
5246 },
5247 );
5248
5249 languages.insert(
5250 "kdl".to_string(),
5251 LanguageConfig {
5252 extensions: vec!["kdl".to_string()],
5253 filenames: vec![],
5254 grammar: "KDL".to_string(),
5255 comment_prefix: Some("//".to_string()),
5256 auto_indent: true,
5257 auto_close: None,
5258 auto_surround: None,
5259 textmate_grammar: None,
5260 show_whitespace_tabs: true,
5261 line_wrap: None,
5262 wrap_column: None,
5263 page_view: None,
5264 page_width: None,
5265 use_tabs: None,
5266 tab_size: None,
5267 formatter: None,
5268 format_on_save: false,
5269 on_save: vec![],
5270 word_characters: None,
5271 },
5272 );
5273
5274 languages.insert(
5275 "starlark".to_string(),
5276 LanguageConfig {
5277 extensions: vec!["bzl".to_string(), "star".to_string()],
5278 filenames: vec!["BUILD".to_string(), "WORKSPACE".to_string()],
5279 grammar: "Starlark".to_string(),
5280 comment_prefix: Some("#".to_string()),
5281 auto_indent: true,
5282 auto_close: None,
5283 auto_surround: None,
5284 textmate_grammar: None,
5285 show_whitespace_tabs: true,
5286 line_wrap: None,
5287 wrap_column: None,
5288 page_view: None,
5289 page_width: None,
5290 use_tabs: None,
5291 tab_size: None,
5292 formatter: None,
5293 format_on_save: false,
5294 on_save: vec![],
5295 word_characters: None,
5296 },
5297 );
5298
5299 languages.insert(
5300 "justfile".to_string(),
5301 LanguageConfig {
5302 extensions: vec![],
5303 filenames: vec![
5304 "justfile".to_string(),
5305 "Justfile".to_string(),
5306 ".justfile".to_string(),
5307 ],
5308 grammar: "Justfile".to_string(),
5309 comment_prefix: Some("#".to_string()),
5310 auto_indent: true,
5311 auto_close: None,
5312 auto_surround: None,
5313 textmate_grammar: None,
5314 show_whitespace_tabs: true,
5315 line_wrap: None,
5316 wrap_column: None,
5317 page_view: None,
5318 page_width: None,
5319 use_tabs: Some(true),
5320 tab_size: None,
5321 formatter: None,
5322 format_on_save: false,
5323 on_save: vec![],
5324 word_characters: None,
5325 },
5326 );
5327
5328 languages.insert(
5329 "earthfile".to_string(),
5330 LanguageConfig {
5331 extensions: vec!["earth".to_string()],
5332 filenames: vec!["Earthfile".to_string()],
5333 grammar: "Earthfile".to_string(),
5334 comment_prefix: Some("#".to_string()),
5335 auto_indent: true,
5336 auto_close: None,
5337 auto_surround: None,
5338 textmate_grammar: None,
5339 show_whitespace_tabs: true,
5340 line_wrap: None,
5341 wrap_column: None,
5342 page_view: None,
5343 page_width: None,
5344 use_tabs: None,
5345 tab_size: None,
5346 formatter: None,
5347 format_on_save: false,
5348 on_save: vec![],
5349 word_characters: None,
5350 },
5351 );
5352
5353 languages.insert(
5354 "gomod".to_string(),
5355 LanguageConfig {
5356 extensions: vec![],
5357 filenames: vec!["go.mod".to_string(), "go.sum".to_string()],
5358 grammar: "Go Module".to_string(),
5359 comment_prefix: Some("//".to_string()),
5360 auto_indent: true,
5361 auto_close: None,
5362 auto_surround: None,
5363 textmate_grammar: None,
5364 show_whitespace_tabs: true,
5365 line_wrap: None,
5366 wrap_column: None,
5367 page_view: None,
5368 page_width: None,
5369 use_tabs: Some(true),
5370 tab_size: None,
5371 formatter: None,
5372 format_on_save: false,
5373 on_save: vec![],
5374 word_characters: None,
5375 },
5376 );
5377
5378 languages.insert(
5379 "vlang".to_string(),
5380 LanguageConfig {
5381 extensions: vec!["v".to_string(), "vv".to_string()],
5382 filenames: vec![],
5383 grammar: "V".to_string(),
5384 comment_prefix: Some("//".to_string()),
5385 auto_indent: true,
5386 auto_close: None,
5387 auto_surround: None,
5388 textmate_grammar: None,
5389 show_whitespace_tabs: true,
5390 line_wrap: None,
5391 wrap_column: None,
5392 page_view: None,
5393 page_width: None,
5394 use_tabs: None,
5395 tab_size: None,
5396 formatter: None,
5397 format_on_save: false,
5398 on_save: vec![],
5399 word_characters: None,
5400 },
5401 );
5402
5403 languages.insert(
5404 "ini".to_string(),
5405 LanguageConfig {
5406 extensions: vec!["ini".to_string(), "cfg".to_string()],
5407 filenames: vec![],
5408 grammar: "INI".to_string(),
5409 comment_prefix: Some(";".to_string()),
5410 auto_indent: false,
5411 auto_close: None,
5412 auto_surround: None,
5413 textmate_grammar: None,
5414 show_whitespace_tabs: true,
5415 line_wrap: None,
5416 wrap_column: None,
5417 page_view: None,
5418 page_width: None,
5419 use_tabs: None,
5420 tab_size: None,
5421 formatter: None,
5422 format_on_save: false,
5423 on_save: vec![],
5424 word_characters: None,
5425 },
5426 );
5427
5428 languages.insert(
5429 "hyprlang".to_string(),
5430 LanguageConfig {
5431 extensions: vec!["hl".to_string()],
5432 filenames: vec!["hyprland.conf".to_string()],
5433 grammar: "Hyprlang".to_string(),
5434 comment_prefix: Some("#".to_string()),
5435 auto_indent: true,
5436 auto_close: None,
5437 auto_surround: None,
5438 textmate_grammar: None,
5439 show_whitespace_tabs: true,
5440 line_wrap: None,
5441 wrap_column: None,
5442 page_view: None,
5443 page_width: None,
5444 use_tabs: None,
5445 tab_size: None,
5446 formatter: None,
5447 format_on_save: false,
5448 on_save: vec![],
5449 word_characters: None,
5450 },
5451 );
5452
5453 languages
5454 }
5455
5456 #[cfg(feature = "runtime")]
5458 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
5459 let mut lsp = HashMap::new();
5460
5461 let ra_log_path = crate::services::log_dirs::lsp_log_path("rust-analyzer")
5464 .to_string_lossy()
5465 .to_string();
5466
5467 Self::populate_lsp_config(&mut lsp, ra_log_path);
5468 lsp
5469 }
5470
5471 #[cfg(not(feature = "runtime"))]
5473 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
5474 HashMap::new()
5476 }
5477
5478 #[cfg(feature = "runtime")]
5480 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
5481 let mut universal = HashMap::new();
5482
5483 universal.insert(
5496 "quicklsp".to_string(),
5497 LspLanguageConfig::Multi(vec![LspServerConfig {
5498 command: "quicklsp".to_string(),
5499 args: vec![],
5500 enabled: false,
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: Some("QuickLSP".to_string()),
5507 only_features: None,
5508 except_features: None,
5509 root_markers: vec![
5510 "Cargo.toml".to_string(),
5511 "package.json".to_string(),
5512 "go.mod".to_string(),
5513 "pyproject.toml".to_string(),
5514 "requirements.txt".to_string(),
5515 ".git".to_string(),
5516 ],
5517 }]),
5518 );
5519
5520 universal
5521 }
5522
5523 #[cfg(not(feature = "runtime"))]
5525 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
5526 HashMap::new()
5527 }
5528
5529 #[cfg(feature = "runtime")]
5530 fn populate_lsp_config(lsp: &mut HashMap<String, LspLanguageConfig>, ra_log_path: String) {
5531 lsp.insert(
5535 "rust".to_string(),
5536 LspLanguageConfig::Multi(vec![LspServerConfig {
5537 command: "rust-analyzer".to_string(),
5538 args: vec!["--log-file".to_string(), ra_log_path],
5539 enabled: true,
5540 auto_start: false,
5541 process_limits: ProcessLimits::unlimited(),
5542 initialization_options: None,
5543 env: Default::default(),
5544 language_id_overrides: Default::default(),
5545 name: None,
5546 only_features: None,
5547 except_features: None,
5548 root_markers: vec![
5549 "Cargo.toml".to_string(),
5550 "rust-project.json".to_string(),
5551 ".git".to_string(),
5552 ],
5553 }]),
5554 );
5555
5556 lsp.insert(
5558 "python".to_string(),
5559 LspLanguageConfig::Multi(vec![LspServerConfig {
5560 command: "pylsp".to_string(),
5561 args: vec![],
5562 enabled: true,
5563 auto_start: false,
5564 process_limits: ProcessLimits::default(),
5565 initialization_options: None,
5566 env: Default::default(),
5567 language_id_overrides: Default::default(),
5568 name: None,
5569 only_features: None,
5570 except_features: None,
5571 root_markers: vec![
5572 "pyproject.toml".to_string(),
5573 "setup.py".to_string(),
5574 "setup.cfg".to_string(),
5575 "pyrightconfig.json".to_string(),
5576 ".git".to_string(),
5577 ],
5578 }]),
5579 );
5580
5581 lsp.insert(
5584 "javascript".to_string(),
5585 LspLanguageConfig::Multi(vec![LspServerConfig {
5586 command: "typescript-language-server".to_string(),
5587 args: vec!["--stdio".to_string()],
5588 enabled: true,
5589 auto_start: false,
5590 process_limits: ProcessLimits::default(),
5591 initialization_options: None,
5592 env: Default::default(),
5593 language_id_overrides: HashMap::from([(
5594 "jsx".to_string(),
5595 "javascriptreact".to_string(),
5596 )]),
5597 name: None,
5598 only_features: None,
5599 except_features: None,
5600 root_markers: vec![
5601 "tsconfig.json".to_string(),
5602 "jsconfig.json".to_string(),
5603 "package.json".to_string(),
5604 ".git".to_string(),
5605 ],
5606 }]),
5607 );
5608 lsp.insert(
5609 "typescript".to_string(),
5610 LspLanguageConfig::Multi(vec![LspServerConfig {
5611 command: "typescript-language-server".to_string(),
5612 args: vec!["--stdio".to_string()],
5613 enabled: true,
5614 auto_start: false,
5615 process_limits: ProcessLimits::default(),
5616 initialization_options: None,
5617 env: Default::default(),
5618 language_id_overrides: HashMap::from([(
5619 "tsx".to_string(),
5620 "typescriptreact".to_string(),
5621 )]),
5622 name: None,
5623 only_features: None,
5624 except_features: None,
5625 root_markers: vec![
5626 "tsconfig.json".to_string(),
5627 "jsconfig.json".to_string(),
5628 "package.json".to_string(),
5629 ".git".to_string(),
5630 ],
5631 }]),
5632 );
5633
5634 lsp.insert(
5636 "html".to_string(),
5637 LspLanguageConfig::Multi(vec![LspServerConfig {
5638 command: "vscode-html-language-server".to_string(),
5639 args: vec!["--stdio".to_string()],
5640 enabled: true,
5641 auto_start: false,
5642 process_limits: ProcessLimits::default(),
5643 initialization_options: None,
5644 env: Default::default(),
5645 language_id_overrides: Default::default(),
5646 name: None,
5647 only_features: None,
5648 except_features: None,
5649 root_markers: Default::default(),
5650 }]),
5651 );
5652
5653 lsp.insert(
5655 "css".to_string(),
5656 LspLanguageConfig::Multi(vec![LspServerConfig {
5657 command: "vscode-css-language-server".to_string(),
5658 args: vec!["--stdio".to_string()],
5659 enabled: true,
5660 auto_start: false,
5661 process_limits: ProcessLimits::default(),
5662 initialization_options: None,
5663 env: Default::default(),
5664 language_id_overrides: Default::default(),
5665 name: None,
5666 only_features: None,
5667 except_features: None,
5668 root_markers: Default::default(),
5669 }]),
5670 );
5671
5672 lsp.insert(
5674 "c".to_string(),
5675 LspLanguageConfig::Multi(vec![LspServerConfig {
5676 command: "clangd".to_string(),
5677 args: vec![],
5678 enabled: true,
5679 auto_start: false,
5680 process_limits: ProcessLimits::default(),
5681 initialization_options: None,
5682 env: Default::default(),
5683 language_id_overrides: Default::default(),
5684 name: None,
5685 only_features: None,
5686 except_features: None,
5687 root_markers: vec![
5688 "compile_commands.json".to_string(),
5689 "CMakeLists.txt".to_string(),
5690 "Makefile".to_string(),
5691 ".git".to_string(),
5692 ],
5693 }]),
5694 );
5695 lsp.insert(
5696 "cpp".to_string(),
5697 LspLanguageConfig::Multi(vec![LspServerConfig {
5698 command: "clangd".to_string(),
5699 args: vec![],
5700 enabled: true,
5701 auto_start: false,
5702 process_limits: ProcessLimits::default(),
5703 initialization_options: None,
5704 env: Default::default(),
5705 language_id_overrides: Default::default(),
5706 name: None,
5707 only_features: None,
5708 except_features: None,
5709 root_markers: vec![
5710 "compile_commands.json".to_string(),
5711 "CMakeLists.txt".to_string(),
5712 "Makefile".to_string(),
5713 ".git".to_string(),
5714 ],
5715 }]),
5716 );
5717
5718 lsp.insert(
5720 "go".to_string(),
5721 LspLanguageConfig::Multi(vec![LspServerConfig {
5722 command: "gopls".to_string(),
5723 args: vec![],
5724 enabled: true,
5725 auto_start: false,
5726 process_limits: ProcessLimits::default(),
5727 initialization_options: None,
5728 env: Default::default(),
5729 language_id_overrides: Default::default(),
5730 name: None,
5731 only_features: None,
5732 except_features: None,
5733 root_markers: vec![
5734 "go.mod".to_string(),
5735 "go.work".to_string(),
5736 ".git".to_string(),
5737 ],
5738 }]),
5739 );
5740
5741 lsp.insert(
5743 "json".to_string(),
5744 LspLanguageConfig::Multi(vec![LspServerConfig {
5745 command: "vscode-json-language-server".to_string(),
5746 args: vec!["--stdio".to_string()],
5747 enabled: true,
5748 auto_start: false,
5749 process_limits: ProcessLimits::default(),
5750 initialization_options: None,
5751 env: Default::default(),
5752 language_id_overrides: Default::default(),
5753 name: None,
5754 only_features: None,
5755 except_features: None,
5756 root_markers: Default::default(),
5757 }]),
5758 );
5759
5760 lsp.insert(
5764 "jsonc".to_string(),
5765 LspLanguageConfig::Multi(vec![LspServerConfig {
5766 command: "vscode-json-language-server".to_string(),
5767 args: vec!["--stdio".to_string()],
5768 enabled: true,
5769 auto_start: false,
5770 process_limits: ProcessLimits::default(),
5771 initialization_options: None,
5772 env: Default::default(),
5773 language_id_overrides: Default::default(),
5774 name: None,
5775 only_features: None,
5776 except_features: None,
5777 root_markers: Default::default(),
5778 }]),
5779 );
5780
5781 lsp.insert(
5783 "csharp".to_string(),
5784 LspLanguageConfig::Multi(vec![LspServerConfig {
5785 command: "csharp-ls".to_string(),
5786 args: vec![],
5787 enabled: true,
5788 auto_start: false,
5789 process_limits: ProcessLimits::default(),
5790 initialization_options: None,
5791 env: Default::default(),
5792 language_id_overrides: Default::default(),
5793 name: None,
5794 only_features: None,
5795 except_features: None,
5796 root_markers: vec![
5797 "*.csproj".to_string(),
5798 "*.sln".to_string(),
5799 ".git".to_string(),
5800 ],
5801 }]),
5802 );
5803
5804 lsp.insert(
5807 "odin".to_string(),
5808 LspLanguageConfig::Multi(vec![LspServerConfig {
5809 command: "ols".to_string(),
5810 args: vec![],
5811 enabled: true,
5812 auto_start: false,
5813 process_limits: ProcessLimits::default(),
5814 initialization_options: None,
5815 env: Default::default(),
5816 language_id_overrides: Default::default(),
5817 name: None,
5818 only_features: None,
5819 except_features: None,
5820 root_markers: Default::default(),
5821 }]),
5822 );
5823
5824 lsp.insert(
5827 "zig".to_string(),
5828 LspLanguageConfig::Multi(vec![LspServerConfig {
5829 command: "zls".to_string(),
5830 args: vec![],
5831 enabled: true,
5832 auto_start: false,
5833 process_limits: ProcessLimits::default(),
5834 initialization_options: None,
5835 env: Default::default(),
5836 language_id_overrides: Default::default(),
5837 name: None,
5838 only_features: None,
5839 except_features: None,
5840 root_markers: Default::default(),
5841 }]),
5842 );
5843
5844 lsp.insert(
5847 "c3".to_string(),
5848 LspLanguageConfig::Multi(vec![LspServerConfig {
5849 command: "c3lsp".to_string(),
5850 args: vec![],
5851 enabled: true,
5852 auto_start: false,
5853 process_limits: ProcessLimits::default(),
5854 initialization_options: None,
5855 env: Default::default(),
5856 language_id_overrides: Default::default(),
5857 name: None,
5858 only_features: None,
5859 except_features: None,
5860 root_markers: vec!["project.json".to_string(), ".git".to_string()],
5861 }]),
5862 );
5863
5864 lsp.insert(
5867 "java".to_string(),
5868 LspLanguageConfig::Multi(vec![LspServerConfig {
5869 command: "jdtls".to_string(),
5870 args: vec![],
5871 enabled: true,
5872 auto_start: false,
5873 process_limits: ProcessLimits::default(),
5874 initialization_options: None,
5875 env: Default::default(),
5876 language_id_overrides: Default::default(),
5877 name: None,
5878 only_features: None,
5879 except_features: None,
5880 root_markers: vec![
5881 "pom.xml".to_string(),
5882 "build.gradle".to_string(),
5883 "build.gradle.kts".to_string(),
5884 ".git".to_string(),
5885 ],
5886 }]),
5887 );
5888
5889 lsp.insert(
5892 "latex".to_string(),
5893 LspLanguageConfig::Multi(vec![LspServerConfig {
5894 command: "texlab".to_string(),
5895 args: vec![],
5896 enabled: true,
5897 auto_start: false,
5898 process_limits: ProcessLimits::default(),
5899 initialization_options: None,
5900 env: Default::default(),
5901 language_id_overrides: Default::default(),
5902 name: None,
5903 only_features: None,
5904 except_features: None,
5905 root_markers: Default::default(),
5906 }]),
5907 );
5908
5909 lsp.insert(
5912 "markdown".to_string(),
5913 LspLanguageConfig::Multi(vec![LspServerConfig {
5914 command: "marksman".to_string(),
5915 args: vec!["server".to_string()],
5916 enabled: true,
5917 auto_start: false,
5918 process_limits: ProcessLimits::default(),
5919 initialization_options: None,
5920 env: Default::default(),
5921 language_id_overrides: Default::default(),
5922 name: None,
5923 only_features: None,
5924 except_features: None,
5925 root_markers: Default::default(),
5926 }]),
5927 );
5928
5929 lsp.insert(
5932 "templ".to_string(),
5933 LspLanguageConfig::Multi(vec![LspServerConfig {
5934 command: "templ".to_string(),
5935 args: vec!["lsp".to_string()],
5936 enabled: true,
5937 auto_start: false,
5938 process_limits: ProcessLimits::default(),
5939 initialization_options: None,
5940 env: Default::default(),
5941 language_id_overrides: Default::default(),
5942 name: None,
5943 only_features: None,
5944 except_features: None,
5945 root_markers: Default::default(),
5946 }]),
5947 );
5948
5949 lsp.insert(
5952 "typst".to_string(),
5953 LspLanguageConfig::Multi(vec![LspServerConfig {
5954 command: "tinymist".to_string(),
5955 args: vec![],
5956 enabled: true,
5957 auto_start: false,
5958 process_limits: ProcessLimits::default(),
5959 initialization_options: None,
5960 env: Default::default(),
5961 language_id_overrides: Default::default(),
5962 name: None,
5963 only_features: None,
5964 except_features: None,
5965 root_markers: Default::default(),
5966 }]),
5967 );
5968
5969 lsp.insert(
5971 "bash".to_string(),
5972 LspLanguageConfig::Multi(vec![LspServerConfig {
5973 command: "bash-language-server".to_string(),
5974 args: vec!["start".to_string()],
5975 enabled: true,
5976 auto_start: false,
5977 process_limits: ProcessLimits::default(),
5978 initialization_options: None,
5979 env: Default::default(),
5980 language_id_overrides: Default::default(),
5981 name: None,
5982 only_features: None,
5983 except_features: None,
5984 root_markers: Default::default(),
5985 }]),
5986 );
5987
5988 lsp.insert(
5991 "lua".to_string(),
5992 LspLanguageConfig::Multi(vec![LspServerConfig {
5993 command: "lua-language-server".to_string(),
5994 args: vec![],
5995 enabled: true,
5996 auto_start: false,
5997 process_limits: ProcessLimits::default(),
5998 initialization_options: None,
5999 env: Default::default(),
6000 language_id_overrides: Default::default(),
6001 name: None,
6002 only_features: None,
6003 except_features: None,
6004 root_markers: vec![
6005 ".luarc.json".to_string(),
6006 ".luarc.jsonc".to_string(),
6007 ".luacheckrc".to_string(),
6008 ".stylua.toml".to_string(),
6009 ".git".to_string(),
6010 ],
6011 }]),
6012 );
6013
6014 lsp.insert(
6016 "ruby".to_string(),
6017 LspLanguageConfig::Multi(vec![LspServerConfig {
6018 command: "solargraph".to_string(),
6019 args: vec!["stdio".to_string()],
6020 enabled: true,
6021 auto_start: false,
6022 process_limits: ProcessLimits::default(),
6023 initialization_options: None,
6024 env: Default::default(),
6025 language_id_overrides: Default::default(),
6026 name: None,
6027 only_features: None,
6028 except_features: None,
6029 root_markers: vec![
6030 "Gemfile".to_string(),
6031 ".ruby-version".to_string(),
6032 ".git".to_string(),
6033 ],
6034 }]),
6035 );
6036
6037 lsp.insert(
6040 "php".to_string(),
6041 LspLanguageConfig::Multi(vec![LspServerConfig {
6042 command: "phpactor".to_string(),
6043 args: vec!["language-server".to_string()],
6044 enabled: true,
6045 auto_start: false,
6046 process_limits: ProcessLimits::default(),
6047 initialization_options: None,
6048 env: Default::default(),
6049 language_id_overrides: Default::default(),
6050 name: None,
6051 only_features: None,
6052 except_features: None,
6053 root_markers: vec!["composer.json".to_string(), ".git".to_string()],
6054 }]),
6055 );
6056
6057 lsp.insert(
6059 "yaml".to_string(),
6060 LspLanguageConfig::Multi(vec![LspServerConfig {
6061 command: "yaml-language-server".to_string(),
6062 args: vec!["--stdio".to_string()],
6063 enabled: true,
6064 auto_start: false,
6065 process_limits: ProcessLimits::default(),
6066 initialization_options: None,
6067 env: Default::default(),
6068 language_id_overrides: Default::default(),
6069 name: None,
6070 only_features: None,
6071 except_features: None,
6072 root_markers: Default::default(),
6073 }]),
6074 );
6075
6076 lsp.insert(
6079 "toml".to_string(),
6080 LspLanguageConfig::Multi(vec![LspServerConfig {
6081 command: "taplo".to_string(),
6082 args: vec!["lsp".to_string(), "stdio".to_string()],
6083 enabled: true,
6084 auto_start: false,
6085 process_limits: ProcessLimits::default(),
6086 initialization_options: None,
6087 env: Default::default(),
6088 language_id_overrides: Default::default(),
6089 name: None,
6090 only_features: None,
6091 except_features: None,
6092 root_markers: Default::default(),
6093 }]),
6094 );
6095
6096 lsp.insert(
6099 "dart".to_string(),
6100 LspLanguageConfig::Multi(vec![LspServerConfig {
6101 command: "dart".to_string(),
6102 args: vec!["language-server".to_string(), "--protocol=lsp".to_string()],
6103 enabled: true,
6104 auto_start: false,
6105 process_limits: ProcessLimits::default(),
6106 initialization_options: None,
6107 env: Default::default(),
6108 language_id_overrides: Default::default(),
6109 name: None,
6110 only_features: None,
6111 except_features: None,
6112 root_markers: vec!["pubspec.yaml".to_string(), ".git".to_string()],
6113 }]),
6114 );
6115
6116 lsp.insert(
6119 "nushell".to_string(),
6120 LspLanguageConfig::Multi(vec![LspServerConfig {
6121 command: "nu".to_string(),
6122 args: vec!["--lsp".to_string()],
6123 enabled: true,
6124 auto_start: false,
6125 process_limits: ProcessLimits::default(),
6126 initialization_options: None,
6127 env: Default::default(),
6128 language_id_overrides: Default::default(),
6129 name: None,
6130 only_features: None,
6131 except_features: None,
6132 root_markers: Default::default(),
6133 }]),
6134 );
6135
6136 lsp.insert(
6139 "solidity".to_string(),
6140 LspLanguageConfig::Multi(vec![LspServerConfig {
6141 command: "nomicfoundation-solidity-language-server".to_string(),
6142 args: vec!["--stdio".to_string()],
6143 enabled: true,
6144 auto_start: false,
6145 process_limits: ProcessLimits::default(),
6146 initialization_options: None,
6147 env: Default::default(),
6148 language_id_overrides: Default::default(),
6149 name: None,
6150 only_features: None,
6151 except_features: None,
6152 root_markers: Default::default(),
6153 }]),
6154 );
6155
6156 lsp.insert(
6161 "terraform".to_string(),
6162 LspLanguageConfig::Multi(vec![LspServerConfig {
6163 command: "terraform-ls".to_string(),
6164 args: vec!["serve".to_string()],
6165 enabled: true,
6166 auto_start: false,
6167 process_limits: ProcessLimits::default(),
6168 initialization_options: None,
6169 env: Default::default(),
6170 language_id_overrides: Default::default(),
6171 name: None,
6172 only_features: None,
6173 except_features: None,
6174 root_markers: vec![
6175 "*.tf".to_string(),
6176 ".terraform".to_string(),
6177 ".git".to_string(),
6178 ],
6179 }]),
6180 );
6181
6182 lsp.insert(
6185 "cmake".to_string(),
6186 LspLanguageConfig::Multi(vec![LspServerConfig {
6187 command: "cmake-language-server".to_string(),
6188 args: vec![],
6189 enabled: true,
6190 auto_start: false,
6191 process_limits: ProcessLimits::default(),
6192 initialization_options: None,
6193 env: Default::default(),
6194 language_id_overrides: Default::default(),
6195 name: None,
6196 only_features: None,
6197 except_features: None,
6198 root_markers: vec!["CMakeLists.txt".to_string(), ".git".to_string()],
6199 }]),
6200 );
6201
6202 lsp.insert(
6205 "protobuf".to_string(),
6206 LspLanguageConfig::Multi(vec![LspServerConfig {
6207 command: "buf".to_string(),
6208 args: vec!["beta".to_string(), "lsp".to_string()],
6209 enabled: true,
6210 auto_start: false,
6211 process_limits: ProcessLimits::default(),
6212 initialization_options: None,
6213 env: Default::default(),
6214 language_id_overrides: Default::default(),
6215 name: None,
6216 only_features: None,
6217 except_features: None,
6218 root_markers: Default::default(),
6219 }]),
6220 );
6221
6222 lsp.insert(
6225 "graphql".to_string(),
6226 LspLanguageConfig::Multi(vec![LspServerConfig {
6227 command: "graphql-lsp".to_string(),
6228 args: vec!["server".to_string(), "-m".to_string(), "stream".to_string()],
6229 enabled: true,
6230 auto_start: false,
6231 process_limits: ProcessLimits::default(),
6232 initialization_options: None,
6233 env: Default::default(),
6234 language_id_overrides: Default::default(),
6235 name: None,
6236 only_features: None,
6237 except_features: None,
6238 root_markers: Default::default(),
6239 }]),
6240 );
6241
6242 lsp.insert(
6245 "sql".to_string(),
6246 LspLanguageConfig::Multi(vec![LspServerConfig {
6247 command: "sqls".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(
6266 "vue".to_string(),
6267 LspLanguageConfig::Multi(vec![LspServerConfig {
6268 command: "vue-language-server".to_string(),
6269 args: vec!["--stdio".to_string()],
6270 enabled: true,
6271 auto_start: false,
6272 process_limits: ProcessLimits::default(),
6273 initialization_options: None,
6274 env: Default::default(),
6275 language_id_overrides: Default::default(),
6276 name: None,
6277 only_features: None,
6278 except_features: None,
6279 root_markers: Default::default(),
6280 }]),
6281 );
6282
6283 lsp.insert(
6285 "svelte".to_string(),
6286 LspLanguageConfig::Multi(vec![LspServerConfig {
6287 command: "svelteserver".to_string(),
6288 args: vec!["--stdio".to_string()],
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(
6304 "astro".to_string(),
6305 LspLanguageConfig::Multi(vec![LspServerConfig {
6306 command: "astro-ls".to_string(),
6307 args: vec!["--stdio".to_string()],
6308 enabled: true,
6309 auto_start: false,
6310 process_limits: ProcessLimits::default(),
6311 initialization_options: None,
6312 env: Default::default(),
6313 language_id_overrides: Default::default(),
6314 name: None,
6315 only_features: None,
6316 except_features: None,
6317 root_markers: Default::default(),
6318 }]),
6319 );
6320
6321 lsp.insert(
6323 "tailwindcss".to_string(),
6324 LspLanguageConfig::Multi(vec![LspServerConfig {
6325 command: "tailwindcss-language-server".to_string(),
6326 args: vec!["--stdio".to_string()],
6327 enabled: true,
6328 auto_start: false,
6329 process_limits: ProcessLimits::default(),
6330 initialization_options: None,
6331 env: Default::default(),
6332 language_id_overrides: Default::default(),
6333 name: None,
6334 only_features: None,
6335 except_features: None,
6336 root_markers: Default::default(),
6337 }]),
6338 );
6339
6340 lsp.insert(
6345 "nix".to_string(),
6346 LspLanguageConfig::Multi(vec![LspServerConfig {
6347 command: "nil".to_string(),
6348 args: vec![],
6349 enabled: true,
6350 auto_start: false,
6351 process_limits: ProcessLimits::default(),
6352 initialization_options: None,
6353 env: Default::default(),
6354 language_id_overrides: Default::default(),
6355 name: None,
6356 only_features: None,
6357 except_features: None,
6358 root_markers: Default::default(),
6359 }]),
6360 );
6361
6362 lsp.insert(
6365 "kotlin".to_string(),
6366 LspLanguageConfig::Multi(vec![LspServerConfig {
6367 command: "kotlin-language-server".to_string(),
6368 args: vec![],
6369 enabled: true,
6370 auto_start: false,
6371 process_limits: ProcessLimits::default(),
6372 initialization_options: None,
6373 env: Default::default(),
6374 language_id_overrides: Default::default(),
6375 name: None,
6376 only_features: None,
6377 except_features: None,
6378 root_markers: Default::default(),
6379 }]),
6380 );
6381
6382 lsp.insert(
6384 "swift".to_string(),
6385 LspLanguageConfig::Multi(vec![LspServerConfig {
6386 command: "sourcekit-lsp".to_string(),
6387 args: vec![],
6388 enabled: true,
6389 auto_start: false,
6390 process_limits: ProcessLimits::default(),
6391 initialization_options: None,
6392 env: Default::default(),
6393 language_id_overrides: Default::default(),
6394 name: None,
6395 only_features: None,
6396 except_features: None,
6397 root_markers: Default::default(),
6398 }]),
6399 );
6400
6401 lsp.insert(
6404 "scala".to_string(),
6405 LspLanguageConfig::Multi(vec![LspServerConfig {
6406 command: "metals".to_string(),
6407 args: vec![],
6408 enabled: true,
6409 auto_start: false,
6410 process_limits: ProcessLimits::default(),
6411 initialization_options: None,
6412 env: Default::default(),
6413 language_id_overrides: Default::default(),
6414 name: None,
6415 only_features: None,
6416 except_features: None,
6417 root_markers: Default::default(),
6418 }]),
6419 );
6420
6421 lsp.insert(
6424 "elixir".to_string(),
6425 LspLanguageConfig::Multi(vec![LspServerConfig {
6426 command: "elixir-ls".to_string(),
6427 args: vec![],
6428 enabled: true,
6429 auto_start: false,
6430 process_limits: ProcessLimits::default(),
6431 initialization_options: None,
6432 env: Default::default(),
6433 language_id_overrides: Default::default(),
6434 name: None,
6435 only_features: None,
6436 except_features: None,
6437 root_markers: Default::default(),
6438 }]),
6439 );
6440
6441 lsp.insert(
6443 "erlang".to_string(),
6444 LspLanguageConfig::Multi(vec![LspServerConfig {
6445 command: "erlang_ls".to_string(),
6446 args: vec![],
6447 enabled: true,
6448 auto_start: false,
6449 process_limits: ProcessLimits::default(),
6450 initialization_options: None,
6451 env: Default::default(),
6452 language_id_overrides: Default::default(),
6453 name: None,
6454 only_features: None,
6455 except_features: None,
6456 root_markers: Default::default(),
6457 }]),
6458 );
6459
6460 lsp.insert(
6463 "haskell".to_string(),
6464 LspLanguageConfig::Multi(vec![LspServerConfig {
6465 command: "haskell-language-server-wrapper".to_string(),
6466 args: vec!["--lsp".to_string()],
6467 enabled: true,
6468 auto_start: false,
6469 process_limits: ProcessLimits::default(),
6470 initialization_options: None,
6471 env: Default::default(),
6472 language_id_overrides: Default::default(),
6473 name: None,
6474 only_features: None,
6475 except_features: None,
6476 root_markers: Default::default(),
6477 }]),
6478 );
6479
6480 lsp.insert(
6483 "ocaml".to_string(),
6484 LspLanguageConfig::Multi(vec![LspServerConfig {
6485 command: "ocamllsp".to_string(),
6486 args: vec![],
6487 enabled: true,
6488 auto_start: false,
6489 process_limits: ProcessLimits::default(),
6490 initialization_options: None,
6491 env: Default::default(),
6492 language_id_overrides: Default::default(),
6493 name: None,
6494 only_features: None,
6495 except_features: None,
6496 root_markers: Default::default(),
6497 }]),
6498 );
6499
6500 lsp.insert(
6503 "clojure".to_string(),
6504 LspLanguageConfig::Multi(vec![LspServerConfig {
6505 command: "clojure-lsp".to_string(),
6506 args: vec![],
6507 enabled: true,
6508 auto_start: false,
6509 process_limits: ProcessLimits::default(),
6510 initialization_options: None,
6511 env: Default::default(),
6512 language_id_overrides: Default::default(),
6513 name: None,
6514 only_features: None,
6515 except_features: None,
6516 root_markers: Default::default(),
6517 }]),
6518 );
6519
6520 lsp.insert(
6523 "r".to_string(),
6524 LspLanguageConfig::Multi(vec![LspServerConfig {
6525 command: "R".to_string(),
6526 args: vec![
6527 "--vanilla".to_string(),
6528 "-e".to_string(),
6529 "languageserver::run()".to_string(),
6530 ],
6531 enabled: true,
6532 auto_start: false,
6533 process_limits: ProcessLimits::default(),
6534 initialization_options: None,
6535 env: Default::default(),
6536 language_id_overrides: Default::default(),
6537 name: None,
6538 only_features: None,
6539 except_features: None,
6540 root_markers: Default::default(),
6541 }]),
6542 );
6543
6544 lsp.insert(
6547 "julia".to_string(),
6548 LspLanguageConfig::Multi(vec![LspServerConfig {
6549 command: "julia".to_string(),
6550 args: vec![
6551 "--startup-file=no".to_string(),
6552 "--history-file=no".to_string(),
6553 "-e".to_string(),
6554 "using LanguageServer; runserver()".to_string(),
6555 ],
6556 enabled: true,
6557 auto_start: false,
6558 process_limits: ProcessLimits::default(),
6559 initialization_options: None,
6560 env: Default::default(),
6561 language_id_overrides: Default::default(),
6562 name: None,
6563 only_features: None,
6564 except_features: None,
6565 root_markers: Default::default(),
6566 }]),
6567 );
6568
6569 lsp.insert(
6572 "perl".to_string(),
6573 LspLanguageConfig::Multi(vec![LspServerConfig {
6574 command: "perlnavigator".to_string(),
6575 args: vec!["--stdio".to_string()],
6576 enabled: true,
6577 auto_start: false,
6578 process_limits: ProcessLimits::default(),
6579 initialization_options: None,
6580 env: Default::default(),
6581 language_id_overrides: Default::default(),
6582 name: None,
6583 only_features: None,
6584 except_features: None,
6585 root_markers: Default::default(),
6586 }]),
6587 );
6588
6589 lsp.insert(
6592 "nim".to_string(),
6593 LspLanguageConfig::Multi(vec![LspServerConfig {
6594 command: "nimlangserver".to_string(),
6595 args: vec![],
6596 enabled: true,
6597 auto_start: false,
6598 process_limits: ProcessLimits::default(),
6599 initialization_options: None,
6600 env: Default::default(),
6601 language_id_overrides: Default::default(),
6602 name: None,
6603 only_features: None,
6604 except_features: None,
6605 root_markers: Default::default(),
6606 }]),
6607 );
6608
6609 lsp.insert(
6611 "gleam".to_string(),
6612 LspLanguageConfig::Multi(vec![LspServerConfig {
6613 command: "gleam".to_string(),
6614 args: vec!["lsp".to_string()],
6615 enabled: true,
6616 auto_start: false,
6617 process_limits: ProcessLimits::default(),
6618 initialization_options: None,
6619 env: Default::default(),
6620 language_id_overrides: Default::default(),
6621 name: None,
6622 only_features: None,
6623 except_features: None,
6624 root_markers: Default::default(),
6625 }]),
6626 );
6627
6628 lsp.insert(
6631 "racket".to_string(),
6632 LspLanguageConfig::Multi(vec![LspServerConfig {
6633 command: "racket-langserver".to_string(),
6634 args: vec![],
6635 enabled: true,
6636 auto_start: false,
6637 process_limits: ProcessLimits::default(),
6638 initialization_options: None,
6639 env: Default::default(),
6640 language_id_overrides: Default::default(),
6641 name: None,
6642 only_features: None,
6643 except_features: None,
6644 root_markers: vec!["info.rkt".to_string(), ".git".to_string()],
6645 }]),
6646 );
6647
6648 lsp.insert(
6651 "fsharp".to_string(),
6652 LspLanguageConfig::Multi(vec![LspServerConfig {
6653 command: "fsautocomplete".to_string(),
6654 args: vec!["--adaptive-lsp-server-enabled".to_string()],
6655 enabled: true,
6656 auto_start: false,
6657 process_limits: ProcessLimits::default(),
6658 initialization_options: None,
6659 env: Default::default(),
6660 language_id_overrides: Default::default(),
6661 name: None,
6662 only_features: None,
6663 except_features: None,
6664 root_markers: Default::default(),
6665 }]),
6666 );
6667
6668 let svls_config = LspServerConfig {
6672 command: "svls".to_string(),
6673 args: vec![],
6674 enabled: true,
6675 auto_start: false,
6676 process_limits: ProcessLimits::default(),
6677 initialization_options: None,
6678 env: Default::default(),
6679 language_id_overrides: Default::default(),
6680 name: None,
6681 only_features: None,
6682 except_features: None,
6683 root_markers: vec![".svls.toml".to_string(), ".git".to_string()],
6684 };
6685 lsp.insert(
6686 "verilog".to_string(),
6687 LspLanguageConfig::Multi(vec![svls_config.clone()]),
6688 );
6689 lsp.insert(
6690 "systemverilog".to_string(),
6691 LspLanguageConfig::Multi(vec![svls_config]),
6692 );
6693 }
6694 pub fn validate(&self) -> Result<(), ConfigError> {
6695 if self.editor.tab_size == 0 {
6697 return Err(ConfigError::ValidationError(
6698 "tab_size must be greater than 0".to_string(),
6699 ));
6700 }
6701
6702 if self.editor.scroll_offset > 100 {
6704 return Err(ConfigError::ValidationError(
6705 "scroll_offset must be <= 100".to_string(),
6706 ));
6707 }
6708
6709 for binding in &self.keybindings {
6711 if binding.key.is_empty() {
6712 return Err(ConfigError::ValidationError(
6713 "keybinding key cannot be empty".to_string(),
6714 ));
6715 }
6716 if binding.action.is_empty() {
6717 return Err(ConfigError::ValidationError(
6718 "keybinding action cannot be empty".to_string(),
6719 ));
6720 }
6721 }
6722
6723 Ok(())
6724 }
6725}
6726
6727#[derive(Debug)]
6729pub enum ConfigError {
6730 IoError(String),
6731 ParseError(String),
6732 SerializeError(String),
6733 ValidationError(String),
6734}
6735
6736impl std::fmt::Display for ConfigError {
6737 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6738 match self {
6739 Self::IoError(msg) => write!(f, "IO error: {msg}"),
6740 Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
6741 Self::SerializeError(msg) => write!(f, "Serialize error: {msg}"),
6742 Self::ValidationError(msg) => write!(f, "Validation error: {msg}"),
6743 }
6744 }
6745}
6746
6747impl std::error::Error for ConfigError {}
6748
6749#[cfg(test)]
6750mod tests {
6751 use super::*;
6752
6753 #[test]
6754 fn test_file_explorer_width_default_is_percent_30() {
6755 let cfg = FileExplorerConfig::default();
6756 assert_eq!(cfg.width, ExplorerWidth::Percent(30));
6757 }
6758
6759 #[test]
6760 fn test_tree_indicators_defaults_preserve_current_glyphs() {
6761 let cfg = FileExplorerConfig::default();
6765 assert_eq!(cfg.tree_indicator_collapsed, ">");
6766 assert_eq!(cfg.tree_indicator_expanded, "▼");
6767 }
6768
6769 #[test]
6770 fn test_tree_indicators_can_be_overridden_from_json() {
6771 let cfg: FileExplorerConfig = serde_json::from_str(
6772 r#"{"tree_indicator_collapsed": "▸", "tree_indicator_expanded": "▾"}"#,
6773 )
6774 .unwrap();
6775 assert_eq!(cfg.tree_indicator_collapsed, "▸");
6776 assert_eq!(cfg.tree_indicator_expanded, "▾");
6777 }
6778
6779 #[test]
6780 fn test_tree_indicators_partial_override_keeps_default_for_unset() {
6781 let cfg: FileExplorerConfig =
6782 serde_json::from_str(r#"{"tree_indicator_collapsed": "+"}"#).unwrap();
6783 assert_eq!(cfg.tree_indicator_collapsed, "+");
6784 assert_eq!(cfg.tree_indicator_expanded, "▼");
6786 }
6787
6788 #[test]
6791 fn test_width_accepts_legacy_float_fraction() {
6792 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": 0.3}"#).unwrap();
6793 assert_eq!(cfg.width, ExplorerWidth::Percent(30));
6794 }
6795
6796 #[test]
6797 fn test_width_accepts_bare_integer_as_percent() {
6798 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": 42}"#).unwrap();
6800 assert_eq!(cfg.width, ExplorerWidth::Percent(42));
6801 }
6802
6803 #[test]
6804 fn test_width_accepts_percent_string() {
6805 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "75%"}"#).unwrap();
6806 assert_eq!(cfg.width, ExplorerWidth::Percent(75));
6807 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "42 %"}"#).unwrap();
6809 assert_eq!(cfg.width, ExplorerWidth::Percent(42));
6810 }
6811
6812 #[test]
6813 fn test_width_accepts_columns_string() {
6814 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "24"}"#).unwrap();
6815 assert_eq!(cfg.width, ExplorerWidth::Columns(24));
6816 }
6817
6818 #[test]
6819 fn test_width_rejects_percent_over_100() {
6820 let err = serde_json::from_str::<FileExplorerConfig>(r#"{"width": "150%"}"#)
6821 .expect_err("percent > 100 should be rejected");
6822 assert!(err.to_string().contains("100"), "{err}");
6823 }
6824
6825 #[test]
6826 fn test_width_rejects_integer_over_100() {
6827 let err = serde_json::from_str::<FileExplorerConfig>(r#"{"width": 150}"#)
6830 .expect_err("bare integer > 100 should be rejected as percent");
6831 assert!(err.to_string().contains("percent") || err.to_string().contains("100"));
6832 }
6833
6834 #[test]
6835 fn test_width_rejects_garbage_string() {
6836 serde_json::from_str::<FileExplorerConfig>(r#"{"width": "big"}"#)
6837 .expect_err("non-numeric string should be rejected");
6838 }
6839
6840 #[test]
6843 fn test_width_serializes_percent_as_string_with_suffix() {
6844 let cfg = FileExplorerConfig {
6845 width: ExplorerWidth::Percent(30),
6846 ..Default::default()
6847 };
6848 let json = serde_json::to_value(&cfg).unwrap();
6849 assert_eq!(json["width"], serde_json::json!("30%"));
6850 }
6851
6852 #[test]
6853 fn test_width_serializes_columns_as_string_without_suffix() {
6854 let cfg = FileExplorerConfig {
6855 width: ExplorerWidth::Columns(24),
6856 ..Default::default()
6857 };
6858 let json = serde_json::to_value(&cfg).unwrap();
6859 assert_eq!(json["width"], serde_json::json!("24"));
6860 }
6861
6862 #[test]
6863 fn test_width_round_trip_both_variants() {
6864 for value in [ExplorerWidth::Percent(17), ExplorerWidth::Columns(42)] {
6865 let cfg = FileExplorerConfig {
6866 width: value,
6867 ..Default::default()
6868 };
6869 let json = serde_json::to_string(&cfg).unwrap();
6870 let restored: FileExplorerConfig = serde_json::from_str(&json).unwrap();
6871 assert_eq!(restored.width, value, "round trip failed for {:?}", value);
6872 }
6873 }
6874
6875 #[test]
6878 fn test_to_cols_percent() {
6879 assert_eq!(ExplorerWidth::Percent(30).to_cols(100), 30);
6880 assert_eq!(ExplorerWidth::Percent(25).to_cols(120), 30);
6881 assert_eq!(ExplorerWidth::Percent(30).to_cols(0), 0);
6883 assert_eq!(ExplorerWidth::Percent(100).to_cols(200), 200);
6884 }
6885
6886 #[test]
6887 fn test_to_cols_columns_clamps_to_terminal() {
6888 assert_eq!(ExplorerWidth::Columns(24).to_cols(100), 24);
6889 assert_eq!(ExplorerWidth::Columns(999).to_cols(80), 80);
6890 }
6891
6892 #[test]
6895 fn test_to_cols_enforces_min_width() {
6896 assert_eq!(
6898 ExplorerWidth::Columns(0).to_cols(100),
6899 ExplorerWidth::MIN_COLS
6900 );
6901 assert_eq!(
6902 ExplorerWidth::Columns(1).to_cols(100),
6903 ExplorerWidth::MIN_COLS
6904 );
6905 assert_eq!(
6906 ExplorerWidth::Columns(4).to_cols(100),
6907 ExplorerWidth::MIN_COLS
6908 );
6909 assert_eq!(
6910 ExplorerWidth::Percent(0).to_cols(100),
6911 ExplorerWidth::MIN_COLS
6912 );
6913 assert_eq!(
6915 ExplorerWidth::Percent(3).to_cols(100),
6916 ExplorerWidth::MIN_COLS
6917 );
6918 assert_eq!(
6920 ExplorerWidth::Columns(ExplorerWidth::MIN_COLS + 1).to_cols(100),
6921 ExplorerWidth::MIN_COLS + 1
6922 );
6923 }
6924
6925 #[test]
6928 fn test_to_cols_min_floor_yields_to_narrow_terminal() {
6929 assert_eq!(ExplorerWidth::Columns(10).to_cols(3), 3);
6930 assert_eq!(ExplorerWidth::Percent(100).to_cols(2), 2);
6931 assert_eq!(ExplorerWidth::Columns(1).to_cols(0), 0);
6932 }
6933
6934 #[test]
6937 fn test_load_from_file_accepts_legacy_float_fraction_width() {
6938 let dir = tempfile::tempdir().unwrap();
6939 let path = dir.path().join("config.json");
6940 std::fs::write(&path, r#"{"file_explorer":{"width":0.25}}"#).unwrap();
6941 let cfg = Config::load_from_file(&path).expect("legacy float fraction must still load");
6942 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Percent(25));
6943 }
6944
6945 #[test]
6946 fn test_load_from_file_accepts_columns_string_width() {
6947 let dir = tempfile::tempdir().unwrap();
6948 let path = dir.path().join("config.json");
6949 std::fs::write(&path, r#"{"file_explorer":{"width":"42"}}"#).unwrap();
6950 let cfg = Config::load_from_file(&path).unwrap();
6951 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Columns(42));
6952 }
6953
6954 #[test]
6955 fn test_load_from_file_accepts_percent_string_width() {
6956 let dir = tempfile::tempdir().unwrap();
6957 let path = dir.path().join("config.json");
6958 std::fs::write(&path, r#"{"file_explorer":{"width":"55%"}}"#).unwrap();
6959 let cfg = Config::load_from_file(&path).unwrap();
6960 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Percent(55));
6961 }
6962
6963 #[test]
6964 fn test_default_config() {
6965 let config = Config::default();
6966 assert_eq!(config.editor.tab_size, 4);
6967 assert!(config.editor.line_numbers);
6968 assert!(config.editor.syntax_highlighting);
6969 assert!(config.keybindings.is_empty());
6972 let resolved = config.resolve_keymap(&config.active_keybinding_map);
6974 assert!(!resolved.is_empty());
6975 }
6976
6977 #[test]
6978 fn test_all_builtin_keymaps_loadable() {
6979 for name in KeybindingMapName::BUILTIN_OPTIONS {
6980 let keymap = Config::load_builtin_keymap(name);
6981 assert!(keymap.is_some(), "Failed to load builtin keymap '{}'", name);
6982 }
6983 }
6984
6985 #[test]
6986 fn test_config_validation() {
6987 let mut config = Config::default();
6988 assert!(config.validate().is_ok());
6989
6990 config.editor.tab_size = 0;
6991 assert!(config.validate().is_err());
6992 }
6993
6994 #[test]
6995 fn test_macos_keymap_inherits_enter_bindings() {
6996 let config = Config::default();
6997 let bindings = config.resolve_keymap("macos");
6998
6999 let enter_bindings: Vec<_> = bindings.iter().filter(|b| b.key == "Enter").collect();
7000 assert!(
7001 !enter_bindings.is_empty(),
7002 "macos keymap should inherit Enter bindings from default, got {} Enter bindings",
7003 enter_bindings.len()
7004 );
7005 let has_insert_newline = enter_bindings.iter().any(|b| b.action == "insert_newline");
7007 assert!(
7008 has_insert_newline,
7009 "macos keymap should have insert_newline action for Enter key"
7010 );
7011 }
7012
7013 #[test]
7014 fn test_config_serialize_deserialize() {
7015 let config = Config::default();
7017
7018 let json = serde_json::to_string_pretty(&config).unwrap();
7020
7021 let loaded: Config = serde_json::from_str(&json).unwrap();
7023
7024 assert_eq!(config.editor.tab_size, loaded.editor.tab_size);
7025 assert_eq!(config.theme, loaded.theme);
7026 }
7027
7028 #[test]
7029 fn test_config_with_custom_keybinding() {
7030 let json = r#"{
7031 "editor": {
7032 "tab_size": 2
7033 },
7034 "keybindings": [
7035 {
7036 "key": "x",
7037 "modifiers": ["ctrl", "shift"],
7038 "action": "custom_action",
7039 "args": {},
7040 "when": null
7041 }
7042 ]
7043 }"#;
7044
7045 let config: Config = serde_json::from_str(json).unwrap();
7046 assert_eq!(config.editor.tab_size, 2);
7047 assert_eq!(config.keybindings.len(), 1);
7048 assert_eq!(config.keybindings[0].key, "x");
7049 assert_eq!(config.keybindings[0].modifiers.len(), 2);
7050 }
7051
7052 #[test]
7053 fn test_sparse_config_merges_with_defaults() {
7054 let temp_dir = tempfile::tempdir().unwrap();
7056 let config_path = temp_dir.path().join("config.json");
7057
7058 let sparse_config = r#"{
7060 "lsp": {
7061 "rust": {
7062 "command": "custom-rust-analyzer",
7063 "args": ["--custom-arg"]
7064 }
7065 }
7066 }"#;
7067 std::fs::write(&config_path, sparse_config).unwrap();
7068
7069 let loaded = Config::load_from_file(&config_path).unwrap();
7071
7072 assert!(loaded.lsp.contains_key("rust"));
7074 assert_eq!(
7075 loaded.lsp["rust"].as_slice()[0].command,
7076 "custom-rust-analyzer".to_string()
7077 );
7078
7079 assert!(
7081 loaded.lsp.contains_key("python"),
7082 "python LSP should be merged from defaults"
7083 );
7084 assert!(
7085 loaded.lsp.contains_key("typescript"),
7086 "typescript LSP should be merged from defaults"
7087 );
7088 assert!(
7089 loaded.lsp.contains_key("javascript"),
7090 "javascript LSP should be merged from defaults"
7091 );
7092
7093 assert!(loaded.languages.contains_key("rust"));
7095 assert!(loaded.languages.contains_key("python"));
7096 assert!(loaded.languages.contains_key("typescript"));
7097 }
7098
7099 #[test]
7100 fn test_empty_config_gets_all_defaults() {
7101 let temp_dir = tempfile::tempdir().unwrap();
7102 let config_path = temp_dir.path().join("config.json");
7103
7104 std::fs::write(&config_path, "{}").unwrap();
7106
7107 let loaded = Config::load_from_file(&config_path).unwrap();
7108 let defaults = Config::default();
7109
7110 assert_eq!(loaded.lsp.len(), defaults.lsp.len());
7112
7113 assert_eq!(loaded.languages.len(), defaults.languages.len());
7115 }
7116
7117 #[test]
7118 fn test_dynamic_submenu_expansion() {
7119 let temp_dir = tempfile::tempdir().unwrap();
7121 let themes_dir = temp_dir.path().to_path_buf();
7122
7123 let dynamic = MenuItem::DynamicSubmenu {
7124 label: "Test".to_string(),
7125 source: "copy_with_theme".to_string(),
7126 };
7127
7128 let expanded = dynamic.expand_dynamic(&themes_dir);
7129
7130 match expanded {
7132 MenuItem::Submenu { label, items } => {
7133 assert_eq!(label, "Test");
7134 let loader = crate::view::theme::ThemeLoader::embedded_only();
7136 let registry = loader.load_all(&[]);
7137 assert_eq!(items.len(), registry.len());
7138
7139 for (item, theme_info) in items.iter().zip(registry.list().iter()) {
7141 match item {
7142 MenuItem::Action {
7143 label,
7144 action,
7145 args,
7146 ..
7147 } => {
7148 assert_eq!(label, &theme_info.name);
7149 assert_eq!(action, "copy_with_theme");
7150 assert_eq!(
7151 args.get("theme").and_then(|v| v.as_str()),
7152 Some(theme_info.name.as_str())
7153 );
7154 }
7155 _ => panic!("Expected Action item"),
7156 }
7157 }
7158 }
7159 _ => panic!("Expected Submenu after expansion"),
7160 }
7161 }
7162
7163 #[test]
7164 fn test_non_dynamic_item_unchanged() {
7165 let temp_dir = tempfile::tempdir().unwrap();
7167 let themes_dir = temp_dir.path();
7168
7169 let action = MenuItem::Action {
7170 label: "Test".to_string(),
7171 action: "test".to_string(),
7172 args: HashMap::new(),
7173 when: None,
7174 checkbox: None,
7175 };
7176
7177 let expanded = action.expand_dynamic(themes_dir);
7178 match expanded {
7179 MenuItem::Action { label, action, .. } => {
7180 assert_eq!(label, "Test");
7181 assert_eq!(action, "test");
7182 }
7183 _ => panic!("Action should remain Action after expand_dynamic"),
7184 }
7185 }
7186
7187 #[test]
7188 fn test_buffer_config_uses_global_defaults() {
7189 let config = Config::default();
7190 let buffer_config = BufferConfig::resolve(&config, None);
7191
7192 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
7193 assert_eq!(buffer_config.auto_indent, config.editor.auto_indent);
7194 assert!(!buffer_config.use_tabs); assert!(buffer_config.whitespace.any_tabs()); assert!(buffer_config.formatter.is_none());
7197 assert!(!buffer_config.format_on_save);
7198 }
7199
7200 #[test]
7201 fn test_buffer_config_applies_language_overrides() {
7202 let mut config = Config::default();
7203
7204 config.languages.insert(
7206 "go".to_string(),
7207 LanguageConfig {
7208 extensions: vec!["go".to_string()],
7209 filenames: vec![],
7210 grammar: "go".to_string(),
7211 comment_prefix: Some("//".to_string()),
7212 auto_indent: true,
7213 auto_close: None,
7214 auto_surround: None,
7215 textmate_grammar: None,
7216 show_whitespace_tabs: false, line_wrap: None,
7218 wrap_column: None,
7219 page_view: None,
7220 page_width: None,
7221 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
7224 command: "gofmt".to_string(),
7225 args: vec![],
7226 stdin: true,
7227 timeout_ms: 10000,
7228 }),
7229 format_on_save: true,
7230 on_save: vec![],
7231 word_characters: None,
7232 },
7233 );
7234
7235 let buffer_config = BufferConfig::resolve(&config, Some("go"));
7236
7237 assert_eq!(buffer_config.tab_size, 8);
7238 assert!(buffer_config.use_tabs);
7239 assert!(!buffer_config.whitespace.any_tabs()); assert!(buffer_config.format_on_save);
7241 assert!(buffer_config.formatter.is_some());
7242 assert_eq!(buffer_config.formatter.as_ref().unwrap().command, "gofmt");
7243 }
7244
7245 #[test]
7246 fn test_buffer_config_unknown_language_uses_global() {
7247 let config = Config::default();
7248 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
7249
7250 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
7252 assert!(!buffer_config.use_tabs);
7253 }
7254
7255 #[test]
7256 fn test_buffer_config_per_language_line_wrap() {
7257 let mut config = Config::default();
7258 config.editor.line_wrap = false;
7259
7260 config.languages.insert(
7262 "markdown".to_string(),
7263 LanguageConfig {
7264 extensions: vec!["md".to_string()],
7265 line_wrap: Some(true),
7266 ..Default::default()
7267 },
7268 );
7269
7270 let md_config = BufferConfig::resolve(&config, Some("markdown"));
7272 assert!(md_config.line_wrap, "Markdown should have line_wrap=true");
7273
7274 let other_config = BufferConfig::resolve(&config, Some("rust"));
7276 assert!(
7277 !other_config.line_wrap,
7278 "Non-configured languages should use global line_wrap=false"
7279 );
7280
7281 let no_lang_config = BufferConfig::resolve(&config, None);
7283 assert!(
7284 !no_lang_config.line_wrap,
7285 "No language should use global line_wrap=false"
7286 );
7287 }
7288
7289 #[test]
7290 fn test_buffer_config_per_language_wrap_column() {
7291 let mut config = Config::default();
7292 config.editor.wrap_column = Some(120);
7293
7294 config.languages.insert(
7296 "markdown".to_string(),
7297 LanguageConfig {
7298 extensions: vec!["md".to_string()],
7299 wrap_column: Some(80),
7300 ..Default::default()
7301 },
7302 );
7303
7304 let md_config = BufferConfig::resolve(&config, Some("markdown"));
7306 assert_eq!(md_config.wrap_column, Some(80));
7307
7308 let other_config = BufferConfig::resolve(&config, Some("rust"));
7310 assert_eq!(other_config.wrap_column, Some(120));
7311
7312 let no_lang_config = BufferConfig::resolve(&config, None);
7314 assert_eq!(no_lang_config.wrap_column, Some(120));
7315 }
7316
7317 #[test]
7318 fn test_normalize_zero_sentinels_global() {
7319 let mut config = Config::default();
7320 config.editor.wrap_column = Some(0);
7321 config.editor.page_width = Some(0);
7322 config.editor.tab_size = 0;
7323
7324 config.normalize_zero_sentinels();
7325
7326 assert_eq!(config.editor.wrap_column, None);
7328 assert_eq!(config.editor.page_width, None);
7329 assert_eq!(config.editor.tab_size, default_tab_size());
7330 }
7331
7332 #[test]
7333 fn test_normalize_zero_sentinels_language_inherits_global() {
7334 let mut config = Config::default();
7335 config.editor.wrap_column = Some(120);
7336 config.editor.page_width = Some(80);
7337 config.editor.tab_size = 8;
7338
7339 config.languages.insert(
7342 "markdown".to_string(),
7343 LanguageConfig {
7344 extensions: vec!["md".to_string()],
7345 wrap_column: Some(0),
7346 page_width: Some(0),
7347 tab_size: Some(0),
7348 ..Default::default()
7349 },
7350 );
7351
7352 config.normalize_zero_sentinels();
7353
7354 let lang = &config.languages["markdown"];
7355 assert_eq!(lang.wrap_column, None);
7356 assert_eq!(lang.page_width, None);
7357 assert_eq!(lang.tab_size, None);
7358
7359 let md = BufferConfig::resolve(&config, Some("markdown"));
7361 assert_eq!(md.wrap_column, Some(120));
7362 assert_eq!(md.tab_size, 8);
7363 }
7364
7365 #[test]
7366 fn test_buffer_config_indent_string() {
7367 let config = Config::default();
7368
7369 let spaces_config = BufferConfig::resolve(&config, None);
7371 assert_eq!(spaces_config.indent_string(), " "); let mut config_with_tabs = Config::default();
7375 config_with_tabs.languages.insert(
7376 "makefile".to_string(),
7377 LanguageConfig {
7378 use_tabs: Some(true),
7379 tab_size: Some(8),
7380 ..Default::default()
7381 },
7382 );
7383 let tabs_config = BufferConfig::resolve(&config_with_tabs, Some("makefile"));
7384 assert_eq!(tabs_config.indent_string(), "\t");
7385 }
7386
7387 #[test]
7388 fn test_buffer_config_global_use_tabs_inherited() {
7389 let mut config = Config::default();
7392 config.editor.use_tabs = true;
7393
7394 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
7396 assert!(buffer_config.use_tabs);
7397
7398 let buffer_config = BufferConfig::resolve(&config, None);
7400 assert!(buffer_config.use_tabs);
7401
7402 config.languages.insert(
7404 "python".to_string(),
7405 LanguageConfig {
7406 use_tabs: Some(false),
7407 ..Default::default()
7408 },
7409 );
7410 let buffer_config = BufferConfig::resolve(&config, Some("python"));
7411 assert!(!buffer_config.use_tabs);
7412
7413 config.languages.insert(
7415 "rust".to_string(),
7416 LanguageConfig {
7417 use_tabs: None,
7418 ..Default::default()
7419 },
7420 );
7421 let buffer_config = BufferConfig::resolve(&config, Some("rust"));
7422 assert!(buffer_config.use_tabs);
7423 }
7424
7425 #[test]
7431 #[cfg(feature = "runtime")]
7432 fn test_lsp_languages_have_language_config() {
7433 let config = Config::default();
7434 let exceptions = ["tailwindcss"];
7435 for lsp_key in config.lsp.keys() {
7436 if exceptions.contains(&lsp_key.as_str()) {
7437 continue;
7438 }
7439 assert!(
7440 config.languages.contains_key(lsp_key),
7441 "LSP config key '{}' has no matching entry in default_languages(). \
7442 Add a LanguageConfig with the correct file extensions so detect_language() \
7443 can map files to this language.",
7444 lsp_key
7445 );
7446 }
7447 }
7448
7449 #[test]
7450 #[cfg(feature = "runtime")]
7451 fn test_default_config_has_quicklsp_in_universal_lsp() {
7452 let config = Config::default();
7453 assert!(
7454 config.universal_lsp.contains_key("quicklsp"),
7455 "Default config should contain quicklsp in universal_lsp"
7456 );
7457 let quicklsp = &config.universal_lsp["quicklsp"];
7458 let server = &quicklsp.as_slice()[0];
7459 assert_eq!(server.command, "quicklsp");
7460 assert!(!server.enabled, "quicklsp should be disabled by default");
7461 assert_eq!(server.name.as_deref(), Some("QuickLSP"));
7462 assert!(
7466 server.only_features.is_none(),
7467 "quicklsp must not default to a feature whitelist"
7468 );
7469 assert!(server.except_features.is_none());
7470 }
7471
7472 #[test]
7473 fn test_empty_config_preserves_universal_lsp_defaults() {
7474 let temp_dir = tempfile::tempdir().unwrap();
7475 let config_path = temp_dir.path().join("config.json");
7476
7477 std::fs::write(&config_path, "{}").unwrap();
7479
7480 let loaded = Config::load_from_file(&config_path).unwrap();
7481 let defaults = Config::default();
7482
7483 assert_eq!(
7485 loaded.universal_lsp.len(),
7486 defaults.universal_lsp.len(),
7487 "Empty config should preserve all default universal_lsp entries"
7488 );
7489 }
7490
7491 #[test]
7492 fn test_universal_lsp_config_merges_with_defaults() {
7493 let temp_dir = tempfile::tempdir().unwrap();
7494 let config_path = temp_dir.path().join("config.json");
7495
7496 let config_json = r#"{
7498 "universal_lsp": {
7499 "quicklsp": {
7500 "enabled": true
7501 }
7502 }
7503 }"#;
7504 std::fs::write(&config_path, config_json).unwrap();
7505
7506 let loaded = Config::load_from_file(&config_path).unwrap();
7507
7508 assert!(loaded.universal_lsp.contains_key("quicklsp"));
7510 let server = &loaded.universal_lsp["quicklsp"].as_slice()[0];
7511 assert!(server.enabled, "User override should enable quicklsp");
7512 assert_eq!(
7514 server.command, "quicklsp",
7515 "Default command should be merged when not specified by user"
7516 );
7517 }
7518
7519 #[test]
7520 fn test_universal_lsp_custom_server_added() {
7521 let temp_dir = tempfile::tempdir().unwrap();
7522 let config_path = temp_dir.path().join("config.json");
7523
7524 let config_json = r#"{
7526 "universal_lsp": {
7527 "my-custom-server": {
7528 "command": "my-server",
7529 "enabled": true,
7530 "auto_start": true
7531 }
7532 }
7533 }"#;
7534 std::fs::write(&config_path, config_json).unwrap();
7535
7536 let loaded = Config::load_from_file(&config_path).unwrap();
7537
7538 assert!(
7540 loaded.universal_lsp.contains_key("my-custom-server"),
7541 "Custom universal server should be loaded"
7542 );
7543 let server = &loaded.universal_lsp["my-custom-server"].as_slice()[0];
7544 assert_eq!(server.command, "my-server");
7545 assert!(server.enabled);
7546 assert!(server.auto_start);
7547
7548 assert!(
7550 loaded.universal_lsp.contains_key("quicklsp"),
7551 "Default quicklsp should be merged from defaults"
7552 );
7553 }
7554}