1use crate::types::{context_keys, LspLanguageConfig, LspServerConfig, ProcessLimits};
2
3use rust_i18n::t;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use std::borrow::Cow;
7use std::collections::HashMap;
8use std::ops::Deref;
9use std::path::Path;
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(transparent)]
14pub struct ThemeName(pub String);
15
16impl ThemeName {
17 pub const BUILTIN_OPTIONS: &'static [&'static str] =
19 &["dark", "light", "high-contrast", "nostalgia", "terminal"];
20}
21
22impl Deref for ThemeName {
23 type Target = str;
24 fn deref(&self) -> &Self::Target {
25 &self.0
26 }
27}
28
29impl From<String> for ThemeName {
30 fn from(s: String) -> Self {
31 Self(s)
32 }
33}
34
35impl From<&str> for ThemeName {
36 fn from(s: &str) -> Self {
37 Self(s.to_string())
38 }
39}
40
41impl PartialEq<str> for ThemeName {
42 fn eq(&self, other: &str) -> bool {
43 self.0 == other
44 }
45}
46
47impl PartialEq<ThemeName> for str {
48 fn eq(&self, other: &ThemeName) -> bool {
49 self == other.0
50 }
51}
52
53impl JsonSchema for ThemeName {
54 fn schema_name() -> Cow<'static, str> {
55 Cow::Borrowed("ThemeOptions")
56 }
57
58 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
59 schemars::json_schema!({
60 "description": "Available color themes",
61 "type": "string",
62 "enum": Self::BUILTIN_OPTIONS
63 })
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
70#[serde(transparent)]
71pub struct LocaleName(pub Option<String>);
72
73include!(concat!(env!("OUT_DIR"), "/locale_options.rs"));
75
76impl LocaleName {
77 pub const LOCALE_OPTIONS: &'static [Option<&'static str>] = GENERATED_LOCALE_OPTIONS;
81
82 pub fn as_option(&self) -> Option<&str> {
84 self.0.as_deref()
85 }
86}
87
88impl From<Option<String>> for LocaleName {
89 fn from(s: Option<String>) -> Self {
90 Self(s)
91 }
92}
93
94impl From<Option<&str>> for LocaleName {
95 fn from(s: Option<&str>) -> Self {
96 Self(s.map(|s| s.to_string()))
97 }
98}
99
100impl JsonSchema for LocaleName {
101 fn schema_name() -> Cow<'static, str> {
102 Cow::Borrowed("LocaleOptions")
103 }
104
105 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
106 schemars::json_schema!({
107 "description": "UI locale (language). Use null for auto-detection from environment.",
108 "enum": Self::LOCALE_OPTIONS
109 })
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
115#[serde(rename_all = "snake_case")]
116pub enum CursorStyle {
117 #[default]
119 Default,
120 BlinkingBlock,
122 SteadyBlock,
124 BlinkingBar,
126 SteadyBar,
128 BlinkingUnderline,
130 SteadyUnderline,
132}
133
134impl CursorStyle {
135 pub const OPTIONS: &'static [&'static str] = &[
137 "default",
138 "blinking_block",
139 "steady_block",
140 "blinking_bar",
141 "steady_bar",
142 "blinking_underline",
143 "steady_underline",
144 ];
145
146 pub const DESCRIPTIONS: &'static [&'static str] = &[
148 "Terminal default",
149 "█ Blinking block",
150 "█ Solid block",
151 "│ Blinking bar",
152 "│ Solid bar",
153 "_ Blinking underline",
154 "_ Solid underline",
155 ];
156
157 #[cfg(feature = "runtime")]
159 pub fn to_crossterm_style(self) -> crossterm::cursor::SetCursorStyle {
160 use crossterm::cursor::SetCursorStyle;
161 match self {
162 Self::Default => SetCursorStyle::DefaultUserShape,
163 Self::BlinkingBlock => SetCursorStyle::BlinkingBlock,
164 Self::SteadyBlock => SetCursorStyle::SteadyBlock,
165 Self::BlinkingBar => SetCursorStyle::BlinkingBar,
166 Self::SteadyBar => SetCursorStyle::SteadyBar,
167 Self::BlinkingUnderline => SetCursorStyle::BlinkingUnderScore,
168 Self::SteadyUnderline => SetCursorStyle::SteadyUnderScore,
169 }
170 }
171
172 pub fn to_escape_sequence(self) -> &'static [u8] {
175 match self {
176 Self::Default => b"\x1b[0 q",
177 Self::BlinkingBlock => b"\x1b[1 q",
178 Self::SteadyBlock => b"\x1b[2 q",
179 Self::BlinkingUnderline => b"\x1b[3 q",
180 Self::SteadyUnderline => b"\x1b[4 q",
181 Self::BlinkingBar => b"\x1b[5 q",
182 Self::SteadyBar => b"\x1b[6 q",
183 }
184 }
185
186 pub fn parse(s: &str) -> Option<Self> {
188 match s {
189 "default" => Some(CursorStyle::Default),
190 "blinking_block" => Some(CursorStyle::BlinkingBlock),
191 "steady_block" => Some(CursorStyle::SteadyBlock),
192 "blinking_bar" => Some(CursorStyle::BlinkingBar),
193 "steady_bar" => Some(CursorStyle::SteadyBar),
194 "blinking_underline" => Some(CursorStyle::BlinkingUnderline),
195 "steady_underline" => Some(CursorStyle::SteadyUnderline),
196 _ => None,
197 }
198 }
199
200 pub fn as_str(self) -> &'static str {
202 match self {
203 Self::Default => "default",
204 Self::BlinkingBlock => "blinking_block",
205 Self::SteadyBlock => "steady_block",
206 Self::BlinkingBar => "blinking_bar",
207 Self::SteadyBar => "steady_bar",
208 Self::BlinkingUnderline => "blinking_underline",
209 Self::SteadyUnderline => "steady_underline",
210 }
211 }
212}
213
214impl JsonSchema for CursorStyle {
215 fn schema_name() -> Cow<'static, str> {
216 Cow::Borrowed("CursorStyle")
217 }
218
219 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
220 schemars::json_schema!({
221 "description": "Terminal cursor style",
222 "type": "string",
223 "enum": Self::OPTIONS
224 })
225 }
226}
227
228#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
230#[serde(transparent)]
231pub struct KeybindingMapName(pub String);
232
233impl KeybindingMapName {
234 pub const BUILTIN_OPTIONS: &'static [&'static str] =
236 &["default", "emacs", "vscode", "macos", "macos-gui"];
237}
238
239impl Deref for KeybindingMapName {
240 type Target = str;
241 fn deref(&self) -> &Self::Target {
242 &self.0
243 }
244}
245
246impl From<String> for KeybindingMapName {
247 fn from(s: String) -> Self {
248 Self(s)
249 }
250}
251
252impl From<&str> for KeybindingMapName {
253 fn from(s: &str) -> Self {
254 Self(s.to_string())
255 }
256}
257
258impl PartialEq<str> for KeybindingMapName {
259 fn eq(&self, other: &str) -> bool {
260 self.0 == other
261 }
262}
263
264#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
266#[serde(rename_all = "lowercase")]
267pub enum LineEndingOption {
268 #[default]
270 Lf,
271 Crlf,
273 Cr,
275}
276
277impl LineEndingOption {
278 pub fn to_line_ending(&self) -> crate::model::buffer::LineEnding {
280 match self {
281 Self::Lf => crate::model::buffer::LineEnding::LF,
282 Self::Crlf => crate::model::buffer::LineEnding::CRLF,
283 Self::Cr => crate::model::buffer::LineEnding::CR,
284 }
285 }
286}
287
288impl JsonSchema for LineEndingOption {
289 fn schema_name() -> Cow<'static, str> {
290 Cow::Borrowed("LineEndingOption")
291 }
292
293 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
294 schemars::json_schema!({
295 "description": "Default line ending format for new files",
296 "type": "string",
297 "enum": ["lf", "crlf", "cr"],
298 "default": "lf"
299 })
300 }
301}
302
303impl PartialEq<KeybindingMapName> for str {
304 fn eq(&self, other: &KeybindingMapName) -> bool {
305 self == other.0
306 }
307}
308
309impl JsonSchema for KeybindingMapName {
310 fn schema_name() -> Cow<'static, str> {
311 Cow::Borrowed("KeybindingMapOptions")
312 }
313
314 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
315 schemars::json_schema!({
316 "description": "Available keybinding maps",
317 "type": "string",
318 "enum": Self::BUILTIN_OPTIONS
319 })
320 }
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
325pub struct Config {
326 #[serde(default)]
329 pub version: u32,
330
331 #[serde(default = "default_theme_name")]
333 pub theme: ThemeName,
334
335 #[serde(default)]
338 pub locale: LocaleName,
339
340 #[serde(default = "default_true")]
343 pub check_for_updates: bool,
344
345 #[serde(default)]
347 pub editor: EditorConfig,
348
349 #[serde(default)]
351 pub file_explorer: FileExplorerConfig,
352
353 #[serde(default)]
355 pub file_browser: FileBrowserConfig,
356
357 #[serde(default)]
359 pub clipboard: ClipboardConfig,
360
361 #[serde(default)]
363 pub terminal: TerminalConfig,
364
365 #[serde(default)]
367 pub keybindings: Vec<Keybinding>,
368
369 #[serde(default)]
372 pub keybinding_maps: HashMap<String, KeymapConfig>,
373
374 #[serde(default = "default_keybinding_map_name")]
376 pub active_keybinding_map: KeybindingMapName,
377
378 #[serde(default)]
380 pub languages: HashMap<String, LanguageConfig>,
381
382 #[serde(default)]
388 #[schemars(extend("x-enum-from" = "/languages"))]
389 pub default_language: Option<String>,
390
391 #[serde(default)]
395 pub lsp: HashMap<String, LspLanguageConfig>,
396
397 #[serde(default)]
401 pub universal_lsp: HashMap<String, LspLanguageConfig>,
402
403 #[serde(default)]
405 pub warnings: WarningsConfig,
406
407 #[serde(default)]
411 #[schemars(extend("x-standalone-category" = true, "x-no-add" = true))]
412 pub plugins: HashMap<String, PluginConfig>,
413
414 #[serde(default)]
416 pub packages: PackagesConfig,
417}
418
419fn default_keybinding_map_name() -> KeybindingMapName {
420 if cfg!(target_os = "macos") {
423 KeybindingMapName("macos".to_string())
424 } else {
425 KeybindingMapName("default".to_string())
426 }
427}
428
429fn default_theme_name() -> ThemeName {
430 ThemeName("high-contrast".to_string())
431}
432
433#[derive(Debug, Clone, Copy)]
438pub struct WhitespaceVisibility {
439 pub spaces_leading: bool,
440 pub spaces_inner: bool,
441 pub spaces_trailing: bool,
442 pub tabs_leading: bool,
443 pub tabs_inner: bool,
444 pub tabs_trailing: bool,
445}
446
447impl Default for WhitespaceVisibility {
448 fn default() -> Self {
449 Self {
451 spaces_leading: false,
452 spaces_inner: false,
453 spaces_trailing: false,
454 tabs_leading: true,
455 tabs_inner: true,
456 tabs_trailing: true,
457 }
458 }
459}
460
461impl WhitespaceVisibility {
462 pub fn from_editor_config(editor: &EditorConfig) -> Self {
464 if !editor.whitespace_show {
465 return Self {
466 spaces_leading: false,
467 spaces_inner: false,
468 spaces_trailing: false,
469 tabs_leading: false,
470 tabs_inner: false,
471 tabs_trailing: false,
472 };
473 }
474 Self {
475 spaces_leading: editor.whitespace_spaces_leading,
476 spaces_inner: editor.whitespace_spaces_inner,
477 spaces_trailing: editor.whitespace_spaces_trailing,
478 tabs_leading: editor.whitespace_tabs_leading,
479 tabs_inner: editor.whitespace_tabs_inner,
480 tabs_trailing: editor.whitespace_tabs_trailing,
481 }
482 }
483
484 pub fn with_language_tab_override(mut self, show_whitespace_tabs: bool) -> Self {
487 if !show_whitespace_tabs {
488 self.tabs_leading = false;
489 self.tabs_inner = false;
490 self.tabs_trailing = false;
491 }
492 self
493 }
494
495 pub fn any_spaces(&self) -> bool {
497 self.spaces_leading || self.spaces_inner || self.spaces_trailing
498 }
499
500 pub fn any_tabs(&self) -> bool {
502 self.tabs_leading || self.tabs_inner || self.tabs_trailing
503 }
504
505 pub fn any_visible(&self) -> bool {
507 self.any_spaces() || self.any_tabs()
508 }
509
510 pub fn toggle_all(&mut self) {
514 if self.any_visible() {
515 *self = Self {
516 spaces_leading: false,
517 spaces_inner: false,
518 spaces_trailing: false,
519 tabs_leading: false,
520 tabs_inner: false,
521 tabs_trailing: false,
522 };
523 } else {
524 *self = Self::default();
525 }
526 }
527}
528
529#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
549#[serde(try_from = "String", into = "String")]
550pub enum StatusBarElement {
551 Filename,
553 Cursor,
555 CursorCompact,
557 Diagnostics,
559 CursorCount,
561 Messages,
563 Chord,
565 LineEnding,
567 Encoding,
569 Language,
571 Lsp,
573 Warnings,
575 Update,
577 Palette,
579 Clock,
581 RemoteIndicator,
586 CustomToken(String),
588}
589
590impl TryFrom<String> for StatusBarElement {
591 type Error = String;
592 fn try_from(s: String) -> Result<Self, String> {
593 let inner = s
595 .strip_prefix('{')
596 .and_then(|s| s.strip_suffix('}'))
597 .unwrap_or(&s);
598 match inner {
599 "filename" => Ok(Self::Filename),
600 "cursor" => Ok(Self::Cursor),
601 "cursor:compact" => Ok(Self::CursorCompact),
602 "diagnostics" => Ok(Self::Diagnostics),
603 "cursor_count" => Ok(Self::CursorCount),
604 "messages" => Ok(Self::Messages),
605 "chord" => Ok(Self::Chord),
606 "line_ending" => Ok(Self::LineEnding),
607 "encoding" => Ok(Self::Encoding),
608 "language" => Ok(Self::Language),
609 "lsp" => Ok(Self::Lsp),
610 "warnings" => Ok(Self::Warnings),
611 "update" => Ok(Self::Update),
612 "palette" => Ok(Self::Palette),
613 "clock" => Ok(Self::Clock),
614 "remote" => Ok(Self::RemoteIndicator),
615 _ => {
616 if inner.contains(':') {
618 Ok(Self::CustomToken(inner.to_string()))
619 } else {
620 Err(format!("Unknown status bar element: {}", s))
621 }
622 }
623 }
624 }
625}
626
627impl From<StatusBarElement> for String {
628 fn from(e: StatusBarElement) -> String {
629 match e {
630 StatusBarElement::Filename => "{filename}".to_string(),
631 StatusBarElement::Cursor => "{cursor}".to_string(),
632 StatusBarElement::CursorCompact => "{cursor:compact}".to_string(),
633 StatusBarElement::Diagnostics => "{diagnostics}".to_string(),
634 StatusBarElement::CursorCount => "{cursor_count}".to_string(),
635 StatusBarElement::Messages => "{messages}".to_string(),
636 StatusBarElement::Chord => "{chord}".to_string(),
637 StatusBarElement::LineEnding => "{line_ending}".to_string(),
638 StatusBarElement::Encoding => "{encoding}".to_string(),
639 StatusBarElement::Language => "{language}".to_string(),
640 StatusBarElement::Lsp => "{lsp}".to_string(),
641 StatusBarElement::Warnings => "{warnings}".to_string(),
642 StatusBarElement::Update => "{update}".to_string(),
643 StatusBarElement::Palette => "{palette}".to_string(),
644 StatusBarElement::Clock => "{clock}".to_string(),
645 StatusBarElement::RemoteIndicator => "{remote}".to_string(),
646 StatusBarElement::CustomToken(name) => format!("{{{}}}", name),
647 }
648 }
649}
650
651impl schemars::JsonSchema for StatusBarElement {
652 fn schema_name() -> std::borrow::Cow<'static, str> {
653 std::borrow::Cow::Borrowed("StatusBarElement")
654 }
655 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
656 schemars::json_schema!({
657 "type": "string",
658 "x-dual-list-options": [
659 {"value": "{filename}", "name": "Filename"},
660 {"value": "{cursor}", "name": "Cursor"},
661 {"value": "{cursor:compact}", "name": "Cursor (compact)"},
662 {"value": "{diagnostics}", "name": "Diagnostics"},
663 {"value": "{cursor_count}", "name": "Cursor Count"},
664 {"value": "{messages}", "name": "Messages"},
665 {"value": "{chord}", "name": "Chord"},
666 {"value": "{line_ending}", "name": "Line Ending"},
667 {"value": "{encoding}", "name": "Encoding"},
668 {"value": "{language}", "name": "Language"},
669 {"value": "{lsp}", "name": "LSP"},
670 {"value": "{warnings}", "name": "Warnings"},
671 {"value": "{update}", "name": "Update"},
672 {"value": "{palette}", "name": "Palette"},
673 {"value": "{clock}", "name": "Clock"},
674 {"value": "{remote}", "name": "Remote Indicator"}
675 ]
676 })
677 }
678}
679
680fn default_status_bar_left() -> Vec<StatusBarElement> {
681 vec![
693 StatusBarElement::RemoteIndicator,
694 StatusBarElement::Filename,
695 StatusBarElement::Cursor,
696 StatusBarElement::Diagnostics,
697 StatusBarElement::CursorCount,
698 StatusBarElement::Messages,
699 ]
700}
701
702fn default_status_bar_right() -> Vec<StatusBarElement> {
703 vec![
704 StatusBarElement::LineEnding,
705 StatusBarElement::Encoding,
706 StatusBarElement::Language,
707 StatusBarElement::Lsp,
708 StatusBarElement::Warnings,
709 StatusBarElement::Update,
710 StatusBarElement::Palette,
711 ]
712}
713
714#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
729pub struct StatusBarConfig {
730 #[serde(default = "default_status_bar_left")]
733 #[schemars(extend("x-section" = "Status Bar", "x-dual-list-sibling" = "/editor/status_bar/right", "x-dynamically-extendable-status-bar-elements" = true))]
734 pub left: Vec<StatusBarElement>,
735
736 #[serde(default = "default_status_bar_right")]
739 #[schemars(extend("x-section" = "Status Bar", "x-dual-list-sibling" = "/editor/status_bar/left", "x-dynamically-extendable-status-bar-elements" = true))]
740 pub right: Vec<StatusBarElement>,
741}
742
743impl Default for StatusBarConfig {
744 fn default() -> Self {
745 Self {
746 left: default_status_bar_left(),
747 right: default_status_bar_right(),
748 }
749 }
750}
751
752#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
754pub struct EditorConfig {
755 #[serde(default = "default_true")]
762 #[schemars(extend("x-section" = "Display"))]
763 pub animations: bool,
764
765 #[serde(default = "default_true")]
769 #[schemars(extend("x-section" = "Display"))]
770 pub cursor_jump_animation: bool,
771
772 #[serde(default = "default_true")]
774 #[schemars(extend("x-section" = "Display"))]
775 pub line_numbers: bool,
776
777 #[serde(default = "default_false")]
779 #[schemars(extend("x-section" = "Display"))]
780 pub relative_line_numbers: bool,
781
782 #[serde(default = "default_true")]
784 #[schemars(extend("x-section" = "Display"))]
785 pub highlight_current_line: bool,
786
787 #[serde(default = "default_false")]
789 #[schemars(extend("x-section" = "Display"))]
790 pub highlight_current_column: bool,
791
792 #[serde(default = "default_true")]
794 #[schemars(extend("x-section" = "Display"))]
795 pub line_wrap: bool,
796
797 #[serde(default = "default_true")]
799 #[schemars(extend("x-section" = "Display"))]
800 pub wrap_indent: bool,
801
802 #[serde(default)]
807 #[schemars(extend("x-section" = "Display"))]
808 pub wrap_column: Option<usize>,
809
810 #[serde(default = "default_page_width")]
814 #[schemars(extend("x-section" = "Display"))]
815 pub page_width: Option<usize>,
816
817 #[serde(default = "default_true")]
819 #[schemars(extend("x-section" = "Display"))]
820 pub syntax_highlighting: bool,
821
822 #[serde(default = "default_true")]
827 #[schemars(extend("x-section" = "Display"))]
828 pub show_menu_bar: bool,
829
830 #[serde(default = "default_true")]
835 #[schemars(extend("x-section" = "Display"))]
836 pub menu_bar_mnemonics: bool,
837
838 #[serde(default = "default_true")]
843 #[schemars(extend("x-section" = "Display"))]
844 pub show_tab_bar: bool,
845
846 #[serde(default = "default_true")]
851 #[schemars(extend("x-section" = "Display"))]
852 pub show_status_bar: bool,
853
854 #[serde(default)]
857 #[schemars(extend("x-section" = "Status Bar"))]
858 pub status_bar: StatusBarConfig,
859
860 #[serde(default = "default_false")]
867 #[schemars(extend("x-section" = "Display"))]
868 pub show_prompt_line: bool,
869
870 #[serde(default = "default_true")]
874 #[schemars(extend("x-section" = "Display"))]
875 pub show_vertical_scrollbar: bool,
876
877 #[serde(default = "default_false")]
882 #[schemars(extend("x-section" = "Display"))]
883 pub show_horizontal_scrollbar: bool,
884
885 #[serde(default = "default_true")]
889 #[schemars(extend("x-section" = "Display"))]
890 pub show_tilde: bool,
891
892 #[serde(default = "default_false")]
897 #[schemars(extend("x-section" = "Display"))]
898 pub use_terminal_bg: bool,
899
900 #[serde(default = "default_true")]
906 #[schemars(extend("x-section" = "Display"))]
907 pub set_window_title: bool,
908
909 #[serde(default)]
913 #[schemars(extend("x-section" = "Display"))]
914 pub cursor_style: CursorStyle,
915
916 #[serde(default)]
921 #[schemars(extend("x-section" = "Display"))]
922 pub rulers: Vec<usize>,
923
924 #[serde(default = "default_true")]
930 #[schemars(extend("x-section" = "Whitespace"))]
931 pub whitespace_show: bool,
932
933 #[serde(default = "default_false")]
937 #[schemars(extend("x-section" = "Whitespace"))]
938 pub whitespace_spaces_leading: bool,
939
940 #[serde(default = "default_false")]
944 #[schemars(extend("x-section" = "Whitespace"))]
945 pub whitespace_spaces_inner: bool,
946
947 #[serde(default = "default_false")]
951 #[schemars(extend("x-section" = "Whitespace"))]
952 pub whitespace_spaces_trailing: bool,
953
954 #[serde(default = "default_true")]
958 #[schemars(extend("x-section" = "Whitespace"))]
959 pub whitespace_tabs_leading: bool,
960
961 #[serde(default = "default_true")]
965 #[schemars(extend("x-section" = "Whitespace"))]
966 pub whitespace_tabs_inner: bool,
967
968 #[serde(default = "default_true")]
972 #[schemars(extend("x-section" = "Whitespace"))]
973 pub whitespace_tabs_trailing: bool,
974
975 #[serde(default = "default_false")]
981 #[schemars(extend("x-section" = "Editing"))]
982 pub use_tabs: bool,
983
984 #[serde(default = "default_tab_size")]
986 #[schemars(extend("x-section" = "Editing"))]
987 pub tab_size: usize,
988
989 #[serde(default = "default_true")]
991 #[schemars(extend("x-section" = "Editing"))]
992 pub auto_indent: bool,
993
994 #[serde(default = "default_true")]
1001 #[schemars(extend("x-section" = "Editing"))]
1002 pub auto_close: bool,
1003
1004 #[serde(default = "default_true")]
1009 #[schemars(extend("x-section" = "Editing"))]
1010 pub auto_surround: bool,
1011
1012 #[serde(default = "default_scroll_offset")]
1014 #[schemars(extend("x-section" = "Editing"))]
1015 pub scroll_offset: usize,
1016
1017 #[serde(default)]
1022 #[schemars(extend("x-section" = "Editing"))]
1023 pub default_line_ending: LineEndingOption,
1024
1025 #[serde(default = "default_false")]
1028 #[schemars(extend("x-section" = "Editing"))]
1029 pub trim_trailing_whitespace_on_save: bool,
1030
1031 #[serde(default = "default_false")]
1034 #[schemars(extend("x-section" = "Editing"))]
1035 pub ensure_final_newline_on_save: bool,
1036
1037 #[serde(default = "default_true")]
1041 #[schemars(extend("x-section" = "Bracket Matching"))]
1042 pub highlight_matching_brackets: bool,
1043
1044 #[serde(default = "default_true")]
1048 #[schemars(extend("x-section" = "Bracket Matching"))]
1049 pub rainbow_brackets: bool,
1050
1051 #[serde(default = "default_false")]
1058 #[schemars(extend("x-section" = "Completion"))]
1059 pub completion_popup_auto_show: bool,
1060
1061 #[serde(default = "default_true")]
1067 #[schemars(extend("x-section" = "Completion"))]
1068 pub quick_suggestions: bool,
1069
1070 #[serde(default = "default_quick_suggestions_delay")]
1076 #[schemars(extend("x-section" = "Completion"))]
1077 pub quick_suggestions_delay_ms: u64,
1078
1079 #[serde(default = "default_true")]
1083 #[schemars(extend("x-section" = "Completion"))]
1084 pub suggest_on_trigger_characters: bool,
1085
1086 #[serde(default = "default_true")]
1089 #[schemars(extend("x-section" = "LSP"))]
1090 pub enable_inlay_hints: bool,
1091
1092 #[serde(default = "default_false")]
1096 #[schemars(extend("x-section" = "LSP"))]
1097 pub enable_semantic_tokens_full: bool,
1098
1099 #[serde(default = "default_false")]
1104 #[schemars(extend("x-section" = "Diagnostics"))]
1105 pub diagnostics_inline_text: bool,
1106
1107 #[serde(default = "default_mouse_hover_enabled")]
1118 #[schemars(extend("x-section" = "Mouse"))]
1119 pub mouse_hover_enabled: bool,
1120
1121 #[serde(default = "default_mouse_hover_delay")]
1125 #[schemars(extend("x-section" = "Mouse"))]
1126 pub mouse_hover_delay_ms: u64,
1127
1128 #[serde(default = "default_double_click_time")]
1132 #[schemars(extend("x-section" = "Mouse"))]
1133 pub double_click_time_ms: u64,
1134
1135 #[serde(default = "default_false")]
1140 #[schemars(extend("x-section" = "Recovery"))]
1141 pub auto_save_enabled: bool,
1142
1143 #[serde(default = "default_auto_save_interval")]
1148 #[schemars(extend("x-section" = "Recovery"))]
1149 pub auto_save_interval_secs: u32,
1150
1151 #[serde(default = "default_true", alias = "persist_unnamed_buffers")]
1158 #[schemars(extend("x-section" = "Recovery"))]
1159 pub hot_exit: bool,
1160
1161 #[serde(default)]
1168 #[schemars(extend("x-section" = "Startup"))]
1169 pub confirm_quit: bool,
1170
1171 #[serde(default = "default_true")]
1182 #[schemars(extend("x-section" = "Startup"))]
1183 pub restore_previous_session: bool,
1184
1185 #[serde(default = "default_true")]
1196 #[schemars(extend("x-section" = "Startup"))]
1197 pub skip_session_restore_when_files_passed: bool,
1198
1199 #[serde(default = "default_true")]
1207 #[schemars(extend("x-section" = "Startup"))]
1208 pub auto_create_empty_buffer_on_last_buffer_close: bool,
1209
1210 #[serde(default = "default_true")]
1215 #[schemars(extend("x-section" = "Recovery"))]
1216 pub recovery_enabled: bool,
1217
1218 #[serde(default = "default_auto_recovery_save_interval")]
1223 #[schemars(extend("x-section" = "Recovery"))]
1224 pub auto_recovery_save_interval_secs: u32,
1225
1226 #[serde(default = "default_auto_revert_poll_interval")]
1231 #[schemars(extend("x-section" = "Recovery"))]
1232 pub auto_revert_poll_interval_ms: u64,
1233
1234 #[serde(default = "default_true")]
1240 #[schemars(extend("x-section" = "Keyboard"))]
1241 pub keyboard_disambiguate_escape_codes: bool,
1242
1243 #[serde(default = "default_false")]
1248 #[schemars(extend("x-section" = "Keyboard"))]
1249 pub keyboard_report_event_types: bool,
1250
1251 #[serde(default = "default_true")]
1256 #[schemars(extend("x-section" = "Keyboard"))]
1257 pub keyboard_report_alternate_keys: bool,
1258
1259 #[serde(default = "default_false")]
1265 #[schemars(extend("x-section" = "Keyboard"))]
1266 pub keyboard_report_all_keys_as_escape_codes: bool,
1267
1268 #[serde(default = "default_highlight_timeout")]
1271 #[schemars(extend("x-section" = "Performance"))]
1272 pub highlight_timeout_ms: u64,
1273
1274 #[serde(default = "default_snapshot_interval")]
1276 #[schemars(extend("x-section" = "Performance"))]
1277 pub snapshot_interval: usize,
1278
1279 #[serde(default = "default_highlight_context_bytes")]
1284 #[schemars(extend("x-section" = "Performance"))]
1285 pub highlight_context_bytes: usize,
1286
1287 #[serde(default = "default_large_file_threshold")]
1294 #[schemars(extend("x-section" = "Performance"))]
1295 pub large_file_threshold_bytes: u64,
1296
1297 #[serde(default = "default_estimated_line_length")]
1301 #[schemars(extend("x-section" = "Performance"))]
1302 pub estimated_line_length: usize,
1303
1304 #[serde(default = "default_read_concurrency")]
1309 #[schemars(extend("x-section" = "Performance"))]
1310 pub read_concurrency: usize,
1311
1312 #[serde(default = "default_file_tree_poll_interval")]
1317 #[schemars(extend("x-section" = "Performance"))]
1318 pub file_tree_poll_interval_ms: u64,
1319}
1320
1321fn default_tab_size() -> usize {
1322 4
1323}
1324
1325pub const LARGE_FILE_THRESHOLD_BYTES: u64 = 1024 * 1024; fn default_large_file_threshold() -> u64 {
1331 LARGE_FILE_THRESHOLD_BYTES
1332}
1333
1334pub const INDENT_FOLD_MAX_SCAN_LINES: usize = 10_000;
1337
1338pub const INDENT_FOLD_INDICATOR_MAX_SCAN: usize = 50;
1341
1342pub const INDENT_FOLD_MAX_UPWARD_SCAN: usize = 200;
1345
1346fn default_read_concurrency() -> usize {
1347 64
1348}
1349
1350fn default_true() -> bool {
1351 true
1352}
1353
1354fn default_false() -> bool {
1355 false
1356}
1357
1358fn default_quick_suggestions_delay() -> u64 {
1359 150 }
1361
1362fn default_scroll_offset() -> usize {
1363 3
1364}
1365
1366fn default_highlight_timeout() -> u64 {
1367 5
1368}
1369
1370fn default_snapshot_interval() -> usize {
1371 100
1372}
1373
1374fn default_estimated_line_length() -> usize {
1375 80
1376}
1377
1378fn default_auto_save_interval() -> u32 {
1379 30 }
1381
1382fn default_auto_recovery_save_interval() -> u32 {
1383 2 }
1385
1386fn default_highlight_context_bytes() -> usize {
1387 10_000 }
1389
1390fn default_mouse_hover_enabled() -> bool {
1391 !cfg!(windows)
1392}
1393
1394fn default_mouse_hover_delay() -> u64 {
1395 500 }
1397
1398fn default_double_click_time() -> u64 {
1399 500 }
1401
1402fn default_auto_revert_poll_interval() -> u64 {
1403 2000 }
1405
1406fn default_file_tree_poll_interval() -> u64 {
1407 3000 }
1409
1410impl Default for EditorConfig {
1411 fn default() -> Self {
1412 Self {
1413 use_tabs: false,
1414 tab_size: default_tab_size(),
1415 auto_indent: true,
1416 auto_close: true,
1417 auto_surround: true,
1418 animations: true,
1419 cursor_jump_animation: true,
1420 line_numbers: true,
1421 relative_line_numbers: false,
1422 scroll_offset: default_scroll_offset(),
1423 syntax_highlighting: true,
1424 highlight_current_line: true,
1425 highlight_current_column: false,
1426 line_wrap: true,
1427 wrap_indent: true,
1428 wrap_column: None,
1429 page_width: default_page_width(),
1430 highlight_timeout_ms: default_highlight_timeout(),
1431 snapshot_interval: default_snapshot_interval(),
1432 large_file_threshold_bytes: default_large_file_threshold(),
1433 estimated_line_length: default_estimated_line_length(),
1434 enable_inlay_hints: true,
1435 enable_semantic_tokens_full: false,
1436 diagnostics_inline_text: false,
1437 auto_save_enabled: false,
1438 auto_save_interval_secs: default_auto_save_interval(),
1439 hot_exit: true,
1440 confirm_quit: false,
1441 restore_previous_session: true,
1442 skip_session_restore_when_files_passed: true,
1443 auto_create_empty_buffer_on_last_buffer_close: true,
1444 recovery_enabled: true,
1445 auto_recovery_save_interval_secs: default_auto_recovery_save_interval(),
1446 highlight_context_bytes: default_highlight_context_bytes(),
1447 mouse_hover_enabled: default_mouse_hover_enabled(),
1448 mouse_hover_delay_ms: default_mouse_hover_delay(),
1449 double_click_time_ms: default_double_click_time(),
1450 auto_revert_poll_interval_ms: default_auto_revert_poll_interval(),
1451 read_concurrency: default_read_concurrency(),
1452 file_tree_poll_interval_ms: default_file_tree_poll_interval(),
1453 default_line_ending: LineEndingOption::default(),
1454 trim_trailing_whitespace_on_save: false,
1455 ensure_final_newline_on_save: false,
1456 highlight_matching_brackets: true,
1457 rainbow_brackets: true,
1458 cursor_style: CursorStyle::default(),
1459 keyboard_disambiguate_escape_codes: true,
1460 keyboard_report_event_types: false,
1461 keyboard_report_alternate_keys: true,
1462 keyboard_report_all_keys_as_escape_codes: false,
1463 completion_popup_auto_show: false,
1464 quick_suggestions: true,
1465 quick_suggestions_delay_ms: default_quick_suggestions_delay(),
1466 suggest_on_trigger_characters: true,
1467 show_menu_bar: true,
1468 menu_bar_mnemonics: true,
1469 show_tab_bar: true,
1470 show_status_bar: true,
1471 status_bar: StatusBarConfig::default(),
1472 show_prompt_line: false,
1473 show_vertical_scrollbar: true,
1474 show_horizontal_scrollbar: false,
1475 show_tilde: true,
1476 use_terminal_bg: false,
1477 set_window_title: true,
1478 rulers: Vec::new(),
1479 whitespace_show: true,
1480 whitespace_spaces_leading: false,
1481 whitespace_spaces_inner: false,
1482 whitespace_spaces_trailing: false,
1483 whitespace_tabs_leading: true,
1484 whitespace_tabs_inner: true,
1485 whitespace_tabs_trailing: true,
1486 }
1487 }
1488}
1489
1490#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
1492#[serde(rename_all = "snake_case")]
1493pub enum FileExplorerSide {
1494 #[default]
1495 Left,
1496 Right,
1497}
1498
1499#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1501pub struct FileExplorerConfig {
1502 #[serde(default = "default_true")]
1504 pub respect_gitignore: bool,
1505
1506 #[serde(default = "default_false")]
1508 pub show_hidden: bool,
1509
1510 #[serde(default = "default_false")]
1512 pub show_gitignored: bool,
1513
1514 #[serde(default)]
1516 pub custom_ignore_patterns: Vec<String>,
1517
1518 #[serde(default = "default_explorer_width")]
1524 pub width: ExplorerWidth,
1525
1526 #[serde(default = "default_true")]
1533 pub preview_tabs: bool,
1534
1535 #[serde(default = "default_explorer_side")]
1538 pub side: FileExplorerSide,
1539
1540 #[serde(default = "default_true")]
1546 pub auto_open_on_last_buffer_close: bool,
1547
1548 #[serde(default = "default_false")]
1554 pub follow_active_buffer: bool,
1555
1556 #[serde(default = "default_true")]
1563 pub compact_directories: bool,
1564
1565 #[serde(default = "default_tree_indicator_collapsed")]
1571 pub tree_indicator_collapsed: String,
1572
1573 #[serde(default = "default_tree_indicator_expanded")]
1579 pub tree_indicator_expanded: String,
1580}
1581
1582#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1610pub enum ExplorerWidth {
1611 Percent(u8),
1612 Columns(u16),
1613}
1614
1615impl ExplorerWidth {
1616 pub const DEFAULT: Self = Self::Percent(30);
1618
1619 pub const MIN_COLS: u16 = 5;
1625
1626 pub fn to_cols(self, terminal_width: u16) -> u16 {
1634 let raw = match self {
1635 Self::Percent(pct) => ((terminal_width as u32 * pct as u32) / 100) as u16,
1636 Self::Columns(cols) => cols,
1637 };
1638 raw.max(Self::MIN_COLS).min(terminal_width)
1639 }
1640}
1641
1642impl Default for ExplorerWidth {
1643 fn default() -> Self {
1644 Self::DEFAULT
1645 }
1646}
1647
1648impl std::fmt::Display for ExplorerWidth {
1649 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1650 match self {
1651 Self::Percent(n) => write!(f, "{}%", n),
1652 Self::Columns(n) => write!(f, "{}", n),
1653 }
1654 }
1655}
1656
1657#[derive(Debug)]
1659pub struct ExplorerWidthParseError(String);
1660
1661impl std::fmt::Display for ExplorerWidthParseError {
1662 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1663 write!(f, "{}", self.0)
1664 }
1665}
1666
1667impl std::error::Error for ExplorerWidthParseError {}
1668
1669impl std::str::FromStr for ExplorerWidth {
1670 type Err = ExplorerWidthParseError;
1671
1672 fn from_str(s: &str) -> Result<Self, Self::Err> {
1673 let s = s.trim();
1674 if s.is_empty() {
1675 return Err(ExplorerWidthParseError(
1676 "explorer width: empty string".into(),
1677 ));
1678 }
1679 if let Some(rest) = s.strip_suffix('%') {
1680 let n: u16 = rest.trim().parse().map_err(|_| {
1681 ExplorerWidthParseError(format!("explorer width: {:?} is not a valid percent", s))
1682 })?;
1683 if n > 100 {
1684 return Err(ExplorerWidthParseError(format!(
1685 "explorer width: {}% exceeds 100%",
1686 n
1687 )));
1688 }
1689 Ok(Self::Percent(n as u8))
1690 } else {
1691 let n: u16 = s.parse().map_err(|_| {
1692 ExplorerWidthParseError(format!(
1693 "explorer width: {:?} is neither a percent (e.g. \"30%\") nor a column count (e.g. \"24\")",
1694 s
1695 ))
1696 })?;
1697 Ok(Self::Columns(n))
1698 }
1699 }
1700}
1701
1702impl serde::Serialize for ExplorerWidth {
1703 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1704 s.collect_str(self)
1705 }
1706}
1707
1708impl<'de> serde::Deserialize<'de> for ExplorerWidth {
1709 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
1710 let raw = serde_json::Value::deserialize(d)?;
1711 explorer_width::from_value(&raw)
1712 }
1713}
1714
1715impl schemars::JsonSchema for ExplorerWidth {
1716 fn schema_name() -> std::borrow::Cow<'static, str> {
1717 std::borrow::Cow::Borrowed("ExplorerWidth")
1718 }
1719
1720 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
1721 schemars::json_schema!({
1725 "type": "string",
1726 "pattern": r"^(100%|[1-9]?[0-9]%|\d+)$",
1727 "description": "Either a percent like \"30%\" (0–100) or an absolute column count like \"24\".",
1728 })
1729 }
1730}
1731
1732fn default_explorer_width() -> ExplorerWidth {
1733 ExplorerWidth::DEFAULT
1734}
1735
1736fn default_explorer_side() -> FileExplorerSide {
1737 FileExplorerSide::default()
1738}
1739
1740fn default_tree_indicator_collapsed() -> String {
1741 ">".to_string()
1742}
1743
1744fn default_tree_indicator_expanded() -> String {
1745 "▼".to_string()
1746}
1747
1748pub fn default_explorer_width_value() -> ExplorerWidth {
1750 ExplorerWidth::DEFAULT
1751}
1752
1753pub(crate) mod explorer_width {
1757 use super::ExplorerWidth;
1758 use serde::de::{self, Deserialize, Deserializer};
1759 use std::str::FromStr;
1760
1761 pub fn deserialize_optional<'de, D>(d: D) -> Result<Option<ExplorerWidth>, D::Error>
1766 where
1767 D: Deserializer<'de>,
1768 {
1769 let raw = Option::<serde_json::Value>::deserialize(d)?;
1770 match raw {
1771 None | Some(serde_json::Value::Null) => Ok(None),
1772 Some(v) => from_value(&v).map(Some),
1773 }
1774 }
1775
1776 pub(super) fn from_value<E: de::Error>(v: &serde_json::Value) -> Result<ExplorerWidth, E> {
1777 match v {
1778 serde_json::Value::String(s) => ExplorerWidth::from_str(s).map_err(E::custom),
1779 serde_json::Value::Number(n) => {
1780 if let Some(u) = n.as_u64() {
1781 if u > 100 {
1785 return Err(E::custom(format!(
1786 "explorer width: {} exceeds 100 (percent). Use \"{}\" for columns.",
1787 u, u
1788 )));
1789 }
1790 Ok(ExplorerWidth::Percent(u as u8))
1791 } else if let Some(f) = n.as_f64() {
1792 let pct = if (0.0..=1.0).contains(&f) {
1794 f * 100.0
1795 } else {
1796 f
1797 };
1798 if !(0.0..=100.0).contains(&pct) {
1799 return Err(E::custom(format!(
1800 "explorer width: percent {} out of range 0..=100",
1801 pct
1802 )));
1803 }
1804 Ok(ExplorerWidth::Percent(pct.round() as u8))
1805 } else {
1806 Err(E::custom("explorer width: unsupported number"))
1807 }
1808 }
1809 _ => Err(E::custom(
1810 "explorer width: expected \"30%\", \"24\" (columns), or a number",
1811 )),
1812 }
1813 }
1814}
1815
1816#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1827pub struct ClipboardConfig {
1828 #[serde(default = "default_true")]
1831 pub use_osc52: bool,
1832
1833 #[serde(default = "default_true")]
1836 pub use_system_clipboard: bool,
1837}
1838
1839impl Default for ClipboardConfig {
1840 fn default() -> Self {
1841 Self {
1842 use_osc52: true,
1843 use_system_clipboard: true,
1844 }
1845 }
1846}
1847
1848#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1850pub struct TerminalConfig {
1851 #[serde(default = "default_true")]
1854 pub jump_to_end_on_output: bool,
1855
1856 #[serde(default)]
1868 pub shell: Option<TerminalShellConfig>,
1869
1870 #[serde(default = "default_true")]
1882 pub skip_app_execution_alias: bool,
1883}
1884
1885impl Default for TerminalConfig {
1886 fn default() -> Self {
1887 Self {
1888 jump_to_end_on_output: true,
1889 shell: None,
1890 skip_app_execution_alias: true,
1891 }
1892 }
1893}
1894
1895#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1897pub struct TerminalShellConfig {
1898 pub command: String,
1901
1902 #[serde(default)]
1904 pub args: Vec<String>,
1905}
1906
1907#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1909pub struct WarningsConfig {
1910 #[serde(default = "default_true")]
1913 pub show_status_indicator: bool,
1914}
1915
1916impl Default for WarningsConfig {
1917 fn default() -> Self {
1918 Self {
1919 show_status_indicator: true,
1920 }
1921 }
1922}
1923
1924#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1926pub struct PackagesConfig {
1927 #[serde(default = "default_package_sources")]
1930 pub sources: Vec<String>,
1931}
1932
1933fn default_package_sources() -> Vec<String> {
1934 vec!["https://github.com/sinelaw/fresh-plugins-registry".to_string()]
1935}
1936
1937impl Default for PackagesConfig {
1938 fn default() -> Self {
1939 Self {
1940 sources: default_package_sources(),
1941 }
1942 }
1943}
1944
1945pub use fresh_core::config::PluginConfig;
1947
1948impl Default for FileExplorerConfig {
1949 fn default() -> Self {
1950 Self {
1951 respect_gitignore: true,
1952 show_hidden: false,
1953 show_gitignored: false,
1954 custom_ignore_patterns: Vec::new(),
1955 width: default_explorer_width(),
1956 preview_tabs: true,
1957 side: default_explorer_side(),
1958 auto_open_on_last_buffer_close: true,
1959 follow_active_buffer: false,
1960 compact_directories: true,
1961 tree_indicator_collapsed: default_tree_indicator_collapsed(),
1962 tree_indicator_expanded: default_tree_indicator_expanded(),
1963 }
1964 }
1965}
1966
1967#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
1969pub struct FileBrowserConfig {
1970 #[serde(default = "default_false")]
1972 pub show_hidden: bool,
1973}
1974
1975#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1977pub struct KeyPress {
1978 pub key: String,
1980 #[serde(default)]
1982 pub modifiers: Vec<String>,
1983}
1984
1985#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1987#[schemars(extend("x-display-field" = "/action"))]
1988pub struct Keybinding {
1989 #[serde(default, skip_serializing_if = "String::is_empty")]
1991 pub key: String,
1992
1993 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1995 pub modifiers: Vec<String>,
1996
1997 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2000 pub keys: Vec<KeyPress>,
2001
2002 pub action: String,
2004
2005 #[serde(default)]
2007 pub args: HashMap<String, serde_json::Value>,
2008
2009 #[serde(default)]
2011 pub when: Option<String>,
2012}
2013
2014#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2016#[schemars(extend("x-display-field" = "/inherits"))]
2017pub struct KeymapConfig {
2018 #[serde(default, skip_serializing_if = "Option::is_none")]
2020 pub inherits: Option<String>,
2021
2022 #[serde(default)]
2024 pub bindings: Vec<Keybinding>,
2025}
2026
2027#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2029#[schemars(extend("x-display-field" = "/command"))]
2030pub struct FormatterConfig {
2031 pub command: String,
2033
2034 #[serde(default)]
2037 pub args: Vec<String>,
2038
2039 #[serde(default = "default_true")]
2042 pub stdin: bool,
2043
2044 #[serde(default = "default_on_save_timeout")]
2046 pub timeout_ms: u64,
2047}
2048
2049#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2051#[schemars(extend("x-display-field" = "/command"))]
2052pub struct OnSaveAction {
2053 pub command: String,
2056
2057 #[serde(default)]
2060 pub args: Vec<String>,
2061
2062 #[serde(default)]
2064 pub working_dir: Option<String>,
2065
2066 #[serde(default)]
2068 pub stdin: bool,
2069
2070 #[serde(default = "default_on_save_timeout")]
2072 pub timeout_ms: u64,
2073
2074 #[serde(default = "default_true")]
2077 pub enabled: bool,
2078}
2079
2080fn default_on_save_timeout() -> u64 {
2081 10000
2082}
2083
2084fn default_page_width() -> Option<usize> {
2085 Some(80)
2086}
2087
2088#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2090#[schemars(extend("x-display-field" = "/grammar"))]
2091pub struct LanguageConfig {
2092 #[serde(default)]
2094 pub extensions: Vec<String>,
2095
2096 #[serde(default)]
2098 pub filenames: Vec<String>,
2099
2100 #[serde(default)]
2102 pub grammar: String,
2103
2104 #[serde(default)]
2106 pub comment_prefix: Option<String>,
2107
2108 #[serde(default = "default_true")]
2110 pub auto_indent: bool,
2111
2112 #[serde(default)]
2115 pub auto_close: Option<bool>,
2116
2117 #[serde(default)]
2120 pub auto_surround: Option<bool>,
2121
2122 #[serde(default)]
2125 pub textmate_grammar: Option<std::path::PathBuf>,
2126
2127 #[serde(default = "default_true")]
2130 pub show_whitespace_tabs: bool,
2131
2132 #[serde(default)]
2137 pub line_wrap: Option<bool>,
2138
2139 #[serde(default)]
2142 pub wrap_column: Option<usize>,
2143
2144 #[serde(default)]
2149 pub page_view: Option<bool>,
2150
2151 #[serde(default)]
2155 pub page_width: Option<usize>,
2156
2157 #[serde(default)]
2161 pub use_tabs: Option<bool>,
2162
2163 #[serde(default)]
2166 pub tab_size: Option<usize>,
2167
2168 #[serde(default)]
2170 pub formatter: Option<FormatterConfig>,
2171
2172 #[serde(default)]
2174 pub format_on_save: bool,
2175
2176 #[serde(default)]
2180 pub on_save: Vec<OnSaveAction>,
2181
2182 #[serde(default)]
2192 pub word_characters: Option<String>,
2193}
2194
2195#[derive(Debug, Clone)]
2202pub struct BufferConfig {
2203 pub tab_size: usize,
2205
2206 pub use_tabs: bool,
2208
2209 pub auto_indent: bool,
2211
2212 pub auto_close: bool,
2214
2215 pub auto_surround: bool,
2217
2218 pub line_wrap: bool,
2220
2221 pub wrap_column: Option<usize>,
2223
2224 pub whitespace: WhitespaceVisibility,
2226
2227 pub formatter: Option<FormatterConfig>,
2229
2230 pub format_on_save: bool,
2232
2233 pub on_save: Vec<OnSaveAction>,
2235
2236 pub textmate_grammar: Option<std::path::PathBuf>,
2238
2239 pub word_characters: String,
2242}
2243
2244impl BufferConfig {
2245 pub fn resolve(global_config: &Config, language_id: Option<&str>) -> Self {
2254 let editor = &global_config.editor;
2255
2256 let mut whitespace = WhitespaceVisibility::from_editor_config(editor);
2258 let mut config = BufferConfig {
2259 tab_size: editor.tab_size,
2260 use_tabs: editor.use_tabs,
2261 auto_indent: editor.auto_indent,
2262 auto_close: editor.auto_close,
2263 auto_surround: editor.auto_surround,
2264 line_wrap: editor.line_wrap,
2265 wrap_column: editor.wrap_column,
2266 whitespace,
2267 formatter: None,
2268 format_on_save: false,
2269 on_save: Vec::new(),
2270 textmate_grammar: None,
2271 word_characters: String::new(),
2272 };
2273
2274 let lang_config_ref = language_id
2278 .and_then(|id| global_config.languages.get(id))
2279 .or_else(|| {
2280 match language_id {
2282 None | Some("text") => global_config
2283 .default_language
2284 .as_deref()
2285 .and_then(|lang| global_config.languages.get(lang)),
2286 _ => None,
2287 }
2288 });
2289 if let Some(lang_config) = lang_config_ref {
2290 if let Some(ts) = lang_config.tab_size {
2292 config.tab_size = ts;
2293 }
2294
2295 if let Some(use_tabs) = lang_config.use_tabs {
2297 config.use_tabs = use_tabs;
2298 }
2299
2300 if let Some(line_wrap) = lang_config.line_wrap {
2302 config.line_wrap = line_wrap;
2303 }
2304
2305 if lang_config.wrap_column.is_some() {
2307 config.wrap_column = lang_config.wrap_column;
2308 }
2309
2310 config.auto_indent = lang_config.auto_indent;
2312
2313 if config.auto_close {
2315 if let Some(lang_auto_close) = lang_config.auto_close {
2316 config.auto_close = lang_auto_close;
2317 }
2318 }
2319
2320 if config.auto_surround {
2322 if let Some(lang_auto_surround) = lang_config.auto_surround {
2323 config.auto_surround = lang_auto_surround;
2324 }
2325 }
2326
2327 whitespace = whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
2329 config.whitespace = whitespace;
2330
2331 config.formatter = lang_config.formatter.clone();
2333
2334 config.format_on_save = lang_config.format_on_save;
2336
2337 config.on_save = lang_config.on_save.clone();
2339
2340 config.textmate_grammar = lang_config.textmate_grammar.clone();
2342
2343 if let Some(ref wc) = lang_config.word_characters {
2345 config.word_characters = wc.clone();
2346 }
2347 }
2348
2349 config
2350 }
2351
2352 pub fn indent_string(&self) -> String {
2357 if self.use_tabs {
2358 "\t".to_string()
2359 } else {
2360 " ".repeat(self.tab_size)
2361 }
2362 }
2363}
2364
2365#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
2367pub struct MenuConfig {
2368 #[serde(default)]
2370 pub menus: Vec<Menu>,
2371}
2372
2373pub use fresh_core::menu::{Menu, MenuItem};
2375
2376pub trait MenuExt {
2378 fn match_id(&self) -> &str;
2381
2382 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path);
2385}
2386
2387impl MenuExt for Menu {
2388 fn match_id(&self) -> &str {
2389 self.id.as_deref().unwrap_or(&self.label)
2390 }
2391
2392 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path) {
2393 self.items = self
2394 .items
2395 .iter()
2396 .map(|item| item.expand_dynamic(themes_dir))
2397 .collect();
2398 }
2399}
2400
2401pub trait MenuItemExt {
2403 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem;
2406}
2407
2408impl MenuItemExt for MenuItem {
2409 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem {
2410 match self {
2411 MenuItem::DynamicSubmenu { label, source } => {
2412 let items = generate_dynamic_items(source, themes_dir);
2413 MenuItem::Submenu {
2414 label: label.clone(),
2415 items,
2416 }
2417 }
2418 other => other.clone(),
2419 }
2420 }
2421}
2422
2423#[cfg(feature = "runtime")]
2425pub fn generate_dynamic_items(source: &str, themes_dir: &std::path::Path) -> Vec<MenuItem> {
2426 match source {
2427 "copy_with_theme" => {
2428 let loader = crate::view::theme::ThemeLoader::new(themes_dir.to_path_buf());
2430 let registry = loader.load_all(&[]);
2431 registry
2432 .list()
2433 .iter()
2434 .map(|info| {
2435 let mut args = HashMap::new();
2436 args.insert("theme".to_string(), serde_json::json!(info.key));
2437 MenuItem::Action {
2438 label: info.name.clone(),
2439 action: "copy_with_theme".to_string(),
2440 args,
2441 when: Some(context_keys::HAS_SELECTION.to_string()),
2442 checkbox: None,
2443 }
2444 })
2445 .collect()
2446 }
2447 _ => vec![MenuItem::Label {
2448 info: format!("Unknown source: {}", source),
2449 }],
2450 }
2451}
2452
2453#[cfg(not(feature = "runtime"))]
2455pub fn generate_dynamic_items(_source: &str, _themes_dir: &std::path::Path) -> Vec<MenuItem> {
2456 vec![]
2458}
2459
2460impl Default for Config {
2461 fn default() -> Self {
2462 Self {
2463 version: 0,
2464 theme: default_theme_name(),
2465 locale: LocaleName::default(),
2466 check_for_updates: true,
2467 editor: EditorConfig::default(),
2468 file_explorer: FileExplorerConfig::default(),
2469 file_browser: FileBrowserConfig::default(),
2470 clipboard: ClipboardConfig::default(),
2471 terminal: TerminalConfig::default(),
2472 keybindings: vec![], keybinding_maps: HashMap::new(), active_keybinding_map: default_keybinding_map_name(),
2475 languages: Self::default_languages(),
2476 default_language: None,
2477 lsp: Self::default_lsp_config(),
2478 universal_lsp: Self::default_universal_lsp_config(),
2479 warnings: WarningsConfig::default(),
2480 plugins: HashMap::new(), packages: PackagesConfig::default(),
2482 }
2483 }
2484}
2485
2486impl MenuConfig {
2487 pub fn translated() -> Self {
2489 Self {
2490 menus: Self::translated_menus(),
2491 }
2492 }
2493
2494 pub fn translated_menus() -> Vec<Menu> {
2500 vec![
2501 Menu {
2503 id: Some("File".to_string()),
2504 label: t!("menu.file").to_string(),
2505 when: None,
2506 items: vec![
2507 MenuItem::Action {
2508 label: t!("menu.file.new_file").to_string(),
2509 action: "new".to_string(),
2510 args: HashMap::new(),
2511 when: None,
2512 checkbox: None,
2513 },
2514 MenuItem::Action {
2515 label: t!("menu.file.open_file").to_string(),
2516 action: "open".to_string(),
2517 args: HashMap::new(),
2518 when: None,
2519 checkbox: None,
2520 },
2521 MenuItem::Separator { separator: true },
2522 MenuItem::Action {
2523 label: t!("menu.file.save").to_string(),
2524 action: "save".to_string(),
2525 args: HashMap::new(),
2526 when: Some(context_keys::HAS_BUFFER.to_string()),
2527 checkbox: None,
2528 },
2529 MenuItem::Action {
2530 label: t!("menu.file.save_as").to_string(),
2531 action: "save_as".to_string(),
2532 args: HashMap::new(),
2533 when: Some(context_keys::HAS_BUFFER.to_string()),
2534 checkbox: None,
2535 },
2536 MenuItem::Action {
2537 label: t!("menu.file.revert").to_string(),
2538 action: "revert".to_string(),
2539 args: HashMap::new(),
2540 when: Some(context_keys::HAS_BUFFER.to_string()),
2541 checkbox: None,
2542 },
2543 MenuItem::Action {
2544 label: t!("menu.file.reload_with_encoding").to_string(),
2545 action: "reload_with_encoding".to_string(),
2546 args: HashMap::new(),
2547 when: Some(context_keys::HAS_BUFFER.to_string()),
2548 checkbox: None,
2549 },
2550 MenuItem::Separator { separator: true },
2551 MenuItem::Action {
2552 label: t!("menu.file.close_buffer").to_string(),
2553 action: "close".to_string(),
2554 args: HashMap::new(),
2555 when: Some(context_keys::HAS_BUFFER.to_string()),
2556 checkbox: None,
2557 },
2558 MenuItem::Separator { separator: true },
2559 MenuItem::Action {
2560 label: t!("menu.file.switch_project").to_string(),
2561 action: "switch_project".to_string(),
2562 args: HashMap::new(),
2563 when: None,
2564 checkbox: None,
2565 },
2566 MenuItem::Separator { separator: true },
2567 MenuItem::Action {
2568 label: t!("menu.file.detach").to_string(),
2569 action: "detach".to_string(),
2570 args: HashMap::new(),
2571 when: Some(context_keys::SESSION_MODE.to_string()),
2572 checkbox: None,
2573 },
2574 MenuItem::Action {
2575 label: t!("menu.file.quit").to_string(),
2576 action: "quit".to_string(),
2577 args: HashMap::new(),
2578 when: None,
2579 checkbox: None,
2580 },
2581 ],
2582 },
2583 Menu {
2585 id: Some("Edit".to_string()),
2586 label: t!("menu.edit").to_string(),
2587 when: None,
2588 items: vec![
2589 MenuItem::Action {
2590 label: t!("menu.edit.undo").to_string(),
2591 action: "undo".to_string(),
2592 args: HashMap::new(),
2593 when: Some(context_keys::HAS_BUFFER.to_string()),
2594 checkbox: None,
2595 },
2596 MenuItem::Action {
2597 label: t!("menu.edit.redo").to_string(),
2598 action: "redo".to_string(),
2599 args: HashMap::new(),
2600 when: Some(context_keys::HAS_BUFFER.to_string()),
2601 checkbox: None,
2602 },
2603 MenuItem::Separator { separator: true },
2604 MenuItem::Action {
2605 label: t!("menu.edit.cut").to_string(),
2606 action: "cut".to_string(),
2607 args: HashMap::new(),
2608 when: Some(context_keys::CAN_COPY.to_string()),
2609 checkbox: None,
2610 },
2611 MenuItem::Action {
2612 label: t!("menu.edit.copy").to_string(),
2613 action: "copy".to_string(),
2614 args: HashMap::new(),
2615 when: Some(context_keys::CAN_COPY.to_string()),
2616 checkbox: None,
2617 },
2618 MenuItem::DynamicSubmenu {
2619 label: t!("menu.edit.copy_with_formatting").to_string(),
2620 source: "copy_with_theme".to_string(),
2621 },
2622 MenuItem::Action {
2623 label: t!("menu.edit.paste").to_string(),
2624 action: "paste".to_string(),
2625 args: HashMap::new(),
2626 when: Some(context_keys::CAN_PASTE.to_string()),
2627 checkbox: None,
2628 },
2629 MenuItem::Separator { separator: true },
2630 MenuItem::Action {
2631 label: t!("menu.edit.select_all").to_string(),
2632 action: "select_all".to_string(),
2633 args: HashMap::new(),
2634 when: Some(context_keys::HAS_BUFFER.to_string()),
2635 checkbox: None,
2636 },
2637 MenuItem::Separator { separator: true },
2638 MenuItem::Action {
2639 label: t!("menu.edit.find").to_string(),
2640 action: "search".to_string(),
2641 args: HashMap::new(),
2642 when: Some(context_keys::HAS_BUFFER.to_string()),
2643 checkbox: None,
2644 },
2645 MenuItem::Action {
2646 label: t!("menu.edit.find_in_selection").to_string(),
2647 action: "find_in_selection".to_string(),
2648 args: HashMap::new(),
2649 when: Some(context_keys::HAS_SELECTION.to_string()),
2650 checkbox: None,
2651 },
2652 MenuItem::Action {
2653 label: t!("menu.edit.find_next").to_string(),
2654 action: "find_next".to_string(),
2655 args: HashMap::new(),
2656 when: Some(context_keys::HAS_BUFFER.to_string()),
2657 checkbox: None,
2658 },
2659 MenuItem::Action {
2660 label: t!("menu.edit.find_previous").to_string(),
2661 action: "find_previous".to_string(),
2662 args: HashMap::new(),
2663 when: Some(context_keys::HAS_BUFFER.to_string()),
2664 checkbox: None,
2665 },
2666 MenuItem::Action {
2667 label: t!("menu.edit.replace").to_string(),
2668 action: "query_replace".to_string(),
2669 args: HashMap::new(),
2670 when: Some(context_keys::HAS_BUFFER.to_string()),
2671 checkbox: None,
2672 },
2673 MenuItem::Separator { separator: true },
2674 MenuItem::Action {
2675 label: t!("menu.edit.delete_line").to_string(),
2676 action: "delete_line".to_string(),
2677 args: HashMap::new(),
2678 when: Some(context_keys::HAS_BUFFER.to_string()),
2679 checkbox: None,
2680 },
2681 MenuItem::Action {
2682 label: t!("menu.edit.format_buffer").to_string(),
2683 action: "format_buffer".to_string(),
2684 args: HashMap::new(),
2685 when: Some(context_keys::FORMATTER_AVAILABLE.to_string()),
2686 checkbox: None,
2687 },
2688 MenuItem::Separator { separator: true },
2689 MenuItem::Action {
2690 label: t!("menu.edit.settings").to_string(),
2691 action: "open_settings".to_string(),
2692 args: HashMap::new(),
2693 when: None,
2694 checkbox: None,
2695 },
2696 MenuItem::Action {
2697 label: t!("menu.edit.keybinding_editor").to_string(),
2698 action: "open_keybinding_editor".to_string(),
2699 args: HashMap::new(),
2700 when: None,
2701 checkbox: None,
2702 },
2703 ],
2704 },
2705 Menu {
2707 id: Some("View".to_string()),
2708 label: t!("menu.view").to_string(),
2709 when: None,
2710 items: vec![
2711 MenuItem::Action {
2712 label: t!("menu.view.file_explorer").to_string(),
2713 action: "toggle_file_explorer".to_string(),
2714 args: HashMap::new(),
2715 when: None,
2716 checkbox: Some(context_keys::FILE_EXPLORER.to_string()),
2717 },
2718 MenuItem::Separator { separator: true },
2719 MenuItem::Action {
2720 label: t!("menu.view.line_numbers").to_string(),
2721 action: "toggle_line_numbers".to_string(),
2722 args: HashMap::new(),
2723 when: None,
2724 checkbox: Some(context_keys::LINE_NUMBERS.to_string()),
2725 },
2726 MenuItem::Action {
2727 label: t!("menu.view.line_wrap").to_string(),
2728 action: "toggle_line_wrap".to_string(),
2729 args: HashMap::new(),
2730 when: None,
2731 checkbox: Some(context_keys::LINE_WRAP.to_string()),
2732 },
2733 MenuItem::Action {
2734 label: t!("menu.view.mouse_support").to_string(),
2735 action: "toggle_mouse_capture".to_string(),
2736 args: HashMap::new(),
2737 when: None,
2738 checkbox: Some(context_keys::MOUSE_CAPTURE.to_string()),
2739 },
2740 MenuItem::Separator { separator: true },
2741 MenuItem::Action {
2742 label: t!("menu.view.vertical_scrollbar").to_string(),
2743 action: "toggle_vertical_scrollbar".to_string(),
2744 args: HashMap::new(),
2745 when: None,
2746 checkbox: Some(context_keys::VERTICAL_SCROLLBAR.to_string()),
2747 },
2748 MenuItem::Action {
2749 label: t!("menu.view.horizontal_scrollbar").to_string(),
2750 action: "toggle_horizontal_scrollbar".to_string(),
2751 args: HashMap::new(),
2752 when: None,
2753 checkbox: Some(context_keys::HORIZONTAL_SCROLLBAR.to_string()),
2754 },
2755 MenuItem::Separator { separator: true },
2756 MenuItem::Action {
2757 label: t!("menu.view.set_background").to_string(),
2758 action: "set_background".to_string(),
2759 args: HashMap::new(),
2760 when: None,
2761 checkbox: None,
2762 },
2763 MenuItem::Action {
2764 label: t!("menu.view.set_background_blend").to_string(),
2765 action: "set_background_blend".to_string(),
2766 args: HashMap::new(),
2767 when: None,
2768 checkbox: None,
2769 },
2770 MenuItem::Action {
2771 label: t!("menu.view.set_page_width").to_string(),
2772 action: "set_page_width".to_string(),
2773 args: HashMap::new(),
2774 when: None,
2775 checkbox: None,
2776 },
2777 MenuItem::Separator { separator: true },
2778 MenuItem::Action {
2779 label: t!("menu.view.select_theme").to_string(),
2780 action: "select_theme".to_string(),
2781 args: HashMap::new(),
2782 when: None,
2783 checkbox: None,
2784 },
2785 MenuItem::Action {
2786 label: t!("menu.view.select_locale").to_string(),
2787 action: "select_locale".to_string(),
2788 args: HashMap::new(),
2789 when: None,
2790 checkbox: None,
2791 },
2792 MenuItem::Action {
2793 label: t!("menu.view.settings").to_string(),
2794 action: "open_settings".to_string(),
2795 args: HashMap::new(),
2796 when: None,
2797 checkbox: None,
2798 },
2799 MenuItem::Action {
2800 label: t!("menu.view.calibrate_input").to_string(),
2801 action: "calibrate_input".to_string(),
2802 args: HashMap::new(),
2803 when: None,
2804 checkbox: None,
2805 },
2806 MenuItem::Separator { separator: true },
2807 MenuItem::Action {
2808 label: t!("menu.view.split_horizontal").to_string(),
2809 action: "split_horizontal".to_string(),
2810 args: HashMap::new(),
2811 when: Some(context_keys::HAS_BUFFER.to_string()),
2812 checkbox: None,
2813 },
2814 MenuItem::Action {
2815 label: t!("menu.view.split_vertical").to_string(),
2816 action: "split_vertical".to_string(),
2817 args: HashMap::new(),
2818 when: Some(context_keys::HAS_BUFFER.to_string()),
2819 checkbox: None,
2820 },
2821 MenuItem::Action {
2822 label: t!("menu.view.close_split").to_string(),
2823 action: "close_split".to_string(),
2824 args: HashMap::new(),
2825 when: Some(context_keys::HAS_BUFFER.to_string()),
2826 checkbox: None,
2827 },
2828 MenuItem::Action {
2829 label: t!("menu.view.scroll_sync").to_string(),
2830 action: "toggle_scroll_sync".to_string(),
2831 args: HashMap::new(),
2832 when: Some(context_keys::HAS_SAME_BUFFER_SPLITS.to_string()),
2833 checkbox: Some(context_keys::SCROLL_SYNC.to_string()),
2834 },
2835 MenuItem::Action {
2836 label: t!("menu.view.focus_next_split").to_string(),
2837 action: "next_split".to_string(),
2838 args: HashMap::new(),
2839 when: None,
2840 checkbox: None,
2841 },
2842 MenuItem::Action {
2843 label: t!("menu.view.focus_prev_split").to_string(),
2844 action: "prev_split".to_string(),
2845 args: HashMap::new(),
2846 when: None,
2847 checkbox: None,
2848 },
2849 MenuItem::Action {
2850 label: t!("menu.view.toggle_maximize_split").to_string(),
2851 action: "toggle_maximize_split".to_string(),
2852 args: HashMap::new(),
2853 when: None,
2854 checkbox: None,
2855 },
2856 MenuItem::Separator { separator: true },
2857 MenuItem::Submenu {
2858 label: t!("menu.terminal").to_string(),
2859 items: vec![
2860 MenuItem::Action {
2861 label: t!("menu.terminal.open").to_string(),
2862 action: "open_terminal".to_string(),
2863 args: HashMap::new(),
2864 when: None,
2865 checkbox: None,
2866 },
2867 MenuItem::Action {
2868 label: t!("menu.terminal.close").to_string(),
2869 action: "close_terminal".to_string(),
2870 args: HashMap::new(),
2871 when: None,
2872 checkbox: None,
2873 },
2874 MenuItem::Separator { separator: true },
2875 MenuItem::Action {
2876 label: t!("menu.terminal.toggle_keyboard_capture").to_string(),
2877 action: "toggle_keyboard_capture".to_string(),
2878 args: HashMap::new(),
2879 when: None,
2880 checkbox: None,
2881 },
2882 ],
2883 },
2884 MenuItem::Separator { separator: true },
2885 MenuItem::Submenu {
2886 label: t!("menu.view.keybinding_style").to_string(),
2887 items: vec![
2888 MenuItem::Action {
2889 label: t!("menu.view.keybinding_default").to_string(),
2890 action: "switch_keybinding_map".to_string(),
2891 args: {
2892 let mut map = HashMap::new();
2893 map.insert("map".to_string(), serde_json::json!("default"));
2894 map
2895 },
2896 when: None,
2897 checkbox: Some(context_keys::KEYMAP_DEFAULT.to_string()),
2898 },
2899 MenuItem::Action {
2900 label: t!("menu.view.keybinding_emacs").to_string(),
2901 action: "switch_keybinding_map".to_string(),
2902 args: {
2903 let mut map = HashMap::new();
2904 map.insert("map".to_string(), serde_json::json!("emacs"));
2905 map
2906 },
2907 when: None,
2908 checkbox: Some(context_keys::KEYMAP_EMACS.to_string()),
2909 },
2910 MenuItem::Action {
2911 label: t!("menu.view.keybinding_vscode").to_string(),
2912 action: "switch_keybinding_map".to_string(),
2913 args: {
2914 let mut map = HashMap::new();
2915 map.insert("map".to_string(), serde_json::json!("vscode"));
2916 map
2917 },
2918 when: None,
2919 checkbox: Some(context_keys::KEYMAP_VSCODE.to_string()),
2920 },
2921 MenuItem::Action {
2922 label: "macOS GUI (⌘)".to_string(),
2923 action: "switch_keybinding_map".to_string(),
2924 args: {
2925 let mut map = HashMap::new();
2926 map.insert("map".to_string(), serde_json::json!("macos-gui"));
2927 map
2928 },
2929 when: None,
2930 checkbox: Some(context_keys::KEYMAP_MACOS_GUI.to_string()),
2931 },
2932 ],
2933 },
2934 ],
2935 },
2936 Menu {
2938 id: Some("Selection".to_string()),
2939 label: t!("menu.selection").to_string(),
2940 when: Some(context_keys::HAS_BUFFER.to_string()),
2941 items: vec![
2942 MenuItem::Action {
2943 label: t!("menu.selection.select_all").to_string(),
2944 action: "select_all".to_string(),
2945 args: HashMap::new(),
2946 when: None,
2947 checkbox: None,
2948 },
2949 MenuItem::Action {
2950 label: t!("menu.selection.select_word").to_string(),
2951 action: "select_word".to_string(),
2952 args: HashMap::new(),
2953 when: None,
2954 checkbox: None,
2955 },
2956 MenuItem::Action {
2957 label: t!("menu.selection.select_line").to_string(),
2958 action: "select_line".to_string(),
2959 args: HashMap::new(),
2960 when: None,
2961 checkbox: None,
2962 },
2963 MenuItem::Action {
2964 label: t!("menu.selection.expand_selection").to_string(),
2965 action: "expand_selection".to_string(),
2966 args: HashMap::new(),
2967 when: None,
2968 checkbox: None,
2969 },
2970 MenuItem::Separator { separator: true },
2971 MenuItem::Action {
2972 label: t!("menu.selection.add_cursor_above").to_string(),
2973 action: "add_cursor_above".to_string(),
2974 args: HashMap::new(),
2975 when: None,
2976 checkbox: None,
2977 },
2978 MenuItem::Action {
2979 label: t!("menu.selection.add_cursor_below").to_string(),
2980 action: "add_cursor_below".to_string(),
2981 args: HashMap::new(),
2982 when: None,
2983 checkbox: None,
2984 },
2985 MenuItem::Action {
2986 label: t!("menu.selection.add_cursor_next_match").to_string(),
2987 action: "add_cursor_next_match".to_string(),
2988 args: HashMap::new(),
2989 when: None,
2990 checkbox: None,
2991 },
2992 MenuItem::Action {
2993 label: t!("menu.selection.add_cursors_to_line_ends").to_string(),
2994 action: "add_cursors_to_line_ends".to_string(),
2995 args: HashMap::new(),
2996 when: None,
2997 checkbox: None,
2998 },
2999 MenuItem::Action {
3000 label: t!("menu.selection.remove_secondary_cursors").to_string(),
3001 action: "remove_secondary_cursors".to_string(),
3002 args: HashMap::new(),
3003 when: None,
3004 checkbox: None,
3005 },
3006 ],
3007 },
3008 Menu {
3010 id: Some("Go".to_string()),
3011 label: t!("menu.go").to_string(),
3012 when: None,
3013 items: vec![
3014 MenuItem::Action {
3015 label: t!("menu.go.goto_line").to_string(),
3016 action: "goto_line".to_string(),
3017 args: HashMap::new(),
3018 when: Some(context_keys::HAS_BUFFER.to_string()),
3019 checkbox: None,
3020 },
3021 MenuItem::Action {
3022 label: t!("menu.go.goto_definition").to_string(),
3023 action: "lsp_goto_definition".to_string(),
3024 args: HashMap::new(),
3025 when: Some(context_keys::HAS_BUFFER.to_string()),
3026 checkbox: None,
3027 },
3028 MenuItem::Action {
3029 label: t!("menu.go.find_references").to_string(),
3030 action: "lsp_references".to_string(),
3031 args: HashMap::new(),
3032 when: Some(context_keys::HAS_BUFFER.to_string()),
3033 checkbox: None,
3034 },
3035 MenuItem::Separator { separator: true },
3036 MenuItem::Action {
3037 label: t!("menu.go.next_buffer").to_string(),
3038 action: "next_buffer".to_string(),
3039 args: HashMap::new(),
3040 when: Some(context_keys::HAS_BUFFER.to_string()),
3041 checkbox: None,
3042 },
3043 MenuItem::Action {
3044 label: t!("menu.go.prev_buffer").to_string(),
3045 action: "prev_buffer".to_string(),
3046 args: HashMap::new(),
3047 when: Some(context_keys::HAS_BUFFER.to_string()),
3048 checkbox: None,
3049 },
3050 MenuItem::Separator { separator: true },
3051 MenuItem::Action {
3052 label: t!("menu.go.command_palette").to_string(),
3053 action: "command_palette".to_string(),
3054 args: HashMap::new(),
3055 when: None,
3056 checkbox: None,
3057 },
3058 ],
3059 },
3060 Menu {
3062 id: Some("LSP".to_string()),
3063 label: t!("menu.lsp").to_string(),
3064 when: None,
3065 items: vec![
3066 MenuItem::Action {
3067 label: t!("menu.lsp.show_hover").to_string(),
3068 action: "lsp_hover".to_string(),
3069 args: HashMap::new(),
3070 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3071 checkbox: None,
3072 },
3073 MenuItem::Action {
3074 label: t!("menu.lsp.goto_definition").to_string(),
3075 action: "lsp_goto_definition".to_string(),
3076 args: HashMap::new(),
3077 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3078 checkbox: None,
3079 },
3080 MenuItem::Action {
3081 label: t!("menu.lsp.find_references").to_string(),
3082 action: "lsp_references".to_string(),
3083 args: HashMap::new(),
3084 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3085 checkbox: None,
3086 },
3087 MenuItem::Action {
3088 label: t!("menu.lsp.rename_symbol").to_string(),
3089 action: "lsp_rename".to_string(),
3090 args: HashMap::new(),
3091 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3092 checkbox: None,
3093 },
3094 MenuItem::Separator { separator: true },
3095 MenuItem::Action {
3096 label: t!("menu.lsp.show_completions").to_string(),
3097 action: "lsp_completion".to_string(),
3098 args: HashMap::new(),
3099 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3100 checkbox: None,
3101 },
3102 MenuItem::Action {
3103 label: t!("menu.lsp.show_signature").to_string(),
3104 action: "lsp_signature_help".to_string(),
3105 args: HashMap::new(),
3106 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3107 checkbox: None,
3108 },
3109 MenuItem::Action {
3110 label: t!("menu.lsp.code_actions").to_string(),
3111 action: "lsp_code_actions".to_string(),
3112 args: HashMap::new(),
3113 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3114 checkbox: None,
3115 },
3116 MenuItem::Separator { separator: true },
3117 MenuItem::Action {
3118 label: t!("menu.lsp.toggle_inlay_hints").to_string(),
3119 action: "toggle_inlay_hints".to_string(),
3120 args: HashMap::new(),
3121 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3122 checkbox: Some(context_keys::INLAY_HINTS.to_string()),
3123 },
3124 MenuItem::Action {
3125 label: t!("menu.lsp.toggle_mouse_hover").to_string(),
3126 action: "toggle_mouse_hover".to_string(),
3127 args: HashMap::new(),
3128 when: None,
3129 checkbox: Some(context_keys::MOUSE_HOVER.to_string()),
3130 },
3131 MenuItem::Separator { separator: true },
3132 MenuItem::Action {
3133 label: t!("menu.lsp.show_status").to_string(),
3134 action: "show_lsp_status".to_string(),
3135 args: HashMap::new(),
3136 when: None,
3137 checkbox: None,
3138 },
3139 MenuItem::Action {
3140 label: t!("menu.lsp.restart_server").to_string(),
3141 action: "lsp_restart".to_string(),
3142 args: HashMap::new(),
3143 when: None,
3144 checkbox: None,
3145 },
3146 MenuItem::Action {
3147 label: t!("menu.lsp.stop_server").to_string(),
3148 action: "lsp_stop".to_string(),
3149 args: HashMap::new(),
3150 when: None,
3151 checkbox: None,
3152 },
3153 MenuItem::Separator { separator: true },
3154 MenuItem::Action {
3155 label: t!("menu.lsp.toggle_for_buffer").to_string(),
3156 action: "lsp_toggle_for_buffer".to_string(),
3157 args: HashMap::new(),
3158 when: Some(context_keys::HAS_BUFFER.to_string()),
3159 checkbox: None,
3160 },
3161 ],
3162 },
3163 Menu {
3165 id: Some("Explorer".to_string()),
3166 label: t!("menu.explorer").to_string(),
3167 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3168 items: vec![
3169 MenuItem::Action {
3170 label: t!("menu.explorer.new_file").to_string(),
3171 action: "file_explorer_new_file".to_string(),
3172 args: HashMap::new(),
3173 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3174 checkbox: None,
3175 },
3176 MenuItem::Action {
3177 label: t!("menu.explorer.new_folder").to_string(),
3178 action: "file_explorer_new_directory".to_string(),
3179 args: HashMap::new(),
3180 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3181 checkbox: None,
3182 },
3183 MenuItem::Separator { separator: true },
3184 MenuItem::Action {
3185 label: t!("menu.explorer.open").to_string(),
3186 action: "file_explorer_open".to_string(),
3187 args: HashMap::new(),
3188 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3189 checkbox: None,
3190 },
3191 MenuItem::Action {
3192 label: t!("menu.explorer.rename").to_string(),
3193 action: "file_explorer_rename".to_string(),
3194 args: HashMap::new(),
3195 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3196 checkbox: None,
3197 },
3198 MenuItem::Action {
3199 label: t!("menu.explorer.delete").to_string(),
3200 action: "file_explorer_delete".to_string(),
3201 args: HashMap::new(),
3202 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3203 checkbox: None,
3204 },
3205 MenuItem::Separator { separator: true },
3206 MenuItem::Action {
3207 label: t!("menu.explorer.cut").to_string(),
3208 action: "cut".to_string(),
3209 args: HashMap::new(),
3210 when: Some(context_keys::CAN_COPY.to_string()),
3211 checkbox: None,
3212 },
3213 MenuItem::Action {
3214 label: t!("menu.explorer.copy").to_string(),
3215 action: "copy".to_string(),
3216 args: HashMap::new(),
3217 when: Some(context_keys::CAN_COPY.to_string()),
3218 checkbox: None,
3219 },
3220 MenuItem::Action {
3221 label: t!("menu.explorer.paste").to_string(),
3222 action: "paste".to_string(),
3223 args: HashMap::new(),
3224 when: Some(context_keys::CAN_PASTE.to_string()),
3225 checkbox: None,
3226 },
3227 MenuItem::Separator { separator: true },
3228 MenuItem::Action {
3229 label: t!("menu.explorer.refresh").to_string(),
3230 action: "file_explorer_refresh".to_string(),
3231 args: HashMap::new(),
3232 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3233 checkbox: None,
3234 },
3235 MenuItem::Separator { separator: true },
3236 MenuItem::Action {
3237 label: t!("menu.explorer.show_hidden").to_string(),
3238 action: "file_explorer_toggle_hidden".to_string(),
3239 args: HashMap::new(),
3240 when: Some(context_keys::FILE_EXPLORER.to_string()),
3241 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_HIDDEN.to_string()),
3242 },
3243 MenuItem::Action {
3244 label: t!("menu.explorer.show_gitignored").to_string(),
3245 action: "file_explorer_toggle_gitignored".to_string(),
3246 args: HashMap::new(),
3247 when: Some(context_keys::FILE_EXPLORER.to_string()),
3248 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_GITIGNORED.to_string()),
3249 },
3250 ],
3251 },
3252 Menu {
3254 id: Some("Help".to_string()),
3255 label: t!("menu.help").to_string(),
3256 when: None,
3257 items: vec![
3258 MenuItem::Label {
3259 info: format!("Fresh v{}", env!("CARGO_PKG_VERSION")),
3260 },
3261 MenuItem::Separator { separator: true },
3262 MenuItem::Action {
3263 label: t!("menu.help.show_manual").to_string(),
3264 action: "show_help".to_string(),
3265 args: HashMap::new(),
3266 when: None,
3267 checkbox: None,
3268 },
3269 MenuItem::Action {
3270 label: t!("menu.help.keyboard_shortcuts").to_string(),
3271 action: "keyboard_shortcuts".to_string(),
3272 args: HashMap::new(),
3273 when: None,
3274 checkbox: None,
3275 },
3276 MenuItem::Separator { separator: true },
3277 MenuItem::Action {
3278 label: t!("menu.help.event_debug").to_string(),
3279 action: "event_debug".to_string(),
3280 args: HashMap::new(),
3281 when: None,
3282 checkbox: None,
3283 },
3284 ],
3285 },
3286 ]
3287 }
3288}
3289
3290impl Config {
3291 pub(crate) const FILENAME: &'static str = "config.json";
3293
3294 pub fn apply_runtime_flags(&self) {
3299 #[cfg(windows)]
3300 {
3301 crate::services::terminal::set_skip_app_execution_alias(
3302 self.terminal.skip_app_execution_alias,
3303 );
3304 }
3305 }
3306
3307 pub(crate) fn local_config_path(working_dir: &Path) -> std::path::PathBuf {
3309 working_dir.join(Self::FILENAME)
3310 }
3311
3312 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
3318 let contents = std::fs::read_to_string(path.as_ref())
3319 .map_err(|e| ConfigError::IoError(e.to_string()))?;
3320
3321 let partial: crate::partial_config::PartialConfig =
3323 serde_json::from_str(&contents).map_err(|e| ConfigError::ParseError(e.to_string()))?;
3324
3325 Ok(partial.resolve())
3326 }
3327
3328 fn load_builtin_keymap(name: &str) -> Option<KeymapConfig> {
3330 let json_content = match name {
3331 "default" => include_str!("../keymaps/default.json"),
3332 "emacs" => include_str!("../keymaps/emacs.json"),
3333 "vscode" => include_str!("../keymaps/vscode.json"),
3334 "macos" => include_str!("../keymaps/macos.json"),
3335 "macos-gui" => include_str!("../keymaps/macos-gui.json"),
3336 _ => return None,
3337 };
3338
3339 match serde_json::from_str(json_content) {
3340 Ok(config) => Some(config),
3341 Err(e) => {
3342 eprintln!("Failed to parse builtin keymap '{}': {}", name, e);
3343 None
3344 }
3345 }
3346 }
3347
3348 pub fn resolve_keymap(&self, map_name: &str) -> Vec<Keybinding> {
3351 let mut visited = std::collections::HashSet::new();
3352 self.resolve_keymap_recursive(map_name, &mut visited)
3353 }
3354
3355 fn resolve_keymap_recursive(
3357 &self,
3358 map_name: &str,
3359 visited: &mut std::collections::HashSet<String>,
3360 ) -> Vec<Keybinding> {
3361 if visited.contains(map_name) {
3363 eprintln!(
3364 "Warning: Circular inheritance detected in keymap '{}'",
3365 map_name
3366 );
3367 return Vec::new();
3368 }
3369 visited.insert(map_name.to_string());
3370
3371 let keymap = self
3373 .keybinding_maps
3374 .get(map_name)
3375 .cloned()
3376 .or_else(|| Self::load_builtin_keymap(map_name));
3377
3378 let Some(keymap) = keymap else {
3379 return Vec::new();
3380 };
3381
3382 let mut all_bindings = if let Some(ref parent_name) = keymap.inherits {
3384 self.resolve_keymap_recursive(parent_name, visited)
3385 } else {
3386 Vec::new()
3387 };
3388
3389 all_bindings.extend(keymap.bindings);
3391
3392 all_bindings
3393 }
3394 fn default_languages() -> HashMap<String, LanguageConfig> {
3396 let mut languages = HashMap::new();
3397
3398 languages.insert(
3399 "rust".to_string(),
3400 LanguageConfig {
3401 extensions: vec!["rs".to_string()],
3402 filenames: vec![],
3403 grammar: "rust".to_string(),
3404 comment_prefix: Some("//".to_string()),
3405 auto_indent: true,
3406 auto_close: None,
3407 auto_surround: None,
3408 textmate_grammar: None,
3409 show_whitespace_tabs: true,
3410 line_wrap: None,
3411 wrap_column: None,
3412 page_view: None,
3413 page_width: None,
3414 use_tabs: None,
3415 tab_size: None,
3416 formatter: Some(FormatterConfig {
3417 command: "rustfmt".to_string(),
3418 args: vec!["--edition".to_string(), "2021".to_string()],
3419 stdin: true,
3420 timeout_ms: 10000,
3421 }),
3422 format_on_save: false,
3423 on_save: vec![],
3424 word_characters: None,
3425 },
3426 );
3427
3428 languages.insert(
3429 "javascript".to_string(),
3430 LanguageConfig {
3431 extensions: vec!["js".to_string(), "jsx".to_string(), "mjs".to_string()],
3432 filenames: vec![],
3433 grammar: "javascript".to_string(),
3434 comment_prefix: Some("//".to_string()),
3435 auto_indent: true,
3436 auto_close: None,
3437 auto_surround: None,
3438 textmate_grammar: None,
3439 show_whitespace_tabs: true,
3440 line_wrap: None,
3441 wrap_column: None,
3442 page_view: None,
3443 page_width: None,
3444 use_tabs: None,
3445 tab_size: None,
3446 formatter: Some(FormatterConfig {
3447 command: "prettier".to_string(),
3448 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3449 stdin: true,
3450 timeout_ms: 10000,
3451 }),
3452 format_on_save: false,
3453 on_save: vec![],
3454 word_characters: None,
3455 },
3456 );
3457
3458 languages.insert(
3459 "typescript".to_string(),
3460 LanguageConfig {
3461 extensions: vec!["ts".to_string(), "tsx".to_string(), "mts".to_string()],
3462 filenames: vec![],
3463 grammar: "typescript".to_string(),
3464 comment_prefix: Some("//".to_string()),
3465 auto_indent: true,
3466 auto_close: None,
3467 auto_surround: None,
3468 textmate_grammar: None,
3469 show_whitespace_tabs: true,
3470 line_wrap: None,
3471 wrap_column: None,
3472 page_view: None,
3473 page_width: None,
3474 use_tabs: None,
3475 tab_size: None,
3476 formatter: Some(FormatterConfig {
3477 command: "prettier".to_string(),
3478 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3479 stdin: true,
3480 timeout_ms: 10000,
3481 }),
3482 format_on_save: false,
3483 on_save: vec![],
3484 word_characters: None,
3485 },
3486 );
3487
3488 languages.insert(
3489 "python".to_string(),
3490 LanguageConfig {
3491 extensions: vec!["py".to_string(), "pyi".to_string()],
3492 filenames: vec![],
3493 grammar: "python".to_string(),
3494 comment_prefix: Some("#".to_string()),
3495 auto_indent: true,
3496 auto_close: None,
3497 auto_surround: None,
3498 textmate_grammar: None,
3499 show_whitespace_tabs: true,
3500 line_wrap: None,
3501 wrap_column: None,
3502 page_view: None,
3503 page_width: None,
3504 use_tabs: None,
3505 tab_size: None,
3506 formatter: Some(FormatterConfig {
3507 command: "ruff".to_string(),
3508 args: vec![
3509 "format".to_string(),
3510 "--stdin-filename".to_string(),
3511 "$FILE".to_string(),
3512 ],
3513 stdin: true,
3514 timeout_ms: 10000,
3515 }),
3516 format_on_save: false,
3517 on_save: vec![],
3518 word_characters: None,
3519 },
3520 );
3521
3522 languages.insert(
3523 "c".to_string(),
3524 LanguageConfig {
3525 extensions: vec!["c".to_string(), "h".to_string()],
3526 filenames: vec![],
3527 grammar: "c".to_string(),
3528 comment_prefix: Some("//".to_string()),
3529 auto_indent: true,
3530 auto_close: None,
3531 auto_surround: None,
3532 textmate_grammar: None,
3533 show_whitespace_tabs: true,
3534 line_wrap: None,
3535 wrap_column: None,
3536 page_view: None,
3537 page_width: None,
3538 use_tabs: None,
3539 tab_size: None,
3540 formatter: Some(FormatterConfig {
3541 command: "clang-format".to_string(),
3542 args: vec![],
3543 stdin: true,
3544 timeout_ms: 10000,
3545 }),
3546 format_on_save: false,
3547 on_save: vec![],
3548 word_characters: None,
3549 },
3550 );
3551
3552 languages.insert(
3553 "cpp".to_string(),
3554 LanguageConfig {
3555 extensions: vec![
3556 "cpp".to_string(),
3557 "cc".to_string(),
3558 "cxx".to_string(),
3559 "hpp".to_string(),
3560 "hh".to_string(),
3561 "hxx".to_string(),
3562 ],
3563 filenames: vec![],
3564 grammar: "cpp".to_string(),
3565 comment_prefix: Some("//".to_string()),
3566 auto_indent: true,
3567 auto_close: None,
3568 auto_surround: None,
3569 textmate_grammar: None,
3570 show_whitespace_tabs: true,
3571 line_wrap: None,
3572 wrap_column: None,
3573 page_view: None,
3574 page_width: None,
3575 use_tabs: None,
3576 tab_size: None,
3577 formatter: Some(FormatterConfig {
3578 command: "clang-format".to_string(),
3579 args: vec![],
3580 stdin: true,
3581 timeout_ms: 10000,
3582 }),
3583 format_on_save: false,
3584 on_save: vec![],
3585 word_characters: None,
3586 },
3587 );
3588
3589 languages.insert(
3590 "csharp".to_string(),
3591 LanguageConfig {
3592 extensions: vec!["cs".to_string()],
3593 filenames: vec![],
3594 grammar: "C#".to_string(),
3595 comment_prefix: Some("//".to_string()),
3596 auto_indent: true,
3597 auto_close: None,
3598 auto_surround: None,
3599 textmate_grammar: None,
3600 show_whitespace_tabs: true,
3601 line_wrap: None,
3602 wrap_column: None,
3603 page_view: None,
3604 page_width: None,
3605 use_tabs: None,
3606 tab_size: None,
3607 formatter: None,
3608 format_on_save: false,
3609 on_save: vec![],
3610 word_characters: None,
3611 },
3612 );
3613
3614 languages.insert(
3615 "bash".to_string(),
3616 LanguageConfig {
3617 extensions: vec!["sh".to_string(), "bash".to_string()],
3618 filenames: vec![
3619 ".bash_aliases".to_string(),
3620 ".bash_logout".to_string(),
3621 ".bash_profile".to_string(),
3622 ".bashrc".to_string(),
3623 ".env".to_string(),
3624 ".profile".to_string(),
3625 ".zlogin".to_string(),
3626 ".zlogout".to_string(),
3627 ".zprofile".to_string(),
3628 ".zshenv".to_string(),
3629 ".zshrc".to_string(),
3630 "PKGBUILD".to_string(),
3632 "APKBUILD".to_string(),
3633 ],
3634 grammar: "bash".to_string(),
3635 comment_prefix: Some("#".to_string()),
3636 auto_indent: true,
3637 auto_close: None,
3638 auto_surround: None,
3639 textmate_grammar: None,
3640 show_whitespace_tabs: true,
3641 line_wrap: None,
3642 wrap_column: None,
3643 page_view: None,
3644 page_width: None,
3645 use_tabs: None,
3646 tab_size: None,
3647 formatter: None,
3648 format_on_save: false,
3649 on_save: vec![],
3650 word_characters: None,
3651 },
3652 );
3653
3654 languages.insert(
3655 "makefile".to_string(),
3656 LanguageConfig {
3657 extensions: vec!["mk".to_string()],
3658 filenames: vec![
3659 "Makefile".to_string(),
3660 "makefile".to_string(),
3661 "GNUmakefile".to_string(),
3662 ],
3663 grammar: "Makefile".to_string(),
3664 comment_prefix: Some("#".to_string()),
3665 auto_indent: false,
3666 auto_close: None,
3667 auto_surround: None,
3668 textmate_grammar: None,
3669 show_whitespace_tabs: true,
3670 line_wrap: None,
3671 wrap_column: None,
3672 page_view: None,
3673 page_width: None,
3674 use_tabs: Some(true), tab_size: Some(8), formatter: None,
3677 format_on_save: false,
3678 on_save: vec![],
3679 word_characters: None,
3680 },
3681 );
3682
3683 languages.insert(
3684 "dockerfile".to_string(),
3685 LanguageConfig {
3686 extensions: vec!["dockerfile".to_string()],
3687 filenames: vec!["Dockerfile".to_string(), "Containerfile".to_string()],
3688 grammar: "dockerfile".to_string(),
3689 comment_prefix: Some("#".to_string()),
3690 auto_indent: true,
3691 auto_close: None,
3692 auto_surround: None,
3693 textmate_grammar: None,
3694 show_whitespace_tabs: true,
3695 line_wrap: None,
3696 wrap_column: None,
3697 page_view: None,
3698 page_width: None,
3699 use_tabs: None,
3700 tab_size: None,
3701 formatter: None,
3702 format_on_save: false,
3703 on_save: vec![],
3704 word_characters: None,
3705 },
3706 );
3707
3708 languages.insert(
3709 "json".to_string(),
3710 LanguageConfig {
3711 extensions: vec!["json".to_string()],
3712 filenames: vec![],
3713 grammar: "json".to_string(),
3714 comment_prefix: None,
3715 auto_indent: true,
3716 auto_close: None,
3717 auto_surround: None,
3718 textmate_grammar: None,
3719 show_whitespace_tabs: true,
3720 line_wrap: None,
3721 wrap_column: None,
3722 page_view: None,
3723 page_width: None,
3724 use_tabs: None,
3725 tab_size: None,
3726 formatter: Some(FormatterConfig {
3727 command: "prettier".to_string(),
3728 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3729 stdin: true,
3730 timeout_ms: 10000,
3731 }),
3732 format_on_save: false,
3733 on_save: vec![],
3734 word_characters: None,
3735 },
3736 );
3737
3738 languages.insert(
3745 "jsonc".to_string(),
3746 LanguageConfig {
3747 extensions: vec!["jsonc".to_string()],
3748 filenames: vec![
3749 "devcontainer.json".to_string(),
3750 ".devcontainer.json".to_string(),
3751 "tsconfig.json".to_string(),
3752 "tsconfig.*.json".to_string(),
3753 "jsconfig.json".to_string(),
3754 "jsconfig.*.json".to_string(),
3755 ".eslintrc.json".to_string(),
3756 ".babelrc".to_string(),
3757 ".babelrc.json".to_string(),
3758 ".swcrc".to_string(),
3759 ".jshintrc".to_string(),
3760 ".hintrc".to_string(),
3761 "settings.json".to_string(),
3762 "keybindings.json".to_string(),
3763 "tasks.json".to_string(),
3764 "launch.json".to_string(),
3765 "extensions.json".to_string(),
3766 "argv.json".to_string(),
3767 ],
3768 grammar: "jsonc".to_string(),
3769 comment_prefix: Some("//".to_string()),
3770 auto_indent: true,
3771 auto_close: None,
3772 auto_surround: None,
3773 textmate_grammar: None,
3774 show_whitespace_tabs: true,
3775 line_wrap: None,
3776 wrap_column: None,
3777 page_view: None,
3778 page_width: None,
3779 use_tabs: None,
3780 tab_size: None,
3781 formatter: Some(FormatterConfig {
3782 command: "prettier".to_string(),
3783 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3784 stdin: true,
3785 timeout_ms: 10000,
3786 }),
3787 format_on_save: false,
3788 on_save: vec![],
3789 word_characters: None,
3790 },
3791 );
3792
3793 languages.insert(
3794 "toml".to_string(),
3795 LanguageConfig {
3796 extensions: vec!["toml".to_string()],
3797 filenames: vec!["Cargo.lock".to_string()],
3798 grammar: "toml".to_string(),
3799 comment_prefix: Some("#".to_string()),
3800 auto_indent: true,
3801 auto_close: None,
3802 auto_surround: None,
3803 textmate_grammar: None,
3804 show_whitespace_tabs: true,
3805 line_wrap: None,
3806 wrap_column: None,
3807 page_view: None,
3808 page_width: None,
3809 use_tabs: None,
3810 tab_size: None,
3811 formatter: None,
3812 format_on_save: false,
3813 on_save: vec![],
3814 word_characters: None,
3815 },
3816 );
3817
3818 languages.insert(
3819 "yaml".to_string(),
3820 LanguageConfig {
3821 extensions: vec!["yml".to_string(), "yaml".to_string()],
3822 filenames: vec![],
3823 grammar: "yaml".to_string(),
3824 comment_prefix: Some("#".to_string()),
3825 auto_indent: true,
3826 auto_close: None,
3827 auto_surround: None,
3828 textmate_grammar: None,
3829 show_whitespace_tabs: true,
3830 line_wrap: None,
3831 wrap_column: None,
3832 page_view: None,
3833 page_width: None,
3834 use_tabs: None,
3835 tab_size: None,
3836 formatter: Some(FormatterConfig {
3837 command: "prettier".to_string(),
3838 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3839 stdin: true,
3840 timeout_ms: 10000,
3841 }),
3842 format_on_save: false,
3843 on_save: vec![],
3844 word_characters: None,
3845 },
3846 );
3847
3848 languages.insert(
3849 "markdown".to_string(),
3850 LanguageConfig {
3851 extensions: vec!["md".to_string(), "markdown".to_string()],
3852 filenames: vec!["README".to_string()],
3853 grammar: "markdown".to_string(),
3854 comment_prefix: None,
3855 auto_indent: false,
3856 auto_close: None,
3857 auto_surround: None,
3858 textmate_grammar: None,
3859 show_whitespace_tabs: true,
3860 line_wrap: None,
3861 wrap_column: None,
3862 page_view: None,
3863 page_width: None,
3864 use_tabs: None,
3865 tab_size: None,
3866 formatter: None,
3867 format_on_save: false,
3868 on_save: vec![],
3869 word_characters: None,
3870 },
3871 );
3872
3873 languages.insert(
3875 "go".to_string(),
3876 LanguageConfig {
3877 extensions: vec!["go".to_string()],
3878 filenames: vec![],
3879 grammar: "go".to_string(),
3880 comment_prefix: Some("//".to_string()),
3881 auto_indent: true,
3882 auto_close: None,
3883 auto_surround: None,
3884 textmate_grammar: None,
3885 show_whitespace_tabs: false,
3886 line_wrap: None,
3887 wrap_column: None,
3888 page_view: None,
3889 page_width: None,
3890 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
3893 command: "gofmt".to_string(),
3894 args: vec![],
3895 stdin: true,
3896 timeout_ms: 10000,
3897 }),
3898 format_on_save: false,
3899 on_save: vec![],
3900 word_characters: None,
3901 },
3902 );
3903
3904 languages.insert(
3905 "odin".to_string(),
3906 LanguageConfig {
3907 extensions: vec!["odin".to_string()],
3908 filenames: vec![],
3909 grammar: "odin".to_string(),
3910 comment_prefix: Some("//".to_string()),
3911 auto_indent: true,
3912 auto_close: None,
3913 auto_surround: None,
3914 textmate_grammar: None,
3915 show_whitespace_tabs: false,
3916 line_wrap: None,
3917 wrap_column: None,
3918 page_view: None,
3919 page_width: None,
3920 use_tabs: Some(true),
3921 tab_size: Some(8),
3922 formatter: None,
3923 format_on_save: false,
3924 on_save: vec![],
3925 word_characters: None,
3926 },
3927 );
3928
3929 languages.insert(
3930 "zig".to_string(),
3931 LanguageConfig {
3932 extensions: vec!["zig".to_string(), "zon".to_string()],
3933 filenames: vec![],
3934 grammar: "zig".to_string(),
3935 comment_prefix: Some("//".to_string()),
3936 auto_indent: true,
3937 auto_close: None,
3938 auto_surround: None,
3939 textmate_grammar: None,
3940 show_whitespace_tabs: true,
3941 line_wrap: None,
3942 wrap_column: None,
3943 page_view: None,
3944 page_width: None,
3945 use_tabs: None,
3946 tab_size: None,
3947 formatter: None,
3948 format_on_save: false,
3949 on_save: vec![],
3950 word_characters: None,
3951 },
3952 );
3953
3954 languages.insert(
3955 "c3".to_string(),
3956 LanguageConfig {
3957 extensions: vec!["c3".to_string(), "c3i".to_string(), "c3t".to_string()],
3958 filenames: vec![],
3959 grammar: "c3".to_string(),
3960 comment_prefix: Some("//".to_string()),
3961 auto_indent: true,
3962 auto_close: None,
3963 auto_surround: None,
3964 textmate_grammar: None,
3965 show_whitespace_tabs: true,
3966 line_wrap: None,
3967 wrap_column: None,
3968 page_view: None,
3969 page_width: None,
3970 use_tabs: None,
3971 tab_size: None,
3972 formatter: None,
3973 format_on_save: false,
3974 on_save: vec![],
3975 word_characters: None,
3976 },
3977 );
3978
3979 languages.insert(
3980 "java".to_string(),
3981 LanguageConfig {
3982 extensions: vec!["java".to_string()],
3983 filenames: vec![],
3984 grammar: "java".to_string(),
3985 comment_prefix: Some("//".to_string()),
3986 auto_indent: true,
3987 auto_close: None,
3988 auto_surround: None,
3989 textmate_grammar: None,
3990 show_whitespace_tabs: true,
3991 line_wrap: None,
3992 wrap_column: None,
3993 page_view: None,
3994 page_width: None,
3995 use_tabs: None,
3996 tab_size: None,
3997 formatter: None,
3998 format_on_save: false,
3999 on_save: vec![],
4000 word_characters: None,
4001 },
4002 );
4003
4004 languages.insert(
4005 "latex".to_string(),
4006 LanguageConfig {
4007 extensions: vec![
4008 "tex".to_string(),
4009 "latex".to_string(),
4010 "ltx".to_string(),
4011 "sty".to_string(),
4012 "cls".to_string(),
4013 "bib".to_string(),
4014 ],
4015 filenames: vec![],
4016 grammar: "latex".to_string(),
4017 comment_prefix: Some("%".to_string()),
4018 auto_indent: true,
4019 auto_close: None,
4020 auto_surround: None,
4021 textmate_grammar: None,
4022 show_whitespace_tabs: true,
4023 line_wrap: None,
4024 wrap_column: None,
4025 page_view: None,
4026 page_width: None,
4027 use_tabs: None,
4028 tab_size: None,
4029 formatter: None,
4030 format_on_save: false,
4031 on_save: vec![],
4032 word_characters: None,
4033 },
4034 );
4035
4036 languages.insert(
4037 "templ".to_string(),
4038 LanguageConfig {
4039 extensions: vec!["templ".to_string()],
4040 filenames: vec![],
4041 grammar: "go".to_string(), comment_prefix: Some("//".to_string()),
4043 auto_indent: true,
4044 auto_close: None,
4045 auto_surround: None,
4046 textmate_grammar: None,
4047 show_whitespace_tabs: true,
4048 line_wrap: None,
4049 wrap_column: None,
4050 page_view: None,
4051 page_width: None,
4052 use_tabs: None,
4053 tab_size: None,
4054 formatter: None,
4055 format_on_save: false,
4056 on_save: vec![],
4057 word_characters: None,
4058 },
4059 );
4060
4061 languages.insert(
4063 "git-rebase".to_string(),
4064 LanguageConfig {
4065 extensions: vec![],
4066 filenames: vec!["git-rebase-todo".to_string()],
4067 grammar: "Git Rebase Todo".to_string(),
4068 comment_prefix: Some("#".to_string()),
4069 auto_indent: false,
4070 auto_close: None,
4071 auto_surround: None,
4072 textmate_grammar: None,
4073 show_whitespace_tabs: true,
4074 line_wrap: None,
4075 wrap_column: None,
4076 page_view: None,
4077 page_width: None,
4078 use_tabs: None,
4079 tab_size: None,
4080 formatter: None,
4081 format_on_save: false,
4082 on_save: vec![],
4083 word_characters: None,
4084 },
4085 );
4086
4087 languages.insert(
4088 "git-commit".to_string(),
4089 LanguageConfig {
4090 extensions: vec![],
4091 filenames: vec![
4092 "COMMIT_EDITMSG".to_string(),
4093 "MERGE_MSG".to_string(),
4094 "SQUASH_MSG".to_string(),
4095 "TAG_EDITMSG".to_string(),
4096 ],
4097 grammar: "Git Commit Message".to_string(),
4098 comment_prefix: Some("#".to_string()),
4099 auto_indent: false,
4100 auto_close: None,
4101 auto_surround: None,
4102 textmate_grammar: None,
4103 show_whitespace_tabs: true,
4104 line_wrap: None,
4105 wrap_column: None,
4106 page_view: None,
4107 page_width: None,
4108 use_tabs: None,
4109 tab_size: None,
4110 formatter: None,
4111 format_on_save: false,
4112 on_save: vec![],
4113 word_characters: None,
4114 },
4115 );
4116
4117 languages.insert(
4118 "gitignore".to_string(),
4119 LanguageConfig {
4120 extensions: vec!["gitignore".to_string()],
4121 filenames: vec![
4122 ".gitignore".to_string(),
4123 ".dockerignore".to_string(),
4124 ".npmignore".to_string(),
4125 ".hgignore".to_string(),
4126 ],
4127 grammar: "Gitignore".to_string(),
4128 comment_prefix: Some("#".to_string()),
4129 auto_indent: false,
4130 auto_close: None,
4131 auto_surround: None,
4132 textmate_grammar: None,
4133 show_whitespace_tabs: true,
4134 line_wrap: None,
4135 wrap_column: None,
4136 page_view: None,
4137 page_width: None,
4138 use_tabs: None,
4139 tab_size: None,
4140 formatter: None,
4141 format_on_save: false,
4142 on_save: vec![],
4143 word_characters: None,
4144 },
4145 );
4146
4147 languages.insert(
4148 "gitconfig".to_string(),
4149 LanguageConfig {
4150 extensions: vec!["gitconfig".to_string()],
4151 filenames: vec![".gitconfig".to_string(), ".gitmodules".to_string()],
4152 grammar: "Git Config".to_string(),
4153 comment_prefix: Some("#".to_string()),
4154 auto_indent: true,
4155 auto_close: None,
4156 auto_surround: None,
4157 textmate_grammar: None,
4158 show_whitespace_tabs: true,
4159 line_wrap: None,
4160 wrap_column: None,
4161 page_view: None,
4162 page_width: None,
4163 use_tabs: None,
4164 tab_size: None,
4165 formatter: None,
4166 format_on_save: false,
4167 on_save: vec![],
4168 word_characters: None,
4169 },
4170 );
4171
4172 languages.insert(
4173 "gitattributes".to_string(),
4174 LanguageConfig {
4175 extensions: vec!["gitattributes".to_string()],
4176 filenames: vec![".gitattributes".to_string()],
4177 grammar: "Git Attributes".to_string(),
4178 comment_prefix: Some("#".to_string()),
4179 auto_indent: false,
4180 auto_close: None,
4181 auto_surround: None,
4182 textmate_grammar: None,
4183 show_whitespace_tabs: true,
4184 line_wrap: None,
4185 wrap_column: None,
4186 page_view: None,
4187 page_width: None,
4188 use_tabs: None,
4189 tab_size: None,
4190 formatter: None,
4191 format_on_save: false,
4192 on_save: vec![],
4193 word_characters: None,
4194 },
4195 );
4196
4197 languages.insert(
4198 "typst".to_string(),
4199 LanguageConfig {
4200 extensions: vec!["typ".to_string()],
4201 filenames: vec![],
4202 grammar: "Typst".to_string(),
4203 comment_prefix: Some("//".to_string()),
4204 auto_indent: true,
4205 auto_close: None,
4206 auto_surround: None,
4207 textmate_grammar: None,
4208 show_whitespace_tabs: true,
4209 line_wrap: None,
4210 wrap_column: None,
4211 page_view: None,
4212 page_width: None,
4213 use_tabs: None,
4214 tab_size: None,
4215 formatter: None,
4216 format_on_save: false,
4217 on_save: vec![],
4218 word_characters: None,
4219 },
4220 );
4221
4222 languages.insert(
4227 "kotlin".to_string(),
4228 LanguageConfig {
4229 extensions: vec!["kt".to_string(), "kts".to_string()],
4230 filenames: vec![],
4231 grammar: "Kotlin".to_string(),
4232 comment_prefix: Some("//".to_string()),
4233 auto_indent: true,
4234 auto_close: None,
4235 auto_surround: None,
4236 textmate_grammar: None,
4237 show_whitespace_tabs: true,
4238 line_wrap: None,
4239 wrap_column: None,
4240 page_view: None,
4241 page_width: None,
4242 use_tabs: None,
4243 tab_size: None,
4244 formatter: None,
4245 format_on_save: false,
4246 on_save: vec![],
4247 word_characters: None,
4248 },
4249 );
4250
4251 languages.insert(
4252 "swift".to_string(),
4253 LanguageConfig {
4254 extensions: vec!["swift".to_string()],
4255 filenames: vec![],
4256 grammar: "Swift".to_string(),
4257 comment_prefix: Some("//".to_string()),
4258 auto_indent: true,
4259 auto_close: None,
4260 auto_surround: None,
4261 textmate_grammar: None,
4262 show_whitespace_tabs: true,
4263 line_wrap: None,
4264 wrap_column: None,
4265 page_view: None,
4266 page_width: None,
4267 use_tabs: None,
4268 tab_size: None,
4269 formatter: None,
4270 format_on_save: false,
4271 on_save: vec![],
4272 word_characters: None,
4273 },
4274 );
4275
4276 languages.insert(
4277 "scala".to_string(),
4278 LanguageConfig {
4279 extensions: vec!["scala".to_string(), "sc".to_string()],
4280 filenames: vec![],
4281 grammar: "Scala".to_string(),
4282 comment_prefix: Some("//".to_string()),
4283 auto_indent: true,
4284 auto_close: None,
4285 auto_surround: None,
4286 textmate_grammar: None,
4287 show_whitespace_tabs: true,
4288 line_wrap: None,
4289 wrap_column: None,
4290 page_view: None,
4291 page_width: None,
4292 use_tabs: None,
4293 tab_size: None,
4294 formatter: None,
4295 format_on_save: false,
4296 on_save: vec![],
4297 word_characters: None,
4298 },
4299 );
4300
4301 languages.insert(
4302 "dart".to_string(),
4303 LanguageConfig {
4304 extensions: vec!["dart".to_string()],
4305 filenames: vec![],
4306 grammar: "Dart".to_string(),
4307 comment_prefix: Some("//".to_string()),
4308 auto_indent: true,
4309 auto_close: None,
4310 auto_surround: None,
4311 textmate_grammar: None,
4312 show_whitespace_tabs: true,
4313 line_wrap: None,
4314 wrap_column: None,
4315 page_view: None,
4316 page_width: None,
4317 use_tabs: None,
4318 tab_size: None,
4319 formatter: None,
4320 format_on_save: false,
4321 on_save: vec![],
4322 word_characters: None,
4323 },
4324 );
4325
4326 languages.insert(
4327 "elixir".to_string(),
4328 LanguageConfig {
4329 extensions: vec!["ex".to_string(), "exs".to_string()],
4330 filenames: vec![],
4331 grammar: "Elixir".to_string(),
4332 comment_prefix: Some("#".to_string()),
4333 auto_indent: true,
4334 auto_close: None,
4335 auto_surround: None,
4336 textmate_grammar: None,
4337 show_whitespace_tabs: true,
4338 line_wrap: None,
4339 wrap_column: None,
4340 page_view: None,
4341 page_width: None,
4342 use_tabs: None,
4343 tab_size: None,
4344 formatter: None,
4345 format_on_save: false,
4346 on_save: vec![],
4347 word_characters: None,
4348 },
4349 );
4350
4351 languages.insert(
4352 "erlang".to_string(),
4353 LanguageConfig {
4354 extensions: vec!["erl".to_string(), "hrl".to_string()],
4355 filenames: vec![],
4356 grammar: "Erlang".to_string(),
4357 comment_prefix: Some("%".to_string()),
4358 auto_indent: true,
4359 auto_close: None,
4360 auto_surround: None,
4361 textmate_grammar: None,
4362 show_whitespace_tabs: true,
4363 line_wrap: None,
4364 wrap_column: None,
4365 page_view: None,
4366 page_width: None,
4367 use_tabs: None,
4368 tab_size: None,
4369 formatter: None,
4370 format_on_save: false,
4371 on_save: vec![],
4372 word_characters: None,
4373 },
4374 );
4375
4376 languages.insert(
4377 "haskell".to_string(),
4378 LanguageConfig {
4379 extensions: vec!["hs".to_string(), "lhs".to_string()],
4380 filenames: vec![],
4381 grammar: "Haskell".to_string(),
4382 comment_prefix: Some("--".to_string()),
4383 auto_indent: true,
4384 auto_close: None,
4385 auto_surround: None,
4386 textmate_grammar: None,
4387 show_whitespace_tabs: true,
4388 line_wrap: None,
4389 wrap_column: None,
4390 page_view: None,
4391 page_width: None,
4392 use_tabs: None,
4393 tab_size: None,
4394 formatter: None,
4395 format_on_save: false,
4396 on_save: vec![],
4397 word_characters: None,
4398 },
4399 );
4400
4401 languages.insert(
4402 "ocaml".to_string(),
4403 LanguageConfig {
4404 extensions: vec!["ml".to_string(), "mli".to_string()],
4405 filenames: vec![],
4406 grammar: "OCaml".to_string(),
4407 comment_prefix: None,
4408 auto_indent: true,
4409 auto_close: None,
4410 auto_surround: None,
4411 textmate_grammar: None,
4412 show_whitespace_tabs: true,
4413 line_wrap: None,
4414 wrap_column: None,
4415 page_view: None,
4416 page_width: None,
4417 use_tabs: None,
4418 tab_size: None,
4419 formatter: None,
4420 format_on_save: false,
4421 on_save: vec![],
4422 word_characters: None,
4423 },
4424 );
4425
4426 languages.insert(
4427 "clojure".to_string(),
4428 LanguageConfig {
4429 extensions: vec![
4430 "clj".to_string(),
4431 "cljs".to_string(),
4432 "cljc".to_string(),
4433 "edn".to_string(),
4434 ],
4435 filenames: vec![],
4436 grammar: "Clojure".to_string(),
4437 comment_prefix: Some(";".to_string()),
4438 auto_indent: true,
4439 auto_close: None,
4440 auto_surround: None,
4441 textmate_grammar: None,
4442 show_whitespace_tabs: true,
4443 line_wrap: None,
4444 wrap_column: None,
4445 page_view: None,
4446 page_width: None,
4447 use_tabs: None,
4448 tab_size: None,
4449 formatter: None,
4450 format_on_save: false,
4451 on_save: vec![],
4452 word_characters: None,
4453 },
4454 );
4455
4456 languages.insert(
4457 "r".to_string(),
4458 LanguageConfig {
4459 extensions: vec!["r".to_string(), "R".to_string(), "rmd".to_string()],
4460 filenames: vec![],
4461 grammar: "R".to_string(),
4462 comment_prefix: Some("#".to_string()),
4463 auto_indent: true,
4464 auto_close: None,
4465 auto_surround: None,
4466 textmate_grammar: None,
4467 show_whitespace_tabs: true,
4468 line_wrap: None,
4469 wrap_column: None,
4470 page_view: None,
4471 page_width: None,
4472 use_tabs: None,
4473 tab_size: None,
4474 formatter: None,
4475 format_on_save: false,
4476 on_save: vec![],
4477 word_characters: None,
4478 },
4479 );
4480
4481 languages.insert(
4482 "julia".to_string(),
4483 LanguageConfig {
4484 extensions: vec!["jl".to_string()],
4485 filenames: vec![],
4486 grammar: "Julia".to_string(),
4487 comment_prefix: Some("#".to_string()),
4488 auto_indent: true,
4489 auto_close: None,
4490 auto_surround: None,
4491 textmate_grammar: None,
4492 show_whitespace_tabs: true,
4493 line_wrap: None,
4494 wrap_column: None,
4495 page_view: None,
4496 page_width: None,
4497 use_tabs: None,
4498 tab_size: None,
4499 formatter: None,
4500 format_on_save: false,
4501 on_save: vec![],
4502 word_characters: None,
4503 },
4504 );
4505
4506 languages.insert(
4507 "perl".to_string(),
4508 LanguageConfig {
4509 extensions: vec!["pl".to_string(), "pm".to_string(), "t".to_string()],
4510 filenames: vec![],
4511 grammar: "Perl".to_string(),
4512 comment_prefix: Some("#".to_string()),
4513 auto_indent: true,
4514 auto_close: None,
4515 auto_surround: None,
4516 textmate_grammar: None,
4517 show_whitespace_tabs: true,
4518 line_wrap: None,
4519 wrap_column: None,
4520 page_view: None,
4521 page_width: None,
4522 use_tabs: None,
4523 tab_size: None,
4524 formatter: None,
4525 format_on_save: false,
4526 on_save: vec![],
4527 word_characters: None,
4528 },
4529 );
4530
4531 languages.insert(
4532 "nim".to_string(),
4533 LanguageConfig {
4534 extensions: vec!["nim".to_string(), "nims".to_string(), "nimble".to_string()],
4535 filenames: vec![],
4536 grammar: "Nim".to_string(),
4537 comment_prefix: Some("#".to_string()),
4538 auto_indent: true,
4539 auto_close: None,
4540 auto_surround: None,
4541 textmate_grammar: None,
4542 show_whitespace_tabs: true,
4543 line_wrap: None,
4544 wrap_column: None,
4545 page_view: None,
4546 page_width: None,
4547 use_tabs: None,
4548 tab_size: None,
4549 formatter: None,
4550 format_on_save: false,
4551 on_save: vec![],
4552 word_characters: None,
4553 },
4554 );
4555
4556 languages.insert(
4557 "gleam".to_string(),
4558 LanguageConfig {
4559 extensions: vec!["gleam".to_string()],
4560 filenames: vec![],
4561 grammar: "Gleam".to_string(),
4562 comment_prefix: Some("//".to_string()),
4563 auto_indent: true,
4564 auto_close: None,
4565 auto_surround: None,
4566 textmate_grammar: None,
4567 show_whitespace_tabs: true,
4568 line_wrap: None,
4569 wrap_column: None,
4570 page_view: None,
4571 page_width: None,
4572 use_tabs: None,
4573 tab_size: None,
4574 formatter: None,
4575 format_on_save: false,
4576 on_save: vec![],
4577 word_characters: None,
4578 },
4579 );
4580
4581 languages.insert(
4582 "racket".to_string(),
4583 LanguageConfig {
4584 extensions: vec![
4585 "rkt".to_string(),
4586 "rktd".to_string(),
4587 "rktl".to_string(),
4588 "scrbl".to_string(),
4589 ],
4590 filenames: vec![],
4591 grammar: "Racket".to_string(),
4592 comment_prefix: Some(";".to_string()),
4593 auto_indent: true,
4594 auto_close: None,
4595 auto_surround: None,
4596 textmate_grammar: None,
4597 show_whitespace_tabs: true,
4598 line_wrap: None,
4599 wrap_column: None,
4600 page_view: None,
4601 page_width: None,
4602 use_tabs: None,
4603 tab_size: None,
4604 formatter: None,
4605 format_on_save: false,
4606 on_save: vec![],
4607 word_characters: None,
4608 },
4609 );
4610
4611 languages.insert(
4612 "fsharp".to_string(),
4613 LanguageConfig {
4614 extensions: vec!["fs".to_string(), "fsi".to_string(), "fsx".to_string()],
4615 filenames: vec![],
4616 grammar: "FSharp".to_string(),
4617 comment_prefix: Some("//".to_string()),
4618 auto_indent: true,
4619 auto_close: None,
4620 auto_surround: None,
4621 textmate_grammar: None,
4622 show_whitespace_tabs: true,
4623 line_wrap: None,
4624 wrap_column: None,
4625 page_view: None,
4626 page_width: None,
4627 use_tabs: None,
4628 tab_size: None,
4629 formatter: None,
4630 format_on_save: false,
4631 on_save: vec![],
4632 word_characters: None,
4633 },
4634 );
4635
4636 languages.insert(
4637 "nix".to_string(),
4638 LanguageConfig {
4639 extensions: vec!["nix".to_string()],
4640 filenames: vec![],
4641 grammar: "Nix".to_string(),
4642 comment_prefix: Some("#".to_string()),
4643 auto_indent: true,
4644 auto_close: None,
4645 auto_surround: None,
4646 textmate_grammar: None,
4647 show_whitespace_tabs: true,
4648 line_wrap: None,
4649 wrap_column: None,
4650 page_view: None,
4651 page_width: None,
4652 use_tabs: None,
4653 tab_size: None,
4654 formatter: None,
4655 format_on_save: false,
4656 on_save: vec![],
4657 word_characters: None,
4658 },
4659 );
4660
4661 languages.insert(
4662 "nushell".to_string(),
4663 LanguageConfig {
4664 extensions: vec!["nu".to_string()],
4665 filenames: vec![],
4666 grammar: "Nushell".to_string(),
4667 comment_prefix: Some("#".to_string()),
4668 auto_indent: true,
4669 auto_close: None,
4670 auto_surround: None,
4671 textmate_grammar: None,
4672 show_whitespace_tabs: true,
4673 line_wrap: None,
4674 wrap_column: None,
4675 page_view: None,
4676 page_width: None,
4677 use_tabs: None,
4678 tab_size: None,
4679 formatter: None,
4680 format_on_save: false,
4681 on_save: vec![],
4682 word_characters: None,
4683 },
4684 );
4685
4686 languages.insert(
4687 "solidity".to_string(),
4688 LanguageConfig {
4689 extensions: vec!["sol".to_string()],
4690 filenames: vec![],
4691 grammar: "Solidity".to_string(),
4692 comment_prefix: Some("//".to_string()),
4693 auto_indent: true,
4694 auto_close: None,
4695 auto_surround: None,
4696 textmate_grammar: None,
4697 show_whitespace_tabs: true,
4698 line_wrap: None,
4699 wrap_column: None,
4700 page_view: None,
4701 page_width: None,
4702 use_tabs: None,
4703 tab_size: None,
4704 formatter: None,
4705 format_on_save: false,
4706 on_save: vec![],
4707 word_characters: None,
4708 },
4709 );
4710
4711 languages.insert(
4712 "verilog".to_string(),
4713 LanguageConfig {
4714 extensions: vec!["vh".to_string(), "verilog".to_string()],
4715 filenames: vec![],
4716 grammar: "Verilog".to_string(),
4717 comment_prefix: Some("//".to_string()),
4718 auto_indent: true,
4719 auto_close: None,
4720 auto_surround: None,
4721 textmate_grammar: None,
4722 show_whitespace_tabs: true,
4723 line_wrap: None,
4724 wrap_column: None,
4725 page_view: None,
4726 page_width: None,
4727 use_tabs: None,
4728 tab_size: None,
4729 formatter: None,
4730 format_on_save: false,
4731 on_save: vec![],
4732 word_characters: None,
4733 },
4734 );
4735
4736 languages.insert(
4737 "systemverilog".to_string(),
4738 LanguageConfig {
4739 extensions: vec![
4740 "sv".to_string(),
4741 "svh".to_string(),
4742 "svi".to_string(),
4743 "svp".to_string(),
4744 ],
4745 filenames: vec![],
4746 grammar: "SystemVerilog".to_string(),
4747 comment_prefix: Some("//".to_string()),
4748 auto_indent: true,
4749 auto_close: None,
4750 auto_surround: None,
4751 textmate_grammar: None,
4752 show_whitespace_tabs: true,
4753 line_wrap: None,
4754 wrap_column: None,
4755 page_view: None,
4756 page_width: None,
4757 use_tabs: None,
4758 tab_size: None,
4759 formatter: None,
4760 format_on_save: false,
4761 on_save: vec![],
4762 word_characters: None,
4763 },
4764 );
4765
4766 languages.insert(
4767 "vhdl".to_string(),
4768 LanguageConfig {
4769 extensions: vec!["vhd".to_string(), "vhdl".to_string(), "vho".to_string()],
4770 filenames: vec![],
4771 grammar: "VHDL".to_string(),
4772 comment_prefix: Some("--".to_string()),
4773 auto_indent: true,
4774 auto_close: None,
4775 auto_surround: None,
4776 textmate_grammar: None,
4777 show_whitespace_tabs: true,
4778 line_wrap: None,
4779 wrap_column: None,
4780 page_view: None,
4781 page_width: None,
4782 use_tabs: None,
4783 tab_size: None,
4784 formatter: None,
4785 format_on_save: false,
4786 on_save: vec![],
4787 word_characters: None,
4788 },
4789 );
4790
4791 languages.insert(
4792 "ruby".to_string(),
4793 LanguageConfig {
4794 extensions: vec!["rb".to_string(), "rake".to_string(), "gemspec".to_string()],
4795 filenames: vec![
4796 "Gemfile".to_string(),
4797 "Rakefile".to_string(),
4798 "Guardfile".to_string(),
4799 ],
4800 grammar: "Ruby".to_string(),
4801 comment_prefix: Some("#".to_string()),
4802 auto_indent: true,
4803 auto_close: None,
4804 auto_surround: None,
4805 textmate_grammar: None,
4806 show_whitespace_tabs: true,
4807 line_wrap: None,
4808 wrap_column: None,
4809 page_view: None,
4810 page_width: None,
4811 use_tabs: None,
4812 tab_size: None,
4813 formatter: None,
4814 format_on_save: false,
4815 on_save: vec![],
4816 word_characters: None,
4817 },
4818 );
4819
4820 languages.insert(
4821 "php".to_string(),
4822 LanguageConfig {
4823 extensions: vec!["php".to_string(), "phtml".to_string()],
4824 filenames: vec![],
4825 grammar: "PHP".to_string(),
4826 comment_prefix: Some("//".to_string()),
4827 auto_indent: true,
4828 auto_close: None,
4829 auto_surround: None,
4830 textmate_grammar: None,
4831 show_whitespace_tabs: true,
4832 line_wrap: None,
4833 wrap_column: None,
4834 page_view: None,
4835 page_width: None,
4836 use_tabs: None,
4837 tab_size: None,
4838 formatter: None,
4839 format_on_save: false,
4840 on_save: vec![],
4841 word_characters: None,
4842 },
4843 );
4844
4845 languages.insert(
4846 "lua".to_string(),
4847 LanguageConfig {
4848 extensions: vec!["lua".to_string()],
4849 filenames: vec![],
4850 grammar: "Lua".to_string(),
4851 comment_prefix: Some("--".to_string()),
4852 auto_indent: true,
4853 auto_close: None,
4854 auto_surround: None,
4855 textmate_grammar: None,
4856 show_whitespace_tabs: true,
4857 line_wrap: None,
4858 wrap_column: None,
4859 page_view: None,
4860 page_width: None,
4861 use_tabs: None,
4862 tab_size: None,
4863 formatter: None,
4864 format_on_save: false,
4865 on_save: vec![],
4866 word_characters: None,
4867 },
4868 );
4869
4870 languages.insert(
4871 "html".to_string(),
4872 LanguageConfig {
4873 extensions: vec!["html".to_string(), "htm".to_string()],
4874 filenames: vec![],
4875 grammar: "HTML".to_string(),
4876 comment_prefix: None,
4877 auto_indent: true,
4878 auto_close: None,
4879 auto_surround: None,
4880 textmate_grammar: None,
4881 show_whitespace_tabs: true,
4882 line_wrap: None,
4883 wrap_column: None,
4884 page_view: None,
4885 page_width: None,
4886 use_tabs: None,
4887 tab_size: None,
4888 formatter: None,
4889 format_on_save: false,
4890 on_save: vec![],
4891 word_characters: None,
4892 },
4893 );
4894
4895 languages.insert(
4896 "css".to_string(),
4897 LanguageConfig {
4898 extensions: vec!["css".to_string()],
4899 filenames: vec![],
4900 grammar: "CSS".to_string(),
4901 comment_prefix: None,
4902 auto_indent: true,
4903 auto_close: None,
4904 auto_surround: None,
4905 textmate_grammar: None,
4906 show_whitespace_tabs: true,
4907 line_wrap: None,
4908 wrap_column: None,
4909 page_view: None,
4910 page_width: None,
4911 use_tabs: None,
4912 tab_size: None,
4913 formatter: None,
4914 format_on_save: false,
4915 on_save: vec![],
4916 word_characters: None,
4917 },
4918 );
4919
4920 languages.insert(
4921 "sql".to_string(),
4922 LanguageConfig {
4923 extensions: vec!["sql".to_string()],
4924 filenames: vec![],
4925 grammar: "SQL".to_string(),
4926 comment_prefix: Some("--".to_string()),
4927 auto_indent: true,
4928 auto_close: None,
4929 auto_surround: None,
4930 textmate_grammar: None,
4931 show_whitespace_tabs: true,
4932 line_wrap: None,
4933 wrap_column: None,
4934 page_view: None,
4935 page_width: None,
4936 use_tabs: None,
4937 tab_size: None,
4938 formatter: None,
4939 format_on_save: false,
4940 on_save: vec![],
4941 word_characters: None,
4942 },
4943 );
4944
4945 languages.insert(
4946 "graphql".to_string(),
4947 LanguageConfig {
4948 extensions: vec!["graphql".to_string(), "gql".to_string()],
4949 filenames: vec![],
4950 grammar: "GraphQL".to_string(),
4951 comment_prefix: Some("#".to_string()),
4952 auto_indent: true,
4953 auto_close: None,
4954 auto_surround: None,
4955 textmate_grammar: None,
4956 show_whitespace_tabs: true,
4957 line_wrap: None,
4958 wrap_column: None,
4959 page_view: None,
4960 page_width: None,
4961 use_tabs: None,
4962 tab_size: None,
4963 formatter: None,
4964 format_on_save: false,
4965 on_save: vec![],
4966 word_characters: None,
4967 },
4968 );
4969
4970 languages.insert(
4971 "protobuf".to_string(),
4972 LanguageConfig {
4973 extensions: vec!["proto".to_string()],
4974 filenames: vec![],
4975 grammar: "Protocol Buffers".to_string(),
4976 comment_prefix: Some("//".to_string()),
4977 auto_indent: true,
4978 auto_close: None,
4979 auto_surround: None,
4980 textmate_grammar: None,
4981 show_whitespace_tabs: true,
4982 line_wrap: None,
4983 wrap_column: None,
4984 page_view: None,
4985 page_width: None,
4986 use_tabs: None,
4987 tab_size: None,
4988 formatter: None,
4989 format_on_save: false,
4990 on_save: vec![],
4991 word_characters: None,
4992 },
4993 );
4994
4995 languages.insert(
4996 "cmake".to_string(),
4997 LanguageConfig {
4998 extensions: vec!["cmake".to_string()],
4999 filenames: vec!["CMakeLists.txt".to_string()],
5000 grammar: "CMake".to_string(),
5001 comment_prefix: Some("#".to_string()),
5002 auto_indent: true,
5003 auto_close: None,
5004 auto_surround: None,
5005 textmate_grammar: None,
5006 show_whitespace_tabs: true,
5007 line_wrap: None,
5008 wrap_column: None,
5009 page_view: None,
5010 page_width: None,
5011 use_tabs: None,
5012 tab_size: None,
5013 formatter: None,
5014 format_on_save: false,
5015 on_save: vec![],
5016 word_characters: None,
5017 },
5018 );
5019
5020 languages.insert(
5021 "terraform".to_string(),
5022 LanguageConfig {
5023 extensions: vec!["tf".to_string(), "tfvars".to_string(), "hcl".to_string()],
5024 filenames: vec![],
5025 grammar: "HCL".to_string(),
5026 comment_prefix: Some("#".to_string()),
5027 auto_indent: true,
5028 auto_close: None,
5029 auto_surround: None,
5030 textmate_grammar: None,
5031 show_whitespace_tabs: true,
5032 line_wrap: None,
5033 wrap_column: None,
5034 page_view: None,
5035 page_width: None,
5036 use_tabs: None,
5037 tab_size: None,
5038 formatter: None,
5039 format_on_save: false,
5040 on_save: vec![],
5041 word_characters: None,
5042 },
5043 );
5044
5045 languages.insert(
5046 "vue".to_string(),
5047 LanguageConfig {
5048 extensions: vec!["vue".to_string()],
5049 filenames: vec![],
5050 grammar: "Vue".to_string(),
5051 comment_prefix: None,
5052 auto_indent: true,
5053 auto_close: None,
5054 auto_surround: None,
5055 textmate_grammar: None,
5056 show_whitespace_tabs: true,
5057 line_wrap: None,
5058 wrap_column: None,
5059 page_view: None,
5060 page_width: None,
5061 use_tabs: None,
5062 tab_size: None,
5063 formatter: None,
5064 format_on_save: false,
5065 on_save: vec![],
5066 word_characters: None,
5067 },
5068 );
5069
5070 languages.insert(
5071 "svelte".to_string(),
5072 LanguageConfig {
5073 extensions: vec!["svelte".to_string()],
5074 filenames: vec![],
5075 grammar: "Svelte".to_string(),
5076 comment_prefix: None,
5077 auto_indent: true,
5078 auto_close: None,
5079 auto_surround: None,
5080 textmate_grammar: None,
5081 show_whitespace_tabs: true,
5082 line_wrap: None,
5083 wrap_column: None,
5084 page_view: None,
5085 page_width: None,
5086 use_tabs: None,
5087 tab_size: None,
5088 formatter: None,
5089 format_on_save: false,
5090 on_save: vec![],
5091 word_characters: None,
5092 },
5093 );
5094
5095 languages.insert(
5096 "astro".to_string(),
5097 LanguageConfig {
5098 extensions: vec!["astro".to_string()],
5099 filenames: vec![],
5100 grammar: "Astro".to_string(),
5101 comment_prefix: None,
5102 auto_indent: true,
5103 auto_close: None,
5104 auto_surround: None,
5105 textmate_grammar: None,
5106 show_whitespace_tabs: true,
5107 line_wrap: None,
5108 wrap_column: None,
5109 page_view: None,
5110 page_width: None,
5111 use_tabs: None,
5112 tab_size: None,
5113 formatter: None,
5114 format_on_save: false,
5115 on_save: vec![],
5116 word_characters: None,
5117 },
5118 );
5119
5120 languages.insert(
5123 "scss".to_string(),
5124 LanguageConfig {
5125 extensions: vec!["scss".to_string()],
5126 filenames: vec![],
5127 grammar: "SCSS".to_string(),
5128 comment_prefix: Some("//".to_string()),
5129 auto_indent: true,
5130 auto_close: None,
5131 auto_surround: None,
5132 textmate_grammar: None,
5133 show_whitespace_tabs: true,
5134 line_wrap: None,
5135 wrap_column: None,
5136 page_view: None,
5137 page_width: None,
5138 use_tabs: None,
5139 tab_size: None,
5140 formatter: None,
5141 format_on_save: false,
5142 on_save: vec![],
5143 word_characters: None,
5144 },
5145 );
5146
5147 languages.insert(
5148 "less".to_string(),
5149 LanguageConfig {
5150 extensions: vec!["less".to_string()],
5151 filenames: vec![],
5152 grammar: "LESS".to_string(),
5153 comment_prefix: Some("//".to_string()),
5154 auto_indent: true,
5155 auto_close: None,
5156 auto_surround: None,
5157 textmate_grammar: None,
5158 show_whitespace_tabs: true,
5159 line_wrap: None,
5160 wrap_column: None,
5161 page_view: None,
5162 page_width: None,
5163 use_tabs: None,
5164 tab_size: None,
5165 formatter: None,
5166 format_on_save: false,
5167 on_save: vec![],
5168 word_characters: None,
5169 },
5170 );
5171
5172 languages.insert(
5173 "powershell".to_string(),
5174 LanguageConfig {
5175 extensions: vec!["ps1".to_string(), "psm1".to_string(), "psd1".to_string()],
5176 filenames: vec![],
5177 grammar: "PowerShell".to_string(),
5178 comment_prefix: Some("#".to_string()),
5179 auto_indent: true,
5180 auto_close: None,
5181 auto_surround: None,
5182 textmate_grammar: None,
5183 show_whitespace_tabs: true,
5184 line_wrap: None,
5185 wrap_column: None,
5186 page_view: None,
5187 page_width: None,
5188 use_tabs: None,
5189 tab_size: None,
5190 formatter: None,
5191 format_on_save: false,
5192 on_save: vec![],
5193 word_characters: None,
5194 },
5195 );
5196
5197 languages.insert(
5198 "kdl".to_string(),
5199 LanguageConfig {
5200 extensions: vec!["kdl".to_string()],
5201 filenames: vec![],
5202 grammar: "KDL".to_string(),
5203 comment_prefix: Some("//".to_string()),
5204 auto_indent: true,
5205 auto_close: None,
5206 auto_surround: None,
5207 textmate_grammar: None,
5208 show_whitespace_tabs: true,
5209 line_wrap: None,
5210 wrap_column: None,
5211 page_view: None,
5212 page_width: None,
5213 use_tabs: None,
5214 tab_size: None,
5215 formatter: None,
5216 format_on_save: false,
5217 on_save: vec![],
5218 word_characters: None,
5219 },
5220 );
5221
5222 languages.insert(
5223 "starlark".to_string(),
5224 LanguageConfig {
5225 extensions: vec!["bzl".to_string(), "star".to_string()],
5226 filenames: vec!["BUILD".to_string(), "WORKSPACE".to_string()],
5227 grammar: "Starlark".to_string(),
5228 comment_prefix: Some("#".to_string()),
5229 auto_indent: true,
5230 auto_close: None,
5231 auto_surround: None,
5232 textmate_grammar: None,
5233 show_whitespace_tabs: true,
5234 line_wrap: None,
5235 wrap_column: None,
5236 page_view: None,
5237 page_width: None,
5238 use_tabs: None,
5239 tab_size: None,
5240 formatter: None,
5241 format_on_save: false,
5242 on_save: vec![],
5243 word_characters: None,
5244 },
5245 );
5246
5247 languages.insert(
5248 "justfile".to_string(),
5249 LanguageConfig {
5250 extensions: vec![],
5251 filenames: vec![
5252 "justfile".to_string(),
5253 "Justfile".to_string(),
5254 ".justfile".to_string(),
5255 ],
5256 grammar: "Justfile".to_string(),
5257 comment_prefix: Some("#".to_string()),
5258 auto_indent: true,
5259 auto_close: None,
5260 auto_surround: None,
5261 textmate_grammar: None,
5262 show_whitespace_tabs: true,
5263 line_wrap: None,
5264 wrap_column: None,
5265 page_view: None,
5266 page_width: None,
5267 use_tabs: Some(true),
5268 tab_size: None,
5269 formatter: None,
5270 format_on_save: false,
5271 on_save: vec![],
5272 word_characters: None,
5273 },
5274 );
5275
5276 languages.insert(
5277 "earthfile".to_string(),
5278 LanguageConfig {
5279 extensions: vec!["earth".to_string()],
5280 filenames: vec!["Earthfile".to_string()],
5281 grammar: "Earthfile".to_string(),
5282 comment_prefix: Some("#".to_string()),
5283 auto_indent: true,
5284 auto_close: None,
5285 auto_surround: None,
5286 textmate_grammar: None,
5287 show_whitespace_tabs: true,
5288 line_wrap: None,
5289 wrap_column: None,
5290 page_view: None,
5291 page_width: None,
5292 use_tabs: None,
5293 tab_size: None,
5294 formatter: None,
5295 format_on_save: false,
5296 on_save: vec![],
5297 word_characters: None,
5298 },
5299 );
5300
5301 languages.insert(
5302 "gomod".to_string(),
5303 LanguageConfig {
5304 extensions: vec![],
5305 filenames: vec!["go.mod".to_string(), "go.sum".to_string()],
5306 grammar: "Go Module".to_string(),
5307 comment_prefix: Some("//".to_string()),
5308 auto_indent: true,
5309 auto_close: None,
5310 auto_surround: None,
5311 textmate_grammar: None,
5312 show_whitespace_tabs: true,
5313 line_wrap: None,
5314 wrap_column: None,
5315 page_view: None,
5316 page_width: None,
5317 use_tabs: Some(true),
5318 tab_size: None,
5319 formatter: None,
5320 format_on_save: false,
5321 on_save: vec![],
5322 word_characters: None,
5323 },
5324 );
5325
5326 languages.insert(
5327 "vlang".to_string(),
5328 LanguageConfig {
5329 extensions: vec!["v".to_string(), "vv".to_string()],
5330 filenames: vec![],
5331 grammar: "V".to_string(),
5332 comment_prefix: Some("//".to_string()),
5333 auto_indent: true,
5334 auto_close: None,
5335 auto_surround: None,
5336 textmate_grammar: None,
5337 show_whitespace_tabs: true,
5338 line_wrap: None,
5339 wrap_column: None,
5340 page_view: None,
5341 page_width: None,
5342 use_tabs: None,
5343 tab_size: None,
5344 formatter: None,
5345 format_on_save: false,
5346 on_save: vec![],
5347 word_characters: None,
5348 },
5349 );
5350
5351 languages.insert(
5352 "ini".to_string(),
5353 LanguageConfig {
5354 extensions: vec!["ini".to_string(), "cfg".to_string()],
5355 filenames: vec![],
5356 grammar: "INI".to_string(),
5357 comment_prefix: Some(";".to_string()),
5358 auto_indent: false,
5359 auto_close: None,
5360 auto_surround: None,
5361 textmate_grammar: None,
5362 show_whitespace_tabs: true,
5363 line_wrap: None,
5364 wrap_column: None,
5365 page_view: None,
5366 page_width: None,
5367 use_tabs: None,
5368 tab_size: None,
5369 formatter: None,
5370 format_on_save: false,
5371 on_save: vec![],
5372 word_characters: None,
5373 },
5374 );
5375
5376 languages.insert(
5377 "hyprlang".to_string(),
5378 LanguageConfig {
5379 extensions: vec!["hl".to_string()],
5380 filenames: vec!["hyprland.conf".to_string()],
5381 grammar: "Hyprlang".to_string(),
5382 comment_prefix: Some("#".to_string()),
5383 auto_indent: true,
5384 auto_close: None,
5385 auto_surround: None,
5386 textmate_grammar: None,
5387 show_whitespace_tabs: true,
5388 line_wrap: None,
5389 wrap_column: None,
5390 page_view: None,
5391 page_width: None,
5392 use_tabs: None,
5393 tab_size: None,
5394 formatter: None,
5395 format_on_save: false,
5396 on_save: vec![],
5397 word_characters: None,
5398 },
5399 );
5400
5401 languages
5402 }
5403
5404 #[cfg(feature = "runtime")]
5406 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
5407 let mut lsp = HashMap::new();
5408
5409 let ra_log_path = crate::services::log_dirs::lsp_log_path("rust-analyzer")
5412 .to_string_lossy()
5413 .to_string();
5414
5415 Self::populate_lsp_config(&mut lsp, ra_log_path);
5416 lsp
5417 }
5418
5419 #[cfg(not(feature = "runtime"))]
5421 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
5422 HashMap::new()
5424 }
5425
5426 #[cfg(feature = "runtime")]
5428 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
5429 let mut universal = HashMap::new();
5430
5431 universal.insert(
5444 "quicklsp".to_string(),
5445 LspLanguageConfig::Multi(vec![LspServerConfig {
5446 command: "quicklsp".to_string(),
5447 args: vec![],
5448 enabled: false,
5449 auto_start: false,
5450 process_limits: ProcessLimits::default(),
5451 initialization_options: None,
5452 env: Default::default(),
5453 language_id_overrides: Default::default(),
5454 name: Some("QuickLSP".to_string()),
5455 only_features: None,
5456 except_features: None,
5457 root_markers: vec![
5458 "Cargo.toml".to_string(),
5459 "package.json".to_string(),
5460 "go.mod".to_string(),
5461 "pyproject.toml".to_string(),
5462 "requirements.txt".to_string(),
5463 ".git".to_string(),
5464 ],
5465 }]),
5466 );
5467
5468 universal
5469 }
5470
5471 #[cfg(not(feature = "runtime"))]
5473 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
5474 HashMap::new()
5475 }
5476
5477 #[cfg(feature = "runtime")]
5478 fn populate_lsp_config(lsp: &mut HashMap<String, LspLanguageConfig>, ra_log_path: String) {
5479 lsp.insert(
5483 "rust".to_string(),
5484 LspLanguageConfig::Multi(vec![LspServerConfig {
5485 command: "rust-analyzer".to_string(),
5486 args: vec!["--log-file".to_string(), ra_log_path],
5487 enabled: true,
5488 auto_start: false,
5489 process_limits: ProcessLimits::unlimited(),
5490 initialization_options: None,
5491 env: Default::default(),
5492 language_id_overrides: Default::default(),
5493 name: None,
5494 only_features: None,
5495 except_features: None,
5496 root_markers: vec![
5497 "Cargo.toml".to_string(),
5498 "rust-project.json".to_string(),
5499 ".git".to_string(),
5500 ],
5501 }]),
5502 );
5503
5504 lsp.insert(
5506 "python".to_string(),
5507 LspLanguageConfig::Multi(vec![LspServerConfig {
5508 command: "pylsp".to_string(),
5509 args: vec![],
5510 enabled: true,
5511 auto_start: false,
5512 process_limits: ProcessLimits::default(),
5513 initialization_options: None,
5514 env: Default::default(),
5515 language_id_overrides: Default::default(),
5516 name: None,
5517 only_features: None,
5518 except_features: None,
5519 root_markers: vec![
5520 "pyproject.toml".to_string(),
5521 "setup.py".to_string(),
5522 "setup.cfg".to_string(),
5523 "pyrightconfig.json".to_string(),
5524 ".git".to_string(),
5525 ],
5526 }]),
5527 );
5528
5529 lsp.insert(
5532 "javascript".to_string(),
5533 LspLanguageConfig::Multi(vec![LspServerConfig {
5534 command: "typescript-language-server".to_string(),
5535 args: vec!["--stdio".to_string()],
5536 enabled: true,
5537 auto_start: false,
5538 process_limits: ProcessLimits::default(),
5539 initialization_options: None,
5540 env: Default::default(),
5541 language_id_overrides: HashMap::from([(
5542 "jsx".to_string(),
5543 "javascriptreact".to_string(),
5544 )]),
5545 name: None,
5546 only_features: None,
5547 except_features: None,
5548 root_markers: vec![
5549 "tsconfig.json".to_string(),
5550 "jsconfig.json".to_string(),
5551 "package.json".to_string(),
5552 ".git".to_string(),
5553 ],
5554 }]),
5555 );
5556 lsp.insert(
5557 "typescript".to_string(),
5558 LspLanguageConfig::Multi(vec![LspServerConfig {
5559 command: "typescript-language-server".to_string(),
5560 args: vec!["--stdio".to_string()],
5561 enabled: true,
5562 auto_start: false,
5563 process_limits: ProcessLimits::default(),
5564 initialization_options: None,
5565 env: Default::default(),
5566 language_id_overrides: HashMap::from([(
5567 "tsx".to_string(),
5568 "typescriptreact".to_string(),
5569 )]),
5570 name: None,
5571 only_features: None,
5572 except_features: None,
5573 root_markers: vec![
5574 "tsconfig.json".to_string(),
5575 "jsconfig.json".to_string(),
5576 "package.json".to_string(),
5577 ".git".to_string(),
5578 ],
5579 }]),
5580 );
5581
5582 lsp.insert(
5584 "html".to_string(),
5585 LspLanguageConfig::Multi(vec![LspServerConfig {
5586 command: "vscode-html-language-server".to_string(),
5587 args: vec!["--stdio".to_string()],
5588 enabled: true,
5589 auto_start: false,
5590 process_limits: ProcessLimits::default(),
5591 initialization_options: None,
5592 env: Default::default(),
5593 language_id_overrides: Default::default(),
5594 name: None,
5595 only_features: None,
5596 except_features: None,
5597 root_markers: Default::default(),
5598 }]),
5599 );
5600
5601 lsp.insert(
5603 "css".to_string(),
5604 LspLanguageConfig::Multi(vec![LspServerConfig {
5605 command: "vscode-css-language-server".to_string(),
5606 args: vec!["--stdio".to_string()],
5607 enabled: true,
5608 auto_start: false,
5609 process_limits: ProcessLimits::default(),
5610 initialization_options: None,
5611 env: Default::default(),
5612 language_id_overrides: Default::default(),
5613 name: None,
5614 only_features: None,
5615 except_features: None,
5616 root_markers: Default::default(),
5617 }]),
5618 );
5619
5620 lsp.insert(
5622 "c".to_string(),
5623 LspLanguageConfig::Multi(vec![LspServerConfig {
5624 command: "clangd".to_string(),
5625 args: vec![],
5626 enabled: true,
5627 auto_start: false,
5628 process_limits: ProcessLimits::default(),
5629 initialization_options: None,
5630 env: Default::default(),
5631 language_id_overrides: Default::default(),
5632 name: None,
5633 only_features: None,
5634 except_features: None,
5635 root_markers: vec![
5636 "compile_commands.json".to_string(),
5637 "CMakeLists.txt".to_string(),
5638 "Makefile".to_string(),
5639 ".git".to_string(),
5640 ],
5641 }]),
5642 );
5643 lsp.insert(
5644 "cpp".to_string(),
5645 LspLanguageConfig::Multi(vec![LspServerConfig {
5646 command: "clangd".to_string(),
5647 args: vec![],
5648 enabled: true,
5649 auto_start: false,
5650 process_limits: ProcessLimits::default(),
5651 initialization_options: None,
5652 env: Default::default(),
5653 language_id_overrides: Default::default(),
5654 name: None,
5655 only_features: None,
5656 except_features: None,
5657 root_markers: vec![
5658 "compile_commands.json".to_string(),
5659 "CMakeLists.txt".to_string(),
5660 "Makefile".to_string(),
5661 ".git".to_string(),
5662 ],
5663 }]),
5664 );
5665
5666 lsp.insert(
5668 "go".to_string(),
5669 LspLanguageConfig::Multi(vec![LspServerConfig {
5670 command: "gopls".to_string(),
5671 args: vec![],
5672 enabled: true,
5673 auto_start: false,
5674 process_limits: ProcessLimits::default(),
5675 initialization_options: None,
5676 env: Default::default(),
5677 language_id_overrides: Default::default(),
5678 name: None,
5679 only_features: None,
5680 except_features: None,
5681 root_markers: vec![
5682 "go.mod".to_string(),
5683 "go.work".to_string(),
5684 ".git".to_string(),
5685 ],
5686 }]),
5687 );
5688
5689 lsp.insert(
5691 "json".to_string(),
5692 LspLanguageConfig::Multi(vec![LspServerConfig {
5693 command: "vscode-json-language-server".to_string(),
5694 args: vec!["--stdio".to_string()],
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(
5712 "jsonc".to_string(),
5713 LspLanguageConfig::Multi(vec![LspServerConfig {
5714 command: "vscode-json-language-server".to_string(),
5715 args: vec!["--stdio".to_string()],
5716 enabled: true,
5717 auto_start: false,
5718 process_limits: ProcessLimits::default(),
5719 initialization_options: None,
5720 env: Default::default(),
5721 language_id_overrides: Default::default(),
5722 name: None,
5723 only_features: None,
5724 except_features: None,
5725 root_markers: Default::default(),
5726 }]),
5727 );
5728
5729 lsp.insert(
5731 "csharp".to_string(),
5732 LspLanguageConfig::Multi(vec![LspServerConfig {
5733 command: "csharp-ls".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 "*.csproj".to_string(),
5746 "*.sln".to_string(),
5747 ".git".to_string(),
5748 ],
5749 }]),
5750 );
5751
5752 lsp.insert(
5755 "odin".to_string(),
5756 LspLanguageConfig::Multi(vec![LspServerConfig {
5757 command: "ols".to_string(),
5758 args: vec![],
5759 enabled: true,
5760 auto_start: false,
5761 process_limits: ProcessLimits::default(),
5762 initialization_options: None,
5763 env: Default::default(),
5764 language_id_overrides: Default::default(),
5765 name: None,
5766 only_features: None,
5767 except_features: None,
5768 root_markers: Default::default(),
5769 }]),
5770 );
5771
5772 lsp.insert(
5775 "zig".to_string(),
5776 LspLanguageConfig::Multi(vec![LspServerConfig {
5777 command: "zls".to_string(),
5778 args: vec![],
5779 enabled: true,
5780 auto_start: false,
5781 process_limits: ProcessLimits::default(),
5782 initialization_options: None,
5783 env: Default::default(),
5784 language_id_overrides: Default::default(),
5785 name: None,
5786 only_features: None,
5787 except_features: None,
5788 root_markers: Default::default(),
5789 }]),
5790 );
5791
5792 lsp.insert(
5795 "c3".to_string(),
5796 LspLanguageConfig::Multi(vec![LspServerConfig {
5797 command: "c3lsp".to_string(),
5798 args: vec![],
5799 enabled: true,
5800 auto_start: false,
5801 process_limits: ProcessLimits::default(),
5802 initialization_options: None,
5803 env: Default::default(),
5804 language_id_overrides: Default::default(),
5805 name: None,
5806 only_features: None,
5807 except_features: None,
5808 root_markers: vec!["project.json".to_string(), ".git".to_string()],
5809 }]),
5810 );
5811
5812 lsp.insert(
5815 "java".to_string(),
5816 LspLanguageConfig::Multi(vec![LspServerConfig {
5817 command: "jdtls".to_string(),
5818 args: vec![],
5819 enabled: true,
5820 auto_start: false,
5821 process_limits: ProcessLimits::default(),
5822 initialization_options: None,
5823 env: Default::default(),
5824 language_id_overrides: Default::default(),
5825 name: None,
5826 only_features: None,
5827 except_features: None,
5828 root_markers: vec![
5829 "pom.xml".to_string(),
5830 "build.gradle".to_string(),
5831 "build.gradle.kts".to_string(),
5832 ".git".to_string(),
5833 ],
5834 }]),
5835 );
5836
5837 lsp.insert(
5840 "latex".to_string(),
5841 LspLanguageConfig::Multi(vec![LspServerConfig {
5842 command: "texlab".to_string(),
5843 args: vec![],
5844 enabled: true,
5845 auto_start: false,
5846 process_limits: ProcessLimits::default(),
5847 initialization_options: None,
5848 env: Default::default(),
5849 language_id_overrides: Default::default(),
5850 name: None,
5851 only_features: None,
5852 except_features: None,
5853 root_markers: Default::default(),
5854 }]),
5855 );
5856
5857 lsp.insert(
5860 "markdown".to_string(),
5861 LspLanguageConfig::Multi(vec![LspServerConfig {
5862 command: "marksman".to_string(),
5863 args: vec!["server".to_string()],
5864 enabled: true,
5865 auto_start: false,
5866 process_limits: ProcessLimits::default(),
5867 initialization_options: None,
5868 env: Default::default(),
5869 language_id_overrides: Default::default(),
5870 name: None,
5871 only_features: None,
5872 except_features: None,
5873 root_markers: Default::default(),
5874 }]),
5875 );
5876
5877 lsp.insert(
5880 "templ".to_string(),
5881 LspLanguageConfig::Multi(vec![LspServerConfig {
5882 command: "templ".to_string(),
5883 args: vec!["lsp".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: Default::default(),
5894 }]),
5895 );
5896
5897 lsp.insert(
5900 "typst".to_string(),
5901 LspLanguageConfig::Multi(vec![LspServerConfig {
5902 command: "tinymist".to_string(),
5903 args: vec![],
5904 enabled: true,
5905 auto_start: false,
5906 process_limits: ProcessLimits::default(),
5907 initialization_options: None,
5908 env: Default::default(),
5909 language_id_overrides: Default::default(),
5910 name: None,
5911 only_features: None,
5912 except_features: None,
5913 root_markers: Default::default(),
5914 }]),
5915 );
5916
5917 lsp.insert(
5919 "bash".to_string(),
5920 LspLanguageConfig::Multi(vec![LspServerConfig {
5921 command: "bash-language-server".to_string(),
5922 args: vec!["start".to_string()],
5923 enabled: true,
5924 auto_start: false,
5925 process_limits: ProcessLimits::default(),
5926 initialization_options: None,
5927 env: Default::default(),
5928 language_id_overrides: Default::default(),
5929 name: None,
5930 only_features: None,
5931 except_features: None,
5932 root_markers: Default::default(),
5933 }]),
5934 );
5935
5936 lsp.insert(
5939 "lua".to_string(),
5940 LspLanguageConfig::Multi(vec![LspServerConfig {
5941 command: "lua-language-server".to_string(),
5942 args: vec![],
5943 enabled: true,
5944 auto_start: false,
5945 process_limits: ProcessLimits::default(),
5946 initialization_options: None,
5947 env: Default::default(),
5948 language_id_overrides: Default::default(),
5949 name: None,
5950 only_features: None,
5951 except_features: None,
5952 root_markers: vec![
5953 ".luarc.json".to_string(),
5954 ".luarc.jsonc".to_string(),
5955 ".luacheckrc".to_string(),
5956 ".stylua.toml".to_string(),
5957 ".git".to_string(),
5958 ],
5959 }]),
5960 );
5961
5962 lsp.insert(
5964 "ruby".to_string(),
5965 LspLanguageConfig::Multi(vec![LspServerConfig {
5966 command: "solargraph".to_string(),
5967 args: vec!["stdio".to_string()],
5968 enabled: true,
5969 auto_start: false,
5970 process_limits: ProcessLimits::default(),
5971 initialization_options: None,
5972 env: Default::default(),
5973 language_id_overrides: Default::default(),
5974 name: None,
5975 only_features: None,
5976 except_features: None,
5977 root_markers: vec![
5978 "Gemfile".to_string(),
5979 ".ruby-version".to_string(),
5980 ".git".to_string(),
5981 ],
5982 }]),
5983 );
5984
5985 lsp.insert(
5988 "php".to_string(),
5989 LspLanguageConfig::Multi(vec![LspServerConfig {
5990 command: "phpactor".to_string(),
5991 args: vec!["language-server".to_string()],
5992 enabled: true,
5993 auto_start: false,
5994 process_limits: ProcessLimits::default(),
5995 initialization_options: None,
5996 env: Default::default(),
5997 language_id_overrides: Default::default(),
5998 name: None,
5999 only_features: None,
6000 except_features: None,
6001 root_markers: vec!["composer.json".to_string(), ".git".to_string()],
6002 }]),
6003 );
6004
6005 lsp.insert(
6007 "yaml".to_string(),
6008 LspLanguageConfig::Multi(vec![LspServerConfig {
6009 command: "yaml-language-server".to_string(),
6010 args: vec!["--stdio".to_string()],
6011 enabled: true,
6012 auto_start: false,
6013 process_limits: ProcessLimits::default(),
6014 initialization_options: None,
6015 env: Default::default(),
6016 language_id_overrides: Default::default(),
6017 name: None,
6018 only_features: None,
6019 except_features: None,
6020 root_markers: Default::default(),
6021 }]),
6022 );
6023
6024 lsp.insert(
6027 "toml".to_string(),
6028 LspLanguageConfig::Multi(vec![LspServerConfig {
6029 command: "taplo".to_string(),
6030 args: vec!["lsp".to_string(), "stdio".to_string()],
6031 enabled: true,
6032 auto_start: false,
6033 process_limits: ProcessLimits::default(),
6034 initialization_options: None,
6035 env: Default::default(),
6036 language_id_overrides: Default::default(),
6037 name: None,
6038 only_features: None,
6039 except_features: None,
6040 root_markers: Default::default(),
6041 }]),
6042 );
6043
6044 lsp.insert(
6047 "dart".to_string(),
6048 LspLanguageConfig::Multi(vec![LspServerConfig {
6049 command: "dart".to_string(),
6050 args: vec!["language-server".to_string(), "--protocol=lsp".to_string()],
6051 enabled: true,
6052 auto_start: false,
6053 process_limits: ProcessLimits::default(),
6054 initialization_options: None,
6055 env: Default::default(),
6056 language_id_overrides: Default::default(),
6057 name: None,
6058 only_features: None,
6059 except_features: None,
6060 root_markers: vec!["pubspec.yaml".to_string(), ".git".to_string()],
6061 }]),
6062 );
6063
6064 lsp.insert(
6067 "nushell".to_string(),
6068 LspLanguageConfig::Multi(vec![LspServerConfig {
6069 command: "nu".to_string(),
6070 args: vec!["--lsp".to_string()],
6071 enabled: true,
6072 auto_start: false,
6073 process_limits: ProcessLimits::default(),
6074 initialization_options: None,
6075 env: Default::default(),
6076 language_id_overrides: Default::default(),
6077 name: None,
6078 only_features: None,
6079 except_features: None,
6080 root_markers: Default::default(),
6081 }]),
6082 );
6083
6084 lsp.insert(
6087 "solidity".to_string(),
6088 LspLanguageConfig::Multi(vec![LspServerConfig {
6089 command: "nomicfoundation-solidity-language-server".to_string(),
6090 args: vec!["--stdio".to_string()],
6091 enabled: true,
6092 auto_start: false,
6093 process_limits: ProcessLimits::default(),
6094 initialization_options: None,
6095 env: Default::default(),
6096 language_id_overrides: Default::default(),
6097 name: None,
6098 only_features: None,
6099 except_features: None,
6100 root_markers: Default::default(),
6101 }]),
6102 );
6103
6104 lsp.insert(
6109 "terraform".to_string(),
6110 LspLanguageConfig::Multi(vec![LspServerConfig {
6111 command: "terraform-ls".to_string(),
6112 args: vec!["serve".to_string()],
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: vec![
6123 "*.tf".to_string(),
6124 ".terraform".to_string(),
6125 ".git".to_string(),
6126 ],
6127 }]),
6128 );
6129
6130 lsp.insert(
6133 "cmake".to_string(),
6134 LspLanguageConfig::Multi(vec![LspServerConfig {
6135 command: "cmake-language-server".to_string(),
6136 args: vec![],
6137 enabled: true,
6138 auto_start: false,
6139 process_limits: ProcessLimits::default(),
6140 initialization_options: None,
6141 env: Default::default(),
6142 language_id_overrides: Default::default(),
6143 name: None,
6144 only_features: None,
6145 except_features: None,
6146 root_markers: vec!["CMakeLists.txt".to_string(), ".git".to_string()],
6147 }]),
6148 );
6149
6150 lsp.insert(
6153 "protobuf".to_string(),
6154 LspLanguageConfig::Multi(vec![LspServerConfig {
6155 command: "buf".to_string(),
6156 args: vec!["beta".to_string(), "lsp".to_string()],
6157 enabled: true,
6158 auto_start: false,
6159 process_limits: ProcessLimits::default(),
6160 initialization_options: None,
6161 env: Default::default(),
6162 language_id_overrides: Default::default(),
6163 name: None,
6164 only_features: None,
6165 except_features: None,
6166 root_markers: Default::default(),
6167 }]),
6168 );
6169
6170 lsp.insert(
6173 "graphql".to_string(),
6174 LspLanguageConfig::Multi(vec![LspServerConfig {
6175 command: "graphql-lsp".to_string(),
6176 args: vec!["server".to_string(), "-m".to_string(), "stream".to_string()],
6177 enabled: true,
6178 auto_start: false,
6179 process_limits: ProcessLimits::default(),
6180 initialization_options: None,
6181 env: Default::default(),
6182 language_id_overrides: Default::default(),
6183 name: None,
6184 only_features: None,
6185 except_features: None,
6186 root_markers: Default::default(),
6187 }]),
6188 );
6189
6190 lsp.insert(
6193 "sql".to_string(),
6194 LspLanguageConfig::Multi(vec![LspServerConfig {
6195 command: "sqls".to_string(),
6196 args: vec![],
6197 enabled: true,
6198 auto_start: false,
6199 process_limits: ProcessLimits::default(),
6200 initialization_options: None,
6201 env: Default::default(),
6202 language_id_overrides: Default::default(),
6203 name: None,
6204 only_features: None,
6205 except_features: None,
6206 root_markers: Default::default(),
6207 }]),
6208 );
6209
6210 lsp.insert(
6214 "vue".to_string(),
6215 LspLanguageConfig::Multi(vec![LspServerConfig {
6216 command: "vue-language-server".to_string(),
6217 args: vec!["--stdio".to_string()],
6218 enabled: true,
6219 auto_start: false,
6220 process_limits: ProcessLimits::default(),
6221 initialization_options: None,
6222 env: Default::default(),
6223 language_id_overrides: Default::default(),
6224 name: None,
6225 only_features: None,
6226 except_features: None,
6227 root_markers: Default::default(),
6228 }]),
6229 );
6230
6231 lsp.insert(
6233 "svelte".to_string(),
6234 LspLanguageConfig::Multi(vec![LspServerConfig {
6235 command: "svelteserver".to_string(),
6236 args: vec!["--stdio".to_string()],
6237 enabled: true,
6238 auto_start: false,
6239 process_limits: ProcessLimits::default(),
6240 initialization_options: None,
6241 env: Default::default(),
6242 language_id_overrides: Default::default(),
6243 name: None,
6244 only_features: None,
6245 except_features: None,
6246 root_markers: Default::default(),
6247 }]),
6248 );
6249
6250 lsp.insert(
6252 "astro".to_string(),
6253 LspLanguageConfig::Multi(vec![LspServerConfig {
6254 command: "astro-ls".to_string(),
6255 args: vec!["--stdio".to_string()],
6256 enabled: true,
6257 auto_start: false,
6258 process_limits: ProcessLimits::default(),
6259 initialization_options: None,
6260 env: Default::default(),
6261 language_id_overrides: Default::default(),
6262 name: None,
6263 only_features: None,
6264 except_features: None,
6265 root_markers: Default::default(),
6266 }]),
6267 );
6268
6269 lsp.insert(
6271 "tailwindcss".to_string(),
6272 LspLanguageConfig::Multi(vec![LspServerConfig {
6273 command: "tailwindcss-language-server".to_string(),
6274 args: vec!["--stdio".to_string()],
6275 enabled: true,
6276 auto_start: false,
6277 process_limits: ProcessLimits::default(),
6278 initialization_options: None,
6279 env: Default::default(),
6280 language_id_overrides: Default::default(),
6281 name: None,
6282 only_features: None,
6283 except_features: None,
6284 root_markers: Default::default(),
6285 }]),
6286 );
6287
6288 lsp.insert(
6293 "nix".to_string(),
6294 LspLanguageConfig::Multi(vec![LspServerConfig {
6295 command: "nil".to_string(),
6296 args: vec![],
6297 enabled: true,
6298 auto_start: false,
6299 process_limits: ProcessLimits::default(),
6300 initialization_options: None,
6301 env: Default::default(),
6302 language_id_overrides: Default::default(),
6303 name: None,
6304 only_features: None,
6305 except_features: None,
6306 root_markers: Default::default(),
6307 }]),
6308 );
6309
6310 lsp.insert(
6313 "kotlin".to_string(),
6314 LspLanguageConfig::Multi(vec![LspServerConfig {
6315 command: "kotlin-language-server".to_string(),
6316 args: vec![],
6317 enabled: true,
6318 auto_start: false,
6319 process_limits: ProcessLimits::default(),
6320 initialization_options: None,
6321 env: Default::default(),
6322 language_id_overrides: Default::default(),
6323 name: None,
6324 only_features: None,
6325 except_features: None,
6326 root_markers: Default::default(),
6327 }]),
6328 );
6329
6330 lsp.insert(
6332 "swift".to_string(),
6333 LspLanguageConfig::Multi(vec![LspServerConfig {
6334 command: "sourcekit-lsp".to_string(),
6335 args: vec![],
6336 enabled: true,
6337 auto_start: false,
6338 process_limits: ProcessLimits::default(),
6339 initialization_options: None,
6340 env: Default::default(),
6341 language_id_overrides: Default::default(),
6342 name: None,
6343 only_features: None,
6344 except_features: None,
6345 root_markers: Default::default(),
6346 }]),
6347 );
6348
6349 lsp.insert(
6352 "scala".to_string(),
6353 LspLanguageConfig::Multi(vec![LspServerConfig {
6354 command: "metals".to_string(),
6355 args: vec![],
6356 enabled: true,
6357 auto_start: false,
6358 process_limits: ProcessLimits::default(),
6359 initialization_options: None,
6360 env: Default::default(),
6361 language_id_overrides: Default::default(),
6362 name: None,
6363 only_features: None,
6364 except_features: None,
6365 root_markers: Default::default(),
6366 }]),
6367 );
6368
6369 lsp.insert(
6372 "elixir".to_string(),
6373 LspLanguageConfig::Multi(vec![LspServerConfig {
6374 command: "elixir-ls".to_string(),
6375 args: vec![],
6376 enabled: true,
6377 auto_start: false,
6378 process_limits: ProcessLimits::default(),
6379 initialization_options: None,
6380 env: Default::default(),
6381 language_id_overrides: Default::default(),
6382 name: None,
6383 only_features: None,
6384 except_features: None,
6385 root_markers: Default::default(),
6386 }]),
6387 );
6388
6389 lsp.insert(
6391 "erlang".to_string(),
6392 LspLanguageConfig::Multi(vec![LspServerConfig {
6393 command: "erlang_ls".to_string(),
6394 args: vec![],
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 "haskell".to_string(),
6412 LspLanguageConfig::Multi(vec![LspServerConfig {
6413 command: "haskell-language-server-wrapper".to_string(),
6414 args: vec!["--lsp".to_string()],
6415 enabled: true,
6416 auto_start: false,
6417 process_limits: ProcessLimits::default(),
6418 initialization_options: None,
6419 env: Default::default(),
6420 language_id_overrides: Default::default(),
6421 name: None,
6422 only_features: None,
6423 except_features: None,
6424 root_markers: Default::default(),
6425 }]),
6426 );
6427
6428 lsp.insert(
6431 "ocaml".to_string(),
6432 LspLanguageConfig::Multi(vec![LspServerConfig {
6433 command: "ocamllsp".to_string(),
6434 args: vec![],
6435 enabled: true,
6436 auto_start: false,
6437 process_limits: ProcessLimits::default(),
6438 initialization_options: None,
6439 env: Default::default(),
6440 language_id_overrides: Default::default(),
6441 name: None,
6442 only_features: None,
6443 except_features: None,
6444 root_markers: Default::default(),
6445 }]),
6446 );
6447
6448 lsp.insert(
6451 "clojure".to_string(),
6452 LspLanguageConfig::Multi(vec![LspServerConfig {
6453 command: "clojure-lsp".to_string(),
6454 args: vec![],
6455 enabled: true,
6456 auto_start: false,
6457 process_limits: ProcessLimits::default(),
6458 initialization_options: None,
6459 env: Default::default(),
6460 language_id_overrides: Default::default(),
6461 name: None,
6462 only_features: None,
6463 except_features: None,
6464 root_markers: Default::default(),
6465 }]),
6466 );
6467
6468 lsp.insert(
6471 "r".to_string(),
6472 LspLanguageConfig::Multi(vec![LspServerConfig {
6473 command: "R".to_string(),
6474 args: vec![
6475 "--vanilla".to_string(),
6476 "-e".to_string(),
6477 "languageserver::run()".to_string(),
6478 ],
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 "julia".to_string(),
6496 LspLanguageConfig::Multi(vec![LspServerConfig {
6497 command: "julia".to_string(),
6498 args: vec![
6499 "--startup-file=no".to_string(),
6500 "--history-file=no".to_string(),
6501 "-e".to_string(),
6502 "using LanguageServer; runserver()".to_string(),
6503 ],
6504 enabled: true,
6505 auto_start: false,
6506 process_limits: ProcessLimits::default(),
6507 initialization_options: None,
6508 env: Default::default(),
6509 language_id_overrides: Default::default(),
6510 name: None,
6511 only_features: None,
6512 except_features: None,
6513 root_markers: Default::default(),
6514 }]),
6515 );
6516
6517 lsp.insert(
6520 "perl".to_string(),
6521 LspLanguageConfig::Multi(vec![LspServerConfig {
6522 command: "perlnavigator".to_string(),
6523 args: vec!["--stdio".to_string()],
6524 enabled: true,
6525 auto_start: false,
6526 process_limits: ProcessLimits::default(),
6527 initialization_options: None,
6528 env: Default::default(),
6529 language_id_overrides: Default::default(),
6530 name: None,
6531 only_features: None,
6532 except_features: None,
6533 root_markers: Default::default(),
6534 }]),
6535 );
6536
6537 lsp.insert(
6540 "nim".to_string(),
6541 LspLanguageConfig::Multi(vec![LspServerConfig {
6542 command: "nimlangserver".to_string(),
6543 args: vec![],
6544 enabled: true,
6545 auto_start: false,
6546 process_limits: ProcessLimits::default(),
6547 initialization_options: None,
6548 env: Default::default(),
6549 language_id_overrides: Default::default(),
6550 name: None,
6551 only_features: None,
6552 except_features: None,
6553 root_markers: Default::default(),
6554 }]),
6555 );
6556
6557 lsp.insert(
6559 "gleam".to_string(),
6560 LspLanguageConfig::Multi(vec![LspServerConfig {
6561 command: "gleam".to_string(),
6562 args: vec!["lsp".to_string()],
6563 enabled: true,
6564 auto_start: false,
6565 process_limits: ProcessLimits::default(),
6566 initialization_options: None,
6567 env: Default::default(),
6568 language_id_overrides: Default::default(),
6569 name: None,
6570 only_features: None,
6571 except_features: None,
6572 root_markers: Default::default(),
6573 }]),
6574 );
6575
6576 lsp.insert(
6579 "racket".to_string(),
6580 LspLanguageConfig::Multi(vec![LspServerConfig {
6581 command: "racket-langserver".to_string(),
6582 args: vec![],
6583 enabled: true,
6584 auto_start: false,
6585 process_limits: ProcessLimits::default(),
6586 initialization_options: None,
6587 env: Default::default(),
6588 language_id_overrides: Default::default(),
6589 name: None,
6590 only_features: None,
6591 except_features: None,
6592 root_markers: vec!["info.rkt".to_string(), ".git".to_string()],
6593 }]),
6594 );
6595
6596 lsp.insert(
6599 "fsharp".to_string(),
6600 LspLanguageConfig::Multi(vec![LspServerConfig {
6601 command: "fsautocomplete".to_string(),
6602 args: vec!["--adaptive-lsp-server-enabled".to_string()],
6603 enabled: true,
6604 auto_start: false,
6605 process_limits: ProcessLimits::default(),
6606 initialization_options: None,
6607 env: Default::default(),
6608 language_id_overrides: Default::default(),
6609 name: None,
6610 only_features: None,
6611 except_features: None,
6612 root_markers: Default::default(),
6613 }]),
6614 );
6615
6616 let svls_config = LspServerConfig {
6620 command: "svls".to_string(),
6621 args: vec![],
6622 enabled: true,
6623 auto_start: false,
6624 process_limits: ProcessLimits::default(),
6625 initialization_options: None,
6626 env: Default::default(),
6627 language_id_overrides: Default::default(),
6628 name: None,
6629 only_features: None,
6630 except_features: None,
6631 root_markers: vec![".svls.toml".to_string(), ".git".to_string()],
6632 };
6633 lsp.insert(
6634 "verilog".to_string(),
6635 LspLanguageConfig::Multi(vec![svls_config.clone()]),
6636 );
6637 lsp.insert(
6638 "systemverilog".to_string(),
6639 LspLanguageConfig::Multi(vec![svls_config]),
6640 );
6641 }
6642 pub fn validate(&self) -> Result<(), ConfigError> {
6643 if self.editor.tab_size == 0 {
6645 return Err(ConfigError::ValidationError(
6646 "tab_size must be greater than 0".to_string(),
6647 ));
6648 }
6649
6650 if self.editor.scroll_offset > 100 {
6652 return Err(ConfigError::ValidationError(
6653 "scroll_offset must be <= 100".to_string(),
6654 ));
6655 }
6656
6657 for binding in &self.keybindings {
6659 if binding.key.is_empty() {
6660 return Err(ConfigError::ValidationError(
6661 "keybinding key cannot be empty".to_string(),
6662 ));
6663 }
6664 if binding.action.is_empty() {
6665 return Err(ConfigError::ValidationError(
6666 "keybinding action cannot be empty".to_string(),
6667 ));
6668 }
6669 }
6670
6671 Ok(())
6672 }
6673}
6674
6675#[derive(Debug)]
6677pub enum ConfigError {
6678 IoError(String),
6679 ParseError(String),
6680 SerializeError(String),
6681 ValidationError(String),
6682}
6683
6684impl std::fmt::Display for ConfigError {
6685 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6686 match self {
6687 Self::IoError(msg) => write!(f, "IO error: {msg}"),
6688 Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
6689 Self::SerializeError(msg) => write!(f, "Serialize error: {msg}"),
6690 Self::ValidationError(msg) => write!(f, "Validation error: {msg}"),
6691 }
6692 }
6693}
6694
6695impl std::error::Error for ConfigError {}
6696
6697#[cfg(test)]
6698mod tests {
6699 use super::*;
6700
6701 #[test]
6702 fn test_file_explorer_width_default_is_percent_30() {
6703 let cfg = FileExplorerConfig::default();
6704 assert_eq!(cfg.width, ExplorerWidth::Percent(30));
6705 }
6706
6707 #[test]
6708 fn test_tree_indicators_defaults_preserve_current_glyphs() {
6709 let cfg = FileExplorerConfig::default();
6713 assert_eq!(cfg.tree_indicator_collapsed, ">");
6714 assert_eq!(cfg.tree_indicator_expanded, "▼");
6715 }
6716
6717 #[test]
6718 fn test_tree_indicators_can_be_overridden_from_json() {
6719 let cfg: FileExplorerConfig = serde_json::from_str(
6720 r#"{"tree_indicator_collapsed": "▸", "tree_indicator_expanded": "▾"}"#,
6721 )
6722 .unwrap();
6723 assert_eq!(cfg.tree_indicator_collapsed, "▸");
6724 assert_eq!(cfg.tree_indicator_expanded, "▾");
6725 }
6726
6727 #[test]
6728 fn test_tree_indicators_partial_override_keeps_default_for_unset() {
6729 let cfg: FileExplorerConfig =
6730 serde_json::from_str(r#"{"tree_indicator_collapsed": "+"}"#).unwrap();
6731 assert_eq!(cfg.tree_indicator_collapsed, "+");
6732 assert_eq!(cfg.tree_indicator_expanded, "▼");
6734 }
6735
6736 #[test]
6739 fn test_width_accepts_legacy_float_fraction() {
6740 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": 0.3}"#).unwrap();
6741 assert_eq!(cfg.width, ExplorerWidth::Percent(30));
6742 }
6743
6744 #[test]
6745 fn test_width_accepts_bare_integer_as_percent() {
6746 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": 42}"#).unwrap();
6748 assert_eq!(cfg.width, ExplorerWidth::Percent(42));
6749 }
6750
6751 #[test]
6752 fn test_width_accepts_percent_string() {
6753 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "75%"}"#).unwrap();
6754 assert_eq!(cfg.width, ExplorerWidth::Percent(75));
6755 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "42 %"}"#).unwrap();
6757 assert_eq!(cfg.width, ExplorerWidth::Percent(42));
6758 }
6759
6760 #[test]
6761 fn test_width_accepts_columns_string() {
6762 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "24"}"#).unwrap();
6763 assert_eq!(cfg.width, ExplorerWidth::Columns(24));
6764 }
6765
6766 #[test]
6767 fn test_width_rejects_percent_over_100() {
6768 let err = serde_json::from_str::<FileExplorerConfig>(r#"{"width": "150%"}"#)
6769 .expect_err("percent > 100 should be rejected");
6770 assert!(err.to_string().contains("100"), "{err}");
6771 }
6772
6773 #[test]
6774 fn test_width_rejects_integer_over_100() {
6775 let err = serde_json::from_str::<FileExplorerConfig>(r#"{"width": 150}"#)
6778 .expect_err("bare integer > 100 should be rejected as percent");
6779 assert!(err.to_string().contains("percent") || err.to_string().contains("100"));
6780 }
6781
6782 #[test]
6783 fn test_width_rejects_garbage_string() {
6784 serde_json::from_str::<FileExplorerConfig>(r#"{"width": "big"}"#)
6785 .expect_err("non-numeric string should be rejected");
6786 }
6787
6788 #[test]
6791 fn test_width_serializes_percent_as_string_with_suffix() {
6792 let cfg = FileExplorerConfig {
6793 width: ExplorerWidth::Percent(30),
6794 ..Default::default()
6795 };
6796 let json = serde_json::to_value(&cfg).unwrap();
6797 assert_eq!(json["width"], serde_json::json!("30%"));
6798 }
6799
6800 #[test]
6801 fn test_width_serializes_columns_as_string_without_suffix() {
6802 let cfg = FileExplorerConfig {
6803 width: ExplorerWidth::Columns(24),
6804 ..Default::default()
6805 };
6806 let json = serde_json::to_value(&cfg).unwrap();
6807 assert_eq!(json["width"], serde_json::json!("24"));
6808 }
6809
6810 #[test]
6811 fn test_width_round_trip_both_variants() {
6812 for value in [ExplorerWidth::Percent(17), ExplorerWidth::Columns(42)] {
6813 let cfg = FileExplorerConfig {
6814 width: value,
6815 ..Default::default()
6816 };
6817 let json = serde_json::to_string(&cfg).unwrap();
6818 let restored: FileExplorerConfig = serde_json::from_str(&json).unwrap();
6819 assert_eq!(restored.width, value, "round trip failed for {:?}", value);
6820 }
6821 }
6822
6823 #[test]
6826 fn test_to_cols_percent() {
6827 assert_eq!(ExplorerWidth::Percent(30).to_cols(100), 30);
6828 assert_eq!(ExplorerWidth::Percent(25).to_cols(120), 30);
6829 assert_eq!(ExplorerWidth::Percent(30).to_cols(0), 0);
6831 assert_eq!(ExplorerWidth::Percent(100).to_cols(200), 200);
6832 }
6833
6834 #[test]
6835 fn test_to_cols_columns_clamps_to_terminal() {
6836 assert_eq!(ExplorerWidth::Columns(24).to_cols(100), 24);
6837 assert_eq!(ExplorerWidth::Columns(999).to_cols(80), 80);
6838 }
6839
6840 #[test]
6843 fn test_to_cols_enforces_min_width() {
6844 assert_eq!(
6846 ExplorerWidth::Columns(0).to_cols(100),
6847 ExplorerWidth::MIN_COLS
6848 );
6849 assert_eq!(
6850 ExplorerWidth::Columns(1).to_cols(100),
6851 ExplorerWidth::MIN_COLS
6852 );
6853 assert_eq!(
6854 ExplorerWidth::Columns(4).to_cols(100),
6855 ExplorerWidth::MIN_COLS
6856 );
6857 assert_eq!(
6858 ExplorerWidth::Percent(0).to_cols(100),
6859 ExplorerWidth::MIN_COLS
6860 );
6861 assert_eq!(
6863 ExplorerWidth::Percent(3).to_cols(100),
6864 ExplorerWidth::MIN_COLS
6865 );
6866 assert_eq!(
6868 ExplorerWidth::Columns(ExplorerWidth::MIN_COLS + 1).to_cols(100),
6869 ExplorerWidth::MIN_COLS + 1
6870 );
6871 }
6872
6873 #[test]
6876 fn test_to_cols_min_floor_yields_to_narrow_terminal() {
6877 assert_eq!(ExplorerWidth::Columns(10).to_cols(3), 3);
6878 assert_eq!(ExplorerWidth::Percent(100).to_cols(2), 2);
6879 assert_eq!(ExplorerWidth::Columns(1).to_cols(0), 0);
6880 }
6881
6882 #[test]
6885 fn test_load_from_file_accepts_legacy_float_fraction_width() {
6886 let dir = tempfile::tempdir().unwrap();
6887 let path = dir.path().join("config.json");
6888 std::fs::write(&path, r#"{"file_explorer":{"width":0.25}}"#).unwrap();
6889 let cfg = Config::load_from_file(&path).expect("legacy float fraction must still load");
6890 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Percent(25));
6891 }
6892
6893 #[test]
6894 fn test_load_from_file_accepts_columns_string_width() {
6895 let dir = tempfile::tempdir().unwrap();
6896 let path = dir.path().join("config.json");
6897 std::fs::write(&path, r#"{"file_explorer":{"width":"42"}}"#).unwrap();
6898 let cfg = Config::load_from_file(&path).unwrap();
6899 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Columns(42));
6900 }
6901
6902 #[test]
6903 fn test_load_from_file_accepts_percent_string_width() {
6904 let dir = tempfile::tempdir().unwrap();
6905 let path = dir.path().join("config.json");
6906 std::fs::write(&path, r#"{"file_explorer":{"width":"55%"}}"#).unwrap();
6907 let cfg = Config::load_from_file(&path).unwrap();
6908 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Percent(55));
6909 }
6910
6911 #[test]
6912 fn test_default_config() {
6913 let config = Config::default();
6914 assert_eq!(config.editor.tab_size, 4);
6915 assert!(config.editor.line_numbers);
6916 assert!(config.editor.syntax_highlighting);
6917 assert!(config.keybindings.is_empty());
6920 let resolved = config.resolve_keymap(&config.active_keybinding_map);
6922 assert!(!resolved.is_empty());
6923 }
6924
6925 #[test]
6926 fn test_all_builtin_keymaps_loadable() {
6927 for name in KeybindingMapName::BUILTIN_OPTIONS {
6928 let keymap = Config::load_builtin_keymap(name);
6929 assert!(keymap.is_some(), "Failed to load builtin keymap '{}'", name);
6930 }
6931 }
6932
6933 #[test]
6934 fn test_config_validation() {
6935 let mut config = Config::default();
6936 assert!(config.validate().is_ok());
6937
6938 config.editor.tab_size = 0;
6939 assert!(config.validate().is_err());
6940 }
6941
6942 #[test]
6943 fn test_macos_keymap_inherits_enter_bindings() {
6944 let config = Config::default();
6945 let bindings = config.resolve_keymap("macos");
6946
6947 let enter_bindings: Vec<_> = bindings.iter().filter(|b| b.key == "Enter").collect();
6948 assert!(
6949 !enter_bindings.is_empty(),
6950 "macos keymap should inherit Enter bindings from default, got {} Enter bindings",
6951 enter_bindings.len()
6952 );
6953 let has_insert_newline = enter_bindings.iter().any(|b| b.action == "insert_newline");
6955 assert!(
6956 has_insert_newline,
6957 "macos keymap should have insert_newline action for Enter key"
6958 );
6959 }
6960
6961 #[test]
6962 fn test_config_serialize_deserialize() {
6963 let config = Config::default();
6965
6966 let json = serde_json::to_string_pretty(&config).unwrap();
6968
6969 let loaded: Config = serde_json::from_str(&json).unwrap();
6971
6972 assert_eq!(config.editor.tab_size, loaded.editor.tab_size);
6973 assert_eq!(config.theme, loaded.theme);
6974 }
6975
6976 #[test]
6977 fn test_config_with_custom_keybinding() {
6978 let json = r#"{
6979 "editor": {
6980 "tab_size": 2
6981 },
6982 "keybindings": [
6983 {
6984 "key": "x",
6985 "modifiers": ["ctrl", "shift"],
6986 "action": "custom_action",
6987 "args": {},
6988 "when": null
6989 }
6990 ]
6991 }"#;
6992
6993 let config: Config = serde_json::from_str(json).unwrap();
6994 assert_eq!(config.editor.tab_size, 2);
6995 assert_eq!(config.keybindings.len(), 1);
6996 assert_eq!(config.keybindings[0].key, "x");
6997 assert_eq!(config.keybindings[0].modifiers.len(), 2);
6998 }
6999
7000 #[test]
7001 fn test_sparse_config_merges_with_defaults() {
7002 let temp_dir = tempfile::tempdir().unwrap();
7004 let config_path = temp_dir.path().join("config.json");
7005
7006 let sparse_config = r#"{
7008 "lsp": {
7009 "rust": {
7010 "command": "custom-rust-analyzer",
7011 "args": ["--custom-arg"]
7012 }
7013 }
7014 }"#;
7015 std::fs::write(&config_path, sparse_config).unwrap();
7016
7017 let loaded = Config::load_from_file(&config_path).unwrap();
7019
7020 assert!(loaded.lsp.contains_key("rust"));
7022 assert_eq!(
7023 loaded.lsp["rust"].as_slice()[0].command,
7024 "custom-rust-analyzer".to_string()
7025 );
7026
7027 assert!(
7029 loaded.lsp.contains_key("python"),
7030 "python LSP should be merged from defaults"
7031 );
7032 assert!(
7033 loaded.lsp.contains_key("typescript"),
7034 "typescript LSP should be merged from defaults"
7035 );
7036 assert!(
7037 loaded.lsp.contains_key("javascript"),
7038 "javascript LSP should be merged from defaults"
7039 );
7040
7041 assert!(loaded.languages.contains_key("rust"));
7043 assert!(loaded.languages.contains_key("python"));
7044 assert!(loaded.languages.contains_key("typescript"));
7045 }
7046
7047 #[test]
7048 fn test_empty_config_gets_all_defaults() {
7049 let temp_dir = tempfile::tempdir().unwrap();
7050 let config_path = temp_dir.path().join("config.json");
7051
7052 std::fs::write(&config_path, "{}").unwrap();
7054
7055 let loaded = Config::load_from_file(&config_path).unwrap();
7056 let defaults = Config::default();
7057
7058 assert_eq!(loaded.lsp.len(), defaults.lsp.len());
7060
7061 assert_eq!(loaded.languages.len(), defaults.languages.len());
7063 }
7064
7065 #[test]
7066 fn test_dynamic_submenu_expansion() {
7067 let temp_dir = tempfile::tempdir().unwrap();
7069 let themes_dir = temp_dir.path().to_path_buf();
7070
7071 let dynamic = MenuItem::DynamicSubmenu {
7072 label: "Test".to_string(),
7073 source: "copy_with_theme".to_string(),
7074 };
7075
7076 let expanded = dynamic.expand_dynamic(&themes_dir);
7077
7078 match expanded {
7080 MenuItem::Submenu { label, items } => {
7081 assert_eq!(label, "Test");
7082 let loader = crate::view::theme::ThemeLoader::embedded_only();
7084 let registry = loader.load_all(&[]);
7085 assert_eq!(items.len(), registry.len());
7086
7087 for (item, theme_info) in items.iter().zip(registry.list().iter()) {
7089 match item {
7090 MenuItem::Action {
7091 label,
7092 action,
7093 args,
7094 ..
7095 } => {
7096 assert_eq!(label, &theme_info.name);
7097 assert_eq!(action, "copy_with_theme");
7098 assert_eq!(
7099 args.get("theme").and_then(|v| v.as_str()),
7100 Some(theme_info.name.as_str())
7101 );
7102 }
7103 _ => panic!("Expected Action item"),
7104 }
7105 }
7106 }
7107 _ => panic!("Expected Submenu after expansion"),
7108 }
7109 }
7110
7111 #[test]
7112 fn test_non_dynamic_item_unchanged() {
7113 let temp_dir = tempfile::tempdir().unwrap();
7115 let themes_dir = temp_dir.path();
7116
7117 let action = MenuItem::Action {
7118 label: "Test".to_string(),
7119 action: "test".to_string(),
7120 args: HashMap::new(),
7121 when: None,
7122 checkbox: None,
7123 };
7124
7125 let expanded = action.expand_dynamic(themes_dir);
7126 match expanded {
7127 MenuItem::Action { label, action, .. } => {
7128 assert_eq!(label, "Test");
7129 assert_eq!(action, "test");
7130 }
7131 _ => panic!("Action should remain Action after expand_dynamic"),
7132 }
7133 }
7134
7135 #[test]
7136 fn test_buffer_config_uses_global_defaults() {
7137 let config = Config::default();
7138 let buffer_config = BufferConfig::resolve(&config, None);
7139
7140 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
7141 assert_eq!(buffer_config.auto_indent, config.editor.auto_indent);
7142 assert!(!buffer_config.use_tabs); assert!(buffer_config.whitespace.any_tabs()); assert!(buffer_config.formatter.is_none());
7145 assert!(!buffer_config.format_on_save);
7146 }
7147
7148 #[test]
7149 fn test_buffer_config_applies_language_overrides() {
7150 let mut config = Config::default();
7151
7152 config.languages.insert(
7154 "go".to_string(),
7155 LanguageConfig {
7156 extensions: vec!["go".to_string()],
7157 filenames: vec![],
7158 grammar: "go".to_string(),
7159 comment_prefix: Some("//".to_string()),
7160 auto_indent: true,
7161 auto_close: None,
7162 auto_surround: None,
7163 textmate_grammar: None,
7164 show_whitespace_tabs: false, line_wrap: None,
7166 wrap_column: None,
7167 page_view: None,
7168 page_width: None,
7169 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
7172 command: "gofmt".to_string(),
7173 args: vec![],
7174 stdin: true,
7175 timeout_ms: 10000,
7176 }),
7177 format_on_save: true,
7178 on_save: vec![],
7179 word_characters: None,
7180 },
7181 );
7182
7183 let buffer_config = BufferConfig::resolve(&config, Some("go"));
7184
7185 assert_eq!(buffer_config.tab_size, 8);
7186 assert!(buffer_config.use_tabs);
7187 assert!(!buffer_config.whitespace.any_tabs()); assert!(buffer_config.format_on_save);
7189 assert!(buffer_config.formatter.is_some());
7190 assert_eq!(buffer_config.formatter.as_ref().unwrap().command, "gofmt");
7191 }
7192
7193 #[test]
7194 fn test_buffer_config_unknown_language_uses_global() {
7195 let config = Config::default();
7196 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
7197
7198 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
7200 assert!(!buffer_config.use_tabs);
7201 }
7202
7203 #[test]
7204 fn test_buffer_config_per_language_line_wrap() {
7205 let mut config = Config::default();
7206 config.editor.line_wrap = false;
7207
7208 config.languages.insert(
7210 "markdown".to_string(),
7211 LanguageConfig {
7212 extensions: vec!["md".to_string()],
7213 line_wrap: Some(true),
7214 ..Default::default()
7215 },
7216 );
7217
7218 let md_config = BufferConfig::resolve(&config, Some("markdown"));
7220 assert!(md_config.line_wrap, "Markdown should have line_wrap=true");
7221
7222 let other_config = BufferConfig::resolve(&config, Some("rust"));
7224 assert!(
7225 !other_config.line_wrap,
7226 "Non-configured languages should use global line_wrap=false"
7227 );
7228
7229 let no_lang_config = BufferConfig::resolve(&config, None);
7231 assert!(
7232 !no_lang_config.line_wrap,
7233 "No language should use global line_wrap=false"
7234 );
7235 }
7236
7237 #[test]
7238 fn test_buffer_config_per_language_wrap_column() {
7239 let mut config = Config::default();
7240 config.editor.wrap_column = Some(120);
7241
7242 config.languages.insert(
7244 "markdown".to_string(),
7245 LanguageConfig {
7246 extensions: vec!["md".to_string()],
7247 wrap_column: Some(80),
7248 ..Default::default()
7249 },
7250 );
7251
7252 let md_config = BufferConfig::resolve(&config, Some("markdown"));
7254 assert_eq!(md_config.wrap_column, Some(80));
7255
7256 let other_config = BufferConfig::resolve(&config, Some("rust"));
7258 assert_eq!(other_config.wrap_column, Some(120));
7259
7260 let no_lang_config = BufferConfig::resolve(&config, None);
7262 assert_eq!(no_lang_config.wrap_column, Some(120));
7263 }
7264
7265 #[test]
7266 fn test_buffer_config_indent_string() {
7267 let config = Config::default();
7268
7269 let spaces_config = BufferConfig::resolve(&config, None);
7271 assert_eq!(spaces_config.indent_string(), " "); let mut config_with_tabs = Config::default();
7275 config_with_tabs.languages.insert(
7276 "makefile".to_string(),
7277 LanguageConfig {
7278 use_tabs: Some(true),
7279 tab_size: Some(8),
7280 ..Default::default()
7281 },
7282 );
7283 let tabs_config = BufferConfig::resolve(&config_with_tabs, Some("makefile"));
7284 assert_eq!(tabs_config.indent_string(), "\t");
7285 }
7286
7287 #[test]
7288 fn test_buffer_config_global_use_tabs_inherited() {
7289 let mut config = Config::default();
7292 config.editor.use_tabs = true;
7293
7294 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
7296 assert!(buffer_config.use_tabs);
7297
7298 let buffer_config = BufferConfig::resolve(&config, None);
7300 assert!(buffer_config.use_tabs);
7301
7302 config.languages.insert(
7304 "python".to_string(),
7305 LanguageConfig {
7306 use_tabs: Some(false),
7307 ..Default::default()
7308 },
7309 );
7310 let buffer_config = BufferConfig::resolve(&config, Some("python"));
7311 assert!(!buffer_config.use_tabs);
7312
7313 config.languages.insert(
7315 "rust".to_string(),
7316 LanguageConfig {
7317 use_tabs: None,
7318 ..Default::default()
7319 },
7320 );
7321 let buffer_config = BufferConfig::resolve(&config, Some("rust"));
7322 assert!(buffer_config.use_tabs);
7323 }
7324
7325 #[test]
7331 #[cfg(feature = "runtime")]
7332 fn test_lsp_languages_have_language_config() {
7333 let config = Config::default();
7334 let exceptions = ["tailwindcss"];
7335 for lsp_key in config.lsp.keys() {
7336 if exceptions.contains(&lsp_key.as_str()) {
7337 continue;
7338 }
7339 assert!(
7340 config.languages.contains_key(lsp_key),
7341 "LSP config key '{}' has no matching entry in default_languages(). \
7342 Add a LanguageConfig with the correct file extensions so detect_language() \
7343 can map files to this language.",
7344 lsp_key
7345 );
7346 }
7347 }
7348
7349 #[test]
7350 #[cfg(feature = "runtime")]
7351 fn test_default_config_has_quicklsp_in_universal_lsp() {
7352 let config = Config::default();
7353 assert!(
7354 config.universal_lsp.contains_key("quicklsp"),
7355 "Default config should contain quicklsp in universal_lsp"
7356 );
7357 let quicklsp = &config.universal_lsp["quicklsp"];
7358 let server = &quicklsp.as_slice()[0];
7359 assert_eq!(server.command, "quicklsp");
7360 assert!(!server.enabled, "quicklsp should be disabled by default");
7361 assert_eq!(server.name.as_deref(), Some("QuickLSP"));
7362 assert!(
7366 server.only_features.is_none(),
7367 "quicklsp must not default to a feature whitelist"
7368 );
7369 assert!(server.except_features.is_none());
7370 }
7371
7372 #[test]
7373 fn test_empty_config_preserves_universal_lsp_defaults() {
7374 let temp_dir = tempfile::tempdir().unwrap();
7375 let config_path = temp_dir.path().join("config.json");
7376
7377 std::fs::write(&config_path, "{}").unwrap();
7379
7380 let loaded = Config::load_from_file(&config_path).unwrap();
7381 let defaults = Config::default();
7382
7383 assert_eq!(
7385 loaded.universal_lsp.len(),
7386 defaults.universal_lsp.len(),
7387 "Empty config should preserve all default universal_lsp entries"
7388 );
7389 }
7390
7391 #[test]
7392 fn test_universal_lsp_config_merges_with_defaults() {
7393 let temp_dir = tempfile::tempdir().unwrap();
7394 let config_path = temp_dir.path().join("config.json");
7395
7396 let config_json = r#"{
7398 "universal_lsp": {
7399 "quicklsp": {
7400 "enabled": true
7401 }
7402 }
7403 }"#;
7404 std::fs::write(&config_path, config_json).unwrap();
7405
7406 let loaded = Config::load_from_file(&config_path).unwrap();
7407
7408 assert!(loaded.universal_lsp.contains_key("quicklsp"));
7410 let server = &loaded.universal_lsp["quicklsp"].as_slice()[0];
7411 assert!(server.enabled, "User override should enable quicklsp");
7412 assert_eq!(
7414 server.command, "quicklsp",
7415 "Default command should be merged when not specified by user"
7416 );
7417 }
7418
7419 #[test]
7420 fn test_universal_lsp_custom_server_added() {
7421 let temp_dir = tempfile::tempdir().unwrap();
7422 let config_path = temp_dir.path().join("config.json");
7423
7424 let config_json = r#"{
7426 "universal_lsp": {
7427 "my-custom-server": {
7428 "command": "my-server",
7429 "enabled": true,
7430 "auto_start": true
7431 }
7432 }
7433 }"#;
7434 std::fs::write(&config_path, config_json).unwrap();
7435
7436 let loaded = Config::load_from_file(&config_path).unwrap();
7437
7438 assert!(
7440 loaded.universal_lsp.contains_key("my-custom-server"),
7441 "Custom universal server should be loaded"
7442 );
7443 let server = &loaded.universal_lsp["my-custom-server"].as_slice()[0];
7444 assert_eq!(server.command, "my-server");
7445 assert!(server.enabled);
7446 assert!(server.auto_start);
7447
7448 assert!(
7450 loaded.universal_lsp.contains_key("quicklsp"),
7451 "Default quicklsp should be merged from defaults"
7452 );
7453 }
7454}