@trim21/personal-pi-extensions 0.1.612 → 0.1.619
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/package.json +1 -1
- package/src/aft/config.ts +9 -3
- package/src/aft/index.ts +6 -2
- package/src/aft/logger.ts +9 -3
- package/src/aft/tools.ts +3 -1
- package/src/bwrap/README.md +19 -10
- package/src/bwrap/approval-rules.ts +42 -14
- package/src/bwrap/approval-suggest.ts +12 -4
- package/src/bwrap/core.ts +39 -13
- package/src/bwrap/dcg-scan.ts +16 -6
- package/src/bwrap/holder.ts +3 -1
- package/src/bwrap/mihomo-config.ts +18 -6
- package/src/bwrap/network-stack.ts +69 -24
- package/src/bwrap/runtime.ts +30 -10
- package/src/bwrap/sandbox.ts +1 -1
- package/src/claude-code/common.ts +6 -2
- package/src/claude-code/edit-utils.ts +3 -1
- package/src/claude-code/files.ts +40 -14
- package/src/claude-code/glob.ts +12 -4
- package/src/claude-code/grep.ts +41 -14
- package/src/claude-code/session-tools.ts +18 -6
- package/src/claude-code/shell.ts +9 -3
- package/src/gh/base.ts +72 -24
- package/src/gh/tools/download-release-assets.ts +12 -4
- package/src/gh/tools/list-releases.ts +3 -1
- package/src/gh/tools/list-workflow-runs.ts +9 -3
- package/src/gh/tools/read-ci-logs.ts +21 -7
- package/src/gh/tools/read-repo.ts +3 -1
- package/src/lib/cli-args.ts +6 -2
- package/src/lib/cli.ts +48 -16
- package/src/lib/file-reads.ts +24 -8
- package/src/lib/github.ts +35 -12
- package/src/lib/lsp/adapter.ts +6 -2
- package/src/lib/lsp/bin.ts +12 -4
- package/src/lib/lsp/client.ts +205 -69
- package/src/lib/lsp/diagnostic.ts +6 -2
- package/src/lib/lsp/inspect-tool.ts +25 -9
- package/src/lib/lsp/lsp.ts +138 -46
- package/src/lib/lsp/rename-tool.ts +11 -4
- package/src/lib/lsp/rename.ts +25 -8
- package/src/lib/lsp/server-config.ts +41 -14
- package/src/lib/lsp/watcher.ts +51 -17
- package/src/lib/path.ts +18 -6
- package/src/lib/proc.ts +3 -1
- package/src/lib/proxy.ts +9 -3
- package/src/lib/request-policy.ts +3 -1
- package/src/lib/seq-state.ts +3 -1
- package/src/lib/ui.ts +23 -8
- package/src/lib/write-guard.ts +21 -7
- package/src/openai-cost/config.ts +3 -1
- package/src/openai-cost/cost.ts +54 -18
- package/src/openai-cost/index.ts +3 -1
- package/src/openai-cost/provider.ts +6 -2
- package/src/opencode/bash.ts +9 -3
- package/src/opencode/edit-engine.ts +24 -8
- package/src/opencode/files.ts +30 -10
- package/src/opencode/glob.ts +3 -1
- package/src/opencode/grep.ts +12 -4
- package/src/opencode/question.ts +3 -1
- package/src/opencode/ripgrep.ts +18 -6
- package/src/opencode/todo.ts +3 -1
- package/src/session-name.ts +39 -13
- package/src/spawn-agent-agents.ts +18 -6
- package/src/spawn-agent.ts +53 -18
- package/src/system-prompt/index.ts +9 -3
- package/src/talk/core.ts +143 -48
- package/src/talk/format.ts +12 -4
- package/src/talk/group.ts +9 -3
- package/src/talk/index.ts +30 -10
- package/src/talk/mailbox.ts +30 -10
- package/src/talk/policy.ts +6 -2
- package/src/talk/registry.ts +33 -11
- package/src/talk/storage.ts +3 -1
- package/src/vision-agent.ts +41 -14
- package/src/web/config.ts +3 -1
- package/src/web/fetch.ts +75 -25
- package/src/web/search.ts +30 -10
package/src/lib/lsp/client.ts
CHANGED
|
@@ -151,8 +151,12 @@ async function retryOnContentModified<T>(fn: () => Promise<T>): Promise<T> {
|
|
|
151
151
|
try {
|
|
152
152
|
return await fn();
|
|
153
153
|
} catch (error) {
|
|
154
|
-
if (!(error instanceof ResponseError && error.code === LSP_CONTENT_MODIFIED))
|
|
155
|
-
|
|
154
|
+
if (!(error instanceof ResponseError && error.code === LSP_CONTENT_MODIFIED)) {
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
if (attempt >= renameVerificationTiming.contentModifiedRetries) {
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
156
160
|
}
|
|
157
161
|
}
|
|
158
162
|
}
|
|
@@ -199,12 +203,16 @@ type DefinitionResult = LspLocation | LspLocation[] | LocationLink | LocationLin
|
|
|
199
203
|
|
|
200
204
|
/** Location / LocationLink → path + 0-based 坐标;非 file: URI 是服务器的意外行为,跳过。 */
|
|
201
205
|
function toInspectLocations(result: DefinitionResult): InspectLocation[] {
|
|
202
|
-
if (result === null)
|
|
206
|
+
if (result === null) {
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
203
209
|
const items = Array.isArray(result) ? result : [result];
|
|
204
210
|
const locations: InspectLocation[] = [];
|
|
205
211
|
for (const item of items) {
|
|
206
212
|
if ("targetUri" in item) {
|
|
207
|
-
if (!item.targetUri.startsWith("file:"))
|
|
213
|
+
if (!item.targetUri.startsWith("file:")) {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
208
216
|
const range =
|
|
209
217
|
(item as Omit<LocationLink, "targetSelectionRange"> & { targetSelectionRange?: Range })
|
|
210
218
|
.targetSelectionRange ?? item.targetRange;
|
|
@@ -214,7 +222,9 @@ function toInspectLocations(result: DefinitionResult): InspectLocation[] {
|
|
|
214
222
|
character: range.start.character,
|
|
215
223
|
});
|
|
216
224
|
} else {
|
|
217
|
-
if (!item.uri.startsWith("file:"))
|
|
225
|
+
if (!item.uri.startsWith("file:")) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
218
228
|
locations.push({
|
|
219
229
|
path: normalize(fileURLToPath(item.uri)),
|
|
220
230
|
line: item.range.start.line,
|
|
@@ -230,14 +240,18 @@ const STDERR_TAIL_CHARS = 4_000;
|
|
|
230
240
|
|
|
231
241
|
/** 展开 Error 的 cause 链为一条 message;流包装错误只包一层,逐层展开即可还原根因。 */
|
|
232
242
|
function errorChainMessage(error: unknown): string {
|
|
233
|
-
if (!(error instanceof Error))
|
|
243
|
+
if (!(error instanceof Error)) {
|
|
244
|
+
return String(error);
|
|
245
|
+
}
|
|
234
246
|
const parts: string[] = [];
|
|
235
247
|
let current: unknown = error;
|
|
236
248
|
while (current instanceof Error && current.message && !parts.includes(current.message)) {
|
|
237
249
|
parts.push(current.message);
|
|
238
250
|
current = current.cause;
|
|
239
251
|
}
|
|
240
|
-
if (typeof current === "string" || typeof current === "number")
|
|
252
|
+
if (typeof current === "string" || typeof current === "number") {
|
|
253
|
+
parts.push(String(current));
|
|
254
|
+
}
|
|
241
255
|
return parts.join(": ");
|
|
242
256
|
}
|
|
243
257
|
|
|
@@ -248,9 +262,13 @@ function describeStartupFailure(
|
|
|
248
262
|
stderrTail: string,
|
|
249
263
|
): string {
|
|
250
264
|
const parts = [errorChainMessage(error)];
|
|
251
|
-
if (exitDescription)
|
|
265
|
+
if (exitDescription) {
|
|
266
|
+
parts.push(`server ${exitDescription}`);
|
|
267
|
+
}
|
|
252
268
|
const stderr = stderrTail.trim();
|
|
253
|
-
if (stderr)
|
|
269
|
+
if (stderr) {
|
|
270
|
+
parts.push(`stderr: ${stderr}`);
|
|
271
|
+
}
|
|
254
272
|
return parts.join("; ");
|
|
255
273
|
}
|
|
256
274
|
|
|
@@ -393,14 +411,20 @@ export interface LspClient {
|
|
|
393
411
|
export type Info = LspClient;
|
|
394
412
|
|
|
395
413
|
function getFilePath(uri: string): string | undefined {
|
|
396
|
-
if (!uri.startsWith("file://"))
|
|
414
|
+
if (!uri.startsWith("file://")) {
|
|
415
|
+
return undefined;
|
|
416
|
+
}
|
|
397
417
|
return normalize(fileURLToPath(uri));
|
|
398
418
|
}
|
|
399
419
|
|
|
400
420
|
function getSyncKind(capabilities?: ServerCapabilities): number | undefined {
|
|
401
|
-
if (!capabilities)
|
|
421
|
+
if (!capabilities) {
|
|
422
|
+
return undefined;
|
|
423
|
+
}
|
|
402
424
|
const sync = capabilities.textDocumentSync;
|
|
403
|
-
if (typeof sync === "number")
|
|
425
|
+
if (typeof sync === "number") {
|
|
426
|
+
return sync;
|
|
427
|
+
}
|
|
404
428
|
return sync?.change;
|
|
405
429
|
}
|
|
406
430
|
|
|
@@ -426,16 +450,22 @@ function dedupeDiagnostics(items: Diagnostic[]): Diagnostic[] {
|
|
|
426
450
|
source: item.source,
|
|
427
451
|
range: item.range,
|
|
428
452
|
});
|
|
429
|
-
if (seen.has(key))
|
|
453
|
+
if (seen.has(key)) {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
430
456
|
seen.add(key);
|
|
431
457
|
return true;
|
|
432
458
|
});
|
|
433
459
|
}
|
|
434
460
|
|
|
435
461
|
function configurationValue(settings: unknown, section?: string): unknown {
|
|
436
|
-
if (!section)
|
|
462
|
+
if (!section) {
|
|
463
|
+
return settings ?? null;
|
|
464
|
+
}
|
|
437
465
|
const result = section.split(".").reduce<unknown>((acc, key) => {
|
|
438
|
-
if (!acc || typeof acc !== "object" || !(key in acc))
|
|
466
|
+
if (!acc || typeof acc !== "object" || !(key in acc)) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
439
469
|
return (acc as Record<string, unknown>)[key];
|
|
440
470
|
}, settings);
|
|
441
471
|
return result ?? null;
|
|
@@ -451,7 +481,9 @@ async function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
|
|
|
451
481
|
}),
|
|
452
482
|
]);
|
|
453
483
|
} finally {
|
|
454
|
-
if (timer)
|
|
484
|
+
if (timer) {
|
|
485
|
+
clearTimeout(timer);
|
|
486
|
+
}
|
|
455
487
|
}
|
|
456
488
|
}
|
|
457
489
|
|
|
@@ -464,7 +496,9 @@ function abortReason(signal: AbortSignal): Error {
|
|
|
464
496
|
|
|
465
497
|
/** 可中止的 sleep:中止时以 signal.reason 拒绝,而不是白等到轮询间隔结束。 */
|
|
466
498
|
function sleepWithSignal(ms: number, signal: AbortSignal | undefined): Promise<void> {
|
|
467
|
-
if (!signal)
|
|
499
|
+
if (!signal) {
|
|
500
|
+
return sleep(ms);
|
|
501
|
+
}
|
|
468
502
|
return new Promise<void>((resolve, reject) => {
|
|
469
503
|
const onAbort = (): void => {
|
|
470
504
|
clearTimeout(timer);
|
|
@@ -486,7 +520,9 @@ function sleepWithSignal(ms: number, signal: AbortSignal | undefined): Promise<v
|
|
|
486
520
|
const PULL_RETRY_INTERVAL_MS = 100;
|
|
487
521
|
|
|
488
522
|
function stopProcess(process: LspServerHandle["process"]): Promise<void> {
|
|
489
|
-
if (process.exitCode !== null)
|
|
523
|
+
if (process.exitCode !== null) {
|
|
524
|
+
return Promise.resolve();
|
|
525
|
+
}
|
|
490
526
|
try {
|
|
491
527
|
process.kill();
|
|
492
528
|
} catch {
|
|
@@ -530,8 +566,11 @@ export function evictionPlan(options: {
|
|
|
530
566
|
const stale: string[] = [];
|
|
531
567
|
const evictable: string[] = [];
|
|
532
568
|
for (const path of options.order) {
|
|
533
|
-
if (!options.isResident(path))
|
|
534
|
-
|
|
569
|
+
if (!options.isResident(path)) {
|
|
570
|
+
stale.push(path);
|
|
571
|
+
} else if (!options.isWaiting(path)) {
|
|
572
|
+
evictable.push(path);
|
|
573
|
+
}
|
|
535
574
|
}
|
|
536
575
|
const excess = options.order.length - stale.length - options.maxOpenDocuments;
|
|
537
576
|
return { stale, evict: excess > 0 ? evictable.slice(0, excess) : [] };
|
|
@@ -591,14 +630,17 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
591
630
|
]);
|
|
592
631
|
const updatePushDiagnostics = (filePath: string, next: Diagnostic[]): void => {
|
|
593
632
|
pushDiagnostics.set(filePath, next);
|
|
594
|
-
for (const listener of diagnosticListeners)
|
|
633
|
+
for (const listener of diagnosticListeners) {
|
|
595
634
|
listener({ path: filePath, serverID: input.serverID });
|
|
635
|
+
}
|
|
596
636
|
};
|
|
597
637
|
const updatePullDiagnostics = (filePath: string, next: Diagnostic[]): void => {
|
|
598
638
|
pullDiagnostics.set(filePath, next);
|
|
599
639
|
};
|
|
600
640
|
const emitRegistrationChange = (): void => {
|
|
601
|
-
for (const listener of registrationListeners)
|
|
641
|
+
for (const listener of registrationListeners) {
|
|
642
|
+
listener();
|
|
643
|
+
}
|
|
602
644
|
};
|
|
603
645
|
|
|
604
646
|
// ── LSP 连接处理器 ─────────────────────────────────────────────────────────
|
|
@@ -607,7 +649,9 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
607
649
|
"textDocument/publishDiagnostics",
|
|
608
650
|
(params: { uri: string; version?: number; diagnostics: Diagnostic[] }) => {
|
|
609
651
|
const filePath = getFilePath(params.uri);
|
|
610
|
-
if (!filePath)
|
|
652
|
+
if (!filePath) {
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
611
655
|
// 服务器版本滞后于已发送版本时,该 push 对应的是旧内容(异步重算未完成
|
|
612
656
|
// 时的迟到结果)。忽略,避免与当前版本结果混淆。
|
|
613
657
|
const currentVersion = documentVersions.get(filePath);
|
|
@@ -615,13 +659,17 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
615
659
|
typeof params.version === "number" &&
|
|
616
660
|
currentVersion !== undefined &&
|
|
617
661
|
params.version !== currentVersion;
|
|
618
|
-
if (isStalePush)
|
|
662
|
+
if (isStalePush) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
619
665
|
published.set(filePath, {
|
|
620
666
|
at: Date.now(),
|
|
621
667
|
version: typeof params.version === "number" ? params.version : undefined,
|
|
622
668
|
});
|
|
623
669
|
const document = files[filePath];
|
|
624
|
-
if (document !== undefined)
|
|
670
|
+
if (document !== undefined) {
|
|
671
|
+
document.lastPushEmpty = params.diagnostics.length === 0;
|
|
672
|
+
}
|
|
625
673
|
updatePushDiagnostics(filePath, params.diagnostics);
|
|
626
674
|
},
|
|
627
675
|
);
|
|
@@ -651,7 +699,9 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
651
699
|
changed = true;
|
|
652
700
|
}
|
|
653
701
|
}
|
|
654
|
-
if (changed)
|
|
702
|
+
if (changed) {
|
|
703
|
+
emitRegistrationChange();
|
|
704
|
+
}
|
|
655
705
|
});
|
|
656
706
|
connection.onRequest("client/unregisterCapability", (params) => {
|
|
657
707
|
const registrations =
|
|
@@ -665,7 +715,9 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
665
715
|
changed = true;
|
|
666
716
|
}
|
|
667
717
|
}
|
|
668
|
-
if (changed)
|
|
718
|
+
if (changed) {
|
|
719
|
+
emitRegistrationChange();
|
|
720
|
+
}
|
|
669
721
|
});
|
|
670
722
|
connection.onRequest("workspace/workspaceFolders", () => [
|
|
671
723
|
{ name: "workspace", uri: pathToFileURL(input.root).href },
|
|
@@ -762,7 +814,9 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
762
814
|
isWaiting: (path) => waitingForDiagnostics.has(path),
|
|
763
815
|
maxOpenDocuments,
|
|
764
816
|
});
|
|
765
|
-
for (const path of plan.stale)
|
|
817
|
+
for (const path of plan.stale) {
|
|
818
|
+
lruOrder.delete(path);
|
|
819
|
+
}
|
|
766
820
|
for (const path of plan.evict) {
|
|
767
821
|
lruOrder.delete(path);
|
|
768
822
|
await connection.sendNotification("textDocument/didClose", {
|
|
@@ -794,7 +848,9 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
794
848
|
}
|
|
795
849
|
}
|
|
796
850
|
|
|
797
|
-
if (matched && !merged.has(filePath))
|
|
851
|
+
if (matched && !merged.has(filePath)) {
|
|
852
|
+
merged.set(filePath, []);
|
|
853
|
+
}
|
|
798
854
|
for (const [target, items] of merged) {
|
|
799
855
|
updatePullDiagnostics(target, dedupeDiagnostics(items));
|
|
800
856
|
}
|
|
@@ -814,7 +870,9 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
814
870
|
}),
|
|
815
871
|
diagnosticsRequestTimeoutMs,
|
|
816
872
|
).catch((error: unknown) => {
|
|
817
|
-
if (error instanceof Error && error.message.startsWith("Timeout after"))
|
|
873
|
+
if (error instanceof Error && error.message.startsWith("Timeout after")) {
|
|
874
|
+
timedOut = true;
|
|
875
|
+
}
|
|
818
876
|
return null;
|
|
819
877
|
});
|
|
820
878
|
if (!report) {
|
|
@@ -836,7 +894,9 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
836
894
|
}
|
|
837
895
|
for (const [uri, related] of Object.entries(report.relatedDocuments ?? {})) {
|
|
838
896
|
const relatedPath = getFilePath(uri);
|
|
839
|
-
if (!relatedPath || !Array.isArray(related.items))
|
|
897
|
+
if (!relatedPath || !Array.isArray(related.items)) {
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
840
900
|
push(relatedPath, related.items);
|
|
841
901
|
handled = true;
|
|
842
902
|
matched ||= relatedPath === filePath;
|
|
@@ -857,7 +917,9 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
857
917
|
}),
|
|
858
918
|
diagnosticsRequestTimeoutMs,
|
|
859
919
|
).catch((error: unknown) => {
|
|
860
|
-
if (error instanceof Error && error.message.startsWith("Timeout after"))
|
|
920
|
+
if (error instanceof Error && error.message.startsWith("Timeout after")) {
|
|
921
|
+
timedOut = true;
|
|
922
|
+
}
|
|
861
923
|
return null;
|
|
862
924
|
});
|
|
863
925
|
if (!report) {
|
|
@@ -868,7 +930,9 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
868
930
|
let matched = false;
|
|
869
931
|
for (const item of report.items ?? []) {
|
|
870
932
|
const relatedPath = item.uri ? getFilePath(item.uri) : undefined;
|
|
871
|
-
if (!relatedPath || !Array.isArray(item.items))
|
|
933
|
+
if (!relatedPath || !Array.isArray(item.items)) {
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
872
936
|
const existing = byFile.get(relatedPath) ?? [];
|
|
873
937
|
byFile.set(relatedPath, [...existing, ...item.items]);
|
|
874
938
|
matched ||= relatedPath === filePath;
|
|
@@ -906,15 +970,21 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
906
970
|
requests: Promise<DiagnosticRequestResult>[],
|
|
907
971
|
done: (results: DiagnosticRequestResult[]) => boolean,
|
|
908
972
|
): Promise<PullResult> {
|
|
909
|
-
if (requests.length === 0)
|
|
973
|
+
if (requests.length === 0) {
|
|
974
|
+
return { handled: false, matched: false, timedOut: false };
|
|
975
|
+
}
|
|
910
976
|
|
|
911
977
|
return new Promise<PullResult>((resolve) => {
|
|
912
978
|
const results: DiagnosticRequestResult[] = [];
|
|
913
979
|
let pending = requests.length;
|
|
914
980
|
let resolved = false;
|
|
915
981
|
const finish = (merged: PullResult, force = false) => {
|
|
916
|
-
if (resolved)
|
|
917
|
-
|
|
982
|
+
if (resolved) {
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
if (!force && !done(results)) {
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
918
988
|
resolved = true;
|
|
919
989
|
resolve(merged);
|
|
920
990
|
};
|
|
@@ -926,12 +996,16 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
926
996
|
pending -= 1;
|
|
927
997
|
const merged = mergeResults(filePath, results);
|
|
928
998
|
finish(merged);
|
|
929
|
-
if (pending === 0)
|
|
999
|
+
if (pending === 0) {
|
|
1000
|
+
finish(merged, true);
|
|
1001
|
+
}
|
|
930
1002
|
return;
|
|
931
1003
|
})
|
|
932
1004
|
.catch(() => {
|
|
933
1005
|
pending -= 1;
|
|
934
|
-
if (pending === 0)
|
|
1006
|
+
if (pending === 0) {
|
|
1007
|
+
finish(mergeResults(filePath, results), true);
|
|
1008
|
+
}
|
|
935
1009
|
return;
|
|
936
1010
|
});
|
|
937
1011
|
}
|
|
@@ -942,7 +1016,9 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
942
1016
|
// 慢的 pull 继续在后台合并,不按 identifier 串行。见 opencode PR #23771。
|
|
943
1017
|
async function requestDocumentDiagnostics(filePath: string): Promise<PullResult> {
|
|
944
1018
|
const state = documentPullState();
|
|
945
|
-
if (!state.supported)
|
|
1019
|
+
if (!state.supported) {
|
|
1020
|
+
return { handled: false, matched: false, timedOut: false };
|
|
1021
|
+
}
|
|
946
1022
|
return requestDiagnostics(
|
|
947
1023
|
filePath,
|
|
948
1024
|
[
|
|
@@ -977,12 +1053,16 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
977
1053
|
}
|
|
978
1054
|
|
|
979
1055
|
function waitForRegistrationChange(timeout: number): Promise<boolean> {
|
|
980
|
-
if (timeout <= 0)
|
|
1056
|
+
if (timeout <= 0) {
|
|
1057
|
+
return Promise.resolve(false);
|
|
1058
|
+
}
|
|
981
1059
|
return new Promise<boolean>((resolve) => {
|
|
982
1060
|
let finished = false;
|
|
983
1061
|
const timer = setTimeout(() => finish(false), timeout);
|
|
984
1062
|
const finish = (result: boolean) => {
|
|
985
|
-
if (finished)
|
|
1063
|
+
if (finished) {
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
986
1066
|
finished = true;
|
|
987
1067
|
clearTimeout(timer);
|
|
988
1068
|
registrationListeners.delete(listener);
|
|
@@ -999,26 +1079,40 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
999
1079
|
after: number;
|
|
1000
1080
|
timeout: number;
|
|
1001
1081
|
}): Promise<boolean> {
|
|
1002
|
-
if (request.timeout <= 0)
|
|
1082
|
+
if (request.timeout <= 0) {
|
|
1083
|
+
return Promise.resolve(false);
|
|
1084
|
+
}
|
|
1003
1085
|
return new Promise<boolean>((resolve) => {
|
|
1004
1086
|
let finished = false;
|
|
1005
1087
|
let debounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
1006
1088
|
const timeoutTimer = setTimeout(() => finish(false), request.timeout);
|
|
1007
1089
|
const unsub = () => diagnosticListeners.delete(listener);
|
|
1008
1090
|
const finish = (result: boolean) => {
|
|
1009
|
-
if (finished)
|
|
1091
|
+
if (finished) {
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1010
1094
|
finished = true;
|
|
1011
|
-
if (debounceTimer)
|
|
1095
|
+
if (debounceTimer) {
|
|
1096
|
+
clearTimeout(debounceTimer);
|
|
1097
|
+
}
|
|
1012
1098
|
clearTimeout(timeoutTimer);
|
|
1013
1099
|
unsub();
|
|
1014
1100
|
resolve(result);
|
|
1015
1101
|
};
|
|
1016
1102
|
const schedule = () => {
|
|
1017
1103
|
const hit = published.get(request.path);
|
|
1018
|
-
if (!hit)
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
if (
|
|
1104
|
+
if (!hit) {
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
if (typeof hit.version === "number" && hit.version !== request.version) {
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
if (hit.at < request.after && hit.version !== request.version) {
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
if (debounceTimer) {
|
|
1114
|
+
clearTimeout(debounceTimer);
|
|
1115
|
+
}
|
|
1022
1116
|
debounceTimer = setTimeout(
|
|
1023
1117
|
() => finish(true),
|
|
1024
1118
|
Math.max(0, diagnosticsDebounceMs - (Date.now() - hit.at)),
|
|
@@ -1026,7 +1120,9 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
1026
1120
|
};
|
|
1027
1121
|
|
|
1028
1122
|
const listener = (event: { path: string; serverID: string }) => {
|
|
1029
|
-
if (event.path !== request.path || event.serverID !== input.serverID)
|
|
1123
|
+
if (event.path !== request.path || event.serverID !== input.serverID) {
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1030
1126
|
schedule();
|
|
1031
1127
|
};
|
|
1032
1128
|
diagnosticListeners.add(listener);
|
|
@@ -1051,7 +1147,9 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
1051
1147
|
// 耗满整个窗口后得到同样的「无诊断」。
|
|
1052
1148
|
const known = published.get(request.path);
|
|
1053
1149
|
const document = files[request.path];
|
|
1054
|
-
if (known !== undefined && document !== undefined && known.at >= document.syncedAt)
|
|
1150
|
+
if (known !== undefined && document !== undefined && known.at >= document.syncedAt) {
|
|
1151
|
+
return true;
|
|
1152
|
+
}
|
|
1055
1153
|
|
|
1056
1154
|
const startedAt = request.after ?? Date.now();
|
|
1057
1155
|
// 「上一份 push 为空」的文档用短安静期(见 clientDefaults.diagnosticsSilentWaitTimeoutMs)。
|
|
@@ -1071,9 +1169,13 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
1071
1169
|
|
|
1072
1170
|
while (!connectionClosed && !request.signal?.aborted) {
|
|
1073
1171
|
const remaining = budget - (Date.now() - startedAt);
|
|
1074
|
-
if (remaining <= 0)
|
|
1172
|
+
if (remaining <= 0) {
|
|
1173
|
+
return false;
|
|
1174
|
+
}
|
|
1075
1175
|
const result = await requestDocumentDiagnostics(request.path);
|
|
1076
|
-
if (result.matched)
|
|
1176
|
+
if (result.matched) {
|
|
1177
|
+
return true;
|
|
1178
|
+
}
|
|
1077
1179
|
if (result.timedOut) {
|
|
1078
1180
|
return await pushWait;
|
|
1079
1181
|
}
|
|
@@ -1084,7 +1186,9 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
1084
1186
|
),
|
|
1085
1187
|
sleep(Math.min(remaining, PULL_RETRY_INTERVAL_MS)).then(() => "interval" as const),
|
|
1086
1188
|
]);
|
|
1087
|
-
if (next === "push")
|
|
1189
|
+
if (next === "push") {
|
|
1190
|
+
return true;
|
|
1191
|
+
}
|
|
1088
1192
|
}
|
|
1089
1193
|
return false;
|
|
1090
1194
|
}
|
|
@@ -1105,9 +1209,13 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
1105
1209
|
|
|
1106
1210
|
while (!connectionClosed && !request.signal?.aborted) {
|
|
1107
1211
|
const remaining = diagnosticsFullWaitTimeoutMs - (Date.now() - startedAt);
|
|
1108
|
-
if (remaining <= 0)
|
|
1212
|
+
if (remaining <= 0) {
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1109
1215
|
const result = await requestFullDiagnostics(request.path);
|
|
1110
|
-
if (result.handled || result.matched)
|
|
1216
|
+
if (result.handled || result.matched) {
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1111
1219
|
if (result.timedOut) {
|
|
1112
1220
|
await pushWait;
|
|
1113
1221
|
return;
|
|
@@ -1119,7 +1227,9 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
1119
1227
|
),
|
|
1120
1228
|
sleep(Math.min(remaining, PULL_RETRY_INTERVAL_MS)).then(() => "interval" as const),
|
|
1121
1229
|
]);
|
|
1122
|
-
if (next === "push")
|
|
1230
|
+
if (next === "push") {
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1123
1233
|
}
|
|
1124
1234
|
}
|
|
1125
1235
|
|
|
@@ -1206,8 +1316,12 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
1206
1316
|
params: object,
|
|
1207
1317
|
signal: AbortSignal | undefined,
|
|
1208
1318
|
): Promise<T> {
|
|
1209
|
-
if (!signal)
|
|
1210
|
-
|
|
1319
|
+
if (!signal) {
|
|
1320
|
+
return await connection.sendRequest<T>(method, params);
|
|
1321
|
+
}
|
|
1322
|
+
if (signal.aborted) {
|
|
1323
|
+
throw abortReason(signal);
|
|
1324
|
+
}
|
|
1211
1325
|
const source = new CancellationTokenSource();
|
|
1212
1326
|
const aborted = Promise.withResolvers<never>();
|
|
1213
1327
|
// 中止可能恰好在请求已返回之后才触发:那时 race 已经结算,而这个 promise 的
|
|
@@ -1276,7 +1390,9 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
1276
1390
|
const document = files[resolvedPath];
|
|
1277
1391
|
if (document !== undefined) {
|
|
1278
1392
|
// 写后诊断等待中的文档不退场(didClose 可能抹掉本次写入的诊断结果)
|
|
1279
|
-
if (waitingForDiagnostics.has(resolvedPath))
|
|
1393
|
+
if (waitingForDiagnostics.has(resolvedPath)) {
|
|
1394
|
+
continue;
|
|
1395
|
+
}
|
|
1280
1396
|
// 驻留文档被外部改动:内容一致的自身写入 echo 完全忽略;否则先 didClose
|
|
1281
1397
|
// 让服务器回落磁盘,再以文件事件通知——不 bump 版本,避免与写后等待竞态。
|
|
1282
1398
|
if (change.type === "changed") {
|
|
@@ -1286,7 +1402,9 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
1286
1402
|
} catch {
|
|
1287
1403
|
// 文件已被删除或不可读:按磁盘状态变化处理
|
|
1288
1404
|
}
|
|
1289
|
-
if (disk === document.text)
|
|
1405
|
+
if (disk === document.text) {
|
|
1406
|
+
continue;
|
|
1407
|
+
}
|
|
1290
1408
|
}
|
|
1291
1409
|
await connection.sendNotification("textDocument/didClose", {
|
|
1292
1410
|
textDocument: { uri: pathToFileURL(resolvedPath).href },
|
|
@@ -1299,7 +1417,9 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
1299
1417
|
type: FILE_CHANGE_TYPE[change.type],
|
|
1300
1418
|
});
|
|
1301
1419
|
}
|
|
1302
|
-
if (notified.length === 0)
|
|
1420
|
+
if (notified.length === 0) {
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1303
1423
|
await connection.sendNotification("workspace/didChangeWatchedFiles", {
|
|
1304
1424
|
changes: notified,
|
|
1305
1425
|
});
|
|
@@ -1405,17 +1525,25 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
1405
1525
|
}
|
|
1406
1526
|
throw error;
|
|
1407
1527
|
}
|
|
1408
|
-
if (!prepared)
|
|
1409
|
-
|
|
1528
|
+
if (!prepared) {
|
|
1529
|
+
throw notRenameable();
|
|
1530
|
+
}
|
|
1531
|
+
if (typeof prepared.placeholder === "string") {
|
|
1532
|
+
placeholder = prepared.placeholder;
|
|
1533
|
+
}
|
|
1410
1534
|
}
|
|
1411
1535
|
|
|
1412
1536
|
let locations: { uri: string }[] | null;
|
|
1413
1537
|
try {
|
|
1414
1538
|
locations = await referencesRequest();
|
|
1415
1539
|
} catch (error) {
|
|
1416
|
-
if (!(error instanceof ResponseError && error.code === LSP_METHOD_NOT_FOUND))
|
|
1540
|
+
if (!(error instanceof ResponseError && error.code === LSP_METHOD_NOT_FOUND)) {
|
|
1541
|
+
throw error;
|
|
1542
|
+
}
|
|
1417
1543
|
const edit = await sendRename();
|
|
1418
|
-
if (!edit)
|
|
1544
|
+
if (!edit) {
|
|
1545
|
+
throw notRenameable();
|
|
1546
|
+
}
|
|
1419
1547
|
return placeholder === undefined ? { edit } : { edit, placeholder };
|
|
1420
1548
|
}
|
|
1421
1549
|
|
|
@@ -1439,18 +1567,26 @@ export async function create(input: CreateInput): Promise<LspClient> {
|
|
|
1439
1567
|
const expired = now >= deadline;
|
|
1440
1568
|
if (stable || expired) {
|
|
1441
1569
|
const edit = await sendRename();
|
|
1442
|
-
if (!edit)
|
|
1570
|
+
if (!edit) {
|
|
1571
|
+
throw notRenameable();
|
|
1572
|
+
}
|
|
1443
1573
|
const editPaths = editFilePaths(edit);
|
|
1444
1574
|
const missing: string[] = [];
|
|
1445
1575
|
const extra: string[] = [];
|
|
1446
1576
|
for (const path of stability.paths) {
|
|
1447
|
-
if (!editPaths.has(path))
|
|
1577
|
+
if (!editPaths.has(path)) {
|
|
1578
|
+
missing.push(path);
|
|
1579
|
+
}
|
|
1448
1580
|
}
|
|
1449
1581
|
for (const path of editPaths) {
|
|
1450
|
-
if (!stability.paths.has(path))
|
|
1582
|
+
if (!stability.paths.has(path)) {
|
|
1583
|
+
extra.push(path);
|
|
1584
|
+
}
|
|
1451
1585
|
}
|
|
1452
1586
|
if (missing.length === 0 && extra.length === 0) {
|
|
1453
|
-
if (stable)
|
|
1587
|
+
if (stable) {
|
|
1588
|
+
return placeholder === undefined ? { edit } : { edit, placeholder };
|
|
1589
|
+
}
|
|
1454
1590
|
// 双向一致但稳定窗口未达标:索引可能仍在加载、残缺答案假稳定,
|
|
1455
1591
|
// 宁可报可重试的不完整错误,也不把残缺结果当成功写盘。
|
|
1456
1592
|
throw new RenameIncompleteError([], []);
|
|
@@ -60,7 +60,9 @@ export function report(file: string, issues: Diagnostic[]): DiagnosticReport {
|
|
|
60
60
|
const errors = issues.filter((item) => severityOf(item) === 1);
|
|
61
61
|
const warnings = issues.filter((item) => item.severity === 2);
|
|
62
62
|
const relevant = [...errors, ...warnings];
|
|
63
|
-
if (relevant.length === 0)
|
|
63
|
+
if (relevant.length === 0) {
|
|
64
|
+
return EMPTY_DIAGNOSTIC_REPORT;
|
|
65
|
+
}
|
|
64
66
|
const limited = relevant.slice(0, MAX_PER_FILE);
|
|
65
67
|
const hidden = relevant.slice(MAX_PER_FILE);
|
|
66
68
|
const suffix = hidden.length > 0 ? `\n... and ${hiddenSummary(hidden)}` : "";
|
|
@@ -73,6 +75,8 @@ export function report(file: string, issues: Diagnostic[]): DiagnosticReport {
|
|
|
73
75
|
|
|
74
76
|
/** 把 LSP 诊断块接到工具成功文案后面;无诊断时原样返回。 */
|
|
75
77
|
export function appendLspDiagnosticText(message: string, diagnosticText: string): string {
|
|
76
|
-
if (diagnosticText === "")
|
|
78
|
+
if (diagnosticText === "") {
|
|
79
|
+
return message;
|
|
80
|
+
}
|
|
77
81
|
return `${message}\n\nLSP diagnostics detected in this file\n${diagnosticText}`;
|
|
78
82
|
}
|