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