@fresh-editor/fresh-editor 0.3.5 → 0.3.6

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/plugins/pkg.ts CHANGED
@@ -1,5 +1,13 @@
1
1
  /// <reference path="./lib/fresh.d.ts" />
2
2
 
3
+ import {
4
+ hintBar,
5
+ key,
6
+ list,
7
+ parseHintString,
8
+ WidgetPanel,
9
+ } from "./lib/index.ts";
10
+
3
11
  /**
4
12
  * Fresh Package Manager Plugin
5
13
  *
@@ -1658,6 +1666,16 @@ interface PkgManagerState {
1658
1666
  // Buffer group fields
1659
1667
  groupId: number | null;
1660
1668
  panelBuffers: Record<string, number>;
1669
+ /** Widget panels owned by the pkg manager. The list panel hosts a
1670
+ * `List` widget (one row per package) so the host owns selection
1671
+ * highlight, scroll, click-to-select, mouse wheel, and PageUp/Down.
1672
+ * The footer panel hosts a `HintBar` widget (the Navigate / Tab /
1673
+ * Search / Esc cheat-sheet). Header and detail panels still use
1674
+ * `setPanelContent` since their UI shape spans cross-panel focus
1675
+ * (filter / sync / search / action buttons) which is a larger
1676
+ * redesign. */
1677
+ listPanel: WidgetPanel | null;
1678
+ footerPanel: WidgetPanel | null;
1661
1679
  }
1662
1680
 
