1use crate::types::{context_keys, LspFeature, 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 #[schemars(default = "default_keybinding_map_schema")]
377 pub active_keybinding_map: KeybindingMapName,
378
379 #[serde(default)]
381 pub languages: HashMap<String, LanguageConfig>,
382
383 #[serde(default)]
389 #[schemars(extend("x-enum-from" = "/languages"))]
390 pub default_language: Option<String>,
391
392 #[serde(default = "default_true")]
398 pub lsp_enabled: bool,
399
400 #[serde(default)]
404 pub lsp: HashMap<String, LspLanguageConfig>,
405
406 #[serde(default)]
410 pub universal_lsp: HashMap<String, LspLanguageConfig>,
411
412 #[serde(default)]
414 pub warnings: WarningsConfig,
415
416 #[serde(default)]
420 #[schemars(extend("x-standalone-category" = true, "x-no-add" = true))]
421 pub plugins: HashMap<String, PluginConfig>,
422
423 #[serde(default)]
425 pub packages: PackagesConfig,
426
427 #[serde(default)]
429 pub env: EnvConfig,
430}
431
432#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
441pub struct EnvConfig {
442 #[serde(default = "default_env_detectors")]
445 pub detectors: Vec<EnvDetector>,
446}
447
448impl Default for EnvConfig {
449 fn default() -> Self {
450 Self {
451 detectors: default_env_detectors(),
452 }
453 }
454}
455
456#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
459#[serde(rename_all = "kebab-case")]
460pub enum EnvKind {
461 PathOnly,
465 Shell,
468}
469
470#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
473pub struct EnvDetector {
474 pub name: String,
476 pub markers: Vec<String>,
479 pub kind: EnvKind,
481 pub snippet: String,
488 #[serde(default)]
493 pub require: Vec<String>,
494}
495
496pub fn default_env_detectors() -> Vec<EnvDetector> {
501 let venv_interp = |dir: &str| {
502 vec![
503 format!("{dir}/bin/python"),
504 format!("{dir}/bin/python3"),
505 format!("{dir}/Scripts/python.exe"),
506 ]
507 };
508 vec![
509 EnvDetector {
510 name: ".venv".into(),
511 markers: vec![".venv".into()],
512 kind: EnvKind::PathOnly,
513 snippet: "source .venv/bin/activate".into(),
518 require: venv_interp(".venv"),
519 },
520 EnvDetector {
521 name: "venv".into(),
522 markers: vec!["venv".into()],
523 kind: EnvKind::PathOnly,
524 snippet: "source venv/bin/activate".into(),
525 require: venv_interp("venv"),
526 },
527 EnvDetector {
528 name: "direnv".into(),
529 markers: vec![".envrc".into()],
530 kind: EnvKind::Shell,
531 snippet: "eval \"$(direnv export bash)\"".into(),
532 require: vec![],
533 },
534 EnvDetector {
535 name: "mise".into(),
536 markers: vec![
537 "mise.toml".into(),
538 ".mise.toml".into(),
539 ".tool-versions".into(),
540 ],
541 kind: EnvKind::Shell,
542 snippet: "eval \"$(mise env -s bash)\"".into(),
543 require: vec![],
544 },
545 EnvDetector {
546 name: "pipenv".into(),
547 markers: vec!["Pipfile".into()],
548 kind: EnvKind::Shell,
549 snippet: "source \"$(pipenv --venv)/bin/activate\"".into(),
550 require: vec![],
551 },
552 EnvDetector {
553 name: "poetry".into(),
554 markers: vec!["poetry.lock".into()],
555 kind: EnvKind::Shell,
556 snippet: "source \"$(poetry env info --path)/bin/activate\"".into(),
557 require: vec![],
558 },
559 ]
560}
561
562fn default_keybinding_map_name() -> KeybindingMapName {
563 if cfg!(target_os = "macos") {
566 KeybindingMapName("macos".to_string())
567 } else {
568 KeybindingMapName("default".to_string())
569 }
570}
571
572fn default_keybinding_map_schema() -> KeybindingMapName {
575 KeybindingMapName("default".to_string())
576}
577
578fn default_theme_name() -> ThemeName {
579 ThemeName("high-contrast".to_string())
580}
581
582#[derive(Debug, Clone, Copy)]
587pub struct WhitespaceVisibility {
588 pub spaces_leading: bool,
589 pub spaces_inner: bool,
590 pub spaces_trailing: bool,
591 pub tabs_leading: bool,
592 pub tabs_inner: bool,
593 pub tabs_trailing: bool,
594}
595
596impl Default for WhitespaceVisibility {
597 fn default() -> Self {
598 Self {
600 spaces_leading: false,
601 spaces_inner: false,
602 spaces_trailing: false,
603 tabs_leading: true,
604 tabs_inner: true,
605 tabs_trailing: true,
606 }
607 }
608}
609
610impl WhitespaceVisibility {
611 pub fn from_editor_config(editor: &EditorConfig) -> Self {
613 if !editor.whitespace_show {
614 return Self {
615 spaces_leading: false,
616 spaces_inner: false,
617 spaces_trailing: false,
618 tabs_leading: false,
619 tabs_inner: false,
620 tabs_trailing: false,
621 };
622 }
623 Self {
624 spaces_leading: editor.whitespace_spaces_leading,
625 spaces_inner: editor.whitespace_spaces_inner,
626 spaces_trailing: editor.whitespace_spaces_trailing,
627 tabs_leading: editor.whitespace_tabs_leading,
628 tabs_inner: editor.whitespace_tabs_inner,
629 tabs_trailing: editor.whitespace_tabs_trailing,
630 }
631 }
632
633 pub fn with_language_tab_override(mut self, show_whitespace_tabs: bool) -> Self {
636 if !show_whitespace_tabs {
637 self.tabs_leading = false;
638 self.tabs_inner = false;
639 self.tabs_trailing = false;
640 }
641 self
642 }
643
644 pub fn any_spaces(&self) -> bool {
646 self.spaces_leading || self.spaces_inner || self.spaces_trailing
647 }
648
649 pub fn any_tabs(&self) -> bool {
651 self.tabs_leading || self.tabs_inner || self.tabs_trailing
652 }
653
654 pub fn any_visible(&self) -> bool {
656 self.any_spaces() || self.any_tabs()
657 }
658
659 pub fn toggle_all(&mut self) {
663 if self.any_visible() {
664 *self = Self {
665 spaces_leading: false,
666 spaces_inner: false,
667 spaces_trailing: false,
668 tabs_leading: false,
669 tabs_inner: false,
670 tabs_trailing: false,
671 };
672 } else {
673 *self = Self::default();
674 }
675 }
676}
677
678#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
698#[serde(try_from = "String", into = "String")]
699pub enum StatusBarElement {
700 Filename,
702 Cursor,
704 CursorCompact,
706 Diagnostics,
708 CursorCount,
710 Messages,
712 Chord,
714 LineEnding,
716 Encoding,
718 Language,
720 Lsp,
722 Warnings,
724 Update,
726 Palette,
728 Clock,
730 RemoteIndicator,
735 WorkspaceTrust,
740 CustomToken(String),
742}
743
744impl TryFrom<String> for StatusBarElement {
745 type Error = String;
746 fn try_from(s: String) -> Result<Self, String> {
747 let inner = s
749 .strip_prefix('{')
750 .and_then(|s| s.strip_suffix('}'))
751 .unwrap_or(&s);
752 match inner {
753 "filename" => Ok(Self::Filename),
754 "cursor" => Ok(Self::Cursor),
755 "cursor:compact" => Ok(Self::CursorCompact),
756 "diagnostics" => Ok(Self::Diagnostics),
757 "cursor_count" => Ok(Self::CursorCount),
758 "messages" => Ok(Self::Messages),
759 "chord" => Ok(Self::Chord),
760 "line_ending" => Ok(Self::LineEnding),
761 "encoding" => Ok(Self::Encoding),
762 "language" => Ok(Self::Language),
763 "lsp" => Ok(Self::Lsp),
764 "warnings" => Ok(Self::Warnings),
765 "update" => Ok(Self::Update),
766 "palette" => Ok(Self::Palette),
767 "clock" => Ok(Self::Clock),
768 "remote" => Ok(Self::RemoteIndicator),
769 "trust" => Ok(Self::WorkspaceTrust),
770 _ => {
771 if inner.contains(':') {
773 Ok(Self::CustomToken(inner.to_string()))
774 } else {
775 Err(format!("Unknown status bar element: {}", s))
776 }
777 }
778 }
779 }
780}
781
782impl From<StatusBarElement> for String {
783 fn from(e: StatusBarElement) -> String {
784 match e {
785 StatusBarElement::Filename => "{filename}".to_string(),
786 StatusBarElement::Cursor => "{cursor}".to_string(),
787 StatusBarElement::CursorCompact => "{cursor:compact}".to_string(),
788 StatusBarElement::Diagnostics => "{diagnostics}".to_string(),
789 StatusBarElement::CursorCount => "{cursor_count}".to_string(),
790 StatusBarElement::Messages => "{messages}".to_string(),
791 StatusBarElement::Chord => "{chord}".to_string(),
792 StatusBarElement::LineEnding => "{line_ending}".to_string(),
793 StatusBarElement::Encoding => "{encoding}".to_string(),
794 StatusBarElement::Language => "{language}".to_string(),
795 StatusBarElement::Lsp => "{lsp}".to_string(),
796 StatusBarElement::Warnings => "{warnings}".to_string(),
797 StatusBarElement::Update => "{update}".to_string(),
798 StatusBarElement::Palette => "{palette}".to_string(),
799 StatusBarElement::Clock => "{clock}".to_string(),
800 StatusBarElement::RemoteIndicator => "{remote}".to_string(),
801 StatusBarElement::WorkspaceTrust => "{trust}".to_string(),
802 StatusBarElement::CustomToken(name) => format!("{{{}}}", name),
803 }
804 }
805}
806
807impl schemars::JsonSchema for StatusBarElement {
808 fn schema_name() -> std::borrow::Cow<'static, str> {
809 std::borrow::Cow::Borrowed("StatusBarElement")
810 }
811 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
812 schemars::json_schema!({
813 "type": "string",
814 "x-dual-list-options": [
815 {"value": "{filename}", "name": "Filename"},
816 {"value": "{cursor}", "name": "Cursor"},
817 {"value": "{cursor:compact}", "name": "Cursor (compact)"},
818 {"value": "{diagnostics}", "name": "Diagnostics"},
819 {"value": "{cursor_count}", "name": "Cursor Count"},
820 {"value": "{messages}", "name": "Messages"},
821 {"value": "{chord}", "name": "Chord"},
822 {"value": "{line_ending}", "name": "Line Ending"},
823 {"value": "{encoding}", "name": "Encoding"},
824 {"value": "{language}", "name": "Language"},
825 {"value": "{lsp}", "name": "LSP"},
826 {"value": "{warnings}", "name": "Warnings"},
827 {"value": "{update}", "name": "Update"},
828 {"value": "{palette}", "name": "Palette"},
829 {"value": "{clock}", "name": "Clock"},
830 {"value": "{remote}", "name": "Remote Indicator"},
831 {"value": "{trust}", "name": "Workspace Trust"}
832 ]
833 })
834 }
835}
836
837fn default_status_bar_left() -> Vec<StatusBarElement> {
838 vec![
848 StatusBarElement::WorkspaceTrust,
849 StatusBarElement::RemoteIndicator,
850 StatusBarElement::Cursor,
851 StatusBarElement::Diagnostics,
852 StatusBarElement::CursorCount,
853 StatusBarElement::Messages,
854 ]
855}
856
857fn default_status_bar_right() -> Vec<StatusBarElement> {
858 vec![
859 StatusBarElement::LineEnding,
860 StatusBarElement::Encoding,
861 StatusBarElement::Language,
862 StatusBarElement::Lsp,
863 StatusBarElement::Warnings,
864 StatusBarElement::Update,
865 StatusBarElement::Palette,
866 ]
867}
868
869#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
884pub struct StatusBarConfig {
885 #[serde(default = "default_status_bar_left")]
888 #[schemars(extend("x-section" = "Status Bar", "x-dual-list-sibling" = "/editor/status_bar/right", "x-dynamically-extendable-status-bar-elements" = true))]
889 pub left: Vec<StatusBarElement>,
890
891 #[serde(default = "default_status_bar_right")]
894 #[schemars(extend("x-section" = "Status Bar", "x-dual-list-sibling" = "/editor/status_bar/left", "x-dynamically-extendable-status-bar-elements" = true))]
895 pub right: Vec<StatusBarElement>,
896
897 #[serde(default = "default_status_bar_separator")]
903 #[schemars(extend("x-section" = "Status Bar"))]
904 pub separator: String,
905}
906
907fn default_status_bar_separator() -> String {
908 "".to_string()
909}
910
911impl Default for StatusBarConfig {
912 fn default() -> Self {
913 Self {
914 left: default_status_bar_left(),
915 right: default_status_bar_right(),
916 separator: default_status_bar_separator(),
917 }
918 }
919}
920
921#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
923pub struct EditorConfig {
924 #[serde(default = "default_true")]
931 #[schemars(extend("x-section" = "Display"))]
932 pub animations: bool,
933
934 #[serde(default = "default_true")]
938 #[schemars(extend("x-section" = "Display"))]
939 pub cursor_jump_animation: bool,
940
941 #[serde(default = "default_true")]
943 #[schemars(extend("x-section" = "Display"))]
944 pub line_numbers: bool,
945
946 #[serde(default = "default_false")]
948 #[schemars(extend("x-section" = "Display"))]
949 pub relative_line_numbers: bool,
950
951 #[serde(default = "default_true")]
953 #[schemars(extend("x-section" = "Display"))]
954 pub highlight_current_line: bool,
955
956 #[serde(default = "default_true")]
958 #[schemars(extend("x-section" = "Display"))]
959 pub highlight_occurrences: bool,
960
961 #[serde(default = "default_false")]
966 #[schemars(extend("x-section" = "Display"))]
967 pub hide_current_line_on_selection: bool,
968
969 #[serde(default = "default_false")]
971 #[schemars(extend("x-section" = "Display"))]
972 pub highlight_current_column: bool,
973
974 #[serde(default = "default_true")]
976 #[schemars(extend("x-section" = "Display"))]
977 pub line_wrap: bool,
978
979 #[serde(default = "default_true")]
981 #[schemars(extend("x-section" = "Display"))]
982 pub wrap_indent: bool,
983
984 #[serde(default)]
990 #[schemars(extend("x-section" = "Display"))]
991 pub wrap_column: Option<usize>,
992
993 #[serde(default = "default_page_width")]
997 #[schemars(extend("x-section" = "Display"))]
998 pub page_width: Option<usize>,
999
1000 #[serde(default = "default_true")]
1002 #[schemars(extend("x-section" = "Display"))]
1003 pub syntax_highlighting: bool,
1004
1005 #[serde(default = "default_true")]
1010 #[schemars(extend("x-section" = "Display"))]
1011 pub show_menu_bar: bool,
1012
1013 #[serde(default = "default_false")]
1019 #[schemars(extend("x-section" = "Display"))]
1020 pub screensaver_enabled: bool,
1021
1022 #[serde(default = "default_screensaver_idle_minutes")]
1026 #[schemars(extend("x-section" = "Display"))]
1027 pub screensaver_idle_minutes: u32,
1028
1029 #[serde(default = "default_true")]
1034 #[schemars(extend("x-section" = "Display"))]
1035 pub menu_bar_mnemonics: bool,
1036
1037 #[serde(default = "default_true")]
1042 #[schemars(extend("x-section" = "Display"))]
1043 pub show_tab_bar: bool,
1044
1045 #[serde(default = "default_true")]
1050 #[schemars(extend("x-section" = "Display"))]
1051 pub show_status_bar: bool,
1052
1053 #[serde(default)]
1056 #[schemars(extend("x-section" = "Status Bar"))]
1057 pub status_bar: StatusBarConfig,
1058
1059 #[serde(default = "default_false")]
1066 #[schemars(extend("x-section" = "Display"))]
1067 pub show_prompt_line: bool,
1068
1069 #[serde(default = "default_true")]
1073 #[schemars(extend("x-section" = "Display"))]
1074 pub show_vertical_scrollbar: bool,
1075
1076 #[serde(default = "default_false")]
1081 #[schemars(extend("x-section" = "Display"))]
1082 pub show_horizontal_scrollbar: bool,
1083
1084 #[serde(default = "default_true")]
1088 #[schemars(extend("x-section" = "Display"))]
1089 pub show_tilde: bool,
1090
1091 #[serde(default = "default_false")]
1096 #[schemars(extend("x-section" = "Display"))]
1097 pub use_terminal_bg: bool,
1098
1099 #[serde(default = "default_true")]
1105 #[schemars(extend("x-section" = "Display"))]
1106 pub set_window_title: bool,
1107
1108 #[serde(default = "default_true")]
1115 #[schemars(extend("x-section" = "Display"))]
1116 pub terminal_auto_title: bool,
1117
1118 #[serde(default)]
1122 #[schemars(extend("x-section" = "Display"))]
1123 pub cursor_style: CursorStyle,
1124
1125 #[serde(default)]
1130 #[schemars(extend("x-section" = "Display"))]
1131 pub rulers: Vec<usize>,
1132
1133 #[serde(default = "default_true")]
1139 #[schemars(extend("x-section" = "Whitespace"))]
1140 pub whitespace_show: bool,
1141
1142 #[serde(default = "default_false")]
1146 #[schemars(extend("x-section" = "Whitespace"))]
1147 pub whitespace_spaces_leading: bool,
1148
1149 #[serde(default = "default_false")]
1153 #[schemars(extend("x-section" = "Whitespace"))]
1154 pub whitespace_spaces_inner: bool,
1155
1156 #[serde(default = "default_false")]
1160 #[schemars(extend("x-section" = "Whitespace"))]
1161 pub whitespace_spaces_trailing: bool,
1162
1163 #[serde(default = "default_true")]
1167 #[schemars(extend("x-section" = "Whitespace"))]
1168 pub whitespace_tabs_leading: bool,
1169
1170 #[serde(default = "default_true")]
1174 #[schemars(extend("x-section" = "Whitespace"))]
1175 pub whitespace_tabs_inner: bool,
1176
1177 #[serde(default = "default_true")]
1181 #[schemars(extend("x-section" = "Whitespace"))]
1182 pub whitespace_tabs_trailing: bool,
1183
1184 #[serde(default = "default_false")]
1190 #[schemars(extend("x-section" = "Editing"))]
1191 pub use_tabs: bool,
1192
1193 #[serde(default = "default_tab_size")]
1196 #[schemars(extend("x-section" = "Editing"))]
1197 pub tab_size: usize,
1198
1199 #[serde(default = "default_true")]
1201 #[schemars(extend("x-section" = "Editing"))]
1202 pub auto_indent: bool,
1203
1204 #[serde(default = "default_true")]
1211 #[schemars(extend("x-section" = "Editing"))]
1212 pub auto_close: bool,
1213
1214 #[serde(default = "default_true")]
1219 #[schemars(extend("x-section" = "Editing"))]
1220 pub auto_surround: bool,
1221
1222 #[serde(default = "default_scroll_offset")]
1224 #[schemars(extend("x-section" = "Editing"))]
1225 pub scroll_offset: usize,
1226
1227 #[serde(default)]
1232 #[schemars(extend("x-section" = "Editing"))]
1233 pub default_line_ending: LineEndingOption,
1234
1235 #[serde(default = "default_false")]
1238 #[schemars(extend("x-section" = "Editing"))]
1239 pub trim_trailing_whitespace_on_save: bool,
1240
1241 #[serde(default = "default_false")]
1244 #[schemars(extend("x-section" = "Editing"))]
1245 pub ensure_final_newline_on_save: bool,
1246
1247 #[serde(default = "default_true")]
1255 #[schemars(extend("x-section" = "Editing"))]
1256 pub auto_read_only: bool,
1257
1258 #[serde(default = "default_true")]
1262 #[schemars(extend("x-section" = "Bracket Matching"))]
1263 pub highlight_matching_brackets: bool,
1264
1265 #[serde(default = "default_true")]
1269 #[schemars(extend("x-section" = "Bracket Matching"))]
1270 pub rainbow_brackets: bool,
1271
1272 #[serde(default = "default_false")]
1279 #[schemars(extend("x-section" = "Completion"))]
1280 pub completion_popup_auto_show: bool,
1281
1282 #[serde(default = "default_true")]
1288 #[schemars(extend("x-section" = "Completion"))]
1289 pub quick_suggestions: bool,
1290
1291 #[serde(default = "default_quick_suggestions_delay")]
1297 #[schemars(extend("x-section" = "Completion"))]
1298 pub quick_suggestions_delay_ms: u64,
1299
1300 #[serde(default = "default_true")]
1304 #[schemars(extend("x-section" = "Completion"))]
1305 pub suggest_on_trigger_characters: bool,
1306
1307 #[serde(default = "default_true")]
1310 #[schemars(extend("x-section" = "LSP"))]
1311 pub enable_inlay_hints: bool,
1312
1313 #[serde(default = "default_false")]
1317 #[schemars(extend("x-section" = "LSP"))]
1318 pub enable_semantic_tokens_full: bool,
1319
1320 #[serde(default = "default_false")]
1325 #[schemars(extend("x-section" = "Diagnostics"))]
1326 pub diagnostics_inline_text: bool,
1327
1328 #[serde(default = "default_mouse_hover_enabled")]
1339 #[schemars(extend("x-section" = "Mouse"))]
1340 pub mouse_hover_enabled: bool,
1341
1342 #[serde(default = "default_mouse_hover_delay")]
1346 #[schemars(extend("x-section" = "Mouse"))]
1347 pub mouse_hover_delay_ms: u64,
1348
1349 #[serde(default = "default_double_click_time")]
1353 #[schemars(extend("x-section" = "Mouse"))]
1354 pub double_click_time_ms: u64,
1355
1356 #[serde(default = "default_false")]
1361 #[schemars(extend("x-section" = "Recovery"))]
1362 pub auto_save_enabled: bool,
1363
1364 #[serde(default = "default_auto_save_interval")]
1369 #[schemars(extend("x-section" = "Recovery"))]
1370 pub auto_save_interval_secs: u32,
1371
1372 #[serde(default = "default_true", alias = "persist_unnamed_buffers")]
1379 #[schemars(extend("x-section" = "Recovery"))]
1380 pub hot_exit: bool,
1381
1382 #[serde(default)]
1389 #[schemars(extend("x-section" = "Startup"))]
1390 pub confirm_quit: bool,
1391
1392 #[serde(default = "default_true")]
1403 #[schemars(extend("x-section" = "Startup"))]
1404 pub restore_previous_session: bool,
1405
1406 #[serde(default = "default_true")]
1417 #[schemars(extend("x-section" = "Startup"))]
1418 pub skip_session_restore_when_files_passed: bool,
1419
1420 #[serde(default = "default_true")]
1428 #[schemars(extend("x-section" = "Startup"))]
1429 pub auto_create_empty_buffer_on_last_buffer_close: bool,
1430
1431 #[serde(default = "default_true")]
1436 #[schemars(extend("x-section" = "Recovery"))]
1437 pub recovery_enabled: bool,
1438
1439 #[serde(default = "default_auto_recovery_save_interval")]
1444 #[schemars(extend("x-section" = "Recovery"))]
1445 pub auto_recovery_save_interval_secs: u32,
1446
1447 #[serde(default = "default_auto_revert_poll_interval")]
1452 #[schemars(extend("x-section" = "Recovery"))]
1453 pub auto_revert_poll_interval_ms: u64,
1454
1455 #[serde(default = "default_true")]
1461 #[schemars(extend("x-section" = "Keyboard"))]
1462 pub keyboard_disambiguate_escape_codes: bool,
1463
1464 #[serde(default = "default_false")]
1469 #[schemars(extend("x-section" = "Keyboard"))]
1470 pub keyboard_report_event_types: bool,
1471
1472 #[serde(default = "default_true")]
1477 #[schemars(extend("x-section" = "Keyboard"))]
1478 pub keyboard_report_alternate_keys: bool,
1479
1480 #[serde(default = "default_false")]
1486 #[schemars(extend("x-section" = "Keyboard"))]
1487 pub keyboard_report_all_keys_as_escape_codes: bool,
1488
1489 #[serde(default = "default_highlight_timeout")]
1492 #[schemars(extend("x-section" = "Performance"))]
1493 pub highlight_timeout_ms: u64,
1494
1495 #[serde(default = "default_snapshot_interval")]
1497 #[schemars(extend("x-section" = "Performance"))]
1498 pub snapshot_interval: usize,
1499
1500 #[serde(default = "default_highlight_context_bytes")]
1505 #[schemars(extend("x-section" = "Performance"))]
1506 pub highlight_context_bytes: usize,
1507
1508 #[serde(default = "default_large_file_threshold")]
1515 #[schemars(extend("x-section" = "Performance"))]
1516 pub large_file_threshold_bytes: u64,
1517
1518 #[serde(default = "default_estimated_line_length")]
1522 #[schemars(extend("x-section" = "Performance"))]
1523 pub estimated_line_length: usize,
1524
1525 #[serde(default = "default_read_concurrency")]
1530 #[schemars(extend("x-section" = "Performance"))]
1531 pub read_concurrency: usize,
1532
1533 #[serde(default = "default_file_tree_poll_interval")]
1538 #[schemars(extend("x-section" = "Performance"))]
1539 pub file_tree_poll_interval_ms: u64,
1540}
1541
1542fn default_tab_size() -> usize {
1543 4
1544}
1545
1546pub const LARGE_FILE_THRESHOLD_BYTES: u64 = 1024 * 1024; fn default_large_file_threshold() -> u64 {
1552 LARGE_FILE_THRESHOLD_BYTES
1553}
1554
1555pub const INDENT_FOLD_MAX_SCAN_LINES: usize = 10_000;
1558
1559pub const INDENT_FOLD_INDICATOR_MAX_SCAN: usize = 50;
1562
1563pub const INDENT_FOLD_MAX_UPWARD_SCAN: usize = 200;
1566
1567fn default_read_concurrency() -> usize {
1568 64
1569}
1570
1571fn default_true() -> bool {
1572 true
1573}
1574
1575fn default_screensaver_idle_minutes() -> u32 {
1576 5
1577}
1578
1579fn default_false() -> bool {
1580 false
1581}
1582
1583fn default_quick_suggestions_delay() -> u64 {
1584 150 }
1586
1587fn default_scroll_offset() -> usize {
1588 3
1589}
1590
1591fn default_highlight_timeout() -> u64 {
1592 5
1593}
1594
1595fn default_snapshot_interval() -> usize {
1596 100
1597}
1598
1599fn default_estimated_line_length() -> usize {
1600 80
1601}
1602
1603fn default_auto_save_interval() -> u32 {
1604 30 }
1606
1607fn default_auto_recovery_save_interval() -> u32 {
1608 2 }
1610
1611fn default_highlight_context_bytes() -> usize {
1612 10_000 }
1614
1615fn default_mouse_hover_enabled() -> bool {
1616 !cfg!(windows)
1617}
1618
1619fn default_mouse_hover_delay() -> u64 {
1620 500 }
1622
1623fn default_double_click_time() -> u64 {
1624 500 }
1626
1627fn default_auto_revert_poll_interval() -> u64 {
1628 2000 }
1630
1631fn default_file_tree_poll_interval() -> u64 {
1632 3000 }
1634
1635impl Default for EditorConfig {
1636 fn default() -> Self {
1637 Self {
1638 use_tabs: false,
1639 tab_size: default_tab_size(),
1640 auto_indent: true,
1641 auto_close: true,
1642 auto_surround: true,
1643 animations: true,
1644 cursor_jump_animation: true,
1645 line_numbers: true,
1646 relative_line_numbers: false,
1647 scroll_offset: default_scroll_offset(),
1648 syntax_highlighting: true,
1649 highlight_current_line: true,
1650 highlight_occurrences: true,
1651 hide_current_line_on_selection: false,
1652 highlight_current_column: false,
1653 line_wrap: true,
1654 wrap_indent: true,
1655 wrap_column: None,
1656 page_width: default_page_width(),
1657 highlight_timeout_ms: default_highlight_timeout(),
1658 snapshot_interval: default_snapshot_interval(),
1659 large_file_threshold_bytes: default_large_file_threshold(),
1660 estimated_line_length: default_estimated_line_length(),
1661 enable_inlay_hints: true,
1662 enable_semantic_tokens_full: false,
1663 diagnostics_inline_text: false,
1664 auto_save_enabled: false,
1665 auto_save_interval_secs: default_auto_save_interval(),
1666 hot_exit: true,
1667 confirm_quit: false,
1668 restore_previous_session: true,
1669 skip_session_restore_when_files_passed: true,
1670 auto_create_empty_buffer_on_last_buffer_close: true,
1671 recovery_enabled: true,
1672 auto_recovery_save_interval_secs: default_auto_recovery_save_interval(),
1673 highlight_context_bytes: default_highlight_context_bytes(),
1674 mouse_hover_enabled: default_mouse_hover_enabled(),
1675 mouse_hover_delay_ms: default_mouse_hover_delay(),
1676 double_click_time_ms: default_double_click_time(),
1677 auto_revert_poll_interval_ms: default_auto_revert_poll_interval(),
1678 read_concurrency: default_read_concurrency(),
1679 file_tree_poll_interval_ms: default_file_tree_poll_interval(),
1680 default_line_ending: LineEndingOption::default(),
1681 trim_trailing_whitespace_on_save: false,
1682 ensure_final_newline_on_save: false,
1683 auto_read_only: true,
1684 highlight_matching_brackets: true,
1685 rainbow_brackets: true,
1686 cursor_style: CursorStyle::default(),
1687 keyboard_disambiguate_escape_codes: true,
1688 keyboard_report_event_types: false,
1689 keyboard_report_alternate_keys: true,
1690 keyboard_report_all_keys_as_escape_codes: false,
1691 completion_popup_auto_show: false,
1692 quick_suggestions: true,
1693 quick_suggestions_delay_ms: default_quick_suggestions_delay(),
1694 suggest_on_trigger_characters: true,
1695 show_menu_bar: true,
1696 screensaver_enabled: false,
1697 screensaver_idle_minutes: default_screensaver_idle_minutes(),
1698 menu_bar_mnemonics: true,
1699 show_tab_bar: true,
1700 show_status_bar: true,
1701 status_bar: StatusBarConfig::default(),
1702 show_prompt_line: false,
1703 show_vertical_scrollbar: true,
1704 show_horizontal_scrollbar: false,
1705 show_tilde: true,
1706 use_terminal_bg: false,
1707 set_window_title: true,
1708 terminal_auto_title: true,
1709 rulers: Vec::new(),
1710 whitespace_show: true,
1711 whitespace_spaces_leading: false,
1712 whitespace_spaces_inner: false,
1713 whitespace_spaces_trailing: false,
1714 whitespace_tabs_leading: true,
1715 whitespace_tabs_inner: true,
1716 whitespace_tabs_trailing: true,
1717 }
1718 }
1719}
1720
1721#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
1723#[serde(rename_all = "snake_case")]
1724pub enum FileExplorerSide {
1725 #[default]
1726 Left,
1727 Right,
1728}
1729
1730#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1732pub struct FileExplorerConfig {
1733 #[serde(default = "default_true")]
1735 pub respect_gitignore: bool,
1736
1737 #[serde(default = "default_false")]
1739 pub show_hidden: bool,
1740
1741 #[serde(default = "default_false")]
1743 pub show_gitignored: bool,
1744
1745 #[serde(default)]
1747 pub custom_ignore_patterns: Vec<String>,
1748
1749 #[serde(default = "default_explorer_width")]
1755 pub width: ExplorerWidth,
1756
1757 #[serde(default = "default_true")]
1764 pub preview_tabs: bool,
1765
1766 #[serde(default = "default_explorer_side")]
1769 pub side: FileExplorerSide,
1770
1771 #[serde(default = "default_true")]
1777 pub auto_open_on_last_buffer_close: bool,
1778
1779 #[serde(default = "default_false")]
1785 pub follow_active_buffer: bool,
1786
1787 #[serde(default = "default_true")]
1794 pub compact_directories: bool,
1795
1796 #[serde(default = "default_tree_indicator_collapsed")]
1802 pub tree_indicator_collapsed: String,
1803
1804 #[serde(default = "default_tree_indicator_expanded")]
1810 pub tree_indicator_expanded: String,
1811}
1812
1813#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1841pub enum ExplorerWidth {
1842 Percent(u8),
1843 Columns(u16),
1844}
1845
1846impl ExplorerWidth {
1847 pub const DEFAULT: Self = Self::Percent(30);
1849
1850 pub const MIN_COLS: u16 = 5;
1856
1857 pub fn to_cols(self, terminal_width: u16) -> u16 {
1865 let raw = match self {
1866 Self::Percent(pct) => ((terminal_width as u32 * pct as u32) / 100) as u16,
1867 Self::Columns(cols) => cols,
1868 };
1869 raw.max(Self::MIN_COLS).min(terminal_width)
1870 }
1871}
1872
1873impl Default for ExplorerWidth {
1874 fn default() -> Self {
1875 Self::DEFAULT
1876 }
1877}
1878
1879impl std::fmt::Display for ExplorerWidth {
1880 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1881 match self {
1882 Self::Percent(n) => write!(f, "{}%", n),
1883 Self::Columns(n) => write!(f, "{}", n),
1884 }
1885 }
1886}
1887
1888#[derive(Debug)]
1890pub struct ExplorerWidthParseError(String);
1891
1892impl std::fmt::Display for ExplorerWidthParseError {
1893 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1894 write!(f, "{}", self.0)
1895 }
1896}
1897
1898impl std::error::Error for ExplorerWidthParseError {}
1899
1900impl std::str::FromStr for ExplorerWidth {
1901 type Err = ExplorerWidthParseError;
1902
1903 fn from_str(s: &str) -> Result<Self, Self::Err> {
1904 let s = s.trim();
1905 if s.is_empty() {
1906 return Err(ExplorerWidthParseError(
1907 "explorer width: empty string".into(),
1908 ));
1909 }
1910 if let Some(rest) = s.strip_suffix('%') {
1911 let n: u16 = rest.trim().parse().map_err(|_| {
1912 ExplorerWidthParseError(format!("explorer width: {:?} is not a valid percent", s))
1913 })?;
1914 if n > 100 {
1915 return Err(ExplorerWidthParseError(format!(
1916 "explorer width: {}% exceeds 100%",
1917 n
1918 )));
1919 }
1920 Ok(Self::Percent(n as u8))
1921 } else {
1922 let n: u16 = s.parse().map_err(|_| {
1923 ExplorerWidthParseError(format!(
1924 "explorer width: {:?} is neither a percent (e.g. \"30%\") nor a column count (e.g. \"24\")",
1925 s
1926 ))
1927 })?;
1928 Ok(Self::Columns(n))
1929 }
1930 }
1931}
1932
1933impl serde::Serialize for ExplorerWidth {
1934 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1935 s.collect_str(self)
1936 }
1937}
1938
1939impl<'de> serde::Deserialize<'de> for ExplorerWidth {
1940 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
1941 let raw = serde_json::Value::deserialize(d)?;
1942 explorer_width::from_value(&raw)
1943 }
1944}
1945
1946impl schemars::JsonSchema for ExplorerWidth {
1947 fn schema_name() -> std::borrow::Cow<'static, str> {
1948 std::borrow::Cow::Borrowed("ExplorerWidth")
1949 }
1950
1951 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
1952 schemars::json_schema!({
1956 "type": "string",
1957 "pattern": r"^(100%|[1-9]?[0-9]%|\d+)$",
1958 "description": "Either a percent like \"30%\" (0–100) or an absolute column count like \"24\".",
1959 })
1960 }
1961}
1962
1963fn default_explorer_width() -> ExplorerWidth {
1964 ExplorerWidth::DEFAULT
1965}
1966
1967fn default_explorer_side() -> FileExplorerSide {
1968 FileExplorerSide::default()
1969}
1970
1971fn default_tree_indicator_collapsed() -> String {
1972 ">".to_string()
1973}
1974
1975fn default_tree_indicator_expanded() -> String {
1976 "▼".to_string()
1977}
1978
1979pub fn default_explorer_width_value() -> ExplorerWidth {
1981 ExplorerWidth::DEFAULT
1982}
1983
1984pub(crate) mod explorer_width {
1988 use super::ExplorerWidth;
1989 use serde::de::{self, Deserialize, Deserializer};
1990 use std::str::FromStr;
1991
1992 pub fn deserialize_optional<'de, D>(d: D) -> Result<Option<ExplorerWidth>, D::Error>
1997 where
1998 D: Deserializer<'de>,
1999 {
2000 let raw = Option::<serde_json::Value>::deserialize(d)?;
2001 match raw {
2002 None | Some(serde_json::Value::Null) => Ok(None),
2003 Some(v) => from_value(&v).map(Some),
2004 }
2005 }
2006
2007 pub(super) fn from_value<E: de::Error>(v: &serde_json::Value) -> Result<ExplorerWidth, E> {
2008 match v {
2009 serde_json::Value::String(s) => ExplorerWidth::from_str(s).map_err(E::custom),
2010 serde_json::Value::Number(n) => {
2011 if let Some(u) = n.as_u64() {
2012 if u > 100 {
2016 return Err(E::custom(format!(
2017 "explorer width: {} exceeds 100 (percent). Use \"{}\" for columns.",
2018 u, u
2019 )));
2020 }
2021 Ok(ExplorerWidth::Percent(u as u8))
2022 } else if let Some(f) = n.as_f64() {
2023 let pct = if (0.0..=1.0).contains(&f) {
2025 f * 100.0
2026 } else {
2027 f
2028 };
2029 if !(0.0..=100.0).contains(&pct) {
2030 return Err(E::custom(format!(
2031 "explorer width: percent {} out of range 0..=100",
2032 pct
2033 )));
2034 }
2035 Ok(ExplorerWidth::Percent(pct.round() as u8))
2036 } else {
2037 Err(E::custom("explorer width: unsupported number"))
2038 }
2039 }
2040 _ => Err(E::custom(
2041 "explorer width: expected \"30%\", \"24\" (columns), or a number",
2042 )),
2043 }
2044 }
2045}
2046
2047#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2058pub struct ClipboardConfig {
2059 #[serde(default = "default_true")]
2062 pub use_osc52: bool,
2063
2064 #[serde(default = "default_true")]
2067 pub use_system_clipboard: bool,
2068}
2069
2070impl Default for ClipboardConfig {
2071 fn default() -> Self {
2072 Self {
2073 use_osc52: true,
2074 use_system_clipboard: true,
2075 }
2076 }
2077}
2078
2079#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2081pub struct TerminalConfig {
2082 #[serde(default = "default_true")]
2085 pub jump_to_end_on_output: bool,
2086
2087 #[serde(default)]
2099 pub shell: Option<TerminalShellConfig>,
2100
2101 #[serde(default = "default_true")]
2113 pub skip_app_execution_alias: bool,
2114
2115 #[serde(default = "default_true")]
2124 pub resume_agents: bool,
2125}
2126
2127impl Default for TerminalConfig {
2128 fn default() -> Self {
2129 Self {
2130 jump_to_end_on_output: true,
2131 shell: None,
2132 skip_app_execution_alias: true,
2133 resume_agents: true,
2134 }
2135 }
2136}
2137
2138#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2140pub struct TerminalShellConfig {
2141 pub command: String,
2144
2145 #[serde(default)]
2147 pub args: Vec<String>,
2148}
2149
2150#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2152pub struct WarningsConfig {
2153 #[serde(default = "default_true")]
2156 pub show_status_indicator: bool,
2157}
2158
2159impl Default for WarningsConfig {
2160 fn default() -> Self {
2161 Self {
2162 show_status_indicator: true,
2163 }
2164 }
2165}
2166
2167#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2169pub struct PackagesConfig {
2170 #[serde(default = "default_package_sources")]
2173 pub sources: Vec<String>,
2174}
2175
2176fn default_package_sources() -> Vec<String> {
2177 vec!["https://github.com/sinelaw/fresh-plugins-registry".to_string()]
2178}
2179
2180impl Default for PackagesConfig {
2181 fn default() -> Self {
2182 Self {
2183 sources: default_package_sources(),
2184 }
2185 }
2186}
2187
2188pub use fresh_core::config::PluginConfig;
2190
2191impl Default for FileExplorerConfig {
2192 fn default() -> Self {
2193 Self {
2194 respect_gitignore: true,
2195 show_hidden: false,
2196 show_gitignored: false,
2197 custom_ignore_patterns: Vec::new(),
2198 width: default_explorer_width(),
2199 preview_tabs: true,
2200 side: default_explorer_side(),
2201 auto_open_on_last_buffer_close: true,
2202 follow_active_buffer: false,
2203 compact_directories: true,
2204 tree_indicator_collapsed: default_tree_indicator_collapsed(),
2205 tree_indicator_expanded: default_tree_indicator_expanded(),
2206 }
2207 }
2208}
2209
2210#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
2212pub struct FileBrowserConfig {
2213 #[serde(default = "default_false")]
2215 pub show_hidden: bool,
2216}
2217
2218#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2220pub struct KeyPress {
2221 pub key: String,
2223 #[serde(default)]
2225 pub modifiers: Vec<String>,
2226}
2227
2228#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2230#[schemars(extend("x-display-field" = "/action"))]
2231pub struct Keybinding {
2232 #[serde(default, skip_serializing_if = "String::is_empty")]
2234 pub key: String,
2235
2236 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2238 pub modifiers: Vec<String>,
2239
2240 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2243 pub keys: Vec<KeyPress>,
2244
2245 pub action: String,
2247
2248 #[serde(default)]
2250 pub args: HashMap<String, serde_json::Value>,
2251
2252 #[serde(default)]
2254 pub when: Option<String>,
2255}
2256
2257#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2259#[schemars(extend("x-display-field" = "/inherits"))]
2260pub struct KeymapConfig {
2261 #[serde(default, skip_serializing_if = "Option::is_none")]
2263 pub inherits: Option<String>,
2264
2265 #[serde(default)]
2267 pub bindings: Vec<Keybinding>,
2268}
2269
2270#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2272#[schemars(extend("x-display-field" = "/command"))]
2273pub struct FormatterConfig {
2274 pub command: String,
2276
2277 #[serde(default)]
2280 pub args: Vec<String>,
2281
2282 #[serde(default = "default_true")]
2285 pub stdin: bool,
2286
2287 #[serde(default = "default_on_save_timeout")]
2289 pub timeout_ms: u64,
2290}
2291
2292#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2294#[schemars(extend("x-display-field" = "/command"))]
2295pub struct OnSaveAction {
2296 pub command: String,
2299
2300 #[serde(default)]
2303 pub args: Vec<String>,
2304
2305 #[serde(default)]
2307 pub working_dir: Option<String>,
2308
2309 #[serde(default)]
2311 pub stdin: bool,
2312
2313 #[serde(default = "default_on_save_timeout")]
2315 pub timeout_ms: u64,
2316
2317 #[serde(default = "default_true")]
2320 pub enabled: bool,
2321}
2322
2323fn default_on_save_timeout() -> u64 {
2324 10000
2325}
2326
2327fn default_page_width() -> Option<usize> {
2328 Some(80)
2329}
2330
2331#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2333#[schemars(extend("x-display-field" = "/grammar"))]
2334pub struct LanguageConfig {
2335 #[serde(default)]
2337 pub extensions: Vec<String>,
2338
2339 #[serde(default)]
2341 pub filenames: Vec<String>,
2342
2343 #[serde(default)]
2345 pub grammar: String,
2346
2347 #[serde(default)]
2349 pub comment_prefix: Option<String>,
2350
2351 #[serde(default = "default_true")]
2353 pub auto_indent: bool,
2354
2355 #[serde(default)]
2358 pub auto_close: Option<bool>,
2359
2360 #[serde(default)]
2363 pub auto_surround: Option<bool>,
2364
2365 #[serde(default)]
2368 pub textmate_grammar: Option<std::path::PathBuf>,
2369
2370 #[serde(default = "default_true")]
2373 pub show_whitespace_tabs: bool,
2374
2375 #[serde(default)]
2380 pub line_wrap: Option<bool>,
2381
2382 #[serde(default)]
2386 pub wrap_column: Option<usize>,
2387
2388 #[serde(default)]
2393 pub page_view: Option<bool>,
2394
2395 #[serde(default)]
2400 pub page_width: Option<usize>,
2401
2402 #[serde(default)]
2406 pub use_tabs: Option<bool>,
2407
2408 #[serde(default)]
2412 pub tab_size: Option<usize>,
2413
2414 #[serde(default)]
2416 pub formatter: Option<FormatterConfig>,
2417
2418 #[serde(default)]
2420 pub format_on_save: bool,
2421
2422 #[serde(default)]
2426 pub on_save: Vec<OnSaveAction>,
2427
2428 #[serde(default)]
2438 pub word_characters: Option<String>,
2439
2440 #[serde(default)]
2445 pub indent: Option<IndentRulesConfig>,
2446}
2447
2448#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
2463pub struct IndentRulesConfig {
2464 #[serde(default)]
2468 pub increase_indent_pattern: Option<String>,
2469
2470 #[serde(default)]
2473 pub decrease_indent_pattern: Option<String>,
2474
2475 #[serde(default)]
2479 pub indent_next_line_pattern: Option<String>,
2480
2481 #[serde(default)]
2485 pub dedent_next_line_pattern: Option<String>,
2486
2487 #[serde(default)]
2491 pub self_close_pattern: Option<String>,
2492}
2493
2494impl IndentRulesConfig {
2495 pub fn is_empty(&self) -> bool {
2497 self.increase_indent_pattern.is_none()
2498 && self.decrease_indent_pattern.is_none()
2499 && self.indent_next_line_pattern.is_none()
2500 && self.dedent_next_line_pattern.is_none()
2501 && self.self_close_pattern.is_none()
2502 }
2503}
2504
2505#[cfg(any(feature = "runtime", feature = "wasm"))]
2509pub fn reload_indent_overrides(languages: &HashMap<String, LanguageConfig>) {
2510 use crate::primitives::indent_rules;
2511 indent_rules::clear_user_rules();
2512 for (id, lc) in languages {
2513 let Some(ind) = &lc.indent else { continue };
2514 if ind.is_empty() {
2515 continue;
2516 }
2517 indent_rules::set_user_rule(
2518 id,
2519 ind.increase_indent_pattern.as_deref(),
2520 ind.decrease_indent_pattern.as_deref(),
2521 ind.indent_next_line_pattern.as_deref(),
2522 ind.dedent_next_line_pattern.as_deref(),
2523 ind.self_close_pattern.as_deref(),
2524 );
2525 }
2526}
2527
2528#[derive(Debug, Clone)]
2535pub struct BufferConfig {
2536 pub tab_size: usize,
2538
2539 pub use_tabs: bool,
2541
2542 pub auto_indent: bool,
2544
2545 pub auto_close: bool,
2547
2548 pub auto_surround: bool,
2550
2551 pub line_wrap: bool,
2553
2554 pub wrap_column: Option<usize>,
2556
2557 pub whitespace: WhitespaceVisibility,
2559
2560 pub formatter: Option<FormatterConfig>,
2562
2563 pub format_on_save: bool,
2565
2566 pub on_save: Vec<OnSaveAction>,
2568
2569 pub textmate_grammar: Option<std::path::PathBuf>,
2571
2572 pub word_characters: String,
2575}
2576
2577impl BufferConfig {
2578 pub fn resolve(global_config: &Config, language_id: Option<&str>) -> Self {
2587 let editor = &global_config.editor;
2588
2589 let mut whitespace = WhitespaceVisibility::from_editor_config(editor);
2591 let mut config = BufferConfig {
2592 tab_size: editor.tab_size,
2593 use_tabs: editor.use_tabs,
2594 auto_indent: editor.auto_indent,
2595 auto_close: editor.auto_close,
2596 auto_surround: editor.auto_surround,
2597 line_wrap: editor.line_wrap,
2598 wrap_column: editor.wrap_column,
2599 whitespace,
2600 formatter: None,
2601 format_on_save: false,
2602 on_save: Vec::new(),
2603 textmate_grammar: None,
2604 word_characters: String::new(),
2605 };
2606
2607 let lang_config_ref = language_id
2611 .and_then(|id| global_config.languages.get(id))
2612 .or_else(|| {
2613 match language_id {
2615 None | Some("text") => global_config
2616 .default_language
2617 .as_deref()
2618 .and_then(|lang| global_config.languages.get(lang)),
2619 _ => None,
2620 }
2621 });
2622 if let Some(lang_config) = lang_config_ref {
2623 if let Some(ts) = lang_config.tab_size {
2625 config.tab_size = ts;
2626 }
2627
2628 if let Some(use_tabs) = lang_config.use_tabs {
2630 config.use_tabs = use_tabs;
2631 }
2632
2633 if let Some(line_wrap) = lang_config.line_wrap {
2635 config.line_wrap = line_wrap;
2636 }
2637
2638 if lang_config.wrap_column.is_some() {
2640 config.wrap_column = lang_config.wrap_column;
2641 }
2642
2643 config.auto_indent = lang_config.auto_indent;
2645
2646 if config.auto_close {
2648 if let Some(lang_auto_close) = lang_config.auto_close {
2649 config.auto_close = lang_auto_close;
2650 }
2651 }
2652
2653 if config.auto_surround {
2655 if let Some(lang_auto_surround) = lang_config.auto_surround {
2656 config.auto_surround = lang_auto_surround;
2657 }
2658 }
2659
2660 whitespace = whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
2662 config.whitespace = whitespace;
2663
2664 config.formatter = lang_config.formatter.clone();
2666
2667 config.format_on_save = lang_config.format_on_save;
2669
2670 config.on_save = lang_config.on_save.clone();
2672
2673 config.textmate_grammar = lang_config.textmate_grammar.clone();
2675
2676 if let Some(ref wc) = lang_config.word_characters {
2678 config.word_characters = wc.clone();
2679 }
2680 }
2681
2682 config
2683 }
2684
2685 pub fn indent_string(&self) -> String {
2690 if self.use_tabs {
2691 "\t".to_string()
2692 } else {
2693 " ".repeat(self.tab_size)
2694 }
2695 }
2696}
2697
2698#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
2700pub struct MenuConfig {
2701 #[serde(default)]
2703 pub menus: Vec<Menu>,
2704}
2705
2706pub use fresh_core::menu::{Menu, MenuItem};
2708
2709pub trait MenuExt {
2711 fn match_id(&self) -> &str;
2714
2715 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path);
2718}
2719
2720impl MenuExt for Menu {
2721 fn match_id(&self) -> &str {
2722 self.id.as_deref().unwrap_or(&self.label)
2723 }
2724
2725 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path) {
2726 self.items = self
2727 .items
2728 .iter()
2729 .map(|item| item.expand_dynamic(themes_dir))
2730 .collect();
2731 }
2732}
2733
2734pub trait MenuItemExt {
2736 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem;
2739}
2740
2741impl MenuItemExt for MenuItem {
2742 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem {
2743 match self {
2744 MenuItem::DynamicSubmenu { label, source } => {
2745 let items = generate_dynamic_items(source, themes_dir);
2746 MenuItem::Submenu {
2747 label: label.clone(),
2748 items,
2749 }
2750 }
2751 other => other.clone(),
2752 }
2753 }
2754}
2755
2756#[cfg(feature = "runtime")]
2758pub fn generate_dynamic_items(source: &str, themes_dir: &std::path::Path) -> Vec<MenuItem> {
2759 match source {
2760 "copy_with_theme" => {
2761 let loader = crate::view::theme::ThemeLoader::new(themes_dir.to_path_buf());
2763 let registry = loader.load_all(&[]);
2764 registry
2765 .list()
2766 .iter()
2767 .map(|info| {
2768 let mut args = HashMap::new();
2769 args.insert("theme".to_string(), serde_json::json!(info.key));
2770 MenuItem::Action {
2771 label: info.name.clone(),
2772 action: "copy_with_theme".to_string(),
2773 args,
2774 when: Some(context_keys::HAS_SELECTION.to_string()),
2775 checkbox: None,
2776 }
2777 })
2778 .collect()
2779 }
2780 _ => vec![MenuItem::Label {
2781 info: format!("Unknown source: {}", source),
2782 }],
2783 }
2784}
2785
2786#[cfg(not(feature = "runtime"))]
2788pub fn generate_dynamic_items(_source: &str, _themes_dir: &std::path::Path) -> Vec<MenuItem> {
2789 vec![]
2791}
2792
2793impl Default for Config {
2794 fn default() -> Self {
2795 Self {
2796 version: 0,
2797 theme: default_theme_name(),
2798 locale: LocaleName::default(),
2799 check_for_updates: true,
2800 editor: EditorConfig::default(),
2801 file_explorer: FileExplorerConfig::default(),
2802 file_browser: FileBrowserConfig::default(),
2803 clipboard: ClipboardConfig::default(),
2804 terminal: TerminalConfig::default(),
2805 keybindings: vec![], keybinding_maps: HashMap::new(), active_keybinding_map: default_keybinding_map_name(),
2808 languages: Self::default_languages(),
2809 default_language: None,
2810 lsp_enabled: true,
2811 lsp: Self::default_lsp_config(),
2812 universal_lsp: Self::default_universal_lsp_config(),
2813 warnings: WarningsConfig::default(),
2814 plugins: HashMap::new(),
2815 packages: PackagesConfig::default(),
2816 env: EnvConfig::default(),
2817 }
2818 }
2819}
2820
2821impl MenuConfig {
2822 pub fn translated() -> Self {
2824 Self {
2825 menus: Self::translated_menus(),
2826 }
2827 }
2828
2829 pub fn translated_menus() -> Vec<Menu> {
2835 vec![
2836 Menu {
2838 id: Some("File".to_string()),
2839 label: t!("menu.file").to_string(),
2840 when: None,
2841 items: vec![
2842 MenuItem::Action {
2843 label: t!("menu.file.new_file").to_string(),
2844 action: "new".to_string(),
2845 args: HashMap::new(),
2846 when: None,
2847 checkbox: None,
2848 },
2849 MenuItem::Action {
2850 label: t!("menu.file.open_file").to_string(),
2851 action: "open".to_string(),
2852 args: HashMap::new(),
2853 when: None,
2854 checkbox: None,
2855 },
2856 MenuItem::Separator { separator: true },
2857 MenuItem::Action {
2858 label: t!("menu.file.save").to_string(),
2859 action: "save".to_string(),
2860 args: HashMap::new(),
2861 when: Some(context_keys::HAS_BUFFER.to_string()),
2862 checkbox: None,
2863 },
2864 MenuItem::Action {
2865 label: t!("menu.file.save_as").to_string(),
2866 action: "save_as".to_string(),
2867 args: HashMap::new(),
2868 when: Some(context_keys::HAS_BUFFER.to_string()),
2869 checkbox: None,
2870 },
2871 MenuItem::Action {
2872 label: t!("menu.file.revert").to_string(),
2873 action: "revert".to_string(),
2874 args: HashMap::new(),
2875 when: Some(context_keys::HAS_BUFFER.to_string()),
2876 checkbox: None,
2877 },
2878 MenuItem::Action {
2879 label: t!("menu.file.reload_with_encoding").to_string(),
2880 action: "reload_with_encoding".to_string(),
2881 args: HashMap::new(),
2882 when: Some(context_keys::HAS_BUFFER.to_string()),
2883 checkbox: None,
2884 },
2885 MenuItem::Separator { separator: true },
2886 MenuItem::Action {
2887 label: t!("menu.file.close_buffer").to_string(),
2888 action: "close".to_string(),
2889 args: HashMap::new(),
2890 when: Some(context_keys::HAS_BUFFER.to_string()),
2891 checkbox: None,
2892 },
2893 MenuItem::Separator { separator: true },
2894 MenuItem::Action {
2895 label: t!("menu.file.switch_project").to_string(),
2896 action: "switch_project".to_string(),
2897 args: HashMap::new(),
2898 when: None,
2899 checkbox: None,
2900 },
2901 MenuItem::Separator { separator: true },
2902 MenuItem::Action {
2903 label: t!("menu.file.detach").to_string(),
2904 action: "detach".to_string(),
2905 args: HashMap::new(),
2906 when: Some(context_keys::SESSION_MODE.to_string()),
2907 checkbox: None,
2908 },
2909 MenuItem::Action {
2910 label: t!("menu.file.quit").to_string(),
2911 action: "quit".to_string(),
2912 args: HashMap::new(),
2913 when: None,
2914 checkbox: None,
2915 },
2916 ],
2917 },
2918 Menu {
2920 id: Some("Edit".to_string()),
2921 label: t!("menu.edit").to_string(),
2922 when: None,
2923 items: vec![
2924 MenuItem::Action {
2925 label: t!("menu.edit.undo").to_string(),
2926 action: "undo".to_string(),
2927 args: HashMap::new(),
2928 when: Some(context_keys::HAS_BUFFER.to_string()),
2929 checkbox: None,
2930 },
2931 MenuItem::Action {
2932 label: t!("menu.edit.redo").to_string(),
2933 action: "redo".to_string(),
2934 args: HashMap::new(),
2935 when: Some(context_keys::HAS_BUFFER.to_string()),
2936 checkbox: None,
2937 },
2938 MenuItem::Separator { separator: true },
2939 MenuItem::Action {
2940 label: t!("menu.edit.cut").to_string(),
2941 action: "cut".to_string(),
2942 args: HashMap::new(),
2943 when: Some(context_keys::CAN_COPY.to_string()),
2944 checkbox: None,
2945 },
2946 MenuItem::Action {
2947 label: t!("menu.edit.copy").to_string(),
2948 action: "copy".to_string(),
2949 args: HashMap::new(),
2950 when: Some(context_keys::CAN_COPY.to_string()),
2951 checkbox: None,
2952 },
2953 MenuItem::DynamicSubmenu {
2954 label: t!("menu.edit.copy_with_formatting").to_string(),
2955 source: "copy_with_theme".to_string(),
2956 },
2957 MenuItem::Action {
2958 label: t!("menu.edit.paste").to_string(),
2959 action: "paste".to_string(),
2960 args: HashMap::new(),
2961 when: Some(context_keys::CAN_PASTE.to_string()),
2962 checkbox: None,
2963 },
2964 MenuItem::Separator { separator: true },
2965 MenuItem::Action {
2966 label: t!("menu.edit.select_all").to_string(),
2967 action: "select_all".to_string(),
2968 args: HashMap::new(),
2969 when: Some(context_keys::HAS_BUFFER.to_string()),
2970 checkbox: None,
2971 },
2972 MenuItem::Separator { separator: true },
2973 MenuItem::Action {
2974 label: t!("menu.edit.find").to_string(),
2975 action: "search".to_string(),
2976 args: HashMap::new(),
2977 when: Some(context_keys::HAS_BUFFER.to_string()),
2978 checkbox: None,
2979 },
2980 MenuItem::Action {
2981 label: t!("menu.edit.find_in_selection").to_string(),
2982 action: "find_in_selection".to_string(),
2983 args: HashMap::new(),
2984 when: Some(context_keys::HAS_SELECTION.to_string()),
2985 checkbox: None,
2986 },
2987 MenuItem::Action {
2988 label: t!("menu.edit.find_next").to_string(),
2989 action: "find_next".to_string(),
2990 args: HashMap::new(),
2991 when: Some(context_keys::HAS_BUFFER.to_string()),
2992 checkbox: None,
2993 },
2994 MenuItem::Action {
2995 label: t!("menu.edit.find_previous").to_string(),
2996 action: "find_previous".to_string(),
2997 args: HashMap::new(),
2998 when: Some(context_keys::HAS_BUFFER.to_string()),
2999 checkbox: None,
3000 },
3001 MenuItem::Action {
3002 label: t!("menu.edit.replace").to_string(),
3003 action: "query_replace".to_string(),
3004 args: HashMap::new(),
3005 when: Some(context_keys::HAS_BUFFER.to_string()),
3006 checkbox: None,
3007 },
3008 MenuItem::Separator { separator: true },
3009 MenuItem::Action {
3010 label: t!("menu.edit.delete_line").to_string(),
3011 action: "delete_line".to_string(),
3012 args: HashMap::new(),
3013 when: Some(context_keys::HAS_BUFFER.to_string()),
3014 checkbox: None,
3015 },
3016 MenuItem::Action {
3017 label: t!("menu.edit.format_buffer").to_string(),
3018 action: "format_buffer".to_string(),
3019 args: HashMap::new(),
3020 when: Some(context_keys::FORMATTER_AVAILABLE.to_string()),
3021 checkbox: None,
3022 },
3023 MenuItem::Separator { separator: true },
3024 MenuItem::Action {
3025 label: t!("menu.edit.settings").to_string(),
3026 action: "open_settings".to_string(),
3027 args: HashMap::new(),
3028 when: None,
3029 checkbox: None,
3030 },
3031 MenuItem::Action {
3032 label: t!("menu.edit.keybinding_editor").to_string(),
3033 action: "open_keybinding_editor".to_string(),
3034 args: HashMap::new(),
3035 when: None,
3036 checkbox: None,
3037 },
3038 ],
3039 },
3040 Menu {
3042 id: Some("View".to_string()),
3043 label: t!("menu.view").to_string(),
3044 when: None,
3045 items: vec![
3046 MenuItem::Action {
3047 label: t!("menu.view.file_explorer").to_string(),
3048 action: "toggle_file_explorer".to_string(),
3049 args: HashMap::new(),
3050 when: None,
3051 checkbox: Some(context_keys::FILE_EXPLORER.to_string()),
3052 },
3053 MenuItem::Separator { separator: true },
3054 MenuItem::Action {
3055 label: t!("menu.view.line_numbers").to_string(),
3056 action: "toggle_line_numbers".to_string(),
3057 args: HashMap::new(),
3058 when: None,
3059 checkbox: Some(context_keys::LINE_NUMBERS.to_string()),
3060 },
3061 MenuItem::Action {
3062 label: t!("menu.view.line_wrap").to_string(),
3063 action: "toggle_line_wrap".to_string(),
3064 args: HashMap::new(),
3065 when: None,
3066 checkbox: Some(context_keys::LINE_WRAP.to_string()),
3067 },
3068 MenuItem::Action {
3069 label: t!("menu.view.mouse_support").to_string(),
3070 action: "toggle_mouse_capture".to_string(),
3071 args: HashMap::new(),
3072 when: None,
3073 checkbox: Some(context_keys::MOUSE_CAPTURE.to_string()),
3074 },
3075 MenuItem::Separator { separator: true },
3076 MenuItem::Action {
3077 label: t!("menu.view.vertical_scrollbar").to_string(),
3078 action: "toggle_vertical_scrollbar".to_string(),
3079 args: HashMap::new(),
3080 when: None,
3081 checkbox: Some(context_keys::VERTICAL_SCROLLBAR.to_string()),
3082 },
3083 MenuItem::Action {
3084 label: t!("menu.view.horizontal_scrollbar").to_string(),
3085 action: "toggle_horizontal_scrollbar".to_string(),
3086 args: HashMap::new(),
3087 when: None,
3088 checkbox: Some(context_keys::HORIZONTAL_SCROLLBAR.to_string()),
3089 },
3090 MenuItem::Separator { separator: true },
3091 MenuItem::Action {
3092 label: t!("menu.view.set_background").to_string(),
3093 action: "set_background".to_string(),
3094 args: HashMap::new(),
3095 when: None,
3096 checkbox: None,
3097 },
3098 MenuItem::Action {
3099 label: t!("menu.view.set_background_blend").to_string(),
3100 action: "set_background_blend".to_string(),
3101 args: HashMap::new(),
3102 when: None,
3103 checkbox: None,
3104 },
3105 MenuItem::Action {
3106 label: t!("menu.view.set_page_width").to_string(),
3107 action: "set_page_width".to_string(),
3108 args: HashMap::new(),
3109 when: None,
3110 checkbox: None,
3111 },
3112 MenuItem::Separator { separator: true },
3113 MenuItem::Action {
3114 label: t!("menu.view.select_theme").to_string(),
3115 action: "select_theme".to_string(),
3116 args: HashMap::new(),
3117 when: None,
3118 checkbox: None,
3119 },
3120 MenuItem::Action {
3121 label: t!("menu.view.select_locale").to_string(),
3122 action: "select_locale".to_string(),
3123 args: HashMap::new(),
3124 when: None,
3125 checkbox: None,
3126 },
3127 MenuItem::Action {
3128 label: t!("menu.view.settings").to_string(),
3129 action: "open_settings".to_string(),
3130 args: HashMap::new(),
3131 when: None,
3132 checkbox: None,
3133 },
3134 MenuItem::Action {
3135 label: t!("menu.view.calibrate_input").to_string(),
3136 action: "calibrate_input".to_string(),
3137 args: HashMap::new(),
3138 when: None,
3139 checkbox: None,
3140 },
3141 MenuItem::Separator { separator: true },
3142 MenuItem::Action {
3143 label: t!("menu.view.split_horizontal").to_string(),
3144 action: "split_horizontal".to_string(),
3145 args: HashMap::new(),
3146 when: Some(context_keys::HAS_BUFFER.to_string()),
3147 checkbox: None,
3148 },
3149 MenuItem::Action {
3150 label: t!("menu.view.split_vertical").to_string(),
3151 action: "split_vertical".to_string(),
3152 args: HashMap::new(),
3153 when: Some(context_keys::HAS_BUFFER.to_string()),
3154 checkbox: None,
3155 },
3156 MenuItem::Action {
3157 label: t!("menu.view.close_split").to_string(),
3158 action: "close_split".to_string(),
3159 args: HashMap::new(),
3160 when: Some(context_keys::HAS_BUFFER.to_string()),
3161 checkbox: None,
3162 },
3163 MenuItem::Action {
3164 label: t!("menu.view.scroll_sync").to_string(),
3165 action: "toggle_scroll_sync".to_string(),
3166 args: HashMap::new(),
3167 when: Some(context_keys::HAS_SAME_BUFFER_SPLITS.to_string()),
3168 checkbox: Some(context_keys::SCROLL_SYNC.to_string()),
3169 },
3170 MenuItem::Action {
3171 label: t!("menu.view.focus_next_split").to_string(),
3172 action: "next_split".to_string(),
3173 args: HashMap::new(),
3174 when: None,
3175 checkbox: None,
3176 },
3177 MenuItem::Action {
3178 label: t!("menu.view.focus_prev_split").to_string(),
3179 action: "prev_split".to_string(),
3180 args: HashMap::new(),
3181 when: None,
3182 checkbox: None,
3183 },
3184 MenuItem::Action {
3185 label: t!("menu.view.toggle_maximize_split").to_string(),
3186 action: "toggle_maximize_split".to_string(),
3187 args: HashMap::new(),
3188 when: None,
3189 checkbox: None,
3190 },
3191 MenuItem::Separator { separator: true },
3192 MenuItem::Submenu {
3193 label: t!("menu.terminal").to_string(),
3194 items: vec![
3195 MenuItem::Action {
3196 label: t!("menu.terminal.open").to_string(),
3197 action: "open_terminal".to_string(),
3198 args: HashMap::new(),
3199 when: None,
3200 checkbox: None,
3201 },
3202 MenuItem::Action {
3203 label: t!("menu.terminal.close").to_string(),
3204 action: "close_terminal".to_string(),
3205 args: HashMap::new(),
3206 when: None,
3207 checkbox: None,
3208 },
3209 MenuItem::Action {
3210 label: t!("menu.terminal.send_selection").to_string(),
3211 action: "send_selection_to_terminal".to_string(),
3212 args: HashMap::new(),
3213 when: None,
3214 checkbox: None,
3215 },
3216 MenuItem::Separator { separator: true },
3217 MenuItem::Action {
3218 label: t!("menu.terminal.toggle_keyboard_capture").to_string(),
3219 action: "toggle_keyboard_capture".to_string(),
3220 args: HashMap::new(),
3221 when: None,
3222 checkbox: None,
3223 },
3224 ],
3225 },
3226 MenuItem::Separator { separator: true },
3227 MenuItem::Submenu {
3228 label: t!("menu.view.keybinding_style").to_string(),
3229 items: vec![
3230 MenuItem::Action {
3231 label: t!("menu.view.keybinding_default").to_string(),
3232 action: "switch_keybinding_map".to_string(),
3233 args: {
3234 let mut map = HashMap::new();
3235 map.insert("map".to_string(), serde_json::json!("default"));
3236 map
3237 },
3238 when: None,
3239 checkbox: Some(context_keys::KEYMAP_DEFAULT.to_string()),
3240 },
3241 MenuItem::Action {
3242 label: t!("menu.view.keybinding_emacs").to_string(),
3243 action: "switch_keybinding_map".to_string(),
3244 args: {
3245 let mut map = HashMap::new();
3246 map.insert("map".to_string(), serde_json::json!("emacs"));
3247 map
3248 },
3249 when: None,
3250 checkbox: Some(context_keys::KEYMAP_EMACS.to_string()),
3251 },
3252 MenuItem::Action {
3253 label: t!("menu.view.keybinding_vscode").to_string(),
3254 action: "switch_keybinding_map".to_string(),
3255 args: {
3256 let mut map = HashMap::new();
3257 map.insert("map".to_string(), serde_json::json!("vscode"));
3258 map
3259 },
3260 when: None,
3261 checkbox: Some(context_keys::KEYMAP_VSCODE.to_string()),
3262 },
3263 MenuItem::Action {
3264 label: "macOS GUI (⌘)".to_string(),
3265 action: "switch_keybinding_map".to_string(),
3266 args: {
3267 let mut map = HashMap::new();
3268 map.insert("map".to_string(), serde_json::json!("macos-gui"));
3269 map
3270 },
3271 when: None,
3272 checkbox: Some(context_keys::KEYMAP_MACOS_GUI.to_string()),
3273 },
3274 ],
3275 },
3276 ],
3277 },
3278 Menu {
3280 id: Some("Selection".to_string()),
3281 label: t!("menu.selection").to_string(),
3282 when: Some(context_keys::HAS_BUFFER.to_string()),
3283 items: vec![
3284 MenuItem::Action {
3285 label: t!("menu.selection.select_all").to_string(),
3286 action: "select_all".to_string(),
3287 args: HashMap::new(),
3288 when: None,
3289 checkbox: None,
3290 },
3291 MenuItem::Action {
3292 label: t!("menu.selection.select_word").to_string(),
3293 action: "select_word".to_string(),
3294 args: HashMap::new(),
3295 when: None,
3296 checkbox: None,
3297 },
3298 MenuItem::Action {
3299 label: t!("menu.selection.select_line").to_string(),
3300 action: "select_line".to_string(),
3301 args: HashMap::new(),
3302 when: None,
3303 checkbox: None,
3304 },
3305 MenuItem::Action {
3306 label: t!("menu.selection.expand_selection").to_string(),
3307 action: "expand_selection".to_string(),
3308 args: HashMap::new(),
3309 when: None,
3310 checkbox: None,
3311 },
3312 MenuItem::Separator { separator: true },
3313 MenuItem::Action {
3314 label: t!("menu.selection.add_cursor_above").to_string(),
3315 action: "add_cursor_above".to_string(),
3316 args: HashMap::new(),
3317 when: None,
3318 checkbox: None,
3319 },
3320 MenuItem::Action {
3321 label: t!("menu.selection.add_cursor_below").to_string(),
3322 action: "add_cursor_below".to_string(),
3323 args: HashMap::new(),
3324 when: None,
3325 checkbox: None,
3326 },
3327 MenuItem::Action {
3328 label: t!("menu.selection.add_cursor_next_match").to_string(),
3329 action: "add_cursor_next_match".to_string(),
3330 args: HashMap::new(),
3331 when: None,
3332 checkbox: None,
3333 },
3334 MenuItem::Action {
3335 label: t!("menu.selection.add_cursors_to_line_ends").to_string(),
3336 action: "add_cursors_to_line_ends".to_string(),
3337 args: HashMap::new(),
3338 when: None,
3339 checkbox: None,
3340 },
3341 MenuItem::Action {
3342 label: t!("menu.selection.remove_secondary_cursors").to_string(),
3343 action: "remove_secondary_cursors".to_string(),
3344 args: HashMap::new(),
3345 when: None,
3346 checkbox: None,
3347 },
3348 ],
3349 },
3350 Menu {
3352 id: Some("Go".to_string()),
3353 label: t!("menu.go").to_string(),
3354 when: None,
3355 items: vec![
3356 MenuItem::Action {
3357 label: t!("menu.go.goto_line").to_string(),
3358 action: "goto_line".to_string(),
3359 args: HashMap::new(),
3360 when: Some(context_keys::HAS_BUFFER.to_string()),
3361 checkbox: None,
3362 },
3363 MenuItem::Action {
3364 label: t!("menu.go.goto_definition").to_string(),
3365 action: "lsp_goto_definition".to_string(),
3366 args: HashMap::new(),
3367 when: Some(context_keys::HAS_BUFFER.to_string()),
3368 checkbox: None,
3369 },
3370 MenuItem::Action {
3371 label: t!("menu.go.find_references").to_string(),
3372 action: "lsp_references".to_string(),
3373 args: HashMap::new(),
3374 when: Some(context_keys::HAS_BUFFER.to_string()),
3375 checkbox: None,
3376 },
3377 MenuItem::Separator { separator: true },
3378 MenuItem::Action {
3379 label: t!("menu.go.next_buffer").to_string(),
3380 action: "next_buffer".to_string(),
3381 args: HashMap::new(),
3382 when: Some(context_keys::HAS_BUFFER.to_string()),
3383 checkbox: None,
3384 },
3385 MenuItem::Action {
3386 label: t!("menu.go.prev_buffer").to_string(),
3387 action: "prev_buffer".to_string(),
3388 args: HashMap::new(),
3389 when: Some(context_keys::HAS_BUFFER.to_string()),
3390 checkbox: None,
3391 },
3392 MenuItem::Separator { separator: true },
3393 MenuItem::Action {
3394 label: t!("menu.go.command_palette").to_string(),
3395 action: "command_palette".to_string(),
3396 args: HashMap::new(),
3397 when: None,
3398 checkbox: None,
3399 },
3400 ],
3401 },
3402 Menu {
3404 id: Some("LSP".to_string()),
3405 label: t!("menu.lsp").to_string(),
3406 when: None,
3407 items: vec![
3408 MenuItem::Action {
3409 label: t!("menu.lsp.show_hover").to_string(),
3410 action: "lsp_hover".to_string(),
3411 args: HashMap::new(),
3412 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3413 checkbox: None,
3414 },
3415 MenuItem::Action {
3416 label: t!("menu.lsp.goto_definition").to_string(),
3417 action: "lsp_goto_definition".to_string(),
3418 args: HashMap::new(),
3419 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3420 checkbox: None,
3421 },
3422 MenuItem::Action {
3423 label: t!("menu.lsp.find_references").to_string(),
3424 action: "lsp_references".to_string(),
3425 args: HashMap::new(),
3426 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3427 checkbox: None,
3428 },
3429 MenuItem::Action {
3430 label: t!("menu.lsp.rename_symbol").to_string(),
3431 action: "lsp_rename".to_string(),
3432 args: HashMap::new(),
3433 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3434 checkbox: None,
3435 },
3436 MenuItem::Separator { separator: true },
3437 MenuItem::Action {
3438 label: t!("menu.lsp.show_completions").to_string(),
3439 action: "lsp_completion".to_string(),
3440 args: HashMap::new(),
3441 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3442 checkbox: None,
3443 },
3444 MenuItem::Action {
3445 label: t!("menu.lsp.show_signature").to_string(),
3446 action: "lsp_signature_help".to_string(),
3447 args: HashMap::new(),
3448 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3449 checkbox: None,
3450 },
3451 MenuItem::Action {
3452 label: t!("menu.lsp.code_actions").to_string(),
3453 action: "lsp_code_actions".to_string(),
3454 args: HashMap::new(),
3455 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3456 checkbox: None,
3457 },
3458 MenuItem::Separator { separator: true },
3459 MenuItem::Action {
3460 label: t!("menu.lsp.toggle_inlay_hints").to_string(),
3461 action: "toggle_inlay_hints".to_string(),
3462 args: HashMap::new(),
3463 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3464 checkbox: Some(context_keys::INLAY_HINTS.to_string()),
3465 },
3466 MenuItem::Action {
3467 label: t!("menu.lsp.toggle_mouse_hover").to_string(),
3468 action: "toggle_mouse_hover".to_string(),
3469 args: HashMap::new(),
3470 when: None,
3471 checkbox: Some(context_keys::MOUSE_HOVER.to_string()),
3472 },
3473 MenuItem::Separator { separator: true },
3474 MenuItem::Action {
3475 label: t!("menu.lsp.show_status").to_string(),
3476 action: "show_lsp_status".to_string(),
3477 args: HashMap::new(),
3478 when: None,
3479 checkbox: None,
3480 },
3481 MenuItem::Action {
3482 label: t!("menu.lsp.restart_server").to_string(),
3483 action: "lsp_restart".to_string(),
3484 args: HashMap::new(),
3485 when: None,
3486 checkbox: None,
3487 },
3488 MenuItem::Action {
3489 label: t!("menu.lsp.stop_server").to_string(),
3490 action: "lsp_stop".to_string(),
3491 args: HashMap::new(),
3492 when: None,
3493 checkbox: None,
3494 },
3495 MenuItem::Separator { separator: true },
3496 MenuItem::Action {
3497 label: t!("menu.lsp.toggle_for_buffer").to_string(),
3498 action: "lsp_toggle_for_buffer".to_string(),
3499 args: HashMap::new(),
3500 when: Some(context_keys::HAS_BUFFER.to_string()),
3501 checkbox: None,
3502 },
3503 ],
3504 },
3505 Menu {
3507 id: Some("Explorer".to_string()),
3508 label: t!("menu.explorer").to_string(),
3509 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3510 items: vec![
3511 MenuItem::Action {
3512 label: t!("menu.explorer.new_file").to_string(),
3513 action: "file_explorer_new_file".to_string(),
3514 args: HashMap::new(),
3515 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3516 checkbox: None,
3517 },
3518 MenuItem::Action {
3519 label: t!("menu.explorer.new_folder").to_string(),
3520 action: "file_explorer_new_directory".to_string(),
3521 args: HashMap::new(),
3522 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3523 checkbox: None,
3524 },
3525 MenuItem::Separator { separator: true },
3526 MenuItem::Action {
3527 label: t!("menu.explorer.open").to_string(),
3528 action: "file_explorer_open".to_string(),
3529 args: HashMap::new(),
3530 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3531 checkbox: None,
3532 },
3533 MenuItem::Action {
3534 label: t!("menu.explorer.rename").to_string(),
3535 action: "file_explorer_rename".to_string(),
3536 args: HashMap::new(),
3537 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3538 checkbox: None,
3539 },
3540 MenuItem::Action {
3541 label: t!("menu.explorer.delete").to_string(),
3542 action: "file_explorer_delete".to_string(),
3543 args: HashMap::new(),
3544 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3545 checkbox: None,
3546 },
3547 MenuItem::Separator { separator: true },
3548 MenuItem::Action {
3549 label: t!("menu.explorer.cut").to_string(),
3550 action: "cut".to_string(),
3551 args: HashMap::new(),
3552 when: Some(context_keys::CAN_COPY.to_string()),
3553 checkbox: None,
3554 },
3555 MenuItem::Action {
3556 label: t!("menu.explorer.copy").to_string(),
3557 action: "copy".to_string(),
3558 args: HashMap::new(),
3559 when: Some(context_keys::CAN_COPY.to_string()),
3560 checkbox: None,
3561 },
3562 MenuItem::Action {
3563 label: t!("menu.explorer.paste").to_string(),
3564 action: "paste".to_string(),
3565 args: HashMap::new(),
3566 when: Some(context_keys::CAN_PASTE.to_string()),
3567 checkbox: None,
3568 },
3569 MenuItem::Separator { separator: true },
3570 MenuItem::Action {
3571 label: t!("menu.explorer.refresh").to_string(),
3572 action: "file_explorer_refresh".to_string(),
3573 args: HashMap::new(),
3574 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3575 checkbox: None,
3576 },
3577 MenuItem::Separator { separator: true },
3578 MenuItem::Action {
3579 label: t!("menu.explorer.show_hidden").to_string(),
3580 action: "file_explorer_toggle_hidden".to_string(),
3581 args: HashMap::new(),
3582 when: Some(context_keys::FILE_EXPLORER.to_string()),
3583 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_HIDDEN.to_string()),
3584 },
3585 MenuItem::Action {
3586 label: t!("menu.explorer.show_gitignored").to_string(),
3587 action: "file_explorer_toggle_gitignored".to_string(),
3588 args: HashMap::new(),
3589 when: Some(context_keys::FILE_EXPLORER.to_string()),
3590 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_GITIGNORED.to_string()),
3591 },
3592 ],
3593 },
3594 Menu {
3596 id: Some("Help".to_string()),
3597 label: t!("menu.help").to_string(),
3598 when: None,
3599 items: vec![
3600 MenuItem::Label {
3601 info: format!("Fresh v{}", env!("CARGO_PKG_VERSION")),
3602 },
3603 MenuItem::Separator { separator: true },
3604 MenuItem::Action {
3605 label: t!("menu.help.show_manual").to_string(),
3606 action: "show_help".to_string(),
3607 args: HashMap::new(),
3608 when: None,
3609 checkbox: None,
3610 },
3611 MenuItem::Action {
3612 label: t!("menu.help.keyboard_shortcuts").to_string(),
3613 action: "keyboard_shortcuts".to_string(),
3614 args: HashMap::new(),
3615 when: None,
3616 checkbox: None,
3617 },
3618 MenuItem::Separator { separator: true },
3619 MenuItem::Action {
3620 label: t!("menu.help.event_debug").to_string(),
3621 action: "event_debug".to_string(),
3622 args: HashMap::new(),
3623 when: None,
3624 checkbox: None,
3625 },
3626 ],
3627 },
3628 ]
3629 }
3630}
3631
3632impl Config {
3633 pub(crate) const FILENAME: &'static str = "config.json";
3635
3636 pub(crate) fn normalize_zero_sentinels(&mut self) {
3650 if self.editor.wrap_column == Some(0) {
3651 self.editor.wrap_column = None;
3652 }
3653 if self.editor.page_width == Some(0) {
3654 self.editor.page_width = None;
3655 }
3656 if self.editor.tab_size == 0 {
3657 self.editor.tab_size = default_tab_size();
3658 }
3659 for lang in self.languages.values_mut() {
3660 if lang.wrap_column == Some(0) {
3661 lang.wrap_column = None;
3662 }
3663 if lang.page_width == Some(0) {
3664 lang.page_width = None;
3665 }
3666 if lang.tab_size == Some(0) {
3667 lang.tab_size = None;
3668 }
3669 }
3670 }
3671
3672 pub fn apply_runtime_flags(&self) {
3677 #[cfg(windows)]
3678 {
3679 crate::services::terminal::set_skip_app_execution_alias(
3680 self.terminal.skip_app_execution_alias,
3681 );
3682 }
3683 }
3684
3685 pub(crate) fn local_config_path(working_dir: &Path) -> std::path::PathBuf {
3687 working_dir.join(Self::FILENAME)
3688 }
3689
3690 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
3696 let contents = std::fs::read_to_string(path.as_ref())
3697 .map_err(|e| ConfigError::IoError(e.to_string()))?;
3698
3699 let partial: crate::partial_config::PartialConfig =
3701 serde_json::from_str(&contents).map_err(|e| ConfigError::ParseError(e.to_string()))?;
3702
3703 Ok(partial.resolve())
3704 }
3705
3706 fn load_builtin_keymap(name: &str) -> Option<KeymapConfig> {
3708 let json_content = match name {
3709 "default" => include_str!("../keymaps/default.json"),
3710 "emacs" => include_str!("../keymaps/emacs.json"),
3711 "vscode" => include_str!("../keymaps/vscode.json"),
3712 "macos" => include_str!("../keymaps/macos.json"),
3713 "macos-gui" => include_str!("../keymaps/macos-gui.json"),
3714 _ => return None,
3715 };
3716
3717 match serde_json::from_str(json_content) {
3718 Ok(config) => Some(config),
3719 Err(e) => {
3720 eprintln!("Failed to parse builtin keymap '{}': {}", name, e);
3721 None
3722 }
3723 }
3724 }
3725
3726 pub fn resolve_keymap(&self, map_name: &str) -> Vec<Keybinding> {
3729 let mut visited = std::collections::HashSet::new();
3730 self.resolve_keymap_recursive(map_name, &mut visited)
3731 }
3732
3733 fn resolve_keymap_recursive(
3735 &self,
3736 map_name: &str,
3737 visited: &mut std::collections::HashSet<String>,
3738 ) -> Vec<Keybinding> {
3739 if visited.contains(map_name) {
3741 eprintln!(
3742 "Warning: Circular inheritance detected in keymap '{}'",
3743 map_name
3744 );
3745 return Vec::new();
3746 }
3747 visited.insert(map_name.to_string());
3748
3749 let keymap = self
3751 .keybinding_maps
3752 .get(map_name)
3753 .cloned()
3754 .or_else(|| Self::load_builtin_keymap(map_name));
3755
3756 let Some(keymap) = keymap else {
3757 return Vec::new();
3758 };
3759
3760 let mut all_bindings = if let Some(ref parent_name) = keymap.inherits {
3762 self.resolve_keymap_recursive(parent_name, visited)
3763 } else {
3764 Vec::new()
3765 };
3766
3767 all_bindings.extend(keymap.bindings);
3769
3770 all_bindings
3771 }
3772 fn default_languages() -> HashMap<String, LanguageConfig> {
3774 let mut languages = HashMap::new();
3775
3776 languages.insert(
3777 "rust".to_string(),
3778 LanguageConfig {
3779 extensions: vec!["rs".to_string()],
3780 filenames: vec![],
3781 grammar: "rust".to_string(),
3782 comment_prefix: Some("//".to_string()),
3783 auto_indent: true,
3784 auto_close: None,
3785 auto_surround: None,
3786 textmate_grammar: None,
3787 show_whitespace_tabs: true,
3788 line_wrap: None,
3789 wrap_column: None,
3790 page_view: None,
3791 page_width: None,
3792 use_tabs: None,
3793 tab_size: None,
3794 formatter: Some(FormatterConfig {
3795 command: "rustfmt".to_string(),
3796 args: vec!["--edition".to_string(), "2021".to_string()],
3797 stdin: true,
3798 timeout_ms: 10000,
3799 }),
3800 format_on_save: false,
3801 on_save: vec![],
3802 word_characters: None,
3803 indent: None,
3804 },
3805 );
3806
3807 languages.insert(
3808 "javascript".to_string(),
3809 LanguageConfig {
3810 extensions: vec!["js".to_string(), "jsx".to_string(), "mjs".to_string()],
3811 filenames: vec![],
3812 grammar: "javascript".to_string(),
3813 comment_prefix: Some("//".to_string()),
3814 auto_indent: true,
3815 auto_close: None,
3816 auto_surround: None,
3817 textmate_grammar: None,
3818 show_whitespace_tabs: true,
3819 line_wrap: None,
3820 wrap_column: None,
3821 page_view: None,
3822 page_width: None,
3823 use_tabs: None,
3824 tab_size: None,
3825 formatter: Some(FormatterConfig {
3826 command: "prettier".to_string(),
3827 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3828 stdin: true,
3829 timeout_ms: 10000,
3830 }),
3831 format_on_save: false,
3832 on_save: vec![],
3833 word_characters: None,
3834 indent: None,
3835 },
3836 );
3837
3838 languages.insert(
3839 "typescript".to_string(),
3840 LanguageConfig {
3841 extensions: vec!["ts".to_string(), "tsx".to_string(), "mts".to_string()],
3842 filenames: vec![],
3843 grammar: "typescript".to_string(),
3844 comment_prefix: Some("//".to_string()),
3845 auto_indent: true,
3846 auto_close: None,
3847 auto_surround: None,
3848 textmate_grammar: None,
3849 show_whitespace_tabs: true,
3850 line_wrap: None,
3851 wrap_column: None,
3852 page_view: None,
3853 page_width: None,
3854 use_tabs: None,
3855 tab_size: None,
3856 formatter: Some(FormatterConfig {
3857 command: "prettier".to_string(),
3858 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3859 stdin: true,
3860 timeout_ms: 10000,
3861 }),
3862 format_on_save: false,
3863 on_save: vec![],
3864 word_characters: None,
3865 indent: None,
3866 },
3867 );
3868
3869 languages.insert(
3870 "python".to_string(),
3871 LanguageConfig {
3872 extensions: vec!["py".to_string(), "pyi".to_string()],
3873 filenames: vec![],
3874 grammar: "python".to_string(),
3875 comment_prefix: Some("#".to_string()),
3876 auto_indent: true,
3877 auto_close: None,
3878 auto_surround: None,
3879 textmate_grammar: None,
3880 show_whitespace_tabs: true,
3881 line_wrap: None,
3882 wrap_column: None,
3883 page_view: None,
3884 page_width: None,
3885 use_tabs: None,
3886 tab_size: None,
3887 formatter: Some(FormatterConfig {
3888 command: "ruff".to_string(),
3889 args: vec![
3890 "format".to_string(),
3891 "--stdin-filename".to_string(),
3892 "$FILE".to_string(),
3893 ],
3894 stdin: true,
3895 timeout_ms: 10000,
3896 }),
3897 format_on_save: false,
3898 on_save: vec![],
3899 word_characters: None,
3900 indent: None,
3901 },
3902 );
3903
3904 languages.insert(
3905 "gdscript".to_string(),
3906 LanguageConfig {
3907 extensions: vec!["gd".to_string()],
3908 filenames: vec![],
3909 grammar: "gdscript".to_string(),
3910 comment_prefix: Some("#".to_string()),
3911 auto_indent: true,
3912 auto_close: None,
3913 auto_surround: None,
3914 textmate_grammar: None,
3915 show_whitespace_tabs: true,
3916 line_wrap: None,
3917 wrap_column: None,
3918 page_view: None,
3919 page_width: None,
3920 use_tabs: None,
3921 tab_size: None,
3922 formatter: None,
3923 format_on_save: false,
3924 on_save: vec![],
3925 word_characters: None,
3926 indent: None,
3927 },
3928 );
3929
3930 languages.insert(
3931 "c".to_string(),
3932 LanguageConfig {
3933 extensions: vec!["c".to_string(), "h".to_string()],
3934 filenames: vec![],
3935 grammar: "c".to_string(),
3936 comment_prefix: Some("//".to_string()),
3937 auto_indent: true,
3938 auto_close: None,
3939 auto_surround: None,
3940 textmate_grammar: None,
3941 show_whitespace_tabs: true,
3942 line_wrap: None,
3943 wrap_column: None,
3944 page_view: None,
3945 page_width: None,
3946 use_tabs: None,
3947 tab_size: None,
3948 formatter: Some(FormatterConfig {
3949 command: "clang-format".to_string(),
3950 args: vec![],
3951 stdin: true,
3952 timeout_ms: 10000,
3953 }),
3954 format_on_save: false,
3955 on_save: vec![],
3956 word_characters: None,
3957 indent: None,
3958 },
3959 );
3960
3961 languages.insert(
3962 "cpp".to_string(),
3963 LanguageConfig {
3964 extensions: vec![
3965 "cpp".to_string(),
3966 "cc".to_string(),
3967 "cxx".to_string(),
3968 "hpp".to_string(),
3969 "hh".to_string(),
3970 "hxx".to_string(),
3971 ],
3972 filenames: vec![],
3973 grammar: "cpp".to_string(),
3974 comment_prefix: Some("//".to_string()),
3975 auto_indent: true,
3976 auto_close: None,
3977 auto_surround: None,
3978 textmate_grammar: None,
3979 show_whitespace_tabs: true,
3980 line_wrap: None,
3981 wrap_column: None,
3982 page_view: None,
3983 page_width: None,
3984 use_tabs: None,
3985 tab_size: None,
3986 formatter: Some(FormatterConfig {
3987 command: "clang-format".to_string(),
3988 args: vec![],
3989 stdin: true,
3990 timeout_ms: 10000,
3991 }),
3992 format_on_save: false,
3993 on_save: vec![],
3994 word_characters: None,
3995 indent: None,
3996 },
3997 );
3998
3999 languages.insert(
4000 "csharp".to_string(),
4001 LanguageConfig {
4002 extensions: vec!["cs".to_string()],
4003 filenames: vec![],
4004 grammar: "C#".to_string(),
4005 comment_prefix: Some("//".to_string()),
4006 auto_indent: true,
4007 auto_close: None,
4008 auto_surround: None,
4009 textmate_grammar: None,
4010 show_whitespace_tabs: true,
4011 line_wrap: None,
4012 wrap_column: None,
4013 page_view: None,
4014 page_width: None,
4015 use_tabs: None,
4016 tab_size: None,
4017 formatter: None,
4018 format_on_save: false,
4019 on_save: vec![],
4020 word_characters: None,
4021 indent: None,
4022 },
4023 );
4024
4025 languages.insert(
4026 "bash".to_string(),
4027 LanguageConfig {
4028 extensions: vec!["sh".to_string(), "bash".to_string()],
4029 filenames: vec![
4030 ".bash_aliases".to_string(),
4031 ".bash_logout".to_string(),
4032 ".bash_profile".to_string(),
4033 ".bashrc".to_string(),
4034 ".env".to_string(),
4035 ".profile".to_string(),
4036 ".zlogin".to_string(),
4037 ".zlogout".to_string(),
4038 ".zprofile".to_string(),
4039 ".zshenv".to_string(),
4040 ".zshrc".to_string(),
4041 "PKGBUILD".to_string(),
4043 "APKBUILD".to_string(),
4044 ],
4045 grammar: "bash".to_string(),
4046 comment_prefix: Some("#".to_string()),
4047 auto_indent: true,
4048 auto_close: None,
4049 auto_surround: None,
4050 textmate_grammar: None,
4051 show_whitespace_tabs: true,
4052 line_wrap: None,
4053 wrap_column: None,
4054 page_view: None,
4055 page_width: None,
4056 use_tabs: None,
4057 tab_size: None,
4058 formatter: None,
4059 format_on_save: false,
4060 on_save: vec![],
4061 word_characters: None,
4062 indent: None,
4063 },
4064 );
4065
4066 languages.insert(
4067 "fish".to_string(),
4068 LanguageConfig {
4069 extensions: vec!["fish".to_string()],
4070 filenames: vec![],
4071 grammar: "fish".to_string(),
4072 comment_prefix: Some("#".to_string()),
4073 auto_indent: true,
4074 auto_close: None,
4075 auto_surround: None,
4076 textmate_grammar: None,
4077 show_whitespace_tabs: true,
4078 line_wrap: None,
4079 wrap_column: None,
4080 page_view: None,
4081 page_width: None,
4082 use_tabs: None,
4083 tab_size: None,
4084 formatter: None,
4085 format_on_save: false,
4086 on_save: vec![],
4087 word_characters: None,
4088 indent: None,
4089 },
4090 );
4091
4092 languages.insert(
4093 "makefile".to_string(),
4094 LanguageConfig {
4095 extensions: vec!["mk".to_string()],
4096 filenames: vec![
4097 "Makefile".to_string(),
4098 "makefile".to_string(),
4099 "GNUmakefile".to_string(),
4100 ],
4101 grammar: "Makefile".to_string(),
4102 comment_prefix: Some("#".to_string()),
4103 auto_indent: false,
4104 auto_close: None,
4105 auto_surround: None,
4106 textmate_grammar: None,
4107 show_whitespace_tabs: true,
4108 line_wrap: None,
4109 wrap_column: None,
4110 page_view: None,
4111 page_width: None,
4112 use_tabs: Some(true), tab_size: Some(8), formatter: None,
4115 format_on_save: false,
4116 on_save: vec![],
4117 word_characters: None,
4118 indent: None,
4119 },
4120 );
4121
4122 languages.insert(
4123 "dockerfile".to_string(),
4124 LanguageConfig {
4125 extensions: vec!["dockerfile".to_string()],
4126 filenames: vec!["Dockerfile".to_string(), "Containerfile".to_string()],
4127 grammar: "dockerfile".to_string(),
4128 comment_prefix: Some("#".to_string()),
4129 auto_indent: true,
4130 auto_close: None,
4131 auto_surround: None,
4132 textmate_grammar: None,
4133 show_whitespace_tabs: true,
4134 line_wrap: None,
4135 wrap_column: None,
4136 page_view: None,
4137 page_width: None,
4138 use_tabs: None,
4139 tab_size: None,
4140 formatter: None,
4141 format_on_save: false,
4142 on_save: vec![],
4143 word_characters: None,
4144 indent: None,
4145 },
4146 );
4147
4148 languages.insert(
4149 "json".to_string(),
4150 LanguageConfig {
4151 extensions: vec!["json".to_string()],
4152 filenames: vec![],
4153 grammar: "json".to_string(),
4154 comment_prefix: None,
4155 auto_indent: true,
4156 auto_close: None,
4157 auto_surround: None,
4158 textmate_grammar: None,
4159 show_whitespace_tabs: true,
4160 line_wrap: None,
4161 wrap_column: None,
4162 page_view: None,
4163 page_width: None,
4164 use_tabs: None,
4165 tab_size: None,
4166 formatter: Some(FormatterConfig {
4167 command: "prettier".to_string(),
4168 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
4169 stdin: true,
4170 timeout_ms: 10000,
4171 }),
4172 format_on_save: false,
4173 on_save: vec![],
4174 word_characters: None,
4175 indent: None,
4176 },
4177 );
4178
4179 languages.insert(
4186 "jsonc".to_string(),
4187 LanguageConfig {
4188 extensions: vec!["jsonc".to_string()],
4189 filenames: vec![
4190 "devcontainer.json".to_string(),
4191 ".devcontainer.json".to_string(),
4192 "tsconfig.json".to_string(),
4193 "tsconfig.*.json".to_string(),
4194 "jsconfig.json".to_string(),
4195 "jsconfig.*.json".to_string(),
4196 ".eslintrc.json".to_string(),
4197 ".babelrc".to_string(),
4198 ".babelrc.json".to_string(),
4199 ".swcrc".to_string(),
4200 ".jshintrc".to_string(),
4201 ".hintrc".to_string(),
4202 "settings.json".to_string(),
4203 "keybindings.json".to_string(),
4204 "tasks.json".to_string(),
4205 "launch.json".to_string(),
4206 "extensions.json".to_string(),
4207 "argv.json".to_string(),
4208 ],
4209 grammar: "jsonc".to_string(),
4210 comment_prefix: Some("//".to_string()),
4211 auto_indent: true,
4212 auto_close: None,
4213 auto_surround: None,
4214 textmate_grammar: None,
4215 show_whitespace_tabs: true,
4216 line_wrap: None,
4217 wrap_column: None,
4218 page_view: None,
4219 page_width: None,
4220 use_tabs: None,
4221 tab_size: None,
4222 formatter: Some(FormatterConfig {
4223 command: "prettier".to_string(),
4224 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
4225 stdin: true,
4226 timeout_ms: 10000,
4227 }),
4228 format_on_save: false,
4229 on_save: vec![],
4230 word_characters: None,
4231 indent: None,
4232 },
4233 );
4234
4235 languages.insert(
4236 "toml".to_string(),
4237 LanguageConfig {
4238 extensions: vec!["toml".to_string()],
4239 filenames: vec!["Cargo.lock".to_string()],
4240 grammar: "toml".to_string(),
4241 comment_prefix: Some("#".to_string()),
4242 auto_indent: true,
4243 auto_close: None,
4244 auto_surround: None,
4245 textmate_grammar: None,
4246 show_whitespace_tabs: true,
4247 line_wrap: None,
4248 wrap_column: None,
4249 page_view: None,
4250 page_width: None,
4251 use_tabs: None,
4252 tab_size: None,
4253 formatter: None,
4254 format_on_save: false,
4255 on_save: vec![],
4256 word_characters: None,
4257 indent: None,
4258 },
4259 );
4260
4261 languages.insert(
4262 "yaml".to_string(),
4263 LanguageConfig {
4264 extensions: vec!["yml".to_string(), "yaml".to_string()],
4265 filenames: vec![],
4266 grammar: "yaml".to_string(),
4267 comment_prefix: Some("#".to_string()),
4268 auto_indent: true,
4269 auto_close: None,
4270 auto_surround: None,
4271 textmate_grammar: None,
4272 show_whitespace_tabs: true,
4273 line_wrap: None,
4274 wrap_column: None,
4275 page_view: None,
4276 page_width: None,
4277 use_tabs: None,
4278 tab_size: None,
4279 formatter: Some(FormatterConfig {
4280 command: "prettier".to_string(),
4281 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
4282 stdin: true,
4283 timeout_ms: 10000,
4284 }),
4285 format_on_save: false,
4286 on_save: vec![],
4287 word_characters: None,
4288 indent: None,
4289 },
4290 );
4291
4292 languages.insert(
4293 "markdown".to_string(),
4294 LanguageConfig {
4295 extensions: vec!["md".to_string(), "markdown".to_string()],
4296 filenames: vec!["README".to_string()],
4297 grammar: "markdown".to_string(),
4298 comment_prefix: None,
4299 auto_indent: false,
4300 auto_close: None,
4301 auto_surround: None,
4302 textmate_grammar: None,
4303 show_whitespace_tabs: true,
4304 line_wrap: None,
4305 wrap_column: None,
4306 page_view: None,
4307 page_width: None,
4308 use_tabs: None,
4309 tab_size: None,
4310 formatter: None,
4311 format_on_save: false,
4312 on_save: vec![],
4313 word_characters: None,
4314 indent: None,
4315 },
4316 );
4317
4318 languages.insert(
4320 "go".to_string(),
4321 LanguageConfig {
4322 extensions: vec!["go".to_string()],
4323 filenames: vec![],
4324 grammar: "go".to_string(),
4325 comment_prefix: Some("//".to_string()),
4326 auto_indent: true,
4327 auto_close: None,
4328 auto_surround: None,
4329 textmate_grammar: None,
4330 show_whitespace_tabs: false,
4331 line_wrap: None,
4332 wrap_column: None,
4333 page_view: None,
4334 page_width: None,
4335 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
4338 command: "gofmt".to_string(),
4339 args: vec![],
4340 stdin: true,
4341 timeout_ms: 10000,
4342 }),
4343 format_on_save: false,
4344 on_save: vec![],
4345 word_characters: None,
4346 indent: None,
4347 },
4348 );
4349
4350 languages.insert(
4351 "odin".to_string(),
4352 LanguageConfig {
4353 extensions: vec!["odin".to_string()],
4354 filenames: vec![],
4355 grammar: "odin".to_string(),
4356 comment_prefix: Some("//".to_string()),
4357 auto_indent: true,
4358 auto_close: None,
4359 auto_surround: None,
4360 textmate_grammar: None,
4361 show_whitespace_tabs: false,
4362 line_wrap: None,
4363 wrap_column: None,
4364 page_view: None,
4365 page_width: None,
4366 use_tabs: Some(true),
4367 tab_size: Some(8),
4368 formatter: None,
4369 format_on_save: false,
4370 on_save: vec![],
4371 word_characters: None,
4372 indent: None,
4373 },
4374 );
4375
4376 languages.insert(
4377 "zig".to_string(),
4378 LanguageConfig {
4379 extensions: vec!["zig".to_string(), "zon".to_string()],
4380 filenames: vec![],
4381 grammar: "zig".to_string(),
4382 comment_prefix: Some("//".to_string()),
4383 auto_indent: true,
4384 auto_close: None,
4385 auto_surround: None,
4386 textmate_grammar: None,
4387 show_whitespace_tabs: true,
4388 line_wrap: None,
4389 wrap_column: None,
4390 page_view: None,
4391 page_width: None,
4392 use_tabs: None,
4393 tab_size: None,
4394 formatter: None,
4395 format_on_save: false,
4396 on_save: vec![],
4397 word_characters: None,
4398 indent: None,
4399 },
4400 );
4401
4402 languages.insert(
4403 "c3".to_string(),
4404 LanguageConfig {
4405 extensions: vec!["c3".to_string(), "c3i".to_string(), "c3t".to_string()],
4406 filenames: vec![],
4407 grammar: "c3".to_string(),
4408 comment_prefix: Some("//".to_string()),
4409 auto_indent: true,
4410 auto_close: None,
4411 auto_surround: None,
4412 textmate_grammar: None,
4413 show_whitespace_tabs: true,
4414 line_wrap: None,
4415 wrap_column: None,
4416 page_view: None,
4417 page_width: None,
4418 use_tabs: None,
4419 tab_size: None,
4420 formatter: None,
4421 format_on_save: false,
4422 on_save: vec![],
4423 word_characters: None,
4424 indent: None,
4425 },
4426 );
4427
4428 languages.insert(
4429 "java".to_string(),
4430 LanguageConfig {
4431 extensions: vec!["java".to_string()],
4432 filenames: vec![],
4433 grammar: "java".to_string(),
4434 comment_prefix: Some("//".to_string()),
4435 auto_indent: true,
4436 auto_close: None,
4437 auto_surround: None,
4438 textmate_grammar: None,
4439 show_whitespace_tabs: true,
4440 line_wrap: None,
4441 wrap_column: None,
4442 page_view: None,
4443 page_width: None,
4444 use_tabs: None,
4445 tab_size: None,
4446 formatter: None,
4447 format_on_save: false,
4448 on_save: vec![],
4449 word_characters: None,
4450 indent: None,
4451 },
4452 );
4453
4454 languages.insert(
4455 "latex".to_string(),
4456 LanguageConfig {
4457 extensions: vec![
4458 "tex".to_string(),
4459 "latex".to_string(),
4460 "ltx".to_string(),
4461 "sty".to_string(),
4462 "cls".to_string(),
4463 "bib".to_string(),
4464 ],
4465 filenames: vec![],
4466 grammar: "latex".to_string(),
4467 comment_prefix: Some("%".to_string()),
4468 auto_indent: true,
4469 auto_close: None,
4470 auto_surround: None,
4471 textmate_grammar: None,
4472 show_whitespace_tabs: true,
4473 line_wrap: None,
4474 wrap_column: None,
4475 page_view: None,
4476 page_width: None,
4477 use_tabs: None,
4478 tab_size: None,
4479 formatter: None,
4480 format_on_save: false,
4481 on_save: vec![],
4482 word_characters: None,
4483 indent: None,
4484 },
4485 );
4486
4487 languages.insert(
4488 "templ".to_string(),
4489 LanguageConfig {
4490 extensions: vec!["templ".to_string()],
4491 filenames: vec![],
4492 grammar: "go".to_string(), comment_prefix: Some("//".to_string()),
4494 auto_indent: true,
4495 auto_close: None,
4496 auto_surround: None,
4497 textmate_grammar: None,
4498 show_whitespace_tabs: true,
4499 line_wrap: None,
4500 wrap_column: None,
4501 page_view: None,
4502 page_width: None,
4503 use_tabs: None,
4504 tab_size: None,
4505 formatter: None,
4506 format_on_save: false,
4507 on_save: vec![],
4508 word_characters: None,
4509 indent: None,
4510 },
4511 );
4512
4513 languages.insert(
4514 "smali".to_string(),
4515 LanguageConfig {
4516 extensions: vec!["smali".to_string()],
4517 filenames: vec![],
4518 grammar: "Smali".to_string(),
4519 comment_prefix: Some("#".to_string()),
4520 auto_indent: true,
4521 auto_close: None,
4522 auto_surround: None,
4523 textmate_grammar: None,
4524 show_whitespace_tabs: true,
4525 line_wrap: None,
4526 wrap_column: None,
4527 page_view: None,
4528 page_width: None,
4529 use_tabs: None,
4530 tab_size: None,
4531 formatter: None,
4532 format_on_save: false,
4533 on_save: vec![],
4534 word_characters: None,
4535 indent: None,
4536 },
4537 );
4538
4539 languages.insert(
4541 "git-rebase".to_string(),
4542 LanguageConfig {
4543 extensions: vec![],
4544 filenames: vec!["git-rebase-todo".to_string()],
4545 grammar: "Git Rebase Todo".to_string(),
4546 comment_prefix: Some("#".to_string()),
4547 auto_indent: false,
4548 auto_close: None,
4549 auto_surround: None,
4550 textmate_grammar: None,
4551 show_whitespace_tabs: true,
4552 line_wrap: None,
4553 wrap_column: None,
4554 page_view: None,
4555 page_width: None,
4556 use_tabs: None,
4557 tab_size: None,
4558 formatter: None,
4559 format_on_save: false,
4560 on_save: vec![],
4561 word_characters: None,
4562 indent: None,
4563 },
4564 );
4565
4566 languages.insert(
4567 "git-commit".to_string(),
4568 LanguageConfig {
4569 extensions: vec![],
4570 filenames: vec![
4571 "COMMIT_EDITMSG".to_string(),
4572 "MERGE_MSG".to_string(),
4573 "SQUASH_MSG".to_string(),
4574 "TAG_EDITMSG".to_string(),
4575 ],
4576 grammar: "Git Commit Message".to_string(),
4577 comment_prefix: Some("#".to_string()),
4578 auto_indent: false,
4579 auto_close: None,
4580 auto_surround: None,
4581 textmate_grammar: None,
4582 show_whitespace_tabs: true,
4583 line_wrap: None,
4584 wrap_column: None,
4585 page_view: None,
4586 page_width: None,
4587 use_tabs: None,
4588 tab_size: None,
4589 formatter: None,
4590 format_on_save: false,
4591 on_save: vec![],
4592 word_characters: None,
4593 indent: None,
4594 },
4595 );
4596
4597 languages.insert(
4598 "gitignore".to_string(),
4599 LanguageConfig {
4600 extensions: vec!["gitignore".to_string()],
4601 filenames: vec![
4602 ".gitignore".to_string(),
4603 ".dockerignore".to_string(),
4604 ".npmignore".to_string(),
4605 ".hgignore".to_string(),
4606 ],
4607 grammar: "Gitignore".to_string(),
4608 comment_prefix: Some("#".to_string()),
4609 auto_indent: false,
4610 auto_close: None,
4611 auto_surround: None,
4612 textmate_grammar: None,
4613 show_whitespace_tabs: true,
4614 line_wrap: None,
4615 wrap_column: None,
4616 page_view: None,
4617 page_width: None,
4618 use_tabs: None,
4619 tab_size: None,
4620 formatter: None,
4621 format_on_save: false,
4622 on_save: vec![],
4623 word_characters: None,
4624 indent: None,
4625 },
4626 );
4627
4628 languages.insert(
4629 "gitconfig".to_string(),
4630 LanguageConfig {
4631 extensions: vec!["gitconfig".to_string()],
4632 filenames: vec![".gitconfig".to_string(), ".gitmodules".to_string()],
4633 grammar: "Git Config".to_string(),
4634 comment_prefix: Some("#".to_string()),
4635 auto_indent: true,
4636 auto_close: None,
4637 auto_surround: None,
4638 textmate_grammar: None,
4639 show_whitespace_tabs: true,
4640 line_wrap: None,
4641 wrap_column: None,
4642 page_view: None,
4643 page_width: None,
4644 use_tabs: None,
4645 tab_size: None,
4646 formatter: None,
4647 format_on_save: false,
4648 on_save: vec![],
4649 word_characters: None,
4650 indent: None,
4651 },
4652 );
4653
4654 languages.insert(
4655 "gitattributes".to_string(),
4656 LanguageConfig {
4657 extensions: vec!["gitattributes".to_string()],
4658 filenames: vec![".gitattributes".to_string()],
4659 grammar: "Git Attributes".to_string(),
4660 comment_prefix: Some("#".to_string()),
4661 auto_indent: false,
4662 auto_close: None,
4663 auto_surround: None,
4664 textmate_grammar: None,
4665 show_whitespace_tabs: true,
4666 line_wrap: None,
4667 wrap_column: None,
4668 page_view: None,
4669 page_width: None,
4670 use_tabs: None,
4671 tab_size: None,
4672 formatter: None,
4673 format_on_save: false,
4674 on_save: vec![],
4675 word_characters: None,
4676 indent: None,
4677 },
4678 );
4679
4680 languages.insert(
4681 "typst".to_string(),
4682 LanguageConfig {
4683 extensions: vec!["typ".to_string()],
4684 filenames: vec![],
4685 grammar: "Typst".to_string(),
4686 comment_prefix: Some("//".to_string()),
4687 auto_indent: true,
4688 auto_close: None,
4689 auto_surround: None,
4690 textmate_grammar: None,
4691 show_whitespace_tabs: true,
4692 line_wrap: None,
4693 wrap_column: None,
4694 page_view: None,
4695 page_width: None,
4696 use_tabs: None,
4697 tab_size: None,
4698 formatter: None,
4699 format_on_save: false,
4700 on_save: vec![],
4701 word_characters: None,
4702 indent: None,
4703 },
4704 );
4705
4706 languages.insert(
4711 "kotlin".to_string(),
4712 LanguageConfig {
4713 extensions: vec!["kt".to_string(), "kts".to_string()],
4714 filenames: vec![],
4715 grammar: "Kotlin".to_string(),
4716 comment_prefix: Some("//".to_string()),
4717 auto_indent: true,
4718 auto_close: None,
4719 auto_surround: None,
4720 textmate_grammar: None,
4721 show_whitespace_tabs: true,
4722 line_wrap: None,
4723 wrap_column: None,
4724 page_view: None,
4725 page_width: None,
4726 use_tabs: None,
4727 tab_size: None,
4728 formatter: None,
4729 format_on_save: false,
4730 on_save: vec![],
4731 word_characters: None,
4732 indent: None,
4733 },
4734 );
4735
4736 languages.insert(
4737 "swift".to_string(),
4738 LanguageConfig {
4739 extensions: vec!["swift".to_string()],
4740 filenames: vec![],
4741 grammar: "Swift".to_string(),
4742 comment_prefix: Some("//".to_string()),
4743 auto_indent: true,
4744 auto_close: None,
4745 auto_surround: None,
4746 textmate_grammar: None,
4747 show_whitespace_tabs: true,
4748 line_wrap: None,
4749 wrap_column: None,
4750 page_view: None,
4751 page_width: None,
4752 use_tabs: None,
4753 tab_size: None,
4754 formatter: None,
4755 format_on_save: false,
4756 on_save: vec![],
4757 word_characters: None,
4758 indent: None,
4759 },
4760 );
4761
4762 languages.insert(
4763 "scala".to_string(),
4764 LanguageConfig {
4765 extensions: vec!["scala".to_string(), "sc".to_string()],
4766 filenames: vec![],
4767 grammar: "Scala".to_string(),
4768 comment_prefix: Some("//".to_string()),
4769 auto_indent: true,
4770 auto_close: None,
4771 auto_surround: None,
4772 textmate_grammar: None,
4773 show_whitespace_tabs: true,
4774 line_wrap: None,
4775 wrap_column: None,
4776 page_view: None,
4777 page_width: None,
4778 use_tabs: None,
4779 tab_size: None,
4780 formatter: None,
4781 format_on_save: false,
4782 on_save: vec![],
4783 word_characters: None,
4784 indent: None,
4785 },
4786 );
4787
4788 languages.insert(
4789 "dart".to_string(),
4790 LanguageConfig {
4791 extensions: vec!["dart".to_string()],
4792 filenames: vec![],
4793 grammar: "Dart".to_string(),
4794 comment_prefix: Some("//".to_string()),
4795 auto_indent: true,
4796 auto_close: None,
4797 auto_surround: None,
4798 textmate_grammar: None,
4799 show_whitespace_tabs: true,
4800 line_wrap: None,
4801 wrap_column: None,
4802 page_view: None,
4803 page_width: None,
4804 use_tabs: None,
4805 tab_size: None,
4806 formatter: None,
4807 format_on_save: false,
4808 on_save: vec![],
4809 word_characters: None,
4810 indent: None,
4811 },
4812 );
4813
4814 languages.insert(
4815 "elixir".to_string(),
4816 LanguageConfig {
4817 extensions: vec!["ex".to_string(), "exs".to_string()],
4818 filenames: vec![],
4819 grammar: "Elixir".to_string(),
4820 comment_prefix: Some("#".to_string()),
4821 auto_indent: true,
4822 auto_close: None,
4823 auto_surround: None,
4824 textmate_grammar: None,
4825 show_whitespace_tabs: true,
4826 line_wrap: None,
4827 wrap_column: None,
4828 page_view: None,
4829 page_width: None,
4830 use_tabs: None,
4831 tab_size: None,
4832 formatter: None,
4833 format_on_save: false,
4834 on_save: vec![],
4835 word_characters: None,
4836 indent: None,
4837 },
4838 );
4839
4840 languages.insert(
4841 "erlang".to_string(),
4842 LanguageConfig {
4843 extensions: vec!["erl".to_string(), "hrl".to_string()],
4844 filenames: vec![],
4845 grammar: "Erlang".to_string(),
4846 comment_prefix: Some("%".to_string()),
4847 auto_indent: true,
4848 auto_close: None,
4849 auto_surround: None,
4850 textmate_grammar: None,
4851 show_whitespace_tabs: true,
4852 line_wrap: None,
4853 wrap_column: None,
4854 page_view: None,
4855 page_width: None,
4856 use_tabs: None,
4857 tab_size: None,
4858 formatter: None,
4859 format_on_save: false,
4860 on_save: vec![],
4861 word_characters: None,
4862 indent: None,
4863 },
4864 );
4865
4866 languages.insert(
4867 "haskell".to_string(),
4868 LanguageConfig {
4869 extensions: vec!["hs".to_string(), "lhs".to_string()],
4870 filenames: vec![],
4871 grammar: "Haskell".to_string(),
4872 comment_prefix: Some("--".to_string()),
4873 auto_indent: true,
4874 auto_close: None,
4875 auto_surround: None,
4876 textmate_grammar: None,
4877 show_whitespace_tabs: true,
4878 line_wrap: None,
4879 wrap_column: None,
4880 page_view: None,
4881 page_width: None,
4882 use_tabs: None,
4883 tab_size: None,
4884 formatter: None,
4885 format_on_save: false,
4886 on_save: vec![],
4887 word_characters: None,
4888 indent: None,
4889 },
4890 );
4891
4892 languages.insert(
4893 "ocaml".to_string(),
4894 LanguageConfig {
4895 extensions: vec!["ml".to_string(), "mli".to_string()],
4896 filenames: vec![],
4897 grammar: "OCaml".to_string(),
4898 comment_prefix: None,
4899 auto_indent: true,
4900 auto_close: None,
4901 auto_surround: None,
4902 textmate_grammar: None,
4903 show_whitespace_tabs: true,
4904 line_wrap: None,
4905 wrap_column: None,
4906 page_view: None,
4907 page_width: None,
4908 use_tabs: None,
4909 tab_size: None,
4910 formatter: None,
4911 format_on_save: false,
4912 on_save: vec![],
4913 word_characters: None,
4914 indent: None,
4915 },
4916 );
4917
4918 languages.insert(
4919 "clojure".to_string(),
4920 LanguageConfig {
4921 extensions: vec![
4922 "clj".to_string(),
4923 "cljs".to_string(),
4924 "cljc".to_string(),
4925 "edn".to_string(),
4926 ],
4927 filenames: vec![],
4928 grammar: "Clojure".to_string(),
4929 comment_prefix: Some(";".to_string()),
4930 auto_indent: true,
4931 auto_close: None,
4932 auto_surround: None,
4933 textmate_grammar: None,
4934 show_whitespace_tabs: true,
4935 line_wrap: None,
4936 wrap_column: None,
4937 page_view: None,
4938 page_width: None,
4939 use_tabs: None,
4940 tab_size: None,
4941 formatter: None,
4942 format_on_save: false,
4943 on_save: vec![],
4944 word_characters: None,
4945 indent: None,
4946 },
4947 );
4948
4949 languages.insert(
4950 "r".to_string(),
4951 LanguageConfig {
4952 extensions: vec!["r".to_string(), "R".to_string(), "rmd".to_string()],
4953 filenames: vec![],
4954 grammar: "R".to_string(),
4955 comment_prefix: Some("#".to_string()),
4956 auto_indent: true,
4957 auto_close: None,
4958 auto_surround: None,
4959 textmate_grammar: None,
4960 show_whitespace_tabs: true,
4961 line_wrap: None,
4962 wrap_column: None,
4963 page_view: None,
4964 page_width: None,
4965 use_tabs: None,
4966 tab_size: None,
4967 formatter: None,
4968 format_on_save: false,
4969 on_save: vec![],
4970 word_characters: None,
4971 indent: None,
4972 },
4973 );
4974
4975 languages.insert(
4976 "julia".to_string(),
4977 LanguageConfig {
4978 extensions: vec!["jl".to_string()],
4979 filenames: vec![],
4980 grammar: "Julia".to_string(),
4981 comment_prefix: Some("#".to_string()),
4982 auto_indent: true,
4983 auto_close: None,
4984 auto_surround: None,
4985 textmate_grammar: None,
4986 show_whitespace_tabs: true,
4987 line_wrap: None,
4988 wrap_column: None,
4989 page_view: None,
4990 page_width: None,
4991 use_tabs: None,
4992 tab_size: None,
4993 formatter: None,
4994 format_on_save: false,
4995 on_save: vec![],
4996 word_characters: None,
4997 indent: None,
4998 },
4999 );
5000
5001 languages.insert(
5002 "perl".to_string(),
5003 LanguageConfig {
5004 extensions: vec!["pl".to_string(), "pm".to_string(), "t".to_string()],
5005 filenames: vec![],
5006 grammar: "Perl".to_string(),
5007 comment_prefix: Some("#".to_string()),
5008 auto_indent: true,
5009 auto_close: None,
5010 auto_surround: None,
5011 textmate_grammar: None,
5012 show_whitespace_tabs: true,
5013 line_wrap: None,
5014 wrap_column: None,
5015 page_view: None,
5016 page_width: None,
5017 use_tabs: None,
5018 tab_size: None,
5019 formatter: None,
5020 format_on_save: false,
5021 on_save: vec![],
5022 word_characters: None,
5023 indent: None,
5024 },
5025 );
5026
5027 languages.insert(
5028 "nim".to_string(),
5029 LanguageConfig {
5030 extensions: vec!["nim".to_string(), "nims".to_string(), "nimble".to_string()],
5031 filenames: vec![],
5032 grammar: "Nim".to_string(),
5033 comment_prefix: Some("#".to_string()),
5034 auto_indent: true,
5035 auto_close: None,
5036 auto_surround: None,
5037 textmate_grammar: None,
5038 show_whitespace_tabs: true,
5039 line_wrap: None,
5040 wrap_column: None,
5041 page_view: None,
5042 page_width: None,
5043 use_tabs: None,
5044 tab_size: None,
5045 formatter: None,
5046 format_on_save: false,
5047 on_save: vec![],
5048 word_characters: None,
5049 indent: None,
5050 },
5051 );
5052
5053 languages.insert(
5054 "gleam".to_string(),
5055 LanguageConfig {
5056 extensions: vec!["gleam".to_string()],
5057 filenames: vec![],
5058 grammar: "Gleam".to_string(),
5059 comment_prefix: Some("//".to_string()),
5060 auto_indent: true,
5061 auto_close: None,
5062 auto_surround: None,
5063 textmate_grammar: None,
5064 show_whitespace_tabs: true,
5065 line_wrap: None,
5066 wrap_column: None,
5067 page_view: None,
5068 page_width: None,
5069 use_tabs: None,
5070 tab_size: None,
5071 formatter: None,
5072 format_on_save: false,
5073 on_save: vec![],
5074 word_characters: None,
5075 indent: None,
5076 },
5077 );
5078
5079 languages.insert(
5080 "racket".to_string(),
5081 LanguageConfig {
5082 extensions: vec![
5083 "rkt".to_string(),
5084 "rktd".to_string(),
5085 "rktl".to_string(),
5086 "scrbl".to_string(),
5087 ],
5088 filenames: vec![],
5089 grammar: "Racket".to_string(),
5090 comment_prefix: Some(";".to_string()),
5091 auto_indent: true,
5092 auto_close: None,
5093 auto_surround: None,
5094 textmate_grammar: None,
5095 show_whitespace_tabs: true,
5096 line_wrap: None,
5097 wrap_column: None,
5098 page_view: None,
5099 page_width: None,
5100 use_tabs: None,
5101 tab_size: None,
5102 formatter: None,
5103 format_on_save: false,
5104 on_save: vec![],
5105 word_characters: None,
5106 indent: None,
5107 },
5108 );
5109
5110 languages.insert(
5111 "fsharp".to_string(),
5112 LanguageConfig {
5113 extensions: vec!["fs".to_string(), "fsi".to_string(), "fsx".to_string()],
5114 filenames: vec![],
5115 grammar: "FSharp".to_string(),
5116 comment_prefix: Some("//".to_string()),
5117 auto_indent: true,
5118 auto_close: None,
5119 auto_surround: None,
5120 textmate_grammar: None,
5121 show_whitespace_tabs: true,
5122 line_wrap: None,
5123 wrap_column: None,
5124 page_view: None,
5125 page_width: None,
5126 use_tabs: None,
5127 tab_size: None,
5128 formatter: None,
5129 format_on_save: false,
5130 on_save: vec![],
5131 word_characters: None,
5132 indent: None,
5133 },
5134 );
5135
5136 languages.insert(
5137 "nix".to_string(),
5138 LanguageConfig {
5139 extensions: vec!["nix".to_string()],
5140 filenames: vec![],
5141 grammar: "Nix".to_string(),
5142 comment_prefix: Some("#".to_string()),
5143 auto_indent: true,
5144 auto_close: None,
5145 auto_surround: None,
5146 textmate_grammar: None,
5147 show_whitespace_tabs: true,
5148 line_wrap: None,
5149 wrap_column: None,
5150 page_view: None,
5151 page_width: None,
5152 use_tabs: None,
5153 tab_size: None,
5154 formatter: None,
5155 format_on_save: false,
5156 on_save: vec![],
5157 word_characters: None,
5158 indent: None,
5159 },
5160 );
5161
5162 languages.insert(
5163 "nushell".to_string(),
5164 LanguageConfig {
5165 extensions: vec!["nu".to_string()],
5166 filenames: vec![],
5167 grammar: "Nushell".to_string(),
5168 comment_prefix: Some("#".to_string()),
5169 auto_indent: true,
5170 auto_close: None,
5171 auto_surround: None,
5172 textmate_grammar: None,
5173 show_whitespace_tabs: true,
5174 line_wrap: None,
5175 wrap_column: None,
5176 page_view: None,
5177 page_width: None,
5178 use_tabs: None,
5179 tab_size: None,
5180 formatter: None,
5181 format_on_save: false,
5182 on_save: vec![],
5183 word_characters: None,
5184 indent: None,
5185 },
5186 );
5187
5188 languages.insert(
5189 "solidity".to_string(),
5190 LanguageConfig {
5191 extensions: vec!["sol".to_string()],
5192 filenames: vec![],
5193 grammar: "Solidity".to_string(),
5194 comment_prefix: Some("//".to_string()),
5195 auto_indent: true,
5196 auto_close: None,
5197 auto_surround: None,
5198 textmate_grammar: None,
5199 show_whitespace_tabs: true,
5200 line_wrap: None,
5201 wrap_column: None,
5202 page_view: None,
5203 page_width: None,
5204 use_tabs: None,
5205 tab_size: None,
5206 formatter: None,
5207 format_on_save: false,
5208 on_save: vec![],
5209 word_characters: None,
5210 indent: None,
5211 },
5212 );
5213
5214 languages.insert(
5215 "verilog".to_string(),
5216 LanguageConfig {
5217 extensions: vec!["vh".to_string(), "verilog".to_string()],
5218 filenames: vec![],
5219 grammar: "Verilog".to_string(),
5220 comment_prefix: Some("//".to_string()),
5221 auto_indent: true,
5222 auto_close: None,
5223 auto_surround: None,
5224 textmate_grammar: None,
5225 show_whitespace_tabs: true,
5226 line_wrap: None,
5227 wrap_column: None,
5228 page_view: None,
5229 page_width: None,
5230 use_tabs: None,
5231 tab_size: None,
5232 formatter: None,
5233 format_on_save: false,
5234 on_save: vec![],
5235 word_characters: None,
5236 indent: None,
5237 },
5238 );
5239
5240 languages.insert(
5241 "systemverilog".to_string(),
5242 LanguageConfig {
5243 extensions: vec![
5244 "sv".to_string(),
5245 "svh".to_string(),
5246 "svi".to_string(),
5247 "svp".to_string(),
5248 ],
5249 filenames: vec![],
5250 grammar: "SystemVerilog".to_string(),
5251 comment_prefix: Some("//".to_string()),
5252 auto_indent: true,
5253 auto_close: None,
5254 auto_surround: None,
5255 textmate_grammar: None,
5256 show_whitespace_tabs: true,
5257 line_wrap: None,
5258 wrap_column: None,
5259 page_view: None,
5260 page_width: None,
5261 use_tabs: None,
5262 tab_size: None,
5263 formatter: None,
5264 format_on_save: false,
5265 on_save: vec![],
5266 word_characters: None,
5267 indent: None,
5268 },
5269 );
5270
5271 languages.insert(
5272 "vhdl".to_string(),
5273 LanguageConfig {
5274 extensions: vec!["vhd".to_string(), "vhdl".to_string(), "vho".to_string()],
5275 filenames: vec![],
5276 grammar: "VHDL".to_string(),
5277 comment_prefix: Some("--".to_string()),
5278 auto_indent: true,
5279 auto_close: None,
5280 auto_surround: None,
5281 textmate_grammar: None,
5282 show_whitespace_tabs: true,
5283 line_wrap: None,
5284 wrap_column: None,
5285 page_view: None,
5286 page_width: None,
5287 use_tabs: None,
5288 tab_size: None,
5289 formatter: None,
5290 format_on_save: false,
5291 on_save: vec![],
5292 word_characters: None,
5293 indent: None,
5294 },
5295 );
5296
5297 languages.insert(
5301 "asm".to_string(),
5302 LanguageConfig {
5303 extensions: vec!["asm".to_string(), "nasm".to_string()],
5304 filenames: vec![],
5305 grammar: "Assembly".to_string(),
5306 comment_prefix: Some(";".to_string()),
5307 auto_indent: true,
5308 auto_close: None,
5309 auto_surround: None,
5310 textmate_grammar: None,
5311 show_whitespace_tabs: true,
5312 line_wrap: None,
5313 wrap_column: None,
5314 page_view: None,
5315 page_width: None,
5316 use_tabs: None,
5317 tab_size: None,
5318 formatter: None,
5319 format_on_save: false,
5320 on_save: vec![],
5321 word_characters: None,
5322 indent: None,
5323 },
5324 );
5325
5326 languages.insert(
5327 "gas".to_string(),
5328 LanguageConfig {
5329 extensions: vec!["s".to_string(), "S".to_string()],
5332 filenames: vec![],
5333 grammar: "Assembly".to_string(),
5334 comment_prefix: Some("#".to_string()),
5335 auto_indent: true,
5336 auto_close: None,
5337 auto_surround: None,
5338 textmate_grammar: None,
5339 show_whitespace_tabs: true,
5340 line_wrap: None,
5341 wrap_column: None,
5342 page_view: None,
5343 page_width: None,
5344 use_tabs: None,
5345 tab_size: None,
5346 formatter: None,
5347 format_on_save: false,
5348 on_save: vec![],
5349 word_characters: None,
5350 indent: None,
5351 },
5352 );
5353
5354 languages.insert(
5355 "ruby".to_string(),
5356 LanguageConfig {
5357 extensions: vec!["rb".to_string(), "rake".to_string(), "gemspec".to_string()],
5358 filenames: vec![
5359 "Gemfile".to_string(),
5360 "Rakefile".to_string(),
5361 "Guardfile".to_string(),
5362 ],
5363 grammar: "Ruby".to_string(),
5364 comment_prefix: Some("#".to_string()),
5365 auto_indent: true,
5366 auto_close: None,
5367 auto_surround: None,
5368 textmate_grammar: None,
5369 show_whitespace_tabs: true,
5370 line_wrap: None,
5371 wrap_column: None,
5372 page_view: None,
5373 page_width: None,
5374 use_tabs: None,
5375 tab_size: None,
5376 formatter: None,
5377 format_on_save: false,
5378 on_save: vec![],
5379 word_characters: None,
5380 indent: None,
5381 },
5382 );
5383
5384 languages.insert(
5385 "php".to_string(),
5386 LanguageConfig {
5387 extensions: vec!["php".to_string(), "phtml".to_string()],
5388 filenames: vec![],
5389 grammar: "PHP".to_string(),
5390 comment_prefix: Some("//".to_string()),
5391 auto_indent: true,
5392 auto_close: None,
5393 auto_surround: None,
5394 textmate_grammar: None,
5395 show_whitespace_tabs: true,
5396 line_wrap: None,
5397 wrap_column: None,
5398 page_view: None,
5399 page_width: None,
5400 use_tabs: None,
5401 tab_size: None,
5402 formatter: None,
5403 format_on_save: false,
5404 on_save: vec![],
5405 word_characters: None,
5406 indent: None,
5407 },
5408 );
5409
5410 languages.insert(
5411 "lua".to_string(),
5412 LanguageConfig {
5413 extensions: vec!["lua".to_string()],
5414 filenames: vec![],
5415 grammar: "Lua".to_string(),
5416 comment_prefix: Some("--".to_string()),
5417 auto_indent: true,
5418 auto_close: None,
5419 auto_surround: None,
5420 textmate_grammar: None,
5421 show_whitespace_tabs: true,
5422 line_wrap: None,
5423 wrap_column: None,
5424 page_view: None,
5425 page_width: None,
5426 use_tabs: None,
5427 tab_size: None,
5428 formatter: None,
5429 format_on_save: false,
5430 on_save: vec![],
5431 word_characters: None,
5432 indent: None,
5433 },
5434 );
5435
5436 languages.insert(
5437 "html".to_string(),
5438 LanguageConfig {
5439 extensions: vec!["html".to_string(), "htm".to_string()],
5440 filenames: vec![],
5441 grammar: "HTML".to_string(),
5442 comment_prefix: None,
5443 auto_indent: true,
5444 auto_close: None,
5445 auto_surround: None,
5446 textmate_grammar: None,
5447 show_whitespace_tabs: true,
5448 line_wrap: None,
5449 wrap_column: None,
5450 page_view: None,
5451 page_width: None,
5452 use_tabs: None,
5453 tab_size: None,
5454 formatter: None,
5455 format_on_save: false,
5456 on_save: vec![],
5457 word_characters: None,
5458 indent: None,
5459 },
5460 );
5461
5462 languages.insert(
5463 "css".to_string(),
5464 LanguageConfig {
5465 extensions: vec!["css".to_string()],
5466 filenames: vec![],
5467 grammar: "CSS".to_string(),
5468 comment_prefix: None,
5469 auto_indent: true,
5470 auto_close: None,
5471 auto_surround: None,
5472 textmate_grammar: None,
5473 show_whitespace_tabs: true,
5474 line_wrap: None,
5475 wrap_column: None,
5476 page_view: None,
5477 page_width: None,
5478 use_tabs: None,
5479 tab_size: None,
5480 formatter: None,
5481 format_on_save: false,
5482 on_save: vec![],
5483 word_characters: None,
5484 indent: None,
5485 },
5486 );
5487
5488 languages.insert(
5489 "sql".to_string(),
5490 LanguageConfig {
5491 extensions: vec!["sql".to_string()],
5492 filenames: vec![],
5493 grammar: "SQL".to_string(),
5494 comment_prefix: Some("--".to_string()),
5495 auto_indent: true,
5496 auto_close: None,
5497 auto_surround: None,
5498 textmate_grammar: None,
5499 show_whitespace_tabs: true,
5500 line_wrap: None,
5501 wrap_column: None,
5502 page_view: None,
5503 page_width: None,
5504 use_tabs: None,
5505 tab_size: None,
5506 formatter: None,
5507 format_on_save: false,
5508 on_save: vec![],
5509 word_characters: None,
5510 indent: None,
5511 },
5512 );
5513
5514 languages.insert(
5515 "graphql".to_string(),
5516 LanguageConfig {
5517 extensions: vec!["graphql".to_string(), "gql".to_string()],
5518 filenames: vec![],
5519 grammar: "GraphQL".to_string(),
5520 comment_prefix: Some("#".to_string()),
5521 auto_indent: true,
5522 auto_close: None,
5523 auto_surround: None,
5524 textmate_grammar: None,
5525 show_whitespace_tabs: true,
5526 line_wrap: None,
5527 wrap_column: None,
5528 page_view: None,
5529 page_width: None,
5530 use_tabs: None,
5531 tab_size: None,
5532 formatter: None,
5533 format_on_save: false,
5534 on_save: vec![],
5535 word_characters: None,
5536 indent: None,
5537 },
5538 );
5539
5540 languages.insert(
5541 "protobuf".to_string(),
5542 LanguageConfig {
5543 extensions: vec!["proto".to_string()],
5544 filenames: vec![],
5545 grammar: "Protocol Buffers".to_string(),
5546 comment_prefix: Some("//".to_string()),
5547 auto_indent: true,
5548 auto_close: None,
5549 auto_surround: None,
5550 textmate_grammar: None,
5551 show_whitespace_tabs: true,
5552 line_wrap: None,
5553 wrap_column: None,
5554 page_view: None,
5555 page_width: None,
5556 use_tabs: None,
5557 tab_size: None,
5558 formatter: None,
5559 format_on_save: false,
5560 on_save: vec![],
5561 word_characters: None,
5562 indent: None,
5563 },
5564 );
5565
5566 languages.insert(
5567 "cmake".to_string(),
5568 LanguageConfig {
5569 extensions: vec!["cmake".to_string()],
5570 filenames: vec!["CMakeLists.txt".to_string()],
5571 grammar: "CMake".to_string(),
5572 comment_prefix: Some("#".to_string()),
5573 auto_indent: true,
5574 auto_close: None,
5575 auto_surround: None,
5576 textmate_grammar: None,
5577 show_whitespace_tabs: true,
5578 line_wrap: None,
5579 wrap_column: None,
5580 page_view: None,
5581 page_width: None,
5582 use_tabs: None,
5583 tab_size: None,
5584 formatter: None,
5585 format_on_save: false,
5586 on_save: vec![],
5587 word_characters: None,
5588 indent: None,
5589 },
5590 );
5591
5592 languages.insert(
5593 "terraform".to_string(),
5594 LanguageConfig {
5595 extensions: vec!["tf".to_string(), "tfvars".to_string(), "hcl".to_string()],
5596 filenames: vec![],
5597 grammar: "HCL".to_string(),
5598 comment_prefix: Some("#".to_string()),
5599 auto_indent: true,
5600 auto_close: None,
5601 auto_surround: None,
5602 textmate_grammar: None,
5603 show_whitespace_tabs: true,
5604 line_wrap: None,
5605 wrap_column: None,
5606 page_view: None,
5607 page_width: None,
5608 use_tabs: None,
5609 tab_size: None,
5610 formatter: None,
5611 format_on_save: false,
5612 on_save: vec![],
5613 word_characters: None,
5614 indent: None,
5615 },
5616 );
5617
5618 languages.insert(
5619 "vue".to_string(),
5620 LanguageConfig {
5621 extensions: vec!["vue".to_string()],
5622 filenames: vec![],
5623 grammar: "Vue".to_string(),
5624 comment_prefix: None,
5625 auto_indent: true,
5626 auto_close: None,
5627 auto_surround: None,
5628 textmate_grammar: None,
5629 show_whitespace_tabs: true,
5630 line_wrap: None,
5631 wrap_column: None,
5632 page_view: None,
5633 page_width: None,
5634 use_tabs: None,
5635 tab_size: None,
5636 formatter: None,
5637 format_on_save: false,
5638 on_save: vec![],
5639 word_characters: None,
5640 indent: None,
5641 },
5642 );
5643
5644 languages.insert(
5645 "svelte".to_string(),
5646 LanguageConfig {
5647 extensions: vec!["svelte".to_string()],
5648 filenames: vec![],
5649 grammar: "Svelte".to_string(),
5650 comment_prefix: None,
5651 auto_indent: true,
5652 auto_close: None,
5653 auto_surround: None,
5654 textmate_grammar: None,
5655 show_whitespace_tabs: true,
5656 line_wrap: None,
5657 wrap_column: None,
5658 page_view: None,
5659 page_width: None,
5660 use_tabs: None,
5661 tab_size: None,
5662 formatter: None,
5663 format_on_save: false,
5664 on_save: vec![],
5665 word_characters: None,
5666 indent: None,
5667 },
5668 );
5669
5670 languages.insert(
5671 "astro".to_string(),
5672 LanguageConfig {
5673 extensions: vec!["astro".to_string()],
5674 filenames: vec![],
5675 grammar: "Astro".to_string(),
5676 comment_prefix: None,
5677 auto_indent: true,
5678 auto_close: None,
5679 auto_surround: None,
5680 textmate_grammar: None,
5681 show_whitespace_tabs: true,
5682 line_wrap: None,
5683 wrap_column: None,
5684 page_view: None,
5685 page_width: None,
5686 use_tabs: None,
5687 tab_size: None,
5688 formatter: None,
5689 format_on_save: false,
5690 on_save: vec![],
5691 word_characters: None,
5692 indent: None,
5693 },
5694 );
5695
5696 languages.insert(
5699 "scss".to_string(),
5700 LanguageConfig {
5701 extensions: vec!["scss".to_string()],
5702 filenames: vec![],
5703 grammar: "SCSS".to_string(),
5704 comment_prefix: Some("//".to_string()),
5705 auto_indent: true,
5706 auto_close: None,
5707 auto_surround: None,
5708 textmate_grammar: None,
5709 show_whitespace_tabs: true,
5710 line_wrap: None,
5711 wrap_column: None,
5712 page_view: None,
5713 page_width: None,
5714 use_tabs: None,
5715 tab_size: None,
5716 formatter: None,
5717 format_on_save: false,
5718 on_save: vec![],
5719 word_characters: None,
5720 indent: None,
5721 },
5722 );
5723
5724 languages.insert(
5725 "less".to_string(),
5726 LanguageConfig {
5727 extensions: vec!["less".to_string()],
5728 filenames: vec![],
5729 grammar: "LESS".to_string(),
5730 comment_prefix: Some("//".to_string()),
5731 auto_indent: true,
5732 auto_close: None,
5733 auto_surround: None,
5734 textmate_grammar: None,
5735 show_whitespace_tabs: true,
5736 line_wrap: None,
5737 wrap_column: None,
5738 page_view: None,
5739 page_width: None,
5740 use_tabs: None,
5741 tab_size: None,
5742 formatter: None,
5743 format_on_save: false,
5744 on_save: vec![],
5745 word_characters: None,
5746 indent: None,
5747 },
5748 );
5749
5750 languages.insert(
5751 "powershell".to_string(),
5752 LanguageConfig {
5753 extensions: vec!["ps1".to_string(), "psm1".to_string(), "psd1".to_string()],
5754 filenames: vec![],
5755 grammar: "PowerShell".to_string(),
5756 comment_prefix: Some("#".to_string()),
5757 auto_indent: true,
5758 auto_close: None,
5759 auto_surround: None,
5760 textmate_grammar: None,
5761 show_whitespace_tabs: true,
5762 line_wrap: None,
5763 wrap_column: None,
5764 page_view: None,
5765 page_width: None,
5766 use_tabs: None,
5767 tab_size: None,
5768 formatter: None,
5769 format_on_save: false,
5770 on_save: vec![],
5771 word_characters: None,
5772 indent: None,
5773 },
5774 );
5775
5776 languages.insert(
5777 "kdl".to_string(),
5778 LanguageConfig {
5779 extensions: vec!["kdl".to_string()],
5780 filenames: vec![],
5781 grammar: "KDL".to_string(),
5782 comment_prefix: Some("//".to_string()),
5783 auto_indent: true,
5784 auto_close: None,
5785 auto_surround: None,
5786 textmate_grammar: None,
5787 show_whitespace_tabs: true,
5788 line_wrap: None,
5789 wrap_column: None,
5790 page_view: None,
5791 page_width: None,
5792 use_tabs: None,
5793 tab_size: None,
5794 formatter: None,
5795 format_on_save: false,
5796 on_save: vec![],
5797 word_characters: None,
5798 indent: None,
5799 },
5800 );
5801
5802 languages.insert(
5803 "starlark".to_string(),
5804 LanguageConfig {
5805 extensions: vec!["bzl".to_string(), "star".to_string()],
5806 filenames: vec!["BUILD".to_string(), "WORKSPACE".to_string()],
5807 grammar: "Starlark".to_string(),
5808 comment_prefix: Some("#".to_string()),
5809 auto_indent: true,
5810 auto_close: None,
5811 auto_surround: None,
5812 textmate_grammar: None,
5813 show_whitespace_tabs: true,
5814 line_wrap: None,
5815 wrap_column: None,
5816 page_view: None,
5817 page_width: None,
5818 use_tabs: None,
5819 tab_size: None,
5820 formatter: None,
5821 format_on_save: false,
5822 on_save: vec![],
5823 word_characters: None,
5824 indent: None,
5825 },
5826 );
5827
5828 languages.insert(
5829 "justfile".to_string(),
5830 LanguageConfig {
5831 extensions: vec![],
5832 filenames: vec![
5833 "justfile".to_string(),
5834 "Justfile".to_string(),
5835 ".justfile".to_string(),
5836 ],
5837 grammar: "Justfile".to_string(),
5838 comment_prefix: Some("#".to_string()),
5839 auto_indent: true,
5840 auto_close: None,
5841 auto_surround: None,
5842 textmate_grammar: None,
5843 show_whitespace_tabs: true,
5844 line_wrap: None,
5845 wrap_column: None,
5846 page_view: None,
5847 page_width: None,
5848 use_tabs: Some(true),
5849 tab_size: None,
5850 formatter: None,
5851 format_on_save: false,
5852 on_save: vec![],
5853 word_characters: None,
5854 indent: None,
5855 },
5856 );
5857
5858 languages.insert(
5859 "earthfile".to_string(),
5860 LanguageConfig {
5861 extensions: vec!["earth".to_string()],
5862 filenames: vec!["Earthfile".to_string()],
5863 grammar: "Earthfile".to_string(),
5864 comment_prefix: Some("#".to_string()),
5865 auto_indent: true,
5866 auto_close: None,
5867 auto_surround: None,
5868 textmate_grammar: None,
5869 show_whitespace_tabs: true,
5870 line_wrap: None,
5871 wrap_column: None,
5872 page_view: None,
5873 page_width: None,
5874 use_tabs: None,
5875 tab_size: None,
5876 formatter: None,
5877 format_on_save: false,
5878 on_save: vec![],
5879 word_characters: None,
5880 indent: None,
5881 },
5882 );
5883
5884 languages.insert(
5885 "gomod".to_string(),
5886 LanguageConfig {
5887 extensions: vec![],
5888 filenames: vec!["go.mod".to_string(), "go.sum".to_string()],
5889 grammar: "Go Module".to_string(),
5890 comment_prefix: Some("//".to_string()),
5891 auto_indent: true,
5892 auto_close: None,
5893 auto_surround: None,
5894 textmate_grammar: None,
5895 show_whitespace_tabs: true,
5896 line_wrap: None,
5897 wrap_column: None,
5898 page_view: None,
5899 page_width: None,
5900 use_tabs: Some(true),
5901 tab_size: None,
5902 formatter: None,
5903 format_on_save: false,
5904 on_save: vec![],
5905 word_characters: None,
5906 indent: None,
5907 },
5908 );
5909
5910 languages.insert(
5911 "vlang".to_string(),
5912 LanguageConfig {
5913 extensions: vec!["v".to_string(), "vv".to_string()],
5914 filenames: vec![],
5915 grammar: "V".to_string(),
5916 comment_prefix: Some("//".to_string()),
5917 auto_indent: true,
5918 auto_close: None,
5919 auto_surround: None,
5920 textmate_grammar: None,
5921 show_whitespace_tabs: true,
5922 line_wrap: None,
5923 wrap_column: None,
5924 page_view: None,
5925 page_width: None,
5926 use_tabs: None,
5927 tab_size: None,
5928 formatter: None,
5929 format_on_save: false,
5930 on_save: vec![],
5931 word_characters: None,
5932 indent: None,
5933 },
5934 );
5935
5936 languages.insert(
5937 "ini".to_string(),
5938 LanguageConfig {
5939 extensions: vec!["ini".to_string(), "cfg".to_string()],
5940 filenames: vec![],
5941 grammar: "INI".to_string(),
5942 comment_prefix: Some(";".to_string()),
5943 auto_indent: false,
5944 auto_close: None,
5945 auto_surround: None,
5946 textmate_grammar: None,
5947 show_whitespace_tabs: true,
5948 line_wrap: None,
5949 wrap_column: None,
5950 page_view: None,
5951 page_width: None,
5952 use_tabs: None,
5953 tab_size: None,
5954 formatter: None,
5955 format_on_save: false,
5956 on_save: vec![],
5957 word_characters: None,
5958 indent: None,
5959 },
5960 );
5961
5962 languages.insert(
5963 "hyprlang".to_string(),
5964 LanguageConfig {
5965 extensions: vec!["hl".to_string()],
5966 filenames: vec!["hyprland.conf".to_string()],
5967 grammar: "Hyprlang".to_string(),
5968 comment_prefix: Some("#".to_string()),
5969 auto_indent: true,
5970 auto_close: None,
5971 auto_surround: None,
5972 textmate_grammar: None,
5973 show_whitespace_tabs: true,
5974 line_wrap: None,
5975 wrap_column: None,
5976 page_view: None,
5977 page_width: None,
5978 use_tabs: None,
5979 tab_size: None,
5980 formatter: None,
5981 format_on_save: false,
5982 on_save: vec![],
5983 word_characters: None,
5984 indent: None,
5985 },
5986 );
5987
5988 languages
5989 }
5990
5991 #[cfg(feature = "runtime")]
5993 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
5994 let mut lsp = HashMap::new();
5995
5996 let ra_log_path = crate::services::log_dirs::lsp_log_path("rust-analyzer")
5999 .to_string_lossy()
6000 .to_string();
6001
6002 Self::populate_lsp_config(&mut lsp, ra_log_path);
6003 lsp
6004 }
6005
6006 #[cfg(not(feature = "runtime"))]
6008 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
6009 HashMap::new()
6011 }
6012
6013 #[cfg(feature = "runtime")]
6015 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
6016 let mut universal = HashMap::new();
6017
6018 universal.insert(
6031 "quicklsp".to_string(),
6032 LspLanguageConfig::Multi(vec![LspServerConfig {
6033 command: "quicklsp".to_string(),
6034 args: vec![],
6035 enabled: false,
6036 auto_start: false,
6037 process_limits: ProcessLimits::default(),
6038 initialization_options: None,
6039 env: Default::default(),
6040 language_id_overrides: Default::default(),
6041 name: Some("QuickLSP".to_string()),
6042 only_features: None,
6043 except_features: None,
6044 root_markers: vec![
6045 "Cargo.toml".to_string(),
6046 "package.json".to_string(),
6047 "go.mod".to_string(),
6048 "pyproject.toml".to_string(),
6049 "requirements.txt".to_string(),
6050 ".git".to_string(),
6051 ],
6052 }]),
6053 );
6054
6055 universal
6056 }
6057
6058 #[cfg(not(feature = "runtime"))]
6060 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
6061 HashMap::new()
6062 }
6063
6064 #[cfg(feature = "runtime")]
6065 fn populate_lsp_config(lsp: &mut HashMap<String, LspLanguageConfig>, ra_log_path: String) {
6066 lsp.insert(
6070 "rust".to_string(),
6071 LspLanguageConfig::Multi(vec![LspServerConfig {
6072 command: "rust-analyzer".to_string(),
6073 args: vec!["--log-file".to_string(), ra_log_path],
6074 enabled: true,
6075 auto_start: false,
6076 process_limits: ProcessLimits::unlimited(),
6077 initialization_options: None,
6078 env: Default::default(),
6079 language_id_overrides: Default::default(),
6080 name: None,
6081 only_features: None,
6082 except_features: None,
6083 root_markers: vec![
6084 "Cargo.toml".to_string(),
6085 "rust-project.json".to_string(),
6086 ".git".to_string(),
6087 ],
6088 }]),
6089 );
6090
6091 lsp.insert(
6093 "python".to_string(),
6094 LspLanguageConfig::Multi(vec![LspServerConfig {
6095 command: "pylsp".to_string(),
6096 args: vec![],
6097 enabled: true,
6098 auto_start: false,
6099 process_limits: ProcessLimits::default(),
6100 initialization_options: None,
6101 env: Default::default(),
6102 language_id_overrides: Default::default(),
6103 name: None,
6104 only_features: None,
6105 except_features: None,
6106 root_markers: vec![
6107 "pyproject.toml".to_string(),
6108 "setup.py".to_string(),
6109 "setup.cfg".to_string(),
6110 "pyrightconfig.json".to_string(),
6111 ".git".to_string(),
6112 ],
6113 }]),
6114 );
6115
6116 lsp.insert(
6120 "gdscript".to_string(),
6121 LspLanguageConfig::Multi(vec![LspServerConfig {
6122 command: "nc".to_string(),
6123 args: vec!["127.0.0.1".to_string(), "6005".to_string()],
6124 enabled: false,
6125 auto_start: false,
6126 process_limits: ProcessLimits::default(),
6127 initialization_options: None,
6128 env: Default::default(),
6129 language_id_overrides: Default::default(),
6130 name: Some("Godot GDScript".to_string()),
6131 only_features: None,
6132 except_features: None,
6133 root_markers: vec!["project.godot".to_string(), ".git".to_string()],
6134 }]),
6135 );
6136
6137 lsp.insert(
6140 "javascript".to_string(),
6141 LspLanguageConfig::Multi(vec![LspServerConfig {
6142 command: "typescript-language-server".to_string(),
6143 args: vec!["--stdio".to_string()],
6144 enabled: true,
6145 auto_start: false,
6146 process_limits: ProcessLimits::default(),
6147 initialization_options: None,
6148 env: Default::default(),
6149 language_id_overrides: HashMap::from([(
6150 "jsx".to_string(),
6151 "javascriptreact".to_string(),
6152 )]),
6153 name: None,
6154 only_features: None,
6155 except_features: None,
6156 root_markers: vec![
6157 "tsconfig.json".to_string(),
6158 "jsconfig.json".to_string(),
6159 "package.json".to_string(),
6160 ".git".to_string(),
6161 ],
6162 }]),
6163 );
6164 lsp.insert(
6165 "typescript".to_string(),
6166 LspLanguageConfig::Multi(vec![LspServerConfig {
6167 command: "typescript-language-server".to_string(),
6168 args: vec!["--stdio".to_string()],
6169 enabled: true,
6170 auto_start: false,
6171 process_limits: ProcessLimits::default(),
6172 initialization_options: None,
6173 env: Default::default(),
6174 language_id_overrides: HashMap::from([(
6175 "tsx".to_string(),
6176 "typescriptreact".to_string(),
6177 )]),
6178 name: None,
6179 only_features: None,
6180 except_features: None,
6181 root_markers: vec![
6182 "tsconfig.json".to_string(),
6183 "jsconfig.json".to_string(),
6184 "package.json".to_string(),
6185 ".git".to_string(),
6186 ],
6187 }]),
6188 );
6189
6190 lsp.insert(
6192 "html".to_string(),
6193 LspLanguageConfig::Multi(vec![LspServerConfig {
6194 command: "vscode-html-language-server".to_string(),
6195 args: vec!["--stdio".to_string()],
6196 enabled: true,
6197 auto_start: false,
6198 process_limits: ProcessLimits::default(),
6199 initialization_options: None,
6200 env: Default::default(),
6201 language_id_overrides: Default::default(),
6202 name: None,
6203 only_features: None,
6204 except_features: None,
6205 root_markers: Default::default(),
6206 }]),
6207 );
6208
6209 lsp.insert(
6211 "css".to_string(),
6212 LspLanguageConfig::Multi(vec![LspServerConfig {
6213 command: "vscode-css-language-server".to_string(),
6214 args: vec!["--stdio".to_string()],
6215 enabled: true,
6216 auto_start: false,
6217 process_limits: ProcessLimits::default(),
6218 initialization_options: None,
6219 env: Default::default(),
6220 language_id_overrides: Default::default(),
6221 name: None,
6222 only_features: None,
6223 except_features: None,
6224 root_markers: Default::default(),
6225 }]),
6226 );
6227
6228 lsp.insert(
6230 "c".to_string(),
6231 LspLanguageConfig::Multi(vec![LspServerConfig {
6232 command: "clangd".to_string(),
6233 args: vec![],
6234 enabled: true,
6235 auto_start: false,
6236 process_limits: ProcessLimits::default(),
6237 initialization_options: None,
6238 env: Default::default(),
6239 language_id_overrides: Default::default(),
6240 name: None,
6241 only_features: None,
6242 except_features: None,
6243 root_markers: vec![
6244 "compile_commands.json".to_string(),
6245 "CMakeLists.txt".to_string(),
6246 "Makefile".to_string(),
6247 ".git".to_string(),
6248 ],
6249 }]),
6250 );
6251 lsp.insert(
6252 "cpp".to_string(),
6253 LspLanguageConfig::Multi(vec![LspServerConfig {
6254 command: "clangd".to_string(),
6255 args: vec![],
6256 enabled: true,
6257 auto_start: false,
6258 process_limits: ProcessLimits::default(),
6259 initialization_options: None,
6260 env: Default::default(),
6261 language_id_overrides: Default::default(),
6262 name: None,
6263 only_features: None,
6264 except_features: None,
6265 root_markers: vec![
6266 "compile_commands.json".to_string(),
6267 "CMakeLists.txt".to_string(),
6268 "Makefile".to_string(),
6269 ".git".to_string(),
6270 ],
6271 }]),
6272 );
6273
6274 lsp.insert(
6276 "go".to_string(),
6277 LspLanguageConfig::Multi(vec![LspServerConfig {
6278 command: "gopls".to_string(),
6279 args: vec![],
6280 enabled: true,
6281 auto_start: false,
6282 process_limits: ProcessLimits::default(),
6283 initialization_options: None,
6284 env: Default::default(),
6285 language_id_overrides: Default::default(),
6286 name: None,
6287 only_features: None,
6288 except_features: None,
6289 root_markers: vec![
6290 "go.mod".to_string(),
6291 "go.work".to_string(),
6292 ".git".to_string(),
6293 ],
6294 }]),
6295 );
6296
6297 lsp.insert(
6299 "json".to_string(),
6300 LspLanguageConfig::Multi(vec![LspServerConfig {
6301 command: "vscode-json-language-server".to_string(),
6302 args: vec!["--stdio".to_string()],
6303 enabled: true,
6304 auto_start: false,
6305 process_limits: ProcessLimits::default(),
6306 initialization_options: None,
6307 env: Default::default(),
6308 language_id_overrides: Default::default(),
6309 name: None,
6310 only_features: None,
6311 except_features: None,
6312 root_markers: Default::default(),
6313 }]),
6314 );
6315
6316 lsp.insert(
6320 "jsonc".to_string(),
6321 LspLanguageConfig::Multi(vec![LspServerConfig {
6322 command: "vscode-json-language-server".to_string(),
6323 args: vec!["--stdio".to_string()],
6324 enabled: true,
6325 auto_start: false,
6326 process_limits: ProcessLimits::default(),
6327 initialization_options: None,
6328 env: Default::default(),
6329 language_id_overrides: Default::default(),
6330 name: None,
6331 only_features: None,
6332 except_features: None,
6333 root_markers: Default::default(),
6334 }]),
6335 );
6336
6337 lsp.insert(
6339 "csharp".to_string(),
6340 LspLanguageConfig::Multi(vec![LspServerConfig {
6341 command: "csharp-ls".to_string(),
6342 args: vec![],
6343 enabled: true,
6344 auto_start: false,
6345 process_limits: ProcessLimits::default(),
6346 initialization_options: None,
6347 env: Default::default(),
6348 language_id_overrides: Default::default(),
6349 name: None,
6350 only_features: None,
6351 except_features: None,
6352 root_markers: vec![
6353 "*.csproj".to_string(),
6354 "*.sln".to_string(),
6355 ".git".to_string(),
6356 ],
6357 }]),
6358 );
6359
6360 lsp.insert(
6363 "odin".to_string(),
6364 LspLanguageConfig::Multi(vec![LspServerConfig {
6365 command: "ols".to_string(),
6366 args: vec![],
6367 enabled: true,
6368 auto_start: false,
6369 process_limits: ProcessLimits::default(),
6370 initialization_options: None,
6371 env: Default::default(),
6372 language_id_overrides: Default::default(),
6373 name: None,
6374 only_features: None,
6375 except_features: None,
6376 root_markers: Default::default(),
6377 }]),
6378 );
6379
6380 lsp.insert(
6383 "zig".to_string(),
6384 LspLanguageConfig::Multi(vec![LspServerConfig {
6385 command: "zls".to_string(),
6386 args: vec![],
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 "c3".to_string(),
6404 LspLanguageConfig::Multi(vec![LspServerConfig {
6405 command: "c3lsp".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: vec!["project.json".to_string(), ".git".to_string()],
6417 }]),
6418 );
6419
6420 lsp.insert(
6423 "java".to_string(),
6424 LspLanguageConfig::Multi(vec![LspServerConfig {
6425 command: "jdtls".to_string(),
6426 args: vec![],
6427 enabled: true,
6428 auto_start: false,
6429 process_limits: ProcessLimits::default(),
6430 initialization_options: None,
6431 env: Default::default(),
6432 language_id_overrides: Default::default(),
6433 name: None,
6434 only_features: None,
6435 except_features: None,
6436 root_markers: vec![
6437 "pom.xml".to_string(),
6438 "build.gradle".to_string(),
6439 "build.gradle.kts".to_string(),
6440 ".git".to_string(),
6441 ],
6442 }]),
6443 );
6444
6445 lsp.insert(
6448 "latex".to_string(),
6449 LspLanguageConfig::Multi(vec![LspServerConfig {
6450 command: "texlab".to_string(),
6451 args: vec![],
6452 enabled: true,
6453 auto_start: false,
6454 process_limits: ProcessLimits::default(),
6455 initialization_options: None,
6456 env: Default::default(),
6457 language_id_overrides: Default::default(),
6458 name: None,
6459 only_features: None,
6460 except_features: None,
6461 root_markers: Default::default(),
6462 }]),
6463 );
6464
6465 lsp.insert(
6468 "markdown".to_string(),
6469 LspLanguageConfig::Multi(vec![LspServerConfig {
6470 command: "marksman".to_string(),
6471 args: vec!["server".to_string()],
6472 enabled: true,
6473 auto_start: false,
6474 process_limits: ProcessLimits::default(),
6475 initialization_options: None,
6476 env: Default::default(),
6477 language_id_overrides: Default::default(),
6478 name: None,
6479 only_features: None,
6480 except_features: None,
6481 root_markers: Default::default(),
6482 }]),
6483 );
6484
6485 lsp.insert(
6488 "templ".to_string(),
6489 LspLanguageConfig::Multi(vec![LspServerConfig {
6490 command: "templ".to_string(),
6491 args: vec!["lsp".to_string()],
6492 enabled: true,
6493 auto_start: false,
6494 process_limits: ProcessLimits::default(),
6495 initialization_options: None,
6496 env: Default::default(),
6497 language_id_overrides: Default::default(),
6498 name: None,
6499 only_features: None,
6500 except_features: None,
6501 root_markers: Default::default(),
6502 }]),
6503 );
6504
6505 lsp.insert(
6508 "typst".to_string(),
6509 LspLanguageConfig::Multi(vec![LspServerConfig {
6510 command: "tinymist".to_string(),
6511 args: vec![],
6512 enabled: true,
6513 auto_start: false,
6514 process_limits: ProcessLimits::default(),
6515 initialization_options: None,
6516 env: Default::default(),
6517 language_id_overrides: Default::default(),
6518 name: None,
6519 only_features: None,
6520 except_features: None,
6521 root_markers: Default::default(),
6522 }]),
6523 );
6524
6525 lsp.insert(
6527 "bash".to_string(),
6528 LspLanguageConfig::Multi(vec![LspServerConfig {
6529 command: "bash-language-server".to_string(),
6530 args: vec!["start".to_string()],
6531 enabled: true,
6532 auto_start: false,
6533 process_limits: ProcessLimits::default(),
6534 initialization_options: None,
6535 env: Default::default(),
6536 language_id_overrides: Default::default(),
6537 name: None,
6538 only_features: None,
6539 except_features: None,
6540 root_markers: Default::default(),
6541 }]),
6542 );
6543
6544 lsp.insert(
6547 "lua".to_string(),
6548 LspLanguageConfig::Multi(vec![LspServerConfig {
6549 command: "lua-language-server".to_string(),
6550 args: vec![],
6551 enabled: true,
6552 auto_start: false,
6553 process_limits: ProcessLimits::default(),
6554 initialization_options: None,
6555 env: Default::default(),
6556 language_id_overrides: Default::default(),
6557 name: None,
6558 only_features: None,
6559 except_features: None,
6560 root_markers: vec![
6561 ".luarc.json".to_string(),
6562 ".luarc.jsonc".to_string(),
6563 ".luacheckrc".to_string(),
6564 ".stylua.toml".to_string(),
6565 ".git".to_string(),
6566 ],
6567 }]),
6568 );
6569
6570 lsp.insert(
6572 "ruby".to_string(),
6573 LspLanguageConfig::Multi(vec![LspServerConfig {
6574 command: "solargraph".to_string(),
6575 args: vec!["stdio".to_string()],
6576 enabled: true,
6577 auto_start: false,
6578 process_limits: ProcessLimits::default(),
6579 initialization_options: None,
6580 env: Default::default(),
6581 language_id_overrides: Default::default(),
6582 name: None,
6583 only_features: None,
6584 except_features: None,
6585 root_markers: vec![
6586 "Gemfile".to_string(),
6587 ".ruby-version".to_string(),
6588 ".git".to_string(),
6589 ],
6590 }]),
6591 );
6592
6593 lsp.insert(
6596 "php".to_string(),
6597 LspLanguageConfig::Multi(vec![LspServerConfig {
6598 command: "phpactor".to_string(),
6599 args: vec!["language-server".to_string()],
6600 enabled: true,
6601 auto_start: false,
6602 process_limits: ProcessLimits::default(),
6603 initialization_options: None,
6604 env: Default::default(),
6605 language_id_overrides: Default::default(),
6606 name: None,
6607 only_features: None,
6608 except_features: None,
6609 root_markers: vec!["composer.json".to_string(), ".git".to_string()],
6610 }]),
6611 );
6612
6613 lsp.insert(
6615 "yaml".to_string(),
6616 LspLanguageConfig::Multi(vec![LspServerConfig {
6617 command: "yaml-language-server".to_string(),
6618 args: vec!["--stdio".to_string()],
6619 enabled: true,
6620 auto_start: false,
6621 process_limits: ProcessLimits::default(),
6622 initialization_options: None,
6623 env: Default::default(),
6624 language_id_overrides: Default::default(),
6625 name: None,
6626 only_features: None,
6627 except_features: None,
6628 root_markers: Default::default(),
6629 }]),
6630 );
6631
6632 lsp.insert(
6635 "toml".to_string(),
6636 LspLanguageConfig::Multi(vec![LspServerConfig {
6637 command: "taplo".to_string(),
6638 args: vec!["lsp".to_string(), "stdio".to_string()],
6639 enabled: true,
6640 auto_start: false,
6641 process_limits: ProcessLimits::default(),
6642 initialization_options: None,
6643 env: Default::default(),
6644 language_id_overrides: Default::default(),
6645 name: None,
6646 only_features: None,
6647 except_features: None,
6648 root_markers: Default::default(),
6649 }]),
6650 );
6651
6652 lsp.insert(
6655 "dart".to_string(),
6656 LspLanguageConfig::Multi(vec![LspServerConfig {
6657 command: "dart".to_string(),
6658 args: vec!["language-server".to_string(), "--protocol=lsp".to_string()],
6659 enabled: true,
6660 auto_start: false,
6661 process_limits: ProcessLimits::default(),
6662 initialization_options: None,
6663 env: Default::default(),
6664 language_id_overrides: Default::default(),
6665 name: None,
6666 only_features: None,
6667 except_features: None,
6668 root_markers: vec!["pubspec.yaml".to_string(), ".git".to_string()],
6669 }]),
6670 );
6671
6672 lsp.insert(
6675 "nushell".to_string(),
6676 LspLanguageConfig::Multi(vec![LspServerConfig {
6677 command: "nu".to_string(),
6678 args: vec!["--lsp".to_string()],
6679 enabled: true,
6680 auto_start: false,
6681 process_limits: ProcessLimits::default(),
6682 initialization_options: None,
6683 env: Default::default(),
6684 language_id_overrides: Default::default(),
6685 name: None,
6686 only_features: None,
6687 except_features: None,
6688 root_markers: Default::default(),
6689 }]),
6690 );
6691
6692 lsp.insert(
6695 "solidity".to_string(),
6696 LspLanguageConfig::Multi(vec![LspServerConfig {
6697 command: "nomicfoundation-solidity-language-server".to_string(),
6698 args: vec!["--stdio".to_string()],
6699 enabled: true,
6700 auto_start: false,
6701 process_limits: ProcessLimits::default(),
6702 initialization_options: None,
6703 env: Default::default(),
6704 language_id_overrides: Default::default(),
6705 name: None,
6706 only_features: None,
6707 except_features: None,
6708 root_markers: Default::default(),
6709 }]),
6710 );
6711
6712 lsp.insert(
6717 "terraform".to_string(),
6718 LspLanguageConfig::Multi(vec![LspServerConfig {
6719 command: "terraform-ls".to_string(),
6720 args: vec!["serve".to_string()],
6721 enabled: true,
6722 auto_start: false,
6723 process_limits: ProcessLimits::default(),
6724 initialization_options: None,
6725 env: Default::default(),
6726 language_id_overrides: Default::default(),
6727 name: None,
6728 only_features: None,
6729 except_features: None,
6730 root_markers: vec![
6731 "*.tf".to_string(),
6732 ".terraform".to_string(),
6733 ".git".to_string(),
6734 ],
6735 }]),
6736 );
6737
6738 lsp.insert(
6741 "cmake".to_string(),
6742 LspLanguageConfig::Multi(vec![LspServerConfig {
6743 command: "cmake-language-server".to_string(),
6744 args: vec![],
6745 enabled: true,
6746 auto_start: false,
6747 process_limits: ProcessLimits::default(),
6748 initialization_options: None,
6749 env: Default::default(),
6750 language_id_overrides: Default::default(),
6751 name: None,
6752 only_features: None,
6753 except_features: None,
6754 root_markers: vec!["CMakeLists.txt".to_string(), ".git".to_string()],
6755 }]),
6756 );
6757
6758 lsp.insert(
6761 "protobuf".to_string(),
6762 LspLanguageConfig::Multi(vec![LspServerConfig {
6763 command: "buf".to_string(),
6764 args: vec!["beta".to_string(), "lsp".to_string()],
6765 enabled: true,
6766 auto_start: false,
6767 process_limits: ProcessLimits::default(),
6768 initialization_options: None,
6769 env: Default::default(),
6770 language_id_overrides: Default::default(),
6771 name: None,
6772 only_features: None,
6773 except_features: None,
6774 root_markers: Default::default(),
6775 }]),
6776 );
6777
6778 lsp.insert(
6781 "graphql".to_string(),
6782 LspLanguageConfig::Multi(vec![LspServerConfig {
6783 command: "graphql-lsp".to_string(),
6784 args: vec!["server".to_string(), "-m".to_string(), "stream".to_string()],
6785 enabled: true,
6786 auto_start: false,
6787 process_limits: ProcessLimits::default(),
6788 initialization_options: None,
6789 env: Default::default(),
6790 language_id_overrides: Default::default(),
6791 name: None,
6792 only_features: None,
6793 except_features: None,
6794 root_markers: Default::default(),
6795 }]),
6796 );
6797
6798 lsp.insert(
6801 "sql".to_string(),
6802 LspLanguageConfig::Multi(vec![LspServerConfig {
6803 command: "sqls".to_string(),
6804 args: vec![],
6805 enabled: true,
6806 auto_start: false,
6807 process_limits: ProcessLimits::default(),
6808 initialization_options: None,
6809 env: Default::default(),
6810 language_id_overrides: Default::default(),
6811 name: None,
6812 only_features: None,
6813 except_features: None,
6814 root_markers: Default::default(),
6815 }]),
6816 );
6817
6818 lsp.insert(
6822 "vue".to_string(),
6823 LspLanguageConfig::Multi(vec![LspServerConfig {
6824 command: "vue-language-server".to_string(),
6825 args: vec!["--stdio".to_string()],
6826 enabled: true,
6827 auto_start: false,
6828 process_limits: ProcessLimits::default(),
6829 initialization_options: None,
6830 env: Default::default(),
6831 language_id_overrides: Default::default(),
6832 name: None,
6833 only_features: None,
6834 except_features: None,
6835 root_markers: Default::default(),
6836 }]),
6837 );
6838
6839 lsp.insert(
6841 "svelte".to_string(),
6842 LspLanguageConfig::Multi(vec![LspServerConfig {
6843 command: "svelteserver".to_string(),
6844 args: vec!["--stdio".to_string()],
6845 enabled: true,
6846 auto_start: false,
6847 process_limits: ProcessLimits::default(),
6848 initialization_options: None,
6849 env: Default::default(),
6850 language_id_overrides: Default::default(),
6851 name: None,
6852 only_features: None,
6853 except_features: None,
6854 root_markers: Default::default(),
6855 }]),
6856 );
6857
6858 lsp.insert(
6860 "astro".to_string(),
6861 LspLanguageConfig::Multi(vec![LspServerConfig {
6862 command: "astro-ls".to_string(),
6863 args: vec!["--stdio".to_string()],
6864 enabled: true,
6865 auto_start: false,
6866 process_limits: ProcessLimits::default(),
6867 initialization_options: None,
6868 env: Default::default(),
6869 language_id_overrides: Default::default(),
6870 name: None,
6871 only_features: None,
6872 except_features: None,
6873 root_markers: Default::default(),
6874 }]),
6875 );
6876
6877 lsp.insert(
6879 "tailwindcss".to_string(),
6880 LspLanguageConfig::Multi(vec![LspServerConfig {
6881 command: "tailwindcss-language-server".to_string(),
6882 args: vec!["--stdio".to_string()],
6883 enabled: true,
6884 auto_start: false,
6885 process_limits: ProcessLimits::default(),
6886 initialization_options: None,
6887 env: Default::default(),
6888 language_id_overrides: Default::default(),
6889 name: None,
6890 only_features: None,
6891 except_features: None,
6892 root_markers: Default::default(),
6893 }]),
6894 );
6895
6896 lsp.insert(
6901 "nix".to_string(),
6902 LspLanguageConfig::Multi(vec![LspServerConfig {
6903 command: "nil".to_string(),
6904 args: vec![],
6905 enabled: true,
6906 auto_start: false,
6907 process_limits: ProcessLimits::default(),
6908 initialization_options: None,
6909 env: Default::default(),
6910 language_id_overrides: Default::default(),
6911 name: None,
6912 only_features: None,
6913 except_features: None,
6914 root_markers: Default::default(),
6915 }]),
6916 );
6917
6918 lsp.insert(
6921 "kotlin".to_string(),
6922 LspLanguageConfig::Multi(vec![LspServerConfig {
6923 command: "kotlin-language-server".to_string(),
6924 args: vec![],
6925 enabled: true,
6926 auto_start: false,
6927 process_limits: ProcessLimits::default(),
6928 initialization_options: None,
6929 env: Default::default(),
6930 language_id_overrides: Default::default(),
6931 name: None,
6932 only_features: None,
6933 except_features: None,
6934 root_markers: Default::default(),
6935 }]),
6936 );
6937
6938 lsp.insert(
6940 "swift".to_string(),
6941 LspLanguageConfig::Multi(vec![LspServerConfig {
6942 command: "sourcekit-lsp".to_string(),
6943 args: vec![],
6944 enabled: true,
6945 auto_start: false,
6946 process_limits: ProcessLimits::default(),
6947 initialization_options: None,
6948 env: Default::default(),
6949 language_id_overrides: Default::default(),
6950 name: None,
6951 only_features: None,
6952 except_features: None,
6953 root_markers: Default::default(),
6954 }]),
6955 );
6956
6957 lsp.insert(
6960 "scala".to_string(),
6961 LspLanguageConfig::Multi(vec![LspServerConfig {
6962 command: "metals".to_string(),
6963 args: vec![],
6964 enabled: true,
6965 auto_start: false,
6966 process_limits: ProcessLimits::default(),
6967 initialization_options: None,
6968 env: Default::default(),
6969 language_id_overrides: Default::default(),
6970 name: None,
6971 only_features: None,
6972 except_features: None,
6973 root_markers: Default::default(),
6974 }]),
6975 );
6976
6977 lsp.insert(
6980 "elixir".to_string(),
6981 LspLanguageConfig::Multi(vec![LspServerConfig {
6982 command: "elixir-ls".to_string(),
6983 args: vec![],
6984 enabled: true,
6985 auto_start: false,
6986 process_limits: ProcessLimits::default(),
6987 initialization_options: None,
6988 env: Default::default(),
6989 language_id_overrides: Default::default(),
6990 name: None,
6991 only_features: None,
6992 except_features: None,
6993 root_markers: Default::default(),
6994 }]),
6995 );
6996
6997 lsp.insert(
6999 "erlang".to_string(),
7000 LspLanguageConfig::Multi(vec![LspServerConfig {
7001 command: "erlang_ls".to_string(),
7002 args: vec![],
7003 enabled: true,
7004 auto_start: false,
7005 process_limits: ProcessLimits::default(),
7006 initialization_options: None,
7007 env: Default::default(),
7008 language_id_overrides: Default::default(),
7009 name: None,
7010 only_features: None,
7011 except_features: None,
7012 root_markers: Default::default(),
7013 }]),
7014 );
7015
7016 lsp.insert(
7019 "haskell".to_string(),
7020 LspLanguageConfig::Multi(vec![LspServerConfig {
7021 command: "haskell-language-server-wrapper".to_string(),
7022 args: vec!["--lsp".to_string()],
7023 enabled: true,
7024 auto_start: false,
7025 process_limits: ProcessLimits::default(),
7026 initialization_options: None,
7027 env: Default::default(),
7028 language_id_overrides: Default::default(),
7029 name: None,
7030 only_features: None,
7031 except_features: None,
7032 root_markers: Default::default(),
7033 }]),
7034 );
7035
7036 lsp.insert(
7039 "ocaml".to_string(),
7040 LspLanguageConfig::Multi(vec![LspServerConfig {
7041 command: "ocamllsp".to_string(),
7042 args: vec![],
7043 enabled: true,
7044 auto_start: false,
7045 process_limits: ProcessLimits::default(),
7046 initialization_options: None,
7047 env: Default::default(),
7048 language_id_overrides: Default::default(),
7049 name: None,
7050 only_features: None,
7051 except_features: None,
7052 root_markers: Default::default(),
7053 }]),
7054 );
7055
7056 lsp.insert(
7059 "clojure".to_string(),
7060 LspLanguageConfig::Multi(vec![LspServerConfig {
7061 command: "clojure-lsp".to_string(),
7062 args: vec![],
7063 enabled: true,
7064 auto_start: false,
7065 process_limits: ProcessLimits::default(),
7066 initialization_options: None,
7067 env: Default::default(),
7068 language_id_overrides: Default::default(),
7069 name: None,
7070 only_features: None,
7071 except_features: None,
7072 root_markers: Default::default(),
7073 }]),
7074 );
7075
7076 lsp.insert(
7079 "r".to_string(),
7080 LspLanguageConfig::Multi(vec![LspServerConfig {
7081 command: "R".to_string(),
7082 args: vec![
7083 "--vanilla".to_string(),
7084 "-e".to_string(),
7085 "languageserver::run()".to_string(),
7086 ],
7087 enabled: true,
7088 auto_start: false,
7089 process_limits: ProcessLimits::default(),
7090 initialization_options: None,
7091 env: Default::default(),
7092 language_id_overrides: Default::default(),
7093 name: None,
7094 only_features: None,
7095 except_features: None,
7096 root_markers: Default::default(),
7097 }]),
7098 );
7099
7100 lsp.insert(
7103 "julia".to_string(),
7104 LspLanguageConfig::Multi(vec![LspServerConfig {
7105 command: "julia".to_string(),
7106 args: vec![
7107 "--startup-file=no".to_string(),
7108 "--history-file=no".to_string(),
7109 "-e".to_string(),
7110 "using LanguageServer; runserver()".to_string(),
7111 ],
7112 enabled: true,
7113 auto_start: false,
7114 process_limits: ProcessLimits::default(),
7115 initialization_options: None,
7116 env: Default::default(),
7117 language_id_overrides: Default::default(),
7118 name: None,
7119 only_features: None,
7120 except_features: None,
7121 root_markers: Default::default(),
7122 }]),
7123 );
7124
7125 lsp.insert(
7128 "perl".to_string(),
7129 LspLanguageConfig::Multi(vec![LspServerConfig {
7130 command: "perlnavigator".to_string(),
7131 args: vec!["--stdio".to_string()],
7132 enabled: true,
7133 auto_start: false,
7134 process_limits: ProcessLimits::default(),
7135 initialization_options: None,
7136 env: Default::default(),
7137 language_id_overrides: Default::default(),
7138 name: None,
7139 only_features: None,
7140 except_features: None,
7141 root_markers: Default::default(),
7142 }]),
7143 );
7144
7145 lsp.insert(
7148 "nim".to_string(),
7149 LspLanguageConfig::Multi(vec![LspServerConfig {
7150 command: "nimlangserver".to_string(),
7151 args: vec![],
7152 enabled: true,
7153 auto_start: false,
7154 process_limits: ProcessLimits::default(),
7155 initialization_options: None,
7156 env: Default::default(),
7157 language_id_overrides: Default::default(),
7158 name: None,
7159 only_features: None,
7160 except_features: None,
7161 root_markers: Default::default(),
7162 }]),
7163 );
7164
7165 lsp.insert(
7167 "gleam".to_string(),
7168 LspLanguageConfig::Multi(vec![LspServerConfig {
7169 command: "gleam".to_string(),
7170 args: vec!["lsp".to_string()],
7171 enabled: true,
7172 auto_start: false,
7173 process_limits: ProcessLimits::default(),
7174 initialization_options: None,
7175 env: Default::default(),
7176 language_id_overrides: Default::default(),
7177 name: None,
7178 only_features: None,
7179 except_features: None,
7180 root_markers: Default::default(),
7181 }]),
7182 );
7183
7184 lsp.insert(
7187 "racket".to_string(),
7188 LspLanguageConfig::Multi(vec![LspServerConfig {
7189 command: "racket-langserver".to_string(),
7190 args: vec![],
7191 enabled: true,
7192 auto_start: false,
7193 process_limits: ProcessLimits::default(),
7194 initialization_options: None,
7195 env: Default::default(),
7196 language_id_overrides: Default::default(),
7197 name: None,
7198 only_features: None,
7199 except_features: None,
7200 root_markers: vec!["info.rkt".to_string(), ".git".to_string()],
7201 }]),
7202 );
7203
7204 lsp.insert(
7207 "fsharp".to_string(),
7208 LspLanguageConfig::Multi(vec![LspServerConfig {
7209 command: "fsautocomplete".to_string(),
7210 args: vec!["--adaptive-lsp-server-enabled".to_string()],
7211 enabled: true,
7212 auto_start: false,
7213 process_limits: ProcessLimits::default(),
7214 initialization_options: None,
7215 env: Default::default(),
7216 language_id_overrides: Default::default(),
7217 name: None,
7218 only_features: None,
7219 except_features: None,
7220 root_markers: Default::default(),
7221 }]),
7222 );
7223
7224 let svls_config = LspServerConfig {
7228 command: "svls".to_string(),
7229 args: vec![],
7230 enabled: true,
7231 auto_start: false,
7232 process_limits: ProcessLimits::default(),
7233 initialization_options: None,
7234 env: Default::default(),
7235 language_id_overrides: Default::default(),
7236 name: None,
7237 only_features: None,
7238 except_features: None,
7239 root_markers: vec![".svls.toml".to_string(), ".git".to_string()],
7240 };
7241 lsp.insert(
7242 "verilog".to_string(),
7243 LspLanguageConfig::Multi(vec![svls_config.clone()]),
7244 );
7245 lsp.insert(
7246 "systemverilog".to_string(),
7247 LspLanguageConfig::Multi(vec![svls_config]),
7248 );
7249
7250 let asm_lsp_config = LspServerConfig {
7261 command: "asm-lsp".to_string(),
7262 args: vec![],
7263 enabled: true,
7264 auto_start: false,
7265 process_limits: ProcessLimits::default(),
7266 initialization_options: None,
7267 env: Default::default(),
7268 language_id_overrides: Default::default(),
7269 name: None,
7270 only_features: None,
7271 except_features: Some(vec![LspFeature::Diagnostics]),
7272 root_markers: vec![".asm-lsp.toml".to_string(), ".git".to_string()],
7273 };
7274 lsp.insert(
7275 "asm".to_string(),
7276 LspLanguageConfig::Multi(vec![asm_lsp_config.clone()]),
7277 );
7278 lsp.insert(
7279 "gas".to_string(),
7280 LspLanguageConfig::Multi(vec![asm_lsp_config]),
7281 );
7282 }
7283 pub fn validate(&self) -> Result<(), ConfigError> {
7284 if self.editor.tab_size == 0 {
7286 return Err(ConfigError::ValidationError(
7287 "tab_size must be greater than 0".to_string(),
7288 ));
7289 }
7290
7291 if self.editor.scroll_offset > 100 {
7293 return Err(ConfigError::ValidationError(
7294 "scroll_offset must be <= 100".to_string(),
7295 ));
7296 }
7297
7298 for binding in &self.keybindings {
7300 if binding.key.is_empty() {
7301 return Err(ConfigError::ValidationError(
7302 "keybinding key cannot be empty".to_string(),
7303 ));
7304 }
7305 if binding.action.is_empty() {
7306 return Err(ConfigError::ValidationError(
7307 "keybinding action cannot be empty".to_string(),
7308 ));
7309 }
7310 }
7311
7312 Ok(())
7313 }
7314}
7315
7316#[derive(Debug)]
7318pub enum ConfigError {
7319 IoError(String),
7320 ParseError(String),
7321 SerializeError(String),
7322 ValidationError(String),
7323}
7324
7325impl std::fmt::Display for ConfigError {
7326 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7327 match self {
7328 Self::IoError(msg) => write!(f, "IO error: {msg}"),
7329 Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
7330 Self::SerializeError(msg) => write!(f, "Serialize error: {msg}"),
7331 Self::ValidationError(msg) => write!(f, "Validation error: {msg}"),
7332 }
7333 }
7334}
7335
7336impl std::error::Error for ConfigError {}
7337
7338#[cfg(test)]
7339mod tests {
7340 use super::*;
7341
7342 #[test]
7343 fn test_file_explorer_width_default_is_percent_30() {
7344 let cfg = FileExplorerConfig::default();
7345 assert_eq!(cfg.width, ExplorerWidth::Percent(30));
7346 }
7347
7348 #[test]
7349 fn test_tree_indicators_defaults_preserve_current_glyphs() {
7350 let cfg = FileExplorerConfig::default();
7354 assert_eq!(cfg.tree_indicator_collapsed, ">");
7355 assert_eq!(cfg.tree_indicator_expanded, "▼");
7356 }
7357
7358 #[test]
7359 fn test_tree_indicators_can_be_overridden_from_json() {
7360 let cfg: FileExplorerConfig = serde_json::from_str(
7361 r#"{"tree_indicator_collapsed": "▸", "tree_indicator_expanded": "▾"}"#,
7362 )
7363 .unwrap();
7364 assert_eq!(cfg.tree_indicator_collapsed, "▸");
7365 assert_eq!(cfg.tree_indicator_expanded, "▾");
7366 }
7367
7368 #[test]
7369 fn test_tree_indicators_partial_override_keeps_default_for_unset() {
7370 let cfg: FileExplorerConfig =
7371 serde_json::from_str(r#"{"tree_indicator_collapsed": "+"}"#).unwrap();
7372 assert_eq!(cfg.tree_indicator_collapsed, "+");
7373 assert_eq!(cfg.tree_indicator_expanded, "▼");
7375 }
7376
7377 #[test]
7380 fn test_width_accepts_legacy_float_fraction() {
7381 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": 0.3}"#).unwrap();
7382 assert_eq!(cfg.width, ExplorerWidth::Percent(30));
7383 }
7384
7385 #[test]
7386 fn test_width_accepts_bare_integer_as_percent() {
7387 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": 42}"#).unwrap();
7389 assert_eq!(cfg.width, ExplorerWidth::Percent(42));
7390 }
7391
7392 #[test]
7393 fn test_width_accepts_percent_string() {
7394 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "75%"}"#).unwrap();
7395 assert_eq!(cfg.width, ExplorerWidth::Percent(75));
7396 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "42 %"}"#).unwrap();
7398 assert_eq!(cfg.width, ExplorerWidth::Percent(42));
7399 }
7400
7401 #[test]
7402 fn test_width_accepts_columns_string() {
7403 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "24"}"#).unwrap();
7404 assert_eq!(cfg.width, ExplorerWidth::Columns(24));
7405 }
7406
7407 #[test]
7408 fn test_width_rejects_percent_over_100() {
7409 let err = serde_json::from_str::<FileExplorerConfig>(r#"{"width": "150%"}"#)
7410 .expect_err("percent > 100 should be rejected");
7411 assert!(err.to_string().contains("100"), "{err}");
7412 }
7413
7414 #[test]
7415 fn test_width_rejects_integer_over_100() {
7416 let err = serde_json::from_str::<FileExplorerConfig>(r#"{"width": 150}"#)
7419 .expect_err("bare integer > 100 should be rejected as percent");
7420 assert!(err.to_string().contains("percent") || err.to_string().contains("100"));
7421 }
7422
7423 #[test]
7424 fn test_width_rejects_garbage_string() {
7425 serde_json::from_str::<FileExplorerConfig>(r#"{"width": "big"}"#)
7426 .expect_err("non-numeric string should be rejected");
7427 }
7428
7429 #[test]
7432 fn test_width_serializes_percent_as_string_with_suffix() {
7433 let cfg = FileExplorerConfig {
7434 width: ExplorerWidth::Percent(30),
7435 ..Default::default()
7436 };
7437 let json = serde_json::to_value(&cfg).unwrap();
7438 assert_eq!(json["width"], serde_json::json!("30%"));
7439 }
7440
7441 #[test]
7442 fn test_width_serializes_columns_as_string_without_suffix() {
7443 let cfg = FileExplorerConfig {
7444 width: ExplorerWidth::Columns(24),
7445 ..Default::default()
7446 };
7447 let json = serde_json::to_value(&cfg).unwrap();
7448 assert_eq!(json["width"], serde_json::json!("24"));
7449 }
7450
7451 #[test]
7452 fn test_width_round_trip_both_variants() {
7453 for value in [ExplorerWidth::Percent(17), ExplorerWidth::Columns(42)] {
7454 let cfg = FileExplorerConfig {
7455 width: value,
7456 ..Default::default()
7457 };
7458 let json = serde_json::to_string(&cfg).unwrap();
7459 let restored: FileExplorerConfig = serde_json::from_str(&json).unwrap();
7460 assert_eq!(restored.width, value, "round trip failed for {:?}", value);
7461 }
7462 }
7463
7464 #[test]
7467 fn test_to_cols_percent() {
7468 assert_eq!(ExplorerWidth::Percent(30).to_cols(100), 30);
7469 assert_eq!(ExplorerWidth::Percent(25).to_cols(120), 30);
7470 assert_eq!(ExplorerWidth::Percent(30).to_cols(0), 0);
7472 assert_eq!(ExplorerWidth::Percent(100).to_cols(200), 200);
7473 }
7474
7475 #[test]
7476 fn test_to_cols_columns_clamps_to_terminal() {
7477 assert_eq!(ExplorerWidth::Columns(24).to_cols(100), 24);
7478 assert_eq!(ExplorerWidth::Columns(999).to_cols(80), 80);
7479 }
7480
7481 #[test]
7484 fn test_to_cols_enforces_min_width() {
7485 assert_eq!(
7487 ExplorerWidth::Columns(0).to_cols(100),
7488 ExplorerWidth::MIN_COLS
7489 );
7490 assert_eq!(
7491 ExplorerWidth::Columns(1).to_cols(100),
7492 ExplorerWidth::MIN_COLS
7493 );
7494 assert_eq!(
7495 ExplorerWidth::Columns(4).to_cols(100),
7496 ExplorerWidth::MIN_COLS
7497 );
7498 assert_eq!(
7499 ExplorerWidth::Percent(0).to_cols(100),
7500 ExplorerWidth::MIN_COLS
7501 );
7502 assert_eq!(
7504 ExplorerWidth::Percent(3).to_cols(100),
7505 ExplorerWidth::MIN_COLS
7506 );
7507 assert_eq!(
7509 ExplorerWidth::Columns(ExplorerWidth::MIN_COLS + 1).to_cols(100),
7510 ExplorerWidth::MIN_COLS + 1
7511 );
7512 }
7513
7514 #[test]
7517 fn test_to_cols_min_floor_yields_to_narrow_terminal() {
7518 assert_eq!(ExplorerWidth::Columns(10).to_cols(3), 3);
7519 assert_eq!(ExplorerWidth::Percent(100).to_cols(2), 2);
7520 assert_eq!(ExplorerWidth::Columns(1).to_cols(0), 0);
7521 }
7522
7523 #[test]
7526 fn test_load_from_file_accepts_legacy_float_fraction_width() {
7527 let dir = tempfile::tempdir().unwrap();
7528 let path = dir.path().join("config.json");
7529 std::fs::write(&path, r#"{"file_explorer":{"width":0.25}}"#).unwrap();
7530 let cfg = Config::load_from_file(&path).expect("legacy float fraction must still load");
7531 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Percent(25));
7532 }
7533
7534 #[test]
7535 fn test_load_from_file_accepts_columns_string_width() {
7536 let dir = tempfile::tempdir().unwrap();
7537 let path = dir.path().join("config.json");
7538 std::fs::write(&path, r#"{"file_explorer":{"width":"42"}}"#).unwrap();
7539 let cfg = Config::load_from_file(&path).unwrap();
7540 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Columns(42));
7541 }
7542
7543 #[test]
7544 fn test_load_from_file_accepts_percent_string_width() {
7545 let dir = tempfile::tempdir().unwrap();
7546 let path = dir.path().join("config.json");
7547 std::fs::write(&path, r#"{"file_explorer":{"width":"55%"}}"#).unwrap();
7548 let cfg = Config::load_from_file(&path).unwrap();
7549 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Percent(55));
7550 }
7551
7552 #[test]
7553 fn test_default_config() {
7554 let config = Config::default();
7555 assert_eq!(config.editor.tab_size, 4);
7556 assert!(config.editor.line_numbers);
7557 assert!(config.editor.syntax_highlighting);
7558 assert!(config.languages.contains_key("gdscript"));
7559 assert_eq!(config.languages["gdscript"].extensions, vec!["gd"]);
7560 assert!(config.keybindings.is_empty());
7563 let resolved = config.resolve_keymap(&config.active_keybinding_map);
7565 assert!(!resolved.is_empty());
7566 }
7567
7568 #[test]
7569 fn test_all_builtin_keymaps_loadable() {
7570 for name in KeybindingMapName::BUILTIN_OPTIONS {
7571 let keymap = Config::load_builtin_keymap(name);
7572 assert!(keymap.is_some(), "Failed to load builtin keymap '{}'", name);
7573 }
7574 }
7575
7576 #[test]
7577 fn test_config_validation() {
7578 let mut config = Config::default();
7579 assert!(config.validate().is_ok());
7580
7581 config.editor.tab_size = 0;
7582 assert!(config.validate().is_err());
7583 }
7584
7585 #[test]
7586 fn test_macos_keymap_inherits_enter_bindings() {
7587 let config = Config::default();
7588 let bindings = config.resolve_keymap("macos");
7589
7590 let enter_bindings: Vec<_> = bindings.iter().filter(|b| b.key == "Enter").collect();
7591 assert!(
7592 !enter_bindings.is_empty(),
7593 "macos keymap should inherit Enter bindings from default, got {} Enter bindings",
7594 enter_bindings.len()
7595 );
7596 let has_insert_newline = enter_bindings.iter().any(|b| b.action == "insert_newline");
7598 assert!(
7599 has_insert_newline,
7600 "macos keymap should have insert_newline action for Enter key"
7601 );
7602 }
7603
7604 #[test]
7605 fn test_config_serialize_deserialize() {
7606 let config = Config::default();
7608
7609 let json = serde_json::to_string_pretty(&config).unwrap();
7611
7612 let loaded: Config = serde_json::from_str(&json).unwrap();
7614
7615 assert_eq!(config.editor.tab_size, loaded.editor.tab_size);
7616 assert_eq!(config.theme, loaded.theme);
7617 }
7618
7619 #[test]
7620 fn test_config_with_custom_keybinding() {
7621 let json = r#"{
7622 "editor": {
7623 "tab_size": 2
7624 },
7625 "keybindings": [
7626 {
7627 "key": "x",
7628 "modifiers": ["ctrl", "shift"],
7629 "action": "custom_action",
7630 "args": {},
7631 "when": null
7632 }
7633 ]
7634 }"#;
7635
7636 let config: Config = serde_json::from_str(json).unwrap();
7637 assert_eq!(config.editor.tab_size, 2);
7638 assert_eq!(config.keybindings.len(), 1);
7639 assert_eq!(config.keybindings[0].key, "x");
7640 assert_eq!(config.keybindings[0].modifiers.len(), 2);
7641 }
7642
7643 #[test]
7644 fn test_sparse_config_merges_with_defaults() {
7645 let temp_dir = tempfile::tempdir().unwrap();
7647 let config_path = temp_dir.path().join("config.json");
7648
7649 let sparse_config = r#"{
7651 "lsp": {
7652 "rust": {
7653 "command": "custom-rust-analyzer",
7654 "args": ["--custom-arg"]
7655 }
7656 }
7657 }"#;
7658 std::fs::write(&config_path, sparse_config).unwrap();
7659
7660 let loaded = Config::load_from_file(&config_path).unwrap();
7662
7663 assert!(loaded.lsp.contains_key("rust"));
7665 assert_eq!(
7666 loaded.lsp["rust"].as_slice()[0].command,
7667 "custom-rust-analyzer".to_string()
7668 );
7669
7670 assert!(
7672 loaded.lsp.contains_key("python"),
7673 "python LSP should be merged from defaults"
7674 );
7675 assert!(
7676 loaded.lsp.contains_key("typescript"),
7677 "typescript LSP should be merged from defaults"
7678 );
7679 assert!(
7680 loaded.lsp.contains_key("javascript"),
7681 "javascript LSP should be merged from defaults"
7682 );
7683
7684 assert!(loaded.languages.contains_key("rust"));
7686 assert!(loaded.languages.contains_key("python"));
7687 assert!(loaded.languages.contains_key("typescript"));
7688 }
7689
7690 #[test]
7691 fn test_empty_config_gets_all_defaults() {
7692 let temp_dir = tempfile::tempdir().unwrap();
7693 let config_path = temp_dir.path().join("config.json");
7694
7695 std::fs::write(&config_path, "{}").unwrap();
7697
7698 let loaded = Config::load_from_file(&config_path).unwrap();
7699 let defaults = Config::default();
7700
7701 assert_eq!(loaded.lsp.len(), defaults.lsp.len());
7703
7704 assert_eq!(loaded.languages.len(), defaults.languages.len());
7706 }
7707
7708 #[test]
7709 fn test_dynamic_submenu_expansion() {
7710 let temp_dir = tempfile::tempdir().unwrap();
7712 let themes_dir = temp_dir.path().to_path_buf();
7713
7714 let dynamic = MenuItem::DynamicSubmenu {
7715 label: "Test".to_string(),
7716 source: "copy_with_theme".to_string(),
7717 };
7718
7719 let expanded = dynamic.expand_dynamic(&themes_dir);
7720
7721 match expanded {
7723 MenuItem::Submenu { label, items } => {
7724 assert_eq!(label, "Test");
7725 let loader = crate::view::theme::ThemeLoader::embedded_only();
7727 let registry = loader.load_all(&[]);
7728 assert_eq!(items.len(), registry.len());
7729
7730 for (item, theme_info) in items.iter().zip(registry.list().iter()) {
7732 match item {
7733 MenuItem::Action {
7734 label,
7735 action,
7736 args,
7737 ..
7738 } => {
7739 assert_eq!(label, &theme_info.name);
7740 assert_eq!(action, "copy_with_theme");
7741 assert_eq!(
7742 args.get("theme").and_then(|v| v.as_str()),
7743 Some(theme_info.name.as_str())
7744 );
7745 }
7746 _ => panic!("Expected Action item"),
7747 }
7748 }
7749 }
7750 _ => panic!("Expected Submenu after expansion"),
7751 }
7752 }
7753
7754 #[test]
7755 fn test_non_dynamic_item_unchanged() {
7756 let temp_dir = tempfile::tempdir().unwrap();
7758 let themes_dir = temp_dir.path();
7759
7760 let action = MenuItem::Action {
7761 label: "Test".to_string(),
7762 action: "test".to_string(),
7763 args: HashMap::new(),
7764 when: None,
7765 checkbox: None,
7766 };
7767
7768 let expanded = action.expand_dynamic(themes_dir);
7769 match expanded {
7770 MenuItem::Action { label, action, .. } => {
7771 assert_eq!(label, "Test");
7772 assert_eq!(action, "test");
7773 }
7774 _ => panic!("Action should remain Action after expand_dynamic"),
7775 }
7776 }
7777
7778 #[test]
7779 fn test_buffer_config_uses_global_defaults() {
7780 let config = Config::default();
7781 let buffer_config = BufferConfig::resolve(&config, None);
7782
7783 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
7784 assert_eq!(buffer_config.auto_indent, config.editor.auto_indent);
7785 assert!(!buffer_config.use_tabs); assert!(buffer_config.whitespace.any_tabs()); assert!(buffer_config.formatter.is_none());
7788 assert!(!buffer_config.format_on_save);
7789 }
7790
7791 #[test]
7792 fn test_buffer_config_applies_language_overrides() {
7793 let mut config = Config::default();
7794
7795 config.languages.insert(
7797 "go".to_string(),
7798 LanguageConfig {
7799 extensions: vec!["go".to_string()],
7800 filenames: vec![],
7801 grammar: "go".to_string(),
7802 comment_prefix: Some("//".to_string()),
7803 auto_indent: true,
7804 auto_close: None,
7805 auto_surround: None,
7806 textmate_grammar: None,
7807 show_whitespace_tabs: false, line_wrap: None,
7809 wrap_column: None,
7810 page_view: None,
7811 page_width: None,
7812 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
7815 command: "gofmt".to_string(),
7816 args: vec![],
7817 stdin: true,
7818 timeout_ms: 10000,
7819 }),
7820 format_on_save: true,
7821 on_save: vec![],
7822 word_characters: None,
7823 indent: None,
7824 },
7825 );
7826
7827 let buffer_config = BufferConfig::resolve(&config, Some("go"));
7828
7829 assert_eq!(buffer_config.tab_size, 8);
7830 assert!(buffer_config.use_tabs);
7831 assert!(!buffer_config.whitespace.any_tabs()); assert!(buffer_config.format_on_save);
7833 assert!(buffer_config.formatter.is_some());
7834 assert_eq!(buffer_config.formatter.as_ref().unwrap().command, "gofmt");
7835 }
7836
7837 #[test]
7838 fn test_buffer_config_unknown_language_uses_global() {
7839 let config = Config::default();
7840 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
7841
7842 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
7844 assert!(!buffer_config.use_tabs);
7845 }
7846
7847 #[test]
7848 fn test_buffer_config_per_language_line_wrap() {
7849 let mut config = Config::default();
7850 config.editor.line_wrap = false;
7851
7852 config.languages.insert(
7854 "markdown".to_string(),
7855 LanguageConfig {
7856 extensions: vec!["md".to_string()],
7857 line_wrap: Some(true),
7858 ..Default::default()
7859 },
7860 );
7861
7862 let md_config = BufferConfig::resolve(&config, Some("markdown"));
7864 assert!(md_config.line_wrap, "Markdown should have line_wrap=true");
7865
7866 let other_config = BufferConfig::resolve(&config, Some("rust"));
7868 assert!(
7869 !other_config.line_wrap,
7870 "Non-configured languages should use global line_wrap=false"
7871 );
7872
7873 let no_lang_config = BufferConfig::resolve(&config, None);
7875 assert!(
7876 !no_lang_config.line_wrap,
7877 "No language should use global line_wrap=false"
7878 );
7879 }
7880
7881 #[test]
7882 fn test_buffer_config_per_language_wrap_column() {
7883 let mut config = Config::default();
7884 config.editor.wrap_column = Some(120);
7885
7886 config.languages.insert(
7888 "markdown".to_string(),
7889 LanguageConfig {
7890 extensions: vec!["md".to_string()],
7891 wrap_column: Some(80),
7892 ..Default::default()
7893 },
7894 );
7895
7896 let md_config = BufferConfig::resolve(&config, Some("markdown"));
7898 assert_eq!(md_config.wrap_column, Some(80));
7899
7900 let other_config = BufferConfig::resolve(&config, Some("rust"));
7902 assert_eq!(other_config.wrap_column, Some(120));
7903
7904 let no_lang_config = BufferConfig::resolve(&config, None);
7906 assert_eq!(no_lang_config.wrap_column, Some(120));
7907 }
7908
7909 #[test]
7910 fn test_normalize_zero_sentinels_global() {
7911 let mut config = Config::default();
7912 config.editor.wrap_column = Some(0);
7913 config.editor.page_width = Some(0);
7914 config.editor.tab_size = 0;
7915
7916 config.normalize_zero_sentinels();
7917
7918 assert_eq!(config.editor.wrap_column, None);
7920 assert_eq!(config.editor.page_width, None);
7921 assert_eq!(config.editor.tab_size, default_tab_size());
7922 }
7923
7924 #[test]
7925 fn test_normalize_zero_sentinels_language_inherits_global() {
7926 let mut config = Config::default();
7927 config.editor.wrap_column = Some(120);
7928 config.editor.page_width = Some(80);
7929 config.editor.tab_size = 8;
7930
7931 config.languages.insert(
7934 "markdown".to_string(),
7935 LanguageConfig {
7936 extensions: vec!["md".to_string()],
7937 wrap_column: Some(0),
7938 page_width: Some(0),
7939 tab_size: Some(0),
7940 ..Default::default()
7941 },
7942 );
7943
7944 config.normalize_zero_sentinels();
7945
7946 let lang = &config.languages["markdown"];
7947 assert_eq!(lang.wrap_column, None);
7948 assert_eq!(lang.page_width, None);
7949 assert_eq!(lang.tab_size, None);
7950
7951 let md = BufferConfig::resolve(&config, Some("markdown"));
7953 assert_eq!(md.wrap_column, Some(120));
7954 assert_eq!(md.tab_size, 8);
7955 }
7956
7957 #[test]
7958 fn test_buffer_config_indent_string() {
7959 let config = Config::default();
7960
7961 let spaces_config = BufferConfig::resolve(&config, None);
7963 assert_eq!(spaces_config.indent_string(), " "); let mut config_with_tabs = Config::default();
7967 config_with_tabs.languages.insert(
7968 "makefile".to_string(),
7969 LanguageConfig {
7970 use_tabs: Some(true),
7971 tab_size: Some(8),
7972 ..Default::default()
7973 },
7974 );
7975 let tabs_config = BufferConfig::resolve(&config_with_tabs, Some("makefile"));
7976 assert_eq!(tabs_config.indent_string(), "\t");
7977 }
7978
7979 #[test]
7980 fn test_buffer_config_global_use_tabs_inherited() {
7981 let mut config = Config::default();
7984 config.editor.use_tabs = true;
7985
7986 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
7988 assert!(buffer_config.use_tabs);
7989
7990 let buffer_config = BufferConfig::resolve(&config, None);
7992 assert!(buffer_config.use_tabs);
7993
7994 config.languages.insert(
7996 "python".to_string(),
7997 LanguageConfig {
7998 use_tabs: Some(false),
7999 ..Default::default()
8000 },
8001 );
8002 let buffer_config = BufferConfig::resolve(&config, Some("python"));
8003 assert!(!buffer_config.use_tabs);
8004
8005 config.languages.insert(
8007 "rust".to_string(),
8008 LanguageConfig {
8009 use_tabs: None,
8010 ..Default::default()
8011 },
8012 );
8013 let buffer_config = BufferConfig::resolve(&config, Some("rust"));
8014 assert!(buffer_config.use_tabs);
8015 }
8016
8017 #[test]
8023 #[cfg(feature = "runtime")]
8024 fn test_lsp_languages_have_language_config() {
8025 let config = Config::default();
8026 let exceptions = ["tailwindcss"];
8027 for lsp_key in config.lsp.keys() {
8028 if exceptions.contains(&lsp_key.as_str()) {
8029 continue;
8030 }
8031 assert!(
8032 config.languages.contains_key(lsp_key),
8033 "LSP config key '{}' has no matching entry in default_languages(). \
8034 Add a LanguageConfig with the correct file extensions so detect_language() \
8035 can map files to this language.",
8036 lsp_key
8037 );
8038 }
8039 }
8040
8041 #[test]
8042 #[cfg(feature = "runtime")]
8043 fn test_gdscript_lsp_disabled_by_default() {
8044 let config = Config::default();
8045 let server = &config.lsp["gdscript"].as_slice()[0];
8046 assert_eq!(server.command, "nc");
8047 assert_eq!(server.args, vec!["127.0.0.1", "6005"]);
8048 assert!(!server.enabled);
8049 assert_eq!(server.name.as_deref(), Some("Godot GDScript"));
8050 }
8051
8052 #[test]
8053 #[cfg(feature = "runtime")]
8054 fn test_default_config_has_quicklsp_in_universal_lsp() {
8055 let config = Config::default();
8056 assert!(
8057 config.universal_lsp.contains_key("quicklsp"),
8058 "Default config should contain quicklsp in universal_lsp"
8059 );
8060 let quicklsp = &config.universal_lsp["quicklsp"];
8061 let server = &quicklsp.as_slice()[0];
8062 assert_eq!(server.command, "quicklsp");
8063 assert!(!server.enabled, "quicklsp should be disabled by default");
8064 assert_eq!(server.name.as_deref(), Some("QuickLSP"));
8065 assert!(
8069 server.only_features.is_none(),
8070 "quicklsp must not default to a feature whitelist"
8071 );
8072 assert!(server.except_features.is_none());
8073 }
8074
8075 #[test]
8076 fn test_empty_config_preserves_universal_lsp_defaults() {
8077 let temp_dir = tempfile::tempdir().unwrap();
8078 let config_path = temp_dir.path().join("config.json");
8079
8080 std::fs::write(&config_path, "{}").unwrap();
8082
8083 let loaded = Config::load_from_file(&config_path).unwrap();
8084 let defaults = Config::default();
8085
8086 assert_eq!(
8088 loaded.universal_lsp.len(),
8089 defaults.universal_lsp.len(),
8090 "Empty config should preserve all default universal_lsp entries"
8091 );
8092 }
8093
8094 #[test]
8095 fn test_universal_lsp_config_merges_with_defaults() {
8096 let temp_dir = tempfile::tempdir().unwrap();
8097 let config_path = temp_dir.path().join("config.json");
8098
8099 let config_json = r#"{
8101 "universal_lsp": {
8102 "quicklsp": {
8103 "enabled": true
8104 }
8105 }
8106 }"#;
8107 std::fs::write(&config_path, config_json).unwrap();
8108
8109 let loaded = Config::load_from_file(&config_path).unwrap();
8110
8111 assert!(loaded.universal_lsp.contains_key("quicklsp"));
8113 let server = &loaded.universal_lsp["quicklsp"].as_slice()[0];
8114 assert!(server.enabled, "User override should enable quicklsp");
8115 assert_eq!(
8117 server.command, "quicklsp",
8118 "Default command should be merged when not specified by user"
8119 );
8120 }
8121
8122 #[test]
8123 fn test_universal_lsp_custom_server_added() {
8124 let temp_dir = tempfile::tempdir().unwrap();
8125 let config_path = temp_dir.path().join("config.json");
8126
8127 let config_json = r#"{
8129 "universal_lsp": {
8130 "my-custom-server": {
8131 "command": "my-server",
8132 "enabled": true,
8133 "auto_start": true
8134 }
8135 }
8136 }"#;
8137 std::fs::write(&config_path, config_json).unwrap();
8138
8139 let loaded = Config::load_from_file(&config_path).unwrap();
8140
8141 assert!(
8143 loaded.universal_lsp.contains_key("my-custom-server"),
8144 "Custom universal server should be loaded"
8145 );
8146 let server = &loaded.universal_lsp["my-custom-server"].as_slice()[0];
8147 assert_eq!(server.command, "my-server");
8148 assert!(server.enabled);
8149 assert!(server.auto_start);
8150
8151 assert!(
8153 loaded.universal_lsp.contains_key("quicklsp"),
8154 "Default quicklsp should be merged from defaults"
8155 );
8156 }
8157
8158 #[test]
8163 fn test_asm_lsp_defaults_exclude_pull_diagnostics() {
8164 let config = Config::default();
8165 for language in ["asm", "gas"] {
8166 let LspLanguageConfig::Multi(servers) = &config.lsp[language] else {
8167 panic!("expected Multi LSP config for {language}");
8168 };
8169 let server = &servers[0];
8170 assert_eq!(server.command, "asm-lsp");
8171 assert_eq!(
8172 server.except_features,
8173 Some(vec![LspFeature::Diagnostics]),
8174 "{language}: asm-lsp must exclude pull diagnostics"
8175 );
8176 }
8177 }
8178}