@mrclrchtr/supi-code-intelligence 4.9.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 +2 -1
- 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/debug-timing.ts +34 -21
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
- package/node_modules/@mrclrchtr/supi-lsp/README.md +80 -5
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -3
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +34 -21
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
- package/node_modules/@mrclrchtr/supi-lsp/package.json +7 -6
- package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +10 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +206 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +78 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-collection.ts +114 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +372 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +14 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-pull.ts +62 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +491 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-request.ts +9 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +215 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-waiters.ts +206 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +370 -266
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +52 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +185 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-file-state.ts +25 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +467 -115
- package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +238 -11
- package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +10 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/config/defaults.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/debug-telemetry.ts +45 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +38 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +79 -9
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +13 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +300 -24
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +5 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +464 -96
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-refactor-provider.ts +130 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-semantic-provider.ts +45 -329
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/refactor-planning.ts +57 -120
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-edit-normalizer.ts +320 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-symbol-mapper.ts +207 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/readiness.ts +72 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-controller.ts +151 -26
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +40 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +50 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +136 -120
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-transition-debug.ts +49 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +102 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +26 -6
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +34 -21
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/LICENSE +21 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/README.md +258 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/dist/babel.cjs +257 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/dist/jiti.cjs +1 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-cli.mjs +34 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-hooks.mjs +124 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-native.mjs +121 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-register.d.mts +1 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-register.mjs +4 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-static.mjs +23 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.cjs +30 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.d.cts +8 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.d.mts +8 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.mjs +29 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/types.d.ts +420 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/package.json +146 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +7 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/api.ts +0 -9
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +0 -8
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +16 -12
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-controller.ts +65 -87
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/session.ts +47 -95
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-timing.ts +147 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client-lifecycle.ts +39 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client.ts +561 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-message-size.ts +24 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-protocol.ts +452 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-sites.ts +4 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +12 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/exports.ts +4 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/imports.ts +4 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/node-at.ts +5 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +33 -12
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/blocking-test-bootstrap.mjs +34 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/bootstrap.mjs +8 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store-helpers.ts +23 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store.ts +400 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/request-control.ts +48 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime-parser-helpers.ts +24 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime-query-helpers.ts +43 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime.ts +421 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/service.ts +116 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/worker-main.ts +201 -0
- package/package.json +5 -5
- package/src/analysis/health/diagnostics.ts +236 -24
- package/src/analysis/health/recovery.ts +6 -2
- package/src/analysis/provider.ts +58 -0
- package/src/analysis/readiness.ts +30 -9
- package/src/analysis/refactor/apply.ts +59 -20
- package/src/analysis/refactor/mutation-authority.ts +143 -0
- package/src/analysis/refactor/position.ts +47 -0
- package/src/analysis/refactor/safety.ts +18 -10
- package/src/analysis/relations/callees.ts +9 -1
- package/src/analysis/relations/types.ts +5 -2
- package/src/analysis/search/ast-scan-timing.ts +9 -4
- package/src/analysis/search/ast-scan.ts +1 -1
- package/src/analysis/search/deadline.ts +2 -2
- package/src/analysis/search/pattern-analysis.ts +46 -32
- package/src/analysis/search/pattern.ts +12 -2
- package/src/analysis/target/anchored.ts +20 -11
- package/src/analysis/target/file.ts +18 -11
- package/src/analysis/target/identity.ts +19 -3
- package/src/analysis/target/symbol.ts +36 -15
- package/src/api.ts +4 -0
- package/src/diagnostics/evidence.ts +51 -0
- package/src/session/capability-adapter.ts +12 -3
- package/src/session/find-workflow.ts +17 -7
- package/src/session/graph/collect.ts +18 -2
- package/src/session/graph-workflow.ts +19 -12
- package/src/session/health-refresh.ts +148 -0
- package/src/session/health-types.ts +37 -15
- package/src/session/health-workflow.ts +37 -39
- package/src/session/inspect/collect.ts +20 -3
- package/src/session/inspect-workflow.ts +10 -5
- package/src/session/orientation/collect.ts +10 -1
- package/src/session/orientation/context-facts.ts +3 -1
- package/src/session/orientation/context-sections.ts +20 -7
- package/src/session/orientation/gather.ts +17 -11
- package/src/session/orientation-types.ts +2 -1
- package/src/session/orientation-workflow.ts +15 -8
- package/src/session/refactor-plans.ts +2 -0
- package/src/session/refactor-workflow.ts +59 -14
- package/src/session/session.ts +4 -1
- package/src/session/target-workflow.ts +54 -23
- package/src/session/workflow-control.ts +7 -3
- package/src/substrate/lsp/lifecycle.ts +26 -46
- package/src/substrate/lsp/maintenance.ts +199 -25
- package/src/substrate/lsp/state.ts +2 -2
- package/src/substrate/workspace-provider-host.ts +25 -3
- package/src/tool/find/execute.ts +7 -1
- package/src/tool/find/render.ts +20 -3
- package/src/tool/find/tui.ts +23 -21
- package/src/tool/graph/execute.ts +23 -3
- package/src/tool/graph/tui.ts +59 -46
- package/src/tool/health/execute.ts +7 -2
- package/src/tool/health/markdown.ts +56 -14
- package/src/tool/health/refresh-status.ts +105 -0
- package/src/tool/health/tui.ts +95 -98
- package/src/tool/infra/workflow-control.ts +8 -0
- package/src/tool/inspect/execute.ts +7 -1
- package/src/tool/inspect/tui.ts +26 -18
- package/src/tool/orientation/execute.ts +21 -3
- package/src/tool/orientation/tui.ts +51 -35
- package/src/tool/refactor-apply/execute.ts +3 -0
- package/src/tool/refactor-apply/tui.ts +43 -25
- package/src/tool/refactor-plan/execute.ts +4 -0
- package/src/tool/refactor-plan/tui.ts +33 -20
- package/src/tool/register.ts +127 -22
- package/src/tool/resolve/execute.ts +18 -1
- package/src/tool/resolve/tui.ts +42 -24
- package/src/tool/result/display.ts +107 -0
- package/src/tool/result/errors.ts +20 -17
- package/src/tool/result/find.ts +52 -8
- package/src/tool/result/graph.ts +44 -1
- package/src/tool/result/health.ts +53 -1
- package/src/tool/result/inspect.ts +113 -1
- package/src/tool/result/orientation.ts +66 -14
- package/src/tool/result/refactor.ts +36 -4
- package/src/tool/result/resolve.ts +44 -7
- package/src/tool/result/types.ts +20 -0
- package/src/types/execution.ts +18 -5
- package/src/types/index.ts +8 -1
- package/src/ui/status-command.ts +5 -3
- package/src/ui/status-overlay.ts +4 -0
- package/src/ui/tui/common.ts +108 -10
- package/src/ui/tui/display.ts +48 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime.ts +0 -247
|
@@ -1,54 +1,63 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from "../
|
|
1
|
+
// biome-ignore-all lint/style/noExcessiveLinesPerFile: one client's document sync, diagnostics, refresh, and sync-file flow stay in one cohesive class.
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
type CodeQueryResult,
|
|
5
|
+
type CodeRequestControl,
|
|
6
|
+
completedCodeQuery,
|
|
7
|
+
throwIfCodeRequestInterrupted,
|
|
8
|
+
unavailableCodeQuery,
|
|
9
|
+
} from "@mrclrchtr/supi-code-runtime/api";
|
|
10
|
+
import type { Diagnostic, TextDocumentItem } from "../config/types.ts";
|
|
11
|
+
import type { DiagnosticEvidenceSummary } from "../diagnostics/evidence.ts";
|
|
12
12
|
import { detectLanguageId, fileToUri, uriToFile } from "../utils.ts";
|
|
13
|
+
import { applyPushDiagnostics, buildClientDiagnosticSnapshot } from "./client-diagnostic-cache.ts";
|
|
14
|
+
import { collectSynchronizedFileDiagnostics } from "./client-diagnostic-collection.ts";
|
|
15
|
+
import {
|
|
16
|
+
type DiagnosticCacheEntry,
|
|
17
|
+
type DiagnosticSynchronization,
|
|
18
|
+
hasFreshPush,
|
|
19
|
+
isCurrentSynchronization,
|
|
20
|
+
isValidPublishDiagnosticsParams,
|
|
21
|
+
nextDocumentVersion,
|
|
22
|
+
} from "./client-diagnostic-evidence.ts";
|
|
23
|
+
import type { ClientDiagnosticsHost } from "./client-diagnostic-host.ts";
|
|
24
|
+
import {
|
|
25
|
+
pullClientDiagnosticEvidenceFromHost,
|
|
26
|
+
refreshClientOpenDiagnostics,
|
|
27
|
+
sendDidCloseNotification,
|
|
28
|
+
} from "./client-diagnostic-refresh.ts";
|
|
29
|
+
import { DiagnosticObserver, type DiagnosticPushWaitOutcome } from "./client-diagnostic-timing.ts";
|
|
30
|
+
import { DiagnosticWaitRegistry } from "./client-diagnostic-waiters.ts";
|
|
31
|
+
import {
|
|
32
|
+
type ClientDiagnosticSnapshot,
|
|
33
|
+
type DiagnosticEntry,
|
|
34
|
+
fingerprintDocumentContent,
|
|
35
|
+
hasCurrentDiagnosticEvidence,
|
|
36
|
+
type OpenDocumentState,
|
|
37
|
+
} from "./client-document-state.ts";
|
|
38
|
+
import {
|
|
39
|
+
clearTrackedDocumentState,
|
|
40
|
+
reopenDocument,
|
|
41
|
+
synchronizeTrackedDocument,
|
|
42
|
+
} from "./client-document-sync.ts";
|
|
43
|
+
import { getDiagnosticFileState } from "./client-file-state.ts";
|
|
13
44
|
|
|
14
45
|
const DIAGNOSTIC_WAIT_MS = 3_000;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
type DiagnosticCacheEntry = {
|
|
19
|
-
diagnostics: Diagnostic[];
|
|
20
|
-
receivedAt: number;
|
|
21
|
-
version?: number;
|
|
22
|
-
resultId?: string;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/** One file's stored diagnostics as consumed by manager-level collection. */
|
|
26
|
-
export interface DiagnosticEntry {
|
|
27
|
-
uri: string;
|
|
28
|
-
diagnostics: Diagnostic[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface ClientDiagnosticsHost {
|
|
32
|
-
isOperational(): boolean;
|
|
33
|
-
supportsPullDiagnostics(): boolean;
|
|
34
|
-
sendNotification(method: string, params: unknown): void;
|
|
35
|
-
pullDocumentDiagnostics(
|
|
36
|
-
uri: string,
|
|
37
|
-
previousResultId: string | undefined,
|
|
38
|
-
timeoutMs: number,
|
|
39
|
-
): Promise<DocumentDiagnosticReport | null>;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Owns one LSP client's open documents, diagnostic cache, waiters, and refresh policy.
|
|
44
|
-
*
|
|
45
|
-
* The host supplies transport and readiness operations. Callers use `LspClient` as
|
|
46
|
-
* the external seam and do not access this state directly.
|
|
47
|
-
*/
|
|
46
|
+
/** Bounded push wait after a reopen-resync fallback, in milliseconds. */
|
|
47
|
+
const REOPEN_EVIDENCE_WAIT_MS = 1_000;
|
|
48
|
+
/** Own one client's document and diagnostic evidence; revisions prevent stale reuse. */
|
|
48
49
|
export class ClientDiagnostics {
|
|
49
|
-
readonly #openDocs = new Map<string,
|
|
50
|
+
readonly #openDocs = new Map<string, OpenDocumentState>();
|
|
50
51
|
readonly #diagnosticStore = new Map<string, DiagnosticCacheEntry>();
|
|
51
|
-
readonly #
|
|
52
|
+
readonly #waiters = new DiagnosticWaitRegistry();
|
|
53
|
+
readonly #versionHistory = new Map<string, number>();
|
|
54
|
+
readonly #failedUris = new Set<string>();
|
|
55
|
+
/** Client-side sync moment per URI: unversioned pushes before it stay rejected. */
|
|
56
|
+
readonly #unversionedPushSyncMoments = new Map<string, number>();
|
|
57
|
+
/** URIs closed by a lifecycle operation: versioned pushes stay fail-closed. */
|
|
58
|
+
readonly #closedVersionedBarrier = new Set<string>();
|
|
59
|
+
#evidenceRevision = 0;
|
|
60
|
+
#nextSynchronizationId = 0;
|
|
52
61
|
|
|
53
62
|
constructor(private readonly host: ClientDiagnosticsHost) {}
|
|
54
63
|
|
|
@@ -56,36 +65,58 @@ export class ClientDiagnostics {
|
|
|
56
65
|
return Array.from(this.#openDocs.keys()).map(uriToFile);
|
|
57
66
|
}
|
|
58
67
|
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
clear(options: { preserveFailedDocuments?: boolean } = {}): void {
|
|
69
|
+
if (options.preserveFailedDocuments) {
|
|
70
|
+
for (const uri of [...this.#openDocs.keys(), ...this.#diagnosticStore.keys()]) {
|
|
71
|
+
this.#failedUris.add(uri);
|
|
72
|
+
this.#closedVersionedBarrier.add(uri);
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
this.#failedUris.clear();
|
|
76
|
+
this.#closedVersionedBarrier.clear();
|
|
77
|
+
}
|
|
61
78
|
this.#openDocs.clear();
|
|
62
79
|
this.#diagnosticStore.clear();
|
|
63
|
-
this.#
|
|
80
|
+
this.#versionHistory.clear();
|
|
81
|
+
this.#evidenceRevision++;
|
|
82
|
+
this.#unversionedPushSyncMoments.clear();
|
|
83
|
+
this.#waiters.releaseAll();
|
|
84
|
+
this.#waiters.cancelSettle();
|
|
64
85
|
}
|
|
65
86
|
|
|
66
|
-
/** Open a document, or update it when it is already open. */
|
|
67
87
|
didOpen(filePath: string, content: string): void {
|
|
68
88
|
if (!this.host.isOperational()) return;
|
|
69
89
|
|
|
70
90
|
const uri = fileToUri(filePath);
|
|
91
|
+
this.#failedUris.delete(uri);
|
|
71
92
|
if (this.#openDocs.has(uri)) {
|
|
72
93
|
this.didChange(filePath, content);
|
|
73
94
|
return;
|
|
74
95
|
}
|
|
75
96
|
|
|
76
97
|
const languageId = detectLanguageId(filePath);
|
|
77
|
-
this.#
|
|
98
|
+
this.#waiters.cancelSettle();
|
|
99
|
+
// A didOpen is a synchronization: pushes the server sends after it are
|
|
100
|
+
// responses to the open and can become fresh evidence (ADR 0020).
|
|
101
|
+
this.#unversionedPushSyncMoments.set(uri, Date.now());
|
|
102
|
+
this.#closedVersionedBarrier.delete(uri);
|
|
103
|
+
const version = nextDocumentVersion(this.#versionHistory, uri);
|
|
104
|
+
this.#openDocs.set(uri, {
|
|
105
|
+
version,
|
|
106
|
+
synchronizationId: ++this.#nextSynchronizationId,
|
|
107
|
+
evidenceRevision: this.#evidenceRevision,
|
|
108
|
+
contentFingerprint: fingerprintDocumentContent(content),
|
|
109
|
+
});
|
|
78
110
|
this.host.sendNotification("textDocument/didOpen", {
|
|
79
111
|
textDocument: {
|
|
80
112
|
uri,
|
|
81
113
|
languageId,
|
|
82
|
-
version
|
|
114
|
+
version,
|
|
83
115
|
text: content,
|
|
84
116
|
} satisfies TextDocumentItem,
|
|
85
117
|
});
|
|
86
118
|
}
|
|
87
119
|
|
|
88
|
-
/** Update a document, or open it when it is not tracked yet. */
|
|
89
120
|
didChange(filePath: string, content: string): void {
|
|
90
121
|
if (!this.host.isOperational()) return;
|
|
91
122
|
|
|
@@ -95,267 +126,340 @@ export class ClientDiagnostics {
|
|
|
95
126
|
this.didOpen(filePath, content);
|
|
96
127
|
return;
|
|
97
128
|
}
|
|
98
|
-
|
|
99
|
-
doc.
|
|
100
|
-
|
|
129
|
+
const nextFingerprint = fingerprintDocumentContent(content);
|
|
130
|
+
if (doc.contentFingerprint === nextFingerprint) return;
|
|
131
|
+
synchronizeTrackedDocument({
|
|
132
|
+
uri,
|
|
133
|
+
content,
|
|
134
|
+
document: doc,
|
|
135
|
+
nextVersion: () => nextDocumentVersion(this.#versionHistory, uri),
|
|
136
|
+
nextSynchronizationId: () => ++this.#nextSynchronizationId,
|
|
137
|
+
evidenceRevision: this.#evidenceRevision,
|
|
138
|
+
waiters: this.#waiters,
|
|
139
|
+
sendNotification: (method, params) => this.host.sendNotification(method, params),
|
|
140
|
+
markUnversionedSyncMoment: () => this.#unversionedPushSyncMoments.set(uri, Date.now()),
|
|
141
|
+
clearFailedFile: () => this.#failedUris.delete(uri),
|
|
142
|
+
open: () => this.didOpen(filePath, content),
|
|
143
|
+
});
|
|
101
144
|
}
|
|
102
145
|
|
|
103
|
-
/** Close a document and remove its cached diagnostic state. */
|
|
104
146
|
didClose(filePath: string): void {
|
|
105
147
|
const uri = fileToUri(filePath);
|
|
106
148
|
const wasOpen = this.#openDocs.has(uri);
|
|
107
|
-
this.#
|
|
149
|
+
this.#failedUris.delete(uri);
|
|
150
|
+
this.#unversionedPushSyncMoments.delete(uri);
|
|
151
|
+
this.#closedVersionedBarrier.add(uri);
|
|
152
|
+
clearTrackedDocumentState(this.#openDocs, this.#diagnosticStore, this.#waiters, uri);
|
|
108
153
|
|
|
109
154
|
if (wasOpen && this.host.isOperational()) {
|
|
110
|
-
this
|
|
155
|
+
sendDidCloseNotification(this.host, uri);
|
|
111
156
|
}
|
|
112
157
|
}
|
|
113
158
|
|
|
114
|
-
/** Remove missing open documents and diagnostics, and return their file paths. */
|
|
115
159
|
pruneMissingFiles(): string[] {
|
|
116
160
|
const uris = new Set([...this.#openDocs.keys(), ...this.#diagnosticStore.keys()]);
|
|
117
161
|
const removedFiles: string[] = [];
|
|
118
162
|
|
|
119
163
|
for (const uri of uris) {
|
|
120
164
|
const filePath = uriToFile(uri);
|
|
121
|
-
if (
|
|
165
|
+
if (getDiagnosticFileState(filePath) !== "removed") continue;
|
|
122
166
|
|
|
123
167
|
const wasOpen = this.#openDocs.has(uri);
|
|
124
|
-
this.#
|
|
168
|
+
this.#failedUris.add(uri);
|
|
169
|
+
this.#unversionedPushSyncMoments.delete(uri);
|
|
170
|
+
this.#closedVersionedBarrier.add(uri);
|
|
171
|
+
clearTrackedDocumentState(this.#openDocs, this.#diagnosticStore, this.#waiters, uri);
|
|
125
172
|
removedFiles.push(filePath);
|
|
126
|
-
if (wasOpen && this.host.isOperational()) this
|
|
173
|
+
if (wasOpen && this.host.isOperational()) sendDidCloseNotification(this.host, uri);
|
|
127
174
|
}
|
|
128
175
|
|
|
129
176
|
return removedFiles;
|
|
130
177
|
}
|
|
178
|
+
/** Retain a failed document outcome when a replacement cannot reopen it. */
|
|
179
|
+
markFailedFile(filePath: string): void {
|
|
180
|
+
const uri = fileToUri(filePath);
|
|
181
|
+
this.#failedUris.add(uri);
|
|
182
|
+
this.#unversionedPushSyncMoments.delete(uri);
|
|
183
|
+
this.#closedVersionedBarrier.add(uri);
|
|
184
|
+
}
|
|
131
185
|
|
|
186
|
+
getOpenDocumentVersion(filePath: string): number | null {
|
|
187
|
+
return this.#openDocs.get(fileToUri(filePath))?.version ?? null;
|
|
188
|
+
}
|
|
132
189
|
getDiagnostics(filePath: string): Diagnostic[] {
|
|
133
190
|
return this.#diagnosticStore.get(fileToUri(filePath))?.diagnostics ?? [];
|
|
134
191
|
}
|
|
135
|
-
|
|
136
|
-
|
|
192
|
+
getDiagnosticSnapshot(): ClientDiagnosticSnapshot {
|
|
193
|
+
return buildClientDiagnosticSnapshot({
|
|
194
|
+
store: this.#diagnosticStore,
|
|
195
|
+
openDocuments: this.#openDocs,
|
|
196
|
+
failedDocuments: this.#failedUris,
|
|
197
|
+
evidenceRevision: this.#evidenceRevision,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
137
200
|
getAllDiagnostics(): DiagnosticEntry[] {
|
|
138
|
-
|
|
139
|
-
for (const [uri, entry] of this.#diagnosticStore) {
|
|
140
|
-
if (entry.diagnostics.length === 0 || !existsSync(uriToFile(uri))) continue;
|
|
141
|
-
result.push({ uri, diagnostics: entry.diagnostics });
|
|
142
|
-
}
|
|
143
|
-
return result;
|
|
201
|
+
return this.getDiagnosticSnapshot().entries;
|
|
144
202
|
}
|
|
145
|
-
|
|
146
|
-
/** Force the next pull refresh to request complete diagnostic reports. */
|
|
147
203
|
clearPullResultIds(): void {
|
|
148
204
|
for (const entry of this.#diagnosticStore.values()) delete entry.resultId;
|
|
149
205
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
206
|
+
/** Invalidate cache proof while retaining its data as partial fallback. */
|
|
207
|
+
invalidateCachedEvidence(): void {
|
|
208
|
+
this.#evidenceRevision++;
|
|
209
|
+
const knownUris = new Set([
|
|
210
|
+
...this.#openDocs.keys(),
|
|
211
|
+
...this.#diagnosticStore.keys(),
|
|
212
|
+
...this.#versionHistory.keys(),
|
|
213
|
+
...this.#failedUris,
|
|
214
|
+
]);
|
|
215
|
+
// A watched-file notification is sent immediately after this call, so the
|
|
216
|
+
// recorded moment gates unversioned pushes around the invalidation: only
|
|
217
|
+
// pushes the server sends after the change can become fresh evidence.
|
|
218
|
+
const moment = Date.now();
|
|
219
|
+
for (const uri of knownUris) {
|
|
220
|
+
this.#unversionedPushSyncMoments.set(uri, moment);
|
|
221
|
+
this.#closedVersionedBarrier.add(uri);
|
|
156
222
|
}
|
|
157
|
-
|
|
158
|
-
this.#diagnosticStore.set(params.uri, {
|
|
159
|
-
diagnostics: params.diagnostics,
|
|
160
|
-
receivedAt: Date.now(),
|
|
161
|
-
version: params.version ?? undefined,
|
|
162
|
-
});
|
|
163
|
-
this.#releaseDiagnosticWaiters(params.uri);
|
|
164
223
|
}
|
|
165
224
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (this.host.supportsPullDiagnostics()) {
|
|
180
|
-
try {
|
|
181
|
-
await this.#pullDiagnosticsForOpenDocuments(syncStart, maxWaitMs);
|
|
182
|
-
return;
|
|
183
|
-
} catch {
|
|
184
|
-
// Pull diagnostics failed. Wait for push diagnostics instead.
|
|
185
|
-
}
|
|
225
|
+
handlePublishDiagnostics(params: unknown): void {
|
|
226
|
+
if (!isValidPublishDiagnosticsParams(params)) return;
|
|
227
|
+
if (
|
|
228
|
+
!applyPushDiagnostics({
|
|
229
|
+
store: this.#diagnosticStore,
|
|
230
|
+
openDocuments: this.#openDocs,
|
|
231
|
+
params,
|
|
232
|
+
evidenceRevision: this.#evidenceRevision,
|
|
233
|
+
unversionedSyncMoment: this.#unversionedPushSyncMoments.get(params.uri),
|
|
234
|
+
closedVersionedBarrier: this.#closedVersionedBarrier.has(params.uri),
|
|
235
|
+
})
|
|
236
|
+
) {
|
|
237
|
+
return;
|
|
186
238
|
}
|
|
187
|
-
|
|
188
|
-
|
|
239
|
+
this.#waiters.releaseFile(params.uri, "published");
|
|
240
|
+
this.#waiters.notifySettle();
|
|
241
|
+
}
|
|
242
|
+
async refreshOpenDiagnostics(
|
|
243
|
+
options: { maxWaitMs?: number; quietMs?: number } & CodeRequestControl = {},
|
|
244
|
+
): Promise<DiagnosticEvidenceSummary> {
|
|
245
|
+
const requestedFiles = Array.from(
|
|
246
|
+
new Set([...this.#openDocs.keys(), ...this.#diagnosticStore.keys(), ...this.#failedUris]),
|
|
247
|
+
).map(uriToFile);
|
|
248
|
+
return refreshClientOpenDiagnostics({
|
|
249
|
+
host: this.host,
|
|
250
|
+
openDocuments: this.#openDocs,
|
|
251
|
+
diagnosticStore: this.#diagnosticStore,
|
|
252
|
+
waiters: this.#waiters,
|
|
253
|
+
versionHistory: this.#versionHistory,
|
|
254
|
+
requestedFiles,
|
|
255
|
+
evidenceRevision: () => this.#evidenceRevision,
|
|
256
|
+
failedFiles: () => new Set(Array.from(this.#failedUris).map(uriToFile)),
|
|
257
|
+
isRelatedUriTracked: (uri) => this.#openDocs.has(uri) || this.#versionHistory.has(uri),
|
|
258
|
+
nextSynchronizationId: () => ++this.#nextSynchronizationId,
|
|
259
|
+
invalidateEvidence: (uri) => {
|
|
260
|
+
this.#failedUris.add(uri);
|
|
261
|
+
const document = this.#openDocs.get(uri);
|
|
262
|
+
if (document) document.evidenceRevision = -1;
|
|
263
|
+
},
|
|
264
|
+
clearFile: (uri) => {
|
|
265
|
+
this.#failedUris.add(uri);
|
|
266
|
+
this.#unversionedPushSyncMoments.delete(uri);
|
|
267
|
+
this.#closedVersionedBarrier.add(uri);
|
|
268
|
+
clearTrackedDocumentState(this.#openDocs, this.#diagnosticStore, this.#waiters, uri);
|
|
269
|
+
},
|
|
270
|
+
markUnversionedSyncMoment: (uri) => this.#unversionedPushSyncMoments.set(uri, Date.now()),
|
|
271
|
+
clearFailedFile: (uri) => this.#failedUris.delete(uri),
|
|
272
|
+
options,
|
|
273
|
+
});
|
|
189
274
|
}
|
|
190
275
|
|
|
191
|
-
|
|
192
|
-
|
|
276
|
+
async syncAndWaitForDiagnostics(
|
|
277
|
+
filePath: string,
|
|
278
|
+
content: string,
|
|
279
|
+
control?: CodeRequestControl,
|
|
280
|
+
): Promise<CodeQueryResult<Diagnostic[]>> {
|
|
281
|
+
// Reject immediately when the request was already cancelled: no document
|
|
282
|
+
// synchronization or protocol traffic may start for a caller that no
|
|
283
|
+
// longer awaits a result.
|
|
284
|
+
throwIfCodeRequestInterrupted(control);
|
|
285
|
+
const supportsPull = this.host.supportsPullDiagnostics();
|
|
286
|
+
const observer = new DiagnosticObserver("sync-file", supportsPull, control, {
|
|
287
|
+
server: this.host.server,
|
|
288
|
+
cwd: this.host.cwd,
|
|
289
|
+
file: relativeDiagnosticFile(this.host.cwd, filePath),
|
|
290
|
+
});
|
|
193
291
|
const uri = fileToUri(filePath);
|
|
292
|
+
const cached = this.#diagnosticStore.get(uri);
|
|
293
|
+
const cachedDiagnostics = cached ? [...cached.diagnostics] : null;
|
|
194
294
|
const syncStart = Date.now();
|
|
195
|
-
this.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
#resyncOpenDocuments(): void {
|
|
214
|
-
for (const [uri, doc] of this.#openDocs) {
|
|
215
|
-
const filePath = uriToFile(uri);
|
|
216
|
-
try {
|
|
217
|
-
if (!existsSync(filePath)) {
|
|
218
|
-
this.#clearFileState(uri);
|
|
219
|
-
this.#sendDidClose(uri);
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
const content = readFileSync(filePath, "utf-8");
|
|
223
|
-
doc.version++;
|
|
224
|
-
this.#sendDidChange(uri, doc.version, content);
|
|
225
|
-
} catch {
|
|
226
|
-
// Keep the document open when a transient read fails.
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async #pullDiagnosticsForOpenDocuments(syncStart: number, maxWaitMs: number): Promise<void> {
|
|
232
|
-
const deadline = syncStart + maxWaitMs;
|
|
233
|
-
const uris = Array.from(this.#openDocs.keys());
|
|
234
|
-
const results = await Promise.allSettled(
|
|
235
|
-
uris.map(async (uri) => {
|
|
236
|
-
const remaining = deadline - Date.now();
|
|
237
|
-
if (remaining <= 0) throw new Error("pull diagnostic timeout");
|
|
238
|
-
return this.#pullDiagnosticsForUri(uri, remaining);
|
|
239
|
-
}),
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
const anySuccess = results.some((result) => result.status === "fulfilled" && result.value);
|
|
243
|
-
const hadFailure = results.some((result) => result.status === "rejected");
|
|
244
|
-
if ((hadFailure || !anySuccess) && uris.length > 0) {
|
|
245
|
-
throw new Error("pull diagnostics incomplete");
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
async #pullDiagnosticsForUri(uri: string, timeoutMs: number): Promise<boolean> {
|
|
250
|
-
const previousResultId = this.#diagnosticStore.get(uri)?.resultId;
|
|
251
|
-
const report = await this.host.pullDocumentDiagnostics(uri, previousResultId, timeoutMs);
|
|
252
|
-
if (!report) return false;
|
|
253
|
-
this.#applyPullReport(uri, report);
|
|
254
|
-
return true;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
#applyPullReport(uri: string, report: DocumentDiagnosticReport): void {
|
|
258
|
-
if (report.kind === "full") {
|
|
259
|
-
this.#diagnosticStore.set(uri, {
|
|
260
|
-
diagnostics: report.items,
|
|
261
|
-
receivedAt: Date.now(),
|
|
262
|
-
resultId: report.resultId,
|
|
263
|
-
});
|
|
264
|
-
} else {
|
|
265
|
-
const current = this.#diagnosticStore.get(uri);
|
|
266
|
-
if (current) current.resultId = report.resultId;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
for (const [relatedUri, relatedReport] of Object.entries(report.relatedDocuments ?? {})) {
|
|
270
|
-
if (relatedReport.kind !== "full") continue;
|
|
271
|
-
this.#diagnosticStore.set(relatedUri, {
|
|
272
|
-
diagnostics: relatedReport.items,
|
|
273
|
-
receivedAt: Date.now(),
|
|
274
|
-
resultId: relatedReport.resultId,
|
|
295
|
+
const openDocument = this.#openDocs.get(uri);
|
|
296
|
+
const contentUnchanged =
|
|
297
|
+
openDocument?.contentFingerprint === fingerprintDocumentContent(content);
|
|
298
|
+
const synchronizationCurrent = openDocument?.evidenceRevision === this.#evidenceRevision;
|
|
299
|
+
const cacheCurrent = hasCurrentDiagnosticEvidence(openDocument, cached, this.#evidenceRevision);
|
|
300
|
+
if (!contentUnchanged || !synchronizationCurrent || (cached !== undefined && !cacheCurrent)) {
|
|
301
|
+
synchronizeTrackedDocument({
|
|
302
|
+
uri,
|
|
303
|
+
content,
|
|
304
|
+
document: this.#openDocs.get(uri),
|
|
305
|
+
nextVersion: () => nextDocumentVersion(this.#versionHistory, uri),
|
|
306
|
+
nextSynchronizationId: () => ++this.#nextSynchronizationId,
|
|
307
|
+
evidenceRevision: this.#evidenceRevision,
|
|
308
|
+
waiters: this.#waiters,
|
|
309
|
+
sendNotification: (method, params) => this.host.sendNotification(method, params),
|
|
310
|
+
markUnversionedSyncMoment: () => this.#unversionedPushSyncMoments.set(uri, Date.now()),
|
|
311
|
+
clearFailedFile: () => this.#failedUris.delete(uri),
|
|
312
|
+
open: () => this.didOpen(filePath, content),
|
|
275
313
|
});
|
|
276
314
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
maxWaitMs: number,
|
|
282
|
-
quietMs: number,
|
|
283
|
-
): Promise<void> {
|
|
284
|
-
const deadline = syncStart + maxWaitMs;
|
|
285
|
-
while (Date.now() < deadline) {
|
|
286
|
-
const lastReceived = this.#lastDiagnosticReceivedAfter(syncStart) || syncStart;
|
|
287
|
-
const elapsed = Date.now() - lastReceived;
|
|
288
|
-
if (elapsed >= quietMs) return;
|
|
289
|
-
await new Promise((resolve) =>
|
|
290
|
-
setTimeout(resolve, Math.min(quietMs - elapsed, deadline - Date.now(), 50)),
|
|
291
|
-
);
|
|
315
|
+
const synchronization = this.#openDocs.get(uri);
|
|
316
|
+
observer.synchronized();
|
|
317
|
+
if (!synchronization) {
|
|
318
|
+
return unavailableCodeQuery("The document could not be synchronized for diagnostics.");
|
|
292
319
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
320
|
+
if (
|
|
321
|
+
contentUnchanged &&
|
|
322
|
+
hasCurrentDiagnosticEvidence(synchronization, cached, this.#evidenceRevision)
|
|
323
|
+
) {
|
|
324
|
+
observer.cacheReused(1);
|
|
325
|
+
return completedCodeQuery(cachedDiagnostics ?? []);
|
|
299
326
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
this.#diagnosticStore.delete(uri);
|
|
319
|
-
this.#releaseDiagnosticWaiters(uri);
|
|
327
|
+
const request = {
|
|
328
|
+
uri,
|
|
329
|
+
synchronizationId: synchronization.synchronizationId,
|
|
330
|
+
evidenceRevision: synchronization.evidenceRevision,
|
|
331
|
+
};
|
|
332
|
+
return this.#collectFileDiagnosticsWithReopenRetry(
|
|
333
|
+
{
|
|
334
|
+
filePath,
|
|
335
|
+
content,
|
|
336
|
+
uri,
|
|
337
|
+
supportsPull,
|
|
338
|
+
syncStart,
|
|
339
|
+
request,
|
|
340
|
+
cachedDiagnostics,
|
|
341
|
+
observer,
|
|
342
|
+
},
|
|
343
|
+
control,
|
|
344
|
+
);
|
|
320
345
|
}
|
|
321
346
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
347
|
+
/**
|
|
348
|
+
* Collect diagnostics for one synchronized document, retrying once through
|
|
349
|
+
* a reopen-resync on push-only routes when the first wait times out.
|
|
350
|
+
*/
|
|
351
|
+
async #collectFileDiagnosticsWithReopenRetry(
|
|
352
|
+
options: {
|
|
353
|
+
filePath: string;
|
|
354
|
+
content: string;
|
|
355
|
+
uri: string;
|
|
356
|
+
supportsPull: boolean;
|
|
357
|
+
syncStart: number;
|
|
358
|
+
request: DiagnosticSynchronization;
|
|
359
|
+
cachedDiagnostics: Diagnostic[] | null;
|
|
360
|
+
observer: DiagnosticObserver;
|
|
361
|
+
},
|
|
362
|
+
control?: CodeRequestControl,
|
|
363
|
+
): Promise<CodeQueryResult<Diagnostic[]>> {
|
|
364
|
+
const {
|
|
365
|
+
filePath,
|
|
366
|
+
content,
|
|
367
|
+
uri,
|
|
368
|
+
supportsPull,
|
|
369
|
+
syncStart,
|
|
370
|
+
request,
|
|
371
|
+
cachedDiagnostics,
|
|
372
|
+
observer,
|
|
373
|
+
} = options;
|
|
374
|
+
let pushOutcome: DiagnosticPushWaitOutcome | undefined;
|
|
375
|
+
let attemptRequest = request;
|
|
376
|
+
let attemptSyncStart = syncStart;
|
|
377
|
+
let attemptMaxWaitMs = DIAGNOSTIC_WAIT_MS;
|
|
378
|
+
let reopenedOnce = false;
|
|
379
|
+
let result: CodeQueryResult<Diagnostic[]>;
|
|
380
|
+
for (;;) {
|
|
381
|
+
result = await collectSynchronizedFileDiagnostics(
|
|
382
|
+
{
|
|
383
|
+
supportsPull,
|
|
384
|
+
syncStart: attemptSyncStart,
|
|
385
|
+
maxWaitMs: attemptMaxWaitMs,
|
|
386
|
+
request: attemptRequest,
|
|
387
|
+
cachedDiagnostics,
|
|
388
|
+
observer,
|
|
389
|
+
waiters: this.#waiters,
|
|
390
|
+
current: () => isCurrentSynchronization(this.#openDocs, attemptRequest),
|
|
391
|
+
freshPush: () =>
|
|
392
|
+
hasFreshPush(this.#diagnosticStore, attemptRequest, this.#evidenceRevision),
|
|
393
|
+
diagnostics: () => this.getDiagnostics(filePath),
|
|
394
|
+
pullDiagnostics: (timeoutMs, signal) =>
|
|
395
|
+
pullClientDiagnosticEvidenceFromHost({
|
|
396
|
+
host: this.host,
|
|
397
|
+
store: this.#diagnosticStore,
|
|
398
|
+
openDocuments: this.#openDocs,
|
|
399
|
+
currentEvidenceRevision: () => this.#evidenceRevision,
|
|
400
|
+
isRelatedUriTracked: (relatedUri) =>
|
|
401
|
+
this.#openDocs.has(relatedUri) || this.#versionHistory.has(relatedUri),
|
|
402
|
+
request: {
|
|
403
|
+
uri,
|
|
404
|
+
timeoutMs,
|
|
405
|
+
synchronizationId: attemptRequest.synchronizationId,
|
|
406
|
+
evidenceRevision: attemptRequest.evidenceRevision ?? this.#evidenceRevision,
|
|
407
|
+
signal,
|
|
408
|
+
deadline: control?.deadline,
|
|
409
|
+
operationId: control?.operationId,
|
|
410
|
+
},
|
|
411
|
+
}),
|
|
412
|
+
onPushWait: (outcome) => {
|
|
413
|
+
pushOutcome = outcome;
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
control,
|
|
417
|
+
);
|
|
418
|
+
// Reopen-resync fallback (R2): on push-only routes a document that
|
|
419
|
+
// timed out with no push stays unconfirmed — a clean file gets no
|
|
420
|
+
// push on didChange at all. Close and reopen it over the protocol so
|
|
421
|
+
// the server publishes on didOpen, then wait once more with a bounded
|
|
422
|
+
// budget. The cache entry and version history survive the reopen.
|
|
423
|
+
const document = this.#openDocs.get(uri);
|
|
424
|
+
if (
|
|
425
|
+
supportsPull ||
|
|
426
|
+
reopenedOnce ||
|
|
427
|
+
result.kind === "completed" ||
|
|
428
|
+
pushOutcome !== "timed-out" ||
|
|
429
|
+
!document ||
|
|
430
|
+
!isCurrentSynchronization(this.#openDocs, attemptRequest)
|
|
431
|
+
) {
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
reopenDocument({
|
|
435
|
+
uri,
|
|
436
|
+
content,
|
|
437
|
+
document,
|
|
438
|
+
languageId: detectLanguageId(filePath),
|
|
439
|
+
nextVersion: () => nextDocumentVersion(this.#versionHistory, uri),
|
|
440
|
+
nextSynchronizationId: () => ++this.#nextSynchronizationId,
|
|
441
|
+
evidenceRevision: this.#evidenceRevision,
|
|
442
|
+
waiters: this.#waiters,
|
|
443
|
+
sendNotification: (method, params) => this.host.sendNotification(method, params),
|
|
444
|
+
markUnversionedSyncMoment: () => this.#unversionedPushSyncMoments.set(uri, Date.now()),
|
|
445
|
+
});
|
|
446
|
+
observer.reopened(1);
|
|
447
|
+
reopenedOnce = true;
|
|
448
|
+
attemptRequest = {
|
|
449
|
+
uri,
|
|
450
|
+
synchronizationId: document.synchronizationId,
|
|
451
|
+
evidenceRevision: document.evidenceRevision,
|
|
330
452
|
};
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
resolve();
|
|
334
|
-
}, timeoutMs);
|
|
335
|
-
const waiters = this.#diagnosticWaiters.get(uri) ?? [];
|
|
336
|
-
waiters.push(waiter);
|
|
337
|
-
this.#diagnosticWaiters.set(uri, waiters);
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
#removeDiagnosticWaiter(uri: string, waiter: () => void): void {
|
|
342
|
-
const waiters = this.#diagnosticWaiters.get(uri);
|
|
343
|
-
if (!waiters) return;
|
|
344
|
-
const next = waiters.filter((entry) => entry !== waiter);
|
|
345
|
-
if (next.length > 0) this.#diagnosticWaiters.set(uri, next);
|
|
346
|
-
else this.#diagnosticWaiters.delete(uri);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
#releaseAllDiagnosticWaiters(): void {
|
|
350
|
-
for (const uri of Array.from(this.#diagnosticWaiters.keys())) {
|
|
351
|
-
this.#releaseDiagnosticWaiters(uri);
|
|
453
|
+
attemptSyncStart = Date.now();
|
|
454
|
+
attemptMaxWaitMs = REOPEN_EVIDENCE_WAIT_MS;
|
|
352
455
|
}
|
|
456
|
+
observer.pushWaitCompleted(1, pushOutcome ?? "timed-out");
|
|
457
|
+
return result;
|
|
353
458
|
}
|
|
459
|
+
}
|
|
354
460
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
for (const waiter of waiters) waiter();
|
|
360
|
-
}
|
|
461
|
+
/** Return a workspace-relative diagnostic file path for telemetry identity. */
|
|
462
|
+
function relativeDiagnosticFile(cwd: string | undefined, filePath: string): string | undefined {
|
|
463
|
+
if (cwd === undefined) return undefined;
|
|
464
|
+
return path.relative(cwd, path.resolve(cwd, filePath));
|
|
361
465
|
}
|