1663
1681
  const pkgState: PkgManagerState = {
@@ -1674,6 +1692,8 @@ const pkgState: PkgManagerState = {
1674
1692
  viewportHeight: 24,
1675
1693
  groupId: null,
1676
1694
  panelBuffers: {},
1695
+ listPanel: null,
1696
+ footerPanel: null,
1677
1697
  };
1678
1698
 
1679
1699
  // Theme-aware color configuration
@@ -2040,53 +2060,163 @@ function buildPkgHeaderEntries(): TextPropertyEntry[] {
2040
2060
  return entries;
2041
2061
  }
2042
2062
 
2043
- function buildPkgListEntries(): TextPropertyEntry[] {
2063
+ /** Build the per-row item entries for the package List widget.
2064
+ * The list iterates "installed first, then available" to match the
2065
+ * existing buffer-render order, so the row's index in this array
2066
+ * matches `pkgState.selectedIndex`. The leading `▸` arrow that the
2067
+ * old hand-rolled renderer used on the focused row is gone — the
2068
+ * List widget paints the selection highlight via `ui.menu_active_bg`
2069
+ * which carries the same "this row is selected" signal. Section
2070
+ * headers (`INSTALLED (n)` / `AVAILABLE (n)`) are kept as
2071
+ * non-selectable rows; selecting them would be a no-op so the list
2072
+ * navigation skips them implicitly via the index math. */
2073
+ interface PkgListRow {
2074
+ entry: TextPropertyEntry;
2075
+ /** Stable widget-key for the List item. Empty string for non-
2076
+ * selectable header rows (the List still tracks them in
2077
+ * itemKeys but they don't carry useful identity). */
2078
+ key: string;
2079
+ /** Index within `getFilteredItems()` if this row is a real
2080
+ * package, else -1. The widget's `selectedIndex` is reported
2081
+ * back via `widget_event "select"` and we use it to look up
2082
+ * the matching package via this map. */
2083
+ itemIndex: number;
2084
+ }
2085
+
2086
+ function buildPkgListRows(): PkgListRow[] {
2044
2087
  const items = getFilteredItems();
2045
- const installedItems = items.filter(i => i.installed);
2046
- const availableItems = items.filter(i => !i.installed);
2047
- const entries: TextPropertyEntry[] = [];
2088
+ const installedItems = items.filter((i) => i.installed);
2089
+ const availableItems = items.filter((i) => !i.installed);
2090
+ const rows: PkgListRow[] = [];
2048
2091
 
2049
2092
  if (installedItems.length > 0) {
2050
- entries.push({ text: `INSTALLED (${installedItems.length})\n`, properties: { type: "section-title" } });
2093
+ rows.push({
2094
+ entry: {
2095
+ text: `INSTALLED (${installedItems.length})`,
2096
+ properties: { type: "section-title" },
2097
+ },
2098
+ key: "section.installed",
2099
+ itemIndex: -1,
2100
+ });
2051
2101
  let idx = 0;
2052
2102
  for (const item of installedItems) {
2053
- const isSelected = idx === pkgState.selectedIndex;
2054
- const listFocused = pkgState.focus.type === "list";
2055
- const prefix = isSelected && listFocused ? "▸" : " ";
2056
2103
  const status = item.updateAvailable ? "↑" : "✓";
2057
- const ver = item.version.length > 7 ? item.version.slice(0, 6) + "…" : item.version;
2104
+ const ver =
2105
+ item.version.length > 7 ? item.version.slice(0, 6) + "…" : item.version;
2058
2106
  const nameW = Math.max(8, LIST_WIDTH - 16);
2059
- const name = item.name.length > nameW ? item.name.slice(0, nameW - 1) + "…" : item.name;
2060
- entries.push({ text: `${prefix} ${name.padEnd(nameW)} ${ver.padEnd(7)} ${status}\n`, properties: { type: "package-row", selected: isSelected, installed: true } });
2107
+ const name =
2108
+ item.name.length > nameW ? item.name.slice(0, nameW - 1) + "" : item.name;
2109
+ rows.push({
2110
+ entry: {
2111
+ text: ` ${name.padEnd(nameW)} ${ver.padEnd(7)} ${status}`,
2112
+ properties: { type: "package-row", installed: true },
2113
+ },
2114
+ key: `pkg.${item.name}`,
2115
+ itemIndex: idx,
2116
+ });
2061
2117
  idx++;
2062
2118
  }
2063
2119
  }
2064
2120
 
2065
2121
  if (availableItems.length > 0) {
2066
- if (entries.length > 0) entries.push({ text: "\n", properties: { type: "blank" } });
2067
- entries.push({ text: `AVAILABLE (${availableItems.length})\n`, properties: { type: "section-title" } });
2122
+ if (rows.length > 0) {
2123
+ rows.push({
2124
+ entry: { text: "", properties: { type: "blank" } },
2125
+ key: "blank",
2126
+ itemIndex: -1,
2127
+ });
2128
+ }
2129
+ rows.push({
2130
+ entry: {
2131
+ text: `AVAILABLE (${availableItems.length})`,
2132
+ properties: { type: "section-title" },
2133
+ },
2134
+ key: "section.available",
2135
+ itemIndex: -1,
2136
+ });
2068
2137
  let idx = installedItems.length;
2069
2138
  for (const item of availableItems) {
2070
- const isSelected = idx === pkgState.selectedIndex;
2071
- const listFocused = pkgState.focus.type === "list";
2072
- const prefix = isSelected && listFocused ? "" : " ";
2073
- const typeTag = item.packageType === "theme" ? "T" : item.packageType === "language" ? "L" : item.packageType === "bundle" ? "B" : "P";
2139
+ const typeTag =
2140
+ item.packageType === "theme"
2141
+ ? "T"
2142
+ : item.packageType === "language"
2143
+ ? "L"
2144
+ : item.packageType === "bundle"
2145
+ ? "B"
2146
+ : "P";
2074
2147
  const availNameW = Math.max(8, LIST_WIDTH - 10);
2075
- const name = item.name.length > availNameW ? item.name.slice(0, availNameW - 1) + "…" : item.name;
2076
- entries.push({ text: `${prefix} ${name.padEnd(availNameW)} [${typeTag}]\n`, properties: { type: "package-row", selected: isSelected, installed: false } });
2148
+ const name =
2149
+ item.name.length > availNameW
2150
+ ? item.name.slice(0, availNameW - 1) + "…"
2151
+ : item.name;
2152
+ rows.push({
2153
+ entry: {
2154
+ text: ` ${name.padEnd(availNameW)} [${typeTag}]`,
2155
+ properties: { type: "package-row", installed: false },
2156
+ },
2157
+ key: `pkg.${item.name}`,
2158
+ itemIndex: idx,
2159
+ });
2077
2160
  idx++;
2078
2161
  }
2079
2162
  }
2080
2163
 
2081
2164
  if (items.length === 0) {
2082
- if (pkgState.isLoading) {
2083
- entries.push({ text: "Loading...\n", properties: { type: "empty-state" } });
2084
- } else {
2085
- entries.push({ text: "No packages found\n", properties: { type: "empty-state" } });
2086
- }
2165
+ rows.push({
2166
+ entry: {
2167
+ text: pkgState.isLoading ? "Loading..." : "No packages found",
2168
+ properties: { type: "empty-state" },
2169
+ },
2170
+ key: "empty",
2171
+ itemIndex: -1,
2172
+ });
2087
2173
  }
2088
2174
 
2089
- return entries;
2175
+ return rows;
2176
+ }
2177
+
2178
+ /** Cache the row→item-index lookup so `widget_event "select"` can
2179
+ * map the host's reported selection index back to a real package
2180
+ * index in O(1) without rebuilding the row array. Refreshed by
2181
+ * `renderPkgList` every time the list is re-emitted. */
2182
+ let pkgListRowCache: PkgListRow[] = [];
2183
+
2184
+ function selectedRowToItemIndex(rowIndex: number): number {
2185
+ const row = pkgListRowCache[rowIndex];
2186
+ return row ? row.itemIndex : -1;
2187
+ }
2188
+
2189
+ function itemIndexToRow(itemIndex: number): number {
2190
+ for (let i = 0; i < pkgListRowCache.length; i++) {
2191
+ if (pkgListRowCache[i].itemIndex === itemIndex) return i;
2192
+ }
2193
+ return -1;
2194
+ }
2195
+
2196
+ function renderPkgList(): void {
2197
+ if (pkgState.listPanel === null) return;
2198
+ const rows = buildPkgListRows();
2199
+ pkgListRowCache = rows;
2200
+ // Map the plugin's item-index selection to the row-index the List
2201
+ // widget understands. -1 falls back to the first selectable row.
2202
+ let rowSel = itemIndexToRow(pkgState.selectedIndex);
2203
+ if (rowSel < 0) {
2204
+ for (let i = 0; i < rows.length; i++) {
2205
+ if (rows[i].itemIndex >= 0) {
2206
+ rowSel = i;
2207
+ break;
2208
+ }
2209
+ }
2210
+ }
2211
+ pkgState.listPanel.set(
2212
+ list({
2213
+ items: rows.map((r) => r.entry),
2214
+ itemKeys: rows.map((r) => r.key),
2215
+ selectedIndex: rowSel,
2216
+ visibleRows: Math.max(1, rows.length),
2217
+ key: "pkg-list",
2218
+ }),
2219
+ );
2090
2220
  }
2091
2221
 
2092
2222
  function buildPkgDetailEntries(): TextPropertyEntry[] {
@@ -2133,23 +2263,33 @@ function buildPkgDetailEntries(): TextPropertyEntry[] {
2133
2263
  return entries;
2134
2264
  }
2135
2265
 
2136
- function buildPkgFooterEntries(): TextPropertyEntry[] {
2137
- let helpText = " ↑↓ Navigate Tab Next / Search Enter ";
2138
- if (pkgState.focus.type === "action") helpText += "Activate";
2139
- else if (pkgState.focus.type === "filter") helpText += "Filter";
2140
- else if (pkgState.focus.type === "sync") helpText += "Sync";
2141
- else if (pkgState.focus.type === "search") helpText += "Search";
2142
- else helpText += "Select";
2143
- helpText += " Esc Close";
2144
- return [{ text: helpText + "\n", properties: { type: "help" } }];
2266
+ function renderPkgFooter(): void {
2267
+ if (pkgState.footerPanel === null) return;
2268
+ let actionLabel = "Select";
2269
+ if (pkgState.focus.type === "action") actionLabel = "Activate";
2270
+ else if (pkgState.focus.type === "filter") actionLabel = "Filter";
2271
+ else if (pkgState.focus.type === "sync") actionLabel = "Sync";
2272
+ else if (pkgState.focus.type === "search") actionLabel = "Search";
2273
+ // The hint string follows the existing `parseHintString` shape:
2274
+ // tokens separated by two spaces, each token is `<keys>:<label>`
2275
+ // or `<keys> <label>`. The host's HintBar styles the keys portion
2276
+ // with `ui.help_key_fg`.
2277
+ const hintString = `↑↓:Navigate Tab:Next /:Search Enter:${actionLabel} Esc:Close`;
2278
+ pkgState.footerPanel.set(hintBar(parseHintString(hintString)));
2145
2279
  }
2146
2280
 
2147
2281
  function updatePkgManagerView(): void {
2148
2282
  if (pkgState.groupId === null) return;
2283
+ // Header + detail still flow through the legacy `setPanelContent`
2284
+ // path — their UI shape (filter/sync/search inputs in the header,
2285
+ // action buttons in the detail) spans cross-panel focus, which
2286
+ // is a larger redesign best done together with the §4.1
2287
+ // Compositor work. List and footer migrate independently because
2288
+ // they don't participate in cross-panel focus.
2149
2289
  editor.setPanelContent(pkgState.groupId, "header", buildPkgHeaderEntries());
2150
- editor.setPanelContent(pkgState.groupId, "list", buildPkgListEntries());
2290
+ renderPkgList();
2151
2291
  editor.setPanelContent(pkgState.groupId, "detail", buildPkgDetailEntries());
2152
- editor.setPanelContent(pkgState.groupId, "footer", buildPkgFooterEntries());
2292
+ renderPkgFooter();
2153
2293
  }
2154
2294
 
2155
2295
  /**
@@ -2206,6 +2346,17 @@ async function openPackageManager(): Promise<void> {
2206
2346
  pkgState.panelBuffers = groupResult.panels;
2207
2347
  pkgState.isOpen = true;
2208
2348
 
2349
+ // Mount widget panels for the list and footer. Header / detail
2350
+ // still use `setPanelContent` (see `updatePkgManagerView`).
2351
+ const listBufferId = pkgState.panelBuffers["list"];
2352
+ if (typeof listBufferId === "number") {
2353
+ pkgState.listPanel = new WidgetPanel(listBufferId);
2354
+ }
2355
+ const footerBufferId = pkgState.panelBuffers["footer"];
2356
+ if (typeof footerBufferId === "number") {
2357
+ pkgState.footerPanel = new WidgetPanel(footerBufferId);
2358
+ }
2359
+
2209
2360
  // Set initial content for all panels
2210
2361
  updatePkgManagerView();
2211
2362
 
@@ -2239,11 +2390,17 @@ function closePackageManager(): void {
2239
2390
  editor.showBuffer(pkgState.sourceBufferId);
2240
2391
  }
2241
2392
 
2242
- // Reset state
2393
+ // Reset state. The buffer group's close will tear down the panel
2394
+ // buffers and (implicitly) the widget panels rendering into them;
2395
+ // we just null the handles so any stray render call after close
2396
+ // is a no-op.
2243
2397
  pkgState.isOpen = false;
2244
2398
  pkgState.bufferId = null;
2245
2399
  pkgState.splitId = null;
2246
2400
  pkgState.sourceBufferId = null;
2401
+ pkgState.listPanel = null;
2402
+ pkgState.footerPanel = null;
2403
+ pkgListRowCache = [];
2247
2404
  }
2248
2405
 
2249
2406
  /**
@@ -2290,32 +2447,56 @@ function getCurrentFocusIndex(): number {
2290
2447
  }
2291
2448
 
2292
2449
  // Navigation commands
2293
- function pkg_nav_up() : void {
2450
+ function pkg_nav_up(): void {
2294
2451
  if (!pkgState.isOpen) return;
2295
-
2296
- const items = getFilteredItems();
2297
- if (items.length === 0) return;
2298
-
2299
- // Always focus list and navigate (auto-focus behavior)
2300
- pkgState.selectedIndex = Math.max(0, pkgState.selectedIndex - 1);
2452
+ // Always snap focus back to the list and let the host move the
2453
+ // List widget's selection. The resulting `widget_event "select"`
2454
+ // updates `pkgState.selectedIndex` and refreshes the detail
2455
+ // panel — see the `widget_event` listener installed below.
2301
2456
  pkgState.focus = { type: "list" };
2302
- updatePkgManagerView();
2457
+ pkgState.listPanel?.command(key("Up"));
2303
2458
  }
2304
2459
  registerHandler("pkg_nav_up", pkg_nav_up);
2305
2460
 
2306
- function pkg_nav_down() : void {
2461
+ function pkg_nav_down(): void {
2307
2462
  if (!pkgState.isOpen) return;
2308
-
2309
- const items = getFilteredItems();
2310
- if (items.length === 0) return;
2311
-
2312
- // Always focus list and navigate (auto-focus behavior)
2313
- pkgState.selectedIndex = Math.min(items.length - 1, pkgState.selectedIndex + 1);
2314
2463
  pkgState.focus = { type: "list" };
2315
- updatePkgManagerView();
2464
+ pkgState.listPanel?.command(key("Down"));
2316
2465
  }
2317
2466
  registerHandler("pkg_nav_down", pkg_nav_down);
2318
2467
 
2468
+ editor.on("widget_event", (data) => {
2469
+ if (
2470
+ pkgState.listPanel === null ||
2471
+ data.panel_id !== pkgState.listPanel.id()
2472
+ ) {
2473
+ return;
2474
+ }
2475
+ if (data.event_type === "select") {
2476
+ const rowIdx =
2477
+ typeof data.payload?.index === "number" ? data.payload.index : -1;
2478
+ if (rowIdx < 0) return;
2479
+ const itemIdx = selectedRowToItemIndex(rowIdx);
2480
+ if (itemIdx < 0) return; // selection landed on a section header
2481
+ if (itemIdx === pkgState.selectedIndex) return;
2482
+ pkgState.selectedIndex = itemIdx;
2483
+ pkgState.focus = { type: "list" };
2484
+ // Re-render header/detail to reflect the new selection;
2485
+ // the list itself is already updated by the host (we don't
2486
+ // need to call renderPkgList again).
2487
+ if (pkgState.groupId !== null) {
2488
+ editor.setPanelContent(pkgState.groupId, "header", buildPkgHeaderEntries());
2489
+ editor.setPanelContent(pkgState.groupId, "detail", buildPkgDetailEntries());
2490
+ }
2491
+ renderPkgFooter();
2492
+ return;
2493
+ }
2494
+ if (data.event_type === "activate") {
2495
+ void pkg_activate();
2496
+ return;
2497
+ }
2498
+ });
2499
+
2319
2500
  function pkg_next_button() : void {
2320
2501
  if (!pkgState.isOpen) return;
2321
2502
 
@@ -42,9 +42,38 @@ const INSTALL_COMMANDS = {
42
42
  brew: "brew install rust-analyzer",
43
43
  };
44
44
 
45
- // Track error state for Rust LSP
45
+ // Stable plugin id used as the namespace for our menu contributions
46
+ // and as the prefix on every `action_popup_result.action_id` we
47
+ // receive back from the editor.
48
+ const PLUGIN_ID = "rust-lsp";
49
+
50
+ // Track error state for Rust LSP so the menu contributions can be
51
+ // installed (when there's an error) or cleared (after recovery).
46
52
  let rustLspError: { serverCommand: string; message: string } | null = null;
47
53
 
54
+ /**
55
+ * Install the "fix-it" rows into the LSP-Servers popup for `rust`.
56
+ * Mirrors the previous `showActionPopup` payload: copy-install
57
+ * commands and a disable shortcut. Re-call with an empty array to
58
+ * clear our slice.
59
+ *
60
+ * Implements the merge half of #1941 follow-up "Option B": we no
61
+ * longer push our own separate popup; instead the editor's built-in
62
+ * LSP-Servers popup includes our rows under a "Plugin actions"
63
+ * section.
64
+ */
65
+ function publishMenuContributions(): void {
66
+ if (rustLspError === null) {
67
+ editor.setLspMenuContributions(PLUGIN_ID, "rust", []);
68
+ return;
69
+ }
70
+ editor.setLspMenuContributions(PLUGIN_ID, "rust", [
71
+ { id: "copy_rustup", label: `Copy: ${INSTALL_COMMANDS.rustup}` },
72
+ { id: "copy_brew", label: `Copy: ${INSTALL_COMMANDS.brew}` },
73
+ { id: "disable", label: "Disable Rust LSP" },
74
+ ]);
75
+ }
76
+
48
77
  /**
49
78
  * Handle LSP server errors for Rust
50
79
  */
@@ -59,16 +88,18 @@ editor.on("lsp_server_error", (data) => {
59
88
 
60
89
  editor.debug(`rust-lsp: Server error - ${data.error_type}: ${data.message}`);
61
90
 
62
- // Store error state for later reference
91
+ // Store error state for later reference, install fix-it rows into
92
+ // the LSP-Servers popup.
63
93
  rustLspError = {
64
94
  serverCommand: data.server_command,
65
95
  message: data.message,
66
96
  };
97
+ publishMenuContributions();
67
98
 
68
99
  // Show a status message for immediate feedback
69
100
  if (data.error_type === "not_found") {
70
101
  editor.setStatus(
71
- `Rust LSP server '${data.server_command}' not found. Click status bar for help.`
102
+ `Rust LSP server '${data.server_command}' not found. Click the LSP indicator for help.`
72
103
  );
73
104
  } else {
74
105
  editor.setStatus(`Rust LSP error: ${data.message}`);
@@ -76,39 +107,28 @@ editor.on("lsp_server_error", (data) => {
76
107
  });
77
108
 
78
109
  /**
79
- * Handle status bar click when there's a Rust LSP error
110
+ * Detect recovery and clear stale fix-it rows
80
111
  */
81
112
 
82
113
 
83
- // Register hook for status bar clicks
114
+ // Register hook for status bar clicks — used here ONLY to detect
115
+ // LSP recovery and clear stale contributions. The actual "fix-it"
116
+ // popup is the editor's built-in LSP-Servers popup with our
117
+ // contributed rows merged in (no more separate popup).
84
118
  editor.on("lsp_status_clicked", (data) => {
85
- editor.debug(
86
- `rust-lsp: lsp_status_clicked hook received - language=${data.language}, has_error=${data.has_error}, rustLspError=${rustLspError ? "SET" : "NULL"}`
87
- );
88
-
89
- // Only handle Rust language clicks when there's an error
90
- if (data.language !== "rust" || !rustLspError) {
91
- editor.debug(
92
- `rust-lsp: Skipping - language check=${data.language !== "rust"}, error check=${!rustLspError}`
93
- );
119
+ if (data.language !== "rust") {
94
120
  return;
95
121
  }
96
122
 
97
- editor.debug("rust-lsp: Status clicked, showing help popup");
98
-
99
- // Show action popup with install options
100
- const result = editor.showActionPopup({
101
- id: "rust-lsp-help",
102
- title: "Rust Language Server Not Found",
103
- message: `"${rustLspError.serverCommand}" provides code completion, diagnostics, and navigation for Rust files. Copy a command below to install it, or search online for your platform.`,
104
- actions: [
105
- { id: "copy_rustup", label: `Copy: ${INSTALL_COMMANDS.rustup}` },
106
- { id: "copy_brew", label: `Copy: ${INSTALL_COMMANDS.brew}` },
107
- { id: "disable", label: "Disable Rust LSP" },
108
- { id: "dismiss", label: "Dismiss (ESC)" },
109
- ],
110
- });
111
- editor.debug(`rust-lsp: showActionPopup returned ${result}`);
123
+ // Recovery: editor now reports no error for rust LSP came back
124
+ // up (e.g. successful auto-restart after an external kill). Clear
125
+ // our error state and remove the fix-it rows from the popup so
126
+ // the user just sees the standard server actions. (#1941 issue 3)
127
+ if (!data.has_error && rustLspError !== null) {
128
+ editor.debug("rust-lsp: LSP recovered; clearing rustLspError + menu rows");
129
+ rustLspError = null;
130
+ publishMenuContributions();
131
+ }
112
132
  });
113
133
 
114
134
  /**
@@ -122,15 +142,17 @@ editor.on("action_popup_result", (data) => {
122
142
  `rust-lsp: action_popup_result received - popup_id=${data.popup_id}, action_id=${data.action_id}`
123
143
  );
124
144
 
125
- // Only handle our popup
126
- if (data.popup_id !== "rust-lsp-help") {
127
- editor.debug("rust-lsp: Not our popup, skipping");
145
+ // The editor routes contributed-row picks with `popup_id =
146
+ // "lsp_status"` and `action_id = "{plugin_id}|{item_id}"`.
147
+ const prefix = `${PLUGIN_ID}|`;
148
+ if (data.popup_id !== "lsp_status" || !data.action_id.startsWith(prefix)) {
128
149
  return;
129
150
  }
151
+ const itemId = data.action_id.slice(prefix.length);
130
152
 
131
- editor.debug(`rust-lsp: Action selected - ${data.action_id}, rustLspError will remain SET`);
153
+ editor.debug(`rust-lsp: Action selected - ${itemId}`);
132
154
 
133
- switch (data.action_id) {
155
+ switch (itemId) {
134
156
  case "copy_rustup":
135
157
  editor.setClipboard(INSTALL_COMMANDS.rustup);
136
158
  editor.setStatus("Copied: " + INSTALL_COMMANDS.rustup);
@@ -145,15 +167,11 @@ editor.on("action_popup_result", (data) => {
145
167
  editor.disableLspForLanguage("rust");
146
168
  editor.setStatus("Rust LSP disabled");
147
169
  rustLspError = null;
148
- break;
149
-
150
- case "dismiss":
151
- case "dismissed":
152
- // Just close the popup without action
170
+ publishMenuContributions();
153
171
  break;
154
172
 
155
173
  default:
156
- editor.debug(`rust-lsp: Unknown action: ${data.action_id}`);
174
+ editor.debug(`rust-lsp: Unknown action: ${itemId}`);
157
175
  }
158
176
  });
159
177
 
@@ -40,6 +40,7 @@
40
40
  79,
41
41
  120
42
42
  ],
43
+ "selection_modifier": null,
43
44
  "current_line_bg": [
44
45
  40,
45
46
  40,
@@ -180,6 +181,8 @@
180
181
  "status_palette_bg": null,
181
182
  "status_lsp_on_fg": null,
182
183
  "status_lsp_on_bg": null,
184
+ "status_lsp_actionable_fg": null,
185
+ "status_lsp_actionable_bg": null,
183
186
  "prompt_fg": "White",
184
187
  "prompt_bg": "Black",
185
188
  "prompt_selection_fg": "White",
@@ -246,6 +249,7 @@
246
249
  60,
247
250
  80
248
251
  ],
252
+ "semantic_highlight_modifier": null,
249
253
  "terminal_bg": "Default",
250
254
  "terminal_fg": "Default",
251
255
  "status_warning_indicator_bg": [