@mrclrchtr/supi-lsp 10.0.0 → 11.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 +88 -2
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/LICENSE +21 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/README.md +743 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/index.js +17 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/lib/constants.js +184 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/lib/parse.js +1425 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/lib/picomatch.js +361 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/lib/scan.js +403 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/lib/utils.js +72 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/package.json +83 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/posix.js +3 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +4 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +11 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/src/file-associations.ts +190 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +22 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/package.json +3 -3
- package/src/api.ts +1 -0
- package/src/client/client-diagnostic-host.ts +3 -0
- package/src/client/client-diagnostics.ts +6 -4
- package/src/client/client-request-enrollment.ts +66 -22
- package/src/client/client-semantic-input-barrier.ts +37 -5
- package/src/client/client.ts +136 -43
- package/src/manager/manager.ts +261 -22
- package/src/provider/refactor-planning.ts +13 -0
- package/src/provider/semantic-edit-discovery.ts +151 -0
- package/src/provider/semantic-edit-normalizer.ts +97 -129
- package/src/provider/semantic-edit-protocol.ts +132 -0
- package/src/session/runtime-controller.ts +8 -1
- package/src/session/runtime-registry.ts +7 -20
- package/src/session/runtime-rename.ts +172 -0
- package/src/session/workspace-lsp-runtime.ts +14 -0
- package/src/utils.ts +12 -11
package/src/client/client.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { type ChildProcess, execSync, spawn } from "node:child_process";
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import {
|
|
8
|
+
type CFamilyFileAssociationResolver,
|
|
8
9
|
type CodeQueryResult,
|
|
9
10
|
type CodeRequestControl,
|
|
10
11
|
completedCodeQuery,
|
|
@@ -69,11 +70,15 @@ import { createTypeScriptDiagnosticRequestAdapter } from "./client-diagnostic-ty
|
|
|
69
70
|
import { ClientDiagnostics } from "./client-diagnostics.ts";
|
|
70
71
|
import type { ClientDiagnosticSnapshot, DiagnosticEntry } from "./client-document-state.ts";
|
|
71
72
|
import { type NormalizedDocumentSync, normalizeDocumentSync } from "./client-document-sync.ts";
|
|
72
|
-
import { SemanticInputRequestRetryGuard } from "./client-request-enrollment.ts";
|
|
73
73
|
import {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
} from "./client-
|
|
74
|
+
SemanticInputRequestRetryGuard,
|
|
75
|
+
type SemanticInputRequestRetryReason,
|
|
76
|
+
} from "./client-request-enrollment.ts";
|
|
77
|
+
import type {
|
|
78
|
+
SemanticInputDocumentSnapshot,
|
|
79
|
+
SemanticInputSnapshot,
|
|
80
|
+
} from "./client-semantic-input-barrier.ts";
|
|
81
|
+
import { SemanticInputSynchronizationError } from "./client-semantic-input-errors.ts";
|
|
77
82
|
import { JsonRpcClient, JsonRpcRequestError } from "./transport.ts";
|
|
78
83
|
|
|
79
84
|
const SHUTDOWN_TIMEOUT_MS = 5_000;
|
|
@@ -87,7 +92,15 @@ interface SemanticRequestAttemptState {
|
|
|
87
92
|
|
|
88
93
|
interface RefactorTransaction {
|
|
89
94
|
rpc: JsonRpcClient;
|
|
90
|
-
assertCurrent: () => Promise<
|
|
95
|
+
assertCurrent: () => Promise<SemanticInputSnapshot>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface RenameDetailedWithSnapshotsResult {
|
|
99
|
+
readonly result: CodeQueryResult<WorkspaceEdit | null>;
|
|
100
|
+
/** Verified input revision retained when a rename needs document enrollment. */
|
|
101
|
+
readonly inputRevision?: number;
|
|
102
|
+
/** Exact documents synchronized before and rechecked after this request. */
|
|
103
|
+
readonly documentSnapshots: readonly SemanticInputDocumentSnapshot[];
|
|
91
104
|
}
|
|
92
105
|
|
|
93
106
|
/**
|
|
@@ -529,6 +542,8 @@ export class LspClient {
|
|
|
529
542
|
private readonly onLifecycleTransition?: LspClientLifecycleListener,
|
|
530
543
|
/** Absolute workspace root for debug-telemetry identity. */
|
|
531
544
|
readonly cwd?: string,
|
|
545
|
+
/** Immutable workspace association snapshot for document language IDs. */
|
|
546
|
+
fileAssociations?: CFamilyFileAssociationResolver,
|
|
532
547
|
) {
|
|
533
548
|
this.name = name;
|
|
534
549
|
this.root = root;
|
|
@@ -562,6 +577,7 @@ export class LspClient {
|
|
|
562
577
|
this.diagnostics = new ClientDiagnostics({
|
|
563
578
|
server: name,
|
|
564
579
|
cwd: cwd,
|
|
580
|
+
fileAssociations,
|
|
565
581
|
isOperational: () => this.rpc !== null && this._status === "running",
|
|
566
582
|
diagnosticRequestAdapter: this.diagnosticRequestAdapter,
|
|
567
583
|
documentSync: () => this.getDocumentSyncOptions(),
|
|
@@ -1155,8 +1171,32 @@ export class LspClient {
|
|
|
1155
1171
|
newName: string,
|
|
1156
1172
|
control?: CodeRequestControl,
|
|
1157
1173
|
): Promise<CodeQueryResult<WorkspaceEdit | null>> {
|
|
1174
|
+
return (
|
|
1175
|
+
await this.renameDetailedWithSnapshots(filePath, position, newName, {
|
|
1176
|
+
control,
|
|
1177
|
+
snapshotFiles: [filePath],
|
|
1178
|
+
})
|
|
1179
|
+
).result;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
/** Run rename and retain the exact synchronized inputs verified by its transaction. */
|
|
1183
|
+
async renameDetailedWithSnapshots(
|
|
1184
|
+
filePath: string,
|
|
1185
|
+
position: Position,
|
|
1186
|
+
newName: string,
|
|
1187
|
+
options: {
|
|
1188
|
+
readonly control?: CodeRequestControl;
|
|
1189
|
+
readonly snapshotFiles?: readonly string[];
|
|
1190
|
+
/** Reject non-enrollment changes since this earlier rename request. */
|
|
1191
|
+
readonly inputRevision?: number;
|
|
1192
|
+
} = {},
|
|
1193
|
+
): Promise<RenameDetailedWithSnapshotsResult> {
|
|
1194
|
+
const { control, snapshotFiles = [filePath] } = options;
|
|
1158
1195
|
if (!this.supportsRename) {
|
|
1159
|
-
return
|
|
1196
|
+
return {
|
|
1197
|
+
result: unavailableCodeQuery("The server does not advertise textDocument/rename."),
|
|
1198
|
+
documentSnapshots: [],
|
|
1199
|
+
};
|
|
1160
1200
|
}
|
|
1161
1201
|
if (
|
|
1162
1202
|
typeof filePath !== "string" ||
|
|
@@ -1165,9 +1205,12 @@ export class LspClient {
|
|
|
1165
1205
|
typeof newName !== "string" ||
|
|
1166
1206
|
newName.trim().length === 0
|
|
1167
1207
|
) {
|
|
1168
|
-
return
|
|
1169
|
-
|
|
1170
|
-
|
|
1208
|
+
return {
|
|
1209
|
+
result: unavailableCodeQuery(
|
|
1210
|
+
"Rename requires a valid file, position, and non-empty new name.",
|
|
1211
|
+
),
|
|
1212
|
+
documentSnapshots: [],
|
|
1213
|
+
};
|
|
1171
1214
|
}
|
|
1172
1215
|
|
|
1173
1216
|
const params = {
|
|
@@ -1175,22 +1218,39 @@ export class LspClient {
|
|
|
1175
1218
|
position,
|
|
1176
1219
|
newName,
|
|
1177
1220
|
};
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1221
|
+
let documentSnapshots: readonly SemanticInputDocumentSnapshot[] = [];
|
|
1222
|
+
let inputRevision: number | undefined;
|
|
1223
|
+
const requestedFiles = new Set(snapshotFiles.map((file) => path.resolve(file)));
|
|
1224
|
+
const result = await this.runRefactorTransaction(
|
|
1225
|
+
"textDocument/rename",
|
|
1226
|
+
control,
|
|
1227
|
+
async (transaction) => {
|
|
1228
|
+
if (this.supportsPrepareRename) {
|
|
1229
|
+
const prepared = await transaction.rpc.sendRequest(
|
|
1230
|
+
"textDocument/prepareRename",
|
|
1231
|
+
{ textDocument: { uri: fileToUri(filePath) }, position },
|
|
1232
|
+
control,
|
|
1233
|
+
);
|
|
1234
|
+
const preparationError = validatePrepareRenameResult(prepared, position);
|
|
1235
|
+
if (preparationError) throw new Error(preparationError);
|
|
1236
|
+
await transaction.assertCurrent();
|
|
1237
|
+
}
|
|
1238
|
+
return (await transaction.rpc.sendRequest("textDocument/rename", params, control)) as
|
|
1239
|
+
| WorkspaceEdit
|
|
1240
|
+
| null
|
|
1241
|
+
| undefined;
|
|
1242
|
+
},
|
|
1243
|
+
{
|
|
1244
|
+
inputRevision: options.inputRevision,
|
|
1245
|
+
onVerifiedSnapshot: (snapshot) => {
|
|
1246
|
+
inputRevision = snapshot.revision;
|
|
1247
|
+
documentSnapshots = snapshot.documents.filter((document) =>
|
|
1248
|
+
requestedFiles.has(path.resolve(document.filePath)),
|
|
1249
|
+
);
|
|
1250
|
+
},
|
|
1251
|
+
},
|
|
1252
|
+
);
|
|
1253
|
+
return { result, documentSnapshots, inputRevision };
|
|
1194
1254
|
}
|
|
1195
1255
|
|
|
1196
1256
|
async codeActions(
|
|
@@ -1247,7 +1307,9 @@ export class LspClient {
|
|
|
1247
1307
|
control?: CodeRequestControl,
|
|
1248
1308
|
): Promise<CodeQueryResult<Location | Location[] | LocationLink[] | null>> {
|
|
1249
1309
|
if (!this.capabilities?.implementationProvider) {
|
|
1250
|
-
return unavailableCodeQuery(
|
|
1310
|
+
return unavailableCodeQuery(
|
|
1311
|
+
`LSP server "${this.name}" does not advertise textDocument/implementation.`,
|
|
1312
|
+
);
|
|
1251
1313
|
}
|
|
1252
1314
|
return this.query(
|
|
1253
1315
|
"textDocument/implementation",
|
|
@@ -1300,6 +1362,10 @@ export class LspClient {
|
|
|
1300
1362
|
method: string,
|
|
1301
1363
|
control: CodeRequestControl | undefined,
|
|
1302
1364
|
operation: (transaction: RefactorTransaction) => Promise<T | null | undefined>,
|
|
1365
|
+
options: {
|
|
1366
|
+
readonly onVerifiedSnapshot?: (snapshot: SemanticInputSnapshot) => void;
|
|
1367
|
+
readonly inputRevision?: number;
|
|
1368
|
+
} = {},
|
|
1303
1369
|
): Promise<CodeQueryResult<T | null>> {
|
|
1304
1370
|
throwIfCodeRequestInterrupted(control);
|
|
1305
1371
|
if (!this.rpc || this._status !== "running") {
|
|
@@ -1308,8 +1374,9 @@ export class LspClient {
|
|
|
1308
1374
|
);
|
|
1309
1375
|
}
|
|
1310
1376
|
|
|
1311
|
-
const retryGuard = new SemanticInputRequestRetryGuard(
|
|
1312
|
-
this.diagnostics.getSemanticInputChangeSince(revision),
|
|
1377
|
+
const retryGuard = new SemanticInputRequestRetryGuard(
|
|
1378
|
+
(revision) => this.diagnostics.getSemanticInputChangeSince(revision),
|
|
1379
|
+
{ inputRevision: options.inputRevision },
|
|
1313
1380
|
);
|
|
1314
1381
|
for (;;) {
|
|
1315
1382
|
const attempt: SemanticRequestAttemptState = { inputRevision: undefined };
|
|
@@ -1321,9 +1388,10 @@ export class LspClient {
|
|
|
1321
1388
|
operation,
|
|
1322
1389
|
retryGuard,
|
|
1323
1390
|
attempt,
|
|
1391
|
+
onVerifiedSnapshot: options.onVerifiedSnapshot,
|
|
1324
1392
|
});
|
|
1325
1393
|
if (retryGuard.hasRetried) {
|
|
1326
|
-
this.
|
|
1394
|
+
this.recordRequestRetry({ method, control, retryGuard, outcome: "completed" });
|
|
1327
1395
|
}
|
|
1328
1396
|
return completedCodeQuery(data);
|
|
1329
1397
|
} catch (error) {
|
|
@@ -1364,8 +1432,9 @@ export class LspClient {
|
|
|
1364
1432
|
operation: (transaction: RefactorTransaction) => Promise<T | null | undefined>;
|
|
1365
1433
|
retryGuard: SemanticInputRequestRetryGuard;
|
|
1366
1434
|
attempt: SemanticRequestAttemptState;
|
|
1435
|
+
onVerifiedSnapshot?: (snapshot: SemanticInputSnapshot) => void;
|
|
1367
1436
|
}): Promise<T | null> {
|
|
1368
|
-
const { method, control, operation, retryGuard, attempt } = options;
|
|
1437
|
+
const { method, control, operation, retryGuard, attempt, onVerifiedSnapshot } = options;
|
|
1369
1438
|
throwIfCodeRequestInterrupted(control);
|
|
1370
1439
|
retryGuard.assertCurrent();
|
|
1371
1440
|
await this.getReady(control);
|
|
@@ -1381,10 +1450,14 @@ export class LspClient {
|
|
|
1381
1450
|
rpc,
|
|
1382
1451
|
assertCurrent: async () => {
|
|
1383
1452
|
retryGuard.assertCurrent();
|
|
1384
|
-
|
|
1453
|
+
return this.diagnostics.assertSemanticInputsCurrent(inputSnapshot, control);
|
|
1385
1454
|
},
|
|
1386
1455
|
});
|
|
1387
|
-
await this.diagnostics.assertSemanticInputsCurrent(
|
|
1456
|
+
const verifiedSnapshot = await this.diagnostics.assertSemanticInputsCurrent(
|
|
1457
|
+
inputSnapshot,
|
|
1458
|
+
control,
|
|
1459
|
+
);
|
|
1460
|
+
onVerifiedSnapshot?.(verifiedSnapshot);
|
|
1388
1461
|
return data ?? null;
|
|
1389
1462
|
}
|
|
1390
1463
|
|
|
@@ -1418,8 +1491,9 @@ export class LspClient {
|
|
|
1418
1491
|
params: unknown,
|
|
1419
1492
|
control?: CodeRequestControl,
|
|
1420
1493
|
): Promise<CodeQueryResult<T | null>> {
|
|
1421
|
-
const retryGuard = new SemanticInputRequestRetryGuard(
|
|
1422
|
-
this.diagnostics.getSemanticInputChangeSince(revision),
|
|
1494
|
+
const retryGuard = new SemanticInputRequestRetryGuard(
|
|
1495
|
+
(revision) => this.diagnostics.getSemanticInputChangeSince(revision),
|
|
1496
|
+
{ retryContentModified: true },
|
|
1423
1497
|
);
|
|
1424
1498
|
for (;;) {
|
|
1425
1499
|
const attempt: SemanticRequestAttemptState = { inputRevision: undefined };
|
|
@@ -1432,7 +1506,7 @@ export class LspClient {
|
|
|
1432
1506
|
attempt,
|
|
1433
1507
|
});
|
|
1434
1508
|
if (retryGuard.hasRetried) {
|
|
1435
|
-
this.
|
|
1509
|
+
this.recordRequestRetry({ method, control, retryGuard, outcome: "completed" });
|
|
1436
1510
|
}
|
|
1437
1511
|
return completedCodeQuery(data);
|
|
1438
1512
|
} catch (error) {
|
|
@@ -1452,7 +1526,7 @@ export class LspClient {
|
|
|
1452
1526
|
const { method, control, retryGuard, attempt, error } = options;
|
|
1453
1527
|
if (isCodeRequestInterruption(error, control)) {
|
|
1454
1528
|
if (retryGuard.hasRetried) {
|
|
1455
|
-
this.
|
|
1529
|
+
this.recordRequestRetry({ method, control, retryGuard, outcome: "interrupted" });
|
|
1456
1530
|
}
|
|
1457
1531
|
throwIfCodeRequestInterrupted(control);
|
|
1458
1532
|
throw error;
|
|
@@ -1461,17 +1535,32 @@ export class LspClient {
|
|
|
1461
1535
|
const decision = retryGuard.decide(error, attempt.inputRevision);
|
|
1462
1536
|
if (decision.kind === "retry") {
|
|
1463
1537
|
throwIfCodeRequestInterrupted(control);
|
|
1464
|
-
this.
|
|
1538
|
+
this.recordRequestRetry({
|
|
1539
|
+
method,
|
|
1540
|
+
control,
|
|
1541
|
+
retryGuard,
|
|
1542
|
+
outcome: "retry",
|
|
1543
|
+
retryReason: decision.reason,
|
|
1544
|
+
error,
|
|
1545
|
+
});
|
|
1465
1546
|
return;
|
|
1466
1547
|
}
|
|
1467
1548
|
if (retryGuard.hasRetried) {
|
|
1468
|
-
const outcome =
|
|
1469
|
-
this.
|
|
1549
|
+
const outcome = decision.reason ? "exhausted" : "failed";
|
|
1550
|
+
this.recordRequestRetry({
|
|
1551
|
+
method,
|
|
1552
|
+
control,
|
|
1553
|
+
retryGuard,
|
|
1554
|
+
outcome,
|
|
1555
|
+
retryReason: retryGuard.retryReason,
|
|
1556
|
+
failureReason: decision.reason,
|
|
1557
|
+
error: decision.error,
|
|
1558
|
+
});
|
|
1470
1559
|
const detail =
|
|
1471
1560
|
decision.error instanceof Error ? decision.error.message : String(decision.error);
|
|
1472
1561
|
const count = retryGuard.retryCount;
|
|
1473
1562
|
throw new Error(
|
|
1474
|
-
`${detail}
|
|
1563
|
+
`${detail} Request retry ${outcome} after ${count} ${count === 1 ? "retry" : "retries"}.`,
|
|
1475
1564
|
{
|
|
1476
1565
|
cause: decision.error,
|
|
1477
1566
|
},
|
|
@@ -1481,18 +1570,20 @@ export class LspClient {
|
|
|
1481
1570
|
}
|
|
1482
1571
|
|
|
1483
1572
|
/** Record request acceptance separately from transport completion, without request content. */
|
|
1484
|
-
private
|
|
1573
|
+
private recordRequestRetry(options: {
|
|
1485
1574
|
method: string;
|
|
1486
1575
|
control?: CodeRequestControl;
|
|
1487
1576
|
retryGuard: SemanticInputRequestRetryGuard;
|
|
1488
1577
|
outcome: "retry" | "completed" | "exhausted" | "failed" | "interrupted";
|
|
1578
|
+
retryReason?: SemanticInputRequestRetryReason;
|
|
1579
|
+
failureReason?: SemanticInputRequestRetryReason;
|
|
1489
1580
|
error?: unknown;
|
|
1490
1581
|
}): void {
|
|
1491
1582
|
recordDebugEvent({
|
|
1492
1583
|
source: "lsp",
|
|
1493
1584
|
level: "debug",
|
|
1494
|
-
category: "
|
|
1495
|
-
message: "LSP
|
|
1585
|
+
category: "request.retry",
|
|
1586
|
+
message: "LSP request retry",
|
|
1496
1587
|
cwd: boundCwd(this.cwd),
|
|
1497
1588
|
...(options.control?.operationId ? { operationId: options.control.operationId } : {}),
|
|
1498
1589
|
data: {
|
|
@@ -1500,6 +1591,8 @@ export class LspClient {
|
|
|
1500
1591
|
method: truncateIdentity(options.method),
|
|
1501
1592
|
outcome: options.outcome,
|
|
1502
1593
|
retryCount: options.retryGuard.retryCount,
|
|
1594
|
+
retryReason: options.retryReason ?? options.retryGuard.retryReason ?? null,
|
|
1595
|
+
failureReason: options.failureReason ?? null,
|
|
1503
1596
|
changeKind:
|
|
1504
1597
|
options.error instanceof SemanticInputSynchronizationError
|
|
1505
1598
|
? options.error.changeKind
|