@fresh-editor/fresh-editor 0.1.86 → 0.1.87

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Release Notes
2
2
 
3
+ ## 0.1.87
4
+
5
+ ### Features
6
+
7
+ * **Language Support**: Added LSP configurations and syntax highlighting for Zig, Java, LaTeX, Markdown, and Templ.
8
+ * **Git File Highlighting**: Syntax highlighting for git-related files (.gitignore, .gitattributes, .gitmodules).
9
+ * **Plugin Type Safety**: TypeScript type definitions for plugin API with compile-time validation.
10
+
11
+ ### Bug Fixes
12
+
13
+ * **Hover Popup**: Fixed scrolling to bottom, dismiss on click outside, block clicks inside popup.
14
+ * **Settings UI**: Fixed overwriting manual config.json edits when saving from Settings UI (#806).
15
+ * **Windows Terminal**: Fixed truecolor detection and 256-color grayscale conversion overflow.
16
+ * **Composite Buffers**: Fixed mouse click sync, deserialization errors, and cursor positioning.
17
+ * **Plugin Stability**: Plugin thread panics now propagate to main thread for proper error handling.
18
+ * **Review Diff Plugin**: Fixed side-by-side diff commands not appearing in command palette.
19
+
20
+ ---
21
+
3
22
  ## 0.1.86
4
23
 
5
24
  ### Features
package/README.md CHANGED
@@ -241,6 +241,10 @@ Thanks for contributing!
241
241
 
242
242
  7. **LSP**: Ensure LSP interactions follow the correct lifecycle (e.g., `didOpen` must always precede other requests to avoid server-side errors). Use the appropriate existing helpers for this pattern.
243
243
 
244
+ 8. **Regenerate plugin types**: After modifying the plugin API, run `cargo test -p fresh-plugin-runtime write_fresh_dts_file -- --ignored`
245
+
246
+ 9. **Type check plugins**: Run `crates/fresh-editor/plugins/check-types.sh` (requires `tsc`)
247
+
244
248
  **Tip**: You can use tmux + send-keys + render-pane to script ad-hoc tests on the UI, for example when trying to reproduce an issue.
245
249
 
246
250
  ## Privacy
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fresh-editor/fresh-editor",
3
- "version": "0.1.86",
3
+ "version": "0.1.87",
4
4
  "description": "A modern terminal-based text editor with plugin support",
5
5
  "repository": {
6
6
  "type": "git",
package/plugins/README.md CHANGED
@@ -69,3 +69,4 @@ For plugin development guides, see:
69
69
  - **API Reference:** [`docs/plugin-api.md`](../docs/plugin-api.md)
70
70
  - **Examples:** [`examples/README.md`](examples/README.md)
71
71
  - **Clangd Plugin:** [`clangd_support.md`](clangd_support.md)
72
+
@@ -551,7 +551,7 @@ async function renderReviewStream(): Promise<{ entries: TextPropertyEntry[], hig
551
551
  * Updates the buffer UI (text and highlights) based on current state.hunks
552
552
  */
553
553
  async function updateReviewUI() {
554
- if (state.reviewBufferId !== null) {
554
+ if (state.reviewBufferId != null) {
555
555
  const { entries, highlights } = await renderReviewStream();
556
556
  editor.setVirtualBufferContent(state.reviewBufferId, entries);
557
557
 
@@ -677,12 +677,12 @@ globalThis.on_viewport_changed = (data: any) => {
677
677
 
678
678
  const { oldSplitId, newSplitId, oldLineByteOffsets, newLineByteOffsets } = activeSideBySideState;
679
679
 
680
- if (data.split_id === oldSplitId && newLineByteOffsets.length > 0) {
680
+ if (data.splitId === oldSplitId && newLineByteOffsets.length > 0) {
681
681
  // OLD pane scrolled - find which line it's on and sync NEW pane to same line
682
682
  const lineNum = findLineForByte(oldLineByteOffsets, data.top_byte);
683
683
  const targetByte = newLineByteOffsets[Math.min(lineNum, newLineByteOffsets.length - 1)];
684
684
  (editor as any).setSplitScroll(newSplitId, targetByte);
685
- } else if (data.split_id === newSplitId && oldLineByteOffsets.length > 0) {
685
+ } else if (data.splitId === newSplitId && oldLineByteOffsets.length > 0) {
686
686
  // NEW pane scrolled - find which line it's on and sync OLD pane to same line
687
687
  const lineNum = findLineForByte(newLineByteOffsets, data.top_byte);
688
688
  const targetByte = oldLineByteOffsets[Math.min(lineNum, oldLineByteOffsets.length - 1)];
@@ -1100,7 +1100,7 @@ globalThis.review_drill_down = async () => {
1100
1100
  }));
1101
1101
 
1102
1102
  // Create source buffers (hidden from tabs, used by composite)
1103
- const oldBufferId = await editor.createVirtualBuffer({
1103
+ const oldResult = await editor.createVirtualBuffer({
1104
1104
  name: `*OLD:${h.file}*`,
1105
1105
  mode: "normal",
1106
1106
  readOnly: true,
@@ -1109,8 +1109,9 @@ globalThis.review_drill_down = async () => {
1109
1109
  editingDisabled: true,
1110
1110
  hiddenFromTabs: true
1111
1111
  });
1112
+ const oldBufferId = oldResult.bufferId;
1112
1113
 
1113
- const newBufferId = await editor.createVirtualBuffer({
1114
+ const newResult = await editor.createVirtualBuffer({
1114
1115
  name: `*NEW:${h.file}*`,
1115
1116
  mode: "normal",
1116
1117
  readOnly: true,
@@ -1119,6 +1120,7 @@ globalThis.review_drill_down = async () => {
1119
1120
  editingDisabled: true,
1120
1121
  hiddenFromTabs: true
1121
1122
  });
1123
+ const newBufferId = newResult.bufferId;
1122
1124
 
1123
1125
  // Convert hunks to composite buffer format (parse counts from git diff)
1124
1126
  const compositeHunks: TsCompositeHunk[] = fileHunks.map(fh => {
@@ -1130,10 +1132,10 @@ globalThis.review_drill_down = async () => {
1130
1132
  else if (line.startsWith(' ')) { oldCount++; newCount++; }
1131
1133
  }
1132
1134
  return {
1133
- old_start: fh.oldRange.start - 1, // Convert to 0-indexed
1134
- old_count: oldCount || 1,
1135
- new_start: fh.range.start - 1, // Convert to 0-indexed
1136
- new_count: newCount || 1
1135
+ oldStart: fh.oldRange.start - 1, // Convert to 0-indexed
1136
+ oldCount: oldCount || 1,
1137
+ newStart: fh.range.start - 1, // Convert to 0-indexed
1138
+ newCount: newCount || 1
1137
1139
  };
1138
1140
  });
1139
1141
 
@@ -1142,27 +1144,27 @@ globalThis.review_drill_down = async () => {
1142
1144
  name: `*Diff: ${h.file}*`,
1143
1145
  mode: "diff-view",
1144
1146
  layout: {
1145
- layout_type: "side-by-side",
1147
+ type: "side-by-side",
1146
1148
  ratios: [0.5, 0.5],
1147
- show_separator: true
1149
+ showSeparator: true
1148
1150
  },
1149
1151
  sources: [
1150
1152
  {
1151
- buffer_id: oldBufferId,
1153
+ bufferId: oldBufferId,
1152
1154
  label: "OLD (HEAD)",
1153
1155
  editable: false,
1154
1156
  style: {
1155
- remove_bg: [80, 40, 40],
1156
- gutter_style: "diff-markers"
1157
+ removeBg: [80, 40, 40],
1158
+ gutterStyle: "diff-markers"
1157
1159
  }
1158
1160
  },
1159
1161
  {
1160
- buffer_id: newBufferId,
1162
+ bufferId: newBufferId,
1161
1163
  label: "NEW (Working)",
1162
1164
  editable: false,
1163
1165
  style: {
1164
- add_bg: [40, 80, 40],
1165
- gutter_style: "diff-markers"
1166
+ addBg: [40, 80, 40],
1167
+ gutterStyle: "diff-markers"
1166
1168
  }
1167
1169
  }
1168
1170
  ],
@@ -1518,7 +1520,7 @@ globalThis.on_review_buffer_activated = (data: any) => {
1518
1520
  };
1519
1521
 
1520
1522
  globalThis.on_review_buffer_closed = (data: any) => {
1521
- if (data.buffer_id === state.reviewBufferId) stop_review_diff();
1523
+ if (data.buffer_id === state.reviewBufferId) globalThis.stop_review_diff();
1522
1524
  };
1523
1525
 
1524
1526
  // Side-by-side diff for current file using composite buffers
@@ -1663,7 +1665,7 @@ globalThis.side_by_side_diff_current_file = async () => {
1663
1665
  }));
1664
1666
 
1665
1667
  // Create source buffers (hidden from tabs, used by composite)
1666
- const oldBufferId = await editor.createVirtualBuffer({
1668
+ const oldResult = await editor.createVirtualBuffer({
1667
1669
  name: `*OLD:${filePath}*`,
1668
1670
  mode: "normal",
1669
1671
  readOnly: true,
@@ -1672,8 +1674,9 @@ globalThis.side_by_side_diff_current_file = async () => {
1672
1674
  editingDisabled: true,
1673
1675
  hiddenFromTabs: true
1674
1676
  });
1677
+ const oldBufferId = oldResult.bufferId;
1675
1678
 
1676
- const newBufferId = await editor.createVirtualBuffer({
1679
+ const newResult = await editor.createVirtualBuffer({
1677
1680
  name: `*NEW:${filePath}*`,
1678
1681
  mode: "normal",
1679
1682
  readOnly: true,
@@ -1682,13 +1685,14 @@ globalThis.side_by_side_diff_current_file = async () => {
1682
1685
  editingDisabled: true,
1683
1686
  hiddenFromTabs: true
1684
1687
  });
1688
+ const newBufferId = newResult.bufferId;
1685
1689
 
1686
1690
  // Convert hunks to composite buffer format
1687
1691
  const compositeHunks: TsCompositeHunk[] = fileHunks.map(h => ({
1688
- old_start: h.oldRange.start - 1, // Convert to 0-indexed
1689
- old_count: h.oldRange.end - h.oldRange.start + 1,
1690
- new_start: h.range.start - 1, // Convert to 0-indexed
1691
- new_count: h.range.end - h.range.start + 1
1692
+ oldStart: h.oldRange.start - 1, // Convert to 0-indexed
1693
+ oldCount: h.oldRange.end - h.oldRange.start + 1,
1694
+ newStart: h.range.start - 1, // Convert to 0-indexed
1695
+ newCount: h.range.end - h.range.start + 1
1692
1696
  }));
1693
1697
 
1694
1698
  // Create composite buffer with side-by-side layout
@@ -1696,27 +1700,27 @@ globalThis.side_by_side_diff_current_file = async () => {
1696
1700
  name: `*Diff: ${filePath}*`,
1697
1701
  mode: "diff-view",
1698
1702
  layout: {
1699
- layout_type: "side-by-side",
1703
+ type: "side-by-side",
1700
1704
  ratios: [0.5, 0.5],
1701
- show_separator: true
1705
+ showSeparator: true
1702
1706
  },
1703
1707
  sources: [
1704
1708
  {
1705
- buffer_id: oldBufferId,
1709
+ bufferId: oldBufferId,
1706
1710
  label: "OLD (HEAD)",
1707
1711
  editable: false,
1708
1712
  style: {
1709
- remove_bg: [80, 40, 40],
1710
- gutter_style: "diff-markers"
1713
+ removeBg: [80, 40, 40],
1714
+ gutterStyle: "diff-markers"
1711
1715
  }
1712
1716
  },
1713
1717
  {
1714
- buffer_id: newBufferId,
1718
+ bufferId: newBufferId,
1715
1719
  label: "NEW (Working)",
1716
1720
  editable: false,
1717
1721
  style: {
1718
- add_bg: [40, 80, 40],
1719
- gutter_style: "diff-markers"
1722
+ addBg: [40, 80, 40],
1723
+ gutterStyle: "diff-markers"
1720
1724
  }
1721
1725
  }
1722
1726
  ],
@@ -1746,10 +1750,10 @@ globalThis.side_by_side_diff_current_file = async () => {
1746
1750
  };
1747
1751
 
1748
1752
  // Register Modes and Commands
1749
- editor.registerCommand("%cmd.review_diff", "%cmd.review_diff_desc", "start_review_diff", "global");
1753
+ editor.registerCommand("%cmd.review_diff", "%cmd.review_diff_desc", "start_review_diff", null);
1750
1754
  editor.registerCommand("%cmd.stop_review_diff", "%cmd.stop_review_diff_desc", "stop_review_diff", "review-mode");
1751
1755
  editor.registerCommand("%cmd.refresh_review_diff", "%cmd.refresh_review_diff_desc", "review_refresh", "review-mode");
1752
- editor.registerCommand("%cmd.side_by_side_diff", "%cmd.side_by_side_diff_desc", "side_by_side_diff_current_file", "global");
1756
+ editor.registerCommand("%cmd.side_by_side_diff", "%cmd.side_by_side_diff_desc", "side_by_side_diff_current_file", null);
1753
1757
 
1754
1758
  // Review Comment Commands
1755
1759
  editor.registerCommand("%cmd.add_comment", "%cmd.add_comment_desc", "review_add_comment", "review-mode");
@@ -711,16 +711,16 @@ globalThis.calculator_open = async function (): Promise<void> {
711
711
 
712
712
  const entries = renderCalculator();
713
713
 
714
- state.bufferId = await editor.createVirtualBuffer({
714
+ const result = await editor.createVirtualBuffer({
715
715
  name: "*Calculator*",
716
716
  mode: "calculator",
717
- read_only: true,
717
+ readOnly: true,
718
718
  entries,
719
- show_line_numbers: false,
720
- show_cursors: false,
721
- editing_disabled: true,
719
+ showLineNumbers: false,
720
+ showCursors: false,
721
+ editingDisabled: true,
722
722
  });
723
-
723
+ state.bufferId = result.bufferId;
724
724
  state.splitId = editor.getActiveSplitId();
725
725
 
726
726
  editor.setStatus(editor.t("status.opened"));
@@ -97,12 +97,12 @@ globalThis.show_virtual_buffer_demo = async () => {
97
97
  const bufferId = await editor.createVirtualBufferInSplit({
98
98
  name: "*Demo Diagnostics*",
99
99
  mode: "demo-list",
100
- read_only: true,
100
+ readOnly: true,
101
101
  entries: entries,
102
102
  ratio: 0.7, // Original pane takes 70%, demo buffer takes 30%
103
- panel_id: "demo-diagnostics",
104
- show_line_numbers: false,
105
- show_cursors: true,
103
+ panelId: "demo-diagnostics",
104
+ showLineNumbers: false,
105
+ showCursors: true,
106
106
  });
107
107
 
108
108
  editor.setStatus(`Created demo virtual buffer (ID: ${bufferId}) with ${entries.length} items - Press RET to jump to location`);
@@ -478,7 +478,7 @@ globalThis.show_git_blame = async function(): Promise<void> {
478
478
  }
479
479
 
480
480
  // Create virtual buffer with the file content
481
- const bufferId = await editor.createVirtualBufferInExistingSplit({
481
+ const result = await editor.createVirtualBufferInExistingSplit({
482
482
  name: bufferName,
483
483
  mode: "git-blame",
484
484
  readOnly: true,
@@ -489,9 +489,9 @@ globalThis.show_git_blame = async function(): Promise<void> {
489
489
  editingDisabled: true,
490
490
  });
491
491
 
492
- if (bufferId !== null) {
492
+ if (result !== null) {
493
493
  blameState.isOpen = true;
494
- blameState.bufferId = bufferId;
494
+ blameState.bufferId = result.bufferId;
495
495
 
496
496
  // Add virtual lines for blame headers (persistent state model)
497
497
  addBlameHeaders();
@@ -1,4 +1,4 @@
1
- /// <reference path="../types/fresh.d.ts" />
1
+ /// <reference path="./lib/fresh.d.ts" />
2
2
  const editor = getEditor();
3
3
 
4
4
  /**
@@ -133,8 +133,9 @@ async function refreshGitExplorerDecorations() {
133
133
  } else {
134
134
  editor.setFileExplorerDecorations(NAMESPACE, decorations);
135
135
  }
136
- } catch (_err) {
136
+ } catch (err) {
137
137
  editor.clearFileExplorerDecorations(NAMESPACE);
138
+ throw err;
138
139
  } finally {
139
140
  refreshInFlight = false;
140
141
  }
@@ -763,7 +763,7 @@ globalThis.show_git_log = async function(): Promise<void> {
763
763
  gitLogState.cachedContent = entriesToContent(entries);
764
764
 
765
765
  // Create virtual buffer in the current split (replacing current buffer)
766
- const bufferId = await editor.createVirtualBufferInExistingSplit({
766
+ const result = await editor.createVirtualBufferInExistingSplit({
767
767
  name: "*Git Log*",
768
768
  mode: "git-log",
769
769
  readOnly: true,
@@ -774,9 +774,9 @@ globalThis.show_git_log = async function(): Promise<void> {
774
774
  editingDisabled: true,
775
775
  });
776
776
 
777
- if (bufferId !== null) {
777
+ if (result !== null) {
778
778
  gitLogState.isOpen = true;
779
- gitLogState.bufferId = bufferId;
779
+ gitLogState.bufferId = result.bufferId;
780
780
 
781
781
  // Apply syntax highlighting
782
782
  applyGitLogHighlighting();
@@ -897,7 +897,7 @@ globalThis.git_log_show_commit = async function(): Promise<void> {
897
897
  commitDetailState.cachedContent = entriesToContent(entries);
898
898
 
899
899
  // Create virtual buffer in the current split (replacing git log view)
900
- const bufferId = await editor.createVirtualBufferInExistingSplit({
900
+ const result = await editor.createVirtualBufferInExistingSplit({
901
901
  name: `*Commit: ${commit.shortHash}*`,
902
902
  mode: "git-commit-detail",
903
903
  readOnly: true,
@@ -908,9 +908,9 @@ globalThis.git_log_show_commit = async function(): Promise<void> {
908
908
  editingDisabled: true,
909
909
  });
910
910
 
911
- if (bufferId !== null) {
911
+ if (result !== null) {
912
912
  commitDetailState.isOpen = true;
913
- commitDetailState.bufferId = bufferId;
913
+ commitDetailState.bufferId = result.bufferId;
914
914
  commitDetailState.splitId = gitLogState.splitId;
915
915
  commitDetailState.commit = commit;
916
916
 
@@ -1210,7 +1210,7 @@ globalThis.git_commit_detail_open_file = async function(): Promise<void> {
1210
1210
  }
1211
1211
 
1212
1212
  // Create a read-only virtual buffer with the file content
1213
- const bufferId = await editor.createVirtualBufferInExistingSplit({
1213
+ const result = await editor.createVirtualBufferInExistingSplit({
1214
1214
  name: `${file} @ ${commit.shortHash}`,
1215
1215
  mode: "git-file-view",
1216
1216
  readOnly: true,
@@ -1221,16 +1221,16 @@ globalThis.git_commit_detail_open_file = async function(): Promise<void> {
1221
1221
  editingDisabled: true,
1222
1222
  });
1223
1223
 
1224
- if (bufferId !== null) {
1224
+ if (result !== null) {
1225
1225
  // Track file view state so we can navigate back
1226
1226
  fileViewState.isOpen = true;
1227
- fileViewState.bufferId = bufferId;
1227
+ fileViewState.bufferId = result.bufferId;
1228
1228
  fileViewState.splitId = commitDetailState.splitId;
1229
1229
  fileViewState.filePath = file;
1230
1230
  fileViewState.commitHash = commit.hash;
1231
1231
 
1232
1232
  // Apply syntax highlighting based on file type
1233
- applyFileViewHighlighting(bufferId, content, file);
1233
+ applyFileViewHighlighting(result.bufferId, content, file);
1234
1234
 
1235
1235
  const targetLine = line || 1;
1236
1236
  editor.setStatus(editor.t("status.file_view_ready", { file, hash: commit.shortHash, line: String(targetLine) }));
@@ -0,0 +1,65 @@
1
+ /// <reference path="./lib/fresh.d.ts" />
2
+ // Provides installation help when jdtls (Java LSP) is not found
3
+ const editor = getEditor();
4
+
5
+ interface LspServerErrorData {
6
+ language: string;
7
+ server_command: string;
8
+ error_type: string;
9
+ message: string;
10
+ }
11
+
12
+ interface LspStatusClickedData {
13
+ language: string;
14
+ has_error: boolean;
15
+ }
16
+
17
+ interface ActionPopupResultData {
18
+ popup_id: string;
19
+ action_id: string;
20
+ }
21
+
22
+ const INSTALL_URL = "https://github.com/eclipse-jdtls/eclipse.jdt.ls#installation";
23
+ let javaLspError: { serverCommand: string; message: string } | null = null;
24
+
25
+ globalThis.on_java_lsp_server_error = function (data: LspServerErrorData): void {
26
+ if (data.language !== "java") return;
27
+ javaLspError = { serverCommand: data.server_command, message: data.message };
28
+ if (data.error_type === "not_found") {
29
+ editor.setStatus(`Java LSP '${data.server_command}' not found. Click status bar for help.`);
30
+ } else {
31
+ editor.setStatus(`Java LSP error: ${data.message}`);
32
+ }
33
+ };
34
+ editor.on("lsp_server_error", "on_java_lsp_server_error");
35
+
36
+ globalThis.on_java_lsp_status_clicked = function (data: LspStatusClickedData): void {
37
+ if (data.language !== "java" || !javaLspError) return;
38
+ editor.showActionPopup({
39
+ id: "java-lsp-help",
40
+ title: "Java Language Server Not Found",
41
+ message: `Install jdtls for code completion and diagnostics. Visit ${INSTALL_URL}`,
42
+ actions: [
43
+ { id: "copy_url", label: "Copy install URL" },
44
+ { id: "disable", label: "Disable Java LSP" },
45
+ { id: "dismiss", label: "Dismiss (ESC)" },
46
+ ],
47
+ });
48
+ };
49
+ editor.on("lsp_status_clicked", "on_java_lsp_status_clicked");
50
+
51
+ globalThis.on_java_lsp_action_result = function (data: ActionPopupResultData): void {
52
+ if (data.popup_id !== "java-lsp-help") return;
53
+ switch (data.action_id) {
54
+ case "copy_url":
55
+ editor.setClipboard(INSTALL_URL);
56
+ editor.setStatus("Copied: " + INSTALL_URL);
57
+ break;
58
+ case "disable":
59
+ editor.disableLspForLanguage("java");
60
+ editor.setStatus("Java LSP disabled");
61
+ javaLspError = null;
62
+ break;
63
+ }
64
+ };
65
+ editor.on("action_popup_result", "on_java_lsp_action_result");
@@ -0,0 +1,65 @@
1
+ /// <reference path="./lib/fresh.d.ts" />
2
+ // Provides installation help when texlab (LaTeX LSP) is not found
3
+ const editor = getEditor();
4
+
5
+ interface LspServerErrorData {
6
+ language: string;
7
+ server_command: string;
8
+ error_type: string;
9
+ message: string;
10
+ }
11
+
12
+ interface LspStatusClickedData {
13
+ language: string;
14
+ has_error: boolean;
15
+ }
16
+
17
+ interface ActionPopupResultData {
18
+ popup_id: string;
19
+ action_id: string;
20
+ }
21
+
22
+ const INSTALL_URL = "https://github.com/latex-lsp/texlab#installation";
23
+ let latexLspError: { serverCommand: string; message: string } | null = null;
24
+
25
+ globalThis.on_latex_lsp_server_error = function (data: LspServerErrorData): void {
26
+ if (data.language !== "latex") return;
27
+ latexLspError = { serverCommand: data.server_command, message: data.message };
28
+ if (data.error_type === "not_found") {
29
+ editor.setStatus(`LaTeX LSP '${data.server_command}' not found. Click status bar for help.`);
30
+ } else {
31
+ editor.setStatus(`LaTeX LSP error: ${data.message}`);
32
+ }
33
+ };
34
+ editor.on("lsp_server_error", "on_latex_lsp_server_error");
35
+
36
+ globalThis.on_latex_lsp_status_clicked = function (data: LspStatusClickedData): void {
37
+ if (data.language !== "latex" || !latexLspError) return;
38
+ editor.showActionPopup({
39
+ id: "latex-lsp-help",
40
+ title: "LaTeX Language Server Not Found",
41
+ message: `Install texlab for code completion and diagnostics. Visit ${INSTALL_URL}`,
42
+ actions: [
43
+ { id: "copy_url", label: "Copy install URL" },
44
+ { id: "disable", label: "Disable LaTeX LSP" },
45
+ { id: "dismiss", label: "Dismiss (ESC)" },
46
+ ],
47
+ });
48
+ };
49
+ editor.on("lsp_status_clicked", "on_latex_lsp_status_clicked");
50
+
51
+ globalThis.on_latex_lsp_action_result = function (data: ActionPopupResultData): void {
52
+ if (data.popup_id !== "latex-lsp-help") return;
53
+ switch (data.action_id) {
54
+ case "copy_url":
55
+ editor.setClipboard(INSTALL_URL);
56
+ editor.setStatus("Copied: " + INSTALL_URL);
57
+ break;
58
+ case "disable":
59
+ editor.disableLspForLanguage("latex");
60
+ editor.setStatus("LaTeX LSP disabled");
61
+ latexLspError = null;
62
+ break;
63
+ }
64
+ };
65
+ editor.on("action_popup_result", "on_latex_lsp_action_result");