@fresh-editor/fresh-editor 0.2.16 → 0.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/package.json +1 -1
  3. package/plugins/astro-lsp.ts +118 -0
  4. package/plugins/bash-lsp.ts +161 -0
  5. package/plugins/clojure-lsp.ts +125 -0
  6. package/plugins/cmake-lsp.ts +138 -0
  7. package/plugins/config-schema.json +33 -4
  8. package/plugins/dart-lsp.ts +144 -0
  9. package/plugins/elixir-lsp.ts +120 -0
  10. package/plugins/erlang-lsp.ts +121 -0
  11. package/plugins/fsharp-lsp.ts +125 -0
  12. package/plugins/gleam-lsp.ts +124 -0
  13. package/plugins/graphql-lsp.ts +139 -0
  14. package/plugins/haskell-lsp.ts +125 -0
  15. package/plugins/julia-lsp.ts +111 -0
  16. package/plugins/kotlin-lsp.ts +162 -0
  17. package/plugins/lib/fresh.d.ts +25 -0
  18. package/plugins/lua-lsp.ts +161 -0
  19. package/plugins/nim-lsp.ts +118 -0
  20. package/plugins/nix-lsp.ts +125 -0
  21. package/plugins/nushell-lsp.ts +144 -0
  22. package/plugins/ocaml-lsp.ts +119 -0
  23. package/plugins/perl-lsp.ts +118 -0
  24. package/plugins/php-lsp.ts +165 -0
  25. package/plugins/pkg.ts +33 -47
  26. package/plugins/protobuf-lsp.ts +144 -0
  27. package/plugins/r-lsp.ts +118 -0
  28. package/plugins/ruby-lsp.ts +165 -0
  29. package/plugins/scala-lsp.ts +119 -0
  30. package/plugins/solidity-lsp.ts +130 -0
  31. package/plugins/sql-lsp.ts +129 -0
  32. package/plugins/svelte-lsp.ts +119 -0
  33. package/plugins/swift-lsp.ts +120 -0
  34. package/plugins/tailwindcss-lsp.ts +119 -0
  35. package/plugins/terraform-lsp.ts +144 -0
  36. package/plugins/theme_editor.i18n.json +70 -14
  37. package/plugins/theme_editor.ts +71 -39
  38. package/plugins/toml-lsp.ts +162 -0
  39. package/plugins/typst-lsp.ts +165 -0
  40. package/plugins/vue-lsp.ts +118 -0
  41. package/plugins/yaml-lsp.ts +163 -0
@@ -74,28 +74,28 @@ type PickerFocusTarget =
74
74
  // =============================================================================
75
75
 
76
76
  const NAMED_COLORS_PER_ROW = 6;
