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 "c3".to_string(),
3928 LanguageConfig {
3929 extensions: vec!["c3".to_string(), "c3i".to_string(), "c3t".to_string()],
3930 filenames: vec![],
3931 grammar: "c3".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 "java".to_string(),
3953 LanguageConfig {
3954 extensions: vec!["java".to_string()],
3955 filenames: vec![],
3956 grammar: "java".to_string(),
3957 comment_prefix: Some("//".to_string()),
3958 auto_indent: true,
3959 auto_close: None,
3960 auto_surround: None,
3961 textmate_grammar: None,
3962 show_whitespace_tabs: true,
3963 line_wrap: None,
3964 wrap_column: None,
3965 page_view: None,
3966 page_width: None,
3967 use_tabs: None,
3968 tab_size: None,
3969 formatter: None,
3970 format_on_save: false,
3971 on_save: vec![],
3972 word_characters: None,
3973 },
3974 );
3975
3976 languages.insert(
3977 "latex".to_string(),
3978 LanguageConfig {
3979 extensions: vec![
3980 "tex".to_string(),
3981 "latex".to_string(),
3982 "ltx".to_string(),
3983 "sty".to_string(),
3984 "cls".to_string(),
3985 "bib".to_string(),
3986 ],
3987 filenames: vec![],
3988 grammar: "latex".to_string(),
3989 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(
4009 "templ".to_string(),
4010 LanguageConfig {
4011 extensions: vec!["templ".to_string()],
4012 filenames: vec![],
4013 grammar: "go".to_string(), comment_prefix: Some("//".to_string()),
4015 auto_indent: true,
4016 auto_close: None,
4017 auto_surround: None,
4018 textmate_grammar: None,
4019 show_whitespace_tabs: true,
4020 line_wrap: None,
4021 wrap_column: None,
4022 page_view: None,
4023 page_width: None,
4024 use_tabs: None,
4025 tab_size: None,
4026 formatter: None,
4027 format_on_save: false,
4028 on_save: vec![],
4029 word_characters: None,
4030 },
4031 );
4032
4033 languages.insert(
4035 "git-rebase".to_string(),
4036 LanguageConfig {
4037 extensions: vec![],
4038 filenames: vec!["git-rebase-todo".to_string()],
4039 grammar: "Git Rebase Todo".to_string(),
4040 comment_prefix: Some("#".to_string()),
4041 auto_indent: false,
4042 auto_close: None,
4043 auto_surround: None,
4044 textmate_grammar: None,
4045 show_whitespace_tabs: true,
4046 line_wrap: None,
4047 wrap_column: None,
4048 page_view: None,
4049 page_width: None,
4050 use_tabs: None,
4051 tab_size: None,
4052 formatter: None,
4053 format_on_save: false,
4054 on_save: vec![],
4055 word_characters: None,
4056 },
4057 );
4058
4059 languages.insert(
4060 "git-commit".to_string(),
4061 LanguageConfig {
4062 extensions: vec![],
4063 filenames: vec![
4064 "COMMIT_EDITMSG".to_string(),
4065 "MERGE_MSG".to_string(),
4066 "SQUASH_MSG".to_string(),
4067 "TAG_EDITMSG".to_string(),
4068 ],
4069 grammar: "Git Commit Message".to_string(),
4070 comment_prefix: Some("#".to_string()),
4071 auto_indent: false,
4072 auto_close: None,
4073 auto_surround: None,
4074 textmate_grammar: None,
4075 show_whitespace_tabs: true,
4076 line_wrap: None,
4077 wrap_column: None,
4078 page_view: None,
4079 page_width: None,
4080 use_tabs: None,
4081 tab_size: None,
4082 formatter: None,
4083 format_on_save: false,
4084 on_save: vec![],
4085 word_characters: None,
4086 },
4087 );
4088
4089 languages.insert(
4090 "gitignore".to_string(),
4091 LanguageConfig {
4092 extensions: vec!["gitignore".to_string()],
4093 filenames: vec![
4094 ".gitignore".to_string(),
4095 ".dockerignore".to_string(),
4096 ".npmignore".to_string(),
4097 ".hgignore".to_string(),
4098 ],
4099 grammar: "Gitignore".to_string(),
4100 comment_prefix: Some("#".to_string()),
4101 auto_indent: false,
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 "gitconfig".to_string(),
4121 LanguageConfig {
4122 extensions: vec!["gitconfig".to_string()],
4123 filenames: vec![".gitconfig".to_string(), ".gitmodules".to_string()],
4124 grammar: "Git Config".to_string(),
4125 comment_prefix: Some("#".to_string()),
4126 auto_indent: true,
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 "gitattributes".to_string(),
4146 LanguageConfig {
4147 extensions: vec!["gitattributes".to_string()],
4148 filenames: vec![".gitattributes".to_string()],
4149 grammar: "Git Attributes".to_string(),
4150 comment_prefix: Some("#".to_string()),
4151 auto_indent: false,
4152 auto_close: None,
4153 auto_surround: None,
4154 textmate_grammar: None,
4155 show_whitespace_tabs: true,
4156 line_wrap: None,
4157 wrap_column: None,
4158 page_view: None,
4159 page_width: None,
4160 use_tabs: None,
4161 tab_size: None,
4162 formatter: None,
4163 format_on_save: false,
4164 on_save: vec![],
4165 word_characters: None,
4166 },
4167 );
4168
4169 languages.insert(
4170 "typst".to_string(),
4171 LanguageConfig {
4172 extensions: vec!["typ".to_string()],
4173 filenames: vec![],
4174 grammar: "Typst".to_string(),
4175 comment_prefix: Some("//".to_string()),
4176 auto_indent: true,
4177 auto_close: None,
4178 auto_surround: None,
4179 textmate_grammar: None,
4180 show_whitespace_tabs: true,
4181 line_wrap: None,
4182 wrap_column: None,
4183 page_view: None,
4184 page_width: None,
4185 use_tabs: None,
4186 tab_size: None,
4187 formatter: None,
4188 format_on_save: false,
4189 on_save: vec![],
4190 word_characters: None,
4191 },
4192 );
4193
4194 languages.insert(
4199 "kotlin".to_string(),
4200 LanguageConfig {
4201 extensions: vec!["kt".to_string(), "kts".to_string()],
4202 filenames: vec![],
4203 grammar: "Kotlin".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 "swift".to_string(),
4225 LanguageConfig {
4226 extensions: vec!["swift".to_string()],
4227 filenames: vec![],
4228 grammar: "Swift".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 "scala".to_string(),
4250 LanguageConfig {
4251 extensions: vec!["scala".to_string(), "sc".to_string()],
4252 filenames: vec![],
4253 grammar: "Scala".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 "dart".to_string(),
4275 LanguageConfig {
4276 extensions: vec!["dart".to_string()],
4277 filenames: vec![],
4278 grammar: "Dart".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 "elixir".to_string(),
4300 LanguageConfig {
4301 extensions: vec!["ex".to_string(), "exs".to_string()],
4302 filenames: vec![],
4303 grammar: "Elixir".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 "erlang".to_string(),
4325 LanguageConfig {
4326 extensions: vec!["erl".to_string(), "hrl".to_string()],
4327 filenames: vec![],
4328 grammar: "Erlang".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 "haskell".to_string(),
4350 LanguageConfig {
4351 extensions: vec!["hs".to_string(), "lhs".to_string()],
4352 filenames: vec![],
4353 grammar: "Haskell".to_string(),
4354 comment_prefix: Some("--".to_string()),
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 "ocaml".to_string(),
4375 LanguageConfig {
4376 extensions: vec!["ml".to_string(), "mli".to_string()],
4377 filenames: vec![],
4378 grammar: "OCaml".to_string(),
4379 comment_prefix: None,
4380 auto_indent: true,
4381 auto_close: None,
4382 auto_surround: None,
4383 textmate_grammar: None,
4384 show_whitespace_tabs: true,
4385 line_wrap: None,
4386 wrap_column: None,
4387 page_view: None,
4388 page_width: None,
4389 use_tabs: None,
4390 tab_size: None,
4391 formatter: None,
4392 format_on_save: false,
4393 on_save: vec![],
4394 word_characters: None,
4395 },
4396 );
4397
4398 languages.insert(
4399 "clojure".to_string(),
4400 LanguageConfig {
4401 extensions: vec![
4402 "clj".to_string(),
4403 "cljs".to_string(),
4404 "cljc".to_string(),
4405 "edn".to_string(),
4406 ],
4407 filenames: vec![],
4408 grammar: "Clojure".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 "r".to_string(),
4430 LanguageConfig {
4431 extensions: vec!["r".to_string(), "R".to_string(), "rmd".to_string()],
4432 filenames: vec![],
4433 grammar: "R".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 "julia".to_string(),
4455 LanguageConfig {
4456 extensions: vec!["jl".to_string()],
4457 filenames: vec![],
4458 grammar: "Julia".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 "perl".to_string(),
4480 LanguageConfig {
4481 extensions: vec!["pl".to_string(), "pm".to_string(), "t".to_string()],
4482 filenames: vec![],
4483 grammar: "Perl".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 "nim".to_string(),
4505 LanguageConfig {
4506 extensions: vec!["nim".to_string(), "nims".to_string(), "nimble".to_string()],
4507 filenames: vec![],
4508 grammar: "Nim".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 "gleam".to_string(),
4530 LanguageConfig {
4531 extensions: vec!["gleam".to_string()],
4532 filenames: vec![],
4533 grammar: "Gleam".to_string(),
4534 comment_prefix: Some("//".to_string()),
4535 auto_indent: true,
4536 auto_close: None,
4537 auto_surround: None,
4538 textmate_grammar: None,
4539 show_whitespace_tabs: true,
4540 line_wrap: None,
4541 wrap_column: None,
4542 page_view: None,
4543 page_width: None,
4544 use_tabs: None,
4545 tab_size: None,
4546 formatter: None,
4547 format_on_save: false,
4548 on_save: vec![],
4549 word_characters: None,
4550 },
4551 );
4552
4553 languages.insert(
4554 "racket".to_string(),
4555 LanguageConfig {
4556 extensions: vec![
4557 "rkt".to_string(),
4558 "rktd".to_string(),
4559 "rktl".to_string(),
4560 "scrbl".to_string(),
4561 ],
4562 filenames: vec![],
4563 grammar: "Racket".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 "fsharp".to_string(),
4585 LanguageConfig {
4586 extensions: vec!["fs".to_string(), "fsi".to_string(), "fsx".to_string()],
4587 filenames: vec![],
4588 grammar: "FSharp".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 "nix".to_string(),
4610 LanguageConfig {
4611 extensions: vec!["nix".to_string()],
4612 filenames: vec![],
4613 grammar: "Nix".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 "nushell".to_string(),
4635 LanguageConfig {
4636 extensions: vec!["nu".to_string()],
4637 filenames: vec![],
4638 grammar: "Nushell".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 "solidity".to_string(),
4660 LanguageConfig {
4661 extensions: vec!["sol".to_string()],
4662 filenames: vec![],
4663 grammar: "Solidity".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 "verilog".to_string(),
4685 LanguageConfig {
4686 extensions: vec!["vh".to_string(), "verilog".to_string()],
4687 filenames: vec![],
4688 grammar: "Verilog".to_string(),
4689 comment_prefix: Some("//".to_string()),
4690 auto_indent: true,
4691 auto_close: None,
4692 auto_surround: None,
4693 textmate_grammar: None,
4694 show_whitespace_tabs: true,
4695 line_wrap: None,
4696 wrap_column: None,
4697 page_view: None,
4698 page_width: None,
4699 use_tabs: None,
4700 tab_size: None,
4701 formatter: None,
4702 format_on_save: false,
4703 on_save: vec![],
4704 word_characters: None,
4705 },
4706 );
4707
4708 languages.insert(
4709 "systemverilog".to_string(),
4710 LanguageConfig {
4711 extensions: vec![
4712 "sv".to_string(),
4713 "svh".to_string(),
4714 "svi".to_string(),
4715 "svp".to_string(),
4716 ],
4717 filenames: vec![],
4718 grammar: "SystemVerilog".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 "vhdl".to_string(),
4740 LanguageConfig {
4741 extensions: vec!["vhd".to_string(), "vhdl".to_string(), "vho".to_string()],
4742 filenames: vec![],
4743 grammar: "VHDL".to_string(),
4744 comment_prefix: Some("--".to_string()),
4745 auto_indent: true,
4746 auto_close: None,
4747 auto_surround: None,
4748 textmate_grammar: None,
4749 show_whitespace_tabs: true,
4750 line_wrap: None,
4751 wrap_column: None,
4752 page_view: None,
4753 page_width: None,
4754 use_tabs: None,
4755 tab_size: None,
4756 formatter: None,
4757 format_on_save: false,
4758 on_save: vec![],
4759 word_characters: None,
4760 },
4761 );
4762
4763 languages.insert(
4764 "ruby".to_string(),
4765 LanguageConfig {
4766 extensions: vec!["rb".to_string(), "rake".to_string(), "gemspec".to_string()],
4767 filenames: vec![
4768 "Gemfile".to_string(),
4769 "Rakefile".to_string(),
4770 "Guardfile".to_string(),
4771 ],
4772 grammar: "Ruby".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 "php".to_string(),
4794 LanguageConfig {
4795 extensions: vec!["php".to_string(), "phtml".to_string()],
4796 filenames: vec![],
4797 grammar: "PHP".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 "lua".to_string(),
4819 LanguageConfig {
4820 extensions: vec!["lua".to_string()],
4821 filenames: vec![],
4822 grammar: "Lua".to_string(),
4823 comment_prefix: Some("--".to_string()),
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 "html".to_string(),
4844 LanguageConfig {
4845 extensions: vec!["html".to_string(), "htm".to_string()],
4846 filenames: vec![],
4847 grammar: "HTML".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 "css".to_string(),
4869 LanguageConfig {
4870 extensions: vec!["css".to_string()],
4871 filenames: vec![],
4872 grammar: "CSS".to_string(),
4873 comment_prefix: None,
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 "sql".to_string(),
4894 LanguageConfig {
4895 extensions: vec!["sql".to_string()],
4896 filenames: vec![],
4897 grammar: "SQL".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 "graphql".to_string(),
4919 LanguageConfig {
4920 extensions: vec!["graphql".to_string(), "gql".to_string()],
4921 filenames: vec![],
4922 grammar: "GraphQL".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 "protobuf".to_string(),
4944 LanguageConfig {
4945 extensions: vec!["proto".to_string()],
4946 filenames: vec![],
4947 grammar: "Protocol Buffers".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 "cmake".to_string(),
4969 LanguageConfig {
4970 extensions: vec!["cmake".to_string()],
4971 filenames: vec!["CMakeLists.txt".to_string()],
4972 grammar: "CMake".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 "terraform".to_string(),
4994 LanguageConfig {
4995 extensions: vec!["tf".to_string(), "tfvars".to_string(), "hcl".to_string()],
4996 filenames: vec![],
4997 grammar: "HCL".to_string(),
4998 comment_prefix: Some("#".to_string()),
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 "vue".to_string(),
5019 LanguageConfig {
5020 extensions: vec!["vue".to_string()],
5021 filenames: vec![],
5022 grammar: "Vue".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 "svelte".to_string(),
5044 LanguageConfig {
5045 extensions: vec!["svelte".to_string()],
5046 filenames: vec![],
5047 grammar: "Svelte".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(
5068 "astro".to_string(),
5069 LanguageConfig {
5070 extensions: vec!["astro".to_string()],
5071 filenames: vec![],
5072 grammar: "Astro".to_string(),
5073 comment_prefix: None,
5074 auto_indent: true,
5075 auto_close: None,
5076 auto_surround: None,
5077 textmate_grammar: None,
5078 show_whitespace_tabs: true,
5079 line_wrap: None,
5080 wrap_column: None,
5081 page_view: None,
5082 page_width: None,
5083 use_tabs: None,
5084 tab_size: None,
5085 formatter: None,
5086 format_on_save: false,
5087 on_save: vec![],
5088 word_characters: None,
5089 },
5090 );
5091
5092 languages.insert(
5095 "scss".to_string(),
5096 LanguageConfig {
5097 extensions: vec!["scss".to_string()],
5098 filenames: vec![],
5099 grammar: "SCSS".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 "less".to_string(),
5121 LanguageConfig {
5122 extensions: vec!["less".to_string()],
5123 filenames: vec![],
5124 grammar: "LESS".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 "powershell".to_string(),
5146 LanguageConfig {
5147 extensions: vec!["ps1".to_string(), "psm1".to_string(), "psd1".to_string()],
5148 filenames: vec![],
5149 grammar: "PowerShell".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 "kdl".to_string(),
5171 LanguageConfig {
5172 extensions: vec!["kdl".to_string()],
5173 filenames: vec![],
5174 grammar: "KDL".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 "starlark".to_string(),
5196 LanguageConfig {
5197 extensions: vec!["bzl".to_string(), "star".to_string()],
5198 filenames: vec!["BUILD".to_string(), "WORKSPACE".to_string()],
5199 grammar: "Starlark".to_string(),
5200 comment_prefix: Some("#".to_string()),
5201 auto_indent: true,
5202 auto_close: None,
5203 auto_surround: None,
5204 textmate_grammar: None,
5205 show_whitespace_tabs: true,
5206 line_wrap: None,
5207 wrap_column: None,
5208 page_view: None,
5209 page_width: None,
5210 use_tabs: None,
5211 tab_size: None,
5212 formatter: None,
5213 format_on_save: false,
5214 on_save: vec![],
5215 word_characters: None,
5216 },
5217 );
5218
5219 languages.insert(
5220 "justfile".to_string(),
5221 LanguageConfig {
5222 extensions: vec![],
5223 filenames: vec![
5224 "justfile".to_string(),
5225 "Justfile".to_string(),
5226 ".justfile".to_string(),
5227 ],
5228 grammar: "Justfile".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: Some(true),
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 "earthfile".to_string(),
5250 LanguageConfig {
5251 extensions: vec!["earth".to_string()],
5252 filenames: vec!["Earthfile".to_string()],
5253 grammar: "Earthfile".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: None,
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 "gomod".to_string(),
5275 LanguageConfig {
5276 extensions: vec![],
5277 filenames: vec!["go.mod".to_string(), "go.sum".to_string()],
5278 grammar: "Go Module".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: Some(true),
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 "vlang".to_string(),
5300 LanguageConfig {
5301 extensions: vec!["v".to_string(), "vv".to_string()],
5302 filenames: vec![],
5303 grammar: "V".to_string(),
5304 comment_prefix: Some("//".to_string()),
5305 auto_indent: true,
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 "ini".to_string(),
5325 LanguageConfig {
5326 extensions: vec!["ini".to_string(), "cfg".to_string()],
5327 filenames: vec![],
5328 grammar: "INI".to_string(),
5329 comment_prefix: Some(";".to_string()),
5330 auto_indent: false,
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.insert(
5349 "hyprlang".to_string(),
5350 LanguageConfig {
5351 extensions: vec!["hl".to_string()],
5352 filenames: vec!["hyprland.conf".to_string()],
5353 grammar: "Hyprlang".to_string(),
5354 comment_prefix: Some("#".to_string()),
5355 auto_indent: true,
5356 auto_close: None,
5357 auto_surround: None,
5358 textmate_grammar: None,
5359 show_whitespace_tabs: true,
5360 line_wrap: None,
5361 wrap_column: None,
5362 page_view: None,
5363 page_width: None,
5364 use_tabs: None,
5365 tab_size: None,
5366 formatter: None,
5367 format_on_save: false,
5368 on_save: vec![],
5369 word_characters: None,
5370 },
5371 );
5372
5373 languages
5374 }
5375
5376 #[cfg(feature = "runtime")]
5378 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
5379 let mut lsp = HashMap::new();
5380
5381 let ra_log_path = crate::services::log_dirs::lsp_log_path("rust-analyzer")
5384 .to_string_lossy()
5385 .to_string();
5386
5387 Self::populate_lsp_config(&mut lsp, ra_log_path);
5388 lsp
5389 }
5390
5391 #[cfg(not(feature = "runtime"))]
5393 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
5394 HashMap::new()
5396 }
5397
5398 #[cfg(feature = "runtime")]
5400 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
5401 let mut universal = HashMap::new();
5402
5403 universal.insert(
5416 "quicklsp".to_string(),
5417 LspLanguageConfig::Multi(vec![LspServerConfig {
5418 command: "quicklsp".to_string(),
5419 args: vec![],
5420 enabled: false,
5421 auto_start: false,
5422 process_limits: ProcessLimits::default(),
5423 initialization_options: None,
5424 env: Default::default(),
5425 language_id_overrides: Default::default(),
5426 name: Some("QuickLSP".to_string()),
5427 only_features: None,
5428 except_features: None,
5429 root_markers: vec![
5430 "Cargo.toml".to_string(),
5431 "package.json".to_string(),
5432 "go.mod".to_string(),
5433 "pyproject.toml".to_string(),
5434 "requirements.txt".to_string(),
5435 ".git".to_string(),
5436 ],
5437 }]),
5438 );
5439
5440 universal
5441 }
5442
5443 #[cfg(not(feature = "runtime"))]
5445 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
5446 HashMap::new()
5447 }
5448
5449 #[cfg(feature = "runtime")]
5450 fn populate_lsp_config(lsp: &mut HashMap<String, LspLanguageConfig>, ra_log_path: String) {
5451 lsp.insert(
5455 "rust".to_string(),
5456 LspLanguageConfig::Multi(vec![LspServerConfig {
5457 command: "rust-analyzer".to_string(),
5458 args: vec!["--log-file".to_string(), ra_log_path],
5459 enabled: true,
5460 auto_start: false,
5461 process_limits: ProcessLimits::unlimited(),
5462 initialization_options: None,
5463 env: Default::default(),
5464 language_id_overrides: Default::default(),
5465 name: None,
5466 only_features: None,
5467 except_features: None,
5468 root_markers: vec![
5469 "Cargo.toml".to_string(),
5470 "rust-project.json".to_string(),
5471 ".git".to_string(),
5472 ],
5473 }]),
5474 );
5475
5476 lsp.insert(
5478 "python".to_string(),
5479 LspLanguageConfig::Multi(vec![LspServerConfig {
5480 command: "pylsp".to_string(),
5481 args: vec![],
5482 enabled: true,
5483 auto_start: false,
5484 process_limits: ProcessLimits::default(),
5485 initialization_options: None,
5486 env: Default::default(),
5487 language_id_overrides: Default::default(),
5488 name: None,
5489 only_features: None,
5490 except_features: None,
5491 root_markers: vec![
5492 "pyproject.toml".to_string(),
5493 "setup.py".to_string(),
5494 "setup.cfg".to_string(),
5495 "pyrightconfig.json".to_string(),
5496 ".git".to_string(),
5497 ],
5498 }]),
5499 );
5500
5501 lsp.insert(
5504 "javascript".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 "jsx".to_string(),
5515 "javascriptreact".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 lsp.insert(
5529 "typescript".to_string(),
5530 LspLanguageConfig::Multi(vec![LspServerConfig {
5531 command: "typescript-language-server".to_string(),
5532 args: vec!["--stdio".to_string()],
5533 enabled: true,
5534 auto_start: false,
5535 process_limits: ProcessLimits::default(),
5536 initialization_options: None,
5537 env: Default::default(),
5538 language_id_overrides: HashMap::from([(
5539 "tsx".to_string(),
5540 "typescriptreact".to_string(),
5541 )]),
5542 name: None,
5543 only_features: None,
5544 except_features: None,
5545 root_markers: vec![
5546 "tsconfig.json".to_string(),
5547 "jsconfig.json".to_string(),
5548 "package.json".to_string(),
5549 ".git".to_string(),
5550 ],
5551 }]),
5552 );
5553
5554 lsp.insert(
5556 "html".to_string(),
5557 LspLanguageConfig::Multi(vec![LspServerConfig {
5558 command: "vscode-html-language-server".to_string(),
5559 args: vec!["--stdio".to_string()],
5560 enabled: true,
5561 auto_start: false,
5562 process_limits: ProcessLimits::default(),
5563 initialization_options: None,
5564 env: Default::default(),
5565 language_id_overrides: Default::default(),
5566 name: None,
5567 only_features: None,
5568 except_features: None,
5569 root_markers: Default::default(),
5570 }]),
5571 );
5572
5573 lsp.insert(
5575 "css".to_string(),
5576 LspLanguageConfig::Multi(vec![LspServerConfig {
5577 command: "vscode-css-language-server".to_string(),
5578 args: vec!["--stdio".to_string()],
5579 enabled: true,
5580 auto_start: false,
5581 process_limits: ProcessLimits::default(),
5582 initialization_options: None,
5583 env: Default::default(),
5584 language_id_overrides: Default::default(),
5585 name: None,
5586 only_features: None,
5587 except_features: None,
5588 root_markers: Default::default(),
5589 }]),
5590 );
5591
5592 lsp.insert(
5594 "c".to_string(),
5595 LspLanguageConfig::Multi(vec![LspServerConfig {
5596 command: "clangd".to_string(),
5597 args: vec![],
5598 enabled: true,
5599 auto_start: false,
5600 process_limits: ProcessLimits::default(),
5601 initialization_options: None,
5602 env: Default::default(),
5603 language_id_overrides: Default::default(),
5604 name: None,
5605 only_features: None,
5606 except_features: None,
5607 root_markers: vec![
5608 "compile_commands.json".to_string(),
5609 "CMakeLists.txt".to_string(),
5610 "Makefile".to_string(),
5611 ".git".to_string(),
5612 ],
5613 }]),
5614 );
5615 lsp.insert(
5616 "cpp".to_string(),
5617 LspLanguageConfig::Multi(vec![LspServerConfig {
5618 command: "clangd".to_string(),
5619 args: vec![],
5620 enabled: true,
5621 auto_start: false,
5622 process_limits: ProcessLimits::default(),
5623 initialization_options: None,
5624 env: Default::default(),
5625 language_id_overrides: Default::default(),
5626 name: None,
5627 only_features: None,
5628 except_features: None,
5629 root_markers: vec![
5630 "compile_commands.json".to_string(),
5631 "CMakeLists.txt".to_string(),
5632 "Makefile".to_string(),
5633 ".git".to_string(),
5634 ],
5635 }]),
5636 );
5637
5638 lsp.insert(
5640 "go".to_string(),
5641 LspLanguageConfig::Multi(vec![LspServerConfig {
5642 command: "gopls".to_string(),
5643 args: vec![],
5644 enabled: true,
5645 auto_start: false,
5646 process_limits: ProcessLimits::default(),
5647 initialization_options: None,
5648 env: Default::default(),
5649 language_id_overrides: Default::default(),
5650 name: None,
5651 only_features: None,
5652 except_features: None,
5653 root_markers: vec![
5654 "go.mod".to_string(),
5655 "go.work".to_string(),
5656 ".git".to_string(),
5657 ],
5658 }]),
5659 );
5660
5661 lsp.insert(
5663 "json".to_string(),
5664 LspLanguageConfig::Multi(vec![LspServerConfig {
5665 command: "vscode-json-language-server".to_string(),
5666 args: vec!["--stdio".to_string()],
5667 enabled: true,
5668 auto_start: false,
5669 process_limits: ProcessLimits::default(),
5670 initialization_options: None,
5671 env: Default::default(),
5672 language_id_overrides: Default::default(),
5673 name: None,
5674 only_features: None,
5675 except_features: None,
5676 root_markers: Default::default(),
5677 }]),
5678 );
5679
5680 lsp.insert(
5684 "jsonc".to_string(),
5685 LspLanguageConfig::Multi(vec![LspServerConfig {
5686 command: "vscode-json-language-server".to_string(),
5687 args: vec!["--stdio".to_string()],
5688 enabled: true,
5689 auto_start: false,
5690 process_limits: ProcessLimits::default(),
5691 initialization_options: None,
5692 env: Default::default(),
5693 language_id_overrides: Default::default(),
5694 name: None,
5695 only_features: None,
5696 except_features: None,
5697 root_markers: Default::default(),
5698 }]),
5699 );
5700
5701 lsp.insert(
5703 "csharp".to_string(),
5704 LspLanguageConfig::Multi(vec![LspServerConfig {
5705 command: "csharp-ls".to_string(),
5706 args: vec![],
5707 enabled: true,
5708 auto_start: false,
5709 process_limits: ProcessLimits::default(),
5710 initialization_options: None,
5711 env: Default::default(),
5712 language_id_overrides: Default::default(),
5713 name: None,
5714 only_features: None,
5715 except_features: None,
5716 root_markers: vec![
5717 "*.csproj".to_string(),
5718 "*.sln".to_string(),
5719 ".git".to_string(),
5720 ],
5721 }]),
5722 );
5723
5724 lsp.insert(
5727 "odin".to_string(),
5728 LspLanguageConfig::Multi(vec![LspServerConfig {
5729 command: "ols".to_string(),
5730 args: vec![],
5731 enabled: true,
5732 auto_start: false,
5733 process_limits: ProcessLimits::default(),
5734 initialization_options: None,
5735 env: Default::default(),
5736 language_id_overrides: Default::default(),
5737 name: None,
5738 only_features: None,
5739 except_features: None,
5740 root_markers: Default::default(),
5741 }]),
5742 );
5743
5744 lsp.insert(
5747 "zig".to_string(),
5748 LspLanguageConfig::Multi(vec![LspServerConfig {
5749 command: "zls".to_string(),
5750 args: vec![],
5751 enabled: true,
5752 auto_start: false,
5753 process_limits: ProcessLimits::default(),
5754 initialization_options: None,
5755 env: Default::default(),
5756 language_id_overrides: Default::default(),
5757 name: None,
5758 only_features: None,
5759 except_features: None,
5760 root_markers: Default::default(),
5761 }]),
5762 );
5763
5764 lsp.insert(
5767 "c3".to_string(),
5768 LspLanguageConfig::Multi(vec![LspServerConfig {
5769 command: "c3lsp".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: vec!["project.json".to_string(), ".git".to_string()],
5781 }]),
5782 );
5783
5784 lsp.insert(
5787 "java".to_string(),
5788 LspLanguageConfig::Multi(vec![LspServerConfig {
5789 command: "jdtls".to_string(),
5790 args: vec![],
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: vec![
5801 "pom.xml".to_string(),
5802 "build.gradle".to_string(),
5803 "build.gradle.kts".to_string(),
5804 ".git".to_string(),
5805 ],
5806 }]),
5807 );
5808
5809 lsp.insert(
5812 "latex".to_string(),
5813 LspLanguageConfig::Multi(vec![LspServerConfig {
5814 command: "texlab".to_string(),
5815 args: vec![],
5816 enabled: true,
5817 auto_start: false,
5818 process_limits: ProcessLimits::default(),
5819 initialization_options: None,
5820 env: Default::default(),
5821 language_id_overrides: Default::default(),
5822 name: None,
5823 only_features: None,
5824 except_features: None,
5825 root_markers: Default::default(),
5826 }]),
5827 );
5828
5829 lsp.insert(
5832 "markdown".to_string(),
5833 LspLanguageConfig::Multi(vec![LspServerConfig {
5834 command: "marksman".to_string(),
5835 args: vec!["server".to_string()],
5836 enabled: true,
5837 auto_start: false,
5838 process_limits: ProcessLimits::default(),
5839 initialization_options: None,
5840 env: Default::default(),
5841 language_id_overrides: Default::default(),
5842 name: None,
5843 only_features: None,
5844 except_features: None,
5845 root_markers: Default::default(),
5846 }]),
5847 );
5848
5849 lsp.insert(
5852 "templ".to_string(),
5853 LspLanguageConfig::Multi(vec![LspServerConfig {
5854 command: "templ".to_string(),
5855 args: vec!["lsp".to_string()],
5856 enabled: true,
5857 auto_start: false,
5858 process_limits: ProcessLimits::default(),
5859 initialization_options: None,
5860 env: Default::default(),
5861 language_id_overrides: Default::default(),
5862 name: None,
5863 only_features: None,
5864 except_features: None,
5865 root_markers: Default::default(),
5866 }]),
5867 );
5868
5869 lsp.insert(
5872 "typst".to_string(),
5873 LspLanguageConfig::Multi(vec![LspServerConfig {
5874 command: "tinymist".to_string(),
5875 args: vec![],
5876 enabled: true,
5877 auto_start: false,
5878 process_limits: ProcessLimits::default(),
5879 initialization_options: None,
5880 env: Default::default(),
5881 language_id_overrides: Default::default(),
5882 name: None,
5883 only_features: None,
5884 except_features: None,
5885 root_markers: Default::default(),
5886 }]),
5887 );
5888
5889 lsp.insert(
5891 "bash".to_string(),
5892 LspLanguageConfig::Multi(vec![LspServerConfig {
5893 command: "bash-language-server".to_string(),
5894 args: vec!["start".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: Default::default(),
5905 }]),
5906 );
5907
5908 lsp.insert(
5911 "lua".to_string(),
5912 LspLanguageConfig::Multi(vec![LspServerConfig {
5913 command: "lua-language-server".to_string(),
5914 args: vec![],
5915 enabled: true,
5916 auto_start: false,
5917 process_limits: ProcessLimits::default(),
5918 initialization_options: None,
5919 env: Default::default(),
5920 language_id_overrides: Default::default(),
5921 name: None,
5922 only_features: None,
5923 except_features: None,
5924 root_markers: vec![
5925 ".luarc.json".to_string(),
5926 ".luarc.jsonc".to_string(),
5927 ".luacheckrc".to_string(),
5928 ".stylua.toml".to_string(),
5929 ".git".to_string(),
5930 ],
5931 }]),
5932 );
5933
5934 lsp.insert(
5936 "ruby".to_string(),
5937 LspLanguageConfig::Multi(vec![LspServerConfig {
5938 command: "solargraph".to_string(),
5939 args: vec!["stdio".to_string()],
5940 enabled: true,
5941 auto_start: false,
5942 process_limits: ProcessLimits::default(),
5943 initialization_options: None,
5944 env: Default::default(),
5945 language_id_overrides: Default::default(),
5946 name: None,
5947 only_features: None,
5948 except_features: None,
5949 root_markers: vec![
5950 "Gemfile".to_string(),
5951 ".ruby-version".to_string(),
5952 ".git".to_string(),
5953 ],
5954 }]),
5955 );
5956
5957 lsp.insert(
5960 "php".to_string(),
5961 LspLanguageConfig::Multi(vec![LspServerConfig {
5962 command: "phpactor".to_string(),
5963 args: vec!["language-server".to_string()],
5964 enabled: true,
5965 auto_start: false,
5966 process_limits: ProcessLimits::default(),
5967 initialization_options: None,
5968 env: Default::default(),
5969 language_id_overrides: Default::default(),
5970 name: None,
5971 only_features: None,
5972 except_features: None,
5973 root_markers: vec!["composer.json".to_string(), ".git".to_string()],
5974 }]),
5975 );
5976
5977 lsp.insert(
5979 "yaml".to_string(),
5980 LspLanguageConfig::Multi(vec![LspServerConfig {
5981 command: "yaml-language-server".to_string(),
5982 args: vec!["--stdio".to_string()],
5983 enabled: true,
5984 auto_start: false,
5985 process_limits: ProcessLimits::default(),
5986 initialization_options: None,
5987 env: Default::default(),
5988 language_id_overrides: Default::default(),
5989 name: None,
5990 only_features: None,
5991 except_features: None,
5992 root_markers: Default::default(),
5993 }]),
5994 );
5995
5996 lsp.insert(
5999 "toml".to_string(),
6000 LspLanguageConfig::Multi(vec![LspServerConfig {
6001 command: "taplo".to_string(),
6002 args: vec!["lsp".to_string(), "stdio".to_string()],
6003 enabled: true,
6004 auto_start: false,
6005 process_limits: ProcessLimits::default(),
6006 initialization_options: None,
6007 env: Default::default(),
6008 language_id_overrides: Default::default(),
6009 name: None,
6010 only_features: None,
6011 except_features: None,
6012 root_markers: Default::default(),
6013 }]),
6014 );
6015
6016 lsp.insert(
6019 "dart".to_string(),
6020 LspLanguageConfig::Multi(vec![LspServerConfig {
6021 command: "dart".to_string(),
6022 args: vec!["language-server".to_string(), "--protocol=lsp".to_string()],
6023 enabled: true,
6024 auto_start: false,
6025 process_limits: ProcessLimits::default(),
6026 initialization_options: None,
6027 env: Default::default(),
6028 language_id_overrides: Default::default(),
6029 name: None,
6030 only_features: None,
6031 except_features: None,
6032 root_markers: vec!["pubspec.yaml".to_string(), ".git".to_string()],
6033 }]),
6034 );
6035
6036 lsp.insert(
6039 "nushell".to_string(),
6040 LspLanguageConfig::Multi(vec![LspServerConfig {
6041 command: "nu".to_string(),
6042 args: vec!["--lsp".to_string()],
6043 enabled: true,
6044 auto_start: false,
6045 process_limits: ProcessLimits::default(),
6046 initialization_options: None,
6047 env: Default::default(),
6048 language_id_overrides: Default::default(),
6049 name: None,
6050 only_features: None,
6051 except_features: None,
6052 root_markers: Default::default(),
6053 }]),
6054 );
6055
6056 lsp.insert(
6059 "solidity".to_string(),
6060 LspLanguageConfig::Multi(vec![LspServerConfig {
6061 command: "nomicfoundation-solidity-language-server".to_string(),
6062 args: vec!["--stdio".to_string()],
6063 enabled: true,
6064 auto_start: false,
6065 process_limits: ProcessLimits::default(),
6066 initialization_options: None,
6067 env: Default::default(),
6068 language_id_overrides: Default::default(),
6069 name: None,
6070 only_features: None,
6071 except_features: None,
6072 root_markers: Default::default(),
6073 }]),
6074 );
6075
6076 lsp.insert(
6081 "terraform".to_string(),
6082 LspLanguageConfig::Multi(vec![LspServerConfig {
6083 command: "terraform-ls".to_string(),
6084 args: vec!["serve".to_string()],
6085 enabled: true,
6086 auto_start: false,
6087 process_limits: ProcessLimits::default(),
6088 initialization_options: None,
6089 env: Default::default(),
6090 language_id_overrides: Default::default(),
6091 name: None,
6092 only_features: None,
6093 except_features: None,
6094 root_markers: vec![
6095 "*.tf".to_string(),
6096 ".terraform".to_string(),
6097 ".git".to_string(),
6098 ],
6099 }]),
6100 );
6101
6102 lsp.insert(
6105 "cmake".to_string(),
6106 LspLanguageConfig::Multi(vec![LspServerConfig {
6107 command: "cmake-language-server".to_string(),
6108 args: vec![],
6109 enabled: true,
6110 auto_start: false,
6111 process_limits: ProcessLimits::default(),
6112 initialization_options: None,
6113 env: Default::default(),
6114 language_id_overrides: Default::default(),
6115 name: None,
6116 only_features: None,
6117 except_features: None,
6118 root_markers: vec!["CMakeLists.txt".to_string(), ".git".to_string()],
6119 }]),
6120 );
6121
6122 lsp.insert(
6125 "protobuf".to_string(),
6126 LspLanguageConfig::Multi(vec![LspServerConfig {
6127 command: "buf".to_string(),
6128 args: vec!["beta".to_string(), "lsp".to_string()],
6129 enabled: true,
6130 auto_start: false,
6131 process_limits: ProcessLimits::default(),
6132 initialization_options: None,
6133 env: Default::default(),
6134 language_id_overrides: Default::default(),
6135 name: None,
6136 only_features: None,
6137 except_features: None,
6138 root_markers: Default::default(),
6139 }]),
6140 );
6141
6142 lsp.insert(
6145 "graphql".to_string(),
6146 LspLanguageConfig::Multi(vec![LspServerConfig {
6147 command: "graphql-lsp".to_string(),
6148 args: vec!["server".to_string(), "-m".to_string(), "stream".to_string()],
6149 enabled: true,
6150 auto_start: false,
6151 process_limits: ProcessLimits::default(),
6152 initialization_options: None,
6153 env: Default::default(),
6154 language_id_overrides: Default::default(),
6155 name: None,
6156 only_features: None,
6157 except_features: None,
6158 root_markers: Default::default(),
6159 }]),
6160 );
6161
6162 lsp.insert(
6165 "sql".to_string(),
6166 LspLanguageConfig::Multi(vec![LspServerConfig {
6167 command: "sqls".to_string(),
6168 args: vec![],
6169 enabled: true,
6170 auto_start: false,
6171 process_limits: ProcessLimits::default(),
6172 initialization_options: None,
6173 env: Default::default(),
6174 language_id_overrides: Default::default(),
6175 name: None,
6176 only_features: None,
6177 except_features: None,
6178 root_markers: Default::default(),
6179 }]),
6180 );
6181
6182 lsp.insert(
6186 "vue".to_string(),
6187 LspLanguageConfig::Multi(vec![LspServerConfig {
6188 command: "vue-language-server".to_string(),
6189 args: vec!["--stdio".to_string()],
6190 enabled: true,
6191 auto_start: false,
6192 process_limits: ProcessLimits::default(),
6193 initialization_options: None,
6194 env: Default::default(),
6195 language_id_overrides: Default::default(),
6196 name: None,
6197 only_features: None,
6198 except_features: None,
6199 root_markers: Default::default(),
6200 }]),
6201 );
6202
6203 lsp.insert(
6205 "svelte".to_string(),
6206 LspLanguageConfig::Multi(vec![LspServerConfig {
6207 command: "svelteserver".to_string(),
6208 args: vec!["--stdio".to_string()],
6209 enabled: true,
6210 auto_start: false,
6211 process_limits: ProcessLimits::default(),
6212 initialization_options: None,
6213 env: Default::default(),
6214 language_id_overrides: Default::default(),
6215 name: None,
6216 only_features: None,
6217 except_features: None,
6218 root_markers: Default::default(),
6219 }]),
6220 );
6221
6222 lsp.insert(
6224 "astro".to_string(),
6225 LspLanguageConfig::Multi(vec![LspServerConfig {
6226 command: "astro-ls".to_string(),
6227 args: vec!["--stdio".to_string()],
6228 enabled: true,
6229 auto_start: false,
6230 process_limits: ProcessLimits::default(),
6231 initialization_options: None,
6232 env: Default::default(),
6233 language_id_overrides: Default::default(),
6234 name: None,
6235 only_features: None,
6236 except_features: None,
6237 root_markers: Default::default(),
6238 }]),
6239 );
6240
6241 lsp.insert(
6243 "tailwindcss".to_string(),
6244 LspLanguageConfig::Multi(vec![LspServerConfig {
6245 command: "tailwindcss-language-server".to_string(),
6246 args: vec!["--stdio".to_string()],
6247 enabled: true,
6248 auto_start: false,
6249 process_limits: ProcessLimits::default(),
6250 initialization_options: None,
6251 env: Default::default(),
6252 language_id_overrides: Default::default(),
6253 name: None,
6254 only_features: None,
6255 except_features: None,
6256 root_markers: Default::default(),
6257 }]),
6258 );
6259
6260 lsp.insert(
6265 "nix".to_string(),
6266 LspLanguageConfig::Multi(vec![LspServerConfig {
6267 command: "nil".to_string(),
6268 args: vec![],
6269 enabled: true,
6270 auto_start: false,
6271 process_limits: ProcessLimits::default(),
6272 initialization_options: None,
6273 env: Default::default(),
6274 language_id_overrides: Default::default(),
6275 name: None,
6276 only_features: None,
6277 except_features: None,
6278 root_markers: Default::default(),
6279 }]),
6280 );
6281
6282 lsp.insert(
6285 "kotlin".to_string(),
6286 LspLanguageConfig::Multi(vec![LspServerConfig {
6287 command: "kotlin-language-server".to_string(),
6288 args: vec![],
6289 enabled: true,
6290 auto_start: false,
6291 process_limits: ProcessLimits::default(),
6292 initialization_options: None,
6293 env: Default::default(),
6294 language_id_overrides: Default::default(),
6295 name: None,
6296 only_features: None,
6297 except_features: None,
6298 root_markers: Default::default(),
6299 }]),
6300 );
6301
6302 lsp.insert(
6304 "swift".to_string(),
6305 LspLanguageConfig::Multi(vec![LspServerConfig {
6306 command: "sourcekit-lsp".to_string(),
6307 args: vec![],
6308 enabled: true,
6309 auto_start: false,
6310 process_limits: ProcessLimits::default(),
6311 initialization_options: None,
6312 env: Default::default(),
6313 language_id_overrides: Default::default(),
6314 name: None,
6315 only_features: None,
6316 except_features: None,
6317 root_markers: Default::default(),
6318 }]),
6319 );
6320
6321 lsp.insert(
6324 "scala".to_string(),
6325 LspLanguageConfig::Multi(vec![LspServerConfig {
6326 command: "metals".to_string(),
6327 args: vec![],
6328 enabled: true,
6329 auto_start: false,
6330 process_limits: ProcessLimits::default(),
6331 initialization_options: None,
6332 env: Default::default(),
6333 language_id_overrides: Default::default(),
6334 name: None,
6335 only_features: None,
6336 except_features: None,
6337 root_markers: Default::default(),
6338 }]),
6339 );
6340
6341 lsp.insert(
6344 "elixir".to_string(),
6345 LspLanguageConfig::Multi(vec![LspServerConfig {
6346 command: "elixir-ls".to_string(),
6347 args: vec![],
6348 enabled: true,
6349 auto_start: false,
6350 process_limits: ProcessLimits::default(),
6351 initialization_options: None,
6352 env: Default::default(),
6353 language_id_overrides: Default::default(),
6354 name: None,
6355 only_features: None,
6356 except_features: None,
6357 root_markers: Default::default(),
6358 }]),
6359 );
6360
6361 lsp.insert(
6363 "erlang".to_string(),
6364 LspLanguageConfig::Multi(vec![LspServerConfig {
6365 command: "erlang_ls".to_string(),
6366 args: vec![],
6367 enabled: true,
6368 auto_start: false,
6369 process_limits: ProcessLimits::default(),
6370 initialization_options: None,
6371 env: Default::default(),
6372 language_id_overrides: Default::default(),
6373 name: None,
6374 only_features: None,
6375 except_features: None,
6376 root_markers: Default::default(),
6377 }]),
6378 );
6379
6380 lsp.insert(
6383 "haskell".to_string(),
6384 LspLanguageConfig::Multi(vec![LspServerConfig {
6385 command: "haskell-language-server-wrapper".to_string(),
6386 args: vec!["--lsp".to_string()],
6387 enabled: true,
6388 auto_start: false,
6389 process_limits: ProcessLimits::default(),
6390 initialization_options: None,
6391 env: Default::default(),
6392 language_id_overrides: Default::default(),
6393 name: None,
6394 only_features: None,
6395 except_features: None,
6396 root_markers: Default::default(),
6397 }]),
6398 );
6399
6400 lsp.insert(
6403 "ocaml".to_string(),
6404 LspLanguageConfig::Multi(vec![LspServerConfig {
6405 command: "ocamllsp".to_string(),
6406 args: vec![],
6407 enabled: true,
6408 auto_start: false,
6409 process_limits: ProcessLimits::default(),
6410 initialization_options: None,
6411 env: Default::default(),
6412 language_id_overrides: Default::default(),
6413 name: None,
6414 only_features: None,
6415 except_features: None,
6416 root_markers: Default::default(),
6417 }]),
6418 );
6419
6420 lsp.insert(
6423 "clojure".to_string(),
6424 LspLanguageConfig::Multi(vec![LspServerConfig {
6425 command: "clojure-lsp".to_string(),
6426 args: vec![],
6427 enabled: true,
6428 auto_start: false,
6429 process_limits: ProcessLimits::default(),
6430 initialization_options: None,
6431 env: Default::default(),
6432 language_id_overrides: Default::default(),
6433 name: None,
6434 only_features: None,
6435 except_features: None,
6436 root_markers: Default::default(),
6437 }]),
6438 );
6439
6440 lsp.insert(
6443 "r".to_string(),
6444 LspLanguageConfig::Multi(vec![LspServerConfig {
6445 command: "R".to_string(),
6446 args: vec![
6447 "--vanilla".to_string(),
6448 "-e".to_string(),
6449 "languageserver::run()".to_string(),
6450 ],
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 "julia".to_string(),
6468 LspLanguageConfig::Multi(vec![LspServerConfig {
6469 command: "julia".to_string(),
6470 args: vec![
6471 "--startup-file=no".to_string(),
6472 "--history-file=no".to_string(),
6473 "-e".to_string(),
6474 "using LanguageServer; runserver()".to_string(),
6475 ],
6476 enabled: true,
6477 auto_start: false,
6478 process_limits: ProcessLimits::default(),
6479 initialization_options: None,
6480 env: Default::default(),
6481 language_id_overrides: Default::default(),
6482 name: None,
6483 only_features: None,
6484 except_features: None,
6485 root_markers: Default::default(),
6486 }]),
6487 );
6488
6489 lsp.insert(
6492 "perl".to_string(),
6493 LspLanguageConfig::Multi(vec![LspServerConfig {
6494 command: "perlnavigator".to_string(),
6495 args: vec!["--stdio".to_string()],
6496 enabled: true,
6497 auto_start: false,
6498 process_limits: ProcessLimits::default(),
6499 initialization_options: None,
6500 env: Default::default(),
6501 language_id_overrides: Default::default(),
6502 name: None,
6503 only_features: None,
6504 except_features: None,
6505 root_markers: Default::default(),
6506 }]),
6507 );
6508
6509 lsp.insert(
6512 "nim".to_string(),
6513 LspLanguageConfig::Multi(vec![LspServerConfig {
6514 command: "nimlangserver".to_string(),
6515 args: vec![],
6516 enabled: true,
6517 auto_start: false,
6518 process_limits: ProcessLimits::default(),
6519 initialization_options: None,
6520 env: Default::default(),
6521 language_id_overrides: Default::default(),
6522 name: None,
6523 only_features: None,
6524 except_features: None,
6525 root_markers: Default::default(),
6526 }]),
6527 );
6528
6529 lsp.insert(
6531 "gleam".to_string(),
6532 LspLanguageConfig::Multi(vec![LspServerConfig {
6533 command: "gleam".to_string(),
6534 args: vec!["lsp".to_string()],
6535 enabled: true,
6536 auto_start: false,
6537 process_limits: ProcessLimits::default(),
6538 initialization_options: None,
6539 env: Default::default(),
6540 language_id_overrides: Default::default(),
6541 name: None,
6542 only_features: None,
6543 except_features: None,
6544 root_markers: Default::default(),
6545 }]),
6546 );
6547
6548 lsp.insert(
6551 "racket".to_string(),
6552 LspLanguageConfig::Multi(vec![LspServerConfig {
6553 command: "racket-langserver".to_string(),
6554 args: vec![],
6555 enabled: true,
6556 auto_start: false,
6557 process_limits: ProcessLimits::default(),
6558 initialization_options: None,
6559 env: Default::default(),
6560 language_id_overrides: Default::default(),
6561 name: None,
6562 only_features: None,
6563 except_features: None,
6564 root_markers: vec!["info.rkt".to_string(), ".git".to_string()],
6565 }]),
6566 );
6567
6568 lsp.insert(
6571 "fsharp".to_string(),
6572 LspLanguageConfig::Multi(vec![LspServerConfig {
6573 command: "fsautocomplete".to_string(),
6574 args: vec!["--adaptive-lsp-server-enabled".to_string()],
6575 enabled: true,
6576 auto_start: false,
6577 process_limits: ProcessLimits::default(),
6578 initialization_options: None,
6579 env: Default::default(),
6580 language_id_overrides: Default::default(),
6581 name: None,
6582 only_features: None,
6583 except_features: None,
6584 root_markers: Default::default(),
6585 }]),
6586 );
6587
6588 let svls_config = LspServerConfig {
6592 command: "svls".to_string(),
6593 args: vec![],
6594 enabled: true,
6595 auto_start: false,
6596 process_limits: ProcessLimits::default(),
6597 initialization_options: None,
6598 env: Default::default(),
6599 language_id_overrides: Default::default(),
6600 name: None,
6601 only_features: None,
6602 except_features: None,
6603 root_markers: vec![".svls.toml".to_string(), ".git".to_string()],
6604 };
6605 lsp.insert(
6606 "verilog".to_string(),
6607 LspLanguageConfig::Multi(vec![svls_config.clone()]),
6608 );
6609 lsp.insert(
6610 "systemverilog".to_string(),
6611 LspLanguageConfig::Multi(vec![svls_config]),
6612 );
6613 }
6614 pub fn validate(&self) -> Result<(), ConfigError> {
6615 if self.editor.tab_size == 0 {
6617 return Err(ConfigError::ValidationError(
6618 "tab_size must be greater than 0".to_string(),
6619 ));
6620 }
6621
6622 if self.editor.scroll_offset > 100 {
6624 return Err(ConfigError::ValidationError(
6625 "scroll_offset must be <= 100".to_string(),
6626 ));
6627 }
6628
6629 for binding in &self.keybindings {
6631 if binding.key.is_empty() {
6632 return Err(ConfigError::ValidationError(
6633 "keybinding key cannot be empty".to_string(),
6634 ));
6635 }
6636 if binding.action.is_empty() {
6637 return Err(ConfigError::ValidationError(
6638 "keybinding action cannot be empty".to_string(),
6639 ));
6640 }
6641 }
6642
6643 Ok(())
6644 }
6645}
6646
6647#[derive(Debug)]
6649pub enum ConfigError {
6650 IoError(String),
6651 ParseError(String),
6652 SerializeError(String),
6653 ValidationError(String),
6654}
6655
6656impl std::fmt::Display for ConfigError {
6657 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6658 match self {
6659 Self::IoError(msg) => write!(f, "IO error: {msg}"),
6660 Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
6661 Self::SerializeError(msg) => write!(f, "Serialize error: {msg}"),
6662 Self::ValidationError(msg) => write!(f, "Validation error: {msg}"),
6663 }
6664 }
6665}
6666
6667impl std::error::Error for ConfigError {}
6668
6669#[cfg(test)]
6670mod tests {
6671 use super::*;
6672
6673 #[test]
6674 fn test_file_explorer_width_default_is_percent_30() {
6675 let cfg = FileExplorerConfig::default();
6676 assert_eq!(cfg.width, ExplorerWidth::Percent(30));
6677 }
6678
6679 #[test]
6680 fn test_tree_indicators_defaults_preserve_current_glyphs() {
6681 let cfg = FileExplorerConfig::default();
6685 assert_eq!(cfg.tree_indicator_collapsed, ">");
6686 assert_eq!(cfg.tree_indicator_expanded, "▼");
6687 }
6688
6689 #[test]
6690 fn test_tree_indicators_can_be_overridden_from_json() {
6691 let cfg: FileExplorerConfig = serde_json::from_str(
6692 r#"{"tree_indicator_collapsed": "▸", "tree_indicator_expanded": "▾"}"#,
6693 )
6694 .unwrap();
6695 assert_eq!(cfg.tree_indicator_collapsed, "▸");
6696 assert_eq!(cfg.tree_indicator_expanded, "▾");
6697 }
6698
6699 #[test]
6700 fn test_tree_indicators_partial_override_keeps_default_for_unset() {
6701 let cfg: FileExplorerConfig =
6702 serde_json::from_str(r#"{"tree_indicator_collapsed": "+"}"#).unwrap();
6703 assert_eq!(cfg.tree_indicator_collapsed, "+");
6704 assert_eq!(cfg.tree_indicator_expanded, "▼");
6706 }
6707
6708 #[test]
6711 fn test_width_accepts_legacy_float_fraction() {
6712 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": 0.3}"#).unwrap();
6713 assert_eq!(cfg.width, ExplorerWidth::Percent(30));
6714 }
6715
6716 #[test]
6717 fn test_width_accepts_bare_integer_as_percent() {
6718 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": 42}"#).unwrap();
6720 assert_eq!(cfg.width, ExplorerWidth::Percent(42));
6721 }
6722
6723 #[test]
6724 fn test_width_accepts_percent_string() {
6725 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "75%"}"#).unwrap();
6726 assert_eq!(cfg.width, ExplorerWidth::Percent(75));
6727 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "42 %"}"#).unwrap();
6729 assert_eq!(cfg.width, ExplorerWidth::Percent(42));
6730 }
6731
6732 #[test]
6733 fn test_width_accepts_columns_string() {
6734 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "24"}"#).unwrap();
6735 assert_eq!(cfg.width, ExplorerWidth::Columns(24));
6736 }
6737
6738 #[test]
6739 fn test_width_rejects_percent_over_100() {
6740 let err = serde_json::from_str::<FileExplorerConfig>(r#"{"width": "150%"}"#)
6741 .expect_err("percent > 100 should be rejected");
6742 assert!(err.to_string().contains("100"), "{err}");
6743 }
6744
6745 #[test]
6746 fn test_width_rejects_integer_over_100() {
6747 let err = serde_json::from_str::<FileExplorerConfig>(r#"{"width": 150}"#)
6750 .expect_err("bare integer > 100 should be rejected as percent");
6751 assert!(err.to_string().contains("percent") || err.to_string().contains("100"));
6752 }
6753
6754 #[test]
6755 fn test_width_rejects_garbage_string() {
6756 serde_json::from_str::<FileExplorerConfig>(r#"{"width": "big"}"#)
6757 .expect_err("non-numeric string should be rejected");
6758 }
6759
6760 #[test]
6763 fn test_width_serializes_percent_as_string_with_suffix() {
6764 let cfg = FileExplorerConfig {
6765 width: ExplorerWidth::Percent(30),
6766 ..Default::default()
6767 };
6768 let json = serde_json::to_value(&cfg).unwrap();
6769 assert_eq!(json["width"], serde_json::json!("30%"));
6770 }
6771
6772 #[test]
6773 fn test_width_serializes_columns_as_string_without_suffix() {
6774 let cfg = FileExplorerConfig {
6775 width: ExplorerWidth::Columns(24),
6776 ..Default::default()
6777 };
6778 let json = serde_json::to_value(&cfg).unwrap();
6779 assert_eq!(json["width"], serde_json::json!("24"));
6780 }
6781
6782 #[test]
6783 fn test_width_round_trip_both_variants() {
6784 for value in [ExplorerWidth::Percent(17), ExplorerWidth::Columns(42)] {
6785 let cfg = FileExplorerConfig {
6786 width: value,
6787 ..Default::default()
6788 };
6789 let json = serde_json::to_string(&cfg).unwrap();
6790 let restored: FileExplorerConfig = serde_json::from_str(&json).unwrap();
6791 assert_eq!(restored.width, value, "round trip failed for {:?}", value);
6792 }
6793 }
6794
6795 #[test]
6798 fn test_to_cols_percent() {
6799 assert_eq!(ExplorerWidth::Percent(30).to_cols(100), 30);
6800 assert_eq!(ExplorerWidth::Percent(25).to_cols(120), 30);
6801 assert_eq!(ExplorerWidth::Percent(30).to_cols(0), 0);
6803 assert_eq!(ExplorerWidth::Percent(100).to_cols(200), 200);
6804 }
6805
6806 #[test]
6807 fn test_to_cols_columns_clamps_to_terminal() {
6808 assert_eq!(ExplorerWidth::Columns(24).to_cols(100), 24);
6809 assert_eq!(ExplorerWidth::Columns(999).to_cols(80), 80);
6810 }
6811
6812 #[test]
6815 fn test_to_cols_enforces_min_width() {
6816 assert_eq!(
6818 ExplorerWidth::Columns(0).to_cols(100),
6819 ExplorerWidth::MIN_COLS
6820 );
6821 assert_eq!(
6822 ExplorerWidth::Columns(1).to_cols(100),
6823 ExplorerWidth::MIN_COLS
6824 );
6825 assert_eq!(
6826 ExplorerWidth::Columns(4).to_cols(100),
6827 ExplorerWidth::MIN_COLS
6828 );
6829 assert_eq!(
6830 ExplorerWidth::Percent(0).to_cols(100),
6831 ExplorerWidth::MIN_COLS
6832 );
6833 assert_eq!(
6835 ExplorerWidth::Percent(3).to_cols(100),
6836 ExplorerWidth::MIN_COLS
6837 );
6838 assert_eq!(
6840 ExplorerWidth::Columns(ExplorerWidth::MIN_COLS + 1).to_cols(100),
6841 ExplorerWidth::MIN_COLS + 1
6842 );
6843 }
6844
6845 #[test]
6848 fn test_to_cols_min_floor_yields_to_narrow_terminal() {
6849 assert_eq!(ExplorerWidth::Columns(10).to_cols(3), 3);
6850 assert_eq!(ExplorerWidth::Percent(100).to_cols(2), 2);
6851 assert_eq!(ExplorerWidth::Columns(1).to_cols(0), 0);
6852 }
6853
6854 #[test]
6857 fn test_load_from_file_accepts_legacy_float_fraction_width() {
6858 let dir = tempfile::tempdir().unwrap();
6859 let path = dir.path().join("config.json");
6860 std::fs::write(&path, r#"{"file_explorer":{"width":0.25}}"#).unwrap();
6861 let cfg = Config::load_from_file(&path).expect("legacy float fraction must still load");
6862 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Percent(25));
6863 }
6864
6865 #[test]
6866 fn test_load_from_file_accepts_columns_string_width() {
6867 let dir = tempfile::tempdir().unwrap();
6868 let path = dir.path().join("config.json");
6869 std::fs::write(&path, r#"{"file_explorer":{"width":"42"}}"#).unwrap();
6870 let cfg = Config::load_from_file(&path).unwrap();
6871 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Columns(42));
6872 }
6873
6874 #[test]
6875 fn test_load_from_file_accepts_percent_string_width() {
6876 let dir = tempfile::tempdir().unwrap();
6877 let path = dir.path().join("config.json");
6878 std::fs::write(&path, r#"{"file_explorer":{"width":"55%"}}"#).unwrap();
6879 let cfg = Config::load_from_file(&path).unwrap();
6880 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Percent(55));
6881 }
6882
6883 #[test]
6884 fn test_default_config() {
6885 let config = Config::default();
6886 assert_eq!(config.editor.tab_size, 4);
6887 assert!(config.editor.line_numbers);
6888 assert!(config.editor.syntax_highlighting);
6889 assert!(config.keybindings.is_empty());
6892 let resolved = config.resolve_keymap(&config.active_keybinding_map);
6894 assert!(!resolved.is_empty());
6895 }
6896
6897 #[test]
6898 fn test_all_builtin_keymaps_loadable() {
6899 for name in KeybindingMapName::BUILTIN_OPTIONS {
6900 let keymap = Config::load_builtin_keymap(name);
6901 assert!(keymap.is_some(), "Failed to load builtin keymap '{}'", name);
6902 }
6903 }
6904
6905 #[test]
6906 fn test_config_validation() {
6907 let mut config = Config::default();
6908 assert!(config.validate().is_ok());
6909
6910 config.editor.tab_size = 0;
6911 assert!(config.validate().is_err());
6912 }
6913
6914 #[test]
6915 fn test_macos_keymap_inherits_enter_bindings() {
6916 let config = Config::default();
6917 let bindings = config.resolve_keymap("macos");
6918
6919 let enter_bindings: Vec<_> = bindings.iter().filter(|b| b.key == "Enter").collect();
6920 assert!(
6921 !enter_bindings.is_empty(),
6922 "macos keymap should inherit Enter bindings from default, got {} Enter bindings",
6923 enter_bindings.len()
6924 );
6925 let has_insert_newline = enter_bindings.iter().any(|b| b.action == "insert_newline");
6927 assert!(
6928 has_insert_newline,
6929 "macos keymap should have insert_newline action for Enter key"
6930 );
6931 }
6932
6933 #[test]
6934 fn test_config_serialize_deserialize() {
6935 let config = Config::default();
6937
6938 let json = serde_json::to_string_pretty(&config).unwrap();
6940
6941 let loaded: Config = serde_json::from_str(&json).unwrap();
6943
6944 assert_eq!(config.editor.tab_size, loaded.editor.tab_size);
6945 assert_eq!(config.theme, loaded.theme);
6946 }
6947
6948 #[test]
6949 fn test_config_with_custom_keybinding() {
6950 let json = r#"{
6951 "editor": {
6952 "tab_size": 2
6953 },
6954 "keybindings": [
6955 {
6956 "key": "x",
6957 "modifiers": ["ctrl", "shift"],
6958 "action": "custom_action",
6959 "args": {},
6960 "when": null
6961 }
6962 ]
6963 }"#;
6964
6965 let config: Config = serde_json::from_str(json).unwrap();
6966 assert_eq!(config.editor.tab_size, 2);
6967 assert_eq!(config.keybindings.len(), 1);
6968 assert_eq!(config.keybindings[0].key, "x");
6969 assert_eq!(config.keybindings[0].modifiers.len(), 2);
6970 }
6971
6972 #[test]
6973 fn test_sparse_config_merges_with_defaults() {
6974 let temp_dir = tempfile::tempdir().unwrap();
6976 let config_path = temp_dir.path().join("config.json");
6977
6978 let sparse_config = r#"{
6980 "lsp": {
6981 "rust": {
6982 "command": "custom-rust-analyzer",
6983 "args": ["--custom-arg"]
6984 }
6985 }
6986 }"#;
6987 std::fs::write(&config_path, sparse_config).unwrap();
6988
6989 let loaded = Config::load_from_file(&config_path).unwrap();
6991
6992 assert!(loaded.lsp.contains_key("rust"));
6994 assert_eq!(
6995 loaded.lsp["rust"].as_slice()[0].command,
6996 "custom-rust-analyzer".to_string()
6997 );
6998
6999 assert!(
7001 loaded.lsp.contains_key("python"),
7002 "python LSP should be merged from defaults"
7003 );
7004 assert!(
7005 loaded.lsp.contains_key("typescript"),
7006 "typescript LSP should be merged from defaults"
7007 );
7008 assert!(
7009 loaded.lsp.contains_key("javascript"),
7010 "javascript LSP should be merged from defaults"
7011 );
7012
7013 assert!(loaded.languages.contains_key("rust"));
7015 assert!(loaded.languages.contains_key("python"));
7016 assert!(loaded.languages.contains_key("typescript"));
7017 }
7018
7019 #[test]
7020 fn test_empty_config_gets_all_defaults() {
7021 let temp_dir = tempfile::tempdir().unwrap();
7022 let config_path = temp_dir.path().join("config.json");
7023
7024 std::fs::write(&config_path, "{}").unwrap();
7026
7027 let loaded = Config::load_from_file(&config_path).unwrap();
7028 let defaults = Config::default();
7029
7030 assert_eq!(loaded.lsp.len(), defaults.lsp.len());
7032
7033 assert_eq!(loaded.languages.len(), defaults.languages.len());
7035 }
7036
7037 #[test]
7038 fn test_dynamic_submenu_expansion() {
7039 let temp_dir = tempfile::tempdir().unwrap();
7041 let themes_dir = temp_dir.path().to_path_buf();
7042
7043 let dynamic = MenuItem::DynamicSubmenu {
7044 label: "Test".to_string(),
7045 source: "copy_with_theme".to_string(),
7046 };
7047
7048 let expanded = dynamic.expand_dynamic(&themes_dir);
7049
7050 match expanded {
7052 MenuItem::Submenu { label, items } => {
7053 assert_eq!(label, "Test");
7054 let loader = crate::view::theme::ThemeLoader::embedded_only();
7056 let registry = loader.load_all(&[]);
7057 assert_eq!(items.len(), registry.len());
7058
7059 for (item, theme_info) in items.iter().zip(registry.list().iter()) {
7061 match item {
7062 MenuItem::Action {
7063 label,
7064 action,
7065 args,
7066 ..
7067 } => {
7068 assert_eq!(label, &theme_info.name);
7069 assert_eq!(action, "copy_with_theme");
7070 assert_eq!(
7071 args.get("theme").and_then(|v| v.as_str()),
7072 Some(theme_info.name.as_str())
7073 );
7074 }
7075 _ => panic!("Expected Action item"),
7076 }
7077 }
7078 }
7079 _ => panic!("Expected Submenu after expansion"),
7080 }
7081 }
7082
7083 #[test]
7084 fn test_non_dynamic_item_unchanged() {
7085 let temp_dir = tempfile::tempdir().unwrap();
7087 let themes_dir = temp_dir.path();
7088
7089 let action = MenuItem::Action {
7090 label: "Test".to_string(),
7091 action: "test".to_string(),
7092 args: HashMap::new(),
7093 when: None,
7094 checkbox: None,
7095 };
7096
7097 let expanded = action.expand_dynamic(themes_dir);
7098 match expanded {
7099 MenuItem::Action { label, action, .. } => {
7100 assert_eq!(label, "Test");
7101 assert_eq!(action, "test");
7102 }
7103 _ => panic!("Action should remain Action after expand_dynamic"),
7104 }
7105 }
7106
7107 #[test]
7108 fn test_buffer_config_uses_global_defaults() {
7109 let config = Config::default();
7110 let buffer_config = BufferConfig::resolve(&config, None);
7111
7112 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
7113 assert_eq!(buffer_config.auto_indent, config.editor.auto_indent);
7114 assert!(!buffer_config.use_tabs); assert!(buffer_config.whitespace.any_tabs()); assert!(buffer_config.formatter.is_none());
7117 assert!(!buffer_config.format_on_save);
7118 }
7119
7120 #[test]
7121 fn test_buffer_config_applies_language_overrides() {
7122 let mut config = Config::default();
7123
7124 config.languages.insert(
7126 "go".to_string(),
7127 LanguageConfig {
7128 extensions: vec!["go".to_string()],
7129 filenames: vec![],
7130 grammar: "go".to_string(),
7131 comment_prefix: Some("//".to_string()),
7132 auto_indent: true,
7133 auto_close: None,
7134 auto_surround: None,
7135 textmate_grammar: None,
7136 show_whitespace_tabs: false, line_wrap: None,
7138 wrap_column: None,
7139 page_view: None,
7140 page_width: None,
7141 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
7144 command: "gofmt".to_string(),
7145 args: vec![],
7146 stdin: true,
7147 timeout_ms: 10000,
7148 }),
7149 format_on_save: true,
7150 on_save: vec![],
7151 word_characters: None,
7152 },
7153 );
7154
7155 let buffer_config = BufferConfig::resolve(&config, Some("go"));
7156
7157 assert_eq!(buffer_config.tab_size, 8);
7158 assert!(buffer_config.use_tabs);
7159 assert!(!buffer_config.whitespace.any_tabs()); assert!(buffer_config.format_on_save);
7161 assert!(buffer_config.formatter.is_some());
7162 assert_eq!(buffer_config.formatter.as_ref().unwrap().command, "gofmt");
7163 }
7164
7165 #[test]
7166 fn test_buffer_config_unknown_language_uses_global() {
7167 let config = Config::default();
7168 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
7169
7170 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
7172 assert!(!buffer_config.use_tabs);
7173 }
7174
7175 #[test]
7176 fn test_buffer_config_per_language_line_wrap() {
7177 let mut config = Config::default();
7178 config.editor.line_wrap = false;
7179
7180 config.languages.insert(
7182 "markdown".to_string(),
7183 LanguageConfig {
7184 extensions: vec!["md".to_string()],
7185 line_wrap: Some(true),
7186 ..Default::default()
7187 },
7188 );
7189
7190 let md_config = BufferConfig::resolve(&config, Some("markdown"));
7192 assert!(md_config.line_wrap, "Markdown should have line_wrap=true");
7193
7194 let other_config = BufferConfig::resolve(&config, Some("rust"));
7196 assert!(
7197 !other_config.line_wrap,
7198 "Non-configured languages should use global line_wrap=false"
7199 );
7200
7201 let no_lang_config = BufferConfig::resolve(&config, None);
7203 assert!(
7204 !no_lang_config.line_wrap,
7205 "No language should use global line_wrap=false"
7206 );
7207 }
7208
7209 #[test]
7210 fn test_buffer_config_per_language_wrap_column() {
7211 let mut config = Config::default();
7212 config.editor.wrap_column = Some(120);
7213
7214 config.languages.insert(
7216 "markdown".to_string(),
7217 LanguageConfig {
7218 extensions: vec!["md".to_string()],
7219 wrap_column: Some(80),
7220 ..Default::default()
7221 },
7222 );
7223
7224 let md_config = BufferConfig::resolve(&config, Some("markdown"));
7226 assert_eq!(md_config.wrap_column, Some(80));
7227
7228 let other_config = BufferConfig::resolve(&config, Some("rust"));
7230 assert_eq!(other_config.wrap_column, Some(120));
7231
7232 let no_lang_config = BufferConfig::resolve(&config, None);
7234 assert_eq!(no_lang_config.wrap_column, Some(120));
7235 }
7236
7237 #[test]
7238 fn test_buffer_config_indent_string() {
7239 let config = Config::default();
7240
7241 let spaces_config = BufferConfig::resolve(&config, None);
7243 assert_eq!(spaces_config.indent_string(), " "); let mut config_with_tabs = Config::default();
7247 config_with_tabs.languages.insert(
7248 "makefile".to_string(),
7249 LanguageConfig {
7250 use_tabs: Some(true),
7251 tab_size: Some(8),
7252 ..Default::default()
7253 },
7254 );
7255 let tabs_config = BufferConfig::resolve(&config_with_tabs, Some("makefile"));
7256 assert_eq!(tabs_config.indent_string(), "\t");
7257 }
7258
7259 #[test]
7260 fn test_buffer_config_global_use_tabs_inherited() {
7261 let mut config = Config::default();
7264 config.editor.use_tabs = true;
7265
7266 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
7268 assert!(buffer_config.use_tabs);
7269
7270 let buffer_config = BufferConfig::resolve(&config, None);
7272 assert!(buffer_config.use_tabs);
7273
7274 config.languages.insert(
7276 "python".to_string(),
7277 LanguageConfig {
7278 use_tabs: Some(false),
7279 ..Default::default()
7280 },
7281 );
7282 let buffer_config = BufferConfig::resolve(&config, Some("python"));
7283 assert!(!buffer_config.use_tabs);
7284
7285 config.languages.insert(
7287 "rust".to_string(),
7288 LanguageConfig {
7289 use_tabs: None,
7290 ..Default::default()
7291 },
7292 );
7293 let buffer_config = BufferConfig::resolve(&config, Some("rust"));
7294 assert!(buffer_config.use_tabs);
7295 }
7296
7297 #[test]
7303 #[cfg(feature = "runtime")]
7304 fn test_lsp_languages_have_language_config() {
7305 let config = Config::default();
7306 let exceptions = ["tailwindcss"];
7307 for lsp_key in config.lsp.keys() {
7308 if exceptions.contains(&lsp_key.as_str()) {
7309 continue;
7310 }
7311 assert!(
7312 config.languages.contains_key(lsp_key),
7313 "LSP config key '{}' has no matching entry in default_languages(). \
7314 Add a LanguageConfig with the correct file extensions so detect_language() \
7315 can map files to this language.",
7316 lsp_key
7317 );
7318 }
7319 }
7320
7321 #[test]
7322 #[cfg(feature = "runtime")]
7323 fn test_default_config_has_quicklsp_in_universal_lsp() {
7324 let config = Config::default();
7325 assert!(
7326 config.universal_lsp.contains_key("quicklsp"),
7327 "Default config should contain quicklsp in universal_lsp"
7328 );
7329 let quicklsp = &config.universal_lsp["quicklsp"];
7330 let server = &quicklsp.as_slice()[0];
7331 assert_eq!(server.command, "quicklsp");
7332 assert!(!server.enabled, "quicklsp should be disabled by default");
7333 assert_eq!(server.name.as_deref(), Some("QuickLSP"));
7334 assert!(
7338 server.only_features.is_none(),
7339 "quicklsp must not default to a feature whitelist"
7340 );
7341 assert!(server.except_features.is_none());
7342 }
7343
7344 #[test]
7345 fn test_empty_config_preserves_universal_lsp_defaults() {
7346 let temp_dir = tempfile::tempdir().unwrap();
7347 let config_path = temp_dir.path().join("config.json");
7348
7349 std::fs::write(&config_path, "{}").unwrap();
7351
7352 let loaded = Config::load_from_file(&config_path).unwrap();
7353 let defaults = Config::default();
7354
7355 assert_eq!(
7357 loaded.universal_lsp.len(),
7358 defaults.universal_lsp.len(),
7359 "Empty config should preserve all default universal_lsp entries"
7360 );
7361 }
7362
7363 #[test]
7364 fn test_universal_lsp_config_merges_with_defaults() {
7365 let temp_dir = tempfile::tempdir().unwrap();
7366 let config_path = temp_dir.path().join("config.json");
7367
7368 let config_json = r#"{
7370 "universal_lsp": {
7371 "quicklsp": {
7372 "enabled": true
7373 }
7374 }
7375 }"#;
7376 std::fs::write(&config_path, config_json).unwrap();
7377
7378 let loaded = Config::load_from_file(&config_path).unwrap();
7379
7380 assert!(loaded.universal_lsp.contains_key("quicklsp"));
7382 let server = &loaded.universal_lsp["quicklsp"].as_slice()[0];
7383 assert!(server.enabled, "User override should enable quicklsp");
7384 assert_eq!(
7386 server.command, "quicklsp",
7387 "Default command should be merged when not specified by user"
7388 );
7389 }
7390
7391 #[test]
7392 fn test_universal_lsp_custom_server_added() {
7393 let temp_dir = tempfile::tempdir().unwrap();
7394 let config_path = temp_dir.path().join("config.json");
7395
7396 let config_json = r#"{
7398 "universal_lsp": {
7399 "my-custom-server": {
7400 "command": "my-server",
7401 "enabled": true,
7402 "auto_start": true
7403 }
7404 }
7405 }"#;
7406 std::fs::write(&config_path, config_json).unwrap();
7407
7408 let loaded = Config::load_from_file(&config_path).unwrap();
7409
7410 assert!(
7412 loaded.universal_lsp.contains_key("my-custom-server"),
7413 "Custom universal server should be loaded"
7414 );
7415 let server = &loaded.universal_lsp["my-custom-server"].as_slice()[0];
7416 assert_eq!(server.command, "my-server");
7417 assert!(server.enabled);
7418 assert!(server.auto_start);
7419
7420 assert!(
7422 loaded.universal_lsp.contains_key("quicklsp"),
7423 "Default quicklsp should be merged from defaults"
7424 );
7425 }
7426}