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)]
807 #[schemars(extend("x-section" = "Display"))]
808 pub wrap_column: Option<usize>,
809
810 #[serde(default = "default_page_width")]
814 #[schemars(extend("x-section" = "Display"))]
815 pub page_width: Option<usize>,
816
817 #[serde(default = "default_true")]
819 #[schemars(extend("x-section" = "Display"))]
820 pub syntax_highlighting: bool,
821
822 #[serde(default = "default_true")]
827 #[schemars(extend("x-section" = "Display"))]
828 pub show_menu_bar: bool,
829
830 #[serde(default = "default_true")]
835 #[schemars(extend("x-section" = "Display"))]
836 pub menu_bar_mnemonics: bool,
837
838 #[serde(default = "default_true")]
843 #[schemars(extend("x-section" = "Display"))]
844 pub show_tab_bar: bool,
845
846 #[serde(default = "default_true")]
851 #[schemars(extend("x-section" = "Display"))]
852 pub show_status_bar: bool,
853
854 #[serde(default)]
857 #[schemars(extend("x-section" = "Status Bar"))]
858 pub status_bar: StatusBarConfig,
859
860 #[serde(default = "default_false")]
867 #[schemars(extend("x-section" = "Display"))]
868 pub show_prompt_line: bool,
869
870 #[serde(default = "default_true")]
874 #[schemars(extend("x-section" = "Display"))]
875 pub show_vertical_scrollbar: bool,
876
877 #[serde(default = "default_false")]
882 #[schemars(extend("x-section" = "Display"))]
883 pub show_horizontal_scrollbar: bool,
884
885 #[serde(default = "default_true")]
889 #[schemars(extend("x-section" = "Display"))]
890 pub show_tilde: bool,
891
892 #[serde(default = "default_false")]
897 #[schemars(extend("x-section" = "Display"))]
898 pub use_terminal_bg: bool,
899
900 #[serde(default = "default_true")]
906 #[schemars(extend("x-section" = "Display"))]
907 pub set_window_title: bool,
908
909 #[serde(default)]
913 #[schemars(extend("x-section" = "Display"))]
914 pub cursor_style: CursorStyle,
915
916 #[serde(default)]
921 #[schemars(extend("x-section" = "Display"))]
922 pub rulers: Vec<usize>,
923
924 #[serde(default = "default_true")]
930 #[schemars(extend("x-section" = "Whitespace"))]
931 pub whitespace_show: bool,
932
933 #[serde(default = "default_false")]
937 #[schemars(extend("x-section" = "Whitespace"))]
938 pub whitespace_spaces_leading: bool,
939
940 #[serde(default = "default_false")]
944 #[schemars(extend("x-section" = "Whitespace"))]
945 pub whitespace_spaces_inner: bool,
946
947 #[serde(default = "default_false")]
951 #[schemars(extend("x-section" = "Whitespace"))]
952 pub whitespace_spaces_trailing: bool,
953
954 #[serde(default = "default_true")]
958 #[schemars(extend("x-section" = "Whitespace"))]
959 pub whitespace_tabs_leading: bool,
960
961 #[serde(default = "default_true")]
965 #[schemars(extend("x-section" = "Whitespace"))]
966 pub whitespace_tabs_inner: bool,
967
968 #[serde(default = "default_true")]
972 #[schemars(extend("x-section" = "Whitespace"))]
973 pub whitespace_tabs_trailing: bool,
974
975 #[serde(default = "default_false")]
981 #[schemars(extend("x-section" = "Editing"))]
982 pub use_tabs: bool,
983
984 #[serde(default = "default_tab_size")]
986 #[schemars(extend("x-section" = "Editing"))]
987 pub tab_size: usize,
988
989 #[serde(default = "default_true")]
991 #[schemars(extend("x-section" = "Editing"))]
992 pub auto_indent: bool,
993
994 #[serde(default = "default_true")]
1001 #[schemars(extend("x-section" = "Editing"))]
1002 pub auto_close: bool,
1003
1004 #[serde(default = "default_true")]
1009 #[schemars(extend("x-section" = "Editing"))]
1010 pub auto_surround: bool,
1011
1012 #[serde(default = "default_scroll_offset")]
1014 #[schemars(extend("x-section" = "Editing"))]
1015 pub scroll_offset: usize,
1016
1017 #[serde(default)]
1022 #[schemars(extend("x-section" = "Editing"))]
1023 pub default_line_ending: LineEndingOption,
1024
1025 #[serde(default = "default_false")]
1028 #[schemars(extend("x-section" = "Editing"))]
1029 pub trim_trailing_whitespace_on_save: bool,
1030
1031 #[serde(default = "default_false")]
1034 #[schemars(extend("x-section" = "Editing"))]
1035 pub ensure_final_newline_on_save: bool,
1036
1037 #[serde(default = "default_true")]
1041 #[schemars(extend("x-section" = "Bracket Matching"))]
1042 pub highlight_matching_brackets: bool,
1043
1044 #[serde(default = "default_true")]
1048 #[schemars(extend("x-section" = "Bracket Matching"))]
1049 pub rainbow_brackets: bool,
1050
1051 #[serde(default = "default_false")]
1058 #[schemars(extend("x-section" = "Completion"))]
1059 pub completion_popup_auto_show: bool,
1060
1061 #[serde(default = "default_true")]
1067 #[schemars(extend("x-section" = "Completion"))]
1068 pub quick_suggestions: bool,
1069
1070 #[serde(default = "default_quick_suggestions_delay")]
1076 #[schemars(extend("x-section" = "Completion"))]
1077 pub quick_suggestions_delay_ms: u64,
1078
1079 #[serde(default = "default_true")]
1083 #[schemars(extend("x-section" = "Completion"))]
1084 pub suggest_on_trigger_characters: bool,
1085
1086 #[serde(default = "default_true")]
1089 #[schemars(extend("x-section" = "LSP"))]
1090 pub enable_inlay_hints: bool,
1091
1092 #[serde(default = "default_false")]
1096 #[schemars(extend("x-section" = "LSP"))]
1097 pub enable_semantic_tokens_full: bool,
1098
1099 #[serde(default = "default_false")]
1104 #[schemars(extend("x-section" = "Diagnostics"))]
1105 pub diagnostics_inline_text: bool,
1106
1107 #[serde(default = "default_mouse_hover_enabled")]
1118 #[schemars(extend("x-section" = "Mouse"))]
1119 pub mouse_hover_enabled: bool,
1120
1121 #[serde(default = "default_mouse_hover_delay")]
1125 #[schemars(extend("x-section" = "Mouse"))]
1126 pub mouse_hover_delay_ms: u64,
1127
1128 #[serde(default = "default_double_click_time")]
1132 #[schemars(extend("x-section" = "Mouse"))]
1133 pub double_click_time_ms: u64,
1134
1135 #[serde(default = "default_false")]
1140 #[schemars(extend("x-section" = "Recovery"))]
1141 pub auto_save_enabled: bool,
1142
1143 #[serde(default = "default_auto_save_interval")]
1148 #[schemars(extend("x-section" = "Recovery"))]
1149 pub auto_save_interval_secs: u32,
1150
1151 #[serde(default = "default_true", alias = "persist_unnamed_buffers")]
1158 #[schemars(extend("x-section" = "Recovery"))]
1159 pub hot_exit: bool,
1160
1161 #[serde(default)]
1168 #[schemars(extend("x-section" = "Startup"))]
1169 pub confirm_quit: bool,
1170
1171 #[serde(default = "default_true")]
1182 #[schemars(extend("x-section" = "Startup"))]
1183 pub restore_previous_session: bool,
1184
1185 #[serde(default = "default_true")]
1196 #[schemars(extend("x-section" = "Startup"))]
1197 pub skip_session_restore_when_files_passed: bool,
1198
1199 #[serde(default = "default_true")]
1207 #[schemars(extend("x-section" = "Startup"))]
1208 pub auto_create_empty_buffer_on_last_buffer_close: bool,
1209
1210 #[serde(default = "default_true")]
1215 #[schemars(extend("x-section" = "Recovery"))]
1216 pub recovery_enabled: bool,
1217
1218 #[serde(default = "default_auto_recovery_save_interval")]
1223 #[schemars(extend("x-section" = "Recovery"))]
1224 pub auto_recovery_save_interval_secs: u32,
1225
1226 #[serde(default = "default_auto_revert_poll_interval")]
1231 #[schemars(extend("x-section" = "Recovery"))]
1232 pub auto_revert_poll_interval_ms: u64,
1233
1234 #[serde(default = "default_true")]
1240 #[schemars(extend("x-section" = "Keyboard"))]
1241 pub keyboard_disambiguate_escape_codes: bool,
1242
1243 #[serde(default = "default_false")]
1248 #[schemars(extend("x-section" = "Keyboard"))]
1249 pub keyboard_report_event_types: bool,
1250
1251 #[serde(default = "default_true")]
1256 #[schemars(extend("x-section" = "Keyboard"))]
1257 pub keyboard_report_alternate_keys: bool,
1258
1259 #[serde(default = "default_false")]
1265 #[schemars(extend("x-section" = "Keyboard"))]
1266 pub keyboard_report_all_keys_as_escape_codes: bool,
1267
1268 #[serde(default = "default_highlight_timeout")]
1271 #[schemars(extend("x-section" = "Performance"))]
1272 pub highlight_timeout_ms: u64,
1273
1274 #[serde(default = "default_snapshot_interval")]
1276 #[schemars(extend("x-section" = "Performance"))]
1277 pub snapshot_interval: usize,
1278
1279 #[serde(default = "default_highlight_context_bytes")]
1284 #[schemars(extend("x-section" = "Performance"))]
1285 pub highlight_context_bytes: usize,
1286
1287 #[serde(default = "default_large_file_threshold")]
1294 #[schemars(extend("x-section" = "Performance"))]
1295 pub large_file_threshold_bytes: u64,
1296
1297 #[serde(default = "default_estimated_line_length")]
1301 #[schemars(extend("x-section" = "Performance"))]
1302 pub estimated_line_length: usize,
1303
1304 #[serde(default = "default_read_concurrency")]
1309 #[schemars(extend("x-section" = "Performance"))]
1310 pub read_concurrency: usize,
1311
1312 #[serde(default = "default_file_tree_poll_interval")]
1317 #[schemars(extend("x-section" = "Performance"))]
1318 pub file_tree_poll_interval_ms: u64,
1319}
1320
1321fn default_tab_size() -> usize {
1322 4
1323}
1324
1325pub const LARGE_FILE_THRESHOLD_BYTES: u64 = 1024 * 1024; fn default_large_file_threshold() -> u64 {
1331 LARGE_FILE_THRESHOLD_BYTES
1332}
1333
1334pub const INDENT_FOLD_MAX_SCAN_LINES: usize = 10_000;
1337
1338pub const INDENT_FOLD_INDICATOR_MAX_SCAN: usize = 50;
1341
1342pub const INDENT_FOLD_MAX_UPWARD_SCAN: usize = 200;
1345
1346fn default_read_concurrency() -> usize {
1347 64
1348}
1349
1350fn default_true() -> bool {
1351 true
1352}
1353
1354fn default_false() -> bool {
1355 false
1356}
1357
1358fn default_quick_suggestions_delay() -> u64 {
1359 150 }
1361
1362fn default_scroll_offset() -> usize {
1363 3
1364}
1365
1366fn default_highlight_timeout() -> u64 {
1367 5
1368}
1369
1370fn default_snapshot_interval() -> usize {
1371 100
1372}
1373
1374fn default_estimated_line_length() -> usize {
1375 80
1376}
1377
1378fn default_auto_save_interval() -> u32 {
1379 30 }
1381
1382fn default_auto_recovery_save_interval() -> u32 {
1383 2 }
1385
1386fn default_highlight_context_bytes() -> usize {
1387 10_000 }
1389
1390fn default_mouse_hover_enabled() -> bool {
1391 !cfg!(windows)
1392}
1393
1394fn default_mouse_hover_delay() -> u64 {
1395 500 }
1397
1398fn default_double_click_time() -> u64 {
1399 500 }
1401
1402fn default_auto_revert_poll_interval() -> u64 {
1403 2000 }
1405
1406fn default_file_tree_poll_interval() -> u64 {
1407 3000 }
1409
1410impl Default for EditorConfig {
1411 fn default() -> Self {
1412 Self {
1413 use_tabs: false,
1414 tab_size: default_tab_size(),
1415 auto_indent: true,
1416 auto_close: true,
1417 auto_surround: true,
1418 animations: true,
1419 cursor_jump_animation: true,
1420 line_numbers: true,
1421 relative_line_numbers: false,
1422 scroll_offset: default_scroll_offset(),
1423 syntax_highlighting: true,
1424 highlight_current_line: true,
1425 highlight_current_column: false,
1426 line_wrap: true,
1427 wrap_indent: true,
1428 wrap_column: None,
1429 page_width: default_page_width(),
1430 highlight_timeout_ms: default_highlight_timeout(),
1431 snapshot_interval: default_snapshot_interval(),
1432 large_file_threshold_bytes: default_large_file_threshold(),
1433 estimated_line_length: default_estimated_line_length(),
1434 enable_inlay_hints: true,
1435 enable_semantic_tokens_full: false,
1436 diagnostics_inline_text: false,
1437 auto_save_enabled: false,
1438 auto_save_interval_secs: default_auto_save_interval(),
1439 hot_exit: true,
1440 confirm_quit: false,
1441 restore_previous_session: true,
1442 skip_session_restore_when_files_passed: true,
1443 auto_create_empty_buffer_on_last_buffer_close: true,
1444 recovery_enabled: true,
1445 auto_recovery_save_interval_secs: default_auto_recovery_save_interval(),
1446 highlight_context_bytes: default_highlight_context_bytes(),
1447 mouse_hover_enabled: default_mouse_hover_enabled(),
1448 mouse_hover_delay_ms: default_mouse_hover_delay(),
1449 double_click_time_ms: default_double_click_time(),
1450 auto_revert_poll_interval_ms: default_auto_revert_poll_interval(),
1451 read_concurrency: default_read_concurrency(),
1452 file_tree_poll_interval_ms: default_file_tree_poll_interval(),
1453 default_line_ending: LineEndingOption::default(),
1454 trim_trailing_whitespace_on_save: false,
1455 ensure_final_newline_on_save: false,
1456 highlight_matching_brackets: true,
1457 rainbow_brackets: true,
1458 cursor_style: CursorStyle::default(),
1459 keyboard_disambiguate_escape_codes: true,
1460 keyboard_report_event_types: false,
1461 keyboard_report_alternate_keys: true,
1462 keyboard_report_all_keys_as_escape_codes: false,
1463 completion_popup_auto_show: false,
1464 quick_suggestions: true,
1465 quick_suggestions_delay_ms: default_quick_suggestions_delay(),
1466 suggest_on_trigger_characters: true,
1467 show_menu_bar: true,
1468 menu_bar_mnemonics: true,
1469 show_tab_bar: true,
1470 show_status_bar: true,
1471 status_bar: StatusBarConfig::default(),
1472 show_prompt_line: false,
1473 show_vertical_scrollbar: true,
1474 show_horizontal_scrollbar: false,
1475 show_tilde: true,
1476 use_terminal_bg: false,
1477 set_window_title: true,
1478 rulers: Vec::new(),
1479 whitespace_show: true,
1480 whitespace_spaces_leading: false,
1481 whitespace_spaces_inner: false,
1482 whitespace_spaces_trailing: false,
1483 whitespace_tabs_leading: true,
1484 whitespace_tabs_inner: true,
1485 whitespace_tabs_trailing: true,
1486 }
1487 }
1488}
1489
1490#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
1492#[serde(rename_all = "snake_case")]
1493pub enum FileExplorerSide {
1494 #[default]
1495 Left,
1496 Right,
1497}
1498
1499#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1501pub struct FileExplorerConfig {
1502 #[serde(default = "default_true")]
1504 pub respect_gitignore: bool,
1505
1506 #[serde(default = "default_false")]
1508 pub show_hidden: bool,
1509
1510 #[serde(default = "default_false")]
1512 pub show_gitignored: bool,
1513
1514 #[serde(default)]
1516 pub custom_ignore_patterns: Vec<String>,
1517
1518 #[serde(default = "default_explorer_width")]
1524 pub width: ExplorerWidth,
1525
1526 #[serde(default = "default_true")]
1533 pub preview_tabs: bool,
1534
1535 #[serde(default = "default_explorer_side")]
1538 pub side: FileExplorerSide,
1539
1540 #[serde(default = "default_true")]
1546 pub auto_open_on_last_buffer_close: bool,
1547
1548 #[serde(default = "default_false")]
1554 pub follow_active_buffer: bool,
1555
1556 #[serde(default = "default_true")]
1563 pub compact_directories: bool,
1564
1565 #[serde(default = "default_tree_indicator_collapsed")]
1571 pub tree_indicator_collapsed: String,
1572
1573 #[serde(default = "default_tree_indicator_expanded")]
1579 pub tree_indicator_expanded: String,
1580}
1581
1582#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1610pub enum ExplorerWidth {
1611 Percent(u8),
1612 Columns(u16),
1613}
1614
1615impl ExplorerWidth {
1616 pub const DEFAULT: Self = Self::Percent(30);
1618
1619 pub const MIN_COLS: u16 = 5;
1625
1626 pub fn to_cols(self, terminal_width: u16) -> u16 {
1634 let raw = match self {
1635 Self::Percent(pct) => ((terminal_width as u32 * pct as u32) / 100) as u16,
1636 Self::Columns(cols) => cols,
1637 };
1638 raw.max(Self::MIN_COLS).min(terminal_width)
1639 }
1640}
1641
1642impl Default for ExplorerWidth {
1643 fn default() -> Self {
1644 Self::DEFAULT
1645 }
1646}
1647
1648impl std::fmt::Display for ExplorerWidth {
1649 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1650 match self {
1651 Self::Percent(n) => write!(f, "{}%", n),
1652 Self::Columns(n) => write!(f, "{}", n),
1653 }
1654 }
1655}
1656
1657#[derive(Debug)]
1659pub struct ExplorerWidthParseError(String);
1660
1661impl std::fmt::Display for ExplorerWidthParseError {
1662 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1663 write!(f, "{}", self.0)
1664 }
1665}
1666
1667impl std::error::Error for ExplorerWidthParseError {}
1668
1669impl std::str::FromStr for ExplorerWidth {
1670 type Err = ExplorerWidthParseError;
1671
1672 fn from_str(s: &str) -> Result<Self, Self::Err> {
1673 let s = s.trim();
1674 if s.is_empty() {
1675 return Err(ExplorerWidthParseError(
1676 "explorer width: empty string".into(),
1677 ));
1678 }
1679 if let Some(rest) = s.strip_suffix('%') {
1680 let n: u16 = rest.trim().parse().map_err(|_| {
1681 ExplorerWidthParseError(format!("explorer width: {:?} is not a valid percent", s))
1682 })?;
1683 if n > 100 {
1684 return Err(ExplorerWidthParseError(format!(
1685 "explorer width: {}% exceeds 100%",
1686 n
1687 )));
1688 }
1689 Ok(Self::Percent(n as u8))
1690 } else {
1691 let n: u16 = s.parse().map_err(|_| {
1692 ExplorerWidthParseError(format!(
1693 "explorer width: {:?} is neither a percent (e.g. \"30%\") nor a column count (e.g. \"24\")",
1694 s
1695 ))
1696 })?;
1697 Ok(Self::Columns(n))
1698 }
1699 }
1700}
1701
1702impl serde::Serialize for ExplorerWidth {
1703 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1704 s.collect_str(self)
1705 }
1706}
1707
1708impl<'de> serde::Deserialize<'de> for ExplorerWidth {
1709 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
1710 let raw = serde_json::Value::deserialize(d)?;
1711 explorer_width::from_value(&raw)
1712 }
1713}
1714
1715impl schemars::JsonSchema for ExplorerWidth {
1716 fn schema_name() -> std::borrow::Cow<'static, str> {
1717 std::borrow::Cow::Borrowed("ExplorerWidth")
1718 }
1719
1720 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
1721 schemars::json_schema!({
1725 "type": "string",
1726 "pattern": r"^(100%|[1-9]?[0-9]%|\d+)$",
1727 "description": "Either a percent like \"30%\" (0–100) or an absolute column count like \"24\".",
1728 })
1729 }
1730}
1731
1732fn default_explorer_width() -> ExplorerWidth {
1733 ExplorerWidth::DEFAULT
1734}
1735
1736fn default_explorer_side() -> FileExplorerSide {
1737 FileExplorerSide::default()
1738}
1739
1740fn default_tree_indicator_collapsed() -> String {
1741 ">".to_string()
1742}
1743
1744fn default_tree_indicator_expanded() -> String {
1745 "▼".to_string()
1746}
1747
1748pub fn default_explorer_width_value() -> ExplorerWidth {
1750 ExplorerWidth::DEFAULT
1751}
1752
1753pub(crate) mod explorer_width {
1757 use super::ExplorerWidth;
1758 use serde::de::{self, Deserialize, Deserializer};
1759 use std::str::FromStr;
1760
1761 pub fn deserialize_optional<'de, D>(d: D) -> Result<Option<ExplorerWidth>, D::Error>
1766 where
1767 D: Deserializer<'de>,
1768 {
1769 let raw = Option::<serde_json::Value>::deserialize(d)?;
1770 match raw {
1771 None | Some(serde_json::Value::Null) => Ok(None),
1772 Some(v) => from_value(&v).map(Some),
1773 }
1774 }
1775
1776 pub(super) fn from_value<E: de::Error>(v: &serde_json::Value) -> Result<ExplorerWidth, E> {
1777 match v {
1778 serde_json::Value::String(s) => ExplorerWidth::from_str(s).map_err(E::custom),
1779 serde_json::Value::Number(n) => {
1780 if let Some(u) = n.as_u64() {
1781 if u > 100 {
1785 return Err(E::custom(format!(
1786 "explorer width: {} exceeds 100 (percent). Use \"{}\" for columns.",
1787 u, u
1788 )));
1789 }
1790 Ok(ExplorerWidth::Percent(u as u8))
1791 } else if let Some(f) = n.as_f64() {
1792 let pct = if (0.0..=1.0).contains(&f) {
1794 f * 100.0
1795 } else {
1796 f
1797 };
1798 if !(0.0..=100.0).contains(&pct) {
1799 return Err(E::custom(format!(
1800 "explorer width: percent {} out of range 0..=100",
1801 pct
1802 )));
1803 }
1804 Ok(ExplorerWidth::Percent(pct.round() as u8))
1805 } else {
1806 Err(E::custom("explorer width: unsupported number"))
1807 }
1808 }
1809 _ => Err(E::custom(
1810 "explorer width: expected \"30%\", \"24\" (columns), or a number",
1811 )),
1812 }
1813 }
1814}
1815
1816#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1827pub struct ClipboardConfig {
1828 #[serde(default = "default_true")]
1831 pub use_osc52: bool,
1832
1833 #[serde(default = "default_true")]
1836 pub use_system_clipboard: bool,
1837}
1838
1839impl Default for ClipboardConfig {
1840 fn default() -> Self {
1841 Self {
1842 use_osc52: true,
1843 use_system_clipboard: true,
1844 }
1845 }
1846}
1847
1848#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1850pub struct TerminalConfig {
1851 #[serde(default = "default_true")]
1854 pub jump_to_end_on_output: bool,
1855
1856 #[serde(default)]
1868 pub shell: Option<TerminalShellConfig>,
1869}
1870
1871impl Default for TerminalConfig {
1872 fn default() -> Self {
1873 Self {
1874 jump_to_end_on_output: true,
1875 shell: None,
1876 }
1877 }
1878}
1879
1880#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1882pub struct TerminalShellConfig {
1883 pub command: String,
1886
1887 #[serde(default)]
1889 pub args: Vec<String>,
1890}
1891
1892#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1894pub struct WarningsConfig {
1895 #[serde(default = "default_true")]
1898 pub show_status_indicator: bool,
1899}
1900
1901impl Default for WarningsConfig {
1902 fn default() -> Self {
1903 Self {
1904 show_status_indicator: true,
1905 }
1906 }
1907}
1908
1909#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1911pub struct PackagesConfig {
1912 #[serde(default = "default_package_sources")]
1915 pub sources: Vec<String>,
1916}
1917
1918fn default_package_sources() -> Vec<String> {
1919 vec!["https://github.com/sinelaw/fresh-plugins-registry".to_string()]
1920}
1921
1922impl Default for PackagesConfig {
1923 fn default() -> Self {
1924 Self {
1925 sources: default_package_sources(),
1926 }
1927 }
1928}
1929
1930pub use fresh_core::config::PluginConfig;
1932
1933impl Default for FileExplorerConfig {
1934 fn default() -> Self {
1935 Self {
1936 respect_gitignore: true,
1937 show_hidden: false,
1938 show_gitignored: false,
1939 custom_ignore_patterns: Vec::new(),
1940 width: default_explorer_width(),
1941 preview_tabs: true,
1942 side: default_explorer_side(),
1943 auto_open_on_last_buffer_close: true,
1944 follow_active_buffer: false,
1945 compact_directories: true,
1946 tree_indicator_collapsed: default_tree_indicator_collapsed(),
1947 tree_indicator_expanded: default_tree_indicator_expanded(),
1948 }
1949 }
1950}
1951
1952#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
1954pub struct FileBrowserConfig {
1955 #[serde(default = "default_false")]
1957 pub show_hidden: bool,
1958}
1959
1960#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1962pub struct KeyPress {
1963 pub key: String,
1965 #[serde(default)]
1967 pub modifiers: Vec<String>,
1968}
1969
1970#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1972#[schemars(extend("x-display-field" = "/action"))]
1973pub struct Keybinding {
1974 #[serde(default, skip_serializing_if = "String::is_empty")]
1976 pub key: String,
1977
1978 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1980 pub modifiers: Vec<String>,
1981
1982 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1985 pub keys: Vec<KeyPress>,
1986
1987 pub action: String,
1989
1990 #[serde(default)]
1992 pub args: HashMap<String, serde_json::Value>,
1993
1994 #[serde(default)]
1996 pub when: Option<String>,
1997}
1998
1999#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2001#[schemars(extend("x-display-field" = "/inherits"))]
2002pub struct KeymapConfig {
2003 #[serde(default, skip_serializing_if = "Option::is_none")]
2005 pub inherits: Option<String>,
2006
2007 #[serde(default)]
2009 pub bindings: Vec<Keybinding>,
2010}
2011
2012#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2014#[schemars(extend("x-display-field" = "/command"))]
2015pub struct FormatterConfig {
2016 pub command: String,
2018
2019 #[serde(default)]
2022 pub args: Vec<String>,
2023
2024 #[serde(default = "default_true")]
2027 pub stdin: bool,
2028
2029 #[serde(default = "default_on_save_timeout")]
2031 pub timeout_ms: u64,
2032}
2033
2034#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2036#[schemars(extend("x-display-field" = "/command"))]
2037pub struct OnSaveAction {
2038 pub command: String,
2041
2042 #[serde(default)]
2045 pub args: Vec<String>,
2046
2047 #[serde(default)]
2049 pub working_dir: Option<String>,
2050
2051 #[serde(default)]
2053 pub stdin: bool,
2054
2055 #[serde(default = "default_on_save_timeout")]
2057 pub timeout_ms: u64,
2058
2059 #[serde(default = "default_true")]
2062 pub enabled: bool,
2063}
2064
2065fn default_on_save_timeout() -> u64 {
2066 10000
2067}
2068
2069fn default_page_width() -> Option<usize> {
2070 Some(80)
2071}
2072
2073#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2075#[schemars(extend("x-display-field" = "/grammar"))]
2076pub struct LanguageConfig {
2077 #[serde(default)]
2079 pub extensions: Vec<String>,
2080
2081 #[serde(default)]
2083 pub filenames: Vec<String>,
2084
2085 #[serde(default)]
2087 pub grammar: String,
2088
2089 #[serde(default)]
2091 pub comment_prefix: Option<String>,
2092
2093 #[serde(default = "default_true")]
2095 pub auto_indent: bool,
2096
2097 #[serde(default)]
2100 pub auto_close: Option<bool>,
2101
2102 #[serde(default)]
2105 pub auto_surround: Option<bool>,
2106
2107 #[serde(default)]
2110 pub textmate_grammar: Option<std::path::PathBuf>,
2111
2112 #[serde(default = "default_true")]
2115 pub show_whitespace_tabs: bool,
2116
2117 #[serde(default)]
2122 pub line_wrap: Option<bool>,
2123
2124 #[serde(default)]
2127 pub wrap_column: Option<usize>,
2128
2129 #[serde(default)]
2134 pub page_view: Option<bool>,
2135
2136 #[serde(default)]
2140 pub page_width: Option<usize>,
2141
2142 #[serde(default)]
2146 pub use_tabs: Option<bool>,
2147
2148 #[serde(default)]
2151 pub tab_size: Option<usize>,
2152
2153 #[serde(default)]
2155 pub formatter: Option<FormatterConfig>,
2156
2157 #[serde(default)]
2159 pub format_on_save: bool,
2160
2161 #[serde(default)]
2165 pub on_save: Vec<OnSaveAction>,
2166
2167 #[serde(default)]
2177 pub word_characters: Option<String>,
2178}
2179
2180#[derive(Debug, Clone)]
2187pub struct BufferConfig {
2188 pub tab_size: usize,
2190
2191 pub use_tabs: bool,
2193
2194 pub auto_indent: bool,
2196
2197 pub auto_close: bool,
2199
2200 pub auto_surround: bool,
2202
2203 pub line_wrap: bool,
2205
2206 pub wrap_column: Option<usize>,
2208
2209 pub whitespace: WhitespaceVisibility,
2211
2212 pub formatter: Option<FormatterConfig>,
2214
2215 pub format_on_save: bool,
2217
2218 pub on_save: Vec<OnSaveAction>,
2220
2221 pub textmate_grammar: Option<std::path::PathBuf>,
2223
2224 pub word_characters: String,
2227}
2228
2229impl BufferConfig {
2230 pub fn resolve(global_config: &Config, language_id: Option<&str>) -> Self {
2239 let editor = &global_config.editor;
2240
2241 let mut whitespace = WhitespaceVisibility::from_editor_config(editor);
2243 let mut config = BufferConfig {
2244 tab_size: editor.tab_size,
2245 use_tabs: editor.use_tabs,
2246 auto_indent: editor.auto_indent,
2247 auto_close: editor.auto_close,
2248 auto_surround: editor.auto_surround,
2249 line_wrap: editor.line_wrap,
2250 wrap_column: editor.wrap_column,
2251 whitespace,
2252 formatter: None,
2253 format_on_save: false,
2254 on_save: Vec::new(),
2255 textmate_grammar: None,
2256 word_characters: String::new(),
2257 };
2258
2259 let lang_config_ref = language_id
2263 .and_then(|id| global_config.languages.get(id))
2264 .or_else(|| {
2265 match language_id {
2267 None | Some("text") => global_config
2268 .default_language
2269 .as_deref()
2270 .and_then(|lang| global_config.languages.get(lang)),
2271 _ => None,
2272 }
2273 });
2274 if let Some(lang_config) = lang_config_ref {
2275 if let Some(ts) = lang_config.tab_size {
2277 config.tab_size = ts;
2278 }
2279
2280 if let Some(use_tabs) = lang_config.use_tabs {
2282 config.use_tabs = use_tabs;
2283 }
2284
2285 if let Some(line_wrap) = lang_config.line_wrap {
2287 config.line_wrap = line_wrap;
2288 }
2289
2290 if lang_config.wrap_column.is_some() {
2292 config.wrap_column = lang_config.wrap_column;
2293 }
2294
2295 config.auto_indent = lang_config.auto_indent;
2297
2298 if config.auto_close {
2300 if let Some(lang_auto_close) = lang_config.auto_close {
2301 config.auto_close = lang_auto_close;
2302 }
2303 }
2304
2305 if config.auto_surround {
2307 if let Some(lang_auto_surround) = lang_config.auto_surround {
2308 config.auto_surround = lang_auto_surround;
2309 }
2310 }
2311
2312 whitespace = whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
2314 config.whitespace = whitespace;
2315
2316 config.formatter = lang_config.formatter.clone();
2318
2319 config.format_on_save = lang_config.format_on_save;
2321
2322 config.on_save = lang_config.on_save.clone();
2324
2325 config.textmate_grammar = lang_config.textmate_grammar.clone();
2327
2328 if let Some(ref wc) = lang_config.word_characters {
2330 config.word_characters = wc.clone();
2331 }
2332 }
2333
2334 config
2335 }
2336
2337 pub fn indent_string(&self) -> String {
2342 if self.use_tabs {
2343 "\t".to_string()
2344 } else {
2345 " ".repeat(self.tab_size)
2346 }
2347 }
2348}
2349
2350#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
2352pub struct MenuConfig {
2353 #[serde(default)]
2355 pub menus: Vec<Menu>,
2356}
2357
2358pub use fresh_core::menu::{Menu, MenuItem};
2360
2361pub trait MenuExt {
2363 fn match_id(&self) -> &str;
2366
2367 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path);
2370}
2371
2372impl MenuExt for Menu {
2373 fn match_id(&self) -> &str {
2374 self.id.as_deref().unwrap_or(&self.label)
2375 }
2376
2377 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path) {
2378 self.items = self
2379 .items
2380 .iter()
2381 .map(|item| item.expand_dynamic(themes_dir))
2382 .collect();
2383 }
2384}
2385
2386pub trait MenuItemExt {
2388 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem;
2391}
2392
2393impl MenuItemExt for MenuItem {
2394 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem {
2395 match self {
2396 MenuItem::DynamicSubmenu { label, source } => {
2397 let items = generate_dynamic_items(source, themes_dir);
2398 MenuItem::Submenu {
2399 label: label.clone(),
2400 items,
2401 }
2402 }
2403 other => other.clone(),
2404 }
2405 }
2406}
2407
2408#[cfg(feature = "runtime")]
2410pub fn generate_dynamic_items(source: &str, themes_dir: &std::path::Path) -> Vec<MenuItem> {
2411 match source {
2412 "copy_with_theme" => {
2413 let loader = crate::view::theme::ThemeLoader::new(themes_dir.to_path_buf());
2415 let registry = loader.load_all(&[]);
2416 registry
2417 .list()
2418 .iter()
2419 .map(|info| {
2420 let mut args = HashMap::new();
2421 args.insert("theme".to_string(), serde_json::json!(info.key));
2422 MenuItem::Action {
2423 label: info.name.clone(),
2424 action: "copy_with_theme".to_string(),
2425 args,
2426 when: Some(context_keys::HAS_SELECTION.to_string()),
2427 checkbox: None,
2428 }
2429 })
2430 .collect()
2431 }
2432 _ => vec![MenuItem::Label {
2433 info: format!("Unknown source: {}", source),
2434 }],
2435 }
2436}
2437
2438#[cfg(not(feature = "runtime"))]
2440pub fn generate_dynamic_items(_source: &str, _themes_dir: &std::path::Path) -> Vec<MenuItem> {
2441 vec![]
2443}
2444
2445impl Default for Config {
2446 fn default() -> Self {
2447 Self {
2448 version: 0,
2449 theme: default_theme_name(),
2450 locale: LocaleName::default(),
2451 check_for_updates: true,
2452 editor: EditorConfig::default(),
2453 file_explorer: FileExplorerConfig::default(),
2454 file_browser: FileBrowserConfig::default(),
2455 clipboard: ClipboardConfig::default(),
2456 terminal: TerminalConfig::default(),
2457 keybindings: vec![], keybinding_maps: HashMap::new(), active_keybinding_map: default_keybinding_map_name(),
2460 languages: Self::default_languages(),
2461 default_language: None,
2462 lsp: Self::default_lsp_config(),
2463 universal_lsp: Self::default_universal_lsp_config(),
2464 warnings: WarningsConfig::default(),
2465 plugins: HashMap::new(), packages: PackagesConfig::default(),
2467 }
2468 }
2469}
2470
2471impl MenuConfig {
2472 pub fn translated() -> Self {
2474 Self {
2475 menus: Self::translated_menus(),
2476 }
2477 }
2478
2479 pub fn translated_menus() -> Vec<Menu> {
2485 vec![
2486 Menu {
2488 id: Some("File".to_string()),
2489 label: t!("menu.file").to_string(),
2490 when: None,
2491 items: vec![
2492 MenuItem::Action {
2493 label: t!("menu.file.new_file").to_string(),
2494 action: "new".to_string(),
2495 args: HashMap::new(),
2496 when: None,
2497 checkbox: None,
2498 },
2499 MenuItem::Action {
2500 label: t!("menu.file.open_file").to_string(),
2501 action: "open".to_string(),
2502 args: HashMap::new(),
2503 when: None,
2504 checkbox: None,
2505 },
2506 MenuItem::Separator { separator: true },
2507 MenuItem::Action {
2508 label: t!("menu.file.save").to_string(),
2509 action: "save".to_string(),
2510 args: HashMap::new(),
2511 when: Some(context_keys::HAS_BUFFER.to_string()),
2512 checkbox: None,
2513 },
2514 MenuItem::Action {
2515 label: t!("menu.file.save_as").to_string(),
2516 action: "save_as".to_string(),
2517 args: HashMap::new(),
2518 when: Some(context_keys::HAS_BUFFER.to_string()),
2519 checkbox: None,
2520 },
2521 MenuItem::Action {
2522 label: t!("menu.file.revert").to_string(),
2523 action: "revert".to_string(),
2524 args: HashMap::new(),
2525 when: Some(context_keys::HAS_BUFFER.to_string()),
2526 checkbox: None,
2527 },
2528 MenuItem::Action {
2529 label: t!("menu.file.reload_with_encoding").to_string(),
2530 action: "reload_with_encoding".to_string(),
2531 args: HashMap::new(),
2532 when: Some(context_keys::HAS_BUFFER.to_string()),
2533 checkbox: None,
2534 },
2535 MenuItem::Separator { separator: true },
2536 MenuItem::Action {
2537 label: t!("menu.file.close_buffer").to_string(),
2538 action: "close".to_string(),
2539 args: HashMap::new(),
2540 when: Some(context_keys::HAS_BUFFER.to_string()),
2541 checkbox: None,
2542 },
2543 MenuItem::Separator { separator: true },
2544 MenuItem::Action {
2545 label: t!("menu.file.switch_project").to_string(),
2546 action: "switch_project".to_string(),
2547 args: HashMap::new(),
2548 when: None,
2549 checkbox: None,
2550 },
2551 MenuItem::Separator { separator: true },
2552 MenuItem::Action {
2553 label: t!("menu.file.detach").to_string(),
2554 action: "detach".to_string(),
2555 args: HashMap::new(),
2556 when: Some(context_keys::SESSION_MODE.to_string()),
2557 checkbox: None,
2558 },
2559 MenuItem::Action {
2560 label: t!("menu.file.quit").to_string(),
2561 action: "quit".to_string(),
2562 args: HashMap::new(),
2563 when: None,
2564 checkbox: None,
2565 },
2566 ],
2567 },
2568 Menu {
2570 id: Some("Edit".to_string()),
2571 label: t!("menu.edit").to_string(),
2572 when: None,
2573 items: vec![
2574 MenuItem::Action {
2575 label: t!("menu.edit.undo").to_string(),
2576 action: "undo".to_string(),
2577 args: HashMap::new(),
2578 when: Some(context_keys::HAS_BUFFER.to_string()),
2579 checkbox: None,
2580 },
2581 MenuItem::Action {
2582 label: t!("menu.edit.redo").to_string(),
2583 action: "redo".to_string(),
2584 args: HashMap::new(),
2585 when: Some(context_keys::HAS_BUFFER.to_string()),
2586 checkbox: None,
2587 },
2588 MenuItem::Separator { separator: true },
2589 MenuItem::Action {
2590 label: t!("menu.edit.cut").to_string(),
2591 action: "cut".to_string(),
2592 args: HashMap::new(),
2593 when: Some(context_keys::CAN_COPY.to_string()),
2594 checkbox: None,
2595 },
2596 MenuItem::Action {
2597 label: t!("menu.edit.copy").to_string(),
2598 action: "copy".to_string(),
2599 args: HashMap::new(),
2600 when: Some(context_keys::CAN_COPY.to_string()),
2601 checkbox: None,
2602 },
2603 MenuItem::DynamicSubmenu {
2604 label: t!("menu.edit.copy_with_formatting").to_string(),
2605 source: "copy_with_theme".to_string(),
2606 },
2607 MenuItem::Action {
2608 label: t!("menu.edit.paste").to_string(),
2609 action: "paste".to_string(),
2610 args: HashMap::new(),
2611 when: Some(context_keys::CAN_PASTE.to_string()),
2612 checkbox: None,
2613 },
2614 MenuItem::Separator { separator: true },
2615 MenuItem::Action {
2616 label: t!("menu.edit.select_all").to_string(),
2617 action: "select_all".to_string(),
2618 args: HashMap::new(),
2619 when: Some(context_keys::HAS_BUFFER.to_string()),
2620 checkbox: None,
2621 },
2622 MenuItem::Separator { separator: true },
2623 MenuItem::Action {
2624 label: t!("menu.edit.find").to_string(),
2625 action: "search".to_string(),
2626 args: HashMap::new(),
2627 when: Some(context_keys::HAS_BUFFER.to_string()),
2628 checkbox: None,
2629 },
2630 MenuItem::Action {
2631 label: t!("menu.edit.find_in_selection").to_string(),
2632 action: "find_in_selection".to_string(),
2633 args: HashMap::new(),
2634 when: Some(context_keys::HAS_SELECTION.to_string()),
2635 checkbox: None,
2636 },
2637 MenuItem::Action {
2638 label: t!("menu.edit.find_next").to_string(),
2639 action: "find_next".to_string(),
2640 args: HashMap::new(),
2641 when: Some(context_keys::HAS_BUFFER.to_string()),
2642 checkbox: None,
2643 },
2644 MenuItem::Action {
2645 label: t!("menu.edit.find_previous").to_string(),
2646 action: "find_previous".to_string(),
2647 args: HashMap::new(),
2648 when: Some(context_keys::HAS_BUFFER.to_string()),
2649 checkbox: None,
2650 },
2651 MenuItem::Action {
2652 label: t!("menu.edit.replace").to_string(),
2653 action: "query_replace".to_string(),
2654 args: HashMap::new(),
2655 when: Some(context_keys::HAS_BUFFER.to_string()),
2656 checkbox: None,
2657 },
2658 MenuItem::Separator { separator: true },
2659 MenuItem::Action {
2660 label: t!("menu.edit.delete_line").to_string(),
2661 action: "delete_line".to_string(),
2662 args: HashMap::new(),
2663 when: Some(context_keys::HAS_BUFFER.to_string()),
2664 checkbox: None,
2665 },
2666 MenuItem::Action {
2667 label: t!("menu.edit.format_buffer").to_string(),
2668 action: "format_buffer".to_string(),
2669 args: HashMap::new(),
2670 when: Some(context_keys::FORMATTER_AVAILABLE.to_string()),
2671 checkbox: None,
2672 },
2673 MenuItem::Separator { separator: true },
2674 MenuItem::Action {
2675 label: t!("menu.edit.settings").to_string(),
2676 action: "open_settings".to_string(),
2677 args: HashMap::new(),
2678 when: None,
2679 checkbox: None,
2680 },
2681 MenuItem::Action {
2682 label: t!("menu.edit.keybinding_editor").to_string(),
2683 action: "open_keybinding_editor".to_string(),
2684 args: HashMap::new(),
2685 when: None,
2686 checkbox: None,
2687 },
2688 ],
2689 },
2690 Menu {
2692 id: Some("View".to_string()),
2693 label: t!("menu.view").to_string(),
2694 when: None,
2695 items: vec![
2696 MenuItem::Action {
2697 label: t!("menu.view.file_explorer").to_string(),
2698 action: "toggle_file_explorer".to_string(),
2699 args: HashMap::new(),
2700 when: None,
2701 checkbox: Some(context_keys::FILE_EXPLORER.to_string()),
2702 },
2703 MenuItem::Separator { separator: true },
2704 MenuItem::Action {
2705 label: t!("menu.view.line_numbers").to_string(),
2706 action: "toggle_line_numbers".to_string(),
2707 args: HashMap::new(),
2708 when: None,
2709 checkbox: Some(context_keys::LINE_NUMBERS.to_string()),
2710 },
2711 MenuItem::Action {
2712 label: t!("menu.view.line_wrap").to_string(),
2713 action: "toggle_line_wrap".to_string(),
2714 args: HashMap::new(),
2715 when: None,
2716 checkbox: Some(context_keys::LINE_WRAP.to_string()),
2717 },
2718 MenuItem::Action {
2719 label: t!("menu.view.mouse_support").to_string(),
2720 action: "toggle_mouse_capture".to_string(),
2721 args: HashMap::new(),
2722 when: None,
2723 checkbox: Some(context_keys::MOUSE_CAPTURE.to_string()),
2724 },
2725 MenuItem::Separator { separator: true },
2726 MenuItem::Action {
2727 label: t!("menu.view.vertical_scrollbar").to_string(),
2728 action: "toggle_vertical_scrollbar".to_string(),
2729 args: HashMap::new(),
2730 when: None,
2731 checkbox: Some(context_keys::VERTICAL_SCROLLBAR.to_string()),
2732 },
2733 MenuItem::Action {
2734 label: t!("menu.view.horizontal_scrollbar").to_string(),
2735 action: "toggle_horizontal_scrollbar".to_string(),
2736 args: HashMap::new(),
2737 when: None,
2738 checkbox: Some(context_keys::HORIZONTAL_SCROLLBAR.to_string()),
2739 },
2740 MenuItem::Separator { separator: true },
2741 MenuItem::Action {
2742 label: t!("menu.view.set_background").to_string(),
2743 action: "set_background".to_string(),
2744 args: HashMap::new(),
2745 when: None,
2746 checkbox: None,
2747 },
2748 MenuItem::Action {
2749 label: t!("menu.view.set_background_blend").to_string(),
2750 action: "set_background_blend".to_string(),
2751 args: HashMap::new(),
2752 when: None,
2753 checkbox: None,
2754 },
2755 MenuItem::Action {
2756 label: t!("menu.view.set_page_width").to_string(),
2757 action: "set_page_width".to_string(),
2758 args: HashMap::new(),
2759 when: None,
2760 checkbox: None,
2761 },
2762 MenuItem::Separator { separator: true },
2763 MenuItem::Action {
2764 label: t!("menu.view.select_theme").to_string(),
2765 action: "select_theme".to_string(),
2766 args: HashMap::new(),
2767 when: None,
2768 checkbox: None,
2769 },
2770 MenuItem::Action {
2771 label: t!("menu.view.select_locale").to_string(),
2772 action: "select_locale".to_string(),
2773 args: HashMap::new(),
2774 when: None,
2775 checkbox: None,
2776 },
2777 MenuItem::Action {
2778 label: t!("menu.view.settings").to_string(),
2779 action: "open_settings".to_string(),
2780 args: HashMap::new(),
2781 when: None,
2782 checkbox: None,
2783 },
2784 MenuItem::Action {
2785 label: t!("menu.view.calibrate_input").to_string(),
2786 action: "calibrate_input".to_string(),
2787 args: HashMap::new(),
2788 when: None,
2789 checkbox: None,
2790 },
2791 MenuItem::Separator { separator: true },
2792 MenuItem::Action {
2793 label: t!("menu.view.split_horizontal").to_string(),
2794 action: "split_horizontal".to_string(),
2795 args: HashMap::new(),
2796 when: Some(context_keys::HAS_BUFFER.to_string()),
2797 checkbox: None,
2798 },
2799 MenuItem::Action {
2800 label: t!("menu.view.split_vertical").to_string(),
2801 action: "split_vertical".to_string(),
2802 args: HashMap::new(),
2803 when: Some(context_keys::HAS_BUFFER.to_string()),
2804 checkbox: None,
2805 },
2806 MenuItem::Action {
2807 label: t!("menu.view.close_split").to_string(),
2808 action: "close_split".to_string(),
2809 args: HashMap::new(),
2810 when: Some(context_keys::HAS_BUFFER.to_string()),
2811 checkbox: None,
2812 },
2813 MenuItem::Action {
2814 label: t!("menu.view.scroll_sync").to_string(),
2815 action: "toggle_scroll_sync".to_string(),
2816 args: HashMap::new(),
2817 when: Some(context_keys::HAS_SAME_BUFFER_SPLITS.to_string()),
2818 checkbox: Some(context_keys::SCROLL_SYNC.to_string()),
2819 },
2820 MenuItem::Action {
2821 label: t!("menu.view.focus_next_split").to_string(),
2822 action: "next_split".to_string(),
2823 args: HashMap::new(),
2824 when: None,
2825 checkbox: None,
2826 },
2827 MenuItem::Action {
2828 label: t!("menu.view.focus_prev_split").to_string(),
2829 action: "prev_split".to_string(),
2830 args: HashMap::new(),
2831 when: None,
2832 checkbox: None,
2833 },
2834 MenuItem::Action {
2835 label: t!("menu.view.toggle_maximize_split").to_string(),
2836 action: "toggle_maximize_split".to_string(),
2837 args: HashMap::new(),
2838 when: None,
2839 checkbox: None,
2840 },
2841 MenuItem::Separator { separator: true },
2842 MenuItem::Submenu {
2843 label: t!("menu.terminal").to_string(),
2844 items: vec![
2845 MenuItem::Action {
2846 label: t!("menu.terminal.open").to_string(),
2847 action: "open_terminal".to_string(),
2848 args: HashMap::new(),
2849 when: None,
2850 checkbox: None,
2851 },
2852 MenuItem::Action {
2853 label: t!("menu.terminal.close").to_string(),
2854 action: "close_terminal".to_string(),
2855 args: HashMap::new(),
2856 when: None,
2857 checkbox: None,
2858 },
2859 MenuItem::Separator { separator: true },
2860 MenuItem::Action {
2861 label: t!("menu.terminal.toggle_keyboard_capture").to_string(),
2862 action: "toggle_keyboard_capture".to_string(),
2863 args: HashMap::new(),
2864 when: None,
2865 checkbox: None,
2866 },
2867 ],
2868 },
2869 MenuItem::Separator { separator: true },
2870 MenuItem::Submenu {
2871 label: t!("menu.view.keybinding_style").to_string(),
2872 items: vec![
2873 MenuItem::Action {
2874 label: t!("menu.view.keybinding_default").to_string(),
2875 action: "switch_keybinding_map".to_string(),
2876 args: {
2877 let mut map = HashMap::new();
2878 map.insert("map".to_string(), serde_json::json!("default"));
2879 map
2880 },
2881 when: None,
2882 checkbox: Some(context_keys::KEYMAP_DEFAULT.to_string()),
2883 },
2884 MenuItem::Action {
2885 label: t!("menu.view.keybinding_emacs").to_string(),
2886 action: "switch_keybinding_map".to_string(),
2887 args: {
2888 let mut map = HashMap::new();
2889 map.insert("map".to_string(), serde_json::json!("emacs"));
2890 map
2891 },
2892 when: None,
2893 checkbox: Some(context_keys::KEYMAP_EMACS.to_string()),
2894 },
2895 MenuItem::Action {
2896 label: t!("menu.view.keybinding_vscode").to_string(),
2897 action: "switch_keybinding_map".to_string(),
2898 args: {
2899 let mut map = HashMap::new();
2900 map.insert("map".to_string(), serde_json::json!("vscode"));
2901 map
2902 },
2903 when: None,
2904 checkbox: Some(context_keys::KEYMAP_VSCODE.to_string()),
2905 },
2906 MenuItem::Action {
2907 label: "macOS GUI (⌘)".to_string(),
2908 action: "switch_keybinding_map".to_string(),
2909 args: {
2910 let mut map = HashMap::new();
2911 map.insert("map".to_string(), serde_json::json!("macos-gui"));
2912 map
2913 },
2914 when: None,
2915 checkbox: Some(context_keys::KEYMAP_MACOS_GUI.to_string()),
2916 },
2917 ],
2918 },
2919 ],
2920 },
2921 Menu {
2923 id: Some("Selection".to_string()),
2924 label: t!("menu.selection").to_string(),
2925 when: Some(context_keys::HAS_BUFFER.to_string()),
2926 items: vec![
2927 MenuItem::Action {
2928 label: t!("menu.selection.select_all").to_string(),
2929 action: "select_all".to_string(),
2930 args: HashMap::new(),
2931 when: None,
2932 checkbox: None,
2933 },
2934 MenuItem::Action {
2935 label: t!("menu.selection.select_word").to_string(),
2936 action: "select_word".to_string(),
2937 args: HashMap::new(),
2938 when: None,
2939 checkbox: None,
2940 },
2941 MenuItem::Action {
2942 label: t!("menu.selection.select_line").to_string(),
2943 action: "select_line".to_string(),
2944 args: HashMap::new(),
2945 when: None,
2946 checkbox: None,
2947 },
2948 MenuItem::Action {
2949 label: t!("menu.selection.expand_selection").to_string(),
2950 action: "expand_selection".to_string(),
2951 args: HashMap::new(),
2952 when: None,
2953 checkbox: None,
2954 },
2955 MenuItem::Separator { separator: true },
2956 MenuItem::Action {
2957 label: t!("menu.selection.add_cursor_above").to_string(),
2958 action: "add_cursor_above".to_string(),
2959 args: HashMap::new(),
2960 when: None,
2961 checkbox: None,
2962 },
2963 MenuItem::Action {
2964 label: t!("menu.selection.add_cursor_below").to_string(),
2965 action: "add_cursor_below".to_string(),
2966 args: HashMap::new(),
2967 when: None,
2968 checkbox: None,
2969 },
2970 MenuItem::Action {
2971 label: t!("menu.selection.add_cursor_next_match").to_string(),
2972 action: "add_cursor_next_match".to_string(),
2973 args: HashMap::new(),
2974 when: None,
2975 checkbox: None,
2976 },
2977 MenuItem::Action {
2978 label: t!("menu.selection.add_cursors_to_line_ends").to_string(),
2979 action: "add_cursors_to_line_ends".to_string(),
2980 args: HashMap::new(),
2981 when: None,
2982 checkbox: None,
2983 },
2984 MenuItem::Action {
2985 label: t!("menu.selection.remove_secondary_cursors").to_string(),
2986 action: "remove_secondary_cursors".to_string(),
2987 args: HashMap::new(),
2988 when: None,
2989 checkbox: None,
2990 },
2991 ],
2992 },
2993 Menu {
2995 id: Some("Go".to_string()),
2996 label: t!("menu.go").to_string(),
2997 when: None,
2998 items: vec![
2999 MenuItem::Action {
3000 label: t!("menu.go.goto_line").to_string(),
3001 action: "goto_line".to_string(),
3002 args: HashMap::new(),
3003 when: Some(context_keys::HAS_BUFFER.to_string()),
3004 checkbox: None,
3005 },
3006 MenuItem::Action {
3007 label: t!("menu.go.goto_definition").to_string(),
3008 action: "lsp_goto_definition".to_string(),
3009 args: HashMap::new(),
3010 when: Some(context_keys::HAS_BUFFER.to_string()),
3011 checkbox: None,
3012 },
3013 MenuItem::Action {
3014 label: t!("menu.go.find_references").to_string(),
3015 action: "lsp_references".to_string(),
3016 args: HashMap::new(),
3017 when: Some(context_keys::HAS_BUFFER.to_string()),
3018 checkbox: None,
3019 },
3020 MenuItem::Separator { separator: true },
3021 MenuItem::Action {
3022 label: t!("menu.go.next_buffer").to_string(),
3023 action: "next_buffer".to_string(),
3024 args: HashMap::new(),
3025 when: Some(context_keys::HAS_BUFFER.to_string()),
3026 checkbox: None,
3027 },
3028 MenuItem::Action {
3029 label: t!("menu.go.prev_buffer").to_string(),
3030 action: "prev_buffer".to_string(),
3031 args: HashMap::new(),
3032 when: Some(context_keys::HAS_BUFFER.to_string()),
3033 checkbox: None,
3034 },
3035 MenuItem::Separator { separator: true },
3036 MenuItem::Action {
3037 label: t!("menu.go.command_palette").to_string(),
3038 action: "command_palette".to_string(),
3039 args: HashMap::new(),
3040 when: None,
3041 checkbox: None,
3042 },
3043 ],
3044 },
3045 Menu {
3047 id: Some("LSP".to_string()),
3048 label: t!("menu.lsp").to_string(),
3049 when: None,
3050 items: vec![
3051 MenuItem::Action {
3052 label: t!("menu.lsp.show_hover").to_string(),
3053 action: "lsp_hover".to_string(),
3054 args: HashMap::new(),
3055 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3056 checkbox: None,
3057 },
3058 MenuItem::Action {
3059 label: t!("menu.lsp.goto_definition").to_string(),
3060 action: "lsp_goto_definition".to_string(),
3061 args: HashMap::new(),
3062 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3063 checkbox: None,
3064 },
3065 MenuItem::Action {
3066 label: t!("menu.lsp.find_references").to_string(),
3067 action: "lsp_references".to_string(),
3068 args: HashMap::new(),
3069 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3070 checkbox: None,
3071 },
3072 MenuItem::Action {
3073 label: t!("menu.lsp.rename_symbol").to_string(),
3074 action: "lsp_rename".to_string(),
3075 args: HashMap::new(),
3076 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3077 checkbox: None,
3078 },
3079 MenuItem::Separator { separator: true },
3080 MenuItem::Action {
3081 label: t!("menu.lsp.show_completions").to_string(),
3082 action: "lsp_completion".to_string(),
3083 args: HashMap::new(),
3084 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3085 checkbox: None,
3086 },
3087 MenuItem::Action {
3088 label: t!("menu.lsp.show_signature").to_string(),
3089 action: "lsp_signature_help".to_string(),
3090 args: HashMap::new(),
3091 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3092 checkbox: None,
3093 },
3094 MenuItem::Action {
3095 label: t!("menu.lsp.code_actions").to_string(),
3096 action: "lsp_code_actions".to_string(),
3097 args: HashMap::new(),
3098 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3099 checkbox: None,
3100 },
3101 MenuItem::Separator { separator: true },
3102 MenuItem::Action {
3103 label: t!("menu.lsp.toggle_inlay_hints").to_string(),
3104 action: "toggle_inlay_hints".to_string(),
3105 args: HashMap::new(),
3106 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3107 checkbox: Some(context_keys::INLAY_HINTS.to_string()),
3108 },
3109 MenuItem::Action {
3110 label: t!("menu.lsp.toggle_mouse_hover").to_string(),
3111 action: "toggle_mouse_hover".to_string(),
3112 args: HashMap::new(),
3113 when: None,
3114 checkbox: Some(context_keys::MOUSE_HOVER.to_string()),
3115 },
3116 MenuItem::Separator { separator: true },
3117 MenuItem::Action {
3118 label: t!("menu.lsp.show_status").to_string(),
3119 action: "show_lsp_status".to_string(),
3120 args: HashMap::new(),
3121 when: None,
3122 checkbox: None,
3123 },
3124 MenuItem::Action {
3125 label: t!("menu.lsp.restart_server").to_string(),
3126 action: "lsp_restart".to_string(),
3127 args: HashMap::new(),
3128 when: None,
3129 checkbox: None,
3130 },
3131 MenuItem::Action {
3132 label: t!("menu.lsp.stop_server").to_string(),
3133 action: "lsp_stop".to_string(),
3134 args: HashMap::new(),
3135 when: None,
3136 checkbox: None,
3137 },
3138 MenuItem::Separator { separator: true },
3139 MenuItem::Action {
3140 label: t!("menu.lsp.toggle_for_buffer").to_string(),
3141 action: "lsp_toggle_for_buffer".to_string(),
3142 args: HashMap::new(),
3143 when: Some(context_keys::HAS_BUFFER.to_string()),
3144 checkbox: None,
3145 },
3146 ],
3147 },
3148 Menu {
3150 id: Some("Explorer".to_string()),
3151 label: t!("menu.explorer").to_string(),
3152 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3153 items: vec![
3154 MenuItem::Action {
3155 label: t!("menu.explorer.new_file").to_string(),
3156 action: "file_explorer_new_file".to_string(),
3157 args: HashMap::new(),
3158 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3159 checkbox: None,
3160 },
3161 MenuItem::Action {
3162 label: t!("menu.explorer.new_folder").to_string(),
3163 action: "file_explorer_new_directory".to_string(),
3164 args: HashMap::new(),
3165 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3166 checkbox: None,
3167 },
3168 MenuItem::Separator { separator: true },
3169 MenuItem::Action {
3170 label: t!("menu.explorer.open").to_string(),
3171 action: "file_explorer_open".to_string(),
3172 args: HashMap::new(),
3173 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3174 checkbox: None,
3175 },
3176 MenuItem::Action {
3177 label: t!("menu.explorer.rename").to_string(),
3178 action: "file_explorer_rename".to_string(),
3179 args: HashMap::new(),
3180 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3181 checkbox: None,
3182 },
3183 MenuItem::Action {
3184 label: t!("menu.explorer.delete").to_string(),
3185 action: "file_explorer_delete".to_string(),
3186 args: HashMap::new(),
3187 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3188 checkbox: None,
3189 },
3190 MenuItem::Separator { separator: true },
3191 MenuItem::Action {
3192 label: t!("menu.explorer.cut").to_string(),
3193 action: "cut".to_string(),
3194 args: HashMap::new(),
3195 when: Some(context_keys::CAN_COPY.to_string()),
3196 checkbox: None,
3197 },
3198 MenuItem::Action {
3199 label: t!("menu.explorer.copy").to_string(),
3200 action: "copy".to_string(),
3201 args: HashMap::new(),
3202 when: Some(context_keys::CAN_COPY.to_string()),
3203 checkbox: None,
3204 },
3205 MenuItem::Action {
3206 label: t!("menu.explorer.paste").to_string(),
3207 action: "paste".to_string(),
3208 args: HashMap::new(),
3209 when: Some(context_keys::CAN_PASTE.to_string()),
3210 checkbox: None,
3211 },
3212 MenuItem::Separator { separator: true },
3213 MenuItem::Action {
3214 label: t!("menu.explorer.refresh").to_string(),
3215 action: "file_explorer_refresh".to_string(),
3216 args: HashMap::new(),
3217 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3218 checkbox: None,
3219 },
3220 MenuItem::Separator { separator: true },
3221 MenuItem::Action {
3222 label: t!("menu.explorer.show_hidden").to_string(),
3223 action: "file_explorer_toggle_hidden".to_string(),
3224 args: HashMap::new(),
3225 when: Some(context_keys::FILE_EXPLORER.to_string()),
3226 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_HIDDEN.to_string()),
3227 },
3228 MenuItem::Action {
3229 label: t!("menu.explorer.show_gitignored").to_string(),
3230 action: "file_explorer_toggle_gitignored".to_string(),
3231 args: HashMap::new(),
3232 when: Some(context_keys::FILE_EXPLORER.to_string()),
3233 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_GITIGNORED.to_string()),
3234 },
3235 ],
3236 },
3237 Menu {
3239 id: Some("Help".to_string()),
3240 label: t!("menu.help").to_string(),
3241 when: None,
3242 items: vec![
3243 MenuItem::Label {
3244 info: format!("Fresh v{}", env!("CARGO_PKG_VERSION")),
3245 },
3246 MenuItem::Separator { separator: true },
3247 MenuItem::Action {
3248 label: t!("menu.help.show_manual").to_string(),
3249 action: "show_help".to_string(),
3250 args: HashMap::new(),
3251 when: None,
3252 checkbox: None,
3253 },
3254 MenuItem::Action {
3255 label: t!("menu.help.keyboard_shortcuts").to_string(),
3256 action: "keyboard_shortcuts".to_string(),
3257 args: HashMap::new(),
3258 when: None,
3259 checkbox: None,
3260 },
3261 MenuItem::Separator { separator: true },
3262 MenuItem::Action {
3263 label: t!("menu.help.event_debug").to_string(),
3264 action: "event_debug".to_string(),
3265 args: HashMap::new(),
3266 when: None,
3267 checkbox: None,
3268 },
3269 ],
3270 },
3271 ]
3272 }
3273}
3274
3275impl Config {
3276 pub(crate) const FILENAME: &'static str = "config.json";
3278
3279 pub(crate) fn local_config_path(working_dir: &Path) -> std::path::PathBuf {
3281 working_dir.join(Self::FILENAME)
3282 }
3283
3284 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
3290 let contents = std::fs::read_to_string(path.as_ref())
3291 .map_err(|e| ConfigError::IoError(e.to_string()))?;
3292
3293 let partial: crate::partial_config::PartialConfig =
3295 serde_json::from_str(&contents).map_err(|e| ConfigError::ParseError(e.to_string()))?;
3296
3297 Ok(partial.resolve())
3298 }
3299
3300 fn load_builtin_keymap(name: &str) -> Option<KeymapConfig> {
3302 let json_content = match name {
3303 "default" => include_str!("../keymaps/default.json"),
3304 "emacs" => include_str!("../keymaps/emacs.json"),
3305 "vscode" => include_str!("../keymaps/vscode.json"),
3306 "macos" => include_str!("../keymaps/macos.json"),
3307 "macos-gui" => include_str!("../keymaps/macos-gui.json"),
3308 _ => return None,
3309 };
3310
3311 match serde_json::from_str(json_content) {
3312 Ok(config) => Some(config),
3313 Err(e) => {
3314 eprintln!("Failed to parse builtin keymap '{}': {}", name, e);
3315 None
3316 }
3317 }
3318 }
3319
3320 pub fn resolve_keymap(&self, map_name: &str) -> Vec<Keybinding> {
3323 let mut visited = std::collections::HashSet::new();
3324 self.resolve_keymap_recursive(map_name, &mut visited)
3325 }
3326
3327 fn resolve_keymap_recursive(
3329 &self,
3330 map_name: &str,
3331 visited: &mut std::collections::HashSet<String>,
3332 ) -> Vec<Keybinding> {
3333 if visited.contains(map_name) {
3335 eprintln!(
3336 "Warning: Circular inheritance detected in keymap '{}'",
3337 map_name
3338 );
3339 return Vec::new();
3340 }
3341 visited.insert(map_name.to_string());
3342
3343 let keymap = self
3345 .keybinding_maps
3346 .get(map_name)
3347 .cloned()
3348 .or_else(|| Self::load_builtin_keymap(map_name));
3349
3350 let Some(keymap) = keymap else {
3351 return Vec::new();
3352 };
3353
3354 let mut all_bindings = if let Some(ref parent_name) = keymap.inherits {
3356 self.resolve_keymap_recursive(parent_name, visited)
3357 } else {
3358 Vec::new()
3359 };
3360
3361 all_bindings.extend(keymap.bindings);
3363
3364 all_bindings
3365 }
3366 fn default_languages() -> HashMap<String, LanguageConfig> {
3368 let mut languages = HashMap::new();
3369
3370 languages.insert(
3371 "rust".to_string(),
3372 LanguageConfig {
3373 extensions: vec!["rs".to_string()],
3374 filenames: vec![],
3375 grammar: "rust".to_string(),
3376 comment_prefix: Some("//".to_string()),
3377 auto_indent: true,
3378 auto_close: None,
3379 auto_surround: None,
3380 textmate_grammar: None,
3381 show_whitespace_tabs: true,
3382 line_wrap: None,
3383 wrap_column: None,
3384 page_view: None,
3385 page_width: None,
3386 use_tabs: None,
3387 tab_size: None,
3388 formatter: Some(FormatterConfig {
3389 command: "rustfmt".to_string(),
3390 args: vec!["--edition".to_string(), "2021".to_string()],
3391 stdin: true,
3392 timeout_ms: 10000,
3393 }),
3394 format_on_save: false,
3395 on_save: vec![],
3396 word_characters: None,
3397 },
3398 );
3399
3400 languages.insert(
3401 "javascript".to_string(),
3402 LanguageConfig {
3403 extensions: vec!["js".to_string(), "jsx".to_string(), "mjs".to_string()],
3404 filenames: vec![],
3405 grammar: "javascript".to_string(),
3406 comment_prefix: Some("//".to_string()),
3407 auto_indent: true,
3408 auto_close: None,
3409 auto_surround: None,
3410 textmate_grammar: None,
3411 show_whitespace_tabs: true,
3412 line_wrap: None,
3413 wrap_column: None,
3414 page_view: None,
3415 page_width: None,
3416 use_tabs: None,
3417 tab_size: None,
3418 formatter: Some(FormatterConfig {
3419 command: "prettier".to_string(),
3420 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3421 stdin: true,
3422 timeout_ms: 10000,
3423 }),
3424 format_on_save: false,
3425 on_save: vec![],
3426 word_characters: None,
3427 },
3428 );
3429
3430 languages.insert(
3431 "typescript".to_string(),
3432 LanguageConfig {
3433 extensions: vec!["ts".to_string(), "tsx".to_string(), "mts".to_string()],
3434 filenames: vec![],
3435 grammar: "typescript".to_string(),
3436 comment_prefix: Some("//".to_string()),
3437 auto_indent: true,
3438 auto_close: None,
3439 auto_surround: None,
3440 textmate_grammar: None,
3441 show_whitespace_tabs: true,
3442 line_wrap: None,
3443 wrap_column: None,
3444 page_view: None,
3445 page_width: None,
3446 use_tabs: None,
3447 tab_size: None,
3448 formatter: Some(FormatterConfig {
3449 command: "prettier".to_string(),
3450 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3451 stdin: true,
3452 timeout_ms: 10000,
3453 }),
3454 format_on_save: false,
3455 on_save: vec![],
3456 word_characters: None,
3457 },
3458 );
3459
3460 languages.insert(
3461 "python".to_string(),
3462 LanguageConfig {
3463 extensions: vec!["py".to_string(), "pyi".to_string()],
3464 filenames: vec![],
3465 grammar: "python".to_string(),
3466 comment_prefix: Some("#".to_string()),
3467 auto_indent: true,
3468 auto_close: None,
3469 auto_surround: None,
3470 textmate_grammar: None,
3471 show_whitespace_tabs: true,
3472 line_wrap: None,
3473 wrap_column: None,
3474 page_view: None,
3475 page_width: None,
3476 use_tabs: None,
3477 tab_size: None,
3478 formatter: Some(FormatterConfig {
3479 command: "ruff".to_string(),
3480 args: vec![
3481 "format".to_string(),
3482 "--stdin-filename".to_string(),
3483 "$FILE".to_string(),
3484 ],
3485 stdin: true,
3486 timeout_ms: 10000,
3487 }),
3488 format_on_save: false,
3489 on_save: vec![],
3490 word_characters: None,
3491 },
3492 );
3493
3494 languages.insert(
3495 "c".to_string(),
3496 LanguageConfig {
3497 extensions: vec!["c".to_string(), "h".to_string()],
3498 filenames: vec![],
3499 grammar: "c".to_string(),
3500 comment_prefix: Some("//".to_string()),
3501 auto_indent: true,
3502 auto_close: None,
3503 auto_surround: None,
3504 textmate_grammar: None,
3505 show_whitespace_tabs: true,
3506 line_wrap: None,
3507 wrap_column: None,
3508 page_view: None,
3509 page_width: None,
3510 use_tabs: None,
3511 tab_size: None,
3512 formatter: Some(FormatterConfig {
3513 command: "clang-format".to_string(),
3514 args: vec![],
3515 stdin: true,
3516 timeout_ms: 10000,
3517 }),
3518 format_on_save: false,
3519 on_save: vec![],
3520 word_characters: None,
3521 },
3522 );
3523
3524 languages.insert(
3525 "cpp".to_string(),
3526 LanguageConfig {
3527 extensions: vec![
3528 "cpp".to_string(),
3529 "cc".to_string(),
3530 "cxx".to_string(),
3531 "hpp".to_string(),
3532 "hh".to_string(),
3533 "hxx".to_string(),
3534 ],
3535 filenames: vec![],
3536 grammar: "cpp".to_string(),
3537 comment_prefix: Some("//".to_string()),
3538 auto_indent: true,
3539 auto_close: None,
3540 auto_surround: None,
3541 textmate_grammar: None,
3542 show_whitespace_tabs: true,
3543 line_wrap: None,
3544 wrap_column: None,
3545 page_view: None,
3546 page_width: None,
3547 use_tabs: None,
3548 tab_size: None,
3549 formatter: Some(FormatterConfig {
3550 command: "clang-format".to_string(),
3551 args: vec![],
3552 stdin: true,
3553 timeout_ms: 10000,
3554 }),
3555 format_on_save: false,
3556 on_save: vec![],
3557 word_characters: None,
3558 },
3559 );
3560
3561 languages.insert(
3562 "csharp".to_string(),
3563 LanguageConfig {
3564 extensions: vec!["cs".to_string()],
3565 filenames: vec![],
3566 grammar: "C#".to_string(),
3567 comment_prefix: Some("//".to_string()),
3568 auto_indent: true,
3569 auto_close: None,
3570 auto_surround: None,
3571 textmate_grammar: None,
3572 show_whitespace_tabs: true,
3573 line_wrap: None,
3574 wrap_column: None,
3575 page_view: None,
3576 page_width: None,
3577 use_tabs: None,
3578 tab_size: None,
3579 formatter: None,
3580 format_on_save: false,
3581 on_save: vec![],
3582 word_characters: None,
3583 },
3584 );
3585
3586 languages.insert(
3587 "bash".to_string(),
3588 LanguageConfig {
3589 extensions: vec!["sh".to_string(), "bash".to_string()],
3590 filenames: vec![
3591 ".bash_aliases".to_string(),
3592 ".bash_logout".to_string(),
3593 ".bash_profile".to_string(),
3594 ".bashrc".to_string(),
3595 ".env".to_string(),
3596 ".profile".to_string(),
3597 ".zlogin".to_string(),
3598 ".zlogout".to_string(),
3599 ".zprofile".to_string(),
3600 ".zshenv".to_string(),
3601 ".zshrc".to_string(),
3602 "PKGBUILD".to_string(),
3604 "APKBUILD".to_string(),
3605 ],
3606 grammar: "bash".to_string(),
3607 comment_prefix: Some("#".to_string()),
3608 auto_indent: true,
3609 auto_close: None,
3610 auto_surround: None,
3611 textmate_grammar: None,
3612 show_whitespace_tabs: true,
3613 line_wrap: None,
3614 wrap_column: None,
3615 page_view: None,
3616 page_width: None,
3617 use_tabs: None,
3618 tab_size: None,
3619 formatter: None,
3620 format_on_save: false,
3621 on_save: vec![],
3622 word_characters: None,
3623 },
3624 );
3625
3626 languages.insert(
3627 "makefile".to_string(),
3628 LanguageConfig {
3629 extensions: vec!["mk".to_string()],
3630 filenames: vec![
3631 "Makefile".to_string(),
3632 "makefile".to_string(),
3633 "GNUmakefile".to_string(),
3634 ],
3635 grammar: "Makefile".to_string(),
3636 comment_prefix: Some("#".to_string()),
3637 auto_indent: false,
3638 auto_close: None,
3639 auto_surround: None,
3640 textmate_grammar: None,
3641 show_whitespace_tabs: true,
3642 line_wrap: None,
3643 wrap_column: None,
3644 page_view: None,
3645 page_width: None,
3646 use_tabs: Some(true), tab_size: Some(8), formatter: None,
3649 format_on_save: false,
3650 on_save: vec![],
3651 word_characters: None,
3652 },
3653 );
3654
3655 languages.insert(
3656 "dockerfile".to_string(),
3657 LanguageConfig {
3658 extensions: vec!["dockerfile".to_string()],
3659 filenames: vec!["Dockerfile".to_string(), "Containerfile".to_string()],
3660 grammar: "dockerfile".to_string(),
3661 comment_prefix: Some("#".to_string()),
3662 auto_indent: true,
3663 auto_close: None,
3664 auto_surround: None,
3665 textmate_grammar: None,
3666 show_whitespace_tabs: true,
3667 line_wrap: None,
3668 wrap_column: None,
3669 page_view: None,
3670 page_width: None,
3671 use_tabs: None,
3672 tab_size: None,
3673 formatter: None,
3674 format_on_save: false,
3675 on_save: vec![],
3676 word_characters: None,
3677 },
3678 );
3679
3680 languages.insert(
3681 "json".to_string(),
3682 LanguageConfig {
3683 extensions: vec!["json".to_string()],
3684 filenames: vec![],
3685 grammar: "json".to_string(),
3686 comment_prefix: None,
3687 auto_indent: true,
3688 auto_close: None,
3689 auto_surround: None,
3690 textmate_grammar: None,
3691 show_whitespace_tabs: true,
3692 line_wrap: None,
3693 wrap_column: None,
3694 page_view: None,
3695 page_width: None,
3696 use_tabs: None,
3697 tab_size: None,
3698 formatter: Some(FormatterConfig {
3699 command: "prettier".to_string(),
3700 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3701 stdin: true,
3702 timeout_ms: 10000,
3703 }),
3704 format_on_save: false,
3705 on_save: vec![],
3706 word_characters: None,
3707 },
3708 );
3709
3710 languages.insert(
3717 "jsonc".to_string(),
3718 LanguageConfig {
3719 extensions: vec!["jsonc".to_string()],
3720 filenames: vec![
3721 "devcontainer.json".to_string(),
3722 ".devcontainer.json".to_string(),
3723 "tsconfig.json".to_string(),
3724 "tsconfig.*.json".to_string(),
3725 "jsconfig.json".to_string(),
3726 "jsconfig.*.json".to_string(),
3727 ".eslintrc.json".to_string(),
3728 ".babelrc".to_string(),
3729 ".babelrc.json".to_string(),
3730 ".swcrc".to_string(),
3731 ".jshintrc".to_string(),
3732 ".hintrc".to_string(),
3733 "settings.json".to_string(),
3734 "keybindings.json".to_string(),
3735 "tasks.json".to_string(),
3736 "launch.json".to_string(),
3737 "extensions.json".to_string(),
3738 "argv.json".to_string(),
3739 ],
3740 grammar: "jsonc".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: Some(FormatterConfig {
3754 command: "prettier".to_string(),
3755 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3756 stdin: true,
3757 timeout_ms: 10000,
3758 }),
3759 format_on_save: false,
3760 on_save: vec![],
3761 word_characters: None,
3762 },
3763 );
3764
3765 languages.insert(
3766 "toml".to_string(),
3767 LanguageConfig {
3768 extensions: vec!["toml".to_string()],
3769 filenames: vec!["Cargo.lock".to_string()],
3770 grammar: "toml".to_string(),
3771 comment_prefix: Some("#".to_string()),
3772 auto_indent: true,
3773 auto_close: None,
3774 auto_surround: None,
3775 textmate_grammar: None,
3776 show_whitespace_tabs: true,
3777 line_wrap: None,
3778 wrap_column: None,
3779 page_view: None,
3780 page_width: None,
3781 use_tabs: None,
3782 tab_size: None,
3783 formatter: None,
3784 format_on_save: false,
3785 on_save: vec![],
3786 word_characters: None,
3787 },
3788 );
3789
3790 languages.insert(
3791 "yaml".to_string(),
3792 LanguageConfig {
3793 extensions: vec!["yml".to_string(), "yaml".to_string()],
3794 filenames: vec![],
3795 grammar: "yaml".to_string(),
3796 comment_prefix: Some("#".to_string()),
3797 auto_indent: true,
3798 auto_close: None,
3799 auto_surround: None,
3800 textmate_grammar: None,
3801 show_whitespace_tabs: true,
3802 line_wrap: None,
3803 wrap_column: None,
3804 page_view: None,
3805 page_width: None,
3806 use_tabs: None,
3807 tab_size: None,
3808 formatter: Some(FormatterConfig {
3809 command: "prettier".to_string(),
3810 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3811 stdin: true,
3812 timeout_ms: 10000,
3813 }),
3814 format_on_save: false,
3815 on_save: vec![],
3816 word_characters: None,
3817 },
3818 );
3819
3820 languages.insert(
3821 "markdown".to_string(),
3822 LanguageConfig {
3823 extensions: vec!["md".to_string(), "markdown".to_string()],
3824 filenames: vec!["README".to_string()],
3825 grammar: "markdown".to_string(),
3826 comment_prefix: None,
3827 auto_indent: false,
3828 auto_close: None,
3829 auto_surround: None,
3830 textmate_grammar: None,
3831 show_whitespace_tabs: true,
3832 line_wrap: None,
3833 wrap_column: None,
3834 page_view: None,
3835 page_width: None,
3836 use_tabs: None,
3837 tab_size: None,
3838 formatter: None,
3839 format_on_save: false,
3840 on_save: vec![],
3841 word_characters: None,
3842 },
3843 );
3844
3845 languages.insert(
3847 "go".to_string(),
3848 LanguageConfig {
3849 extensions: vec!["go".to_string()],
3850 filenames: vec![],
3851 grammar: "go".to_string(),
3852 comment_prefix: Some("//".to_string()),
3853 auto_indent: true,
3854 auto_close: None,
3855 auto_surround: None,
3856 textmate_grammar: None,
3857 show_whitespace_tabs: false,
3858 line_wrap: None,
3859 wrap_column: None,
3860 page_view: None,
3861 page_width: None,
3862 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
3865 command: "gofmt".to_string(),
3866 args: vec![],
3867 stdin: true,
3868 timeout_ms: 10000,
3869 }),
3870 format_on_save: false,
3871 on_save: vec![],
3872 word_characters: None,
3873 },
3874 );
3875
3876 languages.insert(
3877 "odin".to_string(),
3878 LanguageConfig {
3879 extensions: vec!["odin".to_string()],
3880 filenames: vec![],
3881 grammar: "odin".to_string(),
3882 comment_prefix: Some("//".to_string()),
3883 auto_indent: true,
3884 auto_close: None,
3885 auto_surround: None,
3886 textmate_grammar: None,
3887 show_whitespace_tabs: false,
3888 line_wrap: None,
3889 wrap_column: None,
3890 page_view: None,
3891 page_width: None,
3892 use_tabs: Some(true),
3893 tab_size: Some(8),
3894 formatter: None,
3895 format_on_save: false,
3896 on_save: vec![],
3897 word_characters: None,
3898 },
3899 );
3900
3901 languages.insert(
3902 "zig".to_string(),
3903 LanguageConfig {
3904 extensions: vec!["zig".to_string(), "zon".to_string()],
3905 filenames: vec![],
3906 grammar: "zig".to_string(),
3907 comment_prefix: Some("//".to_string()),
3908 auto_indent: true,
3909 auto_close: None,
3910 auto_surround: None,
3911 textmate_grammar: None,
3912 show_whitespace_tabs: true,
3913 line_wrap: None,
3914 wrap_column: None,
3915 page_view: None,
3916 page_width: None,
3917 use_tabs: None,
3918 tab_size: None,
3919 formatter: None,
3920 format_on_save: false,
3921 on_save: vec![],
3922 word_characters: None,
3923 },
3924 );
3925
3926 languages.insert(
3927 "java".to_string(),
3928 LanguageConfig {
3929 extensions: vec!["java".to_string()],
3930 filenames: vec![],
3931 grammar: "java".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: true,
3938 line_wrap: None,
3939 wrap_column: None,
3940 page_view: None,
3941 page_width: None,
3942 use_tabs: None,
3943 tab_size: None,
3944 formatter: None,
3945 format_on_save: false,
3946 on_save: vec![],
3947 word_characters: None,
3948 },
3949 );
3950
3951 languages.insert(
3952 "latex".to_string(),
3953 LanguageConfig {
3954 extensions: vec![
3955 "tex".to_string(),
3956 "latex".to_string(),
3957 "ltx".to_string(),
3958 "sty".to_string(),
3959 "cls".to_string(),
3960 "bib".to_string(),
3961 ],
3962 filenames: vec![],
3963 grammar: "latex".to_string(),
3964 comment_prefix: Some("%".to_string()),
3965 auto_indent: true,
3966 auto_close: None,
3967 auto_surround: None,
3968 textmate_grammar: None,
3969 show_whitespace_tabs: true,
3970 line_wrap: None,
3971 wrap_column: None,
3972 page_view: None,
3973 page_width: None,
3974 use_tabs: None,
3975 tab_size: None,
3976 formatter: None,
3977 format_on_save: false,
3978 on_save: vec![],
3979 word_characters: None,
3980 },
3981 );
3982
3983 languages.insert(
3984 "templ".to_string(),
3985 LanguageConfig {
3986 extensions: vec!["templ".to_string()],
3987 filenames: vec![],
3988 grammar: "go".to_string(), comment_prefix: Some("//".to_string()),
3990 auto_indent: true,
3991 auto_close: None,
3992 auto_surround: None,
3993 textmate_grammar: None,
3994 show_whitespace_tabs: true,
3995 line_wrap: None,
3996 wrap_column: None,
3997 page_view: None,
3998 page_width: None,
3999 use_tabs: None,
4000 tab_size: None,
4001 formatter: None,
4002 format_on_save: false,
4003 on_save: vec![],
4004 word_characters: None,
4005 },
4006 );
4007
4008 languages.insert(
4010 "git-rebase".to_string(),
4011 LanguageConfig {
4012 extensions: vec![],
4013 filenames: vec!["git-rebase-todo".to_string()],
4014 grammar: "Git Rebase Todo".to_string(),
4015 comment_prefix: Some("#".to_string()),
4016 auto_indent: false,
4017 auto_close: None,
4018 auto_surround: None,
4019 textmate_grammar: None,
4020 show_whitespace_tabs: true,
4021 line_wrap: None,
4022 wrap_column: None,
4023 page_view: None,
4024 page_width: None,
4025 use_tabs: None,
4026 tab_size: None,
4027 formatter: None,
4028 format_on_save: false,
4029 on_save: vec![],
4030 word_characters: None,
4031 },
4032 );
4033
4034 languages.insert(
4035 "git-commit".to_string(),
4036 LanguageConfig {
4037 extensions: vec![],
4038 filenames: vec![
4039 "COMMIT_EDITMSG".to_string(),
4040 "MERGE_MSG".to_string(),
4041 "SQUASH_MSG".to_string(),
4042 "TAG_EDITMSG".to_string(),
4043 ],
4044 grammar: "Git Commit Message".to_string(),
4045 comment_prefix: Some("#".to_string()),
4046 auto_indent: false,
4047 auto_close: None,
4048 auto_surround: None,
4049 textmate_grammar: None,
4050 show_whitespace_tabs: true,
4051 line_wrap: None,
4052 wrap_column: None,
4053 page_view: None,
4054 page_width: None,
4055 use_tabs: None,
4056 tab_size: None,
4057 formatter: None,
4058 format_on_save: false,
4059 on_save: vec![],
4060 word_characters: None,
4061 },
4062 );
4063
4064 languages.insert(
4065 "gitignore".to_string(),
4066 LanguageConfig {
4067 extensions: vec!["gitignore".to_string()],
4068 filenames: vec![
4069 ".gitignore".to_string(),
4070 ".dockerignore".to_string(),
4071 ".npmignore".to_string(),
4072 ".hgignore".to_string(),
4073 ],
4074 grammar: "Gitignore".to_string(),
4075 comment_prefix: Some("#".to_string()),
4076 auto_indent: false,
4077 auto_close: None,
4078 auto_surround: None,
4079 textmate_grammar: None,
4080 show_whitespace_tabs: true,
4081 line_wrap: None,
4082 wrap_column: None,
4083 page_view: None,
4084 page_width: None,
4085 use_tabs: None,
4086 tab_size: None,
4087 formatter: None,
4088 format_on_save: false,
4089 on_save: vec![],
4090 word_characters: None,
4091 },
4092 );
4093
4094 languages.insert(
4095 "gitconfig".to_string(),
4096 LanguageConfig {
4097 extensions: vec!["gitconfig".to_string()],
4098 filenames: vec![".gitconfig".to_string(), ".gitmodules".to_string()],
4099 grammar: "Git Config".to_string(),
4100 comment_prefix: Some("#".to_string()),
4101 auto_indent: true,
4102 auto_close: None,
4103 auto_surround: None,
4104 textmate_grammar: None,
4105 show_whitespace_tabs: true,
4106 line_wrap: None,
4107 wrap_column: None,
4108 page_view: None,
4109 page_width: None,
4110 use_tabs: None,
4111 tab_size: None,
4112 formatter: None,
4113 format_on_save: false,
4114 on_save: vec![],
4115 word_characters: None,
4116 },
4117 );
4118
4119 languages.insert(
4120 "gitattributes".to_string(),
4121 LanguageConfig {
4122 extensions: vec!["gitattributes".to_string()],
4123 filenames: vec![".gitattributes".to_string()],
4124 grammar: "Git Attributes".to_string(),
4125 comment_prefix: Some("#".to_string()),
4126 auto_indent: false,
4127 auto_close: None,
4128 auto_surround: None,
4129 textmate_grammar: None,
4130 show_whitespace_tabs: true,
4131 line_wrap: None,
4132 wrap_column: None,
4133 page_view: None,
4134 page_width: None,
4135 use_tabs: None,
4136 tab_size: None,
4137 formatter: None,
4138 format_on_save: false,
4139 on_save: vec![],
4140 word_characters: None,
4141 },
4142 );
4143
4144 languages.insert(
4145 "typst".to_string(),
4146 LanguageConfig {
4147 extensions: vec!["typ".to_string()],
4148 filenames: vec![],
4149 grammar: "Typst".to_string(),
4150 comment_prefix: Some("//".to_string()),
4151 auto_indent: true,
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(
4174 "kotlin".to_string(),
4175 LanguageConfig {
4176 extensions: vec!["kt".to_string(), "kts".to_string()],
4177 filenames: vec![],
4178 grammar: "Kotlin".to_string(),
4179 comment_prefix: Some("//".to_string()),
4180 auto_indent: true,
4181 auto_close: None,
4182 auto_surround: None,
4183 textmate_grammar: None,
4184 show_whitespace_tabs: true,
4185 line_wrap: None,
4186 wrap_column: None,
4187 page_view: None,
4188 page_width: None,
4189 use_tabs: None,
4190 tab_size: None,
4191 formatter: None,
4192 format_on_save: false,
4193 on_save: vec![],
4194 word_characters: None,
4195 },
4196 );
4197
4198 languages.insert(
4199 "swift".to_string(),
4200 LanguageConfig {
4201 extensions: vec!["swift".to_string()],
4202 filenames: vec![],
4203 grammar: "Swift".to_string(),
4204 comment_prefix: Some("//".to_string()),
4205 auto_indent: true,
4206 auto_close: None,
4207 auto_surround: None,
4208 textmate_grammar: None,
4209 show_whitespace_tabs: true,
4210 line_wrap: None,
4211 wrap_column: None,
4212 page_view: None,
4213 page_width: None,
4214 use_tabs: None,
4215 tab_size: None,
4216 formatter: None,
4217 format_on_save: false,
4218 on_save: vec![],
4219 word_characters: None,
4220 },
4221 );
4222
4223 languages.insert(
4224 "scala".to_string(),
4225 LanguageConfig {
4226 extensions: vec!["scala".to_string(), "sc".to_string()],
4227 filenames: vec![],
4228 grammar: "Scala".to_string(),
4229 comment_prefix: Some("//".to_string()),
4230 auto_indent: true,
4231 auto_close: None,
4232 auto_surround: None,
4233 textmate_grammar: None,
4234 show_whitespace_tabs: true,
4235 line_wrap: None,
4236 wrap_column: None,
4237 page_view: None,
4238 page_width: None,
4239 use_tabs: None,
4240 tab_size: None,
4241 formatter: None,
4242 format_on_save: false,
4243 on_save: vec![],
4244 word_characters: None,
4245 },
4246 );
4247
4248 languages.insert(
4249 "dart".to_string(),
4250 LanguageConfig {
4251 extensions: vec!["dart".to_string()],
4252 filenames: vec![],
4253 grammar: "Dart".to_string(),
4254 comment_prefix: Some("//".to_string()),
4255 auto_indent: true,
4256 auto_close: None,
4257 auto_surround: None,
4258 textmate_grammar: None,
4259 show_whitespace_tabs: true,
4260 line_wrap: None,
4261 wrap_column: None,
4262 page_view: None,
4263 page_width: None,
4264 use_tabs: None,
4265 tab_size: None,
4266 formatter: None,
4267 format_on_save: false,
4268 on_save: vec![],
4269 word_characters: None,
4270 },
4271 );
4272
4273 languages.insert(
4274 "elixir".to_string(),
4275 LanguageConfig {
4276 extensions: vec!["ex".to_string(), "exs".to_string()],
4277 filenames: vec![],
4278 grammar: "Elixir".to_string(),
4279 comment_prefix: Some("#".to_string()),
4280 auto_indent: true,
4281 auto_close: None,
4282 auto_surround: None,
4283 textmate_grammar: None,
4284 show_whitespace_tabs: true,
4285 line_wrap: None,
4286 wrap_column: None,
4287 page_view: None,
4288 page_width: None,
4289 use_tabs: None,
4290 tab_size: None,
4291 formatter: None,
4292 format_on_save: false,
4293 on_save: vec![],
4294 word_characters: None,
4295 },
4296 );
4297
4298 languages.insert(
4299 "erlang".to_string(),
4300 LanguageConfig {
4301 extensions: vec!["erl".to_string(), "hrl".to_string()],
4302 filenames: vec![],
4303 grammar: "Erlang".to_string(),
4304 comment_prefix: Some("%".to_string()),
4305 auto_indent: true,
4306 auto_close: None,
4307 auto_surround: None,
4308 textmate_grammar: None,
4309 show_whitespace_tabs: true,
4310 line_wrap: None,
4311 wrap_column: None,
4312 page_view: None,
4313 page_width: None,
4314 use_tabs: None,
4315 tab_size: None,
4316 formatter: None,
4317 format_on_save: false,
4318 on_save: vec![],
4319 word_characters: None,
4320 },
4321 );
4322
4323 languages.insert(
4324 "haskell".to_string(),
4325 LanguageConfig {
4326 extensions: vec!["hs".to_string(), "lhs".to_string()],
4327 filenames: vec![],
4328 grammar: "Haskell".to_string(),
4329 comment_prefix: Some("--".to_string()),
4330 auto_indent: true,
4331 auto_close: None,
4332 auto_surround: None,
4333 textmate_grammar: None,
4334 show_whitespace_tabs: true,
4335 line_wrap: None,
4336 wrap_column: None,
4337 page_view: None,
4338 page_width: None,
4339 use_tabs: None,
4340 tab_size: None,
4341 formatter: None,
4342 format_on_save: false,
4343 on_save: vec![],
4344 word_characters: None,
4345 },
4346 );
4347
4348 languages.insert(
4349 "ocaml".to_string(),
4350 LanguageConfig {
4351 extensions: vec!["ml".to_string(), "mli".to_string()],
4352 filenames: vec![],
4353 grammar: "OCaml".to_string(),
4354 comment_prefix: None,
4355 auto_indent: true,
4356 auto_close: None,
4357 auto_surround: None,
4358 textmate_grammar: None,
4359 show_whitespace_tabs: true,
4360 line_wrap: None,
4361 wrap_column: None,
4362 page_view: None,
4363 page_width: None,
4364 use_tabs: None,
4365 tab_size: None,
4366 formatter: None,
4367 format_on_save: false,
4368 on_save: vec![],
4369 word_characters: None,
4370 },
4371 );
4372
4373 languages.insert(
4374 "clojure".to_string(),
4375 LanguageConfig {
4376 extensions: vec![
4377 "clj".to_string(),
4378 "cljs".to_string(),
4379 "cljc".to_string(),
4380 "edn".to_string(),
4381 ],
4382 filenames: vec![],
4383 grammar: "Clojure".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 "r".to_string(),
4405 LanguageConfig {
4406 extensions: vec!["r".to_string(), "R".to_string(), "rmd".to_string()],
4407 filenames: vec![],
4408 grammar: "R".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 "julia".to_string(),
4430 LanguageConfig {
4431 extensions: vec!["jl".to_string()],
4432 filenames: vec![],
4433 grammar: "Julia".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 "perl".to_string(),
4455 LanguageConfig {
4456 extensions: vec!["pl".to_string(), "pm".to_string(), "t".to_string()],
4457 filenames: vec![],
4458 grammar: "Perl".to_string(),
4459 comment_prefix: Some("#".to_string()),
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 "nim".to_string(),
4480 LanguageConfig {
4481 extensions: vec!["nim".to_string(), "nims".to_string(), "nimble".to_string()],
4482 filenames: vec![],
4483 grammar: "Nim".to_string(),
4484 comment_prefix: Some("#".to_string()),
4485 auto_indent: true,
4486 auto_close: None,
4487 auto_surround: None,
4488 textmate_grammar: None,
4489 show_whitespace_tabs: true,
4490 line_wrap: None,
4491 wrap_column: None,
4492 page_view: None,
4493 page_width: None,
4494 use_tabs: None,
4495 tab_size: None,
4496 formatter: None,
4497 format_on_save: false,
4498 on_save: vec![],
4499 word_characters: None,
4500 },
4501 );
4502
4503 languages.insert(
4504 "gleam".to_string(),
4505 LanguageConfig {
4506 extensions: vec!["gleam".to_string()],
4507 filenames: vec![],
4508 grammar: "Gleam".to_string(),
4509 comment_prefix: Some("//".to_string()),
4510 auto_indent: true,
4511 auto_close: None,
4512 auto_surround: None,
4513 textmate_grammar: None,
4514 show_whitespace_tabs: true,
4515 line_wrap: None,
4516 wrap_column: None,
4517 page_view: None,
4518 page_width: None,
4519 use_tabs: None,
4520 tab_size: None,
4521 formatter: None,
4522 format_on_save: false,
4523 on_save: vec![],
4524 word_characters: None,
4525 },
4526 );
4527
4528 languages.insert(
4529 "racket".to_string(),
4530 LanguageConfig {
4531 extensions: vec![
4532 "rkt".to_string(),
4533 "rktd".to_string(),
4534 "rktl".to_string(),
4535 "scrbl".to_string(),
4536 ],
4537 filenames: vec![],
4538 grammar: "Racket".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 "fsharp".to_string(),
4560 LanguageConfig {
4561 extensions: vec!["fs".to_string(), "fsi".to_string(), "fsx".to_string()],
4562 filenames: vec![],
4563 grammar: "FSharp".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 "nix".to_string(),
4585 LanguageConfig {
4586 extensions: vec!["nix".to_string()],
4587 filenames: vec![],
4588 grammar: "Nix".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 "nushell".to_string(),
4610 LanguageConfig {
4611 extensions: vec!["nu".to_string()],
4612 filenames: vec![],
4613 grammar: "Nushell".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 "solidity".to_string(),
4635 LanguageConfig {
4636 extensions: vec!["sol".to_string()],
4637 filenames: vec![],
4638 grammar: "Solidity".to_string(),
4639 comment_prefix: Some("//".to_string()),
4640 auto_indent: true,
4641 auto_close: None,
4642 auto_surround: None,
4643 textmate_grammar: None,
4644 show_whitespace_tabs: true,
4645 line_wrap: None,
4646 wrap_column: None,
4647 page_view: None,
4648 page_width: None,
4649 use_tabs: None,
4650 tab_size: None,
4651 formatter: None,
4652 format_on_save: false,
4653 on_save: vec![],
4654 word_characters: None,
4655 },
4656 );
4657
4658 languages.insert(
4659 "verilog".to_string(),
4660 LanguageConfig {
4661 extensions: vec!["vh".to_string(), "verilog".to_string()],
4662 filenames: vec![],
4663 grammar: "Verilog".to_string(),
4664 comment_prefix: Some("//".to_string()),
4665 auto_indent: true,
4666 auto_close: None,
4667 auto_surround: None,
4668 textmate_grammar: None,
4669 show_whitespace_tabs: true,
4670 line_wrap: None,
4671 wrap_column: None,
4672 page_view: None,
4673 page_width: None,
4674 use_tabs: None,
4675 tab_size: None,
4676 formatter: None,
4677 format_on_save: false,
4678 on_save: vec![],
4679 word_characters: None,
4680 },
4681 );
4682
4683 languages.insert(
4684 "systemverilog".to_string(),
4685 LanguageConfig {
4686 extensions: vec![
4687 "sv".to_string(),
4688 "svh".to_string(),
4689 "svi".to_string(),
4690 "svp".to_string(),
4691 ],
4692 filenames: vec![],
4693 grammar: "SystemVerilog".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 "vhdl".to_string(),
4715 LanguageConfig {
4716 extensions: vec!["vhd".to_string(), "vhdl".to_string(), "vho".to_string()],
4717 filenames: vec![],
4718 grammar: "VHDL".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 "ruby".to_string(),
4740 LanguageConfig {
4741 extensions: vec!["rb".to_string(), "rake".to_string(), "gemspec".to_string()],
4742 filenames: vec![
4743 "Gemfile".to_string(),
4744 "Rakefile".to_string(),
4745 "Guardfile".to_string(),
4746 ],
4747 grammar: "Ruby".to_string(),
4748 comment_prefix: Some("#".to_string()),
4749 auto_indent: true,
4750 auto_close: None,
4751 auto_surround: None,
4752 textmate_grammar: None,
4753 show_whitespace_tabs: true,
4754 line_wrap: None,
4755 wrap_column: None,
4756 page_view: None,
4757 page_width: None,
4758 use_tabs: None,
4759 tab_size: None,
4760 formatter: None,
4761 format_on_save: false,
4762 on_save: vec![],
4763 word_characters: None,
4764 },
4765 );
4766
4767 languages.insert(
4768 "php".to_string(),
4769 LanguageConfig {
4770 extensions: vec!["php".to_string(), "phtml".to_string()],
4771 filenames: vec![],
4772 grammar: "PHP".to_string(),
4773 comment_prefix: Some("//".to_string()),
4774 auto_indent: true,
4775 auto_close: None,
4776 auto_surround: None,
4777 textmate_grammar: None,
4778 show_whitespace_tabs: true,
4779 line_wrap: None,
4780 wrap_column: None,
4781 page_view: None,
4782 page_width: None,
4783 use_tabs: None,
4784 tab_size: None,
4785 formatter: None,
4786 format_on_save: false,
4787 on_save: vec![],
4788 word_characters: None,
4789 },
4790 );
4791
4792 languages.insert(
4793 "lua".to_string(),
4794 LanguageConfig {
4795 extensions: vec!["lua".to_string()],
4796 filenames: vec![],
4797 grammar: "Lua".to_string(),
4798 comment_prefix: Some("--".to_string()),
4799 auto_indent: true,
4800 auto_close: None,
4801 auto_surround: None,
4802 textmate_grammar: None,
4803 show_whitespace_tabs: true,
4804 line_wrap: None,
4805 wrap_column: None,
4806 page_view: None,
4807 page_width: None,
4808 use_tabs: None,
4809 tab_size: None,
4810 formatter: None,
4811 format_on_save: false,
4812 on_save: vec![],
4813 word_characters: None,
4814 },
4815 );
4816
4817 languages.insert(
4818 "html".to_string(),
4819 LanguageConfig {
4820 extensions: vec!["html".to_string(), "htm".to_string()],
4821 filenames: vec![],
4822 grammar: "HTML".to_string(),
4823 comment_prefix: None,
4824 auto_indent: true,
4825 auto_close: None,
4826 auto_surround: None,
4827 textmate_grammar: None,
4828 show_whitespace_tabs: true,
4829 line_wrap: None,
4830 wrap_column: None,
4831 page_view: None,
4832 page_width: None,
4833 use_tabs: None,
4834 tab_size: None,
4835 formatter: None,
4836 format_on_save: false,
4837 on_save: vec![],
4838 word_characters: None,
4839 },
4840 );
4841
4842 languages.insert(
4843 "css".to_string(),
4844 LanguageConfig {
4845 extensions: vec!["css".to_string()],
4846 filenames: vec![],
4847 grammar: "CSS".to_string(),
4848 comment_prefix: None,
4849 auto_indent: true,
4850 auto_close: None,
4851 auto_surround: None,
4852 textmate_grammar: None,
4853 show_whitespace_tabs: true,
4854 line_wrap: None,
4855 wrap_column: None,
4856 page_view: None,
4857 page_width: None,
4858 use_tabs: None,
4859 tab_size: None,
4860 formatter: None,
4861 format_on_save: false,
4862 on_save: vec![],
4863 word_characters: None,
4864 },
4865 );
4866
4867 languages.insert(
4868 "sql".to_string(),
4869 LanguageConfig {
4870 extensions: vec!["sql".to_string()],
4871 filenames: vec![],
4872 grammar: "SQL".to_string(),
4873 comment_prefix: Some("--".to_string()),
4874 auto_indent: true,
4875 auto_close: None,
4876 auto_surround: None,
4877 textmate_grammar: None,
4878 show_whitespace_tabs: true,
4879 line_wrap: None,
4880 wrap_column: None,
4881 page_view: None,
4882 page_width: None,
4883 use_tabs: None,
4884 tab_size: None,
4885 formatter: None,
4886 format_on_save: false,
4887 on_save: vec![],
4888 word_characters: None,
4889 },
4890 );
4891
4892 languages.insert(
4893 "graphql".to_string(),
4894 LanguageConfig {
4895 extensions: vec!["graphql".to_string(), "gql".to_string()],
4896 filenames: vec![],
4897 grammar: "GraphQL".to_string(),
4898 comment_prefix: Some("#".to_string()),
4899 auto_indent: true,
4900 auto_close: None,
4901 auto_surround: None,
4902 textmate_grammar: None,
4903 show_whitespace_tabs: true,
4904 line_wrap: None,
4905 wrap_column: None,
4906 page_view: None,
4907 page_width: None,
4908 use_tabs: None,
4909 tab_size: None,
4910 formatter: None,
4911 format_on_save: false,
4912 on_save: vec![],
4913 word_characters: None,
4914 },
4915 );
4916
4917 languages.insert(
4918 "protobuf".to_string(),
4919 LanguageConfig {
4920 extensions: vec!["proto".to_string()],
4921 filenames: vec![],
4922 grammar: "Protocol Buffers".to_string(),
4923 comment_prefix: Some("//".to_string()),
4924 auto_indent: true,
4925 auto_close: None,
4926 auto_surround: None,
4927 textmate_grammar: None,
4928 show_whitespace_tabs: true,
4929 line_wrap: None,
4930 wrap_column: None,
4931 page_view: None,
4932 page_width: None,
4933 use_tabs: None,
4934 tab_size: None,
4935 formatter: None,
4936 format_on_save: false,
4937 on_save: vec![],
4938 word_characters: None,
4939 },
4940 );
4941
4942 languages.insert(
4943 "cmake".to_string(),
4944 LanguageConfig {
4945 extensions: vec!["cmake".to_string()],
4946 filenames: vec!["CMakeLists.txt".to_string()],
4947 grammar: "CMake".to_string(),
4948 comment_prefix: Some("#".to_string()),
4949 auto_indent: true,
4950 auto_close: None,
4951 auto_surround: None,
4952 textmate_grammar: None,
4953 show_whitespace_tabs: true,
4954 line_wrap: None,
4955 wrap_column: None,
4956 page_view: None,
4957 page_width: None,
4958 use_tabs: None,
4959 tab_size: None,
4960 formatter: None,
4961 format_on_save: false,
4962 on_save: vec![],
4963 word_characters: None,
4964 },
4965 );
4966
4967 languages.insert(
4968 "terraform".to_string(),
4969 LanguageConfig {
4970 extensions: vec!["tf".to_string(), "tfvars".to_string(), "hcl".to_string()],
4971 filenames: vec![],
4972 grammar: "HCL".to_string(),
4973 comment_prefix: Some("#".to_string()),
4974 auto_indent: true,
4975 auto_close: None,
4976 auto_surround: None,
4977 textmate_grammar: None,
4978 show_whitespace_tabs: true,
4979 line_wrap: None,
4980 wrap_column: None,
4981 page_view: None,
4982 page_width: None,
4983 use_tabs: None,
4984 tab_size: None,
4985 formatter: None,
4986 format_on_save: false,
4987 on_save: vec![],
4988 word_characters: None,
4989 },
4990 );
4991
4992 languages.insert(
4993 "vue".to_string(),
4994 LanguageConfig {
4995 extensions: vec!["vue".to_string()],
4996 filenames: vec![],
4997 grammar: "Vue".to_string(),
4998 comment_prefix: None,
4999 auto_indent: true,
5000 auto_close: None,
5001 auto_surround: None,
5002 textmate_grammar: None,
5003 show_whitespace_tabs: true,
5004 line_wrap: None,
5005 wrap_column: None,
5006 page_view: None,
5007 page_width: None,
5008 use_tabs: None,
5009 tab_size: None,
5010 formatter: None,
5011 format_on_save: false,
5012 on_save: vec![],
5013 word_characters: None,
5014 },
5015 );
5016
5017 languages.insert(
5018 "svelte".to_string(),
5019 LanguageConfig {
5020 extensions: vec!["svelte".to_string()],
5021 filenames: vec![],
5022 grammar: "Svelte".to_string(),
5023 comment_prefix: None,
5024 auto_indent: true,
5025 auto_close: None,
5026 auto_surround: None,
5027 textmate_grammar: None,
5028 show_whitespace_tabs: true,
5029 line_wrap: None,
5030 wrap_column: None,
5031 page_view: None,
5032 page_width: None,
5033 use_tabs: None,
5034 tab_size: None,
5035 formatter: None,
5036 format_on_save: false,
5037 on_save: vec![],
5038 word_characters: None,
5039 },
5040 );
5041
5042 languages.insert(
5043 "astro".to_string(),
5044 LanguageConfig {
5045 extensions: vec!["astro".to_string()],
5046 filenames: vec![],
5047 grammar: "Astro".to_string(),
5048 comment_prefix: None,
5049 auto_indent: true,
5050 auto_close: None,
5051 auto_surround: None,
5052 textmate_grammar: None,
5053 show_whitespace_tabs: true,
5054 line_wrap: None,
5055 wrap_column: None,
5056 page_view: None,
5057 page_width: None,
5058 use_tabs: None,
5059 tab_size: None,
5060 formatter: None,
5061 format_on_save: false,
5062 on_save: vec![],
5063 word_characters: None,
5064 },
5065 );
5066
5067 languages.insert(
5070 "scss".to_string(),
5071 LanguageConfig {
5072 extensions: vec!["scss".to_string()],
5073 filenames: vec![],
5074 grammar: "SCSS".to_string(),
5075 comment_prefix: Some("//".to_string()),
5076 auto_indent: true,
5077 auto_close: None,
5078 auto_surround: None,
5079 textmate_grammar: None,
5080 show_whitespace_tabs: true,
5081 line_wrap: None,
5082 wrap_column: None,
5083 page_view: None,
5084 page_width: None,
5085 use_tabs: None,
5086 tab_size: None,
5087 formatter: None,
5088 format_on_save: false,
5089 on_save: vec![],
5090 word_characters: None,
5091 },
5092 );
5093
5094 languages.insert(
5095 "less".to_string(),
5096 LanguageConfig {
5097 extensions: vec!["less".to_string()],
5098 filenames: vec![],
5099 grammar: "LESS".to_string(),
5100 comment_prefix: Some("//".to_string()),
5101 auto_indent: true,
5102 auto_close: None,
5103 auto_surround: None,
5104 textmate_grammar: None,
5105 show_whitespace_tabs: true,
5106 line_wrap: None,
5107 wrap_column: None,
5108 page_view: None,
5109 page_width: None,
5110 use_tabs: None,
5111 tab_size: None,
5112 formatter: None,
5113 format_on_save: false,
5114 on_save: vec![],
5115 word_characters: None,
5116 },
5117 );
5118
5119 languages.insert(
5120 "powershell".to_string(),
5121 LanguageConfig {
5122 extensions: vec!["ps1".to_string(), "psm1".to_string(), "psd1".to_string()],
5123 filenames: vec![],
5124 grammar: "PowerShell".to_string(),
5125 comment_prefix: Some("#".to_string()),
5126 auto_indent: true,
5127 auto_close: None,
5128 auto_surround: None,
5129 textmate_grammar: None,
5130 show_whitespace_tabs: true,
5131 line_wrap: None,
5132 wrap_column: None,
5133 page_view: None,
5134 page_width: None,
5135 use_tabs: None,
5136 tab_size: None,
5137 formatter: None,
5138 format_on_save: false,
5139 on_save: vec![],
5140 word_characters: None,
5141 },
5142 );
5143
5144 languages.insert(
5145 "kdl".to_string(),
5146 LanguageConfig {
5147 extensions: vec!["kdl".to_string()],
5148 filenames: vec![],
5149 grammar: "KDL".to_string(),
5150 comment_prefix: Some("//".to_string()),
5151 auto_indent: true,
5152 auto_close: None,
5153 auto_surround: None,
5154 textmate_grammar: None,
5155 show_whitespace_tabs: true,
5156 line_wrap: None,
5157 wrap_column: None,
5158 page_view: None,
5159 page_width: None,
5160 use_tabs: None,
5161 tab_size: None,
5162 formatter: None,
5163 format_on_save: false,
5164 on_save: vec![],
5165 word_characters: None,
5166 },
5167 );
5168
5169 languages.insert(
5170 "starlark".to_string(),
5171 LanguageConfig {
5172 extensions: vec!["bzl".to_string(), "star".to_string()],
5173 filenames: vec!["BUILD".to_string(), "WORKSPACE".to_string()],
5174 grammar: "Starlark".to_string(),
5175 comment_prefix: Some("#".to_string()),
5176 auto_indent: true,
5177 auto_close: None,
5178 auto_surround: None,
5179 textmate_grammar: None,
5180 show_whitespace_tabs: true,
5181 line_wrap: None,
5182 wrap_column: None,
5183 page_view: None,
5184 page_width: None,
5185 use_tabs: None,
5186 tab_size: None,
5187 formatter: None,
5188 format_on_save: false,
5189 on_save: vec![],
5190 word_characters: None,
5191 },
5192 );
5193
5194 languages.insert(
5195 "justfile".to_string(),
5196 LanguageConfig {
5197 extensions: vec![],
5198 filenames: vec![
5199 "justfile".to_string(),
5200 "Justfile".to_string(),
5201 ".justfile".to_string(),
5202 ],
5203 grammar: "Justfile".to_string(),
5204 comment_prefix: Some("#".to_string()),
5205 auto_indent: true,
5206 auto_close: None,
5207 auto_surround: None,
5208 textmate_grammar: None,
5209 show_whitespace_tabs: true,
5210 line_wrap: None,
5211 wrap_column: None,
5212 page_view: None,
5213 page_width: None,
5214 use_tabs: Some(true),
5215 tab_size: None,
5216 formatter: None,
5217 format_on_save: false,
5218 on_save: vec![],
5219 word_characters: None,
5220 },
5221 );
5222
5223 languages.insert(
5224 "earthfile".to_string(),
5225 LanguageConfig {
5226 extensions: vec!["earth".to_string()],
5227 filenames: vec!["Earthfile".to_string()],
5228 grammar: "Earthfile".to_string(),
5229 comment_prefix: Some("#".to_string()),
5230 auto_indent: true,
5231 auto_close: None,
5232 auto_surround: None,
5233 textmate_grammar: None,
5234 show_whitespace_tabs: true,
5235 line_wrap: None,
5236 wrap_column: None,
5237 page_view: None,
5238 page_width: None,
5239 use_tabs: None,
5240 tab_size: None,
5241 formatter: None,
5242 format_on_save: false,
5243 on_save: vec![],
5244 word_characters: None,
5245 },
5246 );
5247
5248 languages.insert(
5249 "gomod".to_string(),
5250 LanguageConfig {
5251 extensions: vec![],
5252 filenames: vec!["go.mod".to_string(), "go.sum".to_string()],
5253 grammar: "Go Module".to_string(),
5254 comment_prefix: Some("//".to_string()),
5255 auto_indent: true,
5256 auto_close: None,
5257 auto_surround: None,
5258 textmate_grammar: None,
5259 show_whitespace_tabs: true,
5260 line_wrap: None,
5261 wrap_column: None,
5262 page_view: None,
5263 page_width: None,
5264 use_tabs: Some(true),
5265 tab_size: None,
5266 formatter: None,
5267 format_on_save: false,
5268 on_save: vec![],
5269 word_characters: None,
5270 },
5271 );
5272
5273 languages.insert(
5274 "vlang".to_string(),
5275 LanguageConfig {
5276 extensions: vec!["v".to_string(), "vv".to_string()],
5277 filenames: vec![],
5278 grammar: "V".to_string(),
5279 comment_prefix: Some("//".to_string()),
5280 auto_indent: true,
5281 auto_close: None,
5282 auto_surround: None,
5283 textmate_grammar: None,
5284 show_whitespace_tabs: true,
5285 line_wrap: None,
5286 wrap_column: None,
5287 page_view: None,
5288 page_width: None,
5289 use_tabs: None,
5290 tab_size: None,
5291 formatter: None,
5292 format_on_save: false,
5293 on_save: vec![],
5294 word_characters: None,
5295 },
5296 );
5297
5298 languages.insert(
5299 "ini".to_string(),
5300 LanguageConfig {
5301 extensions: vec!["ini".to_string(), "cfg".to_string()],
5302 filenames: vec![],
5303 grammar: "INI".to_string(),
5304 comment_prefix: Some(";".to_string()),
5305 auto_indent: false,
5306 auto_close: None,
5307 auto_surround: None,
5308 textmate_grammar: None,
5309 show_whitespace_tabs: true,
5310 line_wrap: None,
5311 wrap_column: None,
5312 page_view: None,
5313 page_width: None,
5314 use_tabs: None,
5315 tab_size: None,
5316 formatter: None,
5317 format_on_save: false,
5318 on_save: vec![],
5319 word_characters: None,
5320 },
5321 );
5322
5323 languages.insert(
5324 "hyprlang".to_string(),
5325 LanguageConfig {
5326 extensions: vec!["hl".to_string()],
5327 filenames: vec!["hyprland.conf".to_string()],
5328 grammar: "Hyprlang".to_string(),
5329 comment_prefix: Some("#".to_string()),
5330 auto_indent: true,
5331 auto_close: None,
5332 auto_surround: None,
5333 textmate_grammar: None,
5334 show_whitespace_tabs: true,
5335 line_wrap: None,
5336 wrap_column: None,
5337 page_view: None,
5338 page_width: None,
5339 use_tabs: None,
5340 tab_size: None,
5341 formatter: None,
5342 format_on_save: false,
5343 on_save: vec![],
5344 word_characters: None,
5345 },
5346 );
5347
5348 languages
5349 }
5350
5351 #[cfg(feature = "runtime")]
5353 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
5354 let mut lsp = HashMap::new();
5355
5356 let ra_log_path = crate::services::log_dirs::lsp_log_path("rust-analyzer")
5359 .to_string_lossy()
5360 .to_string();
5361
5362 Self::populate_lsp_config(&mut lsp, ra_log_path);
5363 lsp
5364 }
5365
5366 #[cfg(not(feature = "runtime"))]
5368 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
5369 HashMap::new()
5371 }
5372
5373 #[cfg(feature = "runtime")]
5375 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
5376 let mut universal = HashMap::new();
5377
5378 universal.insert(
5391 "quicklsp".to_string(),
5392 LspLanguageConfig::Multi(vec![LspServerConfig {
5393 command: "quicklsp".to_string(),
5394 args: vec![],
5395 enabled: false,
5396 auto_start: false,
5397 process_limits: ProcessLimits::default(),
5398 initialization_options: None,
5399 env: Default::default(),
5400 language_id_overrides: Default::default(),
5401 name: Some("QuickLSP".to_string()),
5402 only_features: None,
5403 except_features: None,
5404 root_markers: vec![
5405 "Cargo.toml".to_string(),
5406 "package.json".to_string(),
5407 "go.mod".to_string(),
5408 "pyproject.toml".to_string(),
5409 "requirements.txt".to_string(),
5410 ".git".to_string(),
5411 ],
5412 }]),
5413 );
5414
5415 universal
5416 }
5417
5418 #[cfg(not(feature = "runtime"))]
5420 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
5421 HashMap::new()
5422 }
5423
5424 #[cfg(feature = "runtime")]
5425 fn populate_lsp_config(lsp: &mut HashMap<String, LspLanguageConfig>, ra_log_path: String) {
5426 lsp.insert(
5430 "rust".to_string(),
5431 LspLanguageConfig::Multi(vec![LspServerConfig {
5432 command: "rust-analyzer".to_string(),
5433 args: vec!["--log-file".to_string(), ra_log_path],
5434 enabled: true,
5435 auto_start: false,
5436 process_limits: ProcessLimits::unlimited(),
5437 initialization_options: None,
5438 env: Default::default(),
5439 language_id_overrides: Default::default(),
5440 name: None,
5441 only_features: None,
5442 except_features: None,
5443 root_markers: vec![
5444 "Cargo.toml".to_string(),
5445 "rust-project.json".to_string(),
5446 ".git".to_string(),
5447 ],
5448 }]),
5449 );
5450
5451 lsp.insert(
5453 "python".to_string(),
5454 LspLanguageConfig::Multi(vec![LspServerConfig {
5455 command: "pylsp".to_string(),
5456 args: vec![],
5457 enabled: true,
5458 auto_start: false,
5459 process_limits: ProcessLimits::default(),
5460 initialization_options: None,
5461 env: Default::default(),
5462 language_id_overrides: Default::default(),
5463 name: None,
5464 only_features: None,
5465 except_features: None,
5466 root_markers: vec![
5467 "pyproject.toml".to_string(),
5468 "setup.py".to_string(),
5469 "setup.cfg".to_string(),
5470 "pyrightconfig.json".to_string(),
5471 ".git".to_string(),
5472 ],
5473 }]),
5474 );
5475
5476 lsp.insert(
5479 "javascript".to_string(),
5480 LspLanguageConfig::Multi(vec![LspServerConfig {
5481 command: "typescript-language-server".to_string(),
5482 args: vec!["--stdio".to_string()],
5483 enabled: true,
5484 auto_start: false,
5485 process_limits: ProcessLimits::default(),
5486 initialization_options: None,
5487 env: Default::default(),
5488 language_id_overrides: HashMap::from([(
5489 "jsx".to_string(),
5490 "javascriptreact".to_string(),
5491 )]),
5492 name: None,
5493 only_features: None,
5494 except_features: None,
5495 root_markers: vec![
5496 "tsconfig.json".to_string(),
5497 "jsconfig.json".to_string(),
5498 "package.json".to_string(),
5499 ".git".to_string(),
5500 ],
5501 }]),
5502 );
5503 lsp.insert(
5504 "typescript".to_string(),
5505 LspLanguageConfig::Multi(vec![LspServerConfig {
5506 command: "typescript-language-server".to_string(),
5507 args: vec!["--stdio".to_string()],
5508 enabled: true,
5509 auto_start: false,
5510 process_limits: ProcessLimits::default(),
5511 initialization_options: None,
5512 env: Default::default(),
5513 language_id_overrides: HashMap::from([(
5514 "tsx".to_string(),
5515 "typescriptreact".to_string(),
5516 )]),
5517 name: None,
5518 only_features: None,
5519 except_features: None,
5520 root_markers: vec![
5521 "tsconfig.json".to_string(),
5522 "jsconfig.json".to_string(),
5523 "package.json".to_string(),
5524 ".git".to_string(),
5525 ],
5526 }]),
5527 );
5528
5529 lsp.insert(
5531 "html".to_string(),
5532 LspLanguageConfig::Multi(vec![LspServerConfig {
5533 command: "vscode-html-language-server".to_string(),
5534 args: vec!["--stdio".to_string()],
5535 enabled: true,
5536 auto_start: false,
5537 process_limits: ProcessLimits::default(),
5538 initialization_options: None,
5539 env: Default::default(),
5540 language_id_overrides: Default::default(),
5541 name: None,
5542 only_features: None,
5543 except_features: None,
5544 root_markers: Default::default(),
5545 }]),
5546 );
5547
5548 lsp.insert(
5550 "css".to_string(),
5551 LspLanguageConfig::Multi(vec![LspServerConfig {
5552 command: "vscode-css-language-server".to_string(),
5553 args: vec!["--stdio".to_string()],
5554 enabled: true,
5555 auto_start: false,
5556 process_limits: ProcessLimits::default(),
5557 initialization_options: None,
5558 env: Default::default(),
5559 language_id_overrides: Default::default(),
5560 name: None,
5561 only_features: None,
5562 except_features: None,
5563 root_markers: Default::default(),
5564 }]),
5565 );
5566
5567 lsp.insert(
5569 "c".to_string(),
5570 LspLanguageConfig::Multi(vec![LspServerConfig {
5571 command: "clangd".to_string(),
5572 args: vec![],
5573 enabled: true,
5574 auto_start: false,
5575 process_limits: ProcessLimits::default(),
5576 initialization_options: None,
5577 env: Default::default(),
5578 language_id_overrides: Default::default(),
5579 name: None,
5580 only_features: None,
5581 except_features: None,
5582 root_markers: vec![
5583 "compile_commands.json".to_string(),
5584 "CMakeLists.txt".to_string(),
5585 "Makefile".to_string(),
5586 ".git".to_string(),
5587 ],
5588 }]),
5589 );
5590 lsp.insert(
5591 "cpp".to_string(),
5592 LspLanguageConfig::Multi(vec![LspServerConfig {
5593 command: "clangd".to_string(),
5594 args: vec![],
5595 enabled: true,
5596 auto_start: false,
5597 process_limits: ProcessLimits::default(),
5598 initialization_options: None,
5599 env: Default::default(),
5600 language_id_overrides: Default::default(),
5601 name: None,
5602 only_features: None,
5603 except_features: None,
5604 root_markers: vec![
5605 "compile_commands.json".to_string(),
5606 "CMakeLists.txt".to_string(),
5607 "Makefile".to_string(),
5608 ".git".to_string(),
5609 ],
5610 }]),
5611 );
5612
5613 lsp.insert(
5615 "go".to_string(),
5616 LspLanguageConfig::Multi(vec![LspServerConfig {
5617 command: "gopls".to_string(),
5618 args: vec![],
5619 enabled: true,
5620 auto_start: false,
5621 process_limits: ProcessLimits::default(),
5622 initialization_options: None,
5623 env: Default::default(),
5624 language_id_overrides: Default::default(),
5625 name: None,
5626 only_features: None,
5627 except_features: None,
5628 root_markers: vec![
5629 "go.mod".to_string(),
5630 "go.work".to_string(),
5631 ".git".to_string(),
5632 ],
5633 }]),
5634 );
5635
5636 lsp.insert(
5638 "json".to_string(),
5639 LspLanguageConfig::Multi(vec![LspServerConfig {
5640 command: "vscode-json-language-server".to_string(),
5641 args: vec!["--stdio".to_string()],
5642 enabled: true,
5643 auto_start: false,
5644 process_limits: ProcessLimits::default(),
5645 initialization_options: None,
5646 env: Default::default(),
5647 language_id_overrides: Default::default(),
5648 name: None,
5649 only_features: None,
5650 except_features: None,
5651 root_markers: Default::default(),
5652 }]),
5653 );
5654
5655 lsp.insert(
5659 "jsonc".to_string(),
5660 LspLanguageConfig::Multi(vec![LspServerConfig {
5661 command: "vscode-json-language-server".to_string(),
5662 args: vec!["--stdio".to_string()],
5663 enabled: true,
5664 auto_start: false,
5665 process_limits: ProcessLimits::default(),
5666 initialization_options: None,
5667 env: Default::default(),
5668 language_id_overrides: Default::default(),
5669 name: None,
5670 only_features: None,
5671 except_features: None,
5672 root_markers: Default::default(),
5673 }]),
5674 );
5675
5676 lsp.insert(
5678 "csharp".to_string(),
5679 LspLanguageConfig::Multi(vec![LspServerConfig {
5680 command: "csharp-ls".to_string(),
5681 args: vec![],
5682 enabled: true,
5683 auto_start: false,
5684 process_limits: ProcessLimits::default(),
5685 initialization_options: None,
5686 env: Default::default(),
5687 language_id_overrides: Default::default(),
5688 name: None,
5689 only_features: None,
5690 except_features: None,
5691 root_markers: vec![
5692 "*.csproj".to_string(),
5693 "*.sln".to_string(),
5694 ".git".to_string(),
5695 ],
5696 }]),
5697 );
5698
5699 lsp.insert(
5702 "odin".to_string(),
5703 LspLanguageConfig::Multi(vec![LspServerConfig {
5704 command: "ols".to_string(),
5705 args: vec![],
5706 enabled: true,
5707 auto_start: false,
5708 process_limits: ProcessLimits::default(),
5709 initialization_options: None,
5710 env: Default::default(),
5711 language_id_overrides: Default::default(),
5712 name: None,
5713 only_features: None,
5714 except_features: None,
5715 root_markers: Default::default(),
5716 }]),
5717 );
5718
5719 lsp.insert(
5722 "zig".to_string(),
5723 LspLanguageConfig::Multi(vec![LspServerConfig {
5724 command: "zls".to_string(),
5725 args: vec![],
5726 enabled: true,
5727 auto_start: false,
5728 process_limits: ProcessLimits::default(),
5729 initialization_options: None,
5730 env: Default::default(),
5731 language_id_overrides: Default::default(),
5732 name: None,
5733 only_features: None,
5734 except_features: None,
5735 root_markers: Default::default(),
5736 }]),
5737 );
5738
5739 lsp.insert(
5742 "java".to_string(),
5743 LspLanguageConfig::Multi(vec![LspServerConfig {
5744 command: "jdtls".to_string(),
5745 args: vec![],
5746 enabled: true,
5747 auto_start: false,
5748 process_limits: ProcessLimits::default(),
5749 initialization_options: None,
5750 env: Default::default(),
5751 language_id_overrides: Default::default(),
5752 name: None,
5753 only_features: None,
5754 except_features: None,
5755 root_markers: vec![
5756 "pom.xml".to_string(),
5757 "build.gradle".to_string(),
5758 "build.gradle.kts".to_string(),
5759 ".git".to_string(),
5760 ],
5761 }]),
5762 );
5763
5764 lsp.insert(
5767 "latex".to_string(),
5768 LspLanguageConfig::Multi(vec![LspServerConfig {
5769 command: "texlab".to_string(),
5770 args: vec![],
5771 enabled: true,
5772 auto_start: false,
5773 process_limits: ProcessLimits::default(),
5774 initialization_options: None,
5775 env: Default::default(),
5776 language_id_overrides: Default::default(),
5777 name: None,
5778 only_features: None,
5779 except_features: None,
5780 root_markers: Default::default(),
5781 }]),
5782 );
5783
5784 lsp.insert(
5787 "markdown".to_string(),
5788 LspLanguageConfig::Multi(vec![LspServerConfig {
5789 command: "marksman".to_string(),
5790 args: vec!["server".to_string()],
5791 enabled: true,
5792 auto_start: false,
5793 process_limits: ProcessLimits::default(),
5794 initialization_options: None,
5795 env: Default::default(),
5796 language_id_overrides: Default::default(),
5797 name: None,
5798 only_features: None,
5799 except_features: None,
5800 root_markers: Default::default(),
5801 }]),
5802 );
5803
5804 lsp.insert(
5807 "templ".to_string(),
5808 LspLanguageConfig::Multi(vec![LspServerConfig {
5809 command: "templ".to_string(),
5810 args: vec!["lsp".to_string()],
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 "typst".to_string(),
5828 LspLanguageConfig::Multi(vec![LspServerConfig {
5829 command: "tinymist".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(
5846 "bash".to_string(),
5847 LspLanguageConfig::Multi(vec![LspServerConfig {
5848 command: "bash-language-server".to_string(),
5849 args: vec!["start".to_string()],
5850 enabled: true,
5851 auto_start: false,
5852 process_limits: ProcessLimits::default(),
5853 initialization_options: None,
5854 env: Default::default(),
5855 language_id_overrides: Default::default(),
5856 name: None,
5857 only_features: None,
5858 except_features: None,
5859 root_markers: Default::default(),
5860 }]),
5861 );
5862
5863 lsp.insert(
5866 "lua".to_string(),
5867 LspLanguageConfig::Multi(vec![LspServerConfig {
5868 command: "lua-language-server".to_string(),
5869 args: vec![],
5870 enabled: true,
5871 auto_start: false,
5872 process_limits: ProcessLimits::default(),
5873 initialization_options: None,
5874 env: Default::default(),
5875 language_id_overrides: Default::default(),
5876 name: None,
5877 only_features: None,
5878 except_features: None,
5879 root_markers: vec![
5880 ".luarc.json".to_string(),
5881 ".luarc.jsonc".to_string(),
5882 ".luacheckrc".to_string(),
5883 ".stylua.toml".to_string(),
5884 ".git".to_string(),
5885 ],
5886 }]),
5887 );
5888
5889 lsp.insert(
5891 "ruby".to_string(),
5892 LspLanguageConfig::Multi(vec![LspServerConfig {
5893 command: "solargraph".to_string(),
5894 args: vec!["stdio".to_string()],
5895 enabled: true,
5896 auto_start: false,
5897 process_limits: ProcessLimits::default(),
5898 initialization_options: None,
5899 env: Default::default(),
5900 language_id_overrides: Default::default(),
5901 name: None,
5902 only_features: None,
5903 except_features: None,
5904 root_markers: vec![
5905 "Gemfile".to_string(),
5906 ".ruby-version".to_string(),
5907 ".git".to_string(),
5908 ],
5909 }]),
5910 );
5911
5912 lsp.insert(
5915 "php".to_string(),
5916 LspLanguageConfig::Multi(vec![LspServerConfig {
5917 command: "phpactor".to_string(),
5918 args: vec!["language-server".to_string()],
5919 enabled: true,
5920 auto_start: false,
5921 process_limits: ProcessLimits::default(),
5922 initialization_options: None,
5923 env: Default::default(),
5924 language_id_overrides: Default::default(),
5925 name: None,
5926 only_features: None,
5927 except_features: None,
5928 root_markers: vec!["composer.json".to_string(), ".git".to_string()],
5929 }]),
5930 );
5931
5932 lsp.insert(
5934 "yaml".to_string(),
5935 LspLanguageConfig::Multi(vec![LspServerConfig {
5936 command: "yaml-language-server".to_string(),
5937 args: vec!["--stdio".to_string()],
5938 enabled: true,
5939 auto_start: false,
5940 process_limits: ProcessLimits::default(),
5941 initialization_options: None,
5942 env: Default::default(),
5943 language_id_overrides: Default::default(),
5944 name: None,
5945 only_features: None,
5946 except_features: None,
5947 root_markers: Default::default(),
5948 }]),
5949 );
5950
5951 lsp.insert(
5954 "toml".to_string(),
5955 LspLanguageConfig::Multi(vec![LspServerConfig {
5956 command: "taplo".to_string(),
5957 args: vec!["lsp".to_string(), "stdio".to_string()],
5958 enabled: true,
5959 auto_start: false,
5960 process_limits: ProcessLimits::default(),
5961 initialization_options: None,
5962 env: Default::default(),
5963 language_id_overrides: Default::default(),
5964 name: None,
5965 only_features: None,
5966 except_features: None,
5967 root_markers: Default::default(),
5968 }]),
5969 );
5970
5971 lsp.insert(
5974 "dart".to_string(),
5975 LspLanguageConfig::Multi(vec![LspServerConfig {
5976 command: "dart".to_string(),
5977 args: vec!["language-server".to_string(), "--protocol=lsp".to_string()],
5978 enabled: true,
5979 auto_start: false,
5980 process_limits: ProcessLimits::default(),
5981 initialization_options: None,
5982 env: Default::default(),
5983 language_id_overrides: Default::default(),
5984 name: None,
5985 only_features: None,
5986 except_features: None,
5987 root_markers: vec!["pubspec.yaml".to_string(), ".git".to_string()],
5988 }]),
5989 );
5990
5991 lsp.insert(
5994 "nushell".to_string(),
5995 LspLanguageConfig::Multi(vec![LspServerConfig {
5996 command: "nu".to_string(),
5997 args: vec!["--lsp".to_string()],
5998 enabled: true,
5999 auto_start: false,
6000 process_limits: ProcessLimits::default(),
6001 initialization_options: None,
6002 env: Default::default(),
6003 language_id_overrides: Default::default(),
6004 name: None,
6005 only_features: None,
6006 except_features: None,
6007 root_markers: Default::default(),
6008 }]),
6009 );
6010
6011 lsp.insert(
6014 "solidity".to_string(),
6015 LspLanguageConfig::Multi(vec![LspServerConfig {
6016 command: "nomicfoundation-solidity-language-server".to_string(),
6017 args: vec!["--stdio".to_string()],
6018 enabled: true,
6019 auto_start: false,
6020 process_limits: ProcessLimits::default(),
6021 initialization_options: None,
6022 env: Default::default(),
6023 language_id_overrides: Default::default(),
6024 name: None,
6025 only_features: None,
6026 except_features: None,
6027 root_markers: Default::default(),
6028 }]),
6029 );
6030
6031 lsp.insert(
6036 "terraform".to_string(),
6037 LspLanguageConfig::Multi(vec![LspServerConfig {
6038 command: "terraform-ls".to_string(),
6039 args: vec!["serve".to_string()],
6040 enabled: true,
6041 auto_start: false,
6042 process_limits: ProcessLimits::default(),
6043 initialization_options: None,
6044 env: Default::default(),
6045 language_id_overrides: Default::default(),
6046 name: None,
6047 only_features: None,
6048 except_features: None,
6049 root_markers: vec![
6050 "*.tf".to_string(),
6051 ".terraform".to_string(),
6052 ".git".to_string(),
6053 ],
6054 }]),
6055 );
6056
6057 lsp.insert(
6060 "cmake".to_string(),
6061 LspLanguageConfig::Multi(vec![LspServerConfig {
6062 command: "cmake-language-server".to_string(),
6063 args: vec![],
6064 enabled: true,
6065 auto_start: false,
6066 process_limits: ProcessLimits::default(),
6067 initialization_options: None,
6068 env: Default::default(),
6069 language_id_overrides: Default::default(),
6070 name: None,
6071 only_features: None,
6072 except_features: None,
6073 root_markers: vec!["CMakeLists.txt".to_string(), ".git".to_string()],
6074 }]),
6075 );
6076
6077 lsp.insert(
6080 "protobuf".to_string(),
6081 LspLanguageConfig::Multi(vec![LspServerConfig {
6082 command: "buf".to_string(),
6083 args: vec!["beta".to_string(), "lsp".to_string()],
6084 enabled: true,
6085 auto_start: false,
6086 process_limits: ProcessLimits::default(),
6087 initialization_options: None,
6088 env: Default::default(),
6089 language_id_overrides: Default::default(),
6090 name: None,
6091 only_features: None,
6092 except_features: None,
6093 root_markers: Default::default(),
6094 }]),
6095 );
6096
6097 lsp.insert(
6100 "graphql".to_string(),
6101 LspLanguageConfig::Multi(vec![LspServerConfig {
6102 command: "graphql-lsp".to_string(),
6103 args: vec!["server".to_string(), "-m".to_string(), "stream".to_string()],
6104 enabled: true,
6105 auto_start: false,
6106 process_limits: ProcessLimits::default(),
6107 initialization_options: None,
6108 env: Default::default(),
6109 language_id_overrides: Default::default(),
6110 name: None,
6111 only_features: None,
6112 except_features: None,
6113 root_markers: Default::default(),
6114 }]),
6115 );
6116
6117 lsp.insert(
6120 "sql".to_string(),
6121 LspLanguageConfig::Multi(vec![LspServerConfig {
6122 command: "sqls".to_string(),
6123 args: vec![],
6124 enabled: true,
6125 auto_start: false,
6126 process_limits: ProcessLimits::default(),
6127 initialization_options: None,
6128 env: Default::default(),
6129 language_id_overrides: Default::default(),
6130 name: None,
6131 only_features: None,
6132 except_features: None,
6133 root_markers: Default::default(),
6134 }]),
6135 );
6136
6137 lsp.insert(
6141 "vue".to_string(),
6142 LspLanguageConfig::Multi(vec![LspServerConfig {
6143 command: "vue-language-server".to_string(),
6144 args: vec!["--stdio".to_string()],
6145 enabled: true,
6146 auto_start: false,
6147 process_limits: ProcessLimits::default(),
6148 initialization_options: None,
6149 env: Default::default(),
6150 language_id_overrides: Default::default(),
6151 name: None,
6152 only_features: None,
6153 except_features: None,
6154 root_markers: Default::default(),
6155 }]),
6156 );
6157
6158 lsp.insert(
6160 "svelte".to_string(),
6161 LspLanguageConfig::Multi(vec![LspServerConfig {
6162 command: "svelteserver".to_string(),
6163 args: vec!["--stdio".to_string()],
6164 enabled: true,
6165 auto_start: false,
6166 process_limits: ProcessLimits::default(),
6167 initialization_options: None,
6168 env: Default::default(),
6169 language_id_overrides: Default::default(),
6170 name: None,
6171 only_features: None,
6172 except_features: None,
6173 root_markers: Default::default(),
6174 }]),
6175 );
6176
6177 lsp.insert(
6179 "astro".to_string(),
6180 LspLanguageConfig::Multi(vec![LspServerConfig {
6181 command: "astro-ls".to_string(),
6182 args: vec!["--stdio".to_string()],
6183 enabled: true,
6184 auto_start: false,
6185 process_limits: ProcessLimits::default(),
6186 initialization_options: None,
6187 env: Default::default(),
6188 language_id_overrides: Default::default(),
6189 name: None,
6190 only_features: None,
6191 except_features: None,
6192 root_markers: Default::default(),
6193 }]),
6194 );
6195
6196 lsp.insert(
6198 "tailwindcss".to_string(),
6199 LspLanguageConfig::Multi(vec![LspServerConfig {
6200 command: "tailwindcss-language-server".to_string(),
6201 args: vec!["--stdio".to_string()],
6202 enabled: true,
6203 auto_start: false,
6204 process_limits: ProcessLimits::default(),
6205 initialization_options: None,
6206 env: Default::default(),
6207 language_id_overrides: Default::default(),
6208 name: None,
6209 only_features: None,
6210 except_features: None,
6211 root_markers: Default::default(),
6212 }]),
6213 );
6214
6215 lsp.insert(
6220 "nix".to_string(),
6221 LspLanguageConfig::Multi(vec![LspServerConfig {
6222 command: "nil".to_string(),
6223 args: vec![],
6224 enabled: true,
6225 auto_start: false,
6226 process_limits: ProcessLimits::default(),
6227 initialization_options: None,
6228 env: Default::default(),
6229 language_id_overrides: Default::default(),
6230 name: None,
6231 only_features: None,
6232 except_features: None,
6233 root_markers: Default::default(),
6234 }]),
6235 );
6236
6237 lsp.insert(
6240 "kotlin".to_string(),
6241 LspLanguageConfig::Multi(vec![LspServerConfig {
6242 command: "kotlin-language-server".to_string(),
6243 args: vec![],
6244 enabled: true,
6245 auto_start: false,
6246 process_limits: ProcessLimits::default(),
6247 initialization_options: None,
6248 env: Default::default(),
6249 language_id_overrides: Default::default(),
6250 name: None,
6251 only_features: None,
6252 except_features: None,
6253 root_markers: Default::default(),
6254 }]),
6255 );
6256
6257 lsp.insert(
6259 "swift".to_string(),
6260 LspLanguageConfig::Multi(vec![LspServerConfig {
6261 command: "sourcekit-lsp".to_string(),
6262 args: vec![],
6263 enabled: true,
6264 auto_start: false,
6265 process_limits: ProcessLimits::default(),
6266 initialization_options: None,
6267 env: Default::default(),
6268 language_id_overrides: Default::default(),
6269 name: None,
6270 only_features: None,
6271 except_features: None,
6272 root_markers: Default::default(),
6273 }]),
6274 );
6275
6276 lsp.insert(
6279 "scala".to_string(),
6280 LspLanguageConfig::Multi(vec![LspServerConfig {
6281 command: "metals".to_string(),
6282 args: vec![],
6283 enabled: true,
6284 auto_start: false,
6285 process_limits: ProcessLimits::default(),
6286 initialization_options: None,
6287 env: Default::default(),
6288 language_id_overrides: Default::default(),
6289 name: None,
6290 only_features: None,
6291 except_features: None,
6292 root_markers: Default::default(),
6293 }]),
6294 );
6295
6296 lsp.insert(
6299 "elixir".to_string(),
6300 LspLanguageConfig::Multi(vec![LspServerConfig {
6301 command: "elixir-ls".to_string(),
6302 args: vec![],
6303 enabled: true,
6304 auto_start: false,
6305 process_limits: ProcessLimits::default(),
6306 initialization_options: None,
6307 env: Default::default(),
6308 language_id_overrides: Default::default(),
6309 name: None,
6310 only_features: None,
6311 except_features: None,
6312 root_markers: Default::default(),
6313 }]),
6314 );
6315
6316 lsp.insert(
6318 "erlang".to_string(),
6319 LspLanguageConfig::Multi(vec![LspServerConfig {
6320 command: "erlang_ls".to_string(),
6321 args: vec![],
6322 enabled: true,
6323 auto_start: false,
6324 process_limits: ProcessLimits::default(),
6325 initialization_options: None,
6326 env: Default::default(),
6327 language_id_overrides: Default::default(),
6328 name: None,
6329 only_features: None,
6330 except_features: None,
6331 root_markers: Default::default(),
6332 }]),
6333 );
6334
6335 lsp.insert(
6338 "haskell".to_string(),
6339 LspLanguageConfig::Multi(vec![LspServerConfig {
6340 command: "haskell-language-server-wrapper".to_string(),
6341 args: vec!["--lsp".to_string()],
6342 enabled: true,
6343 auto_start: false,
6344 process_limits: ProcessLimits::default(),
6345 initialization_options: None,
6346 env: Default::default(),
6347 language_id_overrides: Default::default(),
6348 name: None,
6349 only_features: None,
6350 except_features: None,
6351 root_markers: Default::default(),
6352 }]),
6353 );
6354
6355 lsp.insert(
6358 "ocaml".to_string(),
6359 LspLanguageConfig::Multi(vec![LspServerConfig {
6360 command: "ocamllsp".to_string(),
6361 args: vec![],
6362 enabled: true,
6363 auto_start: false,
6364 process_limits: ProcessLimits::default(),
6365 initialization_options: None,
6366 env: Default::default(),
6367 language_id_overrides: Default::default(),
6368 name: None,
6369 only_features: None,
6370 except_features: None,
6371 root_markers: Default::default(),
6372 }]),
6373 );
6374
6375 lsp.insert(
6378 "clojure".to_string(),
6379 LspLanguageConfig::Multi(vec![LspServerConfig {
6380 command: "clojure-lsp".to_string(),
6381 args: vec![],
6382 enabled: true,
6383 auto_start: false,
6384 process_limits: ProcessLimits::default(),
6385 initialization_options: None,
6386 env: Default::default(),
6387 language_id_overrides: Default::default(),
6388 name: None,
6389 only_features: None,
6390 except_features: None,
6391 root_markers: Default::default(),
6392 }]),
6393 );
6394
6395 lsp.insert(
6398 "r".to_string(),
6399 LspLanguageConfig::Multi(vec![LspServerConfig {
6400 command: "R".to_string(),
6401 args: vec![
6402 "--vanilla".to_string(),
6403 "-e".to_string(),
6404 "languageserver::run()".to_string(),
6405 ],
6406 enabled: true,
6407 auto_start: false,
6408 process_limits: ProcessLimits::default(),
6409 initialization_options: None,
6410 env: Default::default(),
6411 language_id_overrides: Default::default(),
6412 name: None,
6413 only_features: None,
6414 except_features: None,
6415 root_markers: Default::default(),
6416 }]),
6417 );
6418
6419 lsp.insert(
6422 "julia".to_string(),
6423 LspLanguageConfig::Multi(vec![LspServerConfig {
6424 command: "julia".to_string(),
6425 args: vec![
6426 "--startup-file=no".to_string(),
6427 "--history-file=no".to_string(),
6428 "-e".to_string(),
6429 "using LanguageServer; runserver()".to_string(),
6430 ],
6431 enabled: true,
6432 auto_start: false,
6433 process_limits: ProcessLimits::default(),
6434 initialization_options: None,
6435 env: Default::default(),
6436 language_id_overrides: Default::default(),
6437 name: None,
6438 only_features: None,
6439 except_features: None,
6440 root_markers: Default::default(),
6441 }]),
6442 );
6443
6444 lsp.insert(
6447 "perl".to_string(),
6448 LspLanguageConfig::Multi(vec![LspServerConfig {
6449 command: "perlnavigator".to_string(),
6450 args: vec!["--stdio".to_string()],
6451 enabled: true,
6452 auto_start: false,
6453 process_limits: ProcessLimits::default(),
6454 initialization_options: None,
6455 env: Default::default(),
6456 language_id_overrides: Default::default(),
6457 name: None,
6458 only_features: None,
6459 except_features: None,
6460 root_markers: Default::default(),
6461 }]),
6462 );
6463
6464 lsp.insert(
6467 "nim".to_string(),
6468 LspLanguageConfig::Multi(vec![LspServerConfig {
6469 command: "nimlangserver".to_string(),
6470 args: vec![],
6471 enabled: true,
6472 auto_start: false,
6473 process_limits: ProcessLimits::default(),
6474 initialization_options: None,
6475 env: Default::default(),
6476 language_id_overrides: Default::default(),
6477 name: None,
6478 only_features: None,
6479 except_features: None,
6480 root_markers: Default::default(),
6481 }]),
6482 );
6483
6484 lsp.insert(
6486 "gleam".to_string(),
6487 LspLanguageConfig::Multi(vec![LspServerConfig {
6488 command: "gleam".to_string(),
6489 args: vec!["lsp".to_string()],
6490 enabled: true,
6491 auto_start: false,
6492 process_limits: ProcessLimits::default(),
6493 initialization_options: None,
6494 env: Default::default(),
6495 language_id_overrides: Default::default(),
6496 name: None,
6497 only_features: None,
6498 except_features: None,
6499 root_markers: Default::default(),
6500 }]),
6501 );
6502
6503 lsp.insert(
6506 "racket".to_string(),
6507 LspLanguageConfig::Multi(vec![LspServerConfig {
6508 command: "racket-langserver".to_string(),
6509 args: vec![],
6510 enabled: true,
6511 auto_start: false,
6512 process_limits: ProcessLimits::default(),
6513 initialization_options: None,
6514 env: Default::default(),
6515 language_id_overrides: Default::default(),
6516 name: None,
6517 only_features: None,
6518 except_features: None,
6519 root_markers: vec!["info.rkt".to_string(), ".git".to_string()],
6520 }]),
6521 );
6522
6523 lsp.insert(
6526 "fsharp".to_string(),
6527 LspLanguageConfig::Multi(vec![LspServerConfig {
6528 command: "fsautocomplete".to_string(),
6529 args: vec!["--adaptive-lsp-server-enabled".to_string()],
6530 enabled: true,
6531 auto_start: false,
6532 process_limits: ProcessLimits::default(),
6533 initialization_options: None,
6534 env: Default::default(),
6535 language_id_overrides: Default::default(),
6536 name: None,
6537 only_features: None,
6538 except_features: None,
6539 root_markers: Default::default(),
6540 }]),
6541 );
6542
6543 let svls_config = LspServerConfig {
6547 command: "svls".to_string(),
6548 args: vec![],
6549 enabled: true,
6550 auto_start: false,
6551 process_limits: ProcessLimits::default(),
6552 initialization_options: None,
6553 env: Default::default(),
6554 language_id_overrides: Default::default(),
6555 name: None,
6556 only_features: None,
6557 except_features: None,
6558 root_markers: vec![".svls.toml".to_string(), ".git".to_string()],
6559 };
6560 lsp.insert(
6561 "verilog".to_string(),
6562 LspLanguageConfig::Multi(vec![svls_config.clone()]),
6563 );
6564 lsp.insert(
6565 "systemverilog".to_string(),
6566 LspLanguageConfig::Multi(vec![svls_config]),
6567 );
6568 }
6569 pub fn validate(&self) -> Result<(), ConfigError> {
6570 if self.editor.tab_size == 0 {
6572 return Err(ConfigError::ValidationError(
6573 "tab_size must be greater than 0".to_string(),
6574 ));
6575 }
6576
6577 if self.editor.scroll_offset > 100 {
6579 return Err(ConfigError::ValidationError(
6580 "scroll_offset must be <= 100".to_string(),
6581 ));
6582 }
6583
6584 for binding in &self.keybindings {
6586 if binding.key.is_empty() {
6587 return Err(ConfigError::ValidationError(
6588 "keybinding key cannot be empty".to_string(),
6589 ));
6590 }
6591 if binding.action.is_empty() {
6592 return Err(ConfigError::ValidationError(
6593 "keybinding action cannot be empty".to_string(),
6594 ));
6595 }
6596 }
6597
6598 Ok(())
6599 }
6600}
6601
6602#[derive(Debug)]
6604pub enum ConfigError {
6605 IoError(String),
6606 ParseError(String),
6607 SerializeError(String),
6608 ValidationError(String),
6609}
6610
6611impl std::fmt::Display for ConfigError {
6612 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6613 match self {
6614 Self::IoError(msg) => write!(f, "IO error: {msg}"),
6615 Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
6616 Self::SerializeError(msg) => write!(f, "Serialize error: {msg}"),
6617 Self::ValidationError(msg) => write!(f, "Validation error: {msg}"),
6618 }
6619 }
6620}
6621
6622impl std::error::Error for ConfigError {}
6623
6624#[cfg(test)]
6625mod tests {
6626 use super::*;
6627
6628 #[test]
6629 fn test_file_explorer_width_default_is_percent_30() {
6630 let cfg = FileExplorerConfig::default();
6631 assert_eq!(cfg.width, ExplorerWidth::Percent(30));
6632 }
6633
6634 #[test]
6635 fn test_tree_indicators_defaults_preserve_current_glyphs() {
6636 let cfg = FileExplorerConfig::default();
6640 assert_eq!(cfg.tree_indicator_collapsed, ">");
6641 assert_eq!(cfg.tree_indicator_expanded, "▼");
6642 }
6643
6644 #[test]
6645 fn test_tree_indicators_can_be_overridden_from_json() {
6646 let cfg: FileExplorerConfig = serde_json::from_str(
6647 r#"{"tree_indicator_collapsed": "▸", "tree_indicator_expanded": "▾"}"#,
6648 )
6649 .unwrap();
6650 assert_eq!(cfg.tree_indicator_collapsed, "▸");
6651 assert_eq!(cfg.tree_indicator_expanded, "▾");
6652 }
6653
6654 #[test]
6655 fn test_tree_indicators_partial_override_keeps_default_for_unset() {
6656 let cfg: FileExplorerConfig =
6657 serde_json::from_str(r#"{"tree_indicator_collapsed": "+"}"#).unwrap();
6658 assert_eq!(cfg.tree_indicator_collapsed, "+");
6659 assert_eq!(cfg.tree_indicator_expanded, "▼");
6661 }
6662
6663 #[test]
6666 fn test_width_accepts_legacy_float_fraction() {
6667 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": 0.3}"#).unwrap();
6668 assert_eq!(cfg.width, ExplorerWidth::Percent(30));
6669 }
6670
6671 #[test]
6672 fn test_width_accepts_bare_integer_as_percent() {
6673 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": 42}"#).unwrap();
6675 assert_eq!(cfg.width, ExplorerWidth::Percent(42));
6676 }
6677
6678 #[test]
6679 fn test_width_accepts_percent_string() {
6680 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "75%"}"#).unwrap();
6681 assert_eq!(cfg.width, ExplorerWidth::Percent(75));
6682 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "42 %"}"#).unwrap();
6684 assert_eq!(cfg.width, ExplorerWidth::Percent(42));
6685 }
6686
6687 #[test]
6688 fn test_width_accepts_columns_string() {
6689 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "24"}"#).unwrap();
6690 assert_eq!(cfg.width, ExplorerWidth::Columns(24));
6691 }
6692
6693 #[test]
6694 fn test_width_rejects_percent_over_100() {
6695 let err = serde_json::from_str::<FileExplorerConfig>(r#"{"width": "150%"}"#)
6696 .expect_err("percent > 100 should be rejected");
6697 assert!(err.to_string().contains("100"), "{err}");
6698 }
6699
6700 #[test]
6701 fn test_width_rejects_integer_over_100() {
6702 let err = serde_json::from_str::<FileExplorerConfig>(r#"{"width": 150}"#)
6705 .expect_err("bare integer > 100 should be rejected as percent");
6706 assert!(err.to_string().contains("percent") || err.to_string().contains("100"));
6707 }
6708
6709 #[test]
6710 fn test_width_rejects_garbage_string() {
6711 serde_json::from_str::<FileExplorerConfig>(r#"{"width": "big"}"#)
6712 .expect_err("non-numeric string should be rejected");
6713 }
6714
6715 #[test]
6718 fn test_width_serializes_percent_as_string_with_suffix() {
6719 let cfg = FileExplorerConfig {
6720 width: ExplorerWidth::Percent(30),
6721 ..Default::default()
6722 };
6723 let json = serde_json::to_value(&cfg).unwrap();
6724 assert_eq!(json["width"], serde_json::json!("30%"));
6725 }
6726
6727 #[test]
6728 fn test_width_serializes_columns_as_string_without_suffix() {
6729 let cfg = FileExplorerConfig {
6730 width: ExplorerWidth::Columns(24),
6731 ..Default::default()
6732 };
6733 let json = serde_json::to_value(&cfg).unwrap();
6734 assert_eq!(json["width"], serde_json::json!("24"));
6735 }
6736
6737 #[test]
6738 fn test_width_round_trip_both_variants() {
6739 for value in [ExplorerWidth::Percent(17), ExplorerWidth::Columns(42)] {
6740 let cfg = FileExplorerConfig {
6741 width: value,
6742 ..Default::default()
6743 };
6744 let json = serde_json::to_string(&cfg).unwrap();
6745 let restored: FileExplorerConfig = serde_json::from_str(&json).unwrap();
6746 assert_eq!(restored.width, value, "round trip failed for {:?}", value);
6747 }
6748 }
6749
6750 #[test]
6753 fn test_to_cols_percent() {
6754 assert_eq!(ExplorerWidth::Percent(30).to_cols(100), 30);
6755 assert_eq!(ExplorerWidth::Percent(25).to_cols(120), 30);
6756 assert_eq!(ExplorerWidth::Percent(30).to_cols(0), 0);
6758 assert_eq!(ExplorerWidth::Percent(100).to_cols(200), 200);
6759 }
6760
6761 #[test]
6762 fn test_to_cols_columns_clamps_to_terminal() {
6763 assert_eq!(ExplorerWidth::Columns(24).to_cols(100), 24);
6764 assert_eq!(ExplorerWidth::Columns(999).to_cols(80), 80);
6765 }
6766
6767 #[test]
6770 fn test_to_cols_enforces_min_width() {
6771 assert_eq!(
6773 ExplorerWidth::Columns(0).to_cols(100),
6774 ExplorerWidth::MIN_COLS
6775 );
6776 assert_eq!(
6777 ExplorerWidth::Columns(1).to_cols(100),
6778 ExplorerWidth::MIN_COLS
6779 );
6780 assert_eq!(
6781 ExplorerWidth::Columns(4).to_cols(100),
6782 ExplorerWidth::MIN_COLS
6783 );
6784 assert_eq!(
6785 ExplorerWidth::Percent(0).to_cols(100),
6786 ExplorerWidth::MIN_COLS
6787 );
6788 assert_eq!(
6790 ExplorerWidth::Percent(3).to_cols(100),
6791 ExplorerWidth::MIN_COLS
6792 );
6793 assert_eq!(
6795 ExplorerWidth::Columns(ExplorerWidth::MIN_COLS + 1).to_cols(100),
6796 ExplorerWidth::MIN_COLS + 1
6797 );
6798 }
6799
6800 #[test]
6803 fn test_to_cols_min_floor_yields_to_narrow_terminal() {
6804 assert_eq!(ExplorerWidth::Columns(10).to_cols(3), 3);
6805 assert_eq!(ExplorerWidth::Percent(100).to_cols(2), 2);
6806 assert_eq!(ExplorerWidth::Columns(1).to_cols(0), 0);
6807 }
6808
6809 #[test]
6812 fn test_load_from_file_accepts_legacy_float_fraction_width() {
6813 let dir = tempfile::tempdir().unwrap();
6814 let path = dir.path().join("config.json");
6815 std::fs::write(&path, r#"{"file_explorer":{"width":0.25}}"#).unwrap();
6816 let cfg = Config::load_from_file(&path).expect("legacy float fraction must still load");
6817 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Percent(25));
6818 }
6819
6820 #[test]
6821 fn test_load_from_file_accepts_columns_string_width() {
6822 let dir = tempfile::tempdir().unwrap();
6823 let path = dir.path().join("config.json");
6824 std::fs::write(&path, r#"{"file_explorer":{"width":"42"}}"#).unwrap();
6825 let cfg = Config::load_from_file(&path).unwrap();
6826 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Columns(42));
6827 }
6828
6829 #[test]
6830 fn test_load_from_file_accepts_percent_string_width() {
6831 let dir = tempfile::tempdir().unwrap();
6832 let path = dir.path().join("config.json");
6833 std::fs::write(&path, r#"{"file_explorer":{"width":"55%"}}"#).unwrap();
6834 let cfg = Config::load_from_file(&path).unwrap();
6835 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Percent(55));
6836 }
6837
6838 #[test]
6839 fn test_default_config() {
6840 let config = Config::default();
6841 assert_eq!(config.editor.tab_size, 4);
6842 assert!(config.editor.line_numbers);
6843 assert!(config.editor.syntax_highlighting);
6844 assert!(config.keybindings.is_empty());
6847 let resolved = config.resolve_keymap(&config.active_keybinding_map);
6849 assert!(!resolved.is_empty());
6850 }
6851
6852 #[test]
6853 fn test_all_builtin_keymaps_loadable() {
6854 for name in KeybindingMapName::BUILTIN_OPTIONS {
6855 let keymap = Config::load_builtin_keymap(name);
6856 assert!(keymap.is_some(), "Failed to load builtin keymap '{}'", name);
6857 }
6858 }
6859
6860 #[test]
6861 fn test_config_validation() {
6862 let mut config = Config::default();
6863 assert!(config.validate().is_ok());
6864
6865 config.editor.tab_size = 0;
6866 assert!(config.validate().is_err());
6867 }
6868
6869 #[test]
6870 fn test_macos_keymap_inherits_enter_bindings() {
6871 let config = Config::default();
6872 let bindings = config.resolve_keymap("macos");
6873
6874 let enter_bindings: Vec<_> = bindings.iter().filter(|b| b.key == "Enter").collect();
6875 assert!(
6876 !enter_bindings.is_empty(),
6877 "macos keymap should inherit Enter bindings from default, got {} Enter bindings",
6878 enter_bindings.len()
6879 );
6880 let has_insert_newline = enter_bindings.iter().any(|b| b.action == "insert_newline");
6882 assert!(
6883 has_insert_newline,
6884 "macos keymap should have insert_newline action for Enter key"
6885 );
6886 }
6887
6888 #[test]
6889 fn test_config_serialize_deserialize() {
6890 let config = Config::default();
6892
6893 let json = serde_json::to_string_pretty(&config).unwrap();
6895
6896 let loaded: Config = serde_json::from_str(&json).unwrap();
6898
6899 assert_eq!(config.editor.tab_size, loaded.editor.tab_size);
6900 assert_eq!(config.theme, loaded.theme);
6901 }
6902
6903 #[test]
6904 fn test_config_with_custom_keybinding() {
6905 let json = r#"{
6906 "editor": {
6907 "tab_size": 2
6908 },
6909 "keybindings": [
6910 {
6911 "key": "x",
6912 "modifiers": ["ctrl", "shift"],
6913 "action": "custom_action",
6914 "args": {},
6915 "when": null
6916 }
6917 ]
6918 }"#;
6919
6920 let config: Config = serde_json::from_str(json).unwrap();
6921 assert_eq!(config.editor.tab_size, 2);
6922 assert_eq!(config.keybindings.len(), 1);
6923 assert_eq!(config.keybindings[0].key, "x");
6924 assert_eq!(config.keybindings[0].modifiers.len(), 2);
6925 }
6926
6927 #[test]
6928 fn test_sparse_config_merges_with_defaults() {
6929 let temp_dir = tempfile::tempdir().unwrap();
6931 let config_path = temp_dir.path().join("config.json");
6932
6933 let sparse_config = r#"{
6935 "lsp": {
6936 "rust": {
6937 "command": "custom-rust-analyzer",
6938 "args": ["--custom-arg"]
6939 }
6940 }
6941 }"#;
6942 std::fs::write(&config_path, sparse_config).unwrap();
6943
6944 let loaded = Config::load_from_file(&config_path).unwrap();
6946
6947 assert!(loaded.lsp.contains_key("rust"));
6949 assert_eq!(
6950 loaded.lsp["rust"].as_slice()[0].command,
6951 "custom-rust-analyzer".to_string()
6952 );
6953
6954 assert!(
6956 loaded.lsp.contains_key("python"),
6957 "python LSP should be merged from defaults"
6958 );
6959 assert!(
6960 loaded.lsp.contains_key("typescript"),
6961 "typescript LSP should be merged from defaults"
6962 );
6963 assert!(
6964 loaded.lsp.contains_key("javascript"),
6965 "javascript LSP should be merged from defaults"
6966 );
6967
6968 assert!(loaded.languages.contains_key("rust"));
6970 assert!(loaded.languages.contains_key("python"));
6971 assert!(loaded.languages.contains_key("typescript"));
6972 }
6973
6974 #[test]
6975 fn test_empty_config_gets_all_defaults() {
6976 let temp_dir = tempfile::tempdir().unwrap();
6977 let config_path = temp_dir.path().join("config.json");
6978
6979 std::fs::write(&config_path, "{}").unwrap();
6981
6982 let loaded = Config::load_from_file(&config_path).unwrap();
6983 let defaults = Config::default();
6984
6985 assert_eq!(loaded.lsp.len(), defaults.lsp.len());
6987
6988 assert_eq!(loaded.languages.len(), defaults.languages.len());
6990 }
6991
6992 #[test]
6993 fn test_dynamic_submenu_expansion() {
6994 let temp_dir = tempfile::tempdir().unwrap();
6996 let themes_dir = temp_dir.path().to_path_buf();
6997
6998 let dynamic = MenuItem::DynamicSubmenu {
6999 label: "Test".to_string(),
7000 source: "copy_with_theme".to_string(),
7001 };
7002
7003 let expanded = dynamic.expand_dynamic(&themes_dir);
7004
7005 match expanded {
7007 MenuItem::Submenu { label, items } => {
7008 assert_eq!(label, "Test");
7009 let loader = crate::view::theme::ThemeLoader::embedded_only();
7011 let registry = loader.load_all(&[]);
7012 assert_eq!(items.len(), registry.len());
7013
7014 for (item, theme_info) in items.iter().zip(registry.list().iter()) {
7016 match item {
7017 MenuItem::Action {
7018 label,
7019 action,
7020 args,
7021 ..
7022 } => {
7023 assert_eq!(label, &theme_info.name);
7024 assert_eq!(action, "copy_with_theme");
7025 assert_eq!(
7026 args.get("theme").and_then(|v| v.as_str()),
7027 Some(theme_info.name.as_str())
7028 );
7029 }
7030 _ => panic!("Expected Action item"),
7031 }
7032 }
7033 }
7034 _ => panic!("Expected Submenu after expansion"),
7035 }
7036 }
7037
7038 #[test]
7039 fn test_non_dynamic_item_unchanged() {
7040 let temp_dir = tempfile::tempdir().unwrap();
7042 let themes_dir = temp_dir.path();
7043
7044 let action = MenuItem::Action {
7045 label: "Test".to_string(),
7046 action: "test".to_string(),
7047 args: HashMap::new(),
7048 when: None,
7049 checkbox: None,
7050 };
7051
7052 let expanded = action.expand_dynamic(themes_dir);
7053 match expanded {
7054 MenuItem::Action { label, action, .. } => {
7055 assert_eq!(label, "Test");
7056 assert_eq!(action, "test");
7057 }
7058 _ => panic!("Action should remain Action after expand_dynamic"),
7059 }
7060 }
7061
7062 #[test]
7063 fn test_buffer_config_uses_global_defaults() {
7064 let config = Config::default();
7065 let buffer_config = BufferConfig::resolve(&config, None);
7066
7067 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
7068 assert_eq!(buffer_config.auto_indent, config.editor.auto_indent);
7069 assert!(!buffer_config.use_tabs); assert!(buffer_config.whitespace.any_tabs()); assert!(buffer_config.formatter.is_none());
7072 assert!(!buffer_config.format_on_save);
7073 }
7074
7075 #[test]
7076 fn test_buffer_config_applies_language_overrides() {
7077 let mut config = Config::default();
7078
7079 config.languages.insert(
7081 "go".to_string(),
7082 LanguageConfig {
7083 extensions: vec!["go".to_string()],
7084 filenames: vec![],
7085 grammar: "go".to_string(),
7086 comment_prefix: Some("//".to_string()),
7087 auto_indent: true,
7088 auto_close: None,
7089 auto_surround: None,
7090 textmate_grammar: None,
7091 show_whitespace_tabs: false, line_wrap: None,
7093 wrap_column: None,
7094 page_view: None,
7095 page_width: None,
7096 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
7099 command: "gofmt".to_string(),
7100 args: vec![],
7101 stdin: true,
7102 timeout_ms: 10000,
7103 }),
7104 format_on_save: true,
7105 on_save: vec![],
7106 word_characters: None,
7107 },
7108 );
7109
7110 let buffer_config = BufferConfig::resolve(&config, Some("go"));
7111
7112 assert_eq!(buffer_config.tab_size, 8);
7113 assert!(buffer_config.use_tabs);
7114 assert!(!buffer_config.whitespace.any_tabs()); assert!(buffer_config.format_on_save);
7116 assert!(buffer_config.formatter.is_some());
7117 assert_eq!(buffer_config.formatter.as_ref().unwrap().command, "gofmt");
7118 }
7119
7120 #[test]
7121 fn test_buffer_config_unknown_language_uses_global() {
7122 let config = Config::default();
7123 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
7124
7125 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
7127 assert!(!buffer_config.use_tabs);
7128 }
7129
7130 #[test]
7131 fn test_buffer_config_per_language_line_wrap() {
7132 let mut config = Config::default();
7133 config.editor.line_wrap = false;
7134
7135 config.languages.insert(
7137 "markdown".to_string(),
7138 LanguageConfig {
7139 extensions: vec!["md".to_string()],
7140 line_wrap: Some(true),
7141 ..Default::default()
7142 },
7143 );
7144
7145 let md_config = BufferConfig::resolve(&config, Some("markdown"));
7147 assert!(md_config.line_wrap, "Markdown should have line_wrap=true");
7148
7149 let other_config = BufferConfig::resolve(&config, Some("rust"));
7151 assert!(
7152 !other_config.line_wrap,
7153 "Non-configured languages should use global line_wrap=false"
7154 );
7155
7156 let no_lang_config = BufferConfig::resolve(&config, None);
7158 assert!(
7159 !no_lang_config.line_wrap,
7160 "No language should use global line_wrap=false"
7161 );
7162 }
7163
7164 #[test]
7165 fn test_buffer_config_per_language_wrap_column() {
7166 let mut config = Config::default();
7167 config.editor.wrap_column = Some(120);
7168
7169 config.languages.insert(
7171 "markdown".to_string(),
7172 LanguageConfig {
7173 extensions: vec!["md".to_string()],
7174 wrap_column: Some(80),
7175 ..Default::default()
7176 },
7177 );
7178
7179 let md_config = BufferConfig::resolve(&config, Some("markdown"));
7181 assert_eq!(md_config.wrap_column, Some(80));
7182
7183 let other_config = BufferConfig::resolve(&config, Some("rust"));
7185 assert_eq!(other_config.wrap_column, Some(120));
7186
7187 let no_lang_config = BufferConfig::resolve(&config, None);
7189 assert_eq!(no_lang_config.wrap_column, Some(120));
7190 }
7191
7192 #[test]
7193 fn test_buffer_config_indent_string() {
7194 let config = Config::default();
7195
7196 let spaces_config = BufferConfig::resolve(&config, None);
7198 assert_eq!(spaces_config.indent_string(), " "); let mut config_with_tabs = Config::default();
7202 config_with_tabs.languages.insert(
7203 "makefile".to_string(),
7204 LanguageConfig {
7205 use_tabs: Some(true),
7206 tab_size: Some(8),
7207 ..Default::default()
7208 },
7209 );
7210 let tabs_config = BufferConfig::resolve(&config_with_tabs, Some("makefile"));
7211 assert_eq!(tabs_config.indent_string(), "\t");
7212 }
7213
7214 #[test]
7215 fn test_buffer_config_global_use_tabs_inherited() {
7216 let mut config = Config::default();
7219 config.editor.use_tabs = true;
7220
7221 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
7223 assert!(buffer_config.use_tabs);
7224
7225 let buffer_config = BufferConfig::resolve(&config, None);
7227 assert!(buffer_config.use_tabs);
7228
7229 config.languages.insert(
7231 "python".to_string(),
7232 LanguageConfig {
7233 use_tabs: Some(false),
7234 ..Default::default()
7235 },
7236 );
7237 let buffer_config = BufferConfig::resolve(&config, Some("python"));
7238 assert!(!buffer_config.use_tabs);
7239
7240 config.languages.insert(
7242 "rust".to_string(),
7243 LanguageConfig {
7244 use_tabs: None,
7245 ..Default::default()
7246 },
7247 );
7248 let buffer_config = BufferConfig::resolve(&config, Some("rust"));
7249 assert!(buffer_config.use_tabs);
7250 }
7251
7252 #[test]
7258 #[cfg(feature = "runtime")]
7259 fn test_lsp_languages_have_language_config() {
7260 let config = Config::default();
7261 let exceptions = ["tailwindcss"];
7262 for lsp_key in config.lsp.keys() {
7263 if exceptions.contains(&lsp_key.as_str()) {
7264 continue;
7265 }
7266 assert!(
7267 config.languages.contains_key(lsp_key),
7268 "LSP config key '{}' has no matching entry in default_languages(). \
7269 Add a LanguageConfig with the correct file extensions so detect_language() \
7270 can map files to this language.",
7271 lsp_key
7272 );
7273 }
7274 }
7275
7276 #[test]
7277 #[cfg(feature = "runtime")]
7278 fn test_default_config_has_quicklsp_in_universal_lsp() {
7279 let config = Config::default();
7280 assert!(
7281 config.universal_lsp.contains_key("quicklsp"),
7282 "Default config should contain quicklsp in universal_lsp"
7283 );
7284 let quicklsp = &config.universal_lsp["quicklsp"];
7285 let server = &quicklsp.as_slice()[0];
7286 assert_eq!(server.command, "quicklsp");
7287 assert!(!server.enabled, "quicklsp should be disabled by default");
7288 assert_eq!(server.name.as_deref(), Some("QuickLSP"));
7289 assert!(
7293 server.only_features.is_none(),
7294 "quicklsp must not default to a feature whitelist"
7295 );
7296 assert!(server.except_features.is_none());
7297 }
7298
7299 #[test]
7300 fn test_empty_config_preserves_universal_lsp_defaults() {
7301 let temp_dir = tempfile::tempdir().unwrap();
7302 let config_path = temp_dir.path().join("config.json");
7303
7304 std::fs::write(&config_path, "{}").unwrap();
7306
7307 let loaded = Config::load_from_file(&config_path).unwrap();
7308 let defaults = Config::default();
7309
7310 assert_eq!(
7312 loaded.universal_lsp.len(),
7313 defaults.universal_lsp.len(),
7314 "Empty config should preserve all default universal_lsp entries"
7315 );
7316 }
7317
7318 #[test]
7319 fn test_universal_lsp_config_merges_with_defaults() {
7320 let temp_dir = tempfile::tempdir().unwrap();
7321 let config_path = temp_dir.path().join("config.json");
7322
7323 let config_json = r#"{
7325 "universal_lsp": {
7326 "quicklsp": {
7327 "enabled": true
7328 }
7329 }
7330 }"#;
7331 std::fs::write(&config_path, config_json).unwrap();
7332
7333 let loaded = Config::load_from_file(&config_path).unwrap();
7334
7335 assert!(loaded.universal_lsp.contains_key("quicklsp"));
7337 let server = &loaded.universal_lsp["quicklsp"].as_slice()[0];
7338 assert!(server.enabled, "User override should enable quicklsp");
7339 assert_eq!(
7341 server.command, "quicklsp",
7342 "Default command should be merged when not specified by user"
7343 );
7344 }
7345
7346 #[test]
7347 fn test_universal_lsp_custom_server_added() {
7348 let temp_dir = tempfile::tempdir().unwrap();
7349 let config_path = temp_dir.path().join("config.json");
7350
7351 let config_json = r#"{
7353 "universal_lsp": {
7354 "my-custom-server": {
7355 "command": "my-server",
7356 "enabled": true,
7357 "auto_start": true
7358 }
7359 }
7360 }"#;
7361 std::fs::write(&config_path, config_json).unwrap();
7362
7363 let loaded = Config::load_from_file(&config_path).unwrap();
7364
7365 assert!(
7367 loaded.universal_lsp.contains_key("my-custom-server"),
7368 "Custom universal server should be loaded"
7369 );
7370 let server = &loaded.universal_lsp["my-custom-server"].as_slice()[0];
7371 assert_eq!(server.command, "my-server");
7372 assert!(server.enabled);
7373 assert!(server.auto_start);
7374
7375 assert!(
7377 loaded.universal_lsp.contains_key("quicklsp"),
7378 "Default quicklsp should be merged from defaults"
7379 );
7380 }
7381}