77
- const NAMED_COLOR_GRID: Array<Array<{ display: string; value: string; rgb: RGB | null }>> = [
77
+ const NAMED_COLOR_GRID: Array<Array<{ display: string; value: string; rgb: OverlayColorSpec | null }>> = [
78
78
  [
79
- { display: "Black", value: "Black", rgb: [0, 0, 0] },
80
- { display: "Red", value: "Red", rgb: [255, 0, 0] },
81
- { display: "Green", value: "Green", rgb: [0, 128, 0] },
82
- { display: "Yellow", value: "Yellow", rgb: [255, 255, 0] },
83
- { display: "Blue", value: "Blue", rgb: [0, 0, 255] },
84
- { display: "Magenta", value: "Magenta", rgb: [255, 0, 255] },
79
+ { display: "Black", value: "Black", rgb: "Black" },
80
+ { display: "Red", value: "Red", rgb: "Red" },
81
+ { display: "Green", value: "Green", rgb: "Green" },
82
+ { display: "Yellow", value: "Yellow", rgb: "Yellow" },
83
+ { display: "Blue", value: "Blue", rgb: "Blue" },
84
+ { display: "Magenta", value: "Magenta", rgb: "Magenta" },
85
85
  ],
86
86
  [
87
- { display: "Cyan", value: "Cyan", rgb: [0, 255, 255] },
88
- { display: "Gray", value: "Gray", rgb: [128, 128, 128] },
89
- { display: "DkGray", value: "DarkGray", rgb: [169, 169, 169] },
90
- { display: "LtRed", value: "LightRed", rgb: [255, 128, 128] },
91
- { display: "LtGreen", value: "LightGreen", rgb: [144, 238, 144] },
92
- { display: "LtYellw", value: "LightYellow", rgb: [255, 255, 224] },
87
+ { display: "Cyan", value: "Cyan", rgb: "Cyan" },
88
+ { display: "Gray", value: "Gray", rgb: "Gray" },
89
+ { display: "DkGray", value: "DarkGray", rgb: "DarkGray" },
90
+ { display: "LtRed", value: "LightRed", rgb: "LightRed" },
91
+ { display: "LtGreen", value: "LightGreen", rgb: "LightGreen" },
92
+ { display: "LtYellw", value: "LightYellow", rgb: "LightYellow" },
93
93
  ],
94
94
  [
95
- { display: "LtBlue", value: "LightBlue", rgb: [173, 216, 230] },
96
- { display: "LtMag", value: "LightMagenta", rgb: [255, 128, 255] },
97
- { display: "LtCyan", value: "LightCyan", rgb: [224, 255, 255] },
98
- { display: "White", value: "White", rgb: [255, 255, 255] },
95
+ { display: "LtBlue", value: "LightBlue", rgb: "LightBlue" },
96
+ { display: "LtMag", value: "LightMagenta", rgb: "LightMagenta" },
97
+ { display: "LtCyan", value: "LightCyan", rgb: "LightCyan" },
98
+ { display: "White", value: "White", rgb: "White" },
99
99
  { display: "Default", value: "Default", rgb: null },
100
100
  { display: "Reset", value: "Reset", rgb: null },
101
101
  ],
@@ -555,6 +555,27 @@ function parseColorToRgb(value: ColorValue): RGB | null {
555
555
  return null;
556
556
  }
557
557
 
558
+ /**
559
+ * Convert a color value to an OverlayColorSpec for rendering.
560
+ * Named colors (e.g. "Yellow") are sent as strings so the editor renders them
561
+ * using native ANSI color codes, matching the actual theme rendering.
562
+ * RGB arrays are passed through directly.
563
+ */
564
+ function colorValueToOverlaySpec(value: ColorValue): OverlayColorSpec | null {
565
+ if (Array.isArray(value) && value.length === 3) {
566
+ return value as RGB;
567
+ }
568
+ if (typeof value === "string") {
569
+ // For recognized named colors, send the name directly so the editor
570
+ // uses native ANSI rendering (matching actual theme output)
571
+ if (NAMED_COLORS[value] !== undefined) {
572
+ return value;
573
+ }
574
+ return null;
575
+ }
576
+ return null;
577
+ }
578
+
558
579
  /**
559
580
  * Convert RGB to hex string
560
581
  */
@@ -856,14 +877,19 @@ function buildPickerLines(): PickerLine[] {
856
877
  lines.push({ text: `"${field.def.description}"`, type: "picker-desc" });
857
878
  lines.push({ text: "─".repeat(RIGHT_WIDTH - 2), type: "picker-separator" });
858
879
 
859
- // Hex / RGB value
860
- const colorStr = formatColorValue(field.value);
861
- const rgb = parseColorToRgb(field.value);
862
- let valueLine = `Hex: ${colorStr}`;
863
- if (rgb) {
864
- valueLine += ` RGB: ${rgb[0]}, ${rgb[1]}, ${rgb[2]}`;
880
+ // Color value display
881
+ const isNamed = typeof field.value === "string" && NAMED_COLORS[field.value] !== undefined;
882
+ if (isNamed) {
883
+ lines.push({ text: `Color: ${field.value} (terminal native)`, type: "picker-hex" });
884
+ } else {
885
+ const colorStr = formatColorValue(field.value);
886
+ const rgb = parseColorToRgb(field.value);
887
+ let valueLine = `Hex: ${colorStr}`;
888
+ if (rgb) {
889
+ valueLine += ` RGB: ${rgb[0]}, ${rgb[1]}, ${rgb[2]}`;
890
+ }
891
+ lines.push({ text: valueLine, type: "picker-hex" });
865
892
  }
866
- lines.push({ text: valueLine, type: "picker-hex" });
867
893
 
868
894
  lines.push({ text: "", type: "picker-blank" });
869
895
 
@@ -929,14 +955,22 @@ function styleForLeftEntry(item: TreeLine | undefined): { style?: Partial<Overla
929
955
  const paddedLen = getUtf8ByteLength(text.padEnd(LEFT_WIDTH));
930
956
  const colorValue = item.colorValue;
931
957
  const swatchIdx = colorValue !== undefined ? text.indexOf("██") : -1;
932
- const rgb = colorValue !== undefined ? parseColorToRgb(colorValue) : null;
933
958
 
934
- if (rgb && swatchIdx >= 0) {
959
+ // For the swatch, use the native color representation to ensure it matches
960
+ // how the theme actually renders: named colors (e.g. "Yellow") should use
961
+ // the terminal's native ANSI color, not an RGB approximation.
962
+ const swatchColor: OverlayColorSpec | null = colorValue !== undefined
963
+ ? (typeof colorValue === "string" && NAMED_COLORS[colorValue] !== undefined
964
+ ? colorValue as OverlayColorSpec // Send named color string directly
965
+ : parseColorToRgb(colorValue)) // Send RGB for array values
966
+ : null;
967
+
968
+ if (swatchColor && swatchIdx >= 0) {
935
969
  const swatchStart = getUtf8ByteLength(text.substring(0, swatchIdx));
936
970
  const swatchEnd = swatchStart + getUtf8ByteLength("██");
937
971
  // Non-overlapping segments: fieldName | swatch | value
938
972
  inlines.push({ start: 0, end: swatchStart, style: { fg: colors.fieldName } });
939
- inlines.push({ start: swatchStart, end: swatchEnd, style: { fg: rgb, bg: rgb } });
973
+ inlines.push({ start: swatchStart, end: swatchEnd, style: { fg: swatchColor, bg: swatchColor } });
940
974
  const valueStart = swatchEnd + getUtf8ByteLength(" ");
941
975
  if (valueStart < paddedLen) {
942
976
  inlines.push({ start: valueStart, end: paddedLen, style: { fg: colors.customValue } });
@@ -1015,10 +1049,10 @@ function styleForRightEntry(item: PickerLine | undefined): { style?: Partial<Ove
1015
1049
  // entry text = " " + item.text
1016
1050
  // item.text = " " + token texts concatenated
1017
1051
  const editorBg = getNestedValue(state.themeData, "editor.bg") as ColorValue;
1018
- const bgRgb = parseColorToRgb(editorBg);
1052
+ const bgSpec = colorValueToOverlaySpec(editorBg);
1019
1053
  const entryText = " " + item.text;
1020
1054
  const entryLen = getUtf8ByteLength(entryText);
1021
- const baseStyle: Partial<OverlayOptions> | undefined = bgRgb ? { bg: bgRgb } : undefined;
1055
+ const baseStyle: Partial<OverlayOptions> | undefined = bgSpec ? { bg: bgSpec } : undefined;
1022
1056
 
1023
1057
  // Skip the leading " " + " " (from entry " " prefix + item.text leading " ")
1024
1058
  let charPos = 2; // " " prefix + " " in item.text
@@ -1028,15 +1062,15 @@ function styleForRightEntry(item: PickerLine | undefined): { style?: Partial<Ove
1028
1062
  if (token.syntaxType) {
1029
1063
  const syntaxPath = `syntax.${token.syntaxType}`;
1030
1064
  const syntaxColor = getNestedValue(state.themeData, syntaxPath) as ColorValue;
1031
- const syntaxRgb = parseColorToRgb(syntaxColor);
1032
- if (syntaxRgb) {
1033
- inlines.push({ start: bytePos, end: bytePos + tokenLen, style: { fg: syntaxRgb } });
1065
+ const fgSpec = colorValueToOverlaySpec(syntaxColor);
1066
+ if (fgSpec) {
1067
+ inlines.push({ start: bytePos, end: bytePos + tokenLen, style: { fg: fgSpec } });
1034
1068
  }
1035
1069
  } else {
1036
1070
  const fgColor = getNestedValue(state.themeData, "editor.fg") as ColorValue;
1037
- const fgRgb = parseColorToRgb(fgColor);
1038
- if (fgRgb) {
1039
- inlines.push({ start: bytePos, end: bytePos + tokenLen, style: { fg: fgRgb } });
1071
+ const fgSpec = colorValueToOverlaySpec(fgColor);
1072
+ if (fgSpec) {
1073
+ inlines.push({ start: bytePos, end: bytePos + tokenLen, style: { fg: fgSpec } });
1040
1074
  }
1041
1075
  }
1042
1076
  bytePos += tokenLen;
@@ -1344,11 +1378,9 @@ function buildColorSuggestions(field: ThemeField): PromptSuggestion[] {
1344
1378
  suggestions.push({ text: name, description: editor.t("suggestion.terminal_native"), value: name });
1345
1379
  }
1346
1380
 
1347
- // Add named colors with hex format
1381
+ // Add named colors (terminal native - no hex shown since actual color depends on terminal)
1348
1382
  for (const name of NAMED_COLOR_LIST) {
1349
- const rgb = NAMED_COLORS[name];
1350
- const hexValue = rgbToHex(rgb[0], rgb[1], rgb[2]);
1351
- suggestions.push({ text: name, description: hexValue, value: name });
1383
+ suggestions.push({ text: name, description: editor.t("suggestion.terminal_native"), value: name });
1352
1384
  }
1353
1385
 
1354
1386
  return suggestions;
@@ -0,0 +1,162 @@
1
+ /// <reference path="./lib/fresh.d.ts" />
2
+ const editor = getEditor();
3
+
4
+ /**
5
+ * TOML LSP Helper Plugin
6
+ *
7
+ * Provides user-friendly error handling for TOML LSP server issues.
8
+ * When taplo fails to start, this plugin shows an actionable
9
+ * popup with installation instructions.
10
+ *
11
+ * Features:
12
+ * - Detects TOML LSP server errors (taplo)
13
+ * - Shows popup with install commands (cargo, npm, brew)
14
+ * - Allows copying install commands to clipboard
15
+ * - Provides option to disable TOML LSP
16
+ *
17
+ * Notes:
18
+ * - Taplo supports schema validation for Cargo.toml, pyproject.toml, etc.
19
+ * - Also available as a VS Code extension and CLI formatter
20
+ */
21
+
22
+ interface LspServerErrorData {
23
+ language: string;
24
+ server_command: string;
25
+ error_type: string;
26
+ message: string;
27
+ }
28
+
29
+ interface LspStatusClickedData {
30
+ language: string;
31
+ has_error: boolean;
32
+ }
33
+
34
+ interface ActionPopupResultData {
35
+ popup_id: string;
36
+ action_id: string;
37
+ }
38
+
39
+ // Install commands for TOML LSP server (taplo)
40
+ // See: https://taplo.tamasfe.dev/cli/installation.html
41
+ const INSTALL_COMMANDS = {
42
+ cargo: "cargo install taplo-cli --locked",
43
+ npm: "npm i -g @taplo/cli",
44
+ brew: "brew install taplo",
45
+ };
46
+
47
+ // Track error state for TOML LSP
48
+ let tomlLspError: { serverCommand: string; message: string } | null = null;
49
+
50
+ /**
51
+ * Handle LSP server errors for TOML
52
+ */
53
+ function on_toml_lsp_server_error(data: LspServerErrorData): void {
54
+ // Only handle TOML language errors
55
+ if (data.language !== "toml") {
56
+ return;
57
+ }
58
+
59
+ editor.debug(`toml-lsp: Server error - ${data.error_type}: ${data.message}`);
60
+
61
+ // Store error state for later reference
62
+ tomlLspError = {
63
+ serverCommand: data.server_command,
64
+ message: data.message,
65
+ };
66
+
67
+ // Show a status message for immediate feedback
68
+ if (data.error_type === "not_found") {
69
+ editor.setStatus(
70
+ `TOML LSP server '${data.server_command}' not found. Click status bar for help.`
71
+ );
72
+ } else {
73
+ editor.setStatus(`TOML LSP error: ${data.message}`);
74
+ }
75
+ }
76
+ registerHandler("on_toml_lsp_server_error", on_toml_lsp_server_error);
77
+
78
+ // Register hook for LSP server errors
79
+ editor.on("lsp_server_error", "on_toml_lsp_server_error");
80
+
81
+ /**
82
+ * Handle status bar click when there's a TOML LSP error
83
+ */
84
+ function on_toml_lsp_status_clicked(
85
+ data: LspStatusClickedData
86
+ ): void {
87
+ // Only handle TOML language clicks when there's an error
88
+ if (data.language !== "toml" || !tomlLspError) {
89
+ return;
90
+ }
91
+
92
+ editor.debug("toml-lsp: Status clicked, showing help popup");
93
+
94
+ // Show action popup with install options
95
+ editor.showActionPopup({
96
+ id: "toml-lsp-help",
97
+ title: "TOML Language Server Not Found",
98
+ message: `"${tomlLspError.serverCommand}" provides code completion, validation, formatting, and schema support for TOML files (Cargo.toml, pyproject.toml, etc.). Copy a command below to install it, or visit https://taplo.tamasfe.dev/cli/installation.html for details.`,
99
+ actions: [
100
+ { id: "copy_cargo", label: `Copy: ${INSTALL_COMMANDS.cargo}` },
101
+ { id: "copy_npm", label: `Copy: ${INSTALL_COMMANDS.npm}` },
102
+ { id: "copy_brew", label: `Copy: ${INSTALL_COMMANDS.brew}` },
103
+ { id: "disable", label: "Disable TOML LSP" },
104
+ { id: "dismiss", label: "Dismiss (ESC)" },
105
+ ],
106
+ });
107
+ }
108
+ registerHandler("on_toml_lsp_status_clicked", on_toml_lsp_status_clicked);
109
+
110
+ // Register hook for status bar clicks
111
+ editor.on("lsp_status_clicked", "on_toml_lsp_status_clicked");
112
+
113
+ /**
114
+ * Handle action popup results for TOML LSP help
115
+ */
116
+ function on_toml_lsp_action_result(
117
+ data: ActionPopupResultData
118
+ ): void {
119
+ // Only handle our popup
120
+ if (data.popup_id !== "toml-lsp-help") {
121
+ return;
122
+ }
123
+
124
+ editor.debug(`toml-lsp: Action selected - ${data.action_id}`);
125
+
126
+ switch (data.action_id) {
127
+ case "copy_cargo":
128
+ editor.setClipboard(INSTALL_COMMANDS.cargo);
129
+ editor.setStatus("Copied: " + INSTALL_COMMANDS.cargo);
130
+ break;
131
+
132
+ case "copy_npm":
133
+ editor.setClipboard(INSTALL_COMMANDS.npm);
134
+ editor.setStatus("Copied: " + INSTALL_COMMANDS.npm);
135
+ break;
136
+
137
+ case "copy_brew":
138
+ editor.setClipboard(INSTALL_COMMANDS.brew);
139
+ editor.setStatus("Copied: " + INSTALL_COMMANDS.brew);
140
+ break;
141
+
142
+ case "disable":
143
+ editor.disableLspForLanguage("toml");
144
+ editor.setStatus("TOML LSP disabled");
145
+ tomlLspError = null;
146
+ break;
147
+
148
+ case "dismiss":
149
+ case "dismissed":
150
+ // Just close the popup without action
151
+ break;
152
+
153
+ default:
154
+ editor.debug(`toml-lsp: Unknown action: ${data.action_id}`);
155
+ }
156
+ }
157
+ registerHandler("on_toml_lsp_action_result", on_toml_lsp_action_result);
158
+
159
+ // Register hook for action popup results
160
+ editor.on("action_popup_result", "on_toml_lsp_action_result");
161
+
162
+ editor.debug("toml-lsp: Plugin loaded");
@@ -0,0 +1,165 @@
1
+ /// <reference path="./lib/fresh.d.ts" />
2
+ const editor = getEditor();
3
+
4
+ /**
5
+ * Typst LSP Helper Plugin
6
+ *
7
+ * Provides user-friendly error handling for Typst LSP server issues.
8
+ * When tinymist fails to start, this plugin shows an actionable
9
+ * popup with installation instructions.
10
+ *
11
+ * Features:
12
+ * - Detects Typst LSP server errors (tinymist)
13
+ * - Shows popup with install commands (cargo, brew, etc.)
14
+ * - Allows copying install commands to clipboard
15
+ * - Provides option to disable Typst LSP
16
+ *
17
+ * Alternatives:
18
+ * - typst-lsp: Original Typst LSP (superseded by tinymist)
19
+ *
20
+ * Notes:
21
+ * - Tinymist also provides PDF preview and export features
22
+ * - Also available as VS Code extension "Tinymist Typst"
23
+ */
24
+
25
+ interface LspServerErrorData {
26
+ language: string;
27
+ server_command: string;
28
+ error_type: string;
29
+ message: string;
30
+ }
31
+
32
+ interface LspStatusClickedData {
33
+ language: string;
34
+ has_error: boolean;
35
+ }
36
+
37
+ interface ActionPopupResultData {
38
+ popup_id: string;
39
+ action_id: string;
40
+ }
41
+
42
+ // Install commands for Typst LSP server (tinymist)
43
+ // See: https://github.com/Myriad-Dreamin/tinymist
44
+ const INSTALL_COMMANDS = {
45
+ cargo: "cargo install tinymist",
46
+ brew: "brew install tinymist",
47
+ nix: "nix-env -iA nixpkgs.tinymist",
48
+ };
49
+
50
+ // Track error state for Typst LSP
51
+ let typstLspError: { serverCommand: string; message: string } | null = null;
52
+
53
+ /**
54
+ * Handle LSP server errors for Typst
55
+ */
56
+ function on_typst_lsp_server_error(data: LspServerErrorData): void {
57
+ // Only handle Typst language errors
58
+ if (data.language !== "typst") {
59
+ return;
60
+ }
61
+
62
+ editor.debug(`typst-lsp: Server error - ${data.error_type}: ${data.message}`);
63
+
64
+ // Store error state for later reference
65
+ typstLspError = {
66
+ serverCommand: data.server_command,
67
+ message: data.message,
68
+ };
69
+
70
+ // Show a status message for immediate feedback
71
+ if (data.error_type === "not_found") {
72
+ editor.setStatus(
73
+ `Typst LSP server '${data.server_command}' not found. Click status bar for help.`
74
+ );
75
+ } else {
76
+ editor.setStatus(`Typst LSP error: ${data.message}`);
77
+ }
78
+ }
79
+ registerHandler("on_typst_lsp_server_error", on_typst_lsp_server_error);
80
+
81
+ // Register hook for LSP server errors
82
+ editor.on("lsp_server_error", "on_typst_lsp_server_error");
83
+
84
+ /**
85
+ * Handle status bar click when there's a Typst LSP error
86
+ */
87
+ function on_typst_lsp_status_clicked(
88
+ data: LspStatusClickedData
89
+ ): void {
90
+ // Only handle Typst language clicks when there's an error
91
+ if (data.language !== "typst" || !typstLspError) {
92
+ return;
93
+ }
94
+
95
+ editor.debug("typst-lsp: Status clicked, showing help popup");
96
+
97
+ // Show action popup with install options
98
+ editor.showActionPopup({
99
+ id: "typst-lsp-help",
100
+ title: "Typst Language Server Not Found",
101
+ message: `"${typstLspError.serverCommand}" provides code completion, diagnostics, and preview support for Typst documents. Copy a command below to install it, or visit https://github.com/Myriad-Dreamin/tinymist for details and pre-built binaries. Also available as the "Tinymist Typst" VS Code extension.`,
102
+ actions: [
103
+ { id: "copy_cargo", label: `Copy: ${INSTALL_COMMANDS.cargo}` },
104
+ { id: "copy_brew", label: `Copy: ${INSTALL_COMMANDS.brew}` },
105
+ { id: "copy_nix", label: `Copy: ${INSTALL_COMMANDS.nix}` },
106
+ { id: "disable", label: "Disable Typst LSP" },
107
+ { id: "dismiss", label: "Dismiss (ESC)" },
108
+ ],
109
+ });
110
+ }
111
+ registerHandler("on_typst_lsp_status_clicked", on_typst_lsp_status_clicked);
112
+
113
+ // Register hook for status bar clicks
114
+ editor.on("lsp_status_clicked", "on_typst_lsp_status_clicked");
115
+
116
+ /**
117
+ * Handle action popup results for Typst LSP help
118
+ */
119
+ function on_typst_lsp_action_result(
120
+ data: ActionPopupResultData
121
+ ): void {
122
+ // Only handle our popup
123
+ if (data.popup_id !== "typst-lsp-help") {
124
+ return;
125
+ }
126
+
127
+ editor.debug(`typst-lsp: Action selected - ${data.action_id}`);
128
+
129
+ switch (data.action_id) {
130
+ case "copy_cargo":
131
+ editor.setClipboard(INSTALL_COMMANDS.cargo);
132
+ editor.setStatus("Copied: " + INSTALL_COMMANDS.cargo);
133
+ break;
134
+
135
+ case "copy_brew":
136
+ editor.setClipboard(INSTALL_COMMANDS.brew);
137
+ editor.setStatus("Copied: " + INSTALL_COMMANDS.brew);
138
+ break;
139
+
140
+ case "copy_nix":
141
+ editor.setClipboard(INSTALL_COMMANDS.nix);
142
+ editor.setStatus("Copied: " + INSTALL_COMMANDS.nix);
143
+ break;
144
+
145
+ case "disable":
146
+ editor.disableLspForLanguage("typst");
147
+ editor.setStatus("Typst LSP disabled");
148
+ typstLspError = null;
149
+ break;
150
+
151
+ case "dismiss":
152
+ case "dismissed":
153
+ // Just close the popup without action
154
+ break;
155
+
156
+ default:
157
+ editor.debug(`typst-lsp: Unknown action: ${data.action_id}`);
158
+ }
159
+ }
160
+ registerHandler("on_typst_lsp_action_result", on_typst_lsp_action_result);
161
+
162
+ // Register hook for action popup results
163
+ editor.on("action_popup_result", "on_typst_lsp_action_result");
164
+
165
+ editor.debug("typst-lsp: Plugin loaded");
@@ -0,0 +1,118 @@
1
+ /// <reference path="./lib/fresh.d.ts" />
2
+ const editor = getEditor();
3
+
4
+ /**
5
+ * Vue LSP Helper Plugin
6
+ *
7
+ * Server: vue-language-server (@vue/language-server, formerly Volar)
8
+ * VS Code: "Vue - Official" extension (replaces deprecated Vetur)
9
+ * Neovim: nvim-lspconfig volar
10
+ * Note: Supports hybrid mode with @vue/typescript-plugin for TS integration
11
+ */
12
+
13
+ interface LspServerErrorData {
14
+ language: string;
15
+ server_command: string;
16
+ error_type: string;
17
+ message: string;
18
+ }
19
+
20
+ interface LspStatusClickedData {
21
+ language: string;
22
+ has_error: boolean;
23
+ }
24
+
25
+ interface ActionPopupResultData {
26
+ popup_id: string;
27
+ action_id: string;
28
+ }
29
+
30
+ const INSTALL_COMMANDS = {
31
+ npm: "npm install -g @vue/language-server",
32
+ yarn: "yarn global add @vue/language-server",
33
+ pnpm: "pnpm add -g @vue/language-server",
34
+ };
35
+
36
+ let vueLspError: { serverCommand: string; message: string } | null = null;
37
+
38
+ function on_vue_lsp_server_error(data: LspServerErrorData): void {
39
+ if (data.language !== "vue") {
40
+ return;
41
+ }
42
+
43
+ editor.debug(`vue-lsp: Server error - ${data.error_type}: ${data.message}`);
44
+
45
+ vueLspError = {
46
+ serverCommand: data.server_command,
47
+ message: data.message,
48
+ };
49
+
50
+ if (data.error_type === "not_found") {
51
+ editor.setStatus(
52
+ `Vue LSP server '${data.server_command}' not found. Click status bar for help.`
53
+ );
54
+ } else {
55
+ editor.setStatus(`Vue LSP error: ${data.message}`);
56
+ }
57
+ }
58
+ registerHandler("on_vue_lsp_server_error", on_vue_lsp_server_error);
59
+ editor.on("lsp_server_error", "on_vue_lsp_server_error");
60
+
61
+ function on_vue_lsp_status_clicked(data: LspStatusClickedData): void {
62
+ if (data.language !== "vue" || !vueLspError) {
63
+ return;
64
+ }
65
+
66
+ editor.debug("vue-lsp: Status clicked, showing help popup");
67
+
68
+ editor.showActionPopup({
69
+ id: "vue-lsp-help",
70
+ title: "Vue Language Server Not Found",
71
+ message: `"${vueLspError.serverCommand}" (formerly Volar) provides completion, diagnostics, and refactoring for Vue SFCs. It replaces the deprecated Vetur.\n\nFor TypeScript integration, also install @vue/typescript-plugin.\nVS Code users: Install the "Vue - Official" extension.\nSee: https://github.com/vuejs/language-tools`,
72
+ actions: [
73
+ { id: "copy_npm", label: `Copy: ${INSTALL_COMMANDS.npm}` },
74
+ { id: "copy_pnpm", label: `Copy: ${INSTALL_COMMANDS.pnpm}` },
75
+ { id: "disable", label: "Disable Vue LSP" },
76
+ { id: "dismiss", label: "Dismiss (ESC)" },
77
+ ],
78
+ });
79
+ }
80
+ registerHandler("on_vue_lsp_status_clicked", on_vue_lsp_status_clicked);
81
+ editor.on("lsp_status_clicked", "on_vue_lsp_status_clicked");
82
+
83
+ function on_vue_lsp_action_result(data: ActionPopupResultData): void {
84
+ if (data.popup_id !== "vue-lsp-help") {
85
+ return;
86
+ }
87
+
88
+ editor.debug(`vue-lsp: Action selected - ${data.action_id}`);
89
+
90
+ switch (data.action_id) {
91
+ case "copy_npm":
92
+ editor.setClipboard(INSTALL_COMMANDS.npm);
93
+ editor.setStatus("Copied: " + INSTALL_COMMANDS.npm);
94
+ break;
95
+
96
+ case "copy_pnpm":
97
+ editor.setClipboard(INSTALL_COMMANDS.pnpm);
98
+ editor.setStatus("Copied: " + INSTALL_COMMANDS.pnpm);
99
+ break;
100
+
101
+ case "disable":
102
+ editor.disableLspForLanguage("vue");
103
+ editor.setStatus("Vue LSP disabled");
104
+ vueLspError = null;
105
+ break;
106
+
107
+ case "dismiss":
108
+ case "dismissed":
109
+ break;
110
+
111
+ default:
112
+ editor.debug(`vue-lsp: Unknown action: ${data.action_id}`);
113
+ }
114
+ }
115
+ registerHandler("on_vue_lsp_action_result", on_vue_lsp_action_result);
116
+ editor.on("action_popup_result", "on_vue_lsp_action_result");
117
+
118
+ editor.debug("vue-lsp: Plugin loaded");