@mrclrchtr/supi-lsp 4.10.0 → 5.0.0
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/README.md +80 -5
- package/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
- package/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
- package/node_modules/@mrclrchtr/supi-core/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
- package/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -3
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
- package/package.json +7 -6
- package/src/api.ts +10 -0
- package/src/client/client-diagnostic-cache.ts +206 -0
- package/src/client/client-diagnostic-capabilities.ts +78 -0
- package/src/client/client-diagnostic-collection.ts +114 -0
- package/src/client/client-diagnostic-evidence.ts +372 -0
- package/src/client/client-diagnostic-host.ts +14 -0
- package/src/client/client-diagnostic-pull.ts +62 -0
- package/src/client/client-diagnostic-refresh.ts +491 -0
- package/src/client/client-diagnostic-request.ts +9 -0
- package/src/client/client-diagnostic-timing.ts +71 -11
- package/src/client/client-diagnostic-waiters.ts +206 -0
- package/src/client/client-diagnostics.ts +365 -297
- package/src/client/client-document-state.ts +52 -0
- package/src/client/client-document-sync.ts +185 -0
- package/src/client/client-file-state.ts +25 -0
- package/src/client/client.ts +467 -115
- package/src/client/transport.ts +161 -33
- package/src/config/capabilities.ts +10 -1
- package/src/config/defaults.json +1 -1
- package/src/debug-telemetry.ts +45 -0
- package/src/diagnostics/evidence.ts +38 -0
- package/src/manager/manager-client-state.ts +79 -9
- package/src/manager/manager-diagnostics.ts +13 -4
- package/src/manager/manager-workspace-recovery.ts +300 -24
- package/src/manager/manager-workspace-symbol.ts +5 -2
- package/src/manager/manager.ts +464 -96
- package/src/provider/lsp-refactor-provider.ts +130 -0
- package/src/provider/lsp-semantic-provider.ts +45 -329
- package/src/provider/refactor-planning.ts +57 -120
- package/src/provider/semantic-edit-normalizer.ts +320 -0
- package/src/provider/semantic-symbol-mapper.ts +207 -0
- package/src/session/readiness.ts +72 -2
- package/src/session/runtime-controller.ts +151 -26
- package/src/session/runtime-diagnostic-surface.ts +40 -0
- package/src/session/runtime-diagnostics.ts +50 -0
- package/src/session/runtime-registry.ts +136 -120
- package/src/session/runtime-transition-debug.ts +49 -0
- package/src/session/workspace-lsp-runtime.ts +102 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type CodeRequestControl,
|
|
3
|
+
CodeRequestDeadlineError,
|
|
4
|
+
throwIfCodeRequestInterrupted,
|
|
5
|
+
} from "@mrclrchtr/supi-code-runtime/api";
|
|
6
|
+
import type {
|
|
7
|
+
DiagnosticPushWaitOutcome,
|
|
8
|
+
DiagnosticSettleResult,
|
|
9
|
+
} from "./client-diagnostic-timing.ts";
|
|
10
|
+
|
|
11
|
+
type DiagnosticWaiter = (outcome: "published" | "released") => void;
|
|
12
|
+
|
|
13
|
+
/** One cancellable wait for diagnostic state change. */
|
|
14
|
+
export interface DiagnosticStateWait {
|
|
15
|
+
readonly promise: Promise<void>;
|
|
16
|
+
cancel(): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface DiagnosticSettleOptions {
|
|
20
|
+
readonly syncStart: number;
|
|
21
|
+
readonly maxWaitMs: number;
|
|
22
|
+
readonly quietMs: number;
|
|
23
|
+
readonly settleEpoch: number;
|
|
24
|
+
readonly isComplete: () => boolean;
|
|
25
|
+
readonly latestReceived: () => number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Own diagnostic waiters and their timers for one LSP client. */
|
|
29
|
+
export class DiagnosticWaitRegistry {
|
|
30
|
+
readonly #pushWaiters = new Map<string, DiagnosticWaiter[]>();
|
|
31
|
+
readonly #stateWaiters = new Set<() => void>();
|
|
32
|
+
readonly #settleWaiters = new Set<() => void>();
|
|
33
|
+
#settleEpoch = 0;
|
|
34
|
+
|
|
35
|
+
get settleEpoch(): number {
|
|
36
|
+
return this.#settleEpoch;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Wait for a diagnostic publication or a lifecycle release for one URI. */
|
|
40
|
+
waitForPush(
|
|
41
|
+
uri: string,
|
|
42
|
+
timeoutMs: number,
|
|
43
|
+
control?: CodeRequestControl,
|
|
44
|
+
): Promise<DiagnosticPushWaitOutcome> {
|
|
45
|
+
const deadlineRemaining =
|
|
46
|
+
control?.deadline === undefined ? undefined : control.deadline - Date.now();
|
|
47
|
+
const bindMs =
|
|
48
|
+
deadlineRemaining === undefined ? timeoutMs : Math.min(timeoutMs, deadlineRemaining);
|
|
49
|
+
if (control?.signal?.aborted) {
|
|
50
|
+
return Promise.reject(control.signal.reason ?? new Error("Diagnostic push wait cancelled"));
|
|
51
|
+
}
|
|
52
|
+
if (bindMs <= 0) {
|
|
53
|
+
if (deadlineRemaining !== undefined && deadlineRemaining < timeoutMs) {
|
|
54
|
+
return Promise.reject(new CodeRequestDeadlineError());
|
|
55
|
+
}
|
|
56
|
+
return Promise.resolve("timed-out");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return new Promise<DiagnosticPushWaitOutcome>((resolve, reject) => {
|
|
60
|
+
const cleanup = () => {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
if (abortHandler) control?.signal?.removeEventListener("abort", abortHandler);
|
|
63
|
+
};
|
|
64
|
+
const waiter: DiagnosticWaiter = (outcome) => {
|
|
65
|
+
cleanup();
|
|
66
|
+
this.#removePushWaiter(uri, waiter);
|
|
67
|
+
resolve(outcome);
|
|
68
|
+
};
|
|
69
|
+
const timer = setTimeout(() => {
|
|
70
|
+
this.#removePushWaiter(uri, waiter);
|
|
71
|
+
cleanup();
|
|
72
|
+
if (deadlineRemaining !== undefined && deadlineRemaining < timeoutMs) {
|
|
73
|
+
reject(new CodeRequestDeadlineError());
|
|
74
|
+
} else {
|
|
75
|
+
resolve("timed-out");
|
|
76
|
+
}
|
|
77
|
+
}, bindMs);
|
|
78
|
+
let abortHandler: (() => void) | undefined;
|
|
79
|
+
abortHandler = () => {
|
|
80
|
+
cleanup();
|
|
81
|
+
this.#removePushWaiter(uri, waiter);
|
|
82
|
+
reject(control?.signal?.reason ?? new Error("Diagnostic push wait cancelled"));
|
|
83
|
+
};
|
|
84
|
+
if (control?.signal?.aborted) abortHandler();
|
|
85
|
+
else control?.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
86
|
+
const waiters = this.#pushWaiters.get(uri) ?? [];
|
|
87
|
+
waiters.push(waiter);
|
|
88
|
+
this.#pushWaiters.set(uri, waiters);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Wait until any diagnostic or lifecycle state changes. */
|
|
93
|
+
waitForChange(): DiagnosticStateWait {
|
|
94
|
+
let finish = () => {};
|
|
95
|
+
const promise = new Promise<void>((resolve) => {
|
|
96
|
+
finish = () => {
|
|
97
|
+
this.#stateWaiters.delete(finish);
|
|
98
|
+
resolve();
|
|
99
|
+
};
|
|
100
|
+
this.#stateWaiters.add(finish);
|
|
101
|
+
});
|
|
102
|
+
return { promise, cancel: finish };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Release pending push waiters for one URI. */
|
|
106
|
+
releaseFile(uri: string, outcome: "published" | "released" = "released"): void {
|
|
107
|
+
const waiters = this.#pushWaiters.get(uri);
|
|
108
|
+
if (waiters) {
|
|
109
|
+
this.#pushWaiters.delete(uri);
|
|
110
|
+
for (const waiter of waiters) waiter(outcome);
|
|
111
|
+
}
|
|
112
|
+
this.#notifyStateWaiters();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Release all pending push waiters. */
|
|
116
|
+
releaseAll(): void {
|
|
117
|
+
for (const uri of Array.from(this.#pushWaiters.keys())) this.releaseFile(uri);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Cancel the active settle generation and release its timers. */
|
|
121
|
+
cancelSettle(): void {
|
|
122
|
+
this.#settleEpoch++;
|
|
123
|
+
this.notifySettle();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Notify settle operations that diagnostic state changed. */
|
|
127
|
+
notifySettle(): void {
|
|
128
|
+
for (const release of Array.from(this.#settleWaiters)) release();
|
|
129
|
+
this.#notifyStateWaiters();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Wait for fresh diagnostic state to become quiet or reach its deadline. */
|
|
133
|
+
async waitForSettle(
|
|
134
|
+
options: DiagnosticSettleOptions,
|
|
135
|
+
control?: CodeRequestControl,
|
|
136
|
+
): Promise<DiagnosticSettleResult> {
|
|
137
|
+
const { syncStart, maxWaitMs, quietMs, settleEpoch, isComplete, latestReceived } = options;
|
|
138
|
+
const deadlineRemaining =
|
|
139
|
+
control?.deadline === undefined ? undefined : control.deadline - Date.now();
|
|
140
|
+
const deadline =
|
|
141
|
+
deadlineRemaining === undefined
|
|
142
|
+
? syncStart + maxWaitMs
|
|
143
|
+
: Math.min(syncStart + maxWaitMs, control?.deadline ?? syncStart + maxWaitMs);
|
|
144
|
+
while (Date.now() < deadline) {
|
|
145
|
+
throwIfCodeRequestInterrupted(control);
|
|
146
|
+
if (this.#settleEpoch !== settleEpoch) {
|
|
147
|
+
return { outcome: "released", freshness: "not-observed" };
|
|
148
|
+
}
|
|
149
|
+
const observedAt = latestReceived();
|
|
150
|
+
const elapsed = Date.now() - observedAt;
|
|
151
|
+
const complete = isComplete();
|
|
152
|
+
if (observedAt > 0 && complete && elapsed >= quietMs) {
|
|
153
|
+
return { outcome: "quiet", freshness: "observed" };
|
|
154
|
+
}
|
|
155
|
+
const waitMs =
|
|
156
|
+
observedAt > 0 && complete
|
|
157
|
+
? Math.min(quietMs - elapsed, deadline - Date.now())
|
|
158
|
+
: deadline - Date.now();
|
|
159
|
+
await this.#waitForStateChange(waitMs, control);
|
|
160
|
+
}
|
|
161
|
+
// A bound absolute deadline makes the settle outcome a deadline
|
|
162
|
+
// interruption instead of an ordinary relative timeout.
|
|
163
|
+
throwIfCodeRequestInterrupted(control);
|
|
164
|
+
return {
|
|
165
|
+
outcome: "timed-out",
|
|
166
|
+
freshness: latestReceived() > 0 ? "observed" : "not-observed",
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Wait until diagnostic state changes or the timeout expires. */
|
|
171
|
+
#waitForStateChange(timeoutMs: number, control?: CodeRequestControl): Promise<void> {
|
|
172
|
+
if (timeoutMs <= 0) return Promise.resolve();
|
|
173
|
+
if (control?.signal?.aborted) {
|
|
174
|
+
return Promise.reject(control.signal.reason ?? new Error("Settle wait cancelled"));
|
|
175
|
+
}
|
|
176
|
+
return new Promise((resolve, reject) => {
|
|
177
|
+
let abortHandler: (() => void) | undefined;
|
|
178
|
+
const finish = () => {
|
|
179
|
+
clearTimeout(timer);
|
|
180
|
+
if (abortHandler) control?.signal?.removeEventListener("abort", abortHandler);
|
|
181
|
+
this.#settleWaiters.delete(finish);
|
|
182
|
+
resolve();
|
|
183
|
+
};
|
|
184
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
185
|
+
abortHandler = () => {
|
|
186
|
+
clearTimeout(timer);
|
|
187
|
+
this.#settleWaiters.delete(finish);
|
|
188
|
+
reject(control?.signal?.reason ?? new Error("Settle wait cancelled"));
|
|
189
|
+
};
|
|
190
|
+
control?.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
191
|
+
this.#settleWaiters.add(finish);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#notifyStateWaiters(): void {
|
|
196
|
+
for (const release of Array.from(this.#stateWaiters)) release();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#removePushWaiter(uri: string, waiter: DiagnosticWaiter): void {
|
|
200
|
+
const waiters = this.#pushWaiters.get(uri);
|
|
201
|
+
if (!waiters) return;
|
|
202
|
+
const next = waiters.filter((entry) => entry !== waiter);
|
|
203
|
+
if (next.length > 0) this.#pushWaiters.set(uri, next);
|
|
204
|
+
else this.#pushWaiters.delete(uri);
|
|
205
|
+
}
|
|
206
|
+
}
|