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, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
230#[serde(rename_all = "snake_case")]
231pub enum IndentationGuideMode {
232 #[default]
234 None,
235 All,
237 Active,
239}
240
241impl IndentationGuideMode {
242 pub const OPTIONS: &'static [&'static str] = &["none", "all", "active"];
243}
244
245impl JsonSchema for IndentationGuideMode {
246 fn schema_name() -> Cow<'static, str> {
247 Cow::Borrowed("IndentationGuideMode")
248 }
249
250 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
251 schemars::json_schema!({
252 "description": "Indentation guide rendering mode.",
253 "type": "string",
254 "enum": Self::OPTIONS,
255 "default": "none"
256 })
257 }
258}
259
260#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
262#[serde(transparent)]
263pub struct KeybindingMapName(pub String);
264
265impl KeybindingMapName {
266 pub const BUILTIN_OPTIONS: &'static [&'static str] =
268 &["default", "emacs", "vscode", "macos", "macos-gui"];
269}
270
271impl Deref for KeybindingMapName {
272 type Target = str;
273 fn deref(&self) -> &Self::Target {
274 &self.0
275 }
276}
277
278impl From<String> for KeybindingMapName {
279 fn from(s: String) -> Self {
280 Self(s)
281 }
282}
283
284impl From<&str> for KeybindingMapName {
285 fn from(s: &str) -> Self {
286 Self(s.to_string())
287 }
288}
289
290impl PartialEq<str> for KeybindingMapName {
291 fn eq(&self, other: &str) -> bool {
292 self.0 == other
293 }
294}
295
296#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
298#[serde(rename_all = "lowercase")]
299pub enum LineEndingOption {
300 #[default]
302 Lf,
303 Crlf,
305 Cr,
307}
308
309impl LineEndingOption {
310 pub fn to_line_ending(&self) -> crate::model::buffer::LineEnding {
312 match self {
313 Self::Lf => crate::model::buffer::LineEnding::LF,
314 Self::Crlf => crate::model::buffer::LineEnding::CRLF,
315 Self::Cr => crate::model::buffer::LineEnding::CR,
316 }
317 }
318}
319
320impl JsonSchema for LineEndingOption {
321 fn schema_name() -> Cow<'static, str> {
322 Cow::Borrowed("LineEndingOption")
323 }
324
325 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
326 schemars::json_schema!({
327 "description": "Default line ending format for new files",
328 "type": "string",
329 "enum": ["lf", "crlf", "cr"],
330 "default": "lf"
331 })
332 }
333}
334
335impl PartialEq<KeybindingMapName> for str {
336 fn eq(&self, other: &KeybindingMapName) -> bool {
337 self == other.0
338 }
339}
340
341impl JsonSchema for KeybindingMapName {
342 fn schema_name() -> Cow<'static, str> {
343 Cow::Borrowed("KeybindingMapOptions")
344 }
345
346 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
347 schemars::json_schema!({
348 "description": "Available keybinding maps",
349 "type": "string",
350 "enum": Self::BUILTIN_OPTIONS
351 })
352 }
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
357pub struct Config {
358 #[serde(default)]
361 pub version: u32,
362
363 #[serde(default = "default_theme_name")]
365 pub theme: ThemeName,
366
367 #[serde(default)]
370 pub locale: LocaleName,
371
372 #[serde(default = "default_true")]
375 pub check_for_updates: bool,
376
377 #[serde(default)]
379 pub editor: EditorConfig,
380
381 #[serde(default)]
383 pub file_explorer: FileExplorerConfig,
384
385 #[serde(default)]
387 pub file_browser: FileBrowserConfig,
388
389 #[serde(default)]
391 pub clipboard: ClipboardConfig,
392
393 #[serde(default)]
395 pub terminal: TerminalConfig,
396
397 #[serde(default)]
399 pub keybindings: Vec<Keybinding>,
400
401 #[serde(default)]
404 pub keybinding_maps: HashMap<String, KeymapConfig>,
405
406 #[serde(default = "default_keybinding_map_name")]
408 #[schemars(default = "default_keybinding_map_schema")]
409 pub active_keybinding_map: KeybindingMapName,
410
411 #[serde(default)]
413 pub languages: HashMap<String, LanguageConfig>,
414
415 #[serde(default)]
421 #[schemars(extend("x-enum-from" = "/languages"))]
422 pub default_language: Option<String>,
423
424 #[serde(default = "default_true")]
430 pub lsp_enabled: bool,
431
432 #[serde(default)]
436 pub lsp: HashMap<String, LspLanguageConfig>,
437
438 #[serde(default)]
442 pub universal_lsp: HashMap<String, LspLanguageConfig>,
443
444 #[serde(default)]
446 pub warnings: WarningsConfig,
447
448 #[serde(default)]
452 #[schemars(extend("x-standalone-category" = true, "x-no-add" = true))]
453 pub plugins: HashMap<String, PluginConfig>,
454
455 #[serde(default)]
457 pub packages: PackagesConfig,
458
459 #[serde(default)]
461 pub env: EnvConfig,
462}
463
464#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
473pub struct EnvConfig {
474 #[serde(default = "default_env_detectors")]
477 pub detectors: Vec<EnvDetector>,
478}
479
480impl Default for EnvConfig {
481 fn default() -> Self {
482 Self {
483 detectors: default_env_detectors(),
484 }
485 }
486}
487
488#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
491#[serde(rename_all = "kebab-case")]
492pub enum EnvKind {
493 PathOnly,
497 Shell,
500}
501
502#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
505pub struct EnvDetector {
506 pub name: String,
508 pub markers: Vec<String>,
511 pub kind: EnvKind,
513 pub snippet: String,
520 #[serde(default)]
525 pub require: Vec<String>,
526}
527
528pub fn default_env_detectors() -> Vec<EnvDetector> {
533 let venv_interp = |dir: &str| {
534 vec![
535 format!("{dir}/bin/python"),
536 format!("{dir}/bin/python3"),
537 format!("{dir}/Scripts/python.exe"),
538 ]
539 };
540 vec![
541 EnvDetector {
542 name: ".venv".into(),
543 markers: vec![".venv".into()],
544 kind: EnvKind::PathOnly,
545 snippet: "source .venv/bin/activate".into(),
550 require: venv_interp(".venv"),
551 },
552 EnvDetector {
553 name: "venv".into(),
554 markers: vec!["venv".into()],
555 kind: EnvKind::PathOnly,
556 snippet: "source venv/bin/activate".into(),
557 require: venv_interp("venv"),
558 },
559 EnvDetector {
560 name: "direnv".into(),
561 markers: vec![".envrc".into()],
562 kind: EnvKind::Shell,
563 snippet: "eval \"$(direnv export bash)\"".into(),
564 require: vec![],
565 },
566 EnvDetector {
567 name: "mise".into(),
568 markers: vec![
569 "mise.toml".into(),
570 ".mise.toml".into(),
571 ".tool-versions".into(),
572 ],
573 kind: EnvKind::Shell,
574 snippet: "eval \"$(mise env -s bash)\"".into(),
575 require: vec![],
576 },
577 EnvDetector {
578 name: "pipenv".into(),
579 markers: vec!["Pipfile".into()],
580 kind: EnvKind::Shell,
581 snippet: "source \"$(pipenv --venv)/bin/activate\"".into(),
582 require: vec![],
583 },
584 EnvDetector {
585 name: "poetry".into(),
586 markers: vec!["poetry.lock".into()],
587 kind: EnvKind::Shell,
588 snippet: "source \"$(poetry env info --path)/bin/activate\"".into(),
589 require: vec![],
590 },
591 ]
592}
593
594fn default_keybinding_map_name() -> KeybindingMapName {
595 if cfg!(target_os = "macos") {
598 KeybindingMapName("macos".to_string())
599 } else {
600 KeybindingMapName("default".to_string())
601 }
602}
603
604fn default_keybinding_map_schema() -> KeybindingMapName {
607 KeybindingMapName("default".to_string())
608}
609
610fn default_theme_name() -> ThemeName {
611 ThemeName("high-contrast".to_string())
612}
613
614#[derive(Debug, Clone, Copy)]
619pub struct WhitespaceVisibility {
620 pub spaces_leading: bool,
621 pub spaces_inner: bool,
622 pub spaces_trailing: bool,
623 pub tabs_leading: bool,
624 pub tabs_inner: bool,
625 pub tabs_trailing: bool,
626}
627
628impl Default for WhitespaceVisibility {
629 fn default() -> Self {
630 Self {
632 spaces_leading: false,
633 spaces_inner: false,
634 spaces_trailing: false,
635 tabs_leading: true,
636 tabs_inner: true,
637 tabs_trailing: true,
638 }
639 }
640}
641
642impl WhitespaceVisibility {
643 pub fn from_editor_config(editor: &EditorConfig) -> Self {
645 if !editor.whitespace_show {
646 return Self {
647 spaces_leading: false,
648 spaces_inner: false,
649 spaces_trailing: false,
650 tabs_leading: false,
651 tabs_inner: false,
652 tabs_trailing: false,
653 };
654 }
655 Self {
656 spaces_leading: editor.whitespace_spaces_leading,
657 spaces_inner: editor.whitespace_spaces_inner,
658 spaces_trailing: editor.whitespace_spaces_trailing,
659 tabs_leading: editor.whitespace_tabs_leading,
660 tabs_inner: editor.whitespace_tabs_inner,
661 tabs_trailing: editor.whitespace_tabs_trailing,
662 }
663 }
664
665 pub fn with_language_tab_override(mut self, show_whitespace_tabs: bool) -> Self {
668 if !show_whitespace_tabs {
669 self.tabs_leading = false;
670 self.tabs_inner = false;
671 self.tabs_trailing = false;
672 }
673 self
674 }
675
676 pub fn any_spaces(&self) -> bool {
678 self.spaces_leading || self.spaces_inner || self.spaces_trailing
679 }
680
681 pub fn any_tabs(&self) -> bool {
683 self.tabs_leading || self.tabs_inner || self.tabs_trailing
684 }
685
686 pub fn any_visible(&self) -> bool {
688 self.any_spaces() || self.any_tabs()
689 }
690
691 pub fn toggle_all(&mut self) {
695 if self.any_visible() {
696 *self = Self {
697 spaces_leading: false,
698 spaces_inner: false,
699 spaces_trailing: false,
700 tabs_leading: false,
701 tabs_inner: false,
702 tabs_trailing: false,
703 };
704 } else {
705 *self = Self::default();
706 }
707 }
708}
709
710#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
731#[serde(try_from = "String", into = "String")]
732pub enum StatusBarElement {
733 Filename,
735 ReadOnly,
739 Cursor,
741 CursorCompact,
743 Diagnostics,
745 CursorCount,
747 Messages,
749 Chord,
751 LineEnding,
753 Encoding,
755 Language,
757 Lsp,
759 Warnings,
761 Update,
763 Palette,
765 Clock,
767 RemoteIndicator,
772 WorkspaceTrust,
777 CustomToken(String),
779}
780
781impl TryFrom<String> for StatusBarElement {
782 type Error = String;
783 fn try_from(s: String) -> Result<Self, String> {
784 let inner = s
786 .strip_prefix('{')
787 .and_then(|s| s.strip_suffix('}'))
788 .unwrap_or(&s);
789 match inner {
790 "filename" => Ok(Self::Filename),
791 "read_only" => Ok(Self::ReadOnly),
792 "cursor" => Ok(Self::Cursor),
793 "cursor:compact" => Ok(Self::CursorCompact),
794 "diagnostics" => Ok(Self::Diagnostics),
795 "cursor_count" => Ok(Self::CursorCount),
796 "messages" => Ok(Self::Messages),
797 "chord" => Ok(Self::Chord),
798 "line_ending" => Ok(Self::LineEnding),
799 "encoding" => Ok(Self::Encoding),
800 "language" => Ok(Self::Language),
801 "lsp" => Ok(Self::Lsp),
802 "warnings" => Ok(Self::Warnings),
803 "update" => Ok(Self::Update),
804 "palette" => Ok(Self::Palette),
805 "clock" => Ok(Self::Clock),
806 "remote" => Ok(Self::RemoteIndicator),
807 "trust" => Ok(Self::WorkspaceTrust),
808 _ => {
809 if inner.contains(':') {
811 Ok(Self::CustomToken(inner.to_string()))
812 } else {
813 Err(format!("Unknown status bar element: {}", s))
814 }
815 }
816 }
817 }
818}
819
820impl From<StatusBarElement> for String {
821 fn from(e: StatusBarElement) -> String {
822 match e {
823 StatusBarElement::Filename => "{filename}".to_string(),
824 StatusBarElement::ReadOnly => "{read_only}".to_string(),
825 StatusBarElement::Cursor => "{cursor}".to_string(),
826 StatusBarElement::CursorCompact => "{cursor:compact}".to_string(),
827 StatusBarElement::Diagnostics => "{diagnostics}".to_string(),
828 StatusBarElement::CursorCount => "{cursor_count}".to_string(),
829 StatusBarElement::Messages => "{messages}".to_string(),
830 StatusBarElement::Chord => "{chord}".to_string(),
831 StatusBarElement::LineEnding => "{line_ending}".to_string(),
832 StatusBarElement::Encoding => "{encoding}".to_string(),
833 StatusBarElement::Language => "{language}".to_string(),
834 StatusBarElement::Lsp => "{lsp}".to_string(),
835 StatusBarElement::Warnings => "{warnings}".to_string(),
836 StatusBarElement::Update => "{update}".to_string(),
837 StatusBarElement::Palette => "{palette}".to_string(),
838 StatusBarElement::Clock => "{clock}".to_string(),
839 StatusBarElement::RemoteIndicator => "{remote}".to_string(),
840 StatusBarElement::WorkspaceTrust => "{trust}".to_string(),
841 StatusBarElement::CustomToken(name) => format!("{{{}}}", name),
842 }
843 }
844}
845
846impl schemars::JsonSchema for StatusBarElement {
847 fn schema_name() -> std::borrow::Cow<'static, str> {
848 std::borrow::Cow::Borrowed("StatusBarElement")
849 }
850 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
851 schemars::json_schema!({
852 "type": "string",
853 "x-dual-list-options": [
854 {"value": "{filename}", "name": "Filename"},
855 {"value": "{read_only}", "name": "Read-Only"},
856 {"value": "{cursor}", "name": "Cursor"},
857 {"value": "{cursor:compact}", "name": "Cursor (compact)"},
858 {"value": "{diagnostics}", "name": "Diagnostics"},
859 {"value": "{cursor_count}", "name": "Cursor Count"},
860 {"value": "{messages}", "name": "Messages"},
861 {"value": "{chord}", "name": "Chord"},
862 {"value": "{line_ending}", "name": "Line Ending"},
863 {"value": "{encoding}", "name": "Encoding"},
864 {"value": "{language}", "name": "Language"},
865 {"value": "{lsp}", "name": "LSP"},
866 {"value": "{warnings}", "name": "Warnings"},
867 {"value": "{update}", "name": "Update"},
868 {"value": "{palette}", "name": "Palette"},
869 {"value": "{clock}", "name": "Clock"},
870 {"value": "{remote}", "name": "Remote Indicator"},
871 {"value": "{trust}", "name": "Workspace Trust"}
872 ]
873 })
874 }
875}
876
877fn default_status_bar_left() -> Vec<StatusBarElement> {
878 vec![
888 StatusBarElement::WorkspaceTrust,
889 StatusBarElement::RemoteIndicator,
890 StatusBarElement::Cursor,
891 StatusBarElement::Diagnostics,
892 StatusBarElement::CursorCount,
893 StatusBarElement::Messages,
894 ]
895}
896
897fn default_status_bar_right() -> Vec<StatusBarElement> {
898 vec![
899 StatusBarElement::ReadOnly,
904 StatusBarElement::LineEnding,
905 StatusBarElement::Encoding,
906 StatusBarElement::Language,
907 StatusBarElement::Lsp,
908 StatusBarElement::Warnings,
909 StatusBarElement::Update,
910 StatusBarElement::Palette,
911 ]
912}
913
914#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
929pub struct StatusBarConfig {
930 #[serde(default = "default_status_bar_left")]
933 #[schemars(extend("x-section" = "Status Bar", "x-dual-list-sibling" = "/editor/status_bar/right", "x-dynamically-extendable-status-bar-elements" = true))]
934 pub left: Vec<StatusBarElement>,
935
936 #[serde(default = "default_status_bar_right")]
939 #[schemars(extend("x-section" = "Status Bar", "x-dual-list-sibling" = "/editor/status_bar/left", "x-dynamically-extendable-status-bar-elements" = true))]
940 pub right: Vec<StatusBarElement>,
941
942 #[serde(default = "default_status_bar_separator")]
948 #[schemars(extend("x-section" = "Status Bar"))]
949 pub separator: String,
950}
951
952fn default_status_bar_separator() -> String {
953 "".to_string()
954}
955
956pub fn default_indentation_guide_glyph() -> String {
957 "▏".to_string()
958}
959
960pub fn normalize_indentation_guide_glyph(value: &str) -> String {
961 let trimmed = value.trim();
962 if trimmed.is_empty() {
963 default_indentation_guide_glyph()
964 } else {
965 trimmed.to_string()
966 }
967}
968
969fn deserialize_indentation_guide_glyph<'de, D>(deserializer: D) -> Result<String, D::Error>
970where
971 D: serde::Deserializer<'de>,
972{
973 let value = String::deserialize(deserializer)?;
974 Ok(normalize_indentation_guide_glyph(&value))
975}
976
977impl Default for StatusBarConfig {
978 fn default() -> Self {
979 Self {
980 left: default_status_bar_left(),
981 right: default_status_bar_right(),
982 separator: default_status_bar_separator(),
983 }
984 }
985}
986
987#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
989pub struct EditorConfig {
990 #[serde(default = "default_true")]
997 #[schemars(extend("x-section" = "Display"))]
998 pub animations: bool,
999
1000 #[serde(default = "default_true")]
1004 #[schemars(extend("x-section" = "Display"))]
1005 pub cursor_jump_animation: bool,
1006
1007 #[serde(default = "default_true")]
1009 #[schemars(extend("x-section" = "Display"))]
1010 pub line_numbers: bool,
1011
1012 #[serde(default = "default_false")]
1014 #[schemars(extend("x-section" = "Display"))]
1015 pub relative_line_numbers: bool,
1016
1017 #[serde(default = "default_true")]
1019 #[schemars(extend("x-section" = "Display"))]
1020 pub highlight_current_line: bool,
1021
1022 #[serde(default = "default_true")]
1024 #[schemars(extend("x-section" = "Display"))]
1025 pub highlight_occurrences: bool,
1026
1027 #[serde(default = "default_false")]
1032 #[schemars(extend("x-section" = "Display"))]
1033 pub hide_current_line_on_selection: bool,
1034
1035 #[serde(default = "default_false")]
1037 #[schemars(extend("x-section" = "Display"))]
1038 pub highlight_current_column: bool,
1039
1040 #[serde(default = "default_true")]
1042 #[schemars(extend("x-section" = "Display"))]
1043 pub line_wrap: bool,
1044
1045 #[serde(default = "default_true")]
1047 #[schemars(extend("x-section" = "Display"))]
1048 pub wrap_indent: bool,
1049
1050 #[serde(default)]
1056 #[schemars(extend("x-section" = "Display"))]
1057 pub wrap_column: Option<usize>,
1058
1059 #[serde(default = "default_page_width")]
1063 #[schemars(extend("x-section" = "Display"))]
1064 pub page_width: Option<usize>,
1065
1066 #[serde(default = "default_true")]
1068 #[schemars(extend("x-section" = "Display"))]
1069 pub syntax_highlighting: bool,
1070
1071 #[serde(default = "default_true")]
1076 #[schemars(extend("x-section" = "Display"))]
1077 pub show_menu_bar: bool,
1078
1079 #[serde(default = "default_false")]
1085 #[schemars(extend("x-section" = "Display"))]
1086 pub screensaver_enabled: bool,
1087
1088 #[serde(default = "default_screensaver_idle_minutes")]
1092 #[schemars(extend("x-section" = "Display"))]
1093 pub screensaver_idle_minutes: u32,
1094
1095 #[serde(default = "default_true")]
1100 #[schemars(extend("x-section" = "Display"))]
1101 pub menu_bar_mnemonics: bool,
1102
1103 #[serde(default = "default_true")]
1108 #[schemars(extend("x-section" = "Display"))]
1109 pub show_tab_bar: bool,
1110
1111 #[serde(default = "default_true")]
1116 #[schemars(extend("x-section" = "Display"))]
1117 pub show_status_bar: bool,
1118
1119 #[serde(default)]
1122 #[schemars(extend("x-section" = "Status Bar"))]
1123 pub status_bar: StatusBarConfig,
1124
1125 #[serde(default = "default_false")]
1132 #[schemars(extend("x-section" = "Display"))]
1133 pub show_prompt_line: bool,
1134
1135 #[serde(default = "default_true")]
1139 #[schemars(extend("x-section" = "Display"))]
1140 pub show_vertical_scrollbar: bool,
1141
1142 #[serde(default = "default_false")]
1147 #[schemars(extend("x-section" = "Display"))]
1148 pub show_horizontal_scrollbar: bool,
1149
1150 #[serde(default = "default_true")]
1154 #[schemars(extend("x-section" = "Display"))]
1155 pub show_tilde: bool,
1156
1157 #[serde(default = "default_false")]
1162 #[schemars(extend("x-section" = "Display"))]
1163 pub use_terminal_bg: bool,
1164
1165 #[serde(default = "default_true")]
1171 #[schemars(extend("x-section" = "Display"))]
1172 pub set_window_title: bool,
1173
1174 #[serde(default = "default_true")]
1181 #[schemars(extend("x-section" = "Display"))]
1182 pub terminal_auto_title: bool,
1183
1184 #[serde(default)]
1188 #[schemars(extend("x-section" = "Display"))]
1189 pub cursor_style: CursorStyle,
1190
1191 #[serde(default)]
1196 #[schemars(extend("x-section" = "Display"))]
1197 pub rulers: Vec<usize>,
1198
1199 #[serde(default)]
1210 #[schemars(extend("x-section" = "Display"))]
1211 pub indentation_guide: IndentationGuideMode,
1212
1213 #[serde(
1219 default = "default_indentation_guide_glyph",
1220 deserialize_with = "deserialize_indentation_guide_glyph"
1221 )]
1222 #[schemars(extend("x-section" = "Display"))]
1223 pub indentation_guide_glyph: String,
1224
1225 #[serde(default = "default_true")]
1231 #[schemars(extend("x-section" = "Whitespace"))]
1232 pub whitespace_show: bool,
1233
1234 #[serde(default = "default_false")]
1238 #[schemars(extend("x-section" = "Whitespace"))]
1239 pub whitespace_spaces_leading: bool,
1240
1241 #[serde(default = "default_false")]
1245 #[schemars(extend("x-section" = "Whitespace"))]
1246 pub whitespace_spaces_inner: bool,
1247
1248 #[serde(default = "default_false")]
1252 #[schemars(extend("x-section" = "Whitespace"))]
1253 pub whitespace_spaces_trailing: bool,
1254
1255 #[serde(default = "default_true")]
1259 #[schemars(extend("x-section" = "Whitespace"))]
1260 pub whitespace_tabs_leading: bool,
1261
1262 #[serde(default = "default_true")]
1266 #[schemars(extend("x-section" = "Whitespace"))]
1267 pub whitespace_tabs_inner: bool,
1268
1269 #[serde(default = "default_true")]
1273 #[schemars(extend("x-section" = "Whitespace"))]
1274 pub whitespace_tabs_trailing: bool,
1275
1276 #[serde(default = "default_false")]
1282 #[schemars(extend("x-section" = "Editing"))]
1283 pub use_tabs: bool,
1284
1285 #[serde(default = "default_tab_size")]
1288 #[schemars(extend("x-section" = "Editing"))]
1289 pub tab_size: usize,
1290
1291 #[serde(default = "default_true")]
1293 #[schemars(extend("x-section" = "Editing"))]
1294 pub auto_indent: bool,
1295
1296 #[serde(default = "default_true")]
1303 #[schemars(extend("x-section" = "Editing"))]
1304 pub auto_close: bool,
1305
1306 #[serde(default = "default_true")]
1311 #[schemars(extend("x-section" = "Editing"))]
1312 pub auto_surround: bool,
1313
1314 #[serde(default = "default_scroll_offset")]
1316 #[schemars(extend("x-section" = "Editing"))]
1317 pub scroll_offset: usize,
1318
1319 #[serde(default)]
1324 #[schemars(extend("x-section" = "Editing"))]
1325 pub default_line_ending: LineEndingOption,
1326
1327 #[serde(default = "default_false")]
1330 #[schemars(extend("x-section" = "Editing"))]
1331 pub trim_trailing_whitespace_on_save: bool,
1332
1333 #[serde(default = "default_false")]
1336 #[schemars(extend("x-section" = "Editing"))]
1337 pub ensure_final_newline_on_save: bool,
1338
1339 #[serde(default = "default_true")]
1347 #[schemars(extend("x-section" = "Editing"))]
1348 pub auto_read_only: bool,
1349
1350 #[serde(default = "default_true")]
1354 #[schemars(extend("x-section" = "Bracket Matching"))]
1355 pub highlight_matching_brackets: bool,
1356
1357 #[serde(default = "default_true")]
1361 #[schemars(extend("x-section" = "Bracket Matching"))]
1362 pub rainbow_brackets: bool,
1363
1364 #[serde(default = "default_false")]
1371 #[schemars(extend("x-section" = "Completion"))]
1372 pub completion_popup_auto_show: bool,
1373
1374 #[serde(default = "default_true")]
1380 #[schemars(extend("x-section" = "Completion"))]
1381 pub quick_suggestions: bool,
1382
1383 #[serde(default = "default_quick_suggestions_delay")]
1389 #[schemars(extend("x-section" = "Completion"))]
1390 pub quick_suggestions_delay_ms: u64,
1391
1392 #[serde(default = "default_true")]
1396 #[schemars(extend("x-section" = "Completion"))]
1397 pub suggest_on_trigger_characters: bool,
1398
1399 #[serde(default = "default_true")]
1402 #[schemars(extend("x-section" = "LSP"))]
1403 pub enable_inlay_hints: bool,
1404
1405 #[serde(default = "default_false")]
1409 #[schemars(extend("x-section" = "LSP"))]
1410 pub enable_semantic_tokens_full: bool,
1411
1412 #[serde(default = "default_false")]
1417 #[schemars(extend("x-section" = "Diagnostics"))]
1418 pub diagnostics_inline_text: bool,
1419
1420 #[serde(default = "default_mouse_hover_enabled")]
1431 #[schemars(extend("x-section" = "Mouse"))]
1432 pub mouse_hover_enabled: bool,
1433
1434 #[serde(default = "default_mouse_hover_delay")]
1438 #[schemars(extend("x-section" = "Mouse"))]
1439 pub mouse_hover_delay_ms: u64,
1440
1441 #[serde(default = "default_double_click_time")]
1445 #[schemars(extend("x-section" = "Mouse"))]
1446 pub double_click_time_ms: u64,
1447
1448 #[serde(default = "default_false")]
1453 #[schemars(extend("x-section" = "Recovery"))]
1454 pub auto_save_enabled: bool,
1455
1456 #[serde(default = "default_auto_save_interval")]
1461 #[schemars(extend("x-section" = "Recovery"))]
1462 pub auto_save_interval_secs: u32,
1463
1464 #[serde(default = "default_true", alias = "persist_unnamed_buffers")]
1471 #[schemars(extend("x-section" = "Recovery"))]
1472 pub hot_exit: bool,
1473
1474 #[serde(default)]
1481 #[schemars(extend("x-section" = "Startup"))]
1482 pub confirm_quit: bool,
1483
1484 #[serde(default = "default_true")]
1495 #[schemars(extend("x-section" = "Startup"))]
1496 pub restore_previous_session: bool,
1497
1498 #[serde(default = "default_true")]
1509 #[schemars(extend("x-section" = "Startup"))]
1510 pub skip_session_restore_when_files_passed: bool,
1511
1512 #[serde(default = "default_true")]
1520 #[schemars(extend("x-section" = "Startup"))]
1521 pub auto_create_empty_buffer_on_last_buffer_close: bool,
1522
1523 #[serde(default = "default_true")]
1528 #[schemars(extend("x-section" = "Recovery"))]
1529 pub recovery_enabled: bool,
1530
1531 #[serde(default = "default_auto_recovery_save_interval")]
1536 #[schemars(extend("x-section" = "Recovery"))]
1537 pub auto_recovery_save_interval_secs: u32,
1538
1539 #[serde(default = "default_auto_revert_poll_interval")]
1544 #[schemars(extend("x-section" = "Recovery"))]
1545 pub auto_revert_poll_interval_ms: u64,
1546
1547 #[serde(default = "default_true")]
1553 #[schemars(extend("x-section" = "Keyboard"))]
1554 pub keyboard_disambiguate_escape_codes: bool,
1555
1556 #[serde(default = "default_false")]
1561 #[schemars(extend("x-section" = "Keyboard"))]
1562 pub keyboard_report_event_types: bool,
1563
1564 #[serde(default = "default_true")]
1569 #[schemars(extend("x-section" = "Keyboard"))]
1570 pub keyboard_report_alternate_keys: bool,
1571
1572 #[serde(default = "default_false")]
1578 #[schemars(extend("x-section" = "Keyboard"))]
1579 pub keyboard_report_all_keys_as_escape_codes: bool,
1580
1581 #[serde(default = "default_highlight_timeout")]
1584 #[schemars(extend("x-section" = "Performance"))]
1585 pub highlight_timeout_ms: u64,
1586
1587 #[serde(default = "default_snapshot_interval")]
1589 #[schemars(extend("x-section" = "Performance"))]
1590 pub snapshot_interval: usize,
1591
1592 #[serde(default = "default_highlight_context_bytes")]
1597 #[schemars(extend("x-section" = "Performance"))]
1598 pub highlight_context_bytes: usize,
1599
1600 #[serde(default = "default_large_file_threshold")]
1607 #[schemars(extend("x-section" = "Performance"))]
1608 pub large_file_threshold_bytes: u64,
1609
1610 #[serde(default = "default_estimated_line_length")]
1614 #[schemars(extend("x-section" = "Performance"))]
1615 pub estimated_line_length: usize,
1616
1617 #[serde(default = "default_read_concurrency")]
1622 #[schemars(extend("x-section" = "Performance"))]
1623 pub read_concurrency: usize,
1624
1625 #[serde(default = "default_file_tree_poll_interval")]
1630 #[schemars(extend("x-section" = "Performance"))]
1631 pub file_tree_poll_interval_ms: u64,
1632}
1633
1634fn default_tab_size() -> usize {
1635 4
1636}
1637
1638pub const LARGE_FILE_THRESHOLD_BYTES: u64 = 1024 * 1024; fn default_large_file_threshold() -> u64 {
1644 LARGE_FILE_THRESHOLD_BYTES
1645}
1646
1647pub const INDENT_FOLD_MAX_SCAN_LINES: usize = 10_000;
1650
1651pub const INDENT_FOLD_INDICATOR_MAX_SCAN: usize = 50;
1654
1655pub const INDENT_FOLD_MAX_UPWARD_SCAN: usize = 200;
1658
1659fn default_read_concurrency() -> usize {
1660 64
1661}
1662
1663fn default_true() -> bool {
1664 true
1665}
1666
1667fn default_screensaver_idle_minutes() -> u32 {
1668 5
1669}
1670
1671fn default_false() -> bool {
1672 false
1673}
1674
1675fn default_quick_suggestions_delay() -> u64 {
1676 150 }
1678
1679fn default_scroll_offset() -> usize {
1680 3
1681}
1682
1683fn default_highlight_timeout() -> u64 {
1684 5
1685}
1686
1687fn default_snapshot_interval() -> usize {
1688 100
1689}
1690
1691fn default_estimated_line_length() -> usize {
1692 80
1693}
1694
1695fn default_auto_save_interval() -> u32 {
1696 30 }
1698
1699fn default_auto_recovery_save_interval() -> u32 {
1700 2 }
1702
1703fn default_highlight_context_bytes() -> usize {
1704 10_000 }
1706
1707fn default_mouse_hover_enabled() -> bool {
1708 !cfg!(windows)
1709}
1710
1711fn default_mouse_hover_delay() -> u64 {
1712 500 }
1714
1715fn default_double_click_time() -> u64 {
1716 500 }
1718
1719fn default_auto_revert_poll_interval() -> u64 {
1720 2000 }
1722
1723fn default_file_tree_poll_interval() -> u64 {
1724 3000 }
1726
1727impl Default for EditorConfig {
1728 fn default() -> Self {
1729 Self {
1730 use_tabs: false,
1731 tab_size: default_tab_size(),
1732 auto_indent: true,
1733 auto_close: true,
1734 auto_surround: true,
1735 animations: true,
1736 cursor_jump_animation: true,
1737 line_numbers: true,
1738 relative_line_numbers: false,
1739 scroll_offset: default_scroll_offset(),
1740 syntax_highlighting: true,
1741 highlight_current_line: true,
1742 highlight_occurrences: true,
1743 hide_current_line_on_selection: false,
1744 highlight_current_column: false,
1745 line_wrap: true,
1746 wrap_indent: true,
1747 wrap_column: None,
1748 page_width: default_page_width(),
1749 highlight_timeout_ms: default_highlight_timeout(),
1750 snapshot_interval: default_snapshot_interval(),
1751 large_file_threshold_bytes: default_large_file_threshold(),
1752 estimated_line_length: default_estimated_line_length(),
1753 enable_inlay_hints: true,
1754 enable_semantic_tokens_full: false,
1755 diagnostics_inline_text: false,
1756 auto_save_enabled: false,
1757 auto_save_interval_secs: default_auto_save_interval(),
1758 hot_exit: true,
1759 confirm_quit: false,
1760 restore_previous_session: true,
1761 skip_session_restore_when_files_passed: true,
1762 auto_create_empty_buffer_on_last_buffer_close: true,
1763 recovery_enabled: true,
1764 auto_recovery_save_interval_secs: default_auto_recovery_save_interval(),
1765 highlight_context_bytes: default_highlight_context_bytes(),
1766 mouse_hover_enabled: default_mouse_hover_enabled(),
1767 mouse_hover_delay_ms: default_mouse_hover_delay(),
1768 double_click_time_ms: default_double_click_time(),
1769 auto_revert_poll_interval_ms: default_auto_revert_poll_interval(),
1770 read_concurrency: default_read_concurrency(),
1771 file_tree_poll_interval_ms: default_file_tree_poll_interval(),
1772 default_line_ending: LineEndingOption::default(),
1773 trim_trailing_whitespace_on_save: false,
1774 ensure_final_newline_on_save: false,
1775 auto_read_only: true,
1776 highlight_matching_brackets: true,
1777 rainbow_brackets: true,
1778 cursor_style: CursorStyle::default(),
1779 keyboard_disambiguate_escape_codes: true,
1780 keyboard_report_event_types: false,
1781 keyboard_report_alternate_keys: true,
1782 keyboard_report_all_keys_as_escape_codes: false,
1783 completion_popup_auto_show: false,
1784 quick_suggestions: true,
1785 quick_suggestions_delay_ms: default_quick_suggestions_delay(),
1786 suggest_on_trigger_characters: true,
1787 show_menu_bar: true,
1788 screensaver_enabled: false,
1789 screensaver_idle_minutes: default_screensaver_idle_minutes(),
1790 menu_bar_mnemonics: true,
1791 show_tab_bar: true,
1792 show_status_bar: true,
1793 status_bar: StatusBarConfig::default(),
1794 show_prompt_line: false,
1795 show_vertical_scrollbar: true,
1796 show_horizontal_scrollbar: false,
1797 show_tilde: true,
1798 use_terminal_bg: false,
1799 set_window_title: true,
1800 terminal_auto_title: true,
1801 rulers: Vec::new(),
1802 indentation_guide: IndentationGuideMode::None,
1803 indentation_guide_glyph: default_indentation_guide_glyph(),
1804 whitespace_show: true,
1805 whitespace_spaces_leading: false,
1806 whitespace_spaces_inner: false,
1807 whitespace_spaces_trailing: false,
1808 whitespace_tabs_leading: true,
1809 whitespace_tabs_inner: true,
1810 whitespace_tabs_trailing: true,
1811 }
1812 }
1813}
1814
1815#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
1817#[serde(rename_all = "snake_case")]
1818pub enum FileExplorerSide {
1819 #[default]
1820 Left,
1821 Right,
1822}
1823
1824#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1826pub struct FileExplorerConfig {
1827 #[serde(default = "default_true")]
1829 pub respect_gitignore: bool,
1830
1831 #[serde(default = "default_false")]
1833 pub show_hidden: bool,
1834
1835 #[serde(default = "default_false")]
1837 pub show_gitignored: bool,
1838
1839 #[serde(default)]
1841 pub custom_ignore_patterns: Vec<String>,
1842
1843 #[serde(default = "default_explorer_width")]
1849 pub width: ExplorerWidth,
1850
1851 #[serde(default = "default_true")]
1858 pub preview_tabs: bool,
1859
1860 #[serde(default = "default_explorer_side")]
1863 pub side: FileExplorerSide,
1864
1865 #[serde(default = "default_true")]
1871 pub auto_open_on_last_buffer_close: bool,
1872
1873 #[serde(default = "default_false")]
1879 pub follow_active_buffer: bool,
1880
1881 #[serde(default = "default_true")]
1888 pub compact_directories: bool,
1889
1890 #[serde(default = "default_tree_indicator_collapsed")]
1896 pub tree_indicator_collapsed: String,
1897
1898 #[serde(default = "default_tree_indicator_expanded")]
1904 pub tree_indicator_expanded: String,
1905}
1906
1907#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1935pub enum ExplorerWidth {
1936 Percent(u8),
1937 Columns(u16),
1938}
1939
1940impl ExplorerWidth {
1941 pub const DEFAULT: Self = Self::Percent(30);
1943
1944 pub const MIN_COLS: u16 = 5;
1950
1951 pub fn to_cols(self, terminal_width: u16) -> u16 {
1959 let raw = match self {
1960 Self::Percent(pct) => ((terminal_width as u32 * pct as u32) / 100) as u16,
1961 Self::Columns(cols) => cols,
1962 };
1963 raw.max(Self::MIN_COLS).min(terminal_width)
1964 }
1965}
1966
1967impl Default for ExplorerWidth {
1968 fn default() -> Self {
1969 Self::DEFAULT
1970 }
1971}
1972
1973impl std::fmt::Display for ExplorerWidth {
1974 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1975 match self {
1976 Self::Percent(n) => write!(f, "{}%", n),
1977 Self::Columns(n) => write!(f, "{}", n),
1978 }
1979 }
1980}
1981
1982#[derive(Debug)]
1984pub struct ExplorerWidthParseError(String);
1985
1986impl std::fmt::Display for ExplorerWidthParseError {
1987 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1988 write!(f, "{}", self.0)
1989 }
1990}
1991
1992impl std::error::Error for ExplorerWidthParseError {}
1993
1994impl std::str::FromStr for ExplorerWidth {
1995 type Err = ExplorerWidthParseError;
1996
1997 fn from_str(s: &str) -> Result<Self, Self::Err> {
1998 let s = s.trim();
1999 if s.is_empty() {
2000 return Err(ExplorerWidthParseError(
2001 "explorer width: empty string".into(),
2002 ));
2003 }
2004 if let Some(rest) = s.strip_suffix('%') {
2005 let n: u16 = rest.trim().parse().map_err(|_| {
2006 ExplorerWidthParseError(format!("explorer width: {:?} is not a valid percent", s))
2007 })?;
2008 if n > 100 {
2009 return Err(ExplorerWidthParseError(format!(
2010 "explorer width: {}% exceeds 100%",
2011 n
2012 )));
2013 }
2014 Ok(Self::Percent(n as u8))
2015 } else {
2016 let n: u16 = s.parse().map_err(|_| {
2017 ExplorerWidthParseError(format!(
2018 "explorer width: {:?} is neither a percent (e.g. \"30%\") nor a column count (e.g. \"24\")",
2019 s
2020 ))
2021 })?;
2022 Ok(Self::Columns(n))
2023 }
2024 }
2025}
2026
2027impl serde::Serialize for ExplorerWidth {
2028 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
2029 s.collect_str(self)
2030 }
2031}
2032
2033impl<'de> serde::Deserialize<'de> for ExplorerWidth {
2034 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
2035 let raw = serde_json::Value::deserialize(d)?;
2036 explorer_width::from_value(&raw)
2037 }
2038}
2039
2040impl schemars::JsonSchema for ExplorerWidth {
2041 fn schema_name() -> std::borrow::Cow<'static, str> {
2042 std::borrow::Cow::Borrowed("ExplorerWidth")
2043 }
2044
2045 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
2046 schemars::json_schema!({
2050 "type": "string",
2051 "pattern": r"^(100%|[1-9]?[0-9]%|\d+)$",
2052 "description": "Either a percent like \"30%\" (0–100) or an absolute column count like \"24\".",
2053 })
2054 }
2055}
2056
2057fn default_explorer_width() -> ExplorerWidth {
2058 ExplorerWidth::DEFAULT
2059}
2060
2061fn default_explorer_side() -> FileExplorerSide {
2062 FileExplorerSide::default()
2063}
2064
2065fn default_tree_indicator_collapsed() -> String {
2066 ">".to_string()
2067}
2068
2069fn default_tree_indicator_expanded() -> String {
2070 "▼".to_string()
2071}
2072
2073pub fn default_explorer_width_value() -> ExplorerWidth {
2075 ExplorerWidth::DEFAULT
2076}
2077
2078pub(crate) mod explorer_width {
2082 use super::ExplorerWidth;
2083 use serde::de::{self, Deserialize, Deserializer};
2084 use std::str::FromStr;
2085
2086 pub fn deserialize_optional<'de, D>(d: D) -> Result<Option<ExplorerWidth>, D::Error>
2091 where
2092 D: Deserializer<'de>,
2093 {
2094 let raw = Option::<serde_json::Value>::deserialize(d)?;
2095 match raw {
2096 None | Some(serde_json::Value::Null) => Ok(None),
2097 Some(v) => from_value(&v).map(Some),
2098 }
2099 }
2100
2101 pub(super) fn from_value<E: de::Error>(v: &serde_json::Value) -> Result<ExplorerWidth, E> {
2102 match v {
2103 serde_json::Value::String(s) => ExplorerWidth::from_str(s).map_err(E::custom),
2104 serde_json::Value::Number(n) => {
2105 if let Some(u) = n.as_u64() {
2106 if u > 100 {
2110 return Err(E::custom(format!(
2111 "explorer width: {} exceeds 100 (percent). Use \"{}\" for columns.",
2112 u, u
2113 )));
2114 }
2115 Ok(ExplorerWidth::Percent(u as u8))
2116 } else if let Some(f) = n.as_f64() {
2117 let pct = if (0.0..=1.0).contains(&f) {
2119 f * 100.0
2120 } else {
2121 f
2122 };
2123 if !(0.0..=100.0).contains(&pct) {
2124 return Err(E::custom(format!(
2125 "explorer width: percent {} out of range 0..=100",
2126 pct
2127 )));
2128 }
2129 Ok(ExplorerWidth::Percent(pct.round() as u8))
2130 } else {
2131 Err(E::custom("explorer width: unsupported number"))
2132 }
2133 }
2134 _ => Err(E::custom(
2135 "explorer width: expected \"30%\", \"24\" (columns), or a number",
2136 )),
2137 }
2138 }
2139}
2140
2141#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2152pub struct ClipboardConfig {
2153 #[serde(default = "default_true")]
2156 pub use_osc52: bool,
2157
2158 #[serde(default = "default_true")]
2161 pub use_system_clipboard: bool,
2162}
2163
2164impl Default for ClipboardConfig {
2165 fn default() -> Self {
2166 Self {
2167 use_osc52: true,
2168 use_system_clipboard: true,
2169 }
2170 }
2171}
2172
2173#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2175pub struct TerminalConfig {
2176 #[serde(default = "default_true")]
2179 pub jump_to_end_on_output: bool,
2180
2181 #[serde(default)]
2193 pub shell: Option<TerminalShellConfig>,
2194
2195 #[serde(default = "default_true")]
2207 pub skip_app_execution_alias: bool,
2208
2209 #[serde(default = "default_true")]
2218 pub resume_agents: bool,
2219}
2220
2221impl Default for TerminalConfig {
2222 fn default() -> Self {
2223 Self {
2224 jump_to_end_on_output: true,
2225 shell: None,
2226 skip_app_execution_alias: true,
2227 resume_agents: true,
2228 }
2229 }
2230}
2231
2232#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2234pub struct TerminalShellConfig {
2235 pub command: String,
2238
2239 #[serde(default)]
2241 pub args: Vec<String>,
2242}
2243
2244#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2246pub struct WarningsConfig {
2247 #[serde(default = "default_true")]
2250 pub show_status_indicator: bool,
2251}
2252
2253impl Default for WarningsConfig {
2254 fn default() -> Self {
2255 Self {
2256 show_status_indicator: true,
2257 }
2258 }
2259}
2260
2261#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2263pub struct PackagesConfig {
2264 #[serde(default = "default_package_sources")]
2267 pub sources: Vec<String>,
2268}
2269
2270fn default_package_sources() -> Vec<String> {
2271 vec!["https://github.com/sinelaw/fresh-plugins-registry".to_string()]
2272}
2273
2274impl Default for PackagesConfig {
2275 fn default() -> Self {
2276 Self {
2277 sources: default_package_sources(),
2278 }
2279 }
2280}
2281
2282pub use fresh_core::config::PluginConfig;
2284
2285impl Default for FileExplorerConfig {
2286 fn default() -> Self {
2287 Self {
2288 respect_gitignore: true,
2289 show_hidden: false,
2290 show_gitignored: false,
2291 custom_ignore_patterns: Vec::new(),
2292 width: default_explorer_width(),
2293 preview_tabs: true,
2294 side: default_explorer_side(),
2295 auto_open_on_last_buffer_close: true,
2296 follow_active_buffer: false,
2297 compact_directories: true,
2298 tree_indicator_collapsed: default_tree_indicator_collapsed(),
2299 tree_indicator_expanded: default_tree_indicator_expanded(),
2300 }
2301 }
2302}
2303
2304#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
2306pub struct FileBrowserConfig {
2307 #[serde(default = "default_false")]
2309 pub show_hidden: bool,
2310}
2311
2312#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2314pub struct KeyPress {
2315 pub key: String,
2317 #[serde(default)]
2319 pub modifiers: Vec<String>,
2320}
2321
2322#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2324#[schemars(extend("x-display-field" = "/action"))]
2325pub struct Keybinding {
2326 #[serde(default, skip_serializing_if = "String::is_empty")]
2328 pub key: String,
2329
2330 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2332 pub modifiers: Vec<String>,
2333
2334 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2337 pub keys: Vec<KeyPress>,
2338
2339 pub action: String,
2341
2342 #[serde(default)]
2344 pub args: HashMap<String, serde_json::Value>,
2345
2346 #[serde(default)]
2348 pub when: Option<String>,
2349}
2350
2351#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2353#[schemars(extend("x-display-field" = "/inherits"))]
2354pub struct KeymapConfig {
2355 #[serde(default, skip_serializing_if = "Option::is_none")]
2357 pub inherits: Option<String>,
2358
2359 #[serde(default)]
2361 pub bindings: Vec<Keybinding>,
2362}
2363
2364#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2366#[schemars(extend("x-display-field" = "/command"))]
2367pub struct FormatterConfig {
2368 pub command: String,
2370
2371 #[serde(default)]
2374 pub args: Vec<String>,
2375
2376 #[serde(default = "default_true")]
2379 pub stdin: bool,
2380
2381 #[serde(default = "default_on_save_timeout")]
2383 pub timeout_ms: u64,
2384}
2385
2386#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2388#[schemars(extend("x-display-field" = "/command"))]
2389pub struct OnSaveAction {
2390 pub command: String,
2393
2394 #[serde(default)]
2397 pub args: Vec<String>,
2398
2399 #[serde(default)]
2401 pub working_dir: Option<String>,
2402
2403 #[serde(default)]
2405 pub stdin: bool,
2406
2407 #[serde(default = "default_on_save_timeout")]
2409 pub timeout_ms: u64,
2410
2411 #[serde(default = "default_true")]
2414 pub enabled: bool,
2415}
2416
2417fn default_on_save_timeout() -> u64 {
2418 10000
2419}
2420
2421fn default_page_width() -> Option<usize> {
2422 Some(80)
2423}
2424
2425#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2427#[schemars(extend("x-display-field" = "/grammar"))]
2428pub struct LanguageConfig {
2429 #[serde(default)]
2431 pub extensions: Vec<String>,
2432
2433 #[serde(default)]
2435 pub filenames: Vec<String>,
2436
2437 #[serde(default)]
2439 pub grammar: String,
2440
2441 #[serde(default)]
2443 pub comment_prefix: Option<String>,
2444
2445 #[serde(default = "default_true")]
2447 pub auto_indent: bool,
2448
2449 #[serde(default)]
2452 pub auto_close: Option<bool>,
2453
2454 #[serde(default)]
2457 pub auto_surround: Option<bool>,
2458
2459 #[serde(default)]
2462 pub textmate_grammar: Option<std::path::PathBuf>,
2463
2464 #[serde(default = "default_true")]
2467 pub show_whitespace_tabs: bool,
2468
2469 #[serde(default)]
2474 pub line_wrap: Option<bool>,
2475
2476 #[serde(default)]
2480 pub wrap_column: Option<usize>,
2481
2482 #[serde(default)]
2487 pub page_view: Option<bool>,
2488
2489 #[serde(default)]
2494 pub page_width: Option<usize>,
2495
2496 #[serde(default)]
2500 pub use_tabs: Option<bool>,
2501
2502 #[serde(default)]
2506 pub tab_size: Option<usize>,
2507
2508 #[serde(default)]
2510 pub formatter: Option<FormatterConfig>,
2511
2512 #[serde(default)]
2514 pub format_on_save: bool,
2515
2516 #[serde(default)]
2520 pub on_save: Vec<OnSaveAction>,
2521
2522 #[serde(default)]
2532 pub word_characters: Option<String>,
2533
2534 #[serde(default)]
2539 pub indent: Option<IndentRulesConfig>,
2540}
2541
2542#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
2557pub struct IndentRulesConfig {
2558 #[serde(default)]
2562 pub increase_indent_pattern: Option<String>,
2563
2564 #[serde(default)]
2567 pub decrease_indent_pattern: Option<String>,
2568
2569 #[serde(default)]
2573 pub indent_next_line_pattern: Option<String>,
2574
2575 #[serde(default)]
2579 pub dedent_next_line_pattern: Option<String>,
2580
2581 #[serde(default)]
2585 pub self_close_pattern: Option<String>,
2586}
2587
2588impl IndentRulesConfig {
2589 pub fn is_empty(&self) -> bool {
2591 self.increase_indent_pattern.is_none()
2592 && self.decrease_indent_pattern.is_none()
2593 && self.indent_next_line_pattern.is_none()
2594 && self.dedent_next_line_pattern.is_none()
2595 && self.self_close_pattern.is_none()
2596 }
2597}
2598
2599#[cfg(any(feature = "runtime", feature = "wasm"))]
2603pub fn reload_indent_overrides(languages: &HashMap<String, LanguageConfig>) {
2604 use crate::primitives::indent_rules;
2605 indent_rules::clear_user_rules();
2606 for (id, lc) in languages {
2607 let Some(ind) = &lc.indent else { continue };
2608 if ind.is_empty() {
2609 continue;
2610 }
2611 indent_rules::set_user_rule(
2612 id,
2613 ind.increase_indent_pattern.as_deref(),
2614 ind.decrease_indent_pattern.as_deref(),
2615 ind.indent_next_line_pattern.as_deref(),
2616 ind.dedent_next_line_pattern.as_deref(),
2617 ind.self_close_pattern.as_deref(),
2618 );
2619 }
2620}
2621
2622#[derive(Debug, Clone)]
2629pub struct BufferConfig {
2630 pub tab_size: usize,
2632
2633 pub use_tabs: bool,
2635
2636 pub auto_indent: bool,
2638
2639 pub auto_close: bool,
2641
2642 pub auto_surround: bool,
2644
2645 pub line_wrap: bool,
2647
2648 pub wrap_column: Option<usize>,
2650
2651 pub whitespace: WhitespaceVisibility,
2653
2654 pub formatter: Option<FormatterConfig>,
2656
2657 pub format_on_save: bool,
2659
2660 pub on_save: Vec<OnSaveAction>,
2662
2663 pub textmate_grammar: Option<std::path::PathBuf>,
2665
2666 pub word_characters: String,
2669}
2670
2671impl BufferConfig {
2672 pub fn resolve(global_config: &Config, language_id: Option<&str>) -> Self {
2681 let editor = &global_config.editor;
2682
2683 let mut whitespace = WhitespaceVisibility::from_editor_config(editor);
2685 let mut config = BufferConfig {
2686 tab_size: editor.tab_size,
2687 use_tabs: editor.use_tabs,
2688 auto_indent: editor.auto_indent,
2689 auto_close: editor.auto_close,
2690 auto_surround: editor.auto_surround,
2691 line_wrap: editor.line_wrap,
2692 wrap_column: editor.wrap_column,
2693 whitespace,
2694 formatter: None,
2695 format_on_save: false,
2696 on_save: Vec::new(),
2697 textmate_grammar: None,
2698 word_characters: String::new(),
2699 };
2700
2701 let lang_config_ref = language_id
2705 .and_then(|id| global_config.languages.get(id))
2706 .or_else(|| {
2707 match language_id {
2709 None | Some("text") => global_config
2710 .default_language
2711 .as_deref()
2712 .and_then(|lang| global_config.languages.get(lang)),
2713 _ => None,
2714 }
2715 });
2716 if let Some(lang_config) = lang_config_ref {
2717 if let Some(ts) = lang_config.tab_size {
2719 config.tab_size = ts;
2720 }
2721
2722 if let Some(use_tabs) = lang_config.use_tabs {
2724 config.use_tabs = use_tabs;
2725 }
2726
2727 if let Some(line_wrap) = lang_config.line_wrap {
2729 config.line_wrap = line_wrap;
2730 }
2731
2732 if lang_config.wrap_column.is_some() {
2734 config.wrap_column = lang_config.wrap_column;
2735 }
2736
2737 config.auto_indent = lang_config.auto_indent;
2739
2740 if config.auto_close {
2742 if let Some(lang_auto_close) = lang_config.auto_close {
2743 config.auto_close = lang_auto_close;
2744 }
2745 }
2746
2747 if config.auto_surround {
2749 if let Some(lang_auto_surround) = lang_config.auto_surround {
2750 config.auto_surround = lang_auto_surround;
2751 }
2752 }
2753
2754 whitespace = whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
2756 config.whitespace = whitespace;
2757
2758 config.formatter = lang_config.formatter.clone();
2760
2761 config.format_on_save = lang_config.format_on_save;
2763
2764 config.on_save = lang_config.on_save.clone();
2766
2767 config.textmate_grammar = lang_config.textmate_grammar.clone();
2769
2770 if let Some(ref wc) = lang_config.word_characters {
2772 config.word_characters = wc.clone();
2773 }
2774 }
2775
2776 config
2777 }
2778
2779 pub fn indent_string(&self) -> String {
2784 if self.use_tabs {
2785 "\t".to_string()
2786 } else {
2787 " ".repeat(self.tab_size)
2788 }
2789 }
2790}
2791
2792#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
2794pub struct MenuConfig {
2795 #[serde(default)]
2797 pub menus: Vec<Menu>,
2798}
2799
2800pub use fresh_core::menu::{Menu, MenuItem};
2802
2803pub trait MenuExt {
2805 fn match_id(&self) -> &str;
2808
2809 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path);
2812}
2813
2814impl MenuExt for Menu {
2815 fn match_id(&self) -> &str {
2816 self.id.as_deref().unwrap_or(&self.label)
2817 }
2818
2819 fn expand_dynamic_items(&mut self, themes_dir: &std::path::Path) {
2820 self.items = self
2821 .items
2822 .iter()
2823 .map(|item| item.expand_dynamic(themes_dir))
2824 .collect();
2825 }
2826}
2827
2828pub trait MenuItemExt {
2830 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem;
2833}
2834
2835impl MenuItemExt for MenuItem {
2836 fn expand_dynamic(&self, themes_dir: &std::path::Path) -> MenuItem {
2837 match self {
2838 MenuItem::DynamicSubmenu { label, source } => {
2839 let items = generate_dynamic_items(source, themes_dir);
2840 MenuItem::Submenu {
2841 label: label.clone(),
2842 items,
2843 }
2844 }
2845 other => other.clone(),
2846 }
2847 }
2848}
2849
2850#[cfg(feature = "runtime")]
2852pub fn generate_dynamic_items(source: &str, themes_dir: &std::path::Path) -> Vec<MenuItem> {
2853 match source {
2854 "copy_with_theme" => {
2855 let loader = crate::view::theme::ThemeLoader::new(themes_dir.to_path_buf());
2857 let registry = loader.load_all(&[]);
2858 registry
2859 .list()
2860 .iter()
2861 .map(|info| {
2862 let mut args = HashMap::new();
2863 args.insert("theme".to_string(), serde_json::json!(info.key));
2864 MenuItem::Action {
2865 label: info.name.clone(),
2866 action: "copy_with_theme".to_string(),
2867 args,
2868 when: Some(context_keys::HAS_SELECTION.to_string()),
2869 checkbox: None,
2870 }
2871 })
2872 .collect()
2873 }
2874 _ => vec![MenuItem::Label {
2875 info: format!("Unknown source: {}", source),
2876 }],
2877 }
2878}
2879
2880#[cfg(not(feature = "runtime"))]
2882pub fn generate_dynamic_items(_source: &str, _themes_dir: &std::path::Path) -> Vec<MenuItem> {
2883 vec![]
2885}
2886
2887impl Default for Config {
2888 fn default() -> Self {
2889 Self {
2890 version: 0,
2891 theme: default_theme_name(),
2892 locale: LocaleName::default(),
2893 check_for_updates: true,
2894 editor: EditorConfig::default(),
2895 file_explorer: FileExplorerConfig::default(),
2896 file_browser: FileBrowserConfig::default(),
2897 clipboard: ClipboardConfig::default(),
2898 terminal: TerminalConfig::default(),
2899 keybindings: vec![], keybinding_maps: HashMap::new(), active_keybinding_map: default_keybinding_map_name(),
2902 languages: Self::default_languages(),
2903 default_language: None,
2904 lsp_enabled: true,
2905 lsp: Self::default_lsp_config(),
2906 universal_lsp: Self::default_universal_lsp_config(),
2907 warnings: WarningsConfig::default(),
2908 plugins: HashMap::new(),
2909 packages: PackagesConfig::default(),
2910 env: EnvConfig::default(),
2911 }
2912 }
2913}
2914
2915impl MenuConfig {
2916 pub fn translated() -> Self {
2918 Self {
2919 menus: Self::translated_menus(),
2920 }
2921 }
2922
2923 pub fn translated_menus() -> Vec<Menu> {
2929 vec![
2930 Menu {
2932 id: Some("File".to_string()),
2933 label: t!("menu.file").to_string(),
2934 when: None,
2935 items: vec![
2936 MenuItem::Action {
2937 label: t!("menu.file.new_file").to_string(),
2938 action: "new".to_string(),
2939 args: HashMap::new(),
2940 when: None,
2941 checkbox: None,
2942 },
2943 MenuItem::Action {
2944 label: t!("menu.file.open_file").to_string(),
2945 action: "open".to_string(),
2946 args: HashMap::new(),
2947 when: None,
2948 checkbox: None,
2949 },
2950 MenuItem::Separator { separator: true },
2951 MenuItem::Action {
2952 label: t!("menu.file.save").to_string(),
2953 action: "save".to_string(),
2954 args: HashMap::new(),
2955 when: Some(context_keys::HAS_BUFFER.to_string()),
2956 checkbox: None,
2957 },
2958 MenuItem::Action {
2959 label: t!("menu.file.save_as").to_string(),
2960 action: "save_as".to_string(),
2961 args: HashMap::new(),
2962 when: Some(context_keys::HAS_BUFFER.to_string()),
2963 checkbox: None,
2964 },
2965 MenuItem::Action {
2966 label: t!("menu.file.revert").to_string(),
2967 action: "revert".to_string(),
2968 args: HashMap::new(),
2969 when: Some(context_keys::HAS_BUFFER.to_string()),
2970 checkbox: None,
2971 },
2972 MenuItem::Action {
2973 label: t!("menu.file.reload_with_encoding").to_string(),
2974 action: "reload_with_encoding".to_string(),
2975 args: HashMap::new(),
2976 when: Some(context_keys::HAS_BUFFER.to_string()),
2977 checkbox: None,
2978 },
2979 MenuItem::Separator { separator: true },
2980 MenuItem::Action {
2981 label: t!("menu.file.close_buffer").to_string(),
2982 action: "close".to_string(),
2983 args: HashMap::new(),
2984 when: Some(context_keys::HAS_BUFFER.to_string()),
2985 checkbox: None,
2986 },
2987 MenuItem::Separator { separator: true },
2988 MenuItem::Action {
2989 label: t!("menu.file.switch_project").to_string(),
2990 action: "switch_project".to_string(),
2991 args: HashMap::new(),
2992 when: None,
2993 checkbox: None,
2994 },
2995 MenuItem::Separator { separator: true },
2996 MenuItem::Action {
2997 label: t!("menu.file.detach").to_string(),
2998 action: "detach".to_string(),
2999 args: HashMap::new(),
3000 when: Some(context_keys::SESSION_MODE.to_string()),
3001 checkbox: None,
3002 },
3003 MenuItem::Action {
3004 label: t!("menu.file.quit").to_string(),
3005 action: "quit".to_string(),
3006 args: HashMap::new(),
3007 when: None,
3008 checkbox: None,
3009 },
3010 ],
3011 },
3012 Menu {
3014 id: Some("Edit".to_string()),
3015 label: t!("menu.edit").to_string(),
3016 when: None,
3017 items: vec![
3018 MenuItem::Action {
3019 label: t!("menu.edit.undo").to_string(),
3020 action: "undo".to_string(),
3021 args: HashMap::new(),
3022 when: Some(context_keys::HAS_BUFFER.to_string()),
3023 checkbox: None,
3024 },
3025 MenuItem::Action {
3026 label: t!("menu.edit.redo").to_string(),
3027 action: "redo".to_string(),
3028 args: HashMap::new(),
3029 when: Some(context_keys::HAS_BUFFER.to_string()),
3030 checkbox: None,
3031 },
3032 MenuItem::Separator { separator: true },
3033 MenuItem::Action {
3034 label: t!("menu.edit.cut").to_string(),
3035 action: "cut".to_string(),
3036 args: HashMap::new(),
3037 when: Some(context_keys::CAN_COPY.to_string()),
3038 checkbox: None,
3039 },
3040 MenuItem::Action {
3041 label: t!("menu.edit.copy").to_string(),
3042 action: "copy".to_string(),
3043 args: HashMap::new(),
3044 when: Some(context_keys::CAN_COPY.to_string()),
3045 checkbox: None,
3046 },
3047 MenuItem::DynamicSubmenu {
3048 label: t!("menu.edit.copy_with_formatting").to_string(),
3049 source: "copy_with_theme".to_string(),
3050 },
3051 MenuItem::Action {
3052 label: t!("menu.edit.paste").to_string(),
3053 action: "paste".to_string(),
3054 args: HashMap::new(),
3055 when: Some(context_keys::CAN_PASTE.to_string()),
3056 checkbox: None,
3057 },
3058 MenuItem::Separator { separator: true },
3059 MenuItem::Action {
3060 label: t!("menu.edit.select_all").to_string(),
3061 action: "select_all".to_string(),
3062 args: HashMap::new(),
3063 when: Some(context_keys::HAS_BUFFER.to_string()),
3064 checkbox: None,
3065 },
3066 MenuItem::Separator { separator: true },
3067 MenuItem::Action {
3068 label: t!("menu.edit.find").to_string(),
3069 action: "search".to_string(),
3070 args: HashMap::new(),
3071 when: Some(context_keys::HAS_BUFFER.to_string()),
3072 checkbox: None,
3073 },
3074 MenuItem::Action {
3075 label: t!("menu.edit.find_in_selection").to_string(),
3076 action: "find_in_selection".to_string(),
3077 args: HashMap::new(),
3078 when: Some(context_keys::HAS_SELECTION.to_string()),
3079 checkbox: None,
3080 },
3081 MenuItem::Action {
3082 label: t!("menu.edit.find_next").to_string(),
3083 action: "find_next".to_string(),
3084 args: HashMap::new(),
3085 when: Some(context_keys::HAS_BUFFER.to_string()),
3086 checkbox: None,
3087 },
3088 MenuItem::Action {
3089 label: t!("menu.edit.find_previous").to_string(),
3090 action: "find_previous".to_string(),
3091 args: HashMap::new(),
3092 when: Some(context_keys::HAS_BUFFER.to_string()),
3093 checkbox: None,
3094 },
3095 MenuItem::Action {
3096 label: t!("menu.edit.replace").to_string(),
3097 action: "query_replace".to_string(),
3098 args: HashMap::new(),
3099 when: Some(context_keys::HAS_BUFFER.to_string()),
3100 checkbox: None,
3101 },
3102 MenuItem::Separator { separator: true },
3103 MenuItem::Action {
3104 label: t!("menu.edit.delete_line").to_string(),
3105 action: "delete_line".to_string(),
3106 args: HashMap::new(),
3107 when: Some(context_keys::HAS_BUFFER.to_string()),
3108 checkbox: None,
3109 },
3110 MenuItem::Action {
3111 label: t!("menu.edit.format_buffer").to_string(),
3112 action: "format_buffer".to_string(),
3113 args: HashMap::new(),
3114 when: Some(context_keys::FORMATTER_AVAILABLE.to_string()),
3115 checkbox: None,
3116 },
3117 MenuItem::Separator { separator: true },
3118 MenuItem::Action {
3119 label: t!("menu.edit.settings").to_string(),
3120 action: "open_settings".to_string(),
3121 args: HashMap::new(),
3122 when: None,
3123 checkbox: None,
3124 },
3125 MenuItem::Action {
3126 label: t!("menu.edit.keybinding_editor").to_string(),
3127 action: "open_keybinding_editor".to_string(),
3128 args: HashMap::new(),
3129 when: None,
3130 checkbox: None,
3131 },
3132 ],
3133 },
3134 Menu {
3136 id: Some("View".to_string()),
3137 label: t!("menu.view").to_string(),
3138 when: None,
3139 items: vec![
3140 MenuItem::Action {
3141 label: t!("menu.view.file_explorer").to_string(),
3142 action: "toggle_file_explorer".to_string(),
3143 args: HashMap::new(),
3144 when: None,
3145 checkbox: Some(context_keys::FILE_EXPLORER.to_string()),
3146 },
3147 MenuItem::Separator { separator: true },
3148 MenuItem::Action {
3149 label: t!("menu.view.line_numbers").to_string(),
3150 action: "toggle_line_numbers".to_string(),
3151 args: HashMap::new(),
3152 when: None,
3153 checkbox: Some(context_keys::LINE_NUMBERS.to_string()),
3154 },
3155 MenuItem::Action {
3156 label: t!("menu.view.line_wrap").to_string(),
3157 action: "toggle_line_wrap".to_string(),
3158 args: HashMap::new(),
3159 when: None,
3160 checkbox: Some(context_keys::LINE_WRAP.to_string()),
3161 },
3162 MenuItem::Action {
3163 label: t!("menu.view.mouse_support").to_string(),
3164 action: "toggle_mouse_capture".to_string(),
3165 args: HashMap::new(),
3166 when: None,
3167 checkbox: Some(context_keys::MOUSE_CAPTURE.to_string()),
3168 },
3169 MenuItem::Separator { separator: true },
3170 MenuItem::Action {
3171 label: t!("menu.view.vertical_scrollbar").to_string(),
3172 action: "toggle_vertical_scrollbar".to_string(),
3173 args: HashMap::new(),
3174 when: None,
3175 checkbox: Some(context_keys::VERTICAL_SCROLLBAR.to_string()),
3176 },
3177 MenuItem::Action {
3178 label: t!("menu.view.horizontal_scrollbar").to_string(),
3179 action: "toggle_horizontal_scrollbar".to_string(),
3180 args: HashMap::new(),
3181 when: None,
3182 checkbox: Some(context_keys::HORIZONTAL_SCROLLBAR.to_string()),
3183 },
3184 MenuItem::Separator { separator: true },
3185 MenuItem::Action {
3186 label: t!("menu.view.set_background").to_string(),
3187 action: "set_background".to_string(),
3188 args: HashMap::new(),
3189 when: None,
3190 checkbox: None,
3191 },
3192 MenuItem::Action {
3193 label: t!("menu.view.set_background_blend").to_string(),
3194 action: "set_background_blend".to_string(),
3195 args: HashMap::new(),
3196 when: None,
3197 checkbox: None,
3198 },
3199 MenuItem::Action {
3200 label: t!("menu.view.set_page_width").to_string(),
3201 action: "set_page_width".to_string(),
3202 args: HashMap::new(),
3203 when: None,
3204 checkbox: None,
3205 },
3206 MenuItem::Separator { separator: true },
3207 MenuItem::Action {
3208 label: t!("menu.view.select_theme").to_string(),
3209 action: "select_theme".to_string(),
3210 args: HashMap::new(),
3211 when: None,
3212 checkbox: None,
3213 },
3214 MenuItem::Action {
3215 label: t!("menu.view.select_locale").to_string(),
3216 action: "select_locale".to_string(),
3217 args: HashMap::new(),
3218 when: None,
3219 checkbox: None,
3220 },
3221 MenuItem::Action {
3222 label: t!("menu.view.settings").to_string(),
3223 action: "open_settings".to_string(),
3224 args: HashMap::new(),
3225 when: None,
3226 checkbox: None,
3227 },
3228 MenuItem::Action {
3229 label: t!("menu.view.calibrate_input").to_string(),
3230 action: "calibrate_input".to_string(),
3231 args: HashMap::new(),
3232 when: None,
3233 checkbox: None,
3234 },
3235 MenuItem::Separator { separator: true },
3236 MenuItem::Action {
3237 label: t!("menu.view.split_horizontal").to_string(),
3238 action: "split_horizontal".to_string(),
3239 args: HashMap::new(),
3240 when: Some(context_keys::HAS_BUFFER.to_string()),
3241 checkbox: None,
3242 },
3243 MenuItem::Action {
3244 label: t!("menu.view.split_vertical").to_string(),
3245 action: "split_vertical".to_string(),
3246 args: HashMap::new(),
3247 when: Some(context_keys::HAS_BUFFER.to_string()),
3248 checkbox: None,
3249 },
3250 MenuItem::Action {
3251 label: t!("menu.view.close_split").to_string(),
3252 action: "close_split".to_string(),
3253 args: HashMap::new(),
3254 when: Some(context_keys::HAS_BUFFER.to_string()),
3255 checkbox: None,
3256 },
3257 MenuItem::Action {
3258 label: t!("menu.view.scroll_sync").to_string(),
3259 action: "toggle_scroll_sync".to_string(),
3260 args: HashMap::new(),
3261 when: Some(context_keys::HAS_SAME_BUFFER_SPLITS.to_string()),
3262 checkbox: Some(context_keys::SCROLL_SYNC.to_string()),
3263 },
3264 MenuItem::Action {
3265 label: t!("menu.view.focus_next_split").to_string(),
3266 action: "next_split".to_string(),
3267 args: HashMap::new(),
3268 when: None,
3269 checkbox: None,
3270 },
3271 MenuItem::Action {
3272 label: t!("menu.view.focus_prev_split").to_string(),
3273 action: "prev_split".to_string(),
3274 args: HashMap::new(),
3275 when: None,
3276 checkbox: None,
3277 },
3278 MenuItem::Action {
3279 label: t!("menu.view.toggle_maximize_split").to_string(),
3280 action: "toggle_maximize_split".to_string(),
3281 args: HashMap::new(),
3282 when: None,
3283 checkbox: None,
3284 },
3285 MenuItem::Separator { separator: true },
3286 MenuItem::Submenu {
3287 label: t!("menu.terminal").to_string(),
3288 items: vec![
3289 MenuItem::Action {
3290 label: t!("menu.terminal.open").to_string(),
3291 action: "open_terminal".to_string(),
3292 args: HashMap::new(),
3293 when: None,
3294 checkbox: None,
3295 },
3296 MenuItem::Action {
3297 label: t!("menu.terminal.close").to_string(),
3298 action: "close_terminal".to_string(),
3299 args: HashMap::new(),
3300 when: None,
3301 checkbox: None,
3302 },
3303 MenuItem::Action {
3304 label: t!("menu.terminal.send_selection").to_string(),
3305 action: "send_selection_to_terminal".to_string(),
3306 args: HashMap::new(),
3307 when: None,
3308 checkbox: None,
3309 },
3310 MenuItem::Separator { separator: true },
3311 MenuItem::Action {
3312 label: t!("menu.terminal.toggle_keyboard_capture").to_string(),
3313 action: "toggle_keyboard_capture".to_string(),
3314 args: HashMap::new(),
3315 when: None,
3316 checkbox: None,
3317 },
3318 ],
3319 },
3320 MenuItem::Separator { separator: true },
3321 MenuItem::Submenu {
3322 label: t!("menu.view.keybinding_style").to_string(),
3323 items: vec![
3324 MenuItem::Action {
3325 label: t!("menu.view.keybinding_default").to_string(),
3326 action: "switch_keybinding_map".to_string(),
3327 args: {
3328 let mut map = HashMap::new();
3329 map.insert("map".to_string(), serde_json::json!("default"));
3330 map
3331 },
3332 when: None,
3333 checkbox: Some(context_keys::KEYMAP_DEFAULT.to_string()),
3334 },
3335 MenuItem::Action {
3336 label: t!("menu.view.keybinding_emacs").to_string(),
3337 action: "switch_keybinding_map".to_string(),
3338 args: {
3339 let mut map = HashMap::new();
3340 map.insert("map".to_string(), serde_json::json!("emacs"));
3341 map
3342 },
3343 when: None,
3344 checkbox: Some(context_keys::KEYMAP_EMACS.to_string()),
3345 },
3346 MenuItem::Action {
3347 label: t!("menu.view.keybinding_vscode").to_string(),
3348 action: "switch_keybinding_map".to_string(),
3349 args: {
3350 let mut map = HashMap::new();
3351 map.insert("map".to_string(), serde_json::json!("vscode"));
3352 map
3353 },
3354 when: None,
3355 checkbox: Some(context_keys::KEYMAP_VSCODE.to_string()),
3356 },
3357 MenuItem::Action {
3358 label: "macOS GUI (⌘)".to_string(),
3359 action: "switch_keybinding_map".to_string(),
3360 args: {
3361 let mut map = HashMap::new();
3362 map.insert("map".to_string(), serde_json::json!("macos-gui"));
3363 map
3364 },
3365 when: None,
3366 checkbox: Some(context_keys::KEYMAP_MACOS_GUI.to_string()),
3367 },
3368 ],
3369 },
3370 ],
3371 },
3372 Menu {
3374 id: Some("Selection".to_string()),
3375 label: t!("menu.selection").to_string(),
3376 when: Some(context_keys::HAS_BUFFER.to_string()),
3377 items: vec![
3378 MenuItem::Action {
3379 label: t!("menu.selection.select_all").to_string(),
3380 action: "select_all".to_string(),
3381 args: HashMap::new(),
3382 when: None,
3383 checkbox: None,
3384 },
3385 MenuItem::Action {
3386 label: t!("menu.selection.select_word").to_string(),
3387 action: "select_word".to_string(),
3388 args: HashMap::new(),
3389 when: None,
3390 checkbox: None,
3391 },
3392 MenuItem::Action {
3393 label: t!("menu.selection.select_line").to_string(),
3394 action: "select_line".to_string(),
3395 args: HashMap::new(),
3396 when: None,
3397 checkbox: None,
3398 },
3399 MenuItem::Action {
3400 label: t!("menu.selection.expand_selection").to_string(),
3401 action: "expand_selection".to_string(),
3402 args: HashMap::new(),
3403 when: None,
3404 checkbox: None,
3405 },
3406 MenuItem::Separator { separator: true },
3407 MenuItem::Action {
3408 label: t!("menu.selection.add_cursor_above").to_string(),
3409 action: "add_cursor_above".to_string(),
3410 args: HashMap::new(),
3411 when: None,
3412 checkbox: None,
3413 },
3414 MenuItem::Action {
3415 label: t!("menu.selection.add_cursor_below").to_string(),
3416 action: "add_cursor_below".to_string(),
3417 args: HashMap::new(),
3418 when: None,
3419 checkbox: None,
3420 },
3421 MenuItem::Action {
3422 label: t!("menu.selection.add_cursor_next_match").to_string(),
3423 action: "add_cursor_next_match".to_string(),
3424 args: HashMap::new(),
3425 when: None,
3426 checkbox: None,
3427 },
3428 MenuItem::Action {
3429 label: t!("menu.selection.add_cursors_to_line_ends").to_string(),
3430 action: "add_cursors_to_line_ends".to_string(),
3431 args: HashMap::new(),
3432 when: None,
3433 checkbox: None,
3434 },
3435 MenuItem::Action {
3436 label: t!("menu.selection.remove_secondary_cursors").to_string(),
3437 action: "remove_secondary_cursors".to_string(),
3438 args: HashMap::new(),
3439 when: None,
3440 checkbox: None,
3441 },
3442 ],
3443 },
3444 Menu {
3446 id: Some("Go".to_string()),
3447 label: t!("menu.go").to_string(),
3448 when: None,
3449 items: vec![
3450 MenuItem::Action {
3451 label: t!("menu.go.goto_line").to_string(),
3452 action: "goto_line".to_string(),
3453 args: HashMap::new(),
3454 when: Some(context_keys::HAS_BUFFER.to_string()),
3455 checkbox: None,
3456 },
3457 MenuItem::Action {
3458 label: t!("menu.go.goto_definition").to_string(),
3459 action: "lsp_goto_definition".to_string(),
3460 args: HashMap::new(),
3461 when: Some(context_keys::HAS_BUFFER.to_string()),
3462 checkbox: None,
3463 },
3464 MenuItem::Action {
3465 label: t!("menu.go.find_references").to_string(),
3466 action: "lsp_references".to_string(),
3467 args: HashMap::new(),
3468 when: Some(context_keys::HAS_BUFFER.to_string()),
3469 checkbox: None,
3470 },
3471 MenuItem::Action {
3472 label: t!("menu.go.goto_implementation").to_string(),
3473 action: "lsp_implementation".to_string(),
3474 args: HashMap::new(),
3475 when: Some(context_keys::HAS_BUFFER.to_string()),
3476 checkbox: None,
3477 },
3478 MenuItem::Separator { separator: true },
3479 MenuItem::Action {
3480 label: t!("menu.go.next_buffer").to_string(),
3481 action: "next_buffer".to_string(),
3482 args: HashMap::new(),
3483 when: Some(context_keys::HAS_BUFFER.to_string()),
3484 checkbox: None,
3485 },
3486 MenuItem::Action {
3487 label: t!("menu.go.prev_buffer").to_string(),
3488 action: "prev_buffer".to_string(),
3489 args: HashMap::new(),
3490 when: Some(context_keys::HAS_BUFFER.to_string()),
3491 checkbox: None,
3492 },
3493 MenuItem::Separator { separator: true },
3494 MenuItem::Action {
3495 label: t!("menu.go.command_palette").to_string(),
3496 action: "command_palette".to_string(),
3497 args: HashMap::new(),
3498 when: None,
3499 checkbox: None,
3500 },
3501 ],
3502 },
3503 Menu {
3505 id: Some("LSP".to_string()),
3506 label: t!("menu.lsp").to_string(),
3507 when: None,
3508 items: vec![
3509 MenuItem::Action {
3510 label: t!("menu.lsp.show_hover").to_string(),
3511 action: "lsp_hover".to_string(),
3512 args: HashMap::new(),
3513 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3514 checkbox: None,
3515 },
3516 MenuItem::Action {
3517 label: t!("menu.lsp.goto_definition").to_string(),
3518 action: "lsp_goto_definition".to_string(),
3519 args: HashMap::new(),
3520 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3521 checkbox: None,
3522 },
3523 MenuItem::Action {
3524 label: t!("menu.lsp.find_references").to_string(),
3525 action: "lsp_references".to_string(),
3526 args: HashMap::new(),
3527 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3528 checkbox: None,
3529 },
3530 MenuItem::Action {
3531 label: t!("menu.lsp.goto_implementation").to_string(),
3532 action: "lsp_implementation".to_string(),
3533 args: HashMap::new(),
3534 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3535 checkbox: None,
3536 },
3537 MenuItem::Action {
3538 label: t!("menu.lsp.rename_symbol").to_string(),
3539 action: "lsp_rename".to_string(),
3540 args: HashMap::new(),
3541 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3542 checkbox: None,
3543 },
3544 MenuItem::Separator { separator: true },
3545 MenuItem::Action {
3546 label: t!("menu.lsp.show_completions").to_string(),
3547 action: "lsp_completion".to_string(),
3548 args: HashMap::new(),
3549 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3550 checkbox: None,
3551 },
3552 MenuItem::Action {
3553 label: t!("menu.lsp.show_signature").to_string(),
3554 action: "lsp_signature_help".to_string(),
3555 args: HashMap::new(),
3556 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3557 checkbox: None,
3558 },
3559 MenuItem::Action {
3560 label: t!("menu.lsp.code_actions").to_string(),
3561 action: "lsp_code_actions".to_string(),
3562 args: HashMap::new(),
3563 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3564 checkbox: None,
3565 },
3566 MenuItem::Separator { separator: true },
3567 MenuItem::Action {
3568 label: t!("menu.lsp.toggle_inlay_hints").to_string(),
3569 action: "toggle_inlay_hints".to_string(),
3570 args: HashMap::new(),
3571 when: Some(context_keys::LSP_AVAILABLE.to_string()),
3572 checkbox: Some(context_keys::INLAY_HINTS.to_string()),
3573 },
3574 MenuItem::Action {
3575 label: t!("menu.lsp.toggle_mouse_hover").to_string(),
3576 action: "toggle_mouse_hover".to_string(),
3577 args: HashMap::new(),
3578 when: None,
3579 checkbox: Some(context_keys::MOUSE_HOVER.to_string()),
3580 },
3581 MenuItem::Separator { separator: true },
3582 MenuItem::Action {
3583 label: t!("menu.lsp.show_status").to_string(),
3584 action: "show_lsp_status".to_string(),
3585 args: HashMap::new(),
3586 when: None,
3587 checkbox: None,
3588 },
3589 MenuItem::Action {
3590 label: t!("menu.lsp.restart_server").to_string(),
3591 action: "lsp_restart".to_string(),
3592 args: HashMap::new(),
3593 when: None,
3594 checkbox: None,
3595 },
3596 MenuItem::Action {
3597 label: t!("menu.lsp.stop_server").to_string(),
3598 action: "lsp_stop".to_string(),
3599 args: HashMap::new(),
3600 when: None,
3601 checkbox: None,
3602 },
3603 MenuItem::Separator { separator: true },
3604 MenuItem::Action {
3605 label: t!("menu.lsp.toggle_for_buffer").to_string(),
3606 action: "lsp_toggle_for_buffer".to_string(),
3607 args: HashMap::new(),
3608 when: Some(context_keys::HAS_BUFFER.to_string()),
3609 checkbox: None,
3610 },
3611 ],
3612 },
3613 Menu {
3615 id: Some("Explorer".to_string()),
3616 label: t!("menu.explorer").to_string(),
3617 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3618 items: vec![
3619 MenuItem::Action {
3620 label: t!("menu.explorer.new_file").to_string(),
3621 action: "file_explorer_new_file".to_string(),
3622 args: HashMap::new(),
3623 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3624 checkbox: None,
3625 },
3626 MenuItem::Action {
3627 label: t!("menu.explorer.new_folder").to_string(),
3628 action: "file_explorer_new_directory".to_string(),
3629 args: HashMap::new(),
3630 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3631 checkbox: None,
3632 },
3633 MenuItem::Separator { separator: true },
3634 MenuItem::Action {
3635 label: t!("menu.explorer.open").to_string(),
3636 action: "file_explorer_open".to_string(),
3637 args: HashMap::new(),
3638 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3639 checkbox: None,
3640 },
3641 MenuItem::Action {
3642 label: t!("menu.explorer.rename").to_string(),
3643 action: "file_explorer_rename".to_string(),
3644 args: HashMap::new(),
3645 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3646 checkbox: None,
3647 },
3648 MenuItem::Action {
3649 label: t!("menu.explorer.delete").to_string(),
3650 action: "file_explorer_delete".to_string(),
3651 args: HashMap::new(),
3652 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3653 checkbox: None,
3654 },
3655 MenuItem::Separator { separator: true },
3656 MenuItem::Action {
3657 label: t!("menu.explorer.cut").to_string(),
3658 action: "cut".to_string(),
3659 args: HashMap::new(),
3660 when: Some(context_keys::CAN_COPY.to_string()),
3661 checkbox: None,
3662 },
3663 MenuItem::Action {
3664 label: t!("menu.explorer.copy").to_string(),
3665 action: "copy".to_string(),
3666 args: HashMap::new(),
3667 when: Some(context_keys::CAN_COPY.to_string()),
3668 checkbox: None,
3669 },
3670 MenuItem::Action {
3671 label: t!("menu.explorer.paste").to_string(),
3672 action: "paste".to_string(),
3673 args: HashMap::new(),
3674 when: Some(context_keys::CAN_PASTE.to_string()),
3675 checkbox: None,
3676 },
3677 MenuItem::Separator { separator: true },
3678 MenuItem::Action {
3679 label: t!("menu.explorer.refresh").to_string(),
3680 action: "file_explorer_refresh".to_string(),
3681 args: HashMap::new(),
3682 when: Some(context_keys::FILE_EXPLORER_FOCUSED.to_string()),
3683 checkbox: None,
3684 },
3685 MenuItem::Separator { separator: true },
3686 MenuItem::Action {
3687 label: t!("menu.explorer.show_hidden").to_string(),
3688 action: "file_explorer_toggle_hidden".to_string(),
3689 args: HashMap::new(),
3690 when: Some(context_keys::FILE_EXPLORER.to_string()),
3691 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_HIDDEN.to_string()),
3692 },
3693 MenuItem::Action {
3694 label: t!("menu.explorer.show_gitignored").to_string(),
3695 action: "file_explorer_toggle_gitignored".to_string(),
3696 args: HashMap::new(),
3697 when: Some(context_keys::FILE_EXPLORER.to_string()),
3698 checkbox: Some(context_keys::FILE_EXPLORER_SHOW_GITIGNORED.to_string()),
3699 },
3700 ],
3701 },
3702 Menu {
3704 id: Some("Help".to_string()),
3705 label: t!("menu.help").to_string(),
3706 when: None,
3707 items: vec![
3708 MenuItem::Label {
3709 info: format!("Fresh v{}", env!("CARGO_PKG_VERSION")),
3710 },
3711 MenuItem::Separator { separator: true },
3712 MenuItem::Action {
3713 label: t!("menu.help.show_manual").to_string(),
3714 action: "show_help".to_string(),
3715 args: HashMap::new(),
3716 when: None,
3717 checkbox: None,
3718 },
3719 MenuItem::Action {
3720 label: t!("menu.help.keyboard_shortcuts").to_string(),
3721 action: "keyboard_shortcuts".to_string(),
3722 args: HashMap::new(),
3723 when: None,
3724 checkbox: None,
3725 },
3726 MenuItem::Separator { separator: true },
3727 MenuItem::Action {
3728 label: t!("menu.help.event_debug").to_string(),
3729 action: "event_debug".to_string(),
3730 args: HashMap::new(),
3731 when: None,
3732 checkbox: None,
3733 },
3734 ],
3735 },
3736 ]
3737 }
3738}
3739
3740impl Config {
3741 pub(crate) const FILENAME: &'static str = "config.json";
3743
3744 pub(crate) fn normalize_zero_sentinels(&mut self) {
3758 if self.editor.wrap_column == Some(0) {
3759 self.editor.wrap_column = None;
3760 }
3761 if self.editor.page_width == Some(0) {
3762 self.editor.page_width = None;
3763 }
3764 if self.editor.tab_size == 0 {
3765 self.editor.tab_size = default_tab_size();
3766 }
3767 for lang in self.languages.values_mut() {
3768 if lang.wrap_column == Some(0) {
3769 lang.wrap_column = None;
3770 }
3771 if lang.page_width == Some(0) {
3772 lang.page_width = None;
3773 }
3774 if lang.tab_size == Some(0) {
3775 lang.tab_size = None;
3776 }
3777 }
3778 }
3779
3780 pub fn apply_runtime_flags(&self) {
3785 #[cfg(windows)]
3786 {
3787 crate::services::terminal::set_skip_app_execution_alias(
3788 self.terminal.skip_app_execution_alias,
3789 );
3790 }
3791 }
3792
3793 pub(crate) fn local_config_path(working_dir: &Path) -> std::path::PathBuf {
3795 working_dir.join(Self::FILENAME)
3796 }
3797
3798 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
3804 let contents = std::fs::read_to_string(path.as_ref())
3805 .map_err(|e| ConfigError::IoError(e.to_string()))?;
3806
3807 let value = parse_config_jsonc(&contents)?;
3810 let partial: crate::partial_config::PartialConfig =
3811 serde_json::from_value(value).map_err(|e| ConfigError::ParseError(e.to_string()))?;
3812
3813 Ok(partial.resolve())
3814 }
3815
3816 fn load_builtin_keymap(name: &str) -> Option<KeymapConfig> {
3818 let json_content = match name {
3819 "default" => include_str!("../keymaps/default.json"),
3820 "emacs" => include_str!("../keymaps/emacs.json"),
3821 "vscode" => include_str!("../keymaps/vscode.json"),
3822 "macos" => include_str!("../keymaps/macos.json"),
3823 "macos-gui" => include_str!("../keymaps/macos-gui.json"),
3824 _ => return None,
3825 };
3826
3827 match serde_json::from_str(json_content) {
3828 Ok(config) => Some(config),
3829 Err(e) => {
3830 eprintln!("Failed to parse builtin keymap '{}': {}", name, e);
3831 None
3832 }
3833 }
3834 }
3835
3836 pub fn resolve_keymap(&self, map_name: &str) -> Vec<Keybinding> {
3839 let mut visited = std::collections::HashSet::new();
3840 self.resolve_keymap_recursive(map_name, &mut visited)
3841 }
3842
3843 fn resolve_keymap_recursive(
3845 &self,
3846 map_name: &str,
3847 visited: &mut std::collections::HashSet<String>,
3848 ) -> Vec<Keybinding> {
3849 if visited.contains(map_name) {
3851 eprintln!(
3852 "Warning: Circular inheritance detected in keymap '{}'",
3853 map_name
3854 );
3855 return Vec::new();
3856 }
3857 visited.insert(map_name.to_string());
3858
3859 let keymap = self
3861 .keybinding_maps
3862 .get(map_name)
3863 .cloned()
3864 .or_else(|| Self::load_builtin_keymap(map_name));
3865
3866 let Some(keymap) = keymap else {
3867 return Vec::new();
3868 };
3869
3870 let mut all_bindings = if let Some(ref parent_name) = keymap.inherits {
3872 self.resolve_keymap_recursive(parent_name, visited)
3873 } else {
3874 Vec::new()
3875 };
3876
3877 all_bindings.extend(keymap.bindings);
3879
3880 all_bindings
3881 }
3882 fn default_languages() -> HashMap<String, LanguageConfig> {
3884 let mut languages = HashMap::new();
3885
3886 languages.insert(
3887 "rust".to_string(),
3888 LanguageConfig {
3889 extensions: vec!["rs".to_string()],
3890 filenames: vec![],
3891 grammar: "rust".to_string(),
3892 comment_prefix: Some("//".to_string()),
3893 auto_indent: true,
3894 auto_close: None,
3895 auto_surround: None,
3896 textmate_grammar: None,
3897 show_whitespace_tabs: true,
3898 line_wrap: None,
3899 wrap_column: None,
3900 page_view: None,
3901 page_width: None,
3902 use_tabs: None,
3903 tab_size: None,
3904 formatter: Some(FormatterConfig {
3905 command: "rustfmt".to_string(),
3906 args: vec!["--edition".to_string(), "2021".to_string()],
3907 stdin: true,
3908 timeout_ms: 10000,
3909 }),
3910 format_on_save: false,
3911 on_save: vec![],
3912 word_characters: None,
3913 indent: None,
3914 },
3915 );
3916
3917 languages.insert(
3918 "javascript".to_string(),
3919 LanguageConfig {
3920 extensions: vec!["js".to_string(), "jsx".to_string(), "mjs".to_string()],
3921 filenames: vec![],
3922 grammar: "javascript".to_string(),
3923 comment_prefix: Some("//".to_string()),
3924 auto_indent: true,
3925 auto_close: None,
3926 auto_surround: None,
3927 textmate_grammar: None,
3928 show_whitespace_tabs: true,
3929 line_wrap: None,
3930 wrap_column: None,
3931 page_view: None,
3932 page_width: None,
3933 use_tabs: None,
3934 tab_size: None,
3935 formatter: Some(FormatterConfig {
3936 command: "prettier".to_string(),
3937 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3938 stdin: true,
3939 timeout_ms: 10000,
3940 }),
3941 format_on_save: false,
3942 on_save: vec![],
3943 word_characters: None,
3944 indent: None,
3945 },
3946 );
3947
3948 languages.insert(
3949 "typescript".to_string(),
3950 LanguageConfig {
3951 extensions: vec!["ts".to_string(), "tsx".to_string(), "mts".to_string()],
3952 filenames: vec![],
3953 grammar: "typescript".to_string(),
3954 comment_prefix: Some("//".to_string()),
3955 auto_indent: true,
3956 auto_close: None,
3957 auto_surround: None,
3958 textmate_grammar: None,
3959 show_whitespace_tabs: true,
3960 line_wrap: None,
3961 wrap_column: None,
3962 page_view: None,
3963 page_width: None,
3964 use_tabs: None,
3965 tab_size: None,
3966 formatter: Some(FormatterConfig {
3967 command: "prettier".to_string(),
3968 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
3969 stdin: true,
3970 timeout_ms: 10000,
3971 }),
3972 format_on_save: false,
3973 on_save: vec![],
3974 word_characters: None,
3975 indent: None,
3976 },
3977 );
3978
3979 languages.insert(
3980 "python".to_string(),
3981 LanguageConfig {
3982 extensions: vec!["py".to_string(), "pyi".to_string()],
3983 filenames: vec![],
3984 grammar: "python".to_string(),
3985 comment_prefix: Some("#".to_string()),
3986 auto_indent: true,
3987 auto_close: None,
3988 auto_surround: None,
3989 textmate_grammar: None,
3990 show_whitespace_tabs: true,
3991 line_wrap: None,
3992 wrap_column: None,
3993 page_view: None,
3994 page_width: None,
3995 use_tabs: None,
3996 tab_size: None,
3997 formatter: Some(FormatterConfig {
3998 command: "ruff".to_string(),
3999 args: vec![
4000 "format".to_string(),
4001 "--stdin-filename".to_string(),
4002 "$FILE".to_string(),
4003 ],
4004 stdin: true,
4005 timeout_ms: 10000,
4006 }),
4007 format_on_save: false,
4008 on_save: vec![],
4009 word_characters: None,
4010 indent: None,
4011 },
4012 );
4013
4014 languages.insert(
4015 "gdscript".to_string(),
4016 LanguageConfig {
4017 extensions: vec!["gd".to_string()],
4018 filenames: vec![],
4019 grammar: "gdscript".to_string(),
4020 comment_prefix: Some("#".to_string()),
4021 auto_indent: true,
4022 auto_close: None,
4023 auto_surround: None,
4024 textmate_grammar: None,
4025 show_whitespace_tabs: true,
4026 line_wrap: None,
4027 wrap_column: None,
4028 page_view: None,
4029 page_width: None,
4030 use_tabs: None,
4031 tab_size: None,
4032 formatter: None,
4033 format_on_save: false,
4034 on_save: vec![],
4035 word_characters: None,
4036 indent: None,
4037 },
4038 );
4039
4040 languages.insert(
4041 "c".to_string(),
4042 LanguageConfig {
4043 extensions: vec!["c".to_string(), "h".to_string()],
4044 filenames: vec![],
4045 grammar: "c".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: Some(FormatterConfig {
4059 command: "clang-format".to_string(),
4060 args: vec![],
4061 stdin: true,
4062 timeout_ms: 10000,
4063 }),
4064 format_on_save: false,
4065 on_save: vec![],
4066 word_characters: None,
4067 indent: None,
4068 },
4069 );
4070
4071 languages.insert(
4072 "cpp".to_string(),
4073 LanguageConfig {
4074 extensions: vec![
4075 "cpp".to_string(),
4076 "cc".to_string(),
4077 "cxx".to_string(),
4078 "hpp".to_string(),
4079 "hh".to_string(),
4080 "hxx".to_string(),
4081 ],
4082 filenames: vec![],
4083 grammar: "cpp".to_string(),
4084 comment_prefix: Some("//".to_string()),
4085 auto_indent: true,
4086 auto_close: None,
4087 auto_surround: None,
4088 textmate_grammar: None,
4089 show_whitespace_tabs: true,
4090 line_wrap: None,
4091 wrap_column: None,
4092 page_view: None,
4093 page_width: None,
4094 use_tabs: None,
4095 tab_size: None,
4096 formatter: Some(FormatterConfig {
4097 command: "clang-format".to_string(),
4098 args: vec![],
4099 stdin: true,
4100 timeout_ms: 10000,
4101 }),
4102 format_on_save: false,
4103 on_save: vec![],
4104 word_characters: None,
4105 indent: None,
4106 },
4107 );
4108
4109 languages.insert(
4110 "csharp".to_string(),
4111 LanguageConfig {
4112 extensions: vec!["cs".to_string()],
4113 filenames: vec![],
4114 grammar: "C#".to_string(),
4115 comment_prefix: Some("//".to_string()),
4116 auto_indent: true,
4117 auto_close: None,
4118 auto_surround: None,
4119 textmate_grammar: None,
4120 show_whitespace_tabs: true,
4121 line_wrap: None,
4122 wrap_column: None,
4123 page_view: None,
4124 page_width: None,
4125 use_tabs: None,
4126 tab_size: None,
4127 formatter: None,
4128 format_on_save: false,
4129 on_save: vec![],
4130 word_characters: None,
4131 indent: None,
4132 },
4133 );
4134
4135 languages.insert(
4136 "bash".to_string(),
4137 LanguageConfig {
4138 extensions: vec!["sh".to_string(), "bash".to_string()],
4139 filenames: vec![
4140 ".bash_aliases".to_string(),
4141 ".bash_logout".to_string(),
4142 ".bash_profile".to_string(),
4143 ".bashrc".to_string(),
4144 ".env".to_string(),
4145 ".profile".to_string(),
4146 ".zlogin".to_string(),
4147 ".zlogout".to_string(),
4148 ".zprofile".to_string(),
4149 ".zshenv".to_string(),
4150 ".zshrc".to_string(),
4151 "PKGBUILD".to_string(),
4153 "APKBUILD".to_string(),
4154 ],
4155 grammar: "bash".to_string(),
4156 comment_prefix: Some("#".to_string()),
4157 auto_indent: true,
4158 auto_close: None,
4159 auto_surround: None,
4160 textmate_grammar: None,
4161 show_whitespace_tabs: true,
4162 line_wrap: None,
4163 wrap_column: None,
4164 page_view: None,
4165 page_width: None,
4166 use_tabs: None,
4167 tab_size: None,
4168 formatter: None,
4169 format_on_save: false,
4170 on_save: vec![],
4171 word_characters: None,
4172 indent: None,
4173 },
4174 );
4175
4176 languages.insert(
4177 "fish".to_string(),
4178 LanguageConfig {
4179 extensions: vec!["fish".to_string()],
4180 filenames: vec![],
4181 grammar: "fish".to_string(),
4182 comment_prefix: Some("#".to_string()),
4183 auto_indent: true,
4184 auto_close: None,
4185 auto_surround: None,
4186 textmate_grammar: None,
4187 show_whitespace_tabs: true,
4188 line_wrap: None,
4189 wrap_column: None,
4190 page_view: None,
4191 page_width: None,
4192 use_tabs: None,
4193 tab_size: None,
4194 formatter: None,
4195 format_on_save: false,
4196 on_save: vec![],
4197 word_characters: None,
4198 indent: None,
4199 },
4200 );
4201
4202 languages.insert(
4203 "makefile".to_string(),
4204 LanguageConfig {
4205 extensions: vec!["mk".to_string()],
4206 filenames: vec![
4207 "Makefile".to_string(),
4208 "makefile".to_string(),
4209 "GNUmakefile".to_string(),
4210 ],
4211 grammar: "Makefile".to_string(),
4212 comment_prefix: Some("#".to_string()),
4213 auto_indent: false,
4214 auto_close: None,
4215 auto_surround: None,
4216 textmate_grammar: None,
4217 show_whitespace_tabs: true,
4218 line_wrap: None,
4219 wrap_column: None,
4220 page_view: None,
4221 page_width: None,
4222 use_tabs: Some(true), tab_size: Some(8), formatter: None,
4225 format_on_save: false,
4226 on_save: vec![],
4227 word_characters: None,
4228 indent: None,
4229 },
4230 );
4231
4232 languages.insert(
4233 "dockerfile".to_string(),
4234 LanguageConfig {
4235 extensions: vec!["dockerfile".to_string()],
4236 filenames: vec!["Dockerfile".to_string(), "Containerfile".to_string()],
4237 grammar: "dockerfile".to_string(),
4238 comment_prefix: Some("#".to_string()),
4239 auto_indent: true,
4240 auto_close: None,
4241 auto_surround: None,
4242 textmate_grammar: None,
4243 show_whitespace_tabs: true,
4244 line_wrap: None,
4245 wrap_column: None,
4246 page_view: None,
4247 page_width: None,
4248 use_tabs: None,
4249 tab_size: None,
4250 formatter: None,
4251 format_on_save: false,
4252 on_save: vec![],
4253 word_characters: None,
4254 indent: None,
4255 },
4256 );
4257
4258 languages.insert(
4259 "json".to_string(),
4260 LanguageConfig {
4261 extensions: vec!["json".to_string()],
4262 filenames: vec![],
4263 grammar: "json".to_string(),
4264 comment_prefix: None,
4265 auto_indent: true,
4266 auto_close: None,
4267 auto_surround: None,
4268 textmate_grammar: None,
4269 show_whitespace_tabs: true,
4270 line_wrap: None,
4271 wrap_column: None,
4272 page_view: None,
4273 page_width: None,
4274 use_tabs: None,
4275 tab_size: None,
4276 formatter: Some(FormatterConfig {
4277 command: "prettier".to_string(),
4278 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
4279 stdin: true,
4280 timeout_ms: 10000,
4281 }),
4282 format_on_save: false,
4283 on_save: vec![],
4284 word_characters: None,
4285 indent: None,
4286 },
4287 );
4288
4289 languages.insert(
4296 "jsonc".to_string(),
4297 LanguageConfig {
4298 extensions: vec!["jsonc".to_string()],
4299 filenames: vec![
4300 "devcontainer.json".to_string(),
4301 ".devcontainer.json".to_string(),
4302 "tsconfig.json".to_string(),
4303 "tsconfig.*.json".to_string(),
4304 "jsconfig.json".to_string(),
4305 "jsconfig.*.json".to_string(),
4306 ".eslintrc.json".to_string(),
4307 ".babelrc".to_string(),
4308 ".babelrc.json".to_string(),
4309 ".swcrc".to_string(),
4310 ".jshintrc".to_string(),
4311 ".hintrc".to_string(),
4312 "settings.json".to_string(),
4313 "keybindings.json".to_string(),
4314 "tasks.json".to_string(),
4315 "launch.json".to_string(),
4316 "extensions.json".to_string(),
4317 "argv.json".to_string(),
4318 ],
4319 grammar: "jsonc".to_string(),
4320 comment_prefix: Some("//".to_string()),
4321 auto_indent: true,
4322 auto_close: None,
4323 auto_surround: None,
4324 textmate_grammar: None,
4325 show_whitespace_tabs: true,
4326 line_wrap: None,
4327 wrap_column: None,
4328 page_view: None,
4329 page_width: None,
4330 use_tabs: None,
4331 tab_size: None,
4332 formatter: Some(FormatterConfig {
4333 command: "prettier".to_string(),
4334 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
4335 stdin: true,
4336 timeout_ms: 10000,
4337 }),
4338 format_on_save: false,
4339 on_save: vec![],
4340 word_characters: None,
4341 indent: None,
4342 },
4343 );
4344
4345 languages.insert(
4346 "toml".to_string(),
4347 LanguageConfig {
4348 extensions: vec!["toml".to_string()],
4349 filenames: vec!["Cargo.lock".to_string()],
4350 grammar: "toml".to_string(),
4351 comment_prefix: Some("#".to_string()),
4352 auto_indent: true,
4353 auto_close: None,
4354 auto_surround: None,
4355 textmate_grammar: None,
4356 show_whitespace_tabs: true,
4357 line_wrap: None,
4358 wrap_column: None,
4359 page_view: None,
4360 page_width: None,
4361 use_tabs: None,
4362 tab_size: None,
4363 formatter: None,
4364 format_on_save: false,
4365 on_save: vec![],
4366 word_characters: None,
4367 indent: None,
4368 },
4369 );
4370
4371 languages.insert(
4372 "yaml".to_string(),
4373 LanguageConfig {
4374 extensions: vec!["yml".to_string(), "yaml".to_string()],
4375 filenames: vec![],
4376 grammar: "yaml".to_string(),
4377 comment_prefix: Some("#".to_string()),
4378 auto_indent: true,
4379 auto_close: None,
4380 auto_surround: None,
4381 textmate_grammar: None,
4382 show_whitespace_tabs: true,
4383 line_wrap: None,
4384 wrap_column: None,
4385 page_view: None,
4386 page_width: None,
4387 use_tabs: None,
4388 tab_size: None,
4389 formatter: Some(FormatterConfig {
4390 command: "prettier".to_string(),
4391 args: vec!["--stdin-filepath".to_string(), "$FILE".to_string()],
4392 stdin: true,
4393 timeout_ms: 10000,
4394 }),
4395 format_on_save: false,
4396 on_save: vec![],
4397 word_characters: None,
4398 indent: None,
4399 },
4400 );
4401
4402 languages.insert(
4403 "markdown".to_string(),
4404 LanguageConfig {
4405 extensions: vec!["md".to_string(), "markdown".to_string()],
4406 filenames: vec!["README".to_string()],
4407 grammar: "markdown".to_string(),
4408 comment_prefix: None,
4409 auto_indent: false,
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(
4430 "go".to_string(),
4431 LanguageConfig {
4432 extensions: vec!["go".to_string()],
4433 filenames: vec![],
4434 grammar: "go".to_string(),
4435 comment_prefix: Some("//".to_string()),
4436 auto_indent: true,
4437 auto_close: None,
4438 auto_surround: None,
4439 textmate_grammar: None,
4440 show_whitespace_tabs: false,
4441 line_wrap: None,
4442 wrap_column: None,
4443 page_view: None,
4444 page_width: None,
4445 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
4448 command: "gofmt".to_string(),
4449 args: vec![],
4450 stdin: true,
4451 timeout_ms: 10000,
4452 }),
4453 format_on_save: false,
4454 on_save: vec![],
4455 word_characters: None,
4456 indent: None,
4457 },
4458 );
4459
4460 languages.insert(
4461 "odin".to_string(),
4462 LanguageConfig {
4463 extensions: vec!["odin".to_string()],
4464 filenames: vec![],
4465 grammar: "odin".to_string(),
4466 comment_prefix: Some("//".to_string()),
4467 auto_indent: true,
4468 auto_close: None,
4469 auto_surround: None,
4470 textmate_grammar: None,
4471 show_whitespace_tabs: false,
4472 line_wrap: None,
4473 wrap_column: None,
4474 page_view: None,
4475 page_width: None,
4476 use_tabs: Some(true),
4477 tab_size: Some(8),
4478 formatter: None,
4479 format_on_save: false,
4480 on_save: vec![],
4481 word_characters: None,
4482 indent: None,
4483 },
4484 );
4485
4486 languages.insert(
4487 "zig".to_string(),
4488 LanguageConfig {
4489 extensions: vec!["zig".to_string(), "zon".to_string()],
4490 filenames: vec![],
4491 grammar: "zig".to_string(),
4492 comment_prefix: Some("//".to_string()),
4493 auto_indent: true,
4494 auto_close: None,
4495 auto_surround: None,
4496 textmate_grammar: None,
4497 show_whitespace_tabs: true,
4498 line_wrap: None,
4499 wrap_column: None,
4500 page_view: None,
4501 page_width: None,
4502 use_tabs: None,
4503 tab_size: None,
4504 formatter: None,
4505 format_on_save: false,
4506 on_save: vec![],
4507 word_characters: None,
4508 indent: None,
4509 },
4510 );
4511
4512 languages.insert(
4513 "c3".to_string(),
4514 LanguageConfig {
4515 extensions: vec!["c3".to_string(), "c3i".to_string(), "c3t".to_string()],
4516 filenames: vec![],
4517 grammar: "c3".to_string(),
4518 comment_prefix: Some("//".to_string()),
4519 auto_indent: true,
4520 auto_close: None,
4521 auto_surround: None,
4522 textmate_grammar: None,
4523 show_whitespace_tabs: true,
4524 line_wrap: None,
4525 wrap_column: None,
4526 page_view: None,
4527 page_width: None,
4528 use_tabs: None,
4529 tab_size: None,
4530 formatter: None,
4531 format_on_save: false,
4532 on_save: vec![],
4533 word_characters: None,
4534 indent: None,
4535 },
4536 );
4537
4538 languages.insert(
4539 "java".to_string(),
4540 LanguageConfig {
4541 extensions: vec!["java".to_string()],
4542 filenames: vec![],
4543 grammar: "java".to_string(),
4544 comment_prefix: Some("//".to_string()),
4545 auto_indent: true,
4546 auto_close: None,
4547 auto_surround: None,
4548 textmate_grammar: None,
4549 show_whitespace_tabs: true,
4550 line_wrap: None,
4551 wrap_column: None,
4552 page_view: None,
4553 page_width: None,
4554 use_tabs: None,
4555 tab_size: None,
4556 formatter: None,
4557 format_on_save: false,
4558 on_save: vec![],
4559 word_characters: None,
4560 indent: None,
4561 },
4562 );
4563
4564 languages.insert(
4565 "latex".to_string(),
4566 LanguageConfig {
4567 extensions: vec![
4568 "tex".to_string(),
4569 "latex".to_string(),
4570 "ltx".to_string(),
4571 "sty".to_string(),
4572 "cls".to_string(),
4573 "bib".to_string(),
4574 ],
4575 filenames: vec![],
4576 grammar: "latex".to_string(),
4577 comment_prefix: Some("%".to_string()),
4578 auto_indent: true,
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 "templ".to_string(),
4599 LanguageConfig {
4600 extensions: vec!["templ".to_string()],
4601 filenames: vec![],
4602 grammar: "go".to_string(), comment_prefix: Some("//".to_string()),
4604 auto_indent: true,
4605 auto_close: None,
4606 auto_surround: None,
4607 textmate_grammar: None,
4608 show_whitespace_tabs: true,
4609 line_wrap: None,
4610 wrap_column: None,
4611 page_view: None,
4612 page_width: None,
4613 use_tabs: None,
4614 tab_size: None,
4615 formatter: None,
4616 format_on_save: false,
4617 on_save: vec![],
4618 word_characters: None,
4619 indent: None,
4620 },
4621 );
4622
4623 languages.insert(
4624 "smali".to_string(),
4625 LanguageConfig {
4626 extensions: vec!["smali".to_string()],
4627 filenames: vec![],
4628 grammar: "Smali".to_string(),
4629 comment_prefix: Some("#".to_string()),
4630 auto_indent: true,
4631 auto_close: None,
4632 auto_surround: None,
4633 textmate_grammar: None,
4634 show_whitespace_tabs: true,
4635 line_wrap: None,
4636 wrap_column: None,
4637 page_view: None,
4638 page_width: None,
4639 use_tabs: None,
4640 tab_size: None,
4641 formatter: None,
4642 format_on_save: false,
4643 on_save: vec![],
4644 word_characters: None,
4645 indent: None,
4646 },
4647 );
4648
4649 languages.insert(
4651 "git-rebase".to_string(),
4652 LanguageConfig {
4653 extensions: vec![],
4654 filenames: vec!["git-rebase-todo".to_string()],
4655 grammar: "Git Rebase Todo".to_string(),
4656 comment_prefix: Some("#".to_string()),
4657 auto_indent: false,
4658 auto_close: None,
4659 auto_surround: None,
4660 textmate_grammar: None,
4661 show_whitespace_tabs: true,
4662 line_wrap: None,
4663 wrap_column: None,
4664 page_view: None,
4665 page_width: None,
4666 use_tabs: None,
4667 tab_size: None,
4668 formatter: None,
4669 format_on_save: false,
4670 on_save: vec![],
4671 word_characters: None,
4672 indent: None,
4673 },
4674 );
4675
4676 languages.insert(
4677 "git-commit".to_string(),
4678 LanguageConfig {
4679 extensions: vec![],
4680 filenames: vec![
4681 "COMMIT_EDITMSG".to_string(),
4682 "MERGE_MSG".to_string(),
4683 "SQUASH_MSG".to_string(),
4684 "TAG_EDITMSG".to_string(),
4685 ],
4686 grammar: "Git Commit Message".to_string(),
4687 comment_prefix: Some("#".to_string()),
4688 auto_indent: false,
4689 auto_close: None,
4690 auto_surround: None,
4691 textmate_grammar: None,
4692 show_whitespace_tabs: true,
4693 line_wrap: None,
4694 wrap_column: None,
4695 page_view: None,
4696 page_width: None,
4697 use_tabs: None,
4698 tab_size: None,
4699 formatter: None,
4700 format_on_save: false,
4701 on_save: vec![],
4702 word_characters: None,
4703 indent: None,
4704 },
4705 );
4706
4707 languages.insert(
4708 "gitignore".to_string(),
4709 LanguageConfig {
4710 extensions: vec!["gitignore".to_string()],
4711 filenames: vec![
4712 ".gitignore".to_string(),
4713 ".dockerignore".to_string(),
4714 ".npmignore".to_string(),
4715 ".hgignore".to_string(),
4716 ],
4717 grammar: "Gitignore".to_string(),
4718 comment_prefix: Some("#".to_string()),
4719 auto_indent: false,
4720 auto_close: None,
4721 auto_surround: None,
4722 textmate_grammar: None,
4723 show_whitespace_tabs: true,
4724 line_wrap: None,
4725 wrap_column: None,
4726 page_view: None,
4727 page_width: None,
4728 use_tabs: None,
4729 tab_size: None,
4730 formatter: None,
4731 format_on_save: false,
4732 on_save: vec![],
4733 word_characters: None,
4734 indent: None,
4735 },
4736 );
4737
4738 languages.insert(
4739 "gitconfig".to_string(),
4740 LanguageConfig {
4741 extensions: vec!["gitconfig".to_string()],
4742 filenames: vec![".gitconfig".to_string(), ".gitmodules".to_string()],
4743 grammar: "Git Config".to_string(),
4744 comment_prefix: Some("#".to_string()),
4745 auto_indent: true,
4746 auto_close: None,
4747 auto_surround: None,
4748 textmate_grammar: None,
4749 show_whitespace_tabs: true,
4750 line_wrap: None,
4751 wrap_column: None,
4752 page_view: None,
4753 page_width: None,
4754 use_tabs: None,
4755 tab_size: None,
4756 formatter: None,
4757 format_on_save: false,
4758 on_save: vec![],
4759 word_characters: None,
4760 indent: None,
4761 },
4762 );
4763
4764 languages.insert(
4765 "gitattributes".to_string(),
4766 LanguageConfig {
4767 extensions: vec!["gitattributes".to_string()],
4768 filenames: vec![".gitattributes".to_string()],
4769 grammar: "Git Attributes".to_string(),
4770 comment_prefix: Some("#".to_string()),
4771 auto_indent: false,
4772 auto_close: None,
4773 auto_surround: None,
4774 textmate_grammar: None,
4775 show_whitespace_tabs: true,
4776 line_wrap: None,
4777 wrap_column: None,
4778 page_view: None,
4779 page_width: None,
4780 use_tabs: None,
4781 tab_size: None,
4782 formatter: None,
4783 format_on_save: false,
4784 on_save: vec![],
4785 word_characters: None,
4786 indent: None,
4787 },
4788 );
4789
4790 languages.insert(
4791 "typst".to_string(),
4792 LanguageConfig {
4793 extensions: vec!["typ".to_string()],
4794 filenames: vec![],
4795 grammar: "Typst".to_string(),
4796 comment_prefix: Some("//".to_string()),
4797 auto_indent: true,
4798 auto_close: None,
4799 auto_surround: None,
4800 textmate_grammar: None,
4801 show_whitespace_tabs: true,
4802 line_wrap: None,
4803 wrap_column: None,
4804 page_view: None,
4805 page_width: None,
4806 use_tabs: None,
4807 tab_size: None,
4808 formatter: None,
4809 format_on_save: false,
4810 on_save: vec![],
4811 word_characters: None,
4812 indent: None,
4813 },
4814 );
4815
4816 languages.insert(
4821 "kotlin".to_string(),
4822 LanguageConfig {
4823 extensions: vec!["kt".to_string(), "kts".to_string()],
4824 filenames: vec![],
4825 grammar: "Kotlin".to_string(),
4826 comment_prefix: Some("//".to_string()),
4827 auto_indent: true,
4828 auto_close: None,
4829 auto_surround: None,
4830 textmate_grammar: None,
4831 show_whitespace_tabs: true,
4832 line_wrap: None,
4833 wrap_column: None,
4834 page_view: None,
4835 page_width: None,
4836 use_tabs: None,
4837 tab_size: None,
4838 formatter: None,
4839 format_on_save: false,
4840 on_save: vec![],
4841 word_characters: None,
4842 indent: None,
4843 },
4844 );
4845
4846 languages.insert(
4847 "swift".to_string(),
4848 LanguageConfig {
4849 extensions: vec!["swift".to_string()],
4850 filenames: vec![],
4851 grammar: "Swift".to_string(),
4852 comment_prefix: Some("//".to_string()),
4853 auto_indent: true,
4854 auto_close: None,
4855 auto_surround: None,
4856 textmate_grammar: None,
4857 show_whitespace_tabs: true,
4858 line_wrap: None,
4859 wrap_column: None,
4860 page_view: None,
4861 page_width: None,
4862 use_tabs: None,
4863 tab_size: None,
4864 formatter: None,
4865 format_on_save: false,
4866 on_save: vec![],
4867 word_characters: None,
4868 indent: None,
4869 },
4870 );
4871
4872 languages.insert(
4873 "scala".to_string(),
4874 LanguageConfig {
4875 extensions: vec!["scala".to_string(), "sc".to_string()],
4876 filenames: vec![],
4877 grammar: "Scala".to_string(),
4878 comment_prefix: Some("//".to_string()),
4879 auto_indent: true,
4880 auto_close: None,
4881 auto_surround: None,
4882 textmate_grammar: None,
4883 show_whitespace_tabs: true,
4884 line_wrap: None,
4885 wrap_column: None,
4886 page_view: None,
4887 page_width: None,
4888 use_tabs: None,
4889 tab_size: None,
4890 formatter: None,
4891 format_on_save: false,
4892 on_save: vec![],
4893 word_characters: None,
4894 indent: None,
4895 },
4896 );
4897
4898 languages.insert(
4899 "dart".to_string(),
4900 LanguageConfig {
4901 extensions: vec!["dart".to_string()],
4902 filenames: vec![],
4903 grammar: "Dart".to_string(),
4904 comment_prefix: Some("//".to_string()),
4905 auto_indent: true,
4906 auto_close: None,
4907 auto_surround: None,
4908 textmate_grammar: None,
4909 show_whitespace_tabs: true,
4910 line_wrap: None,
4911 wrap_column: None,
4912 page_view: None,
4913 page_width: None,
4914 use_tabs: None,
4915 tab_size: None,
4916 formatter: None,
4917 format_on_save: false,
4918 on_save: vec![],
4919 word_characters: None,
4920 indent: None,
4921 },
4922 );
4923
4924 languages.insert(
4925 "elixir".to_string(),
4926 LanguageConfig {
4927 extensions: vec!["ex".to_string(), "exs".to_string()],
4928 filenames: vec![],
4929 grammar: "Elixir".to_string(),
4930 comment_prefix: Some("#".to_string()),
4931 auto_indent: true,
4932 auto_close: None,
4933 auto_surround: None,
4934 textmate_grammar: None,
4935 show_whitespace_tabs: true,
4936 line_wrap: None,
4937 wrap_column: None,
4938 page_view: None,
4939 page_width: None,
4940 use_tabs: None,
4941 tab_size: None,
4942 formatter: None,
4943 format_on_save: false,
4944 on_save: vec![],
4945 word_characters: None,
4946 indent: None,
4947 },
4948 );
4949
4950 languages.insert(
4951 "erlang".to_string(),
4952 LanguageConfig {
4953 extensions: vec!["erl".to_string(), "hrl".to_string()],
4954 filenames: vec![],
4955 grammar: "Erlang".to_string(),
4956 comment_prefix: Some("%".to_string()),
4957 auto_indent: true,
4958 auto_close: None,
4959 auto_surround: None,
4960 textmate_grammar: None,
4961 show_whitespace_tabs: true,
4962 line_wrap: None,
4963 wrap_column: None,
4964 page_view: None,
4965 page_width: None,
4966 use_tabs: None,
4967 tab_size: None,
4968 formatter: None,
4969 format_on_save: false,
4970 on_save: vec![],
4971 word_characters: None,
4972 indent: None,
4973 },
4974 );
4975
4976 languages.insert(
4977 "haskell".to_string(),
4978 LanguageConfig {
4979 extensions: vec!["hs".to_string(), "lhs".to_string()],
4980 filenames: vec![],
4981 grammar: "Haskell".to_string(),
4982 comment_prefix: Some("--".to_string()),
4983 auto_indent: true,
4984 auto_close: None,
4985 auto_surround: None,
4986 textmate_grammar: None,
4987 show_whitespace_tabs: true,
4988 line_wrap: None,
4989 wrap_column: None,
4990 page_view: None,
4991 page_width: None,
4992 use_tabs: None,
4993 tab_size: None,
4994 formatter: None,
4995 format_on_save: false,
4996 on_save: vec![],
4997 word_characters: None,
4998 indent: None,
4999 },
5000 );
5001
5002 languages.insert(
5003 "ocaml".to_string(),
5004 LanguageConfig {
5005 extensions: vec!["ml".to_string(), "mli".to_string()],
5006 filenames: vec![],
5007 grammar: "OCaml".to_string(),
5008 comment_prefix: None,
5009 auto_indent: true,
5010 auto_close: None,
5011 auto_surround: None,
5012 textmate_grammar: None,
5013 show_whitespace_tabs: true,
5014 line_wrap: None,
5015 wrap_column: None,
5016 page_view: None,
5017 page_width: None,
5018 use_tabs: None,
5019 tab_size: None,
5020 formatter: None,
5021 format_on_save: false,
5022 on_save: vec![],
5023 word_characters: None,
5024 indent: None,
5025 },
5026 );
5027
5028 languages.insert(
5029 "clojure".to_string(),
5030 LanguageConfig {
5031 extensions: vec![
5032 "clj".to_string(),
5033 "cljs".to_string(),
5034 "cljc".to_string(),
5035 "edn".to_string(),
5036 ],
5037 filenames: vec![],
5038 grammar: "Clojure".to_string(),
5039 comment_prefix: Some(";".to_string()),
5040 auto_indent: true,
5041 auto_close: None,
5042 auto_surround: None,
5043 textmate_grammar: None,
5044 show_whitespace_tabs: true,
5045 line_wrap: None,
5046 wrap_column: None,
5047 page_view: None,
5048 page_width: None,
5049 use_tabs: None,
5050 tab_size: None,
5051 formatter: None,
5052 format_on_save: false,
5053 on_save: vec![],
5054 word_characters: None,
5055 indent: None,
5056 },
5057 );
5058
5059 languages.insert(
5060 "r".to_string(),
5061 LanguageConfig {
5062 extensions: vec!["r".to_string(), "R".to_string(), "rmd".to_string()],
5063 filenames: vec![],
5064 grammar: "R".to_string(),
5065 comment_prefix: Some("#".to_string()),
5066 auto_indent: true,
5067 auto_close: None,
5068 auto_surround: None,
5069 textmate_grammar: None,
5070 show_whitespace_tabs: true,
5071 line_wrap: None,
5072 wrap_column: None,
5073 page_view: None,
5074 page_width: None,
5075 use_tabs: None,
5076 tab_size: None,
5077 formatter: None,
5078 format_on_save: false,
5079 on_save: vec![],
5080 word_characters: None,
5081 indent: None,
5082 },
5083 );
5084
5085 languages.insert(
5086 "julia".to_string(),
5087 LanguageConfig {
5088 extensions: vec!["jl".to_string()],
5089 filenames: vec![],
5090 grammar: "Julia".to_string(),
5091 comment_prefix: Some("#".to_string()),
5092 auto_indent: true,
5093 auto_close: None,
5094 auto_surround: None,
5095 textmate_grammar: None,
5096 show_whitespace_tabs: true,
5097 line_wrap: None,
5098 wrap_column: None,
5099 page_view: None,
5100 page_width: None,
5101 use_tabs: None,
5102 tab_size: None,
5103 formatter: None,
5104 format_on_save: false,
5105 on_save: vec![],
5106 word_characters: None,
5107 indent: None,
5108 },
5109 );
5110
5111 languages.insert(
5112 "perl".to_string(),
5113 LanguageConfig {
5114 extensions: vec!["pl".to_string(), "pm".to_string(), "t".to_string()],
5115 filenames: vec![],
5116 grammar: "Perl".to_string(),
5117 comment_prefix: Some("#".to_string()),
5118 auto_indent: true,
5119 auto_close: None,
5120 auto_surround: None,
5121 textmate_grammar: None,
5122 show_whitespace_tabs: true,
5123 line_wrap: None,
5124 wrap_column: None,
5125 page_view: None,
5126 page_width: None,
5127 use_tabs: None,
5128 tab_size: None,
5129 formatter: None,
5130 format_on_save: false,
5131 on_save: vec![],
5132 word_characters: None,
5133 indent: None,
5134 },
5135 );
5136
5137 languages.insert(
5138 "nim".to_string(),
5139 LanguageConfig {
5140 extensions: vec!["nim".to_string(), "nims".to_string(), "nimble".to_string()],
5141 filenames: vec![],
5142 grammar: "Nim".to_string(),
5143 comment_prefix: Some("#".to_string()),
5144 auto_indent: true,
5145 auto_close: None,
5146 auto_surround: None,
5147 textmate_grammar: None,
5148 show_whitespace_tabs: true,
5149 line_wrap: None,
5150 wrap_column: None,
5151 page_view: None,
5152 page_width: None,
5153 use_tabs: None,
5154 tab_size: None,
5155 formatter: None,
5156 format_on_save: false,
5157 on_save: vec![],
5158 word_characters: None,
5159 indent: None,
5160 },
5161 );
5162
5163 languages.insert(
5164 "gleam".to_string(),
5165 LanguageConfig {
5166 extensions: vec!["gleam".to_string()],
5167 filenames: vec![],
5168 grammar: "Gleam".to_string(),
5169 comment_prefix: Some("//".to_string()),
5170 auto_indent: true,
5171 auto_close: None,
5172 auto_surround: None,
5173 textmate_grammar: None,
5174 show_whitespace_tabs: true,
5175 line_wrap: None,
5176 wrap_column: None,
5177 page_view: None,
5178 page_width: None,
5179 use_tabs: None,
5180 tab_size: None,
5181 formatter: None,
5182 format_on_save: false,
5183 on_save: vec![],
5184 word_characters: None,
5185 indent: None,
5186 },
5187 );
5188
5189 languages.insert(
5190 "racket".to_string(),
5191 LanguageConfig {
5192 extensions: vec![
5193 "rkt".to_string(),
5194 "rktd".to_string(),
5195 "rktl".to_string(),
5196 "scrbl".to_string(),
5197 ],
5198 filenames: vec![],
5199 grammar: "Racket".to_string(),
5200 comment_prefix: Some(";".to_string()),
5201 auto_indent: true,
5202 auto_close: None,
5203 auto_surround: None,
5204 textmate_grammar: None,
5205 show_whitespace_tabs: true,
5206 line_wrap: None,
5207 wrap_column: None,
5208 page_view: None,
5209 page_width: None,
5210 use_tabs: None,
5211 tab_size: None,
5212 formatter: None,
5213 format_on_save: false,
5214 on_save: vec![],
5215 word_characters: None,
5216 indent: None,
5217 },
5218 );
5219
5220 languages.insert(
5221 "fsharp".to_string(),
5222 LanguageConfig {
5223 extensions: vec!["fs".to_string(), "fsi".to_string(), "fsx".to_string()],
5224 filenames: vec![],
5225 grammar: "FSharp".to_string(),
5226 comment_prefix: Some("//".to_string()),
5227 auto_indent: true,
5228 auto_close: None,
5229 auto_surround: None,
5230 textmate_grammar: None,
5231 show_whitespace_tabs: true,
5232 line_wrap: None,
5233 wrap_column: None,
5234 page_view: None,
5235 page_width: None,
5236 use_tabs: None,
5237 tab_size: None,
5238 formatter: None,
5239 format_on_save: false,
5240 on_save: vec![],
5241 word_characters: None,
5242 indent: None,
5243 },
5244 );
5245
5246 languages.insert(
5247 "nix".to_string(),
5248 LanguageConfig {
5249 extensions: vec!["nix".to_string()],
5250 filenames: vec![],
5251 grammar: "Nix".to_string(),
5252 comment_prefix: Some("#".to_string()),
5253 auto_indent: true,
5254 auto_close: None,
5255 auto_surround: None,
5256 textmate_grammar: None,
5257 show_whitespace_tabs: true,
5258 line_wrap: None,
5259 wrap_column: None,
5260 page_view: None,
5261 page_width: None,
5262 use_tabs: None,
5263 tab_size: None,
5264 formatter: None,
5265 format_on_save: false,
5266 on_save: vec![],
5267 word_characters: None,
5268 indent: None,
5269 },
5270 );
5271
5272 languages.insert(
5273 "nushell".to_string(),
5274 LanguageConfig {
5275 extensions: vec!["nu".to_string()],
5276 filenames: vec![],
5277 grammar: "Nushell".to_string(),
5278 comment_prefix: Some("#".to_string()),
5279 auto_indent: true,
5280 auto_close: None,
5281 auto_surround: None,
5282 textmate_grammar: None,
5283 show_whitespace_tabs: true,
5284 line_wrap: None,
5285 wrap_column: None,
5286 page_view: None,
5287 page_width: None,
5288 use_tabs: None,
5289 tab_size: None,
5290 formatter: None,
5291 format_on_save: false,
5292 on_save: vec![],
5293 word_characters: None,
5294 indent: None,
5295 },
5296 );
5297
5298 languages.insert(
5299 "solidity".to_string(),
5300 LanguageConfig {
5301 extensions: vec!["sol".to_string()],
5302 filenames: vec![],
5303 grammar: "Solidity".to_string(),
5304 comment_prefix: Some("//".to_string()),
5305 auto_indent: true,
5306 auto_close: None,
5307 auto_surround: None,
5308 textmate_grammar: None,
5309 show_whitespace_tabs: true,
5310 line_wrap: None,
5311 wrap_column: None,
5312 page_view: None,
5313 page_width: None,
5314 use_tabs: None,
5315 tab_size: None,
5316 formatter: None,
5317 format_on_save: false,
5318 on_save: vec![],
5319 word_characters: None,
5320 indent: None,
5321 },
5322 );
5323
5324 languages.insert(
5325 "verilog".to_string(),
5326 LanguageConfig {
5327 extensions: vec!["vh".to_string(), "verilog".to_string()],
5328 filenames: vec![],
5329 grammar: "Verilog".to_string(),
5330 comment_prefix: Some("//".to_string()),
5331 auto_indent: true,
5332 auto_close: None,
5333 auto_surround: None,
5334 textmate_grammar: None,
5335 show_whitespace_tabs: true,
5336 line_wrap: None,
5337 wrap_column: None,
5338 page_view: None,
5339 page_width: None,
5340 use_tabs: None,
5341 tab_size: None,
5342 formatter: None,
5343 format_on_save: false,
5344 on_save: vec![],
5345 word_characters: None,
5346 indent: None,
5347 },
5348 );
5349
5350 languages.insert(
5351 "systemverilog".to_string(),
5352 LanguageConfig {
5353 extensions: vec![
5354 "sv".to_string(),
5355 "svh".to_string(),
5356 "svi".to_string(),
5357 "svp".to_string(),
5358 ],
5359 filenames: vec![],
5360 grammar: "SystemVerilog".to_string(),
5361 comment_prefix: Some("//".to_string()),
5362 auto_indent: true,
5363 auto_close: None,
5364 auto_surround: None,
5365 textmate_grammar: None,
5366 show_whitespace_tabs: true,
5367 line_wrap: None,
5368 wrap_column: None,
5369 page_view: None,
5370 page_width: None,
5371 use_tabs: None,
5372 tab_size: None,
5373 formatter: None,
5374 format_on_save: false,
5375 on_save: vec![],
5376 word_characters: None,
5377 indent: None,
5378 },
5379 );
5380
5381 languages.insert(
5382 "vhdl".to_string(),
5383 LanguageConfig {
5384 extensions: vec!["vhd".to_string(), "vhdl".to_string(), "vho".to_string()],
5385 filenames: vec![],
5386 grammar: "VHDL".to_string(),
5387 comment_prefix: Some("--".to_string()),
5388 auto_indent: true,
5389 auto_close: None,
5390 auto_surround: None,
5391 textmate_grammar: None,
5392 show_whitespace_tabs: true,
5393 line_wrap: None,
5394 wrap_column: None,
5395 page_view: None,
5396 page_width: None,
5397 use_tabs: None,
5398 tab_size: None,
5399 formatter: None,
5400 format_on_save: false,
5401 on_save: vec![],
5402 word_characters: None,
5403 indent: None,
5404 },
5405 );
5406
5407 languages.insert(
5411 "asm".to_string(),
5412 LanguageConfig {
5413 extensions: vec!["asm".to_string(), "nasm".to_string()],
5414 filenames: vec![],
5415 grammar: "Assembly".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 "gas".to_string(),
5438 LanguageConfig {
5439 extensions: vec!["s".to_string(), "S".to_string()],
5442 filenames: vec![],
5443 grammar: "Assembly".to_string(),
5444 comment_prefix: Some("#".to_string()),
5445 auto_indent: true,
5446 auto_close: None,
5447 auto_surround: None,
5448 textmate_grammar: None,
5449 show_whitespace_tabs: true,
5450 line_wrap: None,
5451 wrap_column: None,
5452 page_view: None,
5453 page_width: None,
5454 use_tabs: None,
5455 tab_size: None,
5456 formatter: None,
5457 format_on_save: false,
5458 on_save: vec![],
5459 word_characters: None,
5460 indent: None,
5461 },
5462 );
5463
5464 languages.insert(
5465 "ruby".to_string(),
5466 LanguageConfig {
5467 extensions: vec!["rb".to_string(), "rake".to_string(), "gemspec".to_string()],
5468 filenames: vec![
5469 "Gemfile".to_string(),
5470 "Rakefile".to_string(),
5471 "Guardfile".to_string(),
5472 ],
5473 grammar: "Ruby".to_string(),
5474 comment_prefix: Some("#".to_string()),
5475 auto_indent: true,
5476 auto_close: None,
5477 auto_surround: None,
5478 textmate_grammar: None,
5479 show_whitespace_tabs: true,
5480 line_wrap: None,
5481 wrap_column: None,
5482 page_view: None,
5483 page_width: None,
5484 use_tabs: None,
5485 tab_size: None,
5486 formatter: None,
5487 format_on_save: false,
5488 on_save: vec![],
5489 word_characters: None,
5490 indent: None,
5491 },
5492 );
5493
5494 languages.insert(
5495 "php".to_string(),
5496 LanguageConfig {
5497 extensions: vec!["php".to_string(), "phtml".to_string()],
5498 filenames: vec![],
5499 grammar: "PHP".to_string(),
5500 comment_prefix: Some("//".to_string()),
5501 auto_indent: true,
5502 auto_close: None,
5503 auto_surround: None,
5504 textmate_grammar: None,
5505 show_whitespace_tabs: true,
5506 line_wrap: None,
5507 wrap_column: None,
5508 page_view: None,
5509 page_width: None,
5510 use_tabs: None,
5511 tab_size: None,
5512 formatter: None,
5513 format_on_save: false,
5514 on_save: vec![],
5515 word_characters: None,
5516 indent: None,
5517 },
5518 );
5519
5520 languages.insert(
5521 "lua".to_string(),
5522 LanguageConfig {
5523 extensions: vec!["lua".to_string()],
5524 filenames: vec![],
5525 grammar: "Lua".to_string(),
5526 comment_prefix: Some("--".to_string()),
5527 auto_indent: true,
5528 auto_close: None,
5529 auto_surround: None,
5530 textmate_grammar: None,
5531 show_whitespace_tabs: true,
5532 line_wrap: None,
5533 wrap_column: None,
5534 page_view: None,
5535 page_width: None,
5536 use_tabs: None,
5537 tab_size: None,
5538 formatter: None,
5539 format_on_save: false,
5540 on_save: vec![],
5541 word_characters: None,
5542 indent: None,
5543 },
5544 );
5545
5546 languages.insert(
5547 "html".to_string(),
5548 LanguageConfig {
5549 extensions: vec!["html".to_string(), "htm".to_string()],
5550 filenames: vec![],
5551 grammar: "HTML".to_string(),
5552 comment_prefix: None,
5553 auto_indent: true,
5554 auto_close: None,
5555 auto_surround: None,
5556 textmate_grammar: None,
5557 show_whitespace_tabs: true,
5558 line_wrap: None,
5559 wrap_column: None,
5560 page_view: None,
5561 page_width: None,
5562 use_tabs: None,
5563 tab_size: None,
5564 formatter: None,
5565 format_on_save: false,
5566 on_save: vec![],
5567 word_characters: None,
5568 indent: None,
5569 },
5570 );
5571
5572 languages.insert(
5573 "css".to_string(),
5574 LanguageConfig {
5575 extensions: vec!["css".to_string()],
5576 filenames: vec![],
5577 grammar: "CSS".to_string(),
5578 comment_prefix: None,
5579 auto_indent: true,
5580 auto_close: None,
5581 auto_surround: None,
5582 textmate_grammar: None,
5583 show_whitespace_tabs: true,
5584 line_wrap: None,
5585 wrap_column: None,
5586 page_view: None,
5587 page_width: None,
5588 use_tabs: None,
5589 tab_size: None,
5590 formatter: None,
5591 format_on_save: false,
5592 on_save: vec![],
5593 word_characters: None,
5594 indent: None,
5595 },
5596 );
5597
5598 languages.insert(
5599 "sql".to_string(),
5600 LanguageConfig {
5601 extensions: vec!["sql".to_string()],
5602 filenames: vec![],
5603 grammar: "SQL".to_string(),
5604 comment_prefix: Some("--".to_string()),
5605 auto_indent: true,
5606 auto_close: None,
5607 auto_surround: None,
5608 textmate_grammar: None,
5609 show_whitespace_tabs: true,
5610 line_wrap: None,
5611 wrap_column: None,
5612 page_view: None,
5613 page_width: None,
5614 use_tabs: None,
5615 tab_size: None,
5616 formatter: None,
5617 format_on_save: false,
5618 on_save: vec![],
5619 word_characters: None,
5620 indent: None,
5621 },
5622 );
5623
5624 languages.insert(
5625 "graphql".to_string(),
5626 LanguageConfig {
5627 extensions: vec!["graphql".to_string(), "gql".to_string()],
5628 filenames: vec![],
5629 grammar: "GraphQL".to_string(),
5630 comment_prefix: Some("#".to_string()),
5631 auto_indent: true,
5632 auto_close: None,
5633 auto_surround: None,
5634 textmate_grammar: None,
5635 show_whitespace_tabs: true,
5636 line_wrap: None,
5637 wrap_column: None,
5638 page_view: None,
5639 page_width: None,
5640 use_tabs: None,
5641 tab_size: None,
5642 formatter: None,
5643 format_on_save: false,
5644 on_save: vec![],
5645 word_characters: None,
5646 indent: None,
5647 },
5648 );
5649
5650 languages.insert(
5651 "protobuf".to_string(),
5652 LanguageConfig {
5653 extensions: vec!["proto".to_string()],
5654 filenames: vec![],
5655 grammar: "Protocol Buffers".to_string(),
5656 comment_prefix: Some("//".to_string()),
5657 auto_indent: true,
5658 auto_close: None,
5659 auto_surround: None,
5660 textmate_grammar: None,
5661 show_whitespace_tabs: true,
5662 line_wrap: None,
5663 wrap_column: None,
5664 page_view: None,
5665 page_width: None,
5666 use_tabs: None,
5667 tab_size: None,
5668 formatter: None,
5669 format_on_save: false,
5670 on_save: vec![],
5671 word_characters: None,
5672 indent: None,
5673 },
5674 );
5675
5676 languages.insert(
5677 "cmake".to_string(),
5678 LanguageConfig {
5679 extensions: vec!["cmake".to_string()],
5680 filenames: vec!["CMakeLists.txt".to_string()],
5681 grammar: "CMake".to_string(),
5682 comment_prefix: Some("#".to_string()),
5683 auto_indent: true,
5684 auto_close: None,
5685 auto_surround: None,
5686 textmate_grammar: None,
5687 show_whitespace_tabs: true,
5688 line_wrap: None,
5689 wrap_column: None,
5690 page_view: None,
5691 page_width: None,
5692 use_tabs: None,
5693 tab_size: None,
5694 formatter: None,
5695 format_on_save: false,
5696 on_save: vec![],
5697 word_characters: None,
5698 indent: None,
5699 },
5700 );
5701
5702 languages.insert(
5703 "terraform".to_string(),
5704 LanguageConfig {
5705 extensions: vec!["tf".to_string(), "tfvars".to_string(), "hcl".to_string()],
5706 filenames: vec![],
5707 grammar: "HCL".to_string(),
5708 comment_prefix: Some("#".to_string()),
5709 auto_indent: true,
5710 auto_close: None,
5711 auto_surround: None,
5712 textmate_grammar: None,
5713 show_whitespace_tabs: true,
5714 line_wrap: None,
5715 wrap_column: None,
5716 page_view: None,
5717 page_width: None,
5718 use_tabs: None,
5719 tab_size: None,
5720 formatter: None,
5721 format_on_save: false,
5722 on_save: vec![],
5723 word_characters: None,
5724 indent: None,
5725 },
5726 );
5727
5728 languages.insert(
5729 "vue".to_string(),
5730 LanguageConfig {
5731 extensions: vec!["vue".to_string()],
5732 filenames: vec![],
5733 grammar: "Vue".to_string(),
5734 comment_prefix: None,
5735 auto_indent: true,
5736 auto_close: None,
5737 auto_surround: None,
5738 textmate_grammar: None,
5739 show_whitespace_tabs: true,
5740 line_wrap: None,
5741 wrap_column: None,
5742 page_view: None,
5743 page_width: None,
5744 use_tabs: None,
5745 tab_size: None,
5746 formatter: None,
5747 format_on_save: false,
5748 on_save: vec![],
5749 word_characters: None,
5750 indent: None,
5751 },
5752 );
5753
5754 languages.insert(
5755 "svelte".to_string(),
5756 LanguageConfig {
5757 extensions: vec!["svelte".to_string()],
5758 filenames: vec![],
5759 grammar: "Svelte".to_string(),
5760 comment_prefix: None,
5761 auto_indent: true,
5762 auto_close: None,
5763 auto_surround: None,
5764 textmate_grammar: None,
5765 show_whitespace_tabs: true,
5766 line_wrap: None,
5767 wrap_column: None,
5768 page_view: None,
5769 page_width: None,
5770 use_tabs: None,
5771 tab_size: None,
5772 formatter: None,
5773 format_on_save: false,
5774 on_save: vec![],
5775 word_characters: None,
5776 indent: None,
5777 },
5778 );
5779
5780 languages.insert(
5781 "astro".to_string(),
5782 LanguageConfig {
5783 extensions: vec!["astro".to_string()],
5784 filenames: vec![],
5785 grammar: "Astro".to_string(),
5786 comment_prefix: None,
5787 auto_indent: true,
5788 auto_close: None,
5789 auto_surround: None,
5790 textmate_grammar: None,
5791 show_whitespace_tabs: true,
5792 line_wrap: None,
5793 wrap_column: None,
5794 page_view: None,
5795 page_width: None,
5796 use_tabs: None,
5797 tab_size: None,
5798 formatter: None,
5799 format_on_save: false,
5800 on_save: vec![],
5801 word_characters: None,
5802 indent: None,
5803 },
5804 );
5805
5806 languages.insert(
5809 "scss".to_string(),
5810 LanguageConfig {
5811 extensions: vec!["scss".to_string()],
5812 filenames: vec![],
5813 grammar: "SCSS".to_string(),
5814 comment_prefix: Some("//".to_string()),
5815 auto_indent: true,
5816 auto_close: None,
5817 auto_surround: None,
5818 textmate_grammar: None,
5819 show_whitespace_tabs: true,
5820 line_wrap: None,
5821 wrap_column: None,
5822 page_view: None,
5823 page_width: None,
5824 use_tabs: None,
5825 tab_size: None,
5826 formatter: None,
5827 format_on_save: false,
5828 on_save: vec![],
5829 word_characters: None,
5830 indent: None,
5831 },
5832 );
5833
5834 languages.insert(
5835 "less".to_string(),
5836 LanguageConfig {
5837 extensions: vec!["less".to_string()],
5838 filenames: vec![],
5839 grammar: "LESS".to_string(),
5840 comment_prefix: Some("//".to_string()),
5841 auto_indent: true,
5842 auto_close: None,
5843 auto_surround: None,
5844 textmate_grammar: None,
5845 show_whitespace_tabs: true,
5846 line_wrap: None,
5847 wrap_column: None,
5848 page_view: None,
5849 page_width: None,
5850 use_tabs: None,
5851 tab_size: None,
5852 formatter: None,
5853 format_on_save: false,
5854 on_save: vec![],
5855 word_characters: None,
5856 indent: None,
5857 },
5858 );
5859
5860 languages.insert(
5861 "powershell".to_string(),
5862 LanguageConfig {
5863 extensions: vec!["ps1".to_string(), "psm1".to_string(), "psd1".to_string()],
5864 filenames: vec![],
5865 grammar: "PowerShell".to_string(),
5866 comment_prefix: Some("#".to_string()),
5867 auto_indent: true,
5868 auto_close: None,
5869 auto_surround: None,
5870 textmate_grammar: None,
5871 show_whitespace_tabs: true,
5872 line_wrap: None,
5873 wrap_column: None,
5874 page_view: None,
5875 page_width: None,
5876 use_tabs: None,
5877 tab_size: None,
5878 formatter: None,
5879 format_on_save: false,
5880 on_save: vec![],
5881 word_characters: None,
5882 indent: None,
5883 },
5884 );
5885
5886 languages.insert(
5887 "kdl".to_string(),
5888 LanguageConfig {
5889 extensions: vec!["kdl".to_string()],
5890 filenames: vec![],
5891 grammar: "KDL".to_string(),
5892 comment_prefix: Some("//".to_string()),
5893 auto_indent: true,
5894 auto_close: None,
5895 auto_surround: None,
5896 textmate_grammar: None,
5897 show_whitespace_tabs: true,
5898 line_wrap: None,
5899 wrap_column: None,
5900 page_view: None,
5901 page_width: None,
5902 use_tabs: None,
5903 tab_size: None,
5904 formatter: None,
5905 format_on_save: false,
5906 on_save: vec![],
5907 word_characters: None,
5908 indent: None,
5909 },
5910 );
5911
5912 languages.insert(
5913 "starlark".to_string(),
5914 LanguageConfig {
5915 extensions: vec!["bzl".to_string(), "star".to_string()],
5916 filenames: vec!["BUILD".to_string(), "WORKSPACE".to_string()],
5917 grammar: "Starlark".to_string(),
5918 comment_prefix: Some("#".to_string()),
5919 auto_indent: true,
5920 auto_close: None,
5921 auto_surround: None,
5922 textmate_grammar: None,
5923 show_whitespace_tabs: true,
5924 line_wrap: None,
5925 wrap_column: None,
5926 page_view: None,
5927 page_width: None,
5928 use_tabs: None,
5929 tab_size: None,
5930 formatter: None,
5931 format_on_save: false,
5932 on_save: vec![],
5933 word_characters: None,
5934 indent: None,
5935 },
5936 );
5937
5938 languages.insert(
5939 "justfile".to_string(),
5940 LanguageConfig {
5941 extensions: vec![],
5942 filenames: vec![
5943 "justfile".to_string(),
5944 "Justfile".to_string(),
5945 ".justfile".to_string(),
5946 ],
5947 grammar: "Justfile".to_string(),
5948 comment_prefix: Some("#".to_string()),
5949 auto_indent: true,
5950 auto_close: None,
5951 auto_surround: None,
5952 textmate_grammar: None,
5953 show_whitespace_tabs: true,
5954 line_wrap: None,
5955 wrap_column: None,
5956 page_view: None,
5957 page_width: None,
5958 use_tabs: Some(true),
5959 tab_size: None,
5960 formatter: None,
5961 format_on_save: false,
5962 on_save: vec![],
5963 word_characters: None,
5964 indent: None,
5965 },
5966 );
5967
5968 languages.insert(
5969 "earthfile".to_string(),
5970 LanguageConfig {
5971 extensions: vec!["earth".to_string()],
5972 filenames: vec!["Earthfile".to_string()],
5973 grammar: "Earthfile".to_string(),
5974 comment_prefix: Some("#".to_string()),
5975 auto_indent: true,
5976 auto_close: None,
5977 auto_surround: None,
5978 textmate_grammar: None,
5979 show_whitespace_tabs: true,
5980 line_wrap: None,
5981 wrap_column: None,
5982 page_view: None,
5983 page_width: None,
5984 use_tabs: None,
5985 tab_size: None,
5986 formatter: None,
5987 format_on_save: false,
5988 on_save: vec![],
5989 word_characters: None,
5990 indent: None,
5991 },
5992 );
5993
5994 languages.insert(
5995 "gomod".to_string(),
5996 LanguageConfig {
5997 extensions: vec![],
5998 filenames: vec!["go.mod".to_string(), "go.sum".to_string()],
5999 grammar: "Go Module".to_string(),
6000 comment_prefix: Some("//".to_string()),
6001 auto_indent: true,
6002 auto_close: None,
6003 auto_surround: None,
6004 textmate_grammar: None,
6005 show_whitespace_tabs: true,
6006 line_wrap: None,
6007 wrap_column: None,
6008 page_view: None,
6009 page_width: None,
6010 use_tabs: Some(true),
6011 tab_size: None,
6012 formatter: None,
6013 format_on_save: false,
6014 on_save: vec![],
6015 word_characters: None,
6016 indent: None,
6017 },
6018 );
6019
6020 languages.insert(
6021 "vlang".to_string(),
6022 LanguageConfig {
6023 extensions: vec!["v".to_string(), "vv".to_string()],
6024 filenames: vec![],
6025 grammar: "V".to_string(),
6026 comment_prefix: Some("//".to_string()),
6027 auto_indent: true,
6028 auto_close: None,
6029 auto_surround: None,
6030 textmate_grammar: None,
6031 show_whitespace_tabs: true,
6032 line_wrap: None,
6033 wrap_column: None,
6034 page_view: None,
6035 page_width: None,
6036 use_tabs: None,
6037 tab_size: None,
6038 formatter: None,
6039 format_on_save: false,
6040 on_save: vec![],
6041 word_characters: None,
6042 indent: None,
6043 },
6044 );
6045
6046 languages.insert(
6047 "ini".to_string(),
6048 LanguageConfig {
6049 extensions: vec!["ini".to_string(), "cfg".to_string()],
6050 filenames: vec![],
6051 grammar: "INI".to_string(),
6052 comment_prefix: Some(";".to_string()),
6053 auto_indent: false,
6054 auto_close: None,
6055 auto_surround: None,
6056 textmate_grammar: None,
6057 show_whitespace_tabs: true,
6058 line_wrap: None,
6059 wrap_column: None,
6060 page_view: None,
6061 page_width: None,
6062 use_tabs: None,
6063 tab_size: None,
6064 formatter: None,
6065 format_on_save: false,
6066 on_save: vec![],
6067 word_characters: None,
6068 indent: None,
6069 },
6070 );
6071
6072 languages.insert(
6073 "hyprlang".to_string(),
6074 LanguageConfig {
6075 extensions: vec!["hl".to_string()],
6076 filenames: vec!["hyprland.conf".to_string()],
6077 grammar: "Hyprlang".to_string(),
6078 comment_prefix: Some("#".to_string()),
6079 auto_indent: true,
6080 auto_close: None,
6081 auto_surround: None,
6082 textmate_grammar: None,
6083 show_whitespace_tabs: true,
6084 line_wrap: None,
6085 wrap_column: None,
6086 page_view: None,
6087 page_width: None,
6088 use_tabs: None,
6089 tab_size: None,
6090 formatter: None,
6091 format_on_save: false,
6092 on_save: vec![],
6093 word_characters: None,
6094 indent: None,
6095 },
6096 );
6097
6098 languages
6099 }
6100
6101 #[cfg(feature = "runtime")]
6103 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
6104 let mut lsp = HashMap::new();
6105
6106 let ra_log_path = crate::services::log_dirs::lsp_log_path("rust-analyzer")
6109 .to_string_lossy()
6110 .to_string();
6111
6112 Self::populate_lsp_config(&mut lsp, ra_log_path);
6113 lsp
6114 }
6115
6116 #[cfg(not(feature = "runtime"))]
6118 fn default_lsp_config() -> HashMap<String, LspLanguageConfig> {
6119 HashMap::new()
6121 }
6122
6123 #[cfg(feature = "runtime")]
6125 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
6126 let mut universal = HashMap::new();
6127
6128 universal.insert(
6141 "quicklsp".to_string(),
6142 LspLanguageConfig::Multi(vec![LspServerConfig {
6143 command: "quicklsp".to_string(),
6144 args: vec![],
6145 enabled: false,
6146 auto_start: false,
6147 process_limits: ProcessLimits::default(),
6148 initialization_options: None,
6149 env: Default::default(),
6150 language_id_overrides: Default::default(),
6151 name: Some("QuickLSP".to_string()),
6152 only_features: None,
6153 except_features: None,
6154 root_markers: vec![
6155 "Cargo.toml".to_string(),
6156 "package.json".to_string(),
6157 "go.mod".to_string(),
6158 "pyproject.toml".to_string(),
6159 "requirements.txt".to_string(),
6160 ".git".to_string(),
6161 ],
6162 }]),
6163 );
6164
6165 universal
6166 }
6167
6168 #[cfg(not(feature = "runtime"))]
6170 fn default_universal_lsp_config() -> HashMap<String, LspLanguageConfig> {
6171 HashMap::new()
6172 }
6173
6174 #[cfg(feature = "runtime")]
6175 fn populate_lsp_config(lsp: &mut HashMap<String, LspLanguageConfig>, ra_log_path: String) {
6176 lsp.insert(
6180 "rust".to_string(),
6181 LspLanguageConfig::Multi(vec![LspServerConfig {
6182 command: "rust-analyzer".to_string(),
6183 args: vec!["--log-file".to_string(), ra_log_path],
6184 enabled: true,
6185 auto_start: false,
6186 process_limits: ProcessLimits::unlimited(),
6187 initialization_options: None,
6188 env: Default::default(),
6189 language_id_overrides: Default::default(),
6190 name: None,
6191 only_features: None,
6192 except_features: None,
6193 root_markers: vec![
6194 "Cargo.toml".to_string(),
6195 "rust-project.json".to_string(),
6196 ".git".to_string(),
6197 ],
6198 }]),
6199 );
6200
6201 lsp.insert(
6203 "python".to_string(),
6204 LspLanguageConfig::Multi(vec![LspServerConfig {
6205 command: "pylsp".to_string(),
6206 args: vec![],
6207 enabled: true,
6208 auto_start: false,
6209 process_limits: ProcessLimits::default(),
6210 initialization_options: None,
6211 env: Default::default(),
6212 language_id_overrides: Default::default(),
6213 name: None,
6214 only_features: None,
6215 except_features: None,
6216 root_markers: vec![
6217 "pyproject.toml".to_string(),
6218 "setup.py".to_string(),
6219 "setup.cfg".to_string(),
6220 "pyrightconfig.json".to_string(),
6221 ".git".to_string(),
6222 ],
6223 }]),
6224 );
6225
6226 lsp.insert(
6230 "gdscript".to_string(),
6231 LspLanguageConfig::Multi(vec![LspServerConfig {
6232 command: "nc".to_string(),
6233 args: vec!["127.0.0.1".to_string(), "6005".to_string()],
6234 enabled: false,
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: Some("Godot GDScript".to_string()),
6241 only_features: None,
6242 except_features: None,
6243 root_markers: vec!["project.godot".to_string(), ".git".to_string()],
6244 }]),
6245 );
6246
6247 lsp.insert(
6250 "javascript".to_string(),
6251 LspLanguageConfig::Multi(vec![LspServerConfig {
6252 command: "typescript-language-server".to_string(),
6253 args: vec!["--stdio".to_string()],
6254 enabled: true,
6255 auto_start: false,
6256 process_limits: ProcessLimits::default(),
6257 initialization_options: None,
6258 env: Default::default(),
6259 language_id_overrides: HashMap::from([(
6260 "jsx".to_string(),
6261 "javascriptreact".to_string(),
6262 )]),
6263 name: None,
6264 only_features: None,
6265 except_features: None,
6266 root_markers: vec![
6267 "tsconfig.json".to_string(),
6268 "jsconfig.json".to_string(),
6269 "package.json".to_string(),
6270 ".git".to_string(),
6271 ],
6272 }]),
6273 );
6274 lsp.insert(
6275 "typescript".to_string(),
6276 LspLanguageConfig::Multi(vec![LspServerConfig {
6277 command: "typescript-language-server".to_string(),
6278 args: vec!["--stdio".to_string()],
6279 enabled: true,
6280 auto_start: false,
6281 process_limits: ProcessLimits::default(),
6282 initialization_options: None,
6283 env: Default::default(),
6284 language_id_overrides: HashMap::from([(
6285 "tsx".to_string(),
6286 "typescriptreact".to_string(),
6287 )]),
6288 name: None,
6289 only_features: None,
6290 except_features: None,
6291 root_markers: vec![
6292 "tsconfig.json".to_string(),
6293 "jsconfig.json".to_string(),
6294 "package.json".to_string(),
6295 ".git".to_string(),
6296 ],
6297 }]),
6298 );
6299
6300 lsp.insert(
6302 "html".to_string(),
6303 LspLanguageConfig::Multi(vec![LspServerConfig {
6304 command: "vscode-html-language-server".to_string(),
6305 args: vec!["--stdio".to_string()],
6306 enabled: true,
6307 auto_start: false,
6308 process_limits: ProcessLimits::default(),
6309 initialization_options: None,
6310 env: Default::default(),
6311 language_id_overrides: Default::default(),
6312 name: None,
6313 only_features: None,
6314 except_features: None,
6315 root_markers: Default::default(),
6316 }]),
6317 );
6318
6319 lsp.insert(
6321 "css".to_string(),
6322 LspLanguageConfig::Multi(vec![LspServerConfig {
6323 command: "vscode-css-language-server".to_string(),
6324 args: vec!["--stdio".to_string()],
6325 enabled: true,
6326 auto_start: false,
6327 process_limits: ProcessLimits::default(),
6328 initialization_options: None,
6329 env: Default::default(),
6330 language_id_overrides: Default::default(),
6331 name: None,
6332 only_features: None,
6333 except_features: None,
6334 root_markers: Default::default(),
6335 }]),
6336 );
6337
6338 lsp.insert(
6340 "c".to_string(),
6341 LspLanguageConfig::Multi(vec![LspServerConfig {
6342 command: "clangd".to_string(),
6343 args: vec![],
6344 enabled: true,
6345 auto_start: false,
6346 process_limits: ProcessLimits::default(),
6347 initialization_options: None,
6348 env: Default::default(),
6349 language_id_overrides: Default::default(),
6350 name: None,
6351 only_features: None,
6352 except_features: None,
6353 root_markers: vec![
6354 "compile_commands.json".to_string(),
6355 "CMakeLists.txt".to_string(),
6356 "Makefile".to_string(),
6357 ".git".to_string(),
6358 ],
6359 }]),
6360 );
6361 lsp.insert(
6362 "cpp".to_string(),
6363 LspLanguageConfig::Multi(vec![LspServerConfig {
6364 command: "clangd".to_string(),
6365 args: vec![],
6366 enabled: true,
6367 auto_start: false,
6368 process_limits: ProcessLimits::default(),
6369 initialization_options: None,
6370 env: Default::default(),
6371 language_id_overrides: Default::default(),
6372 name: None,
6373 only_features: None,
6374 except_features: None,
6375 root_markers: vec![
6376 "compile_commands.json".to_string(),
6377 "CMakeLists.txt".to_string(),
6378 "Makefile".to_string(),
6379 ".git".to_string(),
6380 ],
6381 }]),
6382 );
6383
6384 lsp.insert(
6386 "go".to_string(),
6387 LspLanguageConfig::Multi(vec![LspServerConfig {
6388 command: "gopls".to_string(),
6389 args: vec![],
6390 enabled: true,
6391 auto_start: false,
6392 process_limits: ProcessLimits::default(),
6393 initialization_options: None,
6394 env: Default::default(),
6395 language_id_overrides: Default::default(),
6396 name: None,
6397 only_features: None,
6398 except_features: None,
6399 root_markers: vec![
6400 "go.mod".to_string(),
6401 "go.work".to_string(),
6402 ".git".to_string(),
6403 ],
6404 }]),
6405 );
6406
6407 lsp.insert(
6409 "json".to_string(),
6410 LspLanguageConfig::Multi(vec![LspServerConfig {
6411 command: "vscode-json-language-server".to_string(),
6412 args: vec!["--stdio".to_string()],
6413 enabled: true,
6414 auto_start: false,
6415 process_limits: ProcessLimits::default(),
6416 initialization_options: None,
6417 env: Default::default(),
6418 language_id_overrides: Default::default(),
6419 name: None,
6420 only_features: None,
6421 except_features: None,
6422 root_markers: Default::default(),
6423 }]),
6424 );
6425
6426 lsp.insert(
6430 "jsonc".to_string(),
6431 LspLanguageConfig::Multi(vec![LspServerConfig {
6432 command: "vscode-json-language-server".to_string(),
6433 args: vec!["--stdio".to_string()],
6434 enabled: true,
6435 auto_start: false,
6436 process_limits: ProcessLimits::default(),
6437 initialization_options: None,
6438 env: Default::default(),
6439 language_id_overrides: Default::default(),
6440 name: None,
6441 only_features: None,
6442 except_features: None,
6443 root_markers: Default::default(),
6444 }]),
6445 );
6446
6447 lsp.insert(
6449 "csharp".to_string(),
6450 LspLanguageConfig::Multi(vec![LspServerConfig {
6451 command: "csharp-ls".to_string(),
6452 args: vec![],
6453 enabled: true,
6454 auto_start: false,
6455 process_limits: ProcessLimits::default(),
6456 initialization_options: None,
6457 env: Default::default(),
6458 language_id_overrides: Default::default(),
6459 name: None,
6460 only_features: None,
6461 except_features: None,
6462 root_markers: vec![
6463 "*.csproj".to_string(),
6464 "*.sln".to_string(),
6465 ".git".to_string(),
6466 ],
6467 }]),
6468 );
6469
6470 lsp.insert(
6473 "odin".to_string(),
6474 LspLanguageConfig::Multi(vec![LspServerConfig {
6475 command: "ols".to_string(),
6476 args: vec![],
6477 enabled: true,
6478 auto_start: false,
6479 process_limits: ProcessLimits::default(),
6480 initialization_options: None,
6481 env: Default::default(),
6482 language_id_overrides: Default::default(),
6483 name: None,
6484 only_features: None,
6485 except_features: None,
6486 root_markers: Default::default(),
6487 }]),
6488 );
6489
6490 lsp.insert(
6493 "zig".to_string(),
6494 LspLanguageConfig::Multi(vec![LspServerConfig {
6495 command: "zls".to_string(),
6496 args: vec![],
6497 enabled: true,
6498 auto_start: false,
6499 process_limits: ProcessLimits::default(),
6500 initialization_options: None,
6501 env: Default::default(),
6502 language_id_overrides: Default::default(),
6503 name: None,
6504 only_features: None,
6505 except_features: None,
6506 root_markers: Default::default(),
6507 }]),
6508 );
6509
6510 lsp.insert(
6513 "c3".to_string(),
6514 LspLanguageConfig::Multi(vec![LspServerConfig {
6515 command: "c3lsp".to_string(),
6516 args: vec![],
6517 enabled: true,
6518 auto_start: false,
6519 process_limits: ProcessLimits::default(),
6520 initialization_options: None,
6521 env: Default::default(),
6522 language_id_overrides: Default::default(),
6523 name: None,
6524 only_features: None,
6525 except_features: None,
6526 root_markers: vec!["project.json".to_string(), ".git".to_string()],
6527 }]),
6528 );
6529
6530 lsp.insert(
6533 "java".to_string(),
6534 LspLanguageConfig::Multi(vec![LspServerConfig {
6535 command: "jdtls".to_string(),
6536 args: vec![],
6537 enabled: true,
6538 auto_start: false,
6539 process_limits: ProcessLimits::default(),
6540 initialization_options: None,
6541 env: Default::default(),
6542 language_id_overrides: Default::default(),
6543 name: None,
6544 only_features: None,
6545 except_features: None,
6546 root_markers: vec![
6547 "pom.xml".to_string(),
6548 "build.gradle".to_string(),
6549 "build.gradle.kts".to_string(),
6550 ".git".to_string(),
6551 ],
6552 }]),
6553 );
6554
6555 lsp.insert(
6558 "latex".to_string(),
6559 LspLanguageConfig::Multi(vec![LspServerConfig {
6560 command: "texlab".to_string(),
6561 args: vec![],
6562 enabled: true,
6563 auto_start: false,
6564 process_limits: ProcessLimits::default(),
6565 initialization_options: None,
6566 env: Default::default(),
6567 language_id_overrides: Default::default(),
6568 name: None,
6569 only_features: None,
6570 except_features: None,
6571 root_markers: Default::default(),
6572 }]),
6573 );
6574
6575 lsp.insert(
6578 "markdown".to_string(),
6579 LspLanguageConfig::Multi(vec![LspServerConfig {
6580 command: "marksman".to_string(),
6581 args: vec!["server".to_string()],
6582 enabled: true,
6583 auto_start: false,
6584 process_limits: ProcessLimits::default(),
6585 initialization_options: None,
6586 env: Default::default(),
6587 language_id_overrides: Default::default(),
6588 name: None,
6589 only_features: None,
6590 except_features: None,
6591 root_markers: Default::default(),
6592 }]),
6593 );
6594
6595 lsp.insert(
6598 "templ".to_string(),
6599 LspLanguageConfig::Multi(vec![LspServerConfig {
6600 command: "templ".to_string(),
6601 args: vec!["lsp".to_string()],
6602 enabled: true,
6603 auto_start: false,
6604 process_limits: ProcessLimits::default(),
6605 initialization_options: None,
6606 env: Default::default(),
6607 language_id_overrides: Default::default(),
6608 name: None,
6609 only_features: None,
6610 except_features: None,
6611 root_markers: Default::default(),
6612 }]),
6613 );
6614
6615 lsp.insert(
6618 "typst".to_string(),
6619 LspLanguageConfig::Multi(vec![LspServerConfig {
6620 command: "tinymist".to_string(),
6621 args: vec![],
6622 enabled: true,
6623 auto_start: false,
6624 process_limits: ProcessLimits::default(),
6625 initialization_options: None,
6626 env: Default::default(),
6627 language_id_overrides: Default::default(),
6628 name: None,
6629 only_features: None,
6630 except_features: None,
6631 root_markers: Default::default(),
6632 }]),
6633 );
6634
6635 lsp.insert(
6637 "bash".to_string(),
6638 LspLanguageConfig::Multi(vec![LspServerConfig {
6639 command: "bash-language-server".to_string(),
6640 args: vec!["start".to_string()],
6641 enabled: true,
6642 auto_start: false,
6643 process_limits: ProcessLimits::default(),
6644 initialization_options: None,
6645 env: Default::default(),
6646 language_id_overrides: Default::default(),
6647 name: None,
6648 only_features: None,
6649 except_features: None,
6650 root_markers: Default::default(),
6651 }]),
6652 );
6653
6654 lsp.insert(
6657 "lua".to_string(),
6658 LspLanguageConfig::Multi(vec![LspServerConfig {
6659 command: "lua-language-server".to_string(),
6660 args: vec![],
6661 enabled: true,
6662 auto_start: false,
6663 process_limits: ProcessLimits::default(),
6664 initialization_options: None,
6665 env: Default::default(),
6666 language_id_overrides: Default::default(),
6667 name: None,
6668 only_features: None,
6669 except_features: None,
6670 root_markers: vec![
6671 ".luarc.json".to_string(),
6672 ".luarc.jsonc".to_string(),
6673 ".luacheckrc".to_string(),
6674 ".stylua.toml".to_string(),
6675 ".git".to_string(),
6676 ],
6677 }]),
6678 );
6679
6680 lsp.insert(
6682 "ruby".to_string(),
6683 LspLanguageConfig::Multi(vec![LspServerConfig {
6684 command: "solargraph".to_string(),
6685 args: vec!["stdio".to_string()],
6686 enabled: true,
6687 auto_start: false,
6688 process_limits: ProcessLimits::default(),
6689 initialization_options: None,
6690 env: Default::default(),
6691 language_id_overrides: Default::default(),
6692 name: None,
6693 only_features: None,
6694 except_features: None,
6695 root_markers: vec![
6696 "Gemfile".to_string(),
6697 ".ruby-version".to_string(),
6698 ".git".to_string(),
6699 ],
6700 }]),
6701 );
6702
6703 lsp.insert(
6706 "php".to_string(),
6707 LspLanguageConfig::Multi(vec![LspServerConfig {
6708 command: "phpactor".to_string(),
6709 args: vec!["language-server".to_string()],
6710 enabled: true,
6711 auto_start: false,
6712 process_limits: ProcessLimits::default(),
6713 initialization_options: None,
6714 env: Default::default(),
6715 language_id_overrides: Default::default(),
6716 name: None,
6717 only_features: None,
6718 except_features: None,
6719 root_markers: vec!["composer.json".to_string(), ".git".to_string()],
6720 }]),
6721 );
6722
6723 lsp.insert(
6725 "yaml".to_string(),
6726 LspLanguageConfig::Multi(vec![LspServerConfig {
6727 command: "yaml-language-server".to_string(),
6728 args: vec!["--stdio".to_string()],
6729 enabled: true,
6730 auto_start: false,
6731 process_limits: ProcessLimits::default(),
6732 initialization_options: None,
6733 env: Default::default(),
6734 language_id_overrides: Default::default(),
6735 name: None,
6736 only_features: None,
6737 except_features: None,
6738 root_markers: Default::default(),
6739 }]),
6740 );
6741
6742 lsp.insert(
6745 "toml".to_string(),
6746 LspLanguageConfig::Multi(vec![LspServerConfig {
6747 command: "taplo".to_string(),
6748 args: vec!["lsp".to_string(), "stdio".to_string()],
6749 enabled: true,
6750 auto_start: false,
6751 process_limits: ProcessLimits::default(),
6752 initialization_options: None,
6753 env: Default::default(),
6754 language_id_overrides: Default::default(),
6755 name: None,
6756 only_features: None,
6757 except_features: None,
6758 root_markers: Default::default(),
6759 }]),
6760 );
6761
6762 lsp.insert(
6765 "dart".to_string(),
6766 LspLanguageConfig::Multi(vec![LspServerConfig {
6767 command: "dart".to_string(),
6768 args: vec!["language-server".to_string(), "--protocol=lsp".to_string()],
6769 enabled: true,
6770 auto_start: false,
6771 process_limits: ProcessLimits::default(),
6772 initialization_options: None,
6773 env: Default::default(),
6774 language_id_overrides: Default::default(),
6775 name: None,
6776 only_features: None,
6777 except_features: None,
6778 root_markers: vec!["pubspec.yaml".to_string(), ".git".to_string()],
6779 }]),
6780 );
6781
6782 lsp.insert(
6785 "nushell".to_string(),
6786 LspLanguageConfig::Multi(vec![LspServerConfig {
6787 command: "nu".to_string(),
6788 args: vec!["--lsp".to_string()],
6789 enabled: true,
6790 auto_start: false,
6791 process_limits: ProcessLimits::default(),
6792 initialization_options: None,
6793 env: Default::default(),
6794 language_id_overrides: Default::default(),
6795 name: None,
6796 only_features: None,
6797 except_features: None,
6798 root_markers: Default::default(),
6799 }]),
6800 );
6801
6802 lsp.insert(
6805 "solidity".to_string(),
6806 LspLanguageConfig::Multi(vec![LspServerConfig {
6807 command: "nomicfoundation-solidity-language-server".to_string(),
6808 args: vec!["--stdio".to_string()],
6809 enabled: true,
6810 auto_start: false,
6811 process_limits: ProcessLimits::default(),
6812 initialization_options: None,
6813 env: Default::default(),
6814 language_id_overrides: Default::default(),
6815 name: None,
6816 only_features: None,
6817 except_features: None,
6818 root_markers: Default::default(),
6819 }]),
6820 );
6821
6822 lsp.insert(
6827 "terraform".to_string(),
6828 LspLanguageConfig::Multi(vec![LspServerConfig {
6829 command: "terraform-ls".to_string(),
6830 args: vec!["serve".to_string()],
6831 enabled: true,
6832 auto_start: false,
6833 process_limits: ProcessLimits::default(),
6834 initialization_options: None,
6835 env: Default::default(),
6836 language_id_overrides: Default::default(),
6837 name: None,
6838 only_features: None,
6839 except_features: None,
6840 root_markers: vec![
6841 "*.tf".to_string(),
6842 ".terraform".to_string(),
6843 ".git".to_string(),
6844 ],
6845 }]),
6846 );
6847
6848 lsp.insert(
6851 "cmake".to_string(),
6852 LspLanguageConfig::Multi(vec![LspServerConfig {
6853 command: "cmake-language-server".to_string(),
6854 args: vec![],
6855 enabled: true,
6856 auto_start: false,
6857 process_limits: ProcessLimits::default(),
6858 initialization_options: None,
6859 env: Default::default(),
6860 language_id_overrides: Default::default(),
6861 name: None,
6862 only_features: None,
6863 except_features: None,
6864 root_markers: vec!["CMakeLists.txt".to_string(), ".git".to_string()],
6865 }]),
6866 );
6867
6868 lsp.insert(
6871 "protobuf".to_string(),
6872 LspLanguageConfig::Multi(vec![LspServerConfig {
6873 command: "buf".to_string(),
6874 args: vec!["beta".to_string(), "lsp".to_string()],
6875 enabled: true,
6876 auto_start: false,
6877 process_limits: ProcessLimits::default(),
6878 initialization_options: None,
6879 env: Default::default(),
6880 language_id_overrides: Default::default(),
6881 name: None,
6882 only_features: None,
6883 except_features: None,
6884 root_markers: Default::default(),
6885 }]),
6886 );
6887
6888 lsp.insert(
6891 "graphql".to_string(),
6892 LspLanguageConfig::Multi(vec![LspServerConfig {
6893 command: "graphql-lsp".to_string(),
6894 args: vec!["server".to_string(), "-m".to_string(), "stream".to_string()],
6895 enabled: true,
6896 auto_start: false,
6897 process_limits: ProcessLimits::default(),
6898 initialization_options: None,
6899 env: Default::default(),
6900 language_id_overrides: Default::default(),
6901 name: None,
6902 only_features: None,
6903 except_features: None,
6904 root_markers: Default::default(),
6905 }]),
6906 );
6907
6908 lsp.insert(
6911 "sql".to_string(),
6912 LspLanguageConfig::Multi(vec![LspServerConfig {
6913 command: "sqls".to_string(),
6914 args: vec![],
6915 enabled: true,
6916 auto_start: false,
6917 process_limits: ProcessLimits::default(),
6918 initialization_options: None,
6919 env: Default::default(),
6920 language_id_overrides: Default::default(),
6921 name: None,
6922 only_features: None,
6923 except_features: None,
6924 root_markers: Default::default(),
6925 }]),
6926 );
6927
6928 lsp.insert(
6932 "vue".to_string(),
6933 LspLanguageConfig::Multi(vec![LspServerConfig {
6934 command: "vue-language-server".to_string(),
6935 args: vec!["--stdio".to_string()],
6936 enabled: true,
6937 auto_start: false,
6938 process_limits: ProcessLimits::default(),
6939 initialization_options: None,
6940 env: Default::default(),
6941 language_id_overrides: Default::default(),
6942 name: None,
6943 only_features: None,
6944 except_features: None,
6945 root_markers: Default::default(),
6946 }]),
6947 );
6948
6949 lsp.insert(
6951 "svelte".to_string(),
6952 LspLanguageConfig::Multi(vec![LspServerConfig {
6953 command: "svelteserver".to_string(),
6954 args: vec!["--stdio".to_string()],
6955 enabled: true,
6956 auto_start: false,
6957 process_limits: ProcessLimits::default(),
6958 initialization_options: None,
6959 env: Default::default(),
6960 language_id_overrides: Default::default(),
6961 name: None,
6962 only_features: None,
6963 except_features: None,
6964 root_markers: Default::default(),
6965 }]),
6966 );
6967
6968 lsp.insert(
6970 "astro".to_string(),
6971 LspLanguageConfig::Multi(vec![LspServerConfig {
6972 command: "astro-ls".to_string(),
6973 args: vec!["--stdio".to_string()],
6974 enabled: true,
6975 auto_start: false,
6976 process_limits: ProcessLimits::default(),
6977 initialization_options: None,
6978 env: Default::default(),
6979 language_id_overrides: Default::default(),
6980 name: None,
6981 only_features: None,
6982 except_features: None,
6983 root_markers: Default::default(),
6984 }]),
6985 );
6986
6987 lsp.insert(
6989 "tailwindcss".to_string(),
6990 LspLanguageConfig::Multi(vec![LspServerConfig {
6991 command: "tailwindcss-language-server".to_string(),
6992 args: vec!["--stdio".to_string()],
6993 enabled: true,
6994 auto_start: false,
6995 process_limits: ProcessLimits::default(),
6996 initialization_options: None,
6997 env: Default::default(),
6998 language_id_overrides: Default::default(),
6999 name: None,
7000 only_features: None,
7001 except_features: None,
7002 root_markers: Default::default(),
7003 }]),
7004 );
7005
7006 lsp.insert(
7011 "nix".to_string(),
7012 LspLanguageConfig::Multi(vec![LspServerConfig {
7013 command: "nil".to_string(),
7014 args: vec![],
7015 enabled: true,
7016 auto_start: false,
7017 process_limits: ProcessLimits::default(),
7018 initialization_options: None,
7019 env: Default::default(),
7020 language_id_overrides: Default::default(),
7021 name: None,
7022 only_features: None,
7023 except_features: None,
7024 root_markers: Default::default(),
7025 }]),
7026 );
7027
7028 lsp.insert(
7031 "kotlin".to_string(),
7032 LspLanguageConfig::Multi(vec![LspServerConfig {
7033 command: "kotlin-language-server".to_string(),
7034 args: vec![],
7035 enabled: true,
7036 auto_start: false,
7037 process_limits: ProcessLimits::default(),
7038 initialization_options: None,
7039 env: Default::default(),
7040 language_id_overrides: Default::default(),
7041 name: None,
7042 only_features: None,
7043 except_features: None,
7044 root_markers: Default::default(),
7045 }]),
7046 );
7047
7048 lsp.insert(
7050 "swift".to_string(),
7051 LspLanguageConfig::Multi(vec![LspServerConfig {
7052 command: "sourcekit-lsp".to_string(),
7053 args: vec![],
7054 enabled: true,
7055 auto_start: false,
7056 process_limits: ProcessLimits::default(),
7057 initialization_options: None,
7058 env: Default::default(),
7059 language_id_overrides: Default::default(),
7060 name: None,
7061 only_features: None,
7062 except_features: None,
7063 root_markers: Default::default(),
7064 }]),
7065 );
7066
7067 lsp.insert(
7070 "scala".to_string(),
7071 LspLanguageConfig::Multi(vec![LspServerConfig {
7072 command: "metals".to_string(),
7073 args: vec![],
7074 enabled: true,
7075 auto_start: false,
7076 process_limits: ProcessLimits::default(),
7077 initialization_options: None,
7078 env: Default::default(),
7079 language_id_overrides: Default::default(),
7080 name: None,
7081 only_features: None,
7082 except_features: None,
7083 root_markers: Default::default(),
7084 }]),
7085 );
7086
7087 lsp.insert(
7090 "elixir".to_string(),
7091 LspLanguageConfig::Multi(vec![LspServerConfig {
7092 command: "elixir-ls".to_string(),
7093 args: vec![],
7094 enabled: true,
7095 auto_start: false,
7096 process_limits: ProcessLimits::default(),
7097 initialization_options: None,
7098 env: Default::default(),
7099 language_id_overrides: Default::default(),
7100 name: None,
7101 only_features: None,
7102 except_features: None,
7103 root_markers: Default::default(),
7104 }]),
7105 );
7106
7107 lsp.insert(
7109 "erlang".to_string(),
7110 LspLanguageConfig::Multi(vec![LspServerConfig {
7111 command: "erlang_ls".to_string(),
7112 args: vec![],
7113 enabled: true,
7114 auto_start: false,
7115 process_limits: ProcessLimits::default(),
7116 initialization_options: None,
7117 env: Default::default(),
7118 language_id_overrides: Default::default(),
7119 name: None,
7120 only_features: None,
7121 except_features: None,
7122 root_markers: Default::default(),
7123 }]),
7124 );
7125
7126 lsp.insert(
7129 "haskell".to_string(),
7130 LspLanguageConfig::Multi(vec![LspServerConfig {
7131 command: "haskell-language-server-wrapper".to_string(),
7132 args: vec!["--lsp".to_string()],
7133 enabled: true,
7134 auto_start: false,
7135 process_limits: ProcessLimits::default(),
7136 initialization_options: None,
7137 env: Default::default(),
7138 language_id_overrides: Default::default(),
7139 name: None,
7140 only_features: None,
7141 except_features: None,
7142 root_markers: Default::default(),
7143 }]),
7144 );
7145
7146 lsp.insert(
7149 "ocaml".to_string(),
7150 LspLanguageConfig::Multi(vec![LspServerConfig {
7151 command: "ocamllsp".to_string(),
7152 args: vec![],
7153 enabled: true,
7154 auto_start: false,
7155 process_limits: ProcessLimits::default(),
7156 initialization_options: None,
7157 env: Default::default(),
7158 language_id_overrides: Default::default(),
7159 name: None,
7160 only_features: None,
7161 except_features: None,
7162 root_markers: Default::default(),
7163 }]),
7164 );
7165
7166 lsp.insert(
7169 "clojure".to_string(),
7170 LspLanguageConfig::Multi(vec![LspServerConfig {
7171 command: "clojure-lsp".to_string(),
7172 args: vec![],
7173 enabled: true,
7174 auto_start: false,
7175 process_limits: ProcessLimits::default(),
7176 initialization_options: None,
7177 env: Default::default(),
7178 language_id_overrides: Default::default(),
7179 name: None,
7180 only_features: None,
7181 except_features: None,
7182 root_markers: Default::default(),
7183 }]),
7184 );
7185
7186 lsp.insert(
7189 "r".to_string(),
7190 LspLanguageConfig::Multi(vec![LspServerConfig {
7191 command: "R".to_string(),
7192 args: vec![
7193 "--vanilla".to_string(),
7194 "-e".to_string(),
7195 "languageserver::run()".to_string(),
7196 ],
7197 enabled: true,
7198 auto_start: false,
7199 process_limits: ProcessLimits::default(),
7200 initialization_options: None,
7201 env: Default::default(),
7202 language_id_overrides: Default::default(),
7203 name: None,
7204 only_features: None,
7205 except_features: None,
7206 root_markers: Default::default(),
7207 }]),
7208 );
7209
7210 lsp.insert(
7213 "julia".to_string(),
7214 LspLanguageConfig::Multi(vec![LspServerConfig {
7215 command: "julia".to_string(),
7216 args: vec![
7217 "--startup-file=no".to_string(),
7218 "--history-file=no".to_string(),
7219 "-e".to_string(),
7220 "using LanguageServer; runserver()".to_string(),
7221 ],
7222 enabled: true,
7223 auto_start: false,
7224 process_limits: ProcessLimits::default(),
7225 initialization_options: None,
7226 env: Default::default(),
7227 language_id_overrides: Default::default(),
7228 name: None,
7229 only_features: None,
7230 except_features: None,
7231 root_markers: Default::default(),
7232 }]),
7233 );
7234
7235 lsp.insert(
7238 "perl".to_string(),
7239 LspLanguageConfig::Multi(vec![LspServerConfig {
7240 command: "perlnavigator".to_string(),
7241 args: vec!["--stdio".to_string()],
7242 enabled: true,
7243 auto_start: false,
7244 process_limits: ProcessLimits::default(),
7245 initialization_options: None,
7246 env: Default::default(),
7247 language_id_overrides: Default::default(),
7248 name: None,
7249 only_features: None,
7250 except_features: None,
7251 root_markers: Default::default(),
7252 }]),
7253 );
7254
7255 lsp.insert(
7258 "nim".to_string(),
7259 LspLanguageConfig::Multi(vec![LspServerConfig {
7260 command: "nimlangserver".to_string(),
7261 args: vec![],
7262 enabled: true,
7263 auto_start: false,
7264 process_limits: ProcessLimits::default(),
7265 initialization_options: None,
7266 env: Default::default(),
7267 language_id_overrides: Default::default(),
7268 name: None,
7269 only_features: None,
7270 except_features: None,
7271 root_markers: Default::default(),
7272 }]),
7273 );
7274
7275 lsp.insert(
7277 "gleam".to_string(),
7278 LspLanguageConfig::Multi(vec![LspServerConfig {
7279 command: "gleam".to_string(),
7280 args: vec!["lsp".to_string()],
7281 enabled: true,
7282 auto_start: false,
7283 process_limits: ProcessLimits::default(),
7284 initialization_options: None,
7285 env: Default::default(),
7286 language_id_overrides: Default::default(),
7287 name: None,
7288 only_features: None,
7289 except_features: None,
7290 root_markers: Default::default(),
7291 }]),
7292 );
7293
7294 lsp.insert(
7297 "racket".to_string(),
7298 LspLanguageConfig::Multi(vec![LspServerConfig {
7299 command: "racket-langserver".to_string(),
7300 args: vec![],
7301 enabled: true,
7302 auto_start: false,
7303 process_limits: ProcessLimits::default(),
7304 initialization_options: None,
7305 env: Default::default(),
7306 language_id_overrides: Default::default(),
7307 name: None,
7308 only_features: None,
7309 except_features: None,
7310 root_markers: vec!["info.rkt".to_string(), ".git".to_string()],
7311 }]),
7312 );
7313
7314 lsp.insert(
7317 "fsharp".to_string(),
7318 LspLanguageConfig::Multi(vec![LspServerConfig {
7319 command: "fsautocomplete".to_string(),
7320 args: vec!["--adaptive-lsp-server-enabled".to_string()],
7321 enabled: true,
7322 auto_start: false,
7323 process_limits: ProcessLimits::default(),
7324 initialization_options: None,
7325 env: Default::default(),
7326 language_id_overrides: Default::default(),
7327 name: None,
7328 only_features: None,
7329 except_features: None,
7330 root_markers: Default::default(),
7331 }]),
7332 );
7333
7334 let svls_config = LspServerConfig {
7338 command: "svls".to_string(),
7339 args: vec![],
7340 enabled: true,
7341 auto_start: false,
7342 process_limits: ProcessLimits::default(),
7343 initialization_options: None,
7344 env: Default::default(),
7345 language_id_overrides: Default::default(),
7346 name: None,
7347 only_features: None,
7348 except_features: None,
7349 root_markers: vec![".svls.toml".to_string(), ".git".to_string()],
7350 };
7351 lsp.insert(
7352 "verilog".to_string(),
7353 LspLanguageConfig::Multi(vec![svls_config.clone()]),
7354 );
7355 lsp.insert(
7356 "systemverilog".to_string(),
7357 LspLanguageConfig::Multi(vec![svls_config]),
7358 );
7359
7360 let asm_lsp_config = LspServerConfig {
7371 command: "asm-lsp".to_string(),
7372 args: vec![],
7373 enabled: true,
7374 auto_start: false,
7375 process_limits: ProcessLimits::default(),
7376 initialization_options: None,
7377 env: Default::default(),
7378 language_id_overrides: Default::default(),
7379 name: None,
7380 only_features: None,
7381 except_features: Some(vec![LspFeature::Diagnostics]),
7382 root_markers: vec![".asm-lsp.toml".to_string(), ".git".to_string()],
7383 };
7384 lsp.insert(
7385 "asm".to_string(),
7386 LspLanguageConfig::Multi(vec![asm_lsp_config.clone()]),
7387 );
7388 lsp.insert(
7389 "gas".to_string(),
7390 LspLanguageConfig::Multi(vec![asm_lsp_config]),
7391 );
7392 }
7393 pub fn validate(&self) -> Result<(), ConfigError> {
7394 if self.editor.tab_size == 0 {
7396 return Err(ConfigError::ValidationError(
7397 "tab_size must be greater than 0".to_string(),
7398 ));
7399 }
7400
7401 if self.editor.scroll_offset > 100 {
7403 return Err(ConfigError::ValidationError(
7404 "scroll_offset must be <= 100".to_string(),
7405 ));
7406 }
7407
7408 for binding in &self.keybindings {
7410 if binding.key.is_empty() {
7411 return Err(ConfigError::ValidationError(
7412 "keybinding key cannot be empty".to_string(),
7413 ));
7414 }
7415 if binding.action.is_empty() {
7416 return Err(ConfigError::ValidationError(
7417 "keybinding action cannot be empty".to_string(),
7418 ));
7419 }
7420 }
7421
7422 Ok(())
7423 }
7424}
7425
7426#[derive(Debug)]
7428pub enum ConfigError {
7429 IoError(String),
7430 ParseError(String),
7431 SerializeError(String),
7432 ValidationError(String),
7433}
7434
7435impl std::fmt::Display for ConfigError {
7436 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7437 match self {
7438 Self::IoError(msg) => write!(f, "IO error: {msg}"),
7439 Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
7440 Self::SerializeError(msg) => write!(f, "Serialize error: {msg}"),
7441 Self::ValidationError(msg) => write!(f, "Validation error: {msg}"),
7442 }
7443 }
7444}
7445
7446impl std::error::Error for ConfigError {}
7447
7448pub(crate) fn parse_config_jsonc(contents: &str) -> Result<serde_json::Value, ConfigError> {
7458 let value: serde_json::Value =
7459 jsonc_parser::parse_to_serde_value(contents, &Default::default())
7460 .map_err(|e| ConfigError::ParseError(e.to_string()))?;
7461 Ok(match value {
7464 serde_json::Value::Null => serde_json::Value::Object(Default::default()),
7465 other => other,
7466 })
7467}
7468
7469#[cfg(test)]
7470mod tests {
7471 use super::*;
7472
7473 #[test]
7474 fn test_file_explorer_width_default_is_percent_30() {
7475 let cfg = FileExplorerConfig::default();
7476 assert_eq!(cfg.width, ExplorerWidth::Percent(30));
7477 }
7478
7479 #[test]
7480 fn parse_config_jsonc_tolerates_comments_and_trailing_commas() {
7481 let value = parse_config_jsonc(
7482 r#"{
7483 // line comment
7484 "editor": {
7485 "tab_size": 7, /* block comment */
7486 "line_numbers": false,
7487 }
7488 }"#,
7489 )
7490 .expect("JSONC with comments should parse");
7491 assert_eq!(
7492 value.pointer("/editor/tab_size"),
7493 Some(&serde_json::json!(7))
7494 );
7495 assert_eq!(
7496 value.pointer("/editor/line_numbers"),
7497 Some(&serde_json::json!(false))
7498 );
7499 }
7500
7501 #[test]
7502 fn parse_config_jsonc_empty_and_comment_only_is_empty_object() {
7503 assert_eq!(
7504 parse_config_jsonc("").unwrap(),
7505 serde_json::Value::Object(Default::default())
7506 );
7507 assert_eq!(
7508 parse_config_jsonc("// just a comment\n").unwrap(),
7509 serde_json::Value::Object(Default::default())
7510 );
7511 }
7512
7513 #[test]
7514 fn parse_config_jsonc_rejects_truly_invalid_input() {
7515 assert!(parse_config_jsonc(r#"{ "editor": { "#).is_err());
7518 }
7519
7520 #[test]
7521 fn test_tree_indicators_defaults_preserve_current_glyphs() {
7522 let cfg = FileExplorerConfig::default();
7526 assert_eq!(cfg.tree_indicator_collapsed, ">");
7527 assert_eq!(cfg.tree_indicator_expanded, "▼");
7528 }
7529
7530 #[test]
7531 fn test_tree_indicators_can_be_overridden_from_json() {
7532 let cfg: FileExplorerConfig = serde_json::from_str(
7533 r#"{"tree_indicator_collapsed": "▸", "tree_indicator_expanded": "▾"}"#,
7534 )
7535 .unwrap();
7536 assert_eq!(cfg.tree_indicator_collapsed, "▸");
7537 assert_eq!(cfg.tree_indicator_expanded, "▾");
7538 }
7539
7540 #[test]
7541 fn test_tree_indicators_partial_override_keeps_default_for_unset() {
7542 let cfg: FileExplorerConfig =
7543 serde_json::from_str(r#"{"tree_indicator_collapsed": "+"}"#).unwrap();
7544 assert_eq!(cfg.tree_indicator_collapsed, "+");
7545 assert_eq!(cfg.tree_indicator_expanded, "▼");
7547 }
7548
7549 #[test]
7552 fn test_width_accepts_legacy_float_fraction() {
7553 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": 0.3}"#).unwrap();
7554 assert_eq!(cfg.width, ExplorerWidth::Percent(30));
7555 }
7556
7557 #[test]
7558 fn test_width_accepts_bare_integer_as_percent() {
7559 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": 42}"#).unwrap();
7561 assert_eq!(cfg.width, ExplorerWidth::Percent(42));
7562 }
7563
7564 #[test]
7565 fn test_width_accepts_percent_string() {
7566 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "75%"}"#).unwrap();
7567 assert_eq!(cfg.width, ExplorerWidth::Percent(75));
7568 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "42 %"}"#).unwrap();
7570 assert_eq!(cfg.width, ExplorerWidth::Percent(42));
7571 }
7572
7573 #[test]
7574 fn test_width_accepts_columns_string() {
7575 let cfg: FileExplorerConfig = serde_json::from_str(r#"{"width": "24"}"#).unwrap();
7576 assert_eq!(cfg.width, ExplorerWidth::Columns(24));
7577 }
7578
7579 #[test]
7580 fn test_width_rejects_percent_over_100() {
7581 let err = serde_json::from_str::<FileExplorerConfig>(r#"{"width": "150%"}"#)
7582 .expect_err("percent > 100 should be rejected");
7583 assert!(err.to_string().contains("100"), "{err}");
7584 }
7585
7586 #[test]
7587 fn test_width_rejects_integer_over_100() {
7588 let err = serde_json::from_str::<FileExplorerConfig>(r#"{"width": 150}"#)
7591 .expect_err("bare integer > 100 should be rejected as percent");
7592 assert!(err.to_string().contains("percent") || err.to_string().contains("100"));
7593 }
7594
7595 #[test]
7596 fn test_width_rejects_garbage_string() {
7597 serde_json::from_str::<FileExplorerConfig>(r#"{"width": "big"}"#)
7598 .expect_err("non-numeric string should be rejected");
7599 }
7600
7601 #[test]
7604 fn test_width_serializes_percent_as_string_with_suffix() {
7605 let cfg = FileExplorerConfig {
7606 width: ExplorerWidth::Percent(30),
7607 ..Default::default()
7608 };
7609 let json = serde_json::to_value(&cfg).unwrap();
7610 assert_eq!(json["width"], serde_json::json!("30%"));
7611 }
7612
7613 #[test]
7614 fn test_width_serializes_columns_as_string_without_suffix() {
7615 let cfg = FileExplorerConfig {
7616 width: ExplorerWidth::Columns(24),
7617 ..Default::default()
7618 };
7619 let json = serde_json::to_value(&cfg).unwrap();
7620 assert_eq!(json["width"], serde_json::json!("24"));
7621 }
7622
7623 #[test]
7624 fn test_width_round_trip_both_variants() {
7625 for value in [ExplorerWidth::Percent(17), ExplorerWidth::Columns(42)] {
7626 let cfg = FileExplorerConfig {
7627 width: value,
7628 ..Default::default()
7629 };
7630 let json = serde_json::to_string(&cfg).unwrap();
7631 let restored: FileExplorerConfig = serde_json::from_str(&json).unwrap();
7632 assert_eq!(restored.width, value, "round trip failed for {:?}", value);
7633 }
7634 }
7635
7636 #[test]
7639 fn test_to_cols_percent() {
7640 assert_eq!(ExplorerWidth::Percent(30).to_cols(100), 30);
7641 assert_eq!(ExplorerWidth::Percent(25).to_cols(120), 30);
7642 assert_eq!(ExplorerWidth::Percent(30).to_cols(0), 0);
7644 assert_eq!(ExplorerWidth::Percent(100).to_cols(200), 200);
7645 }
7646
7647 #[test]
7648 fn test_to_cols_columns_clamps_to_terminal() {
7649 assert_eq!(ExplorerWidth::Columns(24).to_cols(100), 24);
7650 assert_eq!(ExplorerWidth::Columns(999).to_cols(80), 80);
7651 }
7652
7653 #[test]
7656 fn test_to_cols_enforces_min_width() {
7657 assert_eq!(
7659 ExplorerWidth::Columns(0).to_cols(100),
7660 ExplorerWidth::MIN_COLS
7661 );
7662 assert_eq!(
7663 ExplorerWidth::Columns(1).to_cols(100),
7664 ExplorerWidth::MIN_COLS
7665 );
7666 assert_eq!(
7667 ExplorerWidth::Columns(4).to_cols(100),
7668 ExplorerWidth::MIN_COLS
7669 );
7670 assert_eq!(
7671 ExplorerWidth::Percent(0).to_cols(100),
7672 ExplorerWidth::MIN_COLS
7673 );
7674 assert_eq!(
7676 ExplorerWidth::Percent(3).to_cols(100),
7677 ExplorerWidth::MIN_COLS
7678 );
7679 assert_eq!(
7681 ExplorerWidth::Columns(ExplorerWidth::MIN_COLS + 1).to_cols(100),
7682 ExplorerWidth::MIN_COLS + 1
7683 );
7684 }
7685
7686 #[test]
7689 fn test_to_cols_min_floor_yields_to_narrow_terminal() {
7690 assert_eq!(ExplorerWidth::Columns(10).to_cols(3), 3);
7691 assert_eq!(ExplorerWidth::Percent(100).to_cols(2), 2);
7692 assert_eq!(ExplorerWidth::Columns(1).to_cols(0), 0);
7693 }
7694
7695 #[test]
7698 fn test_load_from_file_accepts_legacy_float_fraction_width() {
7699 let dir = tempfile::tempdir().unwrap();
7700 let path = dir.path().join("config.json");
7701 std::fs::write(&path, r#"{"file_explorer":{"width":0.25}}"#).unwrap();
7702 let cfg = Config::load_from_file(&path).expect("legacy float fraction must still load");
7703 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Percent(25));
7704 }
7705
7706 #[test]
7707 fn test_load_from_file_accepts_columns_string_width() {
7708 let dir = tempfile::tempdir().unwrap();
7709 let path = dir.path().join("config.json");
7710 std::fs::write(&path, r#"{"file_explorer":{"width":"42"}}"#).unwrap();
7711 let cfg = Config::load_from_file(&path).unwrap();
7712 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Columns(42));
7713 }
7714
7715 #[test]
7716 fn test_load_from_file_accepts_percent_string_width() {
7717 let dir = tempfile::tempdir().unwrap();
7718 let path = dir.path().join("config.json");
7719 std::fs::write(&path, r#"{"file_explorer":{"width":"55%"}}"#).unwrap();
7720 let cfg = Config::load_from_file(&path).unwrap();
7721 assert_eq!(cfg.file_explorer.width, ExplorerWidth::Percent(55));
7722 }
7723
7724 #[test]
7725 fn test_default_config() {
7726 let config = Config::default();
7727 assert_eq!(config.editor.tab_size, 4);
7728 assert!(config.editor.line_numbers);
7729 assert!(config.editor.syntax_highlighting);
7730 assert!(config.languages.contains_key("gdscript"));
7731 assert_eq!(config.languages["gdscript"].extensions, vec!["gd"]);
7732 assert!(config.keybindings.is_empty());
7735 let resolved = config.resolve_keymap(&config.active_keybinding_map);
7737 assert!(!resolved.is_empty());
7738 }
7739
7740 #[test]
7741 fn test_all_builtin_keymaps_loadable() {
7742 for name in KeybindingMapName::BUILTIN_OPTIONS {
7743 let keymap = Config::load_builtin_keymap(name);
7744 assert!(keymap.is_some(), "Failed to load builtin keymap '{}'", name);
7745 }
7746 }
7747
7748 #[test]
7749 fn test_config_validation() {
7750 let mut config = Config::default();
7751 assert!(config.validate().is_ok());
7752
7753 config.editor.tab_size = 0;
7754 assert!(config.validate().is_err());
7755 }
7756
7757 #[test]
7758 fn test_macos_keymap_inherits_enter_bindings() {
7759 let config = Config::default();
7760 let bindings = config.resolve_keymap("macos");
7761
7762 let enter_bindings: Vec<_> = bindings.iter().filter(|b| b.key == "Enter").collect();
7763 assert!(
7764 !enter_bindings.is_empty(),
7765 "macos keymap should inherit Enter bindings from default, got {} Enter bindings",
7766 enter_bindings.len()
7767 );
7768 let has_insert_newline = enter_bindings.iter().any(|b| b.action == "insert_newline");
7770 assert!(
7771 has_insert_newline,
7772 "macos keymap should have insert_newline action for Enter key"
7773 );
7774 }
7775
7776 #[test]
7777 fn test_config_serialize_deserialize() {
7778 let config = Config::default();
7780
7781 let json = serde_json::to_string_pretty(&config).unwrap();
7783
7784 let loaded: Config = serde_json::from_str(&json).unwrap();
7786
7787 assert_eq!(config.editor.tab_size, loaded.editor.tab_size);
7788 assert_eq!(config.theme, loaded.theme);
7789 }
7790
7791 #[test]
7792 fn test_indentation_guide_mode_rejects_booleans() {
7793 assert!(
7794 serde_json::from_str::<Config>(r#"{"editor":{"indentation_guide":false}}"#).is_err()
7795 );
7796 assert!(
7797 serde_json::from_str::<Config>(r#"{"editor":{"indentation_guide":true}}"#).is_err()
7798 );
7799 }
7800
7801 #[test]
7802 fn test_indentation_guide_mode_accepts_string_modes() {
7803 for (value, expected) in [
7804 ("none", IndentationGuideMode::None),
7805 ("all", IndentationGuideMode::All),
7806 ("active", IndentationGuideMode::Active),
7807 ] {
7808 let json = format!(r#"{{"editor":{{"indentation_guide":"{}"}}}}"#, value);
7809 let cfg: Config = serde_json::from_str(&json).unwrap();
7810 assert_eq!(cfg.editor.indentation_guide, expected);
7811 }
7812 }
7813
7814 #[test]
7815 fn test_indentation_guide_glyph_defaults_and_deserializes() {
7816 assert_eq!(Config::default().editor.indentation_guide_glyph, "▏");
7817
7818 let cfg: Config = serde_json::from_str(
7819 r#"{"editor":{"indentation_guide":"all","indentation_guide_glyph":"┊"}}"#,
7820 )
7821 .unwrap();
7822 assert_eq!(cfg.editor.indentation_guide, IndentationGuideMode::All);
7823 assert_eq!(cfg.editor.indentation_guide_glyph, "┊");
7824 }
7825
7826 #[test]
7827 fn test_indentation_guide_glyph_normalizes_whitespace_and_blank_values() {
7828 let cfg: Config =
7829 serde_json::from_str(r#"{"editor":{"indentation_guide_glyph":" ┊ "}}"#).unwrap();
7830 assert_eq!(cfg.editor.indentation_guide_glyph, "┊");
7831
7832 let cfg: Config =
7833 serde_json::from_str(r#"{"editor":{"indentation_guide_glyph":" "}}"#).unwrap();
7834 assert_eq!(cfg.editor.indentation_guide_glyph, "▏");
7835 }
7836
7837 #[test]
7838 fn test_config_with_custom_keybinding() {
7839 let json = r#"{
7840 "editor": {
7841 "tab_size": 2
7842 },
7843 "keybindings": [
7844 {
7845 "key": "x",
7846 "modifiers": ["ctrl", "shift"],
7847 "action": "custom_action",
7848 "args": {},
7849 "when": null
7850 }
7851 ]
7852 }"#;
7853
7854 let config: Config = serde_json::from_str(json).unwrap();
7855 assert_eq!(config.editor.tab_size, 2);
7856 assert_eq!(config.keybindings.len(), 1);
7857 assert_eq!(config.keybindings[0].key, "x");
7858 assert_eq!(config.keybindings[0].modifiers.len(), 2);
7859 }
7860
7861 #[test]
7862 fn test_sparse_config_merges_with_defaults() {
7863 let temp_dir = tempfile::tempdir().unwrap();
7865 let config_path = temp_dir.path().join("config.json");
7866
7867 let sparse_config = r#"{
7869 "lsp": {
7870 "rust": {
7871 "command": "custom-rust-analyzer",
7872 "args": ["--custom-arg"]
7873 }
7874 }
7875 }"#;
7876 std::fs::write(&config_path, sparse_config).unwrap();
7877
7878 let loaded = Config::load_from_file(&config_path).unwrap();
7880
7881 assert!(loaded.lsp.contains_key("rust"));
7883 assert_eq!(
7884 loaded.lsp["rust"].as_slice()[0].command,
7885 "custom-rust-analyzer".to_string()
7886 );
7887
7888 assert!(
7890 loaded.lsp.contains_key("python"),
7891 "python LSP should be merged from defaults"
7892 );
7893 assert!(
7894 loaded.lsp.contains_key("typescript"),
7895 "typescript LSP should be merged from defaults"
7896 );
7897 assert!(
7898 loaded.lsp.contains_key("javascript"),
7899 "javascript LSP should be merged from defaults"
7900 );
7901
7902 assert!(loaded.languages.contains_key("rust"));
7904 assert!(loaded.languages.contains_key("python"));
7905 assert!(loaded.languages.contains_key("typescript"));
7906 }
7907
7908 #[test]
7909 fn test_empty_config_gets_all_defaults() {
7910 let temp_dir = tempfile::tempdir().unwrap();
7911 let config_path = temp_dir.path().join("config.json");
7912
7913 std::fs::write(&config_path, "{}").unwrap();
7915
7916 let loaded = Config::load_from_file(&config_path).unwrap();
7917 let defaults = Config::default();
7918
7919 assert_eq!(loaded.lsp.len(), defaults.lsp.len());
7921
7922 assert_eq!(loaded.languages.len(), defaults.languages.len());
7924 }
7925
7926 #[test]
7927 fn test_dynamic_submenu_expansion() {
7928 let temp_dir = tempfile::tempdir().unwrap();
7930 let themes_dir = temp_dir.path().to_path_buf();
7931
7932 let dynamic = MenuItem::DynamicSubmenu {
7933 label: "Test".to_string(),
7934 source: "copy_with_theme".to_string(),
7935 };
7936
7937 let expanded = dynamic.expand_dynamic(&themes_dir);
7938
7939 match expanded {
7941 MenuItem::Submenu { label, items } => {
7942 assert_eq!(label, "Test");
7943 let loader = crate::view::theme::ThemeLoader::embedded_only();
7945 let registry = loader.load_all(&[]);
7946 assert_eq!(items.len(), registry.len());
7947
7948 for (item, theme_info) in items.iter().zip(registry.list().iter()) {
7950 match item {
7951 MenuItem::Action {
7952 label,
7953 action,
7954 args,
7955 ..
7956 } => {
7957 assert_eq!(label, &theme_info.name);
7958 assert_eq!(action, "copy_with_theme");
7959 assert_eq!(
7960 args.get("theme").and_then(|v| v.as_str()),
7961 Some(theme_info.name.as_str())
7962 );
7963 }
7964 _ => panic!("Expected Action item"),
7965 }
7966 }
7967 }
7968 _ => panic!("Expected Submenu after expansion"),
7969 }
7970 }
7971
7972 #[test]
7973 fn test_non_dynamic_item_unchanged() {
7974 let temp_dir = tempfile::tempdir().unwrap();
7976 let themes_dir = temp_dir.path();
7977
7978 let action = MenuItem::Action {
7979 label: "Test".to_string(),
7980 action: "test".to_string(),
7981 args: HashMap::new(),
7982 when: None,
7983 checkbox: None,
7984 };
7985
7986 let expanded = action.expand_dynamic(themes_dir);
7987 match expanded {
7988 MenuItem::Action { label, action, .. } => {
7989 assert_eq!(label, "Test");
7990 assert_eq!(action, "test");
7991 }
7992 _ => panic!("Action should remain Action after expand_dynamic"),
7993 }
7994 }
7995
7996 #[test]
7997 fn test_buffer_config_uses_global_defaults() {
7998 let config = Config::default();
7999 let buffer_config = BufferConfig::resolve(&config, None);
8000
8001 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
8002 assert_eq!(buffer_config.auto_indent, config.editor.auto_indent);
8003 assert!(!buffer_config.use_tabs); assert!(buffer_config.whitespace.any_tabs()); assert!(buffer_config.formatter.is_none());
8006 assert!(!buffer_config.format_on_save);
8007 }
8008
8009 #[test]
8010 fn test_buffer_config_applies_language_overrides() {
8011 let mut config = Config::default();
8012
8013 config.languages.insert(
8015 "go".to_string(),
8016 LanguageConfig {
8017 extensions: vec!["go".to_string()],
8018 filenames: vec![],
8019 grammar: "go".to_string(),
8020 comment_prefix: Some("//".to_string()),
8021 auto_indent: true,
8022 auto_close: None,
8023 auto_surround: None,
8024 textmate_grammar: None,
8025 show_whitespace_tabs: false, line_wrap: None,
8027 wrap_column: None,
8028 page_view: None,
8029 page_width: None,
8030 use_tabs: Some(true), tab_size: Some(8), formatter: Some(FormatterConfig {
8033 command: "gofmt".to_string(),
8034 args: vec![],
8035 stdin: true,
8036 timeout_ms: 10000,
8037 }),
8038 format_on_save: true,
8039 on_save: vec![],
8040 word_characters: None,
8041 indent: None,
8042 },
8043 );
8044
8045 let buffer_config = BufferConfig::resolve(&config, Some("go"));
8046
8047 assert_eq!(buffer_config.tab_size, 8);
8048 assert!(buffer_config.use_tabs);
8049 assert!(!buffer_config.whitespace.any_tabs()); assert!(buffer_config.format_on_save);
8051 assert!(buffer_config.formatter.is_some());
8052 assert_eq!(buffer_config.formatter.as_ref().unwrap().command, "gofmt");
8053 }
8054
8055 #[test]
8056 fn test_buffer_config_unknown_language_uses_global() {
8057 let config = Config::default();
8058 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
8059
8060 assert_eq!(buffer_config.tab_size, config.editor.tab_size);
8062 assert!(!buffer_config.use_tabs);
8063 }
8064
8065 #[test]
8066 fn test_buffer_config_per_language_line_wrap() {
8067 let mut config = Config::default();
8068 config.editor.line_wrap = false;
8069
8070 config.languages.insert(
8072 "markdown".to_string(),
8073 LanguageConfig {
8074 extensions: vec!["md".to_string()],
8075 line_wrap: Some(true),
8076 ..Default::default()
8077 },
8078 );
8079
8080 let md_config = BufferConfig::resolve(&config, Some("markdown"));
8082 assert!(md_config.line_wrap, "Markdown should have line_wrap=true");
8083
8084 let other_config = BufferConfig::resolve(&config, Some("rust"));
8086 assert!(
8087 !other_config.line_wrap,
8088 "Non-configured languages should use global line_wrap=false"
8089 );
8090
8091 let no_lang_config = BufferConfig::resolve(&config, None);
8093 assert!(
8094 !no_lang_config.line_wrap,
8095 "No language should use global line_wrap=false"
8096 );
8097 }
8098
8099 #[test]
8100 fn test_buffer_config_per_language_wrap_column() {
8101 let mut config = Config::default();
8102 config.editor.wrap_column = Some(120);
8103
8104 config.languages.insert(
8106 "markdown".to_string(),
8107 LanguageConfig {
8108 extensions: vec!["md".to_string()],
8109 wrap_column: Some(80),
8110 ..Default::default()
8111 },
8112 );
8113
8114 let md_config = BufferConfig::resolve(&config, Some("markdown"));
8116 assert_eq!(md_config.wrap_column, Some(80));
8117
8118 let other_config = BufferConfig::resolve(&config, Some("rust"));
8120 assert_eq!(other_config.wrap_column, Some(120));
8121
8122 let no_lang_config = BufferConfig::resolve(&config, None);
8124 assert_eq!(no_lang_config.wrap_column, Some(120));
8125 }
8126
8127 #[test]
8128 fn test_normalize_zero_sentinels_global() {
8129 let mut config = Config::default();
8130 config.editor.wrap_column = Some(0);
8131 config.editor.page_width = Some(0);
8132 config.editor.tab_size = 0;
8133
8134 config.normalize_zero_sentinels();
8135
8136 assert_eq!(config.editor.wrap_column, None);
8138 assert_eq!(config.editor.page_width, None);
8139 assert_eq!(config.editor.tab_size, default_tab_size());
8140 }
8141
8142 #[test]
8143 fn test_normalize_zero_sentinels_language_inherits_global() {
8144 let mut config = Config::default();
8145 config.editor.wrap_column = Some(120);
8146 config.editor.page_width = Some(80);
8147 config.editor.tab_size = 8;
8148
8149 config.languages.insert(
8152 "markdown".to_string(),
8153 LanguageConfig {
8154 extensions: vec!["md".to_string()],
8155 wrap_column: Some(0),
8156 page_width: Some(0),
8157 tab_size: Some(0),
8158 ..Default::default()
8159 },
8160 );
8161
8162 config.normalize_zero_sentinels();
8163
8164 let lang = &config.languages["markdown"];
8165 assert_eq!(lang.wrap_column, None);
8166 assert_eq!(lang.page_width, None);
8167 assert_eq!(lang.tab_size, None);
8168
8169 let md = BufferConfig::resolve(&config, Some("markdown"));
8171 assert_eq!(md.wrap_column, Some(120));
8172 assert_eq!(md.tab_size, 8);
8173 }
8174
8175 #[test]
8176 fn test_buffer_config_indent_string() {
8177 let config = Config::default();
8178
8179 let spaces_config = BufferConfig::resolve(&config, None);
8181 assert_eq!(spaces_config.indent_string(), " "); let mut config_with_tabs = Config::default();
8185 config_with_tabs.languages.insert(
8186 "makefile".to_string(),
8187 LanguageConfig {
8188 use_tabs: Some(true),
8189 tab_size: Some(8),
8190 ..Default::default()
8191 },
8192 );
8193 let tabs_config = BufferConfig::resolve(&config_with_tabs, Some("makefile"));
8194 assert_eq!(tabs_config.indent_string(), "\t");
8195 }
8196
8197 #[test]
8198 fn test_buffer_config_global_use_tabs_inherited() {
8199 let mut config = Config::default();
8202 config.editor.use_tabs = true;
8203
8204 let buffer_config = BufferConfig::resolve(&config, Some("unknown_lang"));
8206 assert!(buffer_config.use_tabs);
8207
8208 let buffer_config = BufferConfig::resolve(&config, None);
8210 assert!(buffer_config.use_tabs);
8211
8212 config.languages.insert(
8214 "python".to_string(),
8215 LanguageConfig {
8216 use_tabs: Some(false),
8217 ..Default::default()
8218 },
8219 );
8220 let buffer_config = BufferConfig::resolve(&config, Some("python"));
8221 assert!(!buffer_config.use_tabs);
8222
8223 config.languages.insert(
8225 "rust".to_string(),
8226 LanguageConfig {
8227 use_tabs: None,
8228 ..Default::default()
8229 },
8230 );
8231 let buffer_config = BufferConfig::resolve(&config, Some("rust"));
8232 assert!(buffer_config.use_tabs);
8233 }
8234
8235 #[test]
8241 #[cfg(feature = "runtime")]
8242 fn test_lsp_languages_have_language_config() {
8243 let config = Config::default();
8244 let exceptions = ["tailwindcss"];
8245 for lsp_key in config.lsp.keys() {
8246 if exceptions.contains(&lsp_key.as_str()) {
8247 continue;
8248 }
8249 assert!(
8250 config.languages.contains_key(lsp_key),
8251 "LSP config key '{}' has no matching entry in default_languages(). \
8252 Add a LanguageConfig with the correct file extensions so detect_language() \
8253 can map files to this language.",
8254 lsp_key
8255 );
8256 }
8257 }
8258
8259 #[test]
8260 #[cfg(feature = "runtime")]
8261 fn test_gdscript_lsp_disabled_by_default() {
8262 let config = Config::default();
8263 let server = &config.lsp["gdscript"].as_slice()[0];
8264 assert_eq!(server.command, "nc");
8265 assert_eq!(server.args, vec!["127.0.0.1", "6005"]);
8266 assert!(!server.enabled);
8267 assert_eq!(server.name.as_deref(), Some("Godot GDScript"));
8268 }
8269
8270 #[test]
8271 #[cfg(feature = "runtime")]
8272 fn test_default_config_has_quicklsp_in_universal_lsp() {
8273 let config = Config::default();
8274 assert!(
8275 config.universal_lsp.contains_key("quicklsp"),
8276 "Default config should contain quicklsp in universal_lsp"
8277 );
8278 let quicklsp = &config.universal_lsp["quicklsp"];
8279 let server = &quicklsp.as_slice()[0];
8280 assert_eq!(server.command, "quicklsp");
8281 assert!(!server.enabled, "quicklsp should be disabled by default");
8282 assert_eq!(server.name.as_deref(), Some("QuickLSP"));
8283 assert!(
8287 server.only_features.is_none(),
8288 "quicklsp must not default to a feature whitelist"
8289 );
8290 assert!(server.except_features.is_none());
8291 }
8292
8293 #[test]
8294 fn test_empty_config_preserves_universal_lsp_defaults() {
8295 let temp_dir = tempfile::tempdir().unwrap();
8296 let config_path = temp_dir.path().join("config.json");
8297
8298 std::fs::write(&config_path, "{}").unwrap();
8300
8301 let loaded = Config::load_from_file(&config_path).unwrap();
8302 let defaults = Config::default();
8303
8304 assert_eq!(
8306 loaded.universal_lsp.len(),
8307 defaults.universal_lsp.len(),
8308 "Empty config should preserve all default universal_lsp entries"
8309 );
8310 }
8311
8312 #[test]
8313 fn test_universal_lsp_config_merges_with_defaults() {
8314 let temp_dir = tempfile::tempdir().unwrap();
8315 let config_path = temp_dir.path().join("config.json");
8316
8317 let config_json = r#"{
8319 "universal_lsp": {
8320 "quicklsp": {
8321 "enabled": true
8322 }
8323 }
8324 }"#;
8325 std::fs::write(&config_path, config_json).unwrap();
8326
8327 let loaded = Config::load_from_file(&config_path).unwrap();
8328
8329 assert!(loaded.universal_lsp.contains_key("quicklsp"));
8331 let server = &loaded.universal_lsp["quicklsp"].as_slice()[0];
8332 assert!(server.enabled, "User override should enable quicklsp");
8333 assert_eq!(
8335 server.command, "quicklsp",
8336 "Default command should be merged when not specified by user"
8337 );
8338 }
8339
8340 #[test]
8341 fn test_universal_lsp_custom_server_added() {
8342 let temp_dir = tempfile::tempdir().unwrap();
8343 let config_path = temp_dir.path().join("config.json");
8344
8345 let config_json = r#"{
8347 "universal_lsp": {
8348 "my-custom-server": {
8349 "command": "my-server",
8350 "enabled": true,
8351 "auto_start": true
8352 }
8353 }
8354 }"#;
8355 std::fs::write(&config_path, config_json).unwrap();
8356
8357 let loaded = Config::load_from_file(&config_path).unwrap();
8358
8359 assert!(
8361 loaded.universal_lsp.contains_key("my-custom-server"),
8362 "Custom universal server should be loaded"
8363 );
8364 let server = &loaded.universal_lsp["my-custom-server"].as_slice()[0];
8365 assert_eq!(server.command, "my-server");
8366 assert!(server.enabled);
8367 assert!(server.auto_start);
8368
8369 assert!(
8371 loaded.universal_lsp.contains_key("quicklsp"),
8372 "Default quicklsp should be merged from defaults"
8373 );
8374 }
8375
8376 #[test]
8381 fn test_asm_lsp_defaults_exclude_pull_diagnostics() {
8382 let config = Config::default();
8383 for language in ["asm", "gas"] {
8384 let LspLanguageConfig::Multi(servers) = &config.lsp[language] else {
8385 panic!("expected Multi LSP config for {language}");
8386 };
8387 let server = &servers[0];
8388 assert_eq!(server.command, "asm-lsp");
8389 assert_eq!(
8390 server.except_features,
8391 Some(vec![LspFeature::Diagnostics]),
8392 "{language}: asm-lsp must exclude pull diagnostics"
8393 );
8394 }
8395 }
8396}