@fresh-editor/fresh-editor 0.2.17 → 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 +84 -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
@@ -0,0 +1,118 @@
1
+ /// <reference path="./lib/fresh.d.ts" />
2
+ const editor = getEditor();
3
+
4
+ /**
5
+ * Perl LSP Helper Plugin
6
+ *
7
+ * Server: PerlNavigator (github.com/bscan/PerlNavigator)
8
+ * VS Code: "Perl Navigator" extension
9
+ * Neovim: nvim-lspconfig perlnavigator
10
+ * Alternative: Perl::LanguageServer (cpan, older)
11
+ * Note: PerlNavigator includes perlcritic and perltidy integration
12
+ */
13
+
14
+ interface LspServerErrorData {
15
+ language: string;
16
+ server_command: string;
17
+ error_type: string;
18
+ message: string;
19
+ }
20
+
21
+ interface LspStatusClickedData {
22
+ language: string;
23
+ has_error: boolean;
24
+ }
25
+
26
+ interface ActionPopupResultData {
27
+ popup_id: string;
28
+ action_id: string;
29
+ }
30
+
31
+ const INSTALL_COMMANDS = {
32
+ npm: "npm install -g perlnavigator-server",
33
+ cpan_alt: "cpanm Perl::LanguageServer",
34
+ };
35
+
36
+ let perlLspError: { serverCommand: string; message: string } | null = null;
37
+
38
+ function on_perl_lsp_server_error(data: LspServerErrorData): void {
39
+ if (data.language !== "perl") {
40
+ return;
41
+ }
42
+
43
+ editor.debug(`perl-lsp: Server error - ${data.error_type}: ${data.message}`);
44
+
45
+ perlLspError = {
46
+ serverCommand: data.server_command,
47
+ message: data.message,
48
+ };
49
+
50
+ if (data.error_type === "not_found") {
51
+ editor.setStatus(
52
+ `Perl LSP server '${data.server_command}' not found. Click status bar for help.`
53
+ );
54
+ } else {
55
+ editor.setStatus(`Perl LSP error: ${data.message}`);
56
+ }
57
+ }
58
+ registerHandler("on_perl_lsp_server_error", on_perl_lsp_server_error);
59
+ editor.on("lsp_server_error", "on_perl_lsp_server_error");
60
+
61
+ function on_perl_lsp_status_clicked(data: LspStatusClickedData): void {
62
+ if (data.language !== "perl" || !perlLspError) {
63
+ return;
64
+ }
65
+
66
+ editor.debug("perl-lsp: Status clicked, showing help popup");
67
+
68
+ editor.showActionPopup({
69
+ id: "perl-lsp-help",
70
+ title: "Perl Language Server Not Found",
71
+ message: `"${perlLspError.serverCommand}" (PerlNavigator) provides completion, diagnostics, navigation, and perlcritic/perltidy integration for Perl.\n\nAlternative: Perl::LanguageServer (older, via CPAN).\nVS Code users: Install the "Perl Navigator" extension.\nSee: https://github.com/bscan/PerlNavigator`,
72
+ actions: [
73
+ { id: "copy_npm", label: `Copy: ${INSTALL_COMMANDS.npm}` },
74
+ { id: "copy_cpan", label: `Copy: ${INSTALL_COMMANDS.cpan_alt}` },
75
+ { id: "disable", label: "Disable Perl LSP" },
76
+ { id: "dismiss", label: "Dismiss (ESC)" },
77
+ ],
78
+ });
79
+ }
80
+ registerHandler("on_perl_lsp_status_clicked", on_perl_lsp_status_clicked);
81
+ editor.on("lsp_status_clicked", "on_perl_lsp_status_clicked");
82
+
83
+ function on_perl_lsp_action_result(data: ActionPopupResultData): void {
84
+ if (data.popup_id !== "perl-lsp-help") {
85
+ return;
86
+ }
87
+
88
+ editor.debug(`perl-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_cpan":
97
+ editor.setClipboard(INSTALL_COMMANDS.cpan_alt);
98
+ editor.setStatus("Copied: " + INSTALL_COMMANDS.cpan_alt);
99
+ break;
100
+
101
+ case "disable":
102
+ editor.disableLspForLanguage("perl");
103
+ editor.setStatus("Perl LSP disabled");
104
+ perlLspError = null;
105
+ break;
106
+
107
+ case "dismiss":
108
+ case "dismissed":
109
+ break;
110
+
111
+ default:
112
+ editor.debug(`perl-lsp: Unknown action: ${data.action_id}`);
113
+ }
114
+ }
115
+ registerHandler("on_perl_lsp_action_result", on_perl_lsp_action_result);
116
+ editor.on("action_popup_result", "on_perl_lsp_action_result");
117
+
118
+ editor.debug("perl-lsp: Plugin loaded");
@@ -0,0 +1,165 @@
1
+ /// <reference path="./lib/fresh.d.ts" />
2
+ const editor = getEditor();
3
+
4
+ /**
5
+ * PHP LSP Helper Plugin
6
+ *
7
+ * Provides user-friendly error handling for PHP LSP server issues.
8
+ * When phpactor fails to start, this plugin shows an actionable
9
+ * popup with installation instructions.
10
+ *
11
+ * Features:
12
+ * - Detects PHP LSP server errors (phpactor)
13
+ * - Shows popup with install commands (composer, brew)
14
+ * - Allows copying install commands to clipboard
15
+ * - Provides option to disable PHP LSP
16
+ *
17
+ * Alternatives:
18
+ * - Intelephense: Feature-rich PHP LSP (https://intelephense.com/) - npm i -g intelephense
19
+ * - PHPStan: Static analysis tool (https://phpstan.org/)
20
+ * - Psalm: PHP static analysis (https://psalm.dev/)
21
+ */
22
+
23
+ interface LspServerErrorData {
24
+ language: string;
25
+ server_command: string;
26
+ error_type: string;
27
+ message: string;
28
+ }
29
+
30
+ interface LspStatusClickedData {
31
+ language: string;
32
+ has_error: boolean;
33
+ }
34
+
35
+ interface ActionPopupResultData {
36
+ popup_id: string;
37
+ action_id: string;
38
+ }
39
+
40
+ // Install commands for PHP LSP server (phpactor)
41
+ // See: https://phpactor.readthedocs.io/en/master/usage/getting-started.html
42
+ const INSTALL_COMMANDS = {
43
+ composer: "composer global require phpactor/phpactor",
44
+ brew: "brew install phpactor",
45
+ };
46
+
47
+ // Alternative LSP server
48
+ const ALT_INSTALL = "npm i -g intelephense";
49
+
50
+ // Track error state for PHP LSP
51
+ let phpLspError: { serverCommand: string; message: string } | null = null;
52
+
53
+ /**
54
+ * Handle LSP server errors for PHP
55
+ */
56
+ function on_php_lsp_server_error(data: LspServerErrorData): void {
57
+ // Only handle PHP language errors
58
+ if (data.language !== "php") {
59
+ return;
60
+ }
61
+
62
+ editor.debug(`php-lsp: Server error - ${data.error_type}: ${data.message}`);
63
+
64
+ // Store error state for later reference
65
+ phpLspError = {
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
+ `PHP LSP server '${data.server_command}' not found. Click status bar for help.`
74
+ );
75
+ } else {
76
+ editor.setStatus(`PHP LSP error: ${data.message}`);
77
+ }
78
+ }
79
+ registerHandler("on_php_lsp_server_error", on_php_lsp_server_error);
80
+
81
+ // Register hook for LSP server errors
82
+ editor.on("lsp_server_error", "on_php_lsp_server_error");
83
+
84
+ /**
85
+ * Handle status bar click when there's a PHP LSP error
86
+ */
87
+ function on_php_lsp_status_clicked(
88
+ data: LspStatusClickedData
89
+ ): void {
90
+ // Only handle PHP language clicks when there's an error
91
+ if (data.language !== "php" || !phpLspError) {
92
+ return;
93
+ }
94
+
95
+ editor.debug("php-lsp: Status clicked, showing help popup");
96
+
97
+ // Show action popup with install options
98
+ editor.showActionPopup({
99
+ id: "php-lsp-help",
100
+ title: "PHP Language Server Not Found",
101
+ message: `"${phpLspError.serverCommand}" provides code completion, diagnostics, and navigation for PHP files. Requires PHP and Composer. Copy a command below to install it, or visit https://phpactor.readthedocs.io for details. Alternative: Intelephense (https://intelephense.com/) is a popular Node.js-based PHP LSP.`,
102
+ actions: [
103
+ { id: "copy_composer", label: `Copy: ${INSTALL_COMMANDS.composer}` },
104
+ { id: "copy_brew", label: `Copy: ${INSTALL_COMMANDS.brew}` },
105
+ { id: "copy_alt", label: `Alternative: ${ALT_INSTALL}` },
106
+ { id: "disable", label: "Disable PHP LSP" },
107
+ { id: "dismiss", label: "Dismiss (ESC)" },
108
+ ],
109
+ });
110
+ }
111
+ registerHandler("on_php_lsp_status_clicked", on_php_lsp_status_clicked);
112
+
113
+ // Register hook for status bar clicks
114
+ editor.on("lsp_status_clicked", "on_php_lsp_status_clicked");
115
+
116
+ /**
117
+ * Handle action popup results for PHP LSP help
118
+ */
119
+ function on_php_lsp_action_result(
120
+ data: ActionPopupResultData
121
+ ): void {
122
+ // Only handle our popup
123
+ if (data.popup_id !== "php-lsp-help") {
124
+ return;
125
+ }
126
+
127
+ editor.debug(`php-lsp: Action selected - ${data.action_id}`);
128
+
129
+ switch (data.action_id) {
130
+ case "copy_composer":
131
+ editor.setClipboard(INSTALL_COMMANDS.composer);
132
+ editor.setStatus("Copied: " + INSTALL_COMMANDS.composer);
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_alt":
141
+ editor.setClipboard(ALT_INSTALL);
142
+ editor.setStatus("Copied: " + ALT_INSTALL);
143
+ break;
144
+
145
+ case "disable":
146
+ editor.disableLspForLanguage("php");
147
+ editor.setStatus("PHP LSP disabled");
148
+ phpLspError = 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(`php-lsp: Unknown action: ${data.action_id}`);
158
+ }
159
+ }
160
+ registerHandler("on_php_lsp_action_result", on_php_lsp_action_result);
161
+
162
+ // Register hook for action popup results
163
+ editor.on("action_popup_result", "on_php_lsp_action_result");
164
+
165
+ editor.debug("php-lsp: Plugin loaded");
package/plugins/pkg.ts CHANGED
@@ -231,14 +231,10 @@ interface ParsedPackageUrl {
231
231
  // =============================================================================
232
232
 
233
233
  /**
234
- * Ensure a directory exists
234
+ * Ensure a directory exists (cross-platform)
235
235
  */
236
- async function ensureDir(path: string): Promise<boolean> {
237
- if (editor.fileExists(path)) {
238
- return true;
239
- }
240
- const result = await editor.spawnProcess("mkdir", ["-p", path]);
241
- return result.exit_code === 0;
236
+ function ensureDir(path: string): boolean {
237
+ return editor.createDir(path);
242
238
  }
243
239
 
244
240
  /**
@@ -395,7 +391,7 @@ async function writeJsonFile(path: string, data: unknown): Promise<boolean> {
395
391
  async function syncRegistry(): Promise<void> {
396
392
  editor.setStatus("Syncing package registry...");
397
393
 
398
- await ensureDir(INDEX_DIR);
394
+ ensureDir(INDEX_DIR);
399
395
 
400
396
  const sources = getRegistrySources();
401
397
  let synced = 0;
@@ -493,7 +489,7 @@ function loadRegistry(type: "plugins" | "themes" | "languages"): RegistryData {
493
489
  * Cache registry data locally for offline/fast access
494
490
  */
495
491
  async function cacheRegistry(): Promise<void> {
496
- await ensureDir(CACHE_DIR);
492
+ ensureDir(CACHE_DIR);
497
493
  const sources = getRegistrySources();
498
494
 
499
495
  for (const source of sources) {
@@ -823,7 +819,7 @@ async function installFromRepo(
823
819
  version?: string
824
820
  ): Promise<boolean> {
825
821
  // Clone to temp directory first to detect package type
826
- const tempDir = `/tmp/fresh-pkg-clone-${hashString(repoUrl)}-${Date.now()}`;
822
+ const tempDir = editor.pathJoin(editor.getTempDir(), `fresh-pkg-clone-${hashString(repoUrl)}-${Date.now()}`);
827
823
 
828
824
  const cloneArgs = ["clone"];
829
825
  if (!version || version === "latest") {
@@ -857,7 +853,7 @@ async function installFromRepo(
857
853
  editor.warn(`[pkg] Invalid package '${packageName}': ${validation.error}`);
858
854
  editor.setStatus(`Failed to install ${packageName}: ${validation.error}`);
859
855
  // Clean up
860
- await editor.spawnProcess("rm", ["-rf", tempDir]);
856
+ editor.removePath(tempDir);
861
857
  return false;
862
858
  }
863
859
 
@@ -877,16 +873,15 @@ async function installFromRepo(
877
873
  // Check if already installed in correct location
878
874
  if (editor.fileExists(correctTargetDir)) {
879
875
  editor.setStatus(`Package '${packageName}' is already installed`);
880
- await editor.spawnProcess("rm", ["-rf", tempDir]);
876
+ editor.removePath(tempDir);
881
877
  return false;
882
878
  }
883
879
 
884
880
  // Ensure correct directory exists and move from temp
885
- await ensureDir(correctPackagesDir);
886
- const moveResult = await editor.spawnProcess("mv", [tempDir, correctTargetDir]);
887
- if (moveResult.exit_code !== 0) {
888
- editor.setStatus(`Failed to install ${packageName}: ${moveResult.stderr}`);
889
- await editor.spawnProcess("rm", ["-rf", tempDir]);
881
+ ensureDir(correctPackagesDir);
882
+ if (!editor.renamePath(tempDir, correctTargetDir)) {
883
+ editor.setStatus(`Failed to install ${packageName}: could not move package to target directory`);
884
+ editor.removePath(tempDir);
890
885
  return false;
891
886
  }
892
887
 
@@ -976,13 +971,12 @@ async function installFromLocalPath(
976
971
  }
977
972
 
978
973
  // Ensure correct directory exists
979
- await ensureDir(correctPackagesDir);
974
+ ensureDir(correctPackagesDir);
980
975
 
981
976
  // Copy the directory to correct target
982
977
  editor.setStatus(`Copying from ${sourcePath}...`);
983
- const copyResult = await editor.spawnProcess("cp", ["-r", sourcePath, correctTargetDir]);
984
- if (copyResult.exit_code !== 0) {
985
- editor.setStatus(`Failed to copy package: ${copyResult.stderr}`);
978
+ if (!editor.copyPath(sourcePath, correctTargetDir)) {
979
+ editor.setStatus(`Failed to copy package from ${sourcePath}`);
986
980
  return false;
987
981
  }
988
982
 
@@ -992,7 +986,7 @@ async function installFromLocalPath(
992
986
  editor.warn(`[pkg] Invalid package '${packageName}': ${validation.error}`);
993
987
  editor.setStatus(`Failed to install ${packageName}: ${validation.error}`);
994
988
  // Clean up the invalid package
995
- await editor.spawnProcess("rm", ["-rf", correctTargetDir]);
989
+ editor.removePath(correctTargetDir);
996
990
  return false;
997
991
  }
998
992
 
@@ -1037,7 +1031,7 @@ async function installFromMonorepo(
1037
1031
  packageName: string,
1038
1032
  version?: string
1039
1033
  ): Promise<boolean> {
1040
- const tempDir = `/tmp/fresh-pkg-${hashString(parsed.repoUrl)}-${Date.now()}`;
1034
+ const tempDir = editor.pathJoin(editor.getTempDir(), `fresh-pkg-${hashString(parsed.repoUrl)}-${Date.now()}`);
1041
1035
 
1042
1036
  try {
1043
1037
  // Clone the full repo to temp
@@ -1068,7 +1062,7 @@ async function installFromMonorepo(
1068
1062
  const subpathDir = editor.pathJoin(tempDir, parsed.subpath!);
1069
1063
  if (!editor.fileExists(subpathDir)) {
1070
1064
  editor.setStatus(`Subpath '${parsed.subpath}' not found in repository`);
1071
- await editor.spawnProcess("rm", ["-rf", tempDir]);
1065
+ editor.removePath(tempDir);
1072
1066
  return false;
1073
1067
  }
1074
1068
 
@@ -1077,7 +1071,7 @@ async function installFromMonorepo(
1077
1071
  if (!validation.valid) {
1078
1072
  editor.warn(`[pkg] Invalid package '${packageName}': ${validation.error}`);
1079
1073
  editor.setStatus(`Failed to install ${packageName}: ${validation.error}`);
1080
- await editor.spawnProcess("rm", ["-rf", tempDir]);
1074
+ editor.removePath(tempDir);
1081
1075
  return false;
1082
1076
  }
1083
1077
 
@@ -1097,19 +1091,18 @@ async function installFromMonorepo(
1097
1091
  // Check if already installed
1098
1092
  if (editor.fileExists(correctTargetDir)) {
1099
1093
  editor.setStatus(`Package '${packageName}' is already installed`);
1100
- await editor.spawnProcess("rm", ["-rf", tempDir]);
1094
+ editor.removePath(tempDir);
1101
1095
  return false;
1102
1096
  }
1103
1097
 
1104
1098
  // Ensure correct directory exists
1105
- await ensureDir(correctPackagesDir);
1099
+ ensureDir(correctPackagesDir);
1106
1100
 
1107
1101
  // Copy subdirectory to correct target
1108
1102
  editor.setStatus(`Installing ${packageName} from ${parsed.subpath}...`);
1109
- const copyResult = await editor.spawnProcess("cp", ["-r", subpathDir, correctTargetDir]);
1110
- if (copyResult.exit_code !== 0) {
1111
- editor.setStatus(`Failed to copy package: ${copyResult.stderr}`);
1112
- await editor.spawnProcess("rm", ["-rf", tempDir]);
1103
+ if (!editor.copyPath(subpathDir, correctTargetDir)) {
1104
+ editor.setStatus(`Failed to copy package from ${parsed.subpath}`);
1105
+ editor.removePath(tempDir);
1113
1106
  return false;
1114
1107
  }
1115
1108
 
@@ -1143,7 +1136,7 @@ async function installFromMonorepo(
1143
1136
  return true;
1144
1137
  } finally {
1145
1138
  // Cleanup temp directory
1146
- await editor.spawnProcess("rm", ["-rf", tempDir]);
1139
+ editor.removePath(tempDir);
1147
1140
  }
1148
1141
  }
1149
1142
 
@@ -1408,16 +1401,14 @@ async function reinstallPackage(pkg: InstalledPackage): Promise<boolean> {
1408
1401
  }
1409
1402
 
1410
1403
  // Remove old copy
1411
- const rmResult = await editor.spawnProcess("rm", ["-rf", pkg.path]);
1412
- if (rmResult.exit_code !== 0) {
1413
- editor.setStatus(`Failed to remove old copy: ${rmResult.stderr}`);
1404
+ if (!editor.removePath(pkg.path)) {
1405
+ editor.setStatus(`Failed to remove old copy of ${pkg.name}`);
1414
1406
  return false;
1415
1407
  }
1416
1408
 
1417
1409
  // Re-copy from source
1418
- const copyResult = await editor.spawnProcess("cp", ["-r", sourcePath, pkg.path]);
1419
- if (copyResult.exit_code !== 0) {
1420
- editor.setStatus(`Failed to copy from source: ${copyResult.stderr}`);
1410
+ if (!editor.copyPath(sourcePath, pkg.path)) {
1411
+ editor.setStatus(`Failed to copy from source: ${sourcePath}`);
1421
1412
  return false;
1422
1413
  }
1423
1414
 
@@ -1474,13 +1465,8 @@ async function removePackage(pkg: InstalledPackage): Promise<boolean> {
1474
1465
  }
1475
1466
  }
1476
1467
 
1477
- // Use trash if available, otherwise rm -rf
1478
- let result = await editor.spawnProcess("trash", [pkg.path]);
1479
- if (result.exit_code !== 0) {
1480
- result = await editor.spawnProcess("rm", ["-rf", pkg.path]);
1481
- }
1482
-
1483
- if (result.exit_code === 0) {
1468
+ // Remove package directory
1469
+ if (editor.removePath(pkg.path)) {
1484
1470
  // Reload themes if we removed a theme so Select Theme list is updated
1485
1471
  if (pkg.type === "theme") {
1486
1472
  editor.reloadThemes();
@@ -1488,7 +1474,7 @@ async function removePackage(pkg: InstalledPackage): Promise<boolean> {
1488
1474
  editor.setStatus(`Removed ${pkg.name}`);
1489
1475
  return true;
1490
1476
  } else {
1491
- editor.setStatus(`Failed to remove ${pkg.name}: ${result.stderr}`);
1477
+ editor.setStatus(`Failed to remove ${pkg.name}`);
1492
1478
  return false;
1493
1479
  }
1494
1480
  }
@@ -1608,7 +1594,7 @@ async function installFromLockfile(): Promise<void> {
1608
1594
  }
1609
1595
  } else {
1610
1596
  // Need to clone
1611
- await ensureDir(PACKAGES_DIR);
1597
+ ensureDir(PACKAGES_DIR);
1612
1598
  const result = await gitCommand(["clone", `${entry.source}`, `${pluginPath}`]);
1613
1599
 
1614
1600
  if (result.exit_code === 0) {
@@ -0,0 +1,144 @@
1
+ /// <reference path="./lib/fresh.d.ts" />
2
+ const editor = getEditor();
3
+
4
+ /**
5
+ * Protobuf LSP Helper Plugin
6
+ *
7
+ * Provides user-friendly error handling for Protobuf LSP server issues.
8
+ * When buf fails to start, this plugin shows an actionable
9
+ * popup with installation instructions.
10
+ *
11
+ * Features:
12
+ * - Detects Protobuf LSP server errors (buf)
13
+ * - Shows popup with install commands (brew, npm, etc.)
14
+ * - Provides option to disable Protobuf LSP
15
+ *
16
+ * VS Code: "Buf" extension (auto-installs buf CLI), "Protobuf support" by peterj
17
+ * Neovim: nvim-lspconfig bufls
18
+ * Alternative: protols (open-source, tree-sitter based, standalone)
19
+ */
20
+
21
+ interface LspServerErrorData {
22
+ language: string;
23
+ server_command: string;
24
+ error_type: string;
25
+ message: string;
26
+ }
27
+
28
+ interface LspStatusClickedData {
29
+ language: string;
30
+ has_error: boolean;
31
+ }
32
+
33
+ interface ActionPopupResultData {
34
+ popup_id: string;
35
+ action_id: string;
36
+ }
37
+
38
+ // Install commands for Protobuf LSP server (buf)
39
+ // See: https://buf.build/docs/installation
40
+ const INSTALL_COMMANDS = {
41
+ brew: "brew install bufbuild/buf/buf",
42
+ npm: "npm i -g @bufbuild/buf",
43
+ go: "go install github.com/bufbuild/buf/cmd/buf@latest",
44
+ };
45
+
46
+ // Track error state for Protobuf LSP
47
+ let protobufLspError: { serverCommand: string; message: string } | null = null;
48
+
49
+ /**
50
+ * Handle LSP server errors for Protobuf
51
+ */
52
+ function on_protobuf_lsp_server_error(data: LspServerErrorData): void {
53
+ if (data.language !== "protobuf") {
54
+ return;
55
+ }
56
+
57
+ editor.debug(`protobuf-lsp: Server error - ${data.error_type}: ${data.message}`);
58
+
59
+ protobufLspError = {
60
+ serverCommand: data.server_command,
61
+ message: data.message,
62
+ };
63
+
64
+ if (data.error_type === "not_found") {
65
+ editor.setStatus(
66
+ `Protobuf LSP server '${data.server_command}' not found. Click status bar for help.`
67
+ );
68
+ } else {
69
+ editor.setStatus(`Protobuf LSP error: ${data.message}`);
70
+ }
71
+ }
72
+ registerHandler("on_protobuf_lsp_server_error", on_protobuf_lsp_server_error);
73
+ editor.on("lsp_server_error", "on_protobuf_lsp_server_error");
74
+
75
+ /**
76
+ * Handle status bar click when there's a Protobuf LSP error
77
+ */
78
+ function on_protobuf_lsp_status_clicked(data: LspStatusClickedData): void {
79
+ if (data.language !== "protobuf" || !protobufLspError) {
80
+ return;
81
+ }
82
+
83
+ editor.debug("protobuf-lsp: Status clicked, showing help popup");
84
+
85
+ editor.showActionPopup({
86
+ id: "protobuf-lsp-help",
87
+ title: "Protobuf Language Server Not Found",
88
+ message: `"${protobufLspError.serverCommand}" (Buf CLI) provides code completion, diagnostics, linting, and formatting for Protocol Buffer files. Copy a command below to install it, or visit https://buf.build/docs/installation for details.`,
89
+ actions: [
90
+ { id: "copy_brew", label: `Copy: ${INSTALL_COMMANDS.brew}` },
91
+ { id: "copy_npm", label: `Copy: ${INSTALL_COMMANDS.npm}` },
92
+ { id: "copy_go", label: `Copy: ${INSTALL_COMMANDS.go}` },
93
+ { id: "disable", label: "Disable Protobuf LSP" },
94
+ { id: "dismiss", label: "Dismiss (ESC)" },
95
+ ],
96
+ });
97
+ }
98
+ registerHandler("on_protobuf_lsp_status_clicked", on_protobuf_lsp_status_clicked);
99
+ editor.on("lsp_status_clicked", "on_protobuf_lsp_status_clicked");
100
+
101
+ /**
102
+ * Handle action popup results for Protobuf LSP help
103
+ */
104
+ function on_protobuf_lsp_action_result(data: ActionPopupResultData): void {
105
+ if (data.popup_id !== "protobuf-lsp-help") {
106
+ return;
107
+ }
108
+
109
+ editor.debug(`protobuf-lsp: Action selected - ${data.action_id}`);
110
+
111
+ switch (data.action_id) {
112
+ case "copy_brew":
113
+ editor.setClipboard(INSTALL_COMMANDS.brew);
114
+ editor.setStatus("Copied: " + INSTALL_COMMANDS.brew);
115
+ break;
116
+
117
+ case "copy_npm":
118
+ editor.setClipboard(INSTALL_COMMANDS.npm);
119
+ editor.setStatus("Copied: " + INSTALL_COMMANDS.npm);
120
+ break;
121
+
122
+ case "copy_go":
123
+ editor.setClipboard(INSTALL_COMMANDS.go);
124
+ editor.setStatus("Copied: " + INSTALL_COMMANDS.go);
125
+ break;
126
+
127
+ case "disable":
128
+ editor.disableLspForLanguage("protobuf");
129
+ editor.setStatus("Protobuf LSP disabled");
130
+ protobufLspError = null;
131
+ break;
132
+
133
+ case "dismiss":
134
+ case "dismissed":
135
+ break;
136
+
137
+ default:
138
+ editor.debug(`protobuf-lsp: Unknown action: ${data.action_id}`);
139
+ }
140
+ }
141
+ registerHandler("on_protobuf_lsp_action_result", on_protobuf_lsp_action_result);
142
+ editor.on("action_popup_result", "on_protobuf_lsp_action_result");
143
+
144
+ editor.debug("protobuf-lsp: Plugin loaded");