@mrclrchtr/supi-code-intelligence 4.10.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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/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/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 +71 -11
- 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 +365 -297
- 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 +161 -33
- 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 +17 -7
- 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/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/package.json +6 -3
- 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 +45 -1
- package/src/analysis/refactor/mutation-authority.ts +143 -0
- 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 -2
- 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 -351
|
@@ -1,62 +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";
|
|
13
31
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} from "./client-
|
|
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";
|
|
20
44
|
|
|
21
45
|
const DIAGNOSTIC_WAIT_MS = 3_000;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
type DiagnosticCacheEntry = {
|
|
27
|
-
diagnostics: Diagnostic[];
|
|
28
|
-
receivedAt: number;
|
|
29
|
-
version?: number;
|
|
30
|
-
resultId?: string;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/** One file's stored diagnostics as consumed by manager-level collection. */
|
|
34
|
-
export interface DiagnosticEntry {
|
|
35
|
-
uri: string;
|
|
36
|
-
diagnostics: Diagnostic[];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface ClientDiagnosticsHost {
|
|
40
|
-
isOperational(): boolean;
|
|
41
|
-
supportsPullDiagnostics(): boolean;
|
|
42
|
-
sendNotification(method: string, params: unknown): void;
|
|
43
|
-
pullDocumentDiagnostics(
|
|
44
|
-
uri: string,
|
|
45
|
-
previousResultId: string | undefined,
|
|
46
|
-
timeoutMs: number,
|
|
47
|
-
): Promise<DocumentDiagnosticReport | null>;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Owns one LSP client's open documents, diagnostic cache, waiters, and refresh policy.
|
|
52
|
-
*
|
|
53
|
-
* The host supplies transport and readiness operations. Callers use `LspClient` as
|
|
54
|
-
* the external seam and do not access this state directly.
|
|
55
|
-
*/
|
|
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. */
|
|
56
49
|
export class ClientDiagnostics {
|
|
57
|
-
readonly #openDocs = new Map<string,
|
|
50
|
+
readonly #openDocs = new Map<string, OpenDocumentState>();
|
|
58
51
|
readonly #diagnosticStore = new Map<string, DiagnosticCacheEntry>();
|
|
59
|
-
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;
|
|
60
61
|
|
|
61
62
|
constructor(private readonly host: ClientDiagnosticsHost) {}
|
|
62
63
|
|
|
@@ -64,36 +65,58 @@ export class ClientDiagnostics {
|
|
|
64
65
|
return Array.from(this.#openDocs.keys()).map(uriToFile);
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
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
|
+
}
|
|
69
78
|
this.#openDocs.clear();
|
|
70
79
|
this.#diagnosticStore.clear();
|
|
71
|
-
this.#
|
|
80
|
+
this.#versionHistory.clear();
|
|
81
|
+
this.#evidenceRevision++;
|
|
82
|
+
this.#unversionedPushSyncMoments.clear();
|
|
83
|
+
this.#waiters.releaseAll();
|
|
84
|
+
this.#waiters.cancelSettle();
|
|
72
85
|
}
|
|
73
86
|
|
|
74
|
-
/** Open a document, or update it when it is already open. */
|
|
75
87
|
didOpen(filePath: string, content: string): void {
|
|
76
88
|
if (!this.host.isOperational()) return;
|
|
77
89
|
|
|
78
90
|
const uri = fileToUri(filePath);
|
|
91
|
+
this.#failedUris.delete(uri);
|
|
79
92
|
if (this.#openDocs.has(uri)) {
|
|
80
93
|
this.didChange(filePath, content);
|
|
81
94
|
return;
|
|
82
95
|
}
|
|
83
96
|
|
|
84
97
|
const languageId = detectLanguageId(filePath);
|
|
85
|
-
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
|
+
});
|
|
86
110
|
this.host.sendNotification("textDocument/didOpen", {
|
|
87
111
|
textDocument: {
|
|
88
112
|
uri,
|
|
89
113
|
languageId,
|
|
90
|
-
version
|
|
114
|
+
version,
|
|
91
115
|
text: content,
|
|
92
116
|
} satisfies TextDocumentItem,
|
|
93
117
|
});
|
|
94
118
|
}
|
|
95
119
|
|
|
96
|
-
/** Update a document, or open it when it is not tracked yet. */
|
|
97
120
|
didChange(filePath: string, content: string): void {
|
|
98
121
|
if (!this.host.isOperational()) return;
|
|
99
122
|
|
|
@@ -103,295 +126,340 @@ export class ClientDiagnostics {
|
|
|
103
126
|
this.didOpen(filePath, content);
|
|
104
127
|
return;
|
|
105
128
|
}
|
|
106
|
-
|
|
107
|
-
doc.
|
|
108
|
-
|
|
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
|
+
});
|
|
109
144
|
}
|
|
110
145
|
|
|
111
|
-
/** Close a document and remove its cached diagnostic state. */
|
|
112
146
|
didClose(filePath: string): void {
|
|
113
147
|
const uri = fileToUri(filePath);
|
|
114
148
|
const wasOpen = this.#openDocs.has(uri);
|
|
115
|
-
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);
|
|
116
153
|
|
|
117
154
|
if (wasOpen && this.host.isOperational()) {
|
|
118
|
-
this
|
|
155
|
+
sendDidCloseNotification(this.host, uri);
|
|
119
156
|
}
|
|
120
157
|
}
|
|
121
158
|
|
|
122
|
-
/** Remove missing open documents and diagnostics, and return their file paths. */
|
|
123
159
|
pruneMissingFiles(): string[] {
|
|
124
160
|
const uris = new Set([...this.#openDocs.keys(), ...this.#diagnosticStore.keys()]);
|
|
125
161
|
const removedFiles: string[] = [];
|
|
126
162
|
|
|
127
163
|
for (const uri of uris) {
|
|
128
164
|
const filePath = uriToFile(uri);
|
|
129
|
-
if (
|
|
165
|
+
if (getDiagnosticFileState(filePath) !== "removed") continue;
|
|
130
166
|
|
|
131
167
|
const wasOpen = this.#openDocs.has(uri);
|
|
132
|
-
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);
|
|
133
172
|
removedFiles.push(filePath);
|
|
134
|
-
if (wasOpen && this.host.isOperational()) this
|
|
173
|
+
if (wasOpen && this.host.isOperational()) sendDidCloseNotification(this.host, uri);
|
|
135
174
|
}
|
|
136
175
|
|
|
137
176
|
return removedFiles;
|
|
138
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
|
+
}
|
|
139
185
|
|
|
186
|
+
getOpenDocumentVersion(filePath: string): number | null {
|
|
187
|
+
return this.#openDocs.get(fileToUri(filePath))?.version ?? null;
|
|
188
|
+
}
|
|
140
189
|
getDiagnostics(filePath: string): Diagnostic[] {
|
|
141
190
|
return this.#diagnosticStore.get(fileToUri(filePath))?.diagnostics ?? [];
|
|
142
191
|
}
|
|
143
|
-
|
|
144
|
-
|
|
192
|
+
getDiagnosticSnapshot(): ClientDiagnosticSnapshot {
|
|
193
|
+
return buildClientDiagnosticSnapshot({
|
|
194
|
+
store: this.#diagnosticStore,
|
|
195
|
+
openDocuments: this.#openDocs,
|
|
196
|
+
failedDocuments: this.#failedUris,
|
|
197
|
+
evidenceRevision: this.#evidenceRevision,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
145
200
|
getAllDiagnostics(): DiagnosticEntry[] {
|
|
146
|
-
|
|
147
|
-
for (const [uri, entry] of this.#diagnosticStore) {
|
|
148
|
-
if (entry.diagnostics.length === 0 || !existsSync(uriToFile(uri))) continue;
|
|
149
|
-
result.push({ uri, diagnostics: entry.diagnostics });
|
|
150
|
-
}
|
|
151
|
-
return result;
|
|
201
|
+
return this.getDiagnosticSnapshot().entries;
|
|
152
202
|
}
|
|
153
|
-
|
|
154
|
-
/** Force the next pull refresh to request complete diagnostic reports. */
|
|
155
203
|
clearPullResultIds(): void {
|
|
156
204
|
for (const entry of this.#diagnosticStore.values()) delete entry.resultId;
|
|
157
205
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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);
|
|
164
222
|
}
|
|
165
|
-
|
|
166
|
-
this.#diagnosticStore.set(params.uri, {
|
|
167
|
-
diagnostics: params.diagnostics,
|
|
168
|
-
receivedAt: Date.now(),
|
|
169
|
-
version: params.version ?? undefined,
|
|
170
|
-
});
|
|
171
|
-
this.#releaseDiagnosticWaiters(params.uri, "published");
|
|
172
223
|
}
|
|
173
224
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
+
) {
|
|
182
237
|
return;
|
|
183
238
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
+
});
|
|
208
274
|
}
|
|
209
275
|
|
|
210
|
-
|
|
211
|
-
|
|
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);
|
|
212
285
|
const supportsPull = this.host.supportsPullDiagnostics();
|
|
213
|
-
const observer = new DiagnosticObserver("sync-file", supportsPull
|
|
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
|
+
});
|
|
214
291
|
const uri = fileToUri(filePath);
|
|
292
|
+
const cached = this.#diagnosticStore.get(uri);
|
|
293
|
+
const cachedDiagnostics = cached ? [...cached.diagnostics] : null;
|
|
215
294
|
const syncStart = Date.now();
|
|
216
|
-
this.
|
|
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),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
const synchronization = this.#openDocs.get(uri);
|
|
217
316
|
observer.synchronized();
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const remaining = DIAGNOSTIC_WAIT_MS - (Date.now() - syncStart);
|
|
221
|
-
try {
|
|
222
|
-
if (remaining <= 0) observer.pullTimedOut();
|
|
223
|
-
else if (await this.#pullDiagnosticsForUri(uri, remaining)) {
|
|
224
|
-
observer.pullCompleted(1);
|
|
225
|
-
return this.getDiagnostics(filePath);
|
|
226
|
-
} else observer.pullFailed(undefined);
|
|
227
|
-
} catch (error) {
|
|
228
|
-
observer.pullFailed(error);
|
|
229
|
-
}
|
|
317
|
+
if (!synchronization) {
|
|
318
|
+
return unavailableCodeQuery("The document could not be synchronized for diagnostics.");
|
|
230
319
|
}
|
|
231
|
-
|
|
232
|
-
|
|
320
|
+
if (
|
|
321
|
+
contentUnchanged &&
|
|
322
|
+
hasCurrentDiagnosticEvidence(synchronization, cached, this.#evidenceRevision)
|
|
323
|
+
) {
|
|
324
|
+
observer.cacheReused(1);
|
|
325
|
+
return completedCodeQuery(cachedDiagnostics ?? []);
|
|
326
|
+
}
|
|
327
|
+
const request = {
|
|
233
328
|
uri,
|
|
234
|
-
|
|
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,
|
|
235
344
|
);
|
|
236
|
-
observer.pushWaitCompleted(1, push);
|
|
237
|
-
return this.getDiagnostics(filePath);
|
|
238
345
|
}
|
|
239
346
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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;
|
|
254
433
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}),
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
const anySuccess = results.some((result) => result.status === "fulfilled" && result.value);
|
|
270
|
-
const failures = results.filter((result) => result.status === "rejected");
|
|
271
|
-
if ((failures.length > 0 || !anySuccess) && uris.length > 0) {
|
|
272
|
-
throw new DiagnosticPullError(failures.some((result) => isDiagnosticTimeout(result.reason)));
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
async #pullDiagnosticsForUri(uri: string, timeoutMs: number): Promise<boolean> {
|
|
277
|
-
const previousResultId = this.#diagnosticStore.get(uri)?.resultId;
|
|
278
|
-
const report = await this.host.pullDocumentDiagnostics(uri, previousResultId, timeoutMs);
|
|
279
|
-
if (!report) return false;
|
|
280
|
-
this.#applyPullReport(uri, report);
|
|
281
|
-
return true;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
#applyPullReport(uri: string, report: DocumentDiagnosticReport): void {
|
|
285
|
-
if (report.kind === "full") {
|
|
286
|
-
this.#diagnosticStore.set(uri, {
|
|
287
|
-
diagnostics: report.items,
|
|
288
|
-
receivedAt: Date.now(),
|
|
289
|
-
resultId: report.resultId,
|
|
290
|
-
});
|
|
291
|
-
} else {
|
|
292
|
-
const current = this.#diagnosticStore.get(uri);
|
|
293
|
-
if (current) current.resultId = report.resultId;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
for (const [relatedUri, relatedReport] of Object.entries(report.relatedDocuments ?? {})) {
|
|
297
|
-
if (relatedReport.kind !== "full") continue;
|
|
298
|
-
this.#diagnosticStore.set(relatedUri, {
|
|
299
|
-
diagnostics: relatedReport.items,
|
|
300
|
-
receivedAt: Date.now(),
|
|
301
|
-
resultId: relatedReport.resultId,
|
|
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()),
|
|
302
445
|
});
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
quietMs: number,
|
|
310
|
-
): Promise<DiagnosticSettleResult> {
|
|
311
|
-
const deadline = syncStart + maxWaitMs;
|
|
312
|
-
while (Date.now() < deadline) {
|
|
313
|
-
const observedAt = this.#lastDiagnosticReceivedAfter(syncStart);
|
|
314
|
-
const elapsed = Date.now() - (observedAt || syncStart);
|
|
315
|
-
if (elapsed >= quietMs) {
|
|
316
|
-
return {
|
|
317
|
-
outcome: "quiet",
|
|
318
|
-
freshness: observedAt > 0 ? "observed" : "not-observed",
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
await new Promise((resolve) =>
|
|
322
|
-
setTimeout(resolve, Math.min(quietMs - elapsed, deadline - Date.now(), 50)),
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
return {
|
|
326
|
-
outcome: "timed-out",
|
|
327
|
-
freshness: this.#lastDiagnosticReceivedAfter(syncStart) > 0 ? "observed" : "not-observed",
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
#lastDiagnosticReceivedAfter(afterTime: number): number {
|
|
332
|
-
let latest = 0;
|
|
333
|
-
for (const entry of this.#diagnosticStore.values()) {
|
|
334
|
-
if (entry.receivedAt > afterTime && entry.receivedAt > latest) latest = entry.receivedAt;
|
|
335
|
-
}
|
|
336
|
-
return latest;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
#sendDidChange(uri: string, version: number, content: string): void {
|
|
340
|
-
this.host.sendNotification("textDocument/didChange", {
|
|
341
|
-
textDocument: { uri, version } satisfies VersionedTextDocumentIdentifier,
|
|
342
|
-
contentChanges: [{ text: content }],
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
#sendDidClose(uri: string): void {
|
|
347
|
-
this.host.sendNotification("textDocument/didClose", {
|
|
348
|
-
textDocument: { uri } satisfies TextDocumentIdentifier,
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
#clearFileState(uri: string): void {
|
|
353
|
-
this.#openDocs.delete(uri);
|
|
354
|
-
this.#diagnosticStore.delete(uri);
|
|
355
|
-
this.#releaseDiagnosticWaiters(uri);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
#waitForDiagnostics(uri: string, timeoutMs: number): Promise<DiagnosticPushWaitOutcome> {
|
|
359
|
-
if (timeoutMs <= 0) return Promise.resolve("timed-out");
|
|
360
|
-
|
|
361
|
-
return new Promise<DiagnosticPushWaitOutcome>((resolve) => {
|
|
362
|
-
const waiter: DiagnosticWaiter = (outcome) => {
|
|
363
|
-
clearTimeout(timer);
|
|
364
|
-
this.#removeDiagnosticWaiter(uri, waiter);
|
|
365
|
-
resolve(outcome);
|
|
446
|
+
observer.reopened(1);
|
|
447
|
+
reopenedOnce = true;
|
|
448
|
+
attemptRequest = {
|
|
449
|
+
uri,
|
|
450
|
+
synchronizationId: document.synchronizationId,
|
|
451
|
+
evidenceRevision: document.evidenceRevision,
|
|
366
452
|
};
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
resolve("timed-out");
|
|
370
|
-
}, timeoutMs);
|
|
371
|
-
const waiters = this.#diagnosticWaiters.get(uri) ?? [];
|
|
372
|
-
waiters.push(waiter);
|
|
373
|
-
this.#diagnosticWaiters.set(uri, waiters);
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
#removeDiagnosticWaiter(uri: string, waiter: DiagnosticWaiter): void {
|
|
378
|
-
const waiters = this.#diagnosticWaiters.get(uri);
|
|
379
|
-
if (!waiters) return;
|
|
380
|
-
const next = waiters.filter((entry) => entry !== waiter);
|
|
381
|
-
if (next.length > 0) this.#diagnosticWaiters.set(uri, next);
|
|
382
|
-
else this.#diagnosticWaiters.delete(uri);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
#releaseAllDiagnosticWaiters(): void {
|
|
386
|
-
for (const uri of Array.from(this.#diagnosticWaiters.keys())) {
|
|
387
|
-
this.#releaseDiagnosticWaiters(uri);
|
|
453
|
+
attemptSyncStart = Date.now();
|
|
454
|
+
attemptMaxWaitMs = REOPEN_EVIDENCE_WAIT_MS;
|
|
388
455
|
}
|
|
456
|
+
observer.pushWaitCompleted(1, pushOutcome ?? "timed-out");
|
|
457
|
+
return result;
|
|
389
458
|
}
|
|
459
|
+
}
|
|
390
460
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
for (const waiter of waiters) waiter(outcome);
|
|
396
|
-
}
|
|
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));
|
|
397
465
|
}
|