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