@mrclrchtr/supi-lsp 1.16.1 → 2.0.1

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 (73) hide show
  1. package/README.md +44 -56
  2. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +6 -1
  3. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/abort-utils.ts +31 -0
  4. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/api.ts +8 -0
  5. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +10 -4
  6. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config.ts +1 -0
  7. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +1 -1
  8. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/index.ts +6 -0
  9. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/model-selection.ts +134 -0
  10. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +9 -2
  11. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +109 -4
  12. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +5 -1
  13. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/spinner-frames.ts +11 -0
  14. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/status-spinner.ts +68 -0
  15. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +3 -2
  16. package/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +7 -0
  17. package/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +56 -2
  18. package/node_modules/@mrclrchtr/supi-code-runtime/src/evidence-badge.ts +40 -0
  19. package/node_modules/@mrclrchtr/supi-code-runtime/src/index.ts +5 -0
  20. package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +76 -3
  21. package/node_modules/@mrclrchtr/supi-code-runtime/src/workspace/runtime.ts +37 -2
  22. package/node_modules/@mrclrchtr/supi-core/package.json +6 -1
  23. package/node_modules/@mrclrchtr/supi-core/src/abort-utils.ts +31 -0
  24. package/node_modules/@mrclrchtr/supi-core/src/api.ts +8 -0
  25. package/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +10 -4
  26. package/node_modules/@mrclrchtr/supi-core/src/config.ts +1 -0
  27. package/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +1 -1
  28. package/node_modules/@mrclrchtr/supi-core/src/index.ts +6 -0
  29. package/node_modules/@mrclrchtr/supi-core/src/model-selection.ts +134 -0
  30. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +9 -2
  31. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +109 -4
  32. package/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +5 -1
  33. package/node_modules/@mrclrchtr/supi-core/src/spinner-frames.ts +11 -0
  34. package/node_modules/@mrclrchtr/supi-core/src/status-spinner.ts +68 -0
  35. package/package.json +4 -31
  36. package/src/api.ts +27 -0
  37. package/src/client/client-refresh.ts +5 -0
  38. package/src/client/client.ts +246 -1
  39. package/src/client/transport.ts +4 -0
  40. package/src/config/capabilities.ts +3 -0
  41. package/src/config/config.ts +4 -2
  42. package/src/config/lsp-settings.ts +102 -0
  43. package/src/config/server-actions.ts +59 -0
  44. package/src/config/server-config.ts +4 -0
  45. package/src/index.ts +11 -0
  46. package/src/manager/capability-index.ts +1 -1
  47. package/src/manager/manager-project-info.ts +2 -1
  48. package/src/manager/manager.ts +115 -26
  49. package/src/provider/lsp-semantic-provider.ts +177 -134
  50. package/src/provider/refactor-planning.ts +214 -0
  51. package/src/session/runtime-controller.ts +320 -0
  52. package/src/session/runtime-registration.ts +26 -5
  53. package/src/session/service-registry.ts +81 -4
  54. package/src/summary.ts +2 -0
  55. package/src/extension.ts +0 -1
  56. package/src/format.ts +0 -373
  57. package/src/handlers/diagnostic-injection.ts +0 -182
  58. package/src/handlers/session-lifecycle.ts +0 -149
  59. package/src/handlers/status-command.ts +0 -38
  60. package/src/handlers/workspace-recovery.ts +0 -92
  61. package/src/lsp.ts +0 -37
  62. package/src/session/lsp-state.ts +0 -93
  63. package/src/session/settings-registration.ts +0 -296
  64. package/src/session/tree-persist.ts +0 -75
  65. package/src/tool/guidance.ts +0 -69
  66. package/src/tool/names.ts +0 -27
  67. package/src/tool/overrides.ts +0 -204
  68. package/src/tool/register-tools.ts +0 -69
  69. package/src/tool/service-actions.ts +0 -399
  70. package/src/tool/tool-specs.ts +0 -248
  71. package/src/ui/renderer.ts +0 -120
  72. package/src/ui/ui.ts +0 -360
  73. package/src/workspace-change.ts +0 -40
package/src/ui/ui.ts DELETED
@@ -1,360 +0,0 @@
1
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import { DynamicBorder } from "@earendil-works/pi-coding-agent";
3
- import type { OverlayHandle } from "@earendil-works/pi-tui";
4
- import { Container, Spacer, Text } from "@earendil-works/pi-tui";
5
- import type { Diagnostic, ProjectServerInfo } from "../config/types.ts";
6
- import { DiagnosticSeverity } from "../config/types.ts";
7
- import type { LspManager } from "../manager/manager.ts";
8
- import type { OutstandingDiagnosticSummaryEntry } from "../manager/manager-types.ts";
9
-
10
- export interface LspInspectorState {
11
- handle: OverlayHandle | null;
12
- close: (() => void) | null;
13
- }
14
-
15
- export function updateLspUi(
16
- ctx: ExtensionContext,
17
- manager: LspManager,
18
- inlineSeverity: number,
19
- servers: ProjectServerInfo[],
20
- ): void {
21
- const diagnostics = manager.getOutstandingDiagnosticSummary(inlineSeverity);
22
-
23
- ctx.ui.setStatus("lsp", buildLspStatus(ctx, servers, diagnostics));
24
- ctx.ui.setWidget(
25
- "lsp",
26
- hasWidgetContent(diagnostics)
27
- ? (_tui, theme) => buildLspWidgetComponent(theme, diagnostics)
28
- : undefined,
29
- { placement: "belowEditor" },
30
- );
31
- }
32
-
33
- // biome-ignore lint/complexity/useMaxParams: overlay inputs travel together
34
- export function toggleLspStatusOverlay(
35
- ctx: ExtensionContext,
36
- manager: LspManager,
37
- inlineSeverity: number,
38
- inspector: LspInspectorState,
39
- servers: ProjectServerInfo[],
40
- ): void {
41
- if (inspector.handle && inspector.close) {
42
- inspector.close();
43
- return;
44
- }
45
-
46
- void ctx.ui
47
- .custom<void>(
48
- (_tui, theme, _kb, done) => {
49
- inspector.close = () => done(undefined);
50
- return createLspInspectorComponent(theme, manager, inlineSeverity, servers);
51
- },
52
- {
53
- overlay: true,
54
- overlayOptions: {
55
- anchor: "right-center",
56
- width: "60%",
57
- minWidth: 72,
58
- maxHeight: "90%",
59
- margin: { right: 1, top: 1, bottom: 1 },
60
- nonCapturing: true,
61
- },
62
- onHandle: (handle) => {
63
- inspector.handle = handle;
64
- },
65
- },
66
- )
67
- .finally(() => {
68
- inspector.handle = null;
69
- inspector.close = null;
70
- });
71
- }
72
-
73
- function createLspInspectorComponent(
74
- theme: ExtensionContext["ui"]["theme"],
75
- manager: LspManager,
76
- inlineSeverity: number,
77
- servers: ProjectServerInfo[],
78
- ): { render: (width: number) => string[]; invalidate: () => void } {
79
- return {
80
- render: (width) =>
81
- buildLspInspectorContainer({
82
- theme,
83
- manager,
84
- inlineSeverity,
85
- servers,
86
- width,
87
- }).render(width),
88
- invalidate: () => {},
89
- };
90
- }
91
-
92
- interface LspInspectorContainerInput {
93
- theme: ExtensionContext["ui"]["theme"];
94
- manager: LspManager;
95
- inlineSeverity: number;
96
- servers: ProjectServerInfo[];
97
- width: number;
98
- }
99
-
100
- function buildLspInspectorContainer(input: LspInspectorContainerInput): Container {
101
- const diagnostics = input.manager.getOutstandingDiagnosticSummary(input.inlineSeverity);
102
- const detailedDiagnostics = input.manager.getOutstandingDiagnostics(input.inlineSeverity);
103
- const container = new Container();
104
-
105
- const { theme, servers, width } = input;
106
-
107
- container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
108
- container.addChild(
109
- new Text(
110
- theme.fg("accent", theme.bold(" λ LSP")) + theme.fg("dim", " inspector /lsp-status toggles"),
111
- 1,
112
- 0,
113
- ),
114
- );
115
-
116
- if (servers.length === 0 && diagnostics.length === 0) {
117
- container.addChild(
118
- new Text(theme.fg("dim", "no LSP servers available for this project"), 1, 0),
119
- );
120
- container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
121
- return container;
122
- }
123
-
124
- container.addChild(new Text(buildOverlaySummaryLine(theme, servers, diagnostics), 1, 0));
125
- container.addChild(new Spacer(1));
126
- container.addChild(
127
- buildOverlaySection(theme, "Servers", buildOverlayServerLines(theme, servers)),
128
- );
129
- container.addChild(new Spacer(1));
130
- container.addChild(
131
- buildOverlaySection(
132
- theme,
133
- diagnostics.length > 0 ? "Problems" : "Diagnostics",
134
- buildOverlayDiagnosticLines(theme, diagnostics, detailedDiagnostics, width),
135
- ),
136
- );
137
- container.addChild(new Spacer(1));
138
- container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
139
-
140
- return container;
141
- }
142
-
143
- function buildLspStatus(
144
- ctx: ExtensionContext,
145
- servers: ProjectServerInfo[],
146
- diagnostics: OutstandingDiagnosticSummaryEntry[],
147
- ): string | undefined {
148
- const runningServers = servers.filter((server) => server.status === "running").length;
149
- const openFiles = servers.reduce((sum, server) => sum + server.openFiles.length, 0);
150
- const totals = collectDiagnosticTotals(diagnostics);
151
-
152
- if (runningServers === 0 && openFiles === 0 && diagnostics.length === 0) {
153
- return undefined;
154
- }
155
-
156
- const { theme } = ctx.ui;
157
- const parts = [theme.fg("accent", "λ lsp")];
158
- if (runningServers > 0) parts.push(theme.fg("dim", pluralize(runningServers, "server")));
159
- if (openFiles > 0) parts.push(theme.fg("dim", pluralize(openFiles, "open file")));
160
- if (totals.errors > 0) parts.push(theme.fg("error", pluralize(totals.errors, "error")));
161
- if (totals.warnings > 0) parts.push(theme.fg("warning", pluralize(totals.warnings, "warning")));
162
- if (totals.information > 0) parts.push(theme.fg("accent", pluralize(totals.information, "info")));
163
- if (totals.hints > 0) parts.push(theme.fg("dim", pluralize(totals.hints, "hint")));
164
- return parts.join(theme.fg("dim", " • "));
165
- }
166
-
167
- function hasWidgetContent(diagnostics: OutstandingDiagnosticSummaryEntry[]): boolean {
168
- return diagnostics.length > 0;
169
- }
170
-
171
- function buildLspWidgetComponent(
172
- theme: ExtensionContext["ui"]["theme"],
173
- diagnostics: OutstandingDiagnosticSummaryEntry[],
174
- ): Container {
175
- const container = new Container();
176
-
177
- for (const line of buildWidgetDiagnosticLines(theme, diagnostics)) {
178
- container.addChild(new Text(line, 0, 0));
179
- }
180
-
181
- return container;
182
- }
183
-
184
- function buildWidgetDiagnosticLines(
185
- theme: ExtensionContext["ui"]["theme"],
186
- diagnostics: OutstandingDiagnosticSummaryEntry[],
187
- ): string[] {
188
- if (diagnostics.length === 1) {
189
- const [entry] = diagnostics;
190
- if (!entry) return [];
191
- return [
192
- `${theme.fg("error", "●")} ${entry.file} ${theme.fg("dim", `— ${formatDiagnosticCounts(entry)}`)}`,
193
- ];
194
- }
195
-
196
- const totals = collectDiagnosticTotals(diagnostics);
197
- const counts: string[] = [];
198
- if (totals.errors > 0) counts.push(theme.fg("error", pluralize(totals.errors, "error")));
199
- if (totals.warnings > 0) counts.push(theme.fg("warning", pluralize(totals.warnings, "warning")));
200
- if (totals.information > 0)
201
- counts.push(theme.fg("accent", pluralize(totals.information, "info")));
202
- if (totals.hints > 0) counts.push(theme.fg("dim", pluralize(totals.hints, "hint")));
203
-
204
- const visibleFiles = diagnostics.slice(0, 2).map((entry) => entry.file);
205
- const remaining = diagnostics.length - visibleFiles.length;
206
- const suffix = remaining > 0 ? `${theme.fg("dim", ` +${remaining} more`)}` : "";
207
-
208
- return [
209
- `${theme.fg("accent", theme.bold("λ LSP diagnostics"))} ${theme.fg("dim", `— ${pluralize(diagnostics.length, "file")}`)} ${counts.join(theme.fg("dim", " • "))}`,
210
- `${theme.fg("error", "↳")} ${visibleFiles.join(", ")}${suffix}`,
211
- ];
212
- }
213
-
214
- function buildOverlaySummaryLine(
215
- theme: ExtensionContext["ui"]["theme"],
216
- servers: ProjectServerInfo[],
217
- diagnostics: OutstandingDiagnosticSummaryEntry[],
218
- ): string {
219
- const runningServers = servers.filter((server) => server.status === "running").length;
220
- const openFiles = servers.reduce((sum, server) => sum + server.openFiles.length, 0);
221
- const totals = collectDiagnosticTotals(diagnostics);
222
-
223
- const parts = [
224
- theme.fg(
225
- "dim",
226
- `${pluralize(runningServers, "server")} • ${pluralize(openFiles, "open file")}`,
227
- ),
228
- ];
229
- if (totals.errors > 0) {
230
- parts.push(theme.fg("error", pluralize(totals.errors, "error")));
231
- } else if (totals.warnings > 0) {
232
- parts.push(theme.fg("warning", pluralize(totals.warnings, "warning")));
233
- } else if (totals.information > 0) {
234
- parts.push(theme.fg("accent", pluralize(totals.information, "info")));
235
- } else if (totals.hints > 0) {
236
- parts.push(theme.fg("dim", pluralize(totals.hints, "hint")));
237
- } else {
238
- parts.push(theme.fg("success", "clean"));
239
- }
240
-
241
- return parts.join(theme.fg("dim", " "));
242
- }
243
-
244
- function buildOverlaySection(
245
- theme: ExtensionContext["ui"]["theme"],
246
- title: string,
247
- lines: string[],
248
- ): Container {
249
- const container = new Container();
250
- container.addChild(new Text(theme.fg("accent", theme.bold(` ${title}`)), 1, 0));
251
- for (const line of lines) {
252
- container.addChild(new Text(line, 2, 0));
253
- }
254
- return container;
255
- }
256
-
257
- function buildOverlayServerLines(
258
- theme: ExtensionContext["ui"]["theme"],
259
- servers: ProjectServerInfo[],
260
- ): string[] {
261
- if (servers.length === 0) {
262
- return [theme.fg("dim", "no LSP servers available for this project")];
263
- }
264
-
265
- return servers.flatMap((server) => {
266
- const statusColor =
267
- server.status === "running" ? "success" : server.status === "error" ? "error" : "warning";
268
- const actions =
269
- server.supportedActions.length > 0 ? server.supportedActions.join(", ") : "none";
270
- const fileTypes = server.fileTypes.map((entry) => `.${entry}`).join(", ");
271
- const openFiles =
272
- server.openFiles.length > 0 ? server.openFiles.slice(0, 3).join(", ") : "none";
273
- const remaining = server.openFiles.length - Math.min(server.openFiles.length, 3);
274
- const suffix = remaining > 0 ? theme.fg("dim", ` +${remaining} more`) : "";
275
-
276
- return [
277
- `${theme.fg("accent", "◆")} ${server.name} ${theme.fg(statusColor, server.status)} ${theme.fg("dim", `— root: ${server.root}`)}`,
278
- `${theme.fg("dim", "↳")} files: ${fileTypes}`,
279
- `${theme.fg("dim", "↳")} actions: ${actions}`,
280
- `${theme.fg("dim", "↳")} open: ${openFiles}${suffix}`,
281
- ];
282
- });
283
- }
284
-
285
- // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: pre-existing — v3.18.0 MarkupContent extraction pushes it over
286
- function buildOverlayDiagnosticLines(
287
- theme: ExtensionContext["ui"]["theme"],
288
- diagnostics: OutstandingDiagnosticSummaryEntry[],
289
- detailedDiagnostics: Array<{ file: string; diagnostics: Diagnostic[] }>,
290
- width: number,
291
- ): string[] {
292
- if (diagnostics.length === 0) {
293
- return [theme.fg("success", "✓ no outstanding diagnostics")];
294
- }
295
-
296
- const lines: string[] = [];
297
- const maxFiles = 5;
298
- const maxMessagesPerFile = 5;
299
- // Budget for prefix: 2 spaces + tree char + space + line:col + space + Text paddingX(2)*2
300
- const maxMessageLen = Math.max(24, width - 18);
301
-
302
- for (const entry of detailedDiagnostics.slice(0, maxFiles)) {
303
- const summary = diagnostics.find((d) => d.file === entry.file);
304
- const countLabel = summary ? formatDiagnosticCounts(summary) : "";
305
- lines.push(`${theme.fg("error", "●")} ${entry.file} ${theme.fg("dim", `— ${countLabel}`)}`);
306
-
307
- for (const diag of entry.diagnostics.slice(0, maxMessagesPerFile)) {
308
- const line = diag.range.start.line + 1;
309
- const col = diag.range.start.character + 1;
310
- const sevColor = diag.severity === DiagnosticSeverity.Error ? "error" : "warning";
311
- const messageText = typeof diag.message === "string" ? diag.message : diag.message.value;
312
- const message = truncate(messageText, maxMessageLen);
313
- lines.push(` ${theme.fg(sevColor, "└")} ${line}:${col} ${theme.fg("dim", message)}`);
314
- }
315
-
316
- const remainingMessages = entry.diagnostics.length - maxMessagesPerFile;
317
- if (remainingMessages > 0) {
318
- lines.push(` ${theme.fg("dim", `└ +${remainingMessages} more`)}`);
319
- }
320
- }
321
-
322
- const remainingFiles = diagnostics.length - Math.min(diagnostics.length, maxFiles);
323
- if (remainingFiles > 0) {
324
- lines.push(theme.fg("dim", `↳ +${remainingFiles} more file${remainingFiles === 1 ? "" : "s"}`));
325
- }
326
-
327
- return lines;
328
- }
329
-
330
- function truncate(text: string, maxLength: number): string {
331
- if (text.length <= maxLength) return text;
332
- return `${text.slice(0, maxLength - 1)}…`;
333
- }
334
-
335
- function formatDiagnosticCounts(entry: OutstandingDiagnosticSummaryEntry): string {
336
- const counts: string[] = [];
337
- if (entry.errors > 0) counts.push(pluralize(entry.errors, "error"));
338
- if (entry.warnings > 0) counts.push(pluralize(entry.warnings, "warning"));
339
- if (entry.information > 0) counts.push(pluralize(entry.information, "info"));
340
- if (entry.hints > 0) counts.push(pluralize(entry.hints, "hint"));
341
- return counts.join(", ");
342
- }
343
-
344
- function collectDiagnosticTotals(
345
- diagnostics: OutstandingDiagnosticSummaryEntry[],
346
- ): Pick<OutstandingDiagnosticSummaryEntry, "errors" | "warnings" | "information" | "hints"> {
347
- return diagnostics.reduce(
348
- (totals, entry) => ({
349
- errors: totals.errors + entry.errors,
350
- warnings: totals.warnings + entry.warnings,
351
- information: totals.information + entry.information,
352
- hints: totals.hints + entry.hints,
353
- }),
354
- { errors: 0, warnings: 0, information: 0, hints: 0 },
355
- );
356
- }
357
-
358
- function pluralize(count: number, word: string): string {
359
- return `${count} ${word}${count === 1 ? "" : "s"}`;
360
- }
@@ -1,40 +0,0 @@
1
- // Workspace change tracking helpers shared by handlers/session-lifecycle.ts,
2
- // handlers/diagnostic-injection.ts, and handlers/workspace-recovery.ts.
3
-
4
- import * as path from "node:path";
5
- import { clearTsconfigCache } from "./config/tsconfig-scope.ts";
6
- import type { FileEvent } from "./config/types.ts";
7
- import { syncWorkspaceSentinelSnapshot } from "./diagnostics/workspace-sentinels.ts";
8
- import type { LspRuntimeState } from "./session/lsp-state.ts";
9
-
10
- /** Update the state's workspace-change tracking fields after a file modification. */
11
- export function markWorkspaceChange(state: LspRuntimeState): void {
12
- state.lastWorkspaceChangeAt = Date.now();
13
- state.staleSuspected = true;
14
- state.lastDiagnosticsFingerprint = null;
15
- state.currentContextToken = null;
16
- }
17
-
18
- /** Notify the LSP manager about file changes and reset pull-diagnostic state. */
19
- export function softRecoverWorkspaceChanges(state: LspRuntimeState, changes: FileEvent[]): boolean {
20
- if (!state.manager || changes.length === 0) return false;
21
-
22
- clearTsconfigCache();
23
- state.manager.clearAllPullResultIds();
24
- state.manager.notifyWorkspaceFileChanges(changes);
25
- markWorkspaceChange(state);
26
- return true;
27
- }
28
-
29
- /** Sync the sentinel-file snapshot and recover from any detected changes. */
30
- export function refreshWorkspaceSentinels(state: LspRuntimeState, cwd: string): boolean {
31
- const { snapshot, changes } = syncWorkspaceSentinelSnapshot(cwd, state.sentinelSnapshot);
32
- state.sentinelSnapshot = snapshot;
33
- return softRecoverWorkspaceChanges(state, changes);
34
- }
35
-
36
- /** Check whether a given file path's extension suggests tsconfig-scope invalidation. */
37
- export function shouldInvalidateTsconfigScopeCache(filePath: string): boolean {
38
- const ext = path.extname(filePath).toLowerCase();
39
- return ext === ".json" || ext === ".jsonc";
40
- }