@mrclrchtr/supi-code-intelligence 4.10.0 → 6.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 +4 -3
- 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 +2 -2
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +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 -6
- package/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
- package/node_modules/@mrclrchtr/supi-lsp/README.md +111 -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 +2 -2
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/api.ts +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 -6
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
- 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 +22 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +245 -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 +171 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +413 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +15 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +214 -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 +690 -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 +97 -16
- 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 +448 -298
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +70 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +231 -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 +529 -119
- package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +161 -33
- package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +12 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/config/config.ts +5 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/config/defaults.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/config/server-config.ts +3 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-extends.ts +68 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-path.ts +8 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +176 -24
- package/node_modules/@mrclrchtr/supi-lsp/src/debug-telemetry.ts +45 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +42 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +46 -10
- 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 +17 -7
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +350 -26
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +5 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +586 -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 +42 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +92 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +165 -118
- 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 +2 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/api.ts +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 -6
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
- 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 +221 -77
- package/src/analysis/health/file-scope.ts +184 -0
- package/src/analysis/health/recovery.ts +14 -3
- package/src/analysis/provider.ts +58 -0
- package/src/analysis/readiness.ts +34 -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 +5 -2
- package/src/analysis/search/deadline.ts +2 -2
- package/src/analysis/search/pattern-analysis.ts +54 -33
- package/src/analysis/search/pattern.ts +18 -3
- 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/config.ts +35 -1
- package/src/diagnostics/evidence.ts +51 -0
- package/src/extension.ts +57 -14
- package/src/overview/overview-data.ts +9 -2
- package/src/overview/overview.ts +49 -30
- package/src/overview/types.ts +2 -0
- package/src/session/capability-adapter.ts +12 -3
- package/src/session/find-types.ts +2 -2
- package/src/session/find-workflow.ts +20 -9
- package/src/session/graph/collect.ts +18 -2
- package/src/session/graph-workflow.ts +19 -12
- package/src/session/health-refresh.ts +175 -0
- package/src/session/health-types.ts +43 -15
- package/src/session/health-workflow.ts +69 -54
- package/src/session/input/workflows.ts +2 -2
- package/src/session/inspect/collect.ts +20 -3
- package/src/session/inspect-workflow.ts +12 -6
- package/src/session/orientation/collect.ts +12 -3
- 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 +4 -1
- package/src/session/orientation-workflow.ts +23 -10
- package/src/session/refactor-plans.ts +2 -0
- package/src/session/refactor-workflow.ts +61 -15
- package/src/session/session.ts +29 -2
- 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 +284 -28
- package/src/substrate/lsp/recovery.ts +11 -5
- package/src/substrate/lsp/state.ts +2 -2
- package/src/substrate/workspace-provider-host.ts +25 -3
- package/src/tool/{find → code_find}/execute.ts +2 -16
- package/src/tool/code_find/guidance.ts +8 -0
- package/src/tool/{find → code_find}/render.ts +21 -4
- package/src/tool/{result/find.ts → code_find/result.ts} +78 -9
- package/src/tool/code_find/spec.ts +37 -0
- package/src/tool/code_find/tui.ts +41 -0
- package/src/tool/code_graph/execute.ts +24 -0
- package/src/tool/code_graph/guidance.ts +6 -0
- package/src/tool/{graph → code_graph}/markdown.ts +1 -1
- package/src/tool/{result/graph.ts → code_graph/result.ts} +111 -3
- package/src/tool/code_graph/spec.ts +39 -0
- package/src/tool/code_graph/tui.ts +117 -0
- package/src/tool/{health → code_health}/execute.ts +2 -13
- package/src/tool/code_health/guidance.ts +8 -0
- package/src/tool/{health → code_health}/markdown.ts +119 -27
- package/src/tool/code_health/refresh-status.ts +105 -0
- package/src/tool/{result/health.ts → code_health/result.ts} +80 -3
- package/src/tool/code_health/spec.ts +41 -0
- package/src/tool/{health → code_health}/tui.ts +95 -98
- package/src/tool/{inspect → code_inspect}/execute.ts +2 -16
- package/src/tool/code_inspect/guidance.ts +6 -0
- package/src/tool/{inspect → code_inspect}/markdown.ts +1 -1
- package/src/tool/{result/inspect.ts → code_inspect/result.ts} +143 -2
- package/src/tool/code_inspect/spec.ts +25 -0
- package/src/tool/code_inspect/tui.ts +44 -0
- package/src/tool/code_orientation/execute.ts +25 -0
- package/src/tool/code_orientation/guidance.ts +9 -0
- package/src/tool/{orientation → code_orientation}/markdown.ts +1 -1
- package/src/tool/code_orientation/result.ts +248 -0
- package/src/tool/code_orientation/spec.ts +25 -0
- package/src/tool/{orientation → code_orientation}/tui.ts +51 -35
- package/src/tool/code_refactor_apply/execute.ts +17 -0
- package/src/tool/code_refactor_apply/guidance.ts +5 -0
- package/src/tool/{refactor-apply/execute.ts → code_refactor_apply/result.ts} +8 -11
- package/src/tool/code_refactor_apply/spec.ts +26 -0
- package/src/tool/code_refactor_apply/tui.ts +73 -0
- package/src/tool/code_refactor_plan/execute.ts +27 -0
- package/src/tool/code_refactor_plan/guidance.ts +8 -0
- package/src/tool/{refactor-plan/execute.ts → code_refactor_plan/result.ts} +13 -22
- package/src/tool/code_refactor_plan/spec.ts +25 -0
- package/src/tool/code_refactor_plan/tui.ts +54 -0
- package/src/tool/code_resolve/execute.ts +19 -0
- package/src/tool/code_resolve/guidance.ts +8 -0
- package/src/tool/{resolve → code_resolve}/markdown.ts +1 -1
- package/src/tool/{result/resolve.ts → code_resolve/result.ts} +79 -29
- package/src/tool/code_resolve/spec.ts +25 -0
- package/src/tool/code_resolve/tui.ts +60 -0
- package/src/tool/guidance.ts +71 -52
- package/src/tool/infra/truncate.ts +40 -0
- package/src/tool/infra/workflow-control.ts +8 -0
- package/src/tool/{refactor-plan/markdown.ts → refactor-markdown.ts} +4 -7
- package/src/tool/register.ts +105 -88
- package/src/tool/result/display.ts +107 -0
- package/src/tool/result/errors.ts +20 -17
- package/src/tool/result/refactor.ts +37 -8
- package/src/tool/result/types.ts +20 -0
- package/src/tool/schemas.ts +26 -157
- package/src/tool/specs.ts +40 -126
- 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
- package/src/tool/find/tui.ts +0 -39
- package/src/tool/graph/execute.ts +0 -62
- package/src/tool/graph/tui.ts +0 -104
- package/src/tool/inspect/tui.ts +0 -36
- package/src/tool/orientation/execute.ts +0 -62
- package/src/tool/refactor-apply/tui.ts +0 -55
- package/src/tool/refactor-plan/tui.ts +0 -41
- package/src/tool/resolve/execute.ts +0 -36
- package/src/tool/resolve/tui.ts +0 -42
- package/src/tool/result/orientation.ts +0 -132
- /package/src/tool/{find → code_find}/ast-kinds.ts +0 -0
- /package/src/tool/{find → code_find}/markdown.ts +0 -0
- /package/src/tool/{find → code_find}/modes.ts +0 -0
- /package/src/tool/{health → code_health}/semantic-state.ts +0 -0
|
@@ -1,99 +1,135 @@
|
|
|
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
|
+
hasCurrentEvidence,
|
|
19
|
+
hasFreshPush,
|
|
20
|
+
isCurrentSynchronization,
|
|
21
|
+
isValidPublishDiagnosticsParams,
|
|
22
|
+
nextDocumentVersion,
|
|
23
|
+
} from "./client-diagnostic-evidence.ts";
|
|
24
|
+
import type { ClientDiagnosticsHost } from "./client-diagnostic-host.ts";
|
|
25
|
+
import {
|
|
26
|
+
DiagnosticPublicationTracker,
|
|
27
|
+
trackerFileIdentityFor,
|
|
28
|
+
} from "./client-diagnostic-publication.ts";
|
|
13
29
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} from "./client-diagnostic-
|
|
30
|
+
pullClientDiagnosticEvidenceFromHost,
|
|
31
|
+
refreshClientOpenDiagnostics,
|
|
32
|
+
sendDidCloseNotification,
|
|
33
|
+
} from "./client-diagnostic-refresh.ts";
|
|
34
|
+
import { DiagnosticObserver, type DiagnosticPushWaitOutcome } from "./client-diagnostic-timing.ts";
|
|
35
|
+
import { DiagnosticWaitRegistry } from "./client-diagnostic-waiters.ts";
|
|
36
|
+
import {
|
|
37
|
+
type ClientDiagnosticSnapshot,
|
|
38
|
+
type DiagnosticEntry,
|
|
39
|
+
fingerprintDocumentContent,
|
|
40
|
+
hasConfirmedDiagnosticEvidence,
|
|
41
|
+
type OpenDocumentState,
|
|
42
|
+
} from "./client-document-state.ts";
|
|
43
|
+
import {
|
|
44
|
+
clearTrackedDocumentState,
|
|
45
|
+
reopenDocument,
|
|
46
|
+
synchronizeTrackedDocument,
|
|
47
|
+
} from "./client-document-sync.ts";
|
|
48
|
+
import { getDiagnosticFileState } from "./client-file-state.ts";
|
|
20
49
|
|
|
21
50
|
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
|
-
*/
|
|
51
|
+
/** Bounded push wait after a reopen-resync fallback, in milliseconds. */
|
|
52
|
+
const REOPEN_EVIDENCE_WAIT_MS = 1_000;
|
|
53
|
+
/** Own one client's document and diagnostic evidence; revisions prevent stale reuse. */
|
|
56
54
|
export class ClientDiagnostics {
|
|
57
|
-
readonly #openDocs = new Map<string,
|
|
55
|
+
readonly #openDocs = new Map<string, OpenDocumentState>();
|
|
58
56
|
readonly #diagnosticStore = new Map<string, DiagnosticCacheEntry>();
|
|
59
|
-
readonly #
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
readonly #waiters = new DiagnosticWaitRegistry();
|
|
58
|
+
readonly #versionHistory = new Map<string, number>();
|
|
59
|
+
readonly #failedUris = new Set<string>();
|
|
60
|
+
/** Bounded push-publication telemetry for one client's diagnostic state. */
|
|
61
|
+
readonly #publications: DiagnosticPublicationTracker;
|
|
62
|
+
/** Client-side sync moment per URI: unversioned pushes before it stay rejected. */
|
|
63
|
+
readonly #unversionedPushSyncMoments = new Map<string, number>();
|
|
64
|
+
/** URIs closed by a lifecycle operation: versioned pushes stay fail-closed. */
|
|
65
|
+
readonly #closedVersionedBarrier = new Set<string>();
|
|
66
|
+
#evidenceRevision = 0;
|
|
67
|
+
#nextSynchronizationId = 0;
|
|
68
|
+
|
|
69
|
+
constructor(private readonly host: ClientDiagnosticsHost) {
|
|
70
|
+
this.#publications = new DiagnosticPublicationTracker(
|
|
71
|
+
{ server: host.server, cwd: host.cwd },
|
|
72
|
+
trackerFileIdentityFor(host.cwd),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
62
75
|
|
|
63
76
|
get openFiles(): string[] {
|
|
64
77
|
return Array.from(this.#openDocs.keys()).map(uriToFile);
|
|
65
78
|
}
|
|
66
79
|
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
clear(options: { preserveFailedDocuments?: boolean } = {}): void {
|
|
81
|
+
if (options.preserveFailedDocuments) {
|
|
82
|
+
for (const uri of [...this.#openDocs.keys(), ...this.#diagnosticStore.keys()]) {
|
|
83
|
+
this.#failedUris.add(uri);
|
|
84
|
+
this.#closedVersionedBarrier.add(uri);
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
this.#failedUris.clear();
|
|
88
|
+
this.#closedVersionedBarrier.clear();
|
|
89
|
+
}
|
|
69
90
|
this.#openDocs.clear();
|
|
70
91
|
this.#diagnosticStore.clear();
|
|
71
|
-
this.#
|
|
92
|
+
this.#versionHistory.clear();
|
|
93
|
+
this.#evidenceRevision++;
|
|
94
|
+
this.#unversionedPushSyncMoments.clear();
|
|
95
|
+
this.#waiters.releaseAll();
|
|
96
|
+
this.#waiters.cancelSettle();
|
|
72
97
|
}
|
|
73
98
|
|
|
74
|
-
/** Open a document, or update it when it is already open. */
|
|
75
99
|
didOpen(filePath: string, content: string): void {
|
|
76
100
|
if (!this.host.isOperational()) return;
|
|
77
101
|
|
|
78
102
|
const uri = fileToUri(filePath);
|
|
103
|
+
this.#failedUris.delete(uri);
|
|
79
104
|
if (this.#openDocs.has(uri)) {
|
|
80
105
|
this.didChange(filePath, content);
|
|
81
106
|
return;
|
|
82
107
|
}
|
|
83
108
|
|
|
84
109
|
const languageId = detectLanguageId(filePath);
|
|
85
|
-
this.#
|
|
110
|
+
this.#waiters.cancelSettle();
|
|
111
|
+
// A didOpen is a synchronization: pushes the server sends after it are
|
|
112
|
+
// responses to the open and can become fresh evidence (ADR 0020).
|
|
113
|
+
this.#unversionedPushSyncMoments.set(uri, Date.now());
|
|
114
|
+
this.#closedVersionedBarrier.delete(uri);
|
|
115
|
+
const version = nextDocumentVersion(this.#versionHistory, uri);
|
|
116
|
+
this.#openDocs.set(uri, {
|
|
117
|
+
version,
|
|
118
|
+
synchronizationId: ++this.#nextSynchronizationId,
|
|
119
|
+
evidenceRevision: this.#evidenceRevision,
|
|
120
|
+
content,
|
|
121
|
+
contentFingerprint: fingerprintDocumentContent(content),
|
|
122
|
+
});
|
|
86
123
|
this.host.sendNotification("textDocument/didOpen", {
|
|
87
124
|
textDocument: {
|
|
88
125
|
uri,
|
|
89
126
|
languageId,
|
|
90
|
-
version
|
|
127
|
+
version,
|
|
91
128
|
text: content,
|
|
92
129
|
} satisfies TextDocumentItem,
|
|
93
130
|
});
|
|
94
131
|
}
|
|
95
132
|
|
|
96
|
-
/** Update a document, or open it when it is not tracked yet. */
|
|
97
133
|
didChange(filePath: string, content: string): void {
|
|
98
134
|
if (!this.host.isOperational()) return;
|
|
99
135
|
|
|
@@ -103,295 +139,409 @@ export class ClientDiagnostics {
|
|
|
103
139
|
this.didOpen(filePath, content);
|
|
104
140
|
return;
|
|
105
141
|
}
|
|
106
|
-
|
|
107
|
-
doc.
|
|
108
|
-
|
|
142
|
+
const nextFingerprint = fingerprintDocumentContent(content);
|
|
143
|
+
if (doc.contentFingerprint === nextFingerprint) return;
|
|
144
|
+
synchronizeTrackedDocument({
|
|
145
|
+
uri,
|
|
146
|
+
content,
|
|
147
|
+
document: doc,
|
|
148
|
+
nextVersion: () => nextDocumentVersion(this.#versionHistory, uri),
|
|
149
|
+
nextSynchronizationId: () => ++this.#nextSynchronizationId,
|
|
150
|
+
evidenceRevision: this.#evidenceRevision,
|
|
151
|
+
incrementalSync: this.host.usesIncrementalDocumentSync(),
|
|
152
|
+
waiters: this.#waiters,
|
|
153
|
+
sendNotification: (method, params) => this.host.sendNotification(method, params),
|
|
154
|
+
markUnversionedSyncMoment: () => this.#unversionedPushSyncMoments.set(uri, Date.now()),
|
|
155
|
+
clearFailedFile: () => this.#failedUris.delete(uri),
|
|
156
|
+
open: () => this.didOpen(filePath, content),
|
|
157
|
+
});
|
|
109
158
|
}
|
|
110
159
|
|
|
111
|
-
/** Close a document and remove its cached diagnostic state. */
|
|
112
160
|
didClose(filePath: string): void {
|
|
113
161
|
const uri = fileToUri(filePath);
|
|
114
162
|
const wasOpen = this.#openDocs.has(uri);
|
|
115
|
-
this.#
|
|
163
|
+
this.#failedUris.delete(uri);
|
|
164
|
+
this.#unversionedPushSyncMoments.delete(uri);
|
|
165
|
+
this.#closedVersionedBarrier.add(uri);
|
|
166
|
+
clearTrackedDocumentState(this.#openDocs, this.#diagnosticStore, this.#waiters, uri);
|
|
116
167
|
|
|
117
168
|
if (wasOpen && this.host.isOperational()) {
|
|
118
|
-
this
|
|
169
|
+
sendDidCloseNotification(this.host, uri);
|
|
119
170
|
}
|
|
120
171
|
}
|
|
121
172
|
|
|
122
|
-
/** Remove missing open documents and diagnostics, and return their file paths. */
|
|
123
173
|
pruneMissingFiles(): string[] {
|
|
124
|
-
const uris = new Set([
|
|
174
|
+
const uris = new Set([
|
|
175
|
+
...this.#openDocs.keys(),
|
|
176
|
+
...this.#diagnosticStore.keys(),
|
|
177
|
+
...this.#failedUris,
|
|
178
|
+
]);
|
|
125
179
|
const removedFiles: string[] = [];
|
|
126
180
|
|
|
127
181
|
for (const uri of uris) {
|
|
128
182
|
const filePath = uriToFile(uri);
|
|
129
|
-
if (
|
|
183
|
+
if (getDiagnosticFileState(filePath) !== "removed") continue;
|
|
130
184
|
|
|
131
185
|
const wasOpen = this.#openDocs.has(uri);
|
|
132
|
-
this.#
|
|
186
|
+
this.#failedUris.delete(uri);
|
|
187
|
+
this.#unversionedPushSyncMoments.delete(uri);
|
|
188
|
+
this.#closedVersionedBarrier.add(uri);
|
|
189
|
+
clearTrackedDocumentState(this.#openDocs, this.#diagnosticStore, this.#waiters, uri);
|
|
133
190
|
removedFiles.push(filePath);
|
|
134
|
-
if (wasOpen && this.host.isOperational()) this
|
|
191
|
+
if (wasOpen && this.host.isOperational()) sendDidCloseNotification(this.host, uri);
|
|
135
192
|
}
|
|
136
193
|
|
|
137
194
|
return removedFiles;
|
|
138
195
|
}
|
|
196
|
+
/** Retain a failed document outcome when a replacement cannot reopen it. */
|
|
197
|
+
markFailedFile(filePath: string): void {
|
|
198
|
+
const uri = fileToUri(filePath);
|
|
199
|
+
this.#failedUris.add(uri);
|
|
200
|
+
this.#unversionedPushSyncMoments.delete(uri);
|
|
201
|
+
this.#closedVersionedBarrier.add(uri);
|
|
202
|
+
}
|
|
139
203
|
|
|
204
|
+
getOpenDocumentVersion(filePath: string): number | null {
|
|
205
|
+
return this.#openDocs.get(fileToUri(filePath))?.version ?? null;
|
|
206
|
+
}
|
|
140
207
|
getDiagnostics(filePath: string): Diagnostic[] {
|
|
141
208
|
return this.#diagnosticStore.get(fileToUri(filePath))?.diagnostics ?? [];
|
|
142
209
|
}
|
|
143
|
-
|
|
144
|
-
|
|
210
|
+
getDiagnosticSnapshot(): ClientDiagnosticSnapshot {
|
|
211
|
+
return buildClientDiagnosticSnapshot({
|
|
212
|
+
store: this.#diagnosticStore,
|
|
213
|
+
openDocuments: this.#openDocs,
|
|
214
|
+
failedDocuments: this.#failedUris,
|
|
215
|
+
evidenceRevision: this.#evidenceRevision,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
145
218
|
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;
|
|
219
|
+
return this.getDiagnosticSnapshot().entries;
|
|
152
220
|
}
|
|
153
|
-
|
|
154
|
-
/** Force the next pull refresh to request complete diagnostic reports. */
|
|
155
221
|
clearPullResultIds(): void {
|
|
156
222
|
for (const entry of this.#diagnosticStore.values()) delete entry.resultId;
|
|
157
223
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
224
|
+
/** Invalidate cache proof while retaining its data as partial fallback. */
|
|
225
|
+
invalidateCachedEvidence(): void {
|
|
226
|
+
this.#evidenceRevision++;
|
|
227
|
+
const knownUris = new Set([
|
|
228
|
+
...this.#openDocs.keys(),
|
|
229
|
+
...this.#diagnosticStore.keys(),
|
|
230
|
+
...this.#versionHistory.keys(),
|
|
231
|
+
...this.#failedUris,
|
|
232
|
+
]);
|
|
233
|
+
// A watched-file notification is sent immediately after this call, so the
|
|
234
|
+
// recorded moment gates unversioned pushes around the invalidation: only
|
|
235
|
+
// pushes the server sends after the change can become fresh evidence.
|
|
236
|
+
const moment = Date.now();
|
|
237
|
+
for (const uri of knownUris) {
|
|
238
|
+
this.#unversionedPushSyncMoments.set(uri, moment);
|
|
239
|
+
this.#closedVersionedBarrier.add(uri);
|
|
164
240
|
}
|
|
241
|
+
}
|
|
165
242
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
243
|
+
handlePublishDiagnostics(params: unknown): void {
|
|
244
|
+
if (!isValidPublishDiagnosticsParams(params)) return;
|
|
245
|
+
const result = applyPushDiagnostics({
|
|
246
|
+
store: this.#diagnosticStore,
|
|
247
|
+
openDocuments: this.#openDocs,
|
|
248
|
+
params,
|
|
249
|
+
evidenceRevision: this.#evidenceRevision,
|
|
250
|
+
unversionedSyncMoment: this.#unversionedPushSyncMoments.get(params.uri),
|
|
251
|
+
closedVersionedBarrier: this.#closedVersionedBarrier.has(params.uri),
|
|
170
252
|
});
|
|
171
|
-
|
|
253
|
+
if (!result.accepted) return;
|
|
254
|
+
const entry = this.#diagnosticStore.get(params.uri);
|
|
255
|
+
if (entry?.synchronizationId !== undefined && entry.evidenceRevision !== undefined) {
|
|
256
|
+
this.#publications.record(
|
|
257
|
+
params.uri,
|
|
258
|
+
entry.synchronizationId,
|
|
259
|
+
entry.evidenceRevision,
|
|
260
|
+
entry.receivedAt,
|
|
261
|
+
);
|
|
262
|
+
if (result.promoted) {
|
|
263
|
+
this.#publications.promoted(
|
|
264
|
+
params.uri,
|
|
265
|
+
entry.synchronizationId,
|
|
266
|
+
entry.evidenceRevision,
|
|
267
|
+
entry.receivedAt,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
this.#waiters.releaseFile(params.uri, "published");
|
|
272
|
+
this.#waiters.notifySettle();
|
|
172
273
|
}
|
|
173
|
-
|
|
174
|
-
/** Re-read open documents, then collect pull diagnostics or wait for push diagnostics. */
|
|
175
274
|
async refreshOpenDiagnostics(
|
|
176
|
-
options: { maxWaitMs?: number; quietMs?: number } = {},
|
|
177
|
-
): Promise<
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (!this.host.isOperational()) {
|
|
181
|
-
observer.skipped(0);
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const maxWaitMs = options.maxWaitMs ?? 3_000;
|
|
186
|
-
const quietMs = options.quietMs ?? 200;
|
|
187
|
-
const syncStart = Date.now();
|
|
188
|
-
this.#resyncOpenDocuments();
|
|
189
|
-
observer.synchronized();
|
|
190
|
-
const documentCount = this.#openDocs.size;
|
|
191
|
-
if (documentCount === 0) {
|
|
192
|
-
observer.skipped(0);
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
275
|
+
options: { maxWaitMs?: number; quietMs?: number } & CodeRequestControl = {},
|
|
276
|
+
): Promise<DiagnosticEvidenceSummary> {
|
|
277
|
+
return this.#refreshOpenDiagnostics(options, false);
|
|
278
|
+
}
|
|
195
279
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
return;
|
|
201
|
-
} catch (error) {
|
|
202
|
-
observer.pullFailed(error);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
280
|
+
/** Force a full document resynchronization for a server refresh request. */
|
|
281
|
+
async refreshForServerRequest(): Promise<DiagnosticEvidenceSummary> {
|
|
282
|
+
return this.#refreshOpenDiagnostics({}, true);
|
|
283
|
+
}
|
|
205
284
|
|
|
206
|
-
|
|
207
|
-
|
|
285
|
+
async #refreshOpenDiagnostics(
|
|
286
|
+
options: { maxWaitMs?: number; quietMs?: number } & CodeRequestControl,
|
|
287
|
+
forceResynchronize: boolean,
|
|
288
|
+
): Promise<DiagnosticEvidenceSummary> {
|
|
289
|
+
const requestedFiles = Array.from(
|
|
290
|
+
new Set([...this.#openDocs.keys(), ...this.#diagnosticStore.keys(), ...this.#failedUris]),
|
|
291
|
+
).map(uriToFile);
|
|
292
|
+
return refreshClientOpenDiagnostics({
|
|
293
|
+
host: this.host,
|
|
294
|
+
openDocuments: this.#openDocs,
|
|
295
|
+
diagnosticStore: this.#diagnosticStore,
|
|
296
|
+
waiters: this.#waiters,
|
|
297
|
+
versionHistory: this.#versionHistory,
|
|
298
|
+
requestedFiles,
|
|
299
|
+
evidenceRevision: () => this.#evidenceRevision,
|
|
300
|
+
failedFiles: () => new Set(Array.from(this.#failedUris).map(uriToFile)),
|
|
301
|
+
isRelatedUriTracked: (uri) => this.#openDocs.has(uri) || this.#versionHistory.has(uri),
|
|
302
|
+
nextSynchronizationId: () => ++this.#nextSynchronizationId,
|
|
303
|
+
invalidateEvidence: (uri) => {
|
|
304
|
+
this.#failedUris.add(uri);
|
|
305
|
+
const document = this.#openDocs.get(uri);
|
|
306
|
+
if (document) document.evidenceRevision = -1;
|
|
307
|
+
},
|
|
308
|
+
clearFile: (uri) => {
|
|
309
|
+
this.#failedUris.delete(uri);
|
|
310
|
+
this.#unversionedPushSyncMoments.delete(uri);
|
|
311
|
+
this.#closedVersionedBarrier.add(uri);
|
|
312
|
+
clearTrackedDocumentState(this.#openDocs, this.#diagnosticStore, this.#waiters, uri);
|
|
313
|
+
},
|
|
314
|
+
markUnversionedSyncMoment: (uri) => this.#unversionedPushSyncMoments.set(uri, Date.now()),
|
|
315
|
+
clearFailedFile: (uri) => this.#failedUris.delete(uri),
|
|
316
|
+
forceResynchronize,
|
|
317
|
+
options,
|
|
318
|
+
publications: {
|
|
319
|
+
emitSummary: (summaryOptions) =>
|
|
320
|
+
this.#publications.emitSummary({
|
|
321
|
+
...summaryOptions,
|
|
322
|
+
identity: {
|
|
323
|
+
server: this.host.server,
|
|
324
|
+
cwd: this.host.cwd,
|
|
325
|
+
...summaryOptions.identity,
|
|
326
|
+
},
|
|
327
|
+
}),
|
|
328
|
+
},
|
|
329
|
+
});
|
|
208
330
|
}
|
|
209
331
|
|
|
210
|
-
|
|
211
|
-
|
|
332
|
+
async syncAndWaitForDiagnostics(
|
|
333
|
+
filePath: string,
|
|
334
|
+
content: string,
|
|
335
|
+
control?: CodeRequestControl,
|
|
336
|
+
): Promise<CodeQueryResult<Diagnostic[]>> {
|
|
337
|
+
// Reject immediately when the request was already cancelled: no document
|
|
338
|
+
// synchronization or protocol traffic may start for a caller that no
|
|
339
|
+
// longer awaits a result.
|
|
340
|
+
throwIfCodeRequestInterrupted(control);
|
|
212
341
|
const supportsPull = this.host.supportsPullDiagnostics();
|
|
213
|
-
const observer = new DiagnosticObserver("sync-file", supportsPull
|
|
342
|
+
const observer = new DiagnosticObserver("sync-file", supportsPull, control, {
|
|
343
|
+
server: this.host.server,
|
|
344
|
+
cwd: this.host.cwd,
|
|
345
|
+
file: relativeDiagnosticFile(this.host.cwd, filePath),
|
|
346
|
+
});
|
|
214
347
|
const uri = fileToUri(filePath);
|
|
348
|
+
const cached = this.#diagnosticStore.get(uri);
|
|
349
|
+
const cachedDiagnostics = cached ? [...cached.diagnostics] : null;
|
|
215
350
|
const syncStart = Date.now();
|
|
216
|
-
this.
|
|
351
|
+
const openDocument = this.#openDocs.get(uri);
|
|
352
|
+
const contentUnchanged =
|
|
353
|
+
openDocument?.contentFingerprint === fingerprintDocumentContent(content);
|
|
354
|
+
const synchronizationCurrent = openDocument?.evidenceRevision === this.#evidenceRevision;
|
|
355
|
+
// Equivalent content joins the current synchronization. Cached evidence
|
|
356
|
+
// cannot force a no-op didChange that cancels an in-flight push or pull.
|
|
357
|
+
if (!contentUnchanged || !synchronizationCurrent) {
|
|
358
|
+
synchronizeTrackedDocument({
|
|
359
|
+
uri,
|
|
360
|
+
content,
|
|
361
|
+
document: this.#openDocs.get(uri),
|
|
362
|
+
nextVersion: () => nextDocumentVersion(this.#versionHistory, uri),
|
|
363
|
+
nextSynchronizationId: () => ++this.#nextSynchronizationId,
|
|
364
|
+
evidenceRevision: this.#evidenceRevision,
|
|
365
|
+
incrementalSync: this.host.usesIncrementalDocumentSync(),
|
|
366
|
+
waiters: this.#waiters,
|
|
367
|
+
sendNotification: (method, params) => this.host.sendNotification(method, params),
|
|
368
|
+
markUnversionedSyncMoment: () => this.#unversionedPushSyncMoments.set(uri, Date.now()),
|
|
369
|
+
clearFailedFile: () => this.#failedUris.delete(uri),
|
|
370
|
+
open: () => this.didOpen(filePath, content),
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
const synchronization = this.#openDocs.get(uri);
|
|
217
374
|
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
|
-
}
|
|
375
|
+
if (!synchronization) {
|
|
376
|
+
return unavailableCodeQuery("The document could not be synchronized for diagnostics.");
|
|
230
377
|
}
|
|
231
|
-
|
|
232
|
-
|
|
378
|
+
if (
|
|
379
|
+
contentUnchanged &&
|
|
380
|
+
hasConfirmedDiagnosticEvidence(synchronization, cached, this.#evidenceRevision)
|
|
381
|
+
) {
|
|
382
|
+
observer.cacheReused(1);
|
|
383
|
+
return completedCodeQuery(cachedDiagnostics ?? []);
|
|
384
|
+
}
|
|
385
|
+
const request = {
|
|
233
386
|
uri,
|
|
234
|
-
|
|
387
|
+
synchronizationId: synchronization.synchronizationId,
|
|
388
|
+
evidenceRevision: synchronization.evidenceRevision,
|
|
389
|
+
};
|
|
390
|
+
return this.#collectFileDiagnosticsWithReopenRetry(
|
|
391
|
+
{
|
|
392
|
+
filePath,
|
|
393
|
+
content,
|
|
394
|
+
uri,
|
|
395
|
+
supportsPull,
|
|
396
|
+
syncStart,
|
|
397
|
+
request,
|
|
398
|
+
cachedDiagnostics,
|
|
399
|
+
observer,
|
|
400
|
+
},
|
|
401
|
+
control,
|
|
235
402
|
);
|
|
236
|
-
observer.pushWaitCompleted(1, push);
|
|
237
|
-
return this.getDiagnostics(filePath);
|
|
238
403
|
}
|
|
239
404
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
405
|
+
/**
|
|
406
|
+
* Collect diagnostics for one synchronized document, retrying once through
|
|
407
|
+
* a reopen-resync on push-only routes when the first wait times out.
|
|
408
|
+
*/
|
|
409
|
+
async #collectFileDiagnosticsWithReopenRetry(
|
|
410
|
+
options: {
|
|
411
|
+
filePath: string;
|
|
412
|
+
content: string;
|
|
413
|
+
uri: string;
|
|
414
|
+
supportsPull: boolean;
|
|
415
|
+
syncStart: number;
|
|
416
|
+
request: DiagnosticSynchronization;
|
|
417
|
+
cachedDiagnostics: Diagnostic[] | null;
|
|
418
|
+
observer: DiagnosticObserver;
|
|
419
|
+
},
|
|
420
|
+
control?: CodeRequestControl,
|
|
421
|
+
): Promise<CodeQueryResult<Diagnostic[]>> {
|
|
422
|
+
const {
|
|
423
|
+
filePath,
|
|
424
|
+
content,
|
|
425
|
+
uri,
|
|
426
|
+
supportsPull,
|
|
427
|
+
syncStart,
|
|
428
|
+
request,
|
|
429
|
+
cachedDiagnostics,
|
|
430
|
+
observer,
|
|
431
|
+
} = options;
|
|
432
|
+
let pushOutcome: DiagnosticPushWaitOutcome | undefined;
|
|
433
|
+
let attemptRequest = request;
|
|
434
|
+
let attemptSyncStart = syncStart;
|
|
435
|
+
let attemptMaxWaitMs = DIAGNOSTIC_WAIT_MS;
|
|
436
|
+
let reopenedOnce = false;
|
|
437
|
+
let result: CodeQueryResult<Diagnostic[]>;
|
|
438
|
+
for (;;) {
|
|
439
|
+
result = await collectSynchronizedFileDiagnostics(
|
|
440
|
+
{
|
|
441
|
+
supportsPull,
|
|
442
|
+
syncStart: attemptSyncStart,
|
|
443
|
+
maxWaitMs: attemptMaxWaitMs,
|
|
444
|
+
request: attemptRequest,
|
|
445
|
+
cachedDiagnostics,
|
|
446
|
+
observer,
|
|
447
|
+
waiters: this.#waiters,
|
|
448
|
+
current: () => isCurrentSynchronization(this.#openDocs, attemptRequest),
|
|
449
|
+
freshPush: () =>
|
|
450
|
+
hasFreshPush(this.#diagnosticStore, attemptRequest, this.#evidenceRevision),
|
|
451
|
+
currentPushReceivedAt: () => {
|
|
452
|
+
const entry = this.#diagnosticStore.get(attemptRequest.uri);
|
|
453
|
+
return entry?.source === "push" &&
|
|
454
|
+
hasCurrentEvidence(this.#diagnosticStore, attemptRequest, this.#evidenceRevision)
|
|
455
|
+
? entry.receivedAt
|
|
456
|
+
: undefined;
|
|
457
|
+
},
|
|
458
|
+
diagnostics: () => this.getDiagnostics(filePath),
|
|
459
|
+
pullDiagnostics: (timeoutMs, signal) =>
|
|
460
|
+
pullClientDiagnosticEvidenceFromHost({
|
|
461
|
+
host: this.host,
|
|
462
|
+
store: this.#diagnosticStore,
|
|
463
|
+
openDocuments: this.#openDocs,
|
|
464
|
+
currentEvidenceRevision: () => this.#evidenceRevision,
|
|
465
|
+
isRelatedUriTracked: (relatedUri) =>
|
|
466
|
+
this.#openDocs.has(relatedUri) || this.#versionHistory.has(relatedUri),
|
|
467
|
+
request: {
|
|
468
|
+
uri,
|
|
469
|
+
timeoutMs,
|
|
470
|
+
synchronizationId: attemptRequest.synchronizationId,
|
|
471
|
+
evidenceRevision: attemptRequest.evidenceRevision ?? this.#evidenceRevision,
|
|
472
|
+
signal,
|
|
473
|
+
deadline: control?.deadline,
|
|
474
|
+
operationId: control?.operationId,
|
|
475
|
+
},
|
|
476
|
+
}),
|
|
477
|
+
onPushWait: (outcome) => {
|
|
478
|
+
pushOutcome = outcome;
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
control,
|
|
482
|
+
);
|
|
483
|
+
// Reopen-resync fallback (R2): on push-only routes a document that
|
|
484
|
+
// timed out with no push stays unconfirmed — a clean file gets no
|
|
485
|
+
// push on didChange at all. Close and reopen it over the protocol so
|
|
486
|
+
// the server publishes on didOpen, then wait once more with a bounded
|
|
487
|
+
// budget. The cache entry and version history survive the reopen.
|
|
488
|
+
const document = this.#openDocs.get(uri);
|
|
489
|
+
if (
|
|
490
|
+
supportsPull ||
|
|
491
|
+
reopenedOnce ||
|
|
492
|
+
result.kind === "completed" ||
|
|
493
|
+
pushOutcome !== "timed-out" ||
|
|
494
|
+
!document ||
|
|
495
|
+
!isCurrentSynchronization(this.#openDocs, attemptRequest)
|
|
496
|
+
) {
|
|
497
|
+
break;
|
|
254
498
|
}
|
|
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,
|
|
499
|
+
reopenDocument({
|
|
500
|
+
uri,
|
|
501
|
+
content,
|
|
502
|
+
document,
|
|
503
|
+
languageId: detectLanguageId(filePath),
|
|
504
|
+
nextVersion: () => nextDocumentVersion(this.#versionHistory, uri),
|
|
505
|
+
nextSynchronizationId: () => ++this.#nextSynchronizationId,
|
|
506
|
+
evidenceRevision: this.#evidenceRevision,
|
|
507
|
+
waiters: this.#waiters,
|
|
508
|
+
sendNotification: (method, params) => this.host.sendNotification(method, params),
|
|
509
|
+
markUnversionedSyncMoment: () => this.#unversionedPushSyncMoments.set(uri, Date.now()),
|
|
302
510
|
});
|
|
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);
|
|
511
|
+
observer.reopened(1);
|
|
512
|
+
reopenedOnce = true;
|
|
513
|
+
attemptRequest = {
|
|
514
|
+
uri,
|
|
515
|
+
synchronizationId: document.synchronizationId,
|
|
516
|
+
evidenceRevision: document.evidenceRevision,
|
|
366
517
|
};
|
|
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);
|
|
518
|
+
attemptSyncStart = Date.now();
|
|
519
|
+
attemptMaxWaitMs = REOPEN_EVIDENCE_WAIT_MS;
|
|
388
520
|
}
|
|
521
|
+
observer.pushWaitCompleted(1, pushOutcome ?? "timed-out");
|
|
522
|
+
this.#publications.emitSummary({
|
|
523
|
+
operation: "sync-file",
|
|
524
|
+
identity: {
|
|
525
|
+
server: this.host.server,
|
|
526
|
+
cwd: this.host.cwd,
|
|
527
|
+
file: relativeDiagnosticFile(this.host.cwd, filePath),
|
|
528
|
+
},
|
|
529
|
+
synchronizations: [
|
|
530
|
+
{
|
|
531
|
+
uri,
|
|
532
|
+
synchronizationId: attemptRequest.synchronizationId,
|
|
533
|
+
evidenceRevision: attemptRequest.evidenceRevision ?? this.#evidenceRevision,
|
|
534
|
+
confirmed: result.kind === "completed",
|
|
535
|
+
},
|
|
536
|
+
],
|
|
537
|
+
operationId: control?.operationId,
|
|
538
|
+
});
|
|
539
|
+
return result;
|
|
389
540
|
}
|
|
541
|
+
}
|
|
390
542
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
for (const waiter of waiters) waiter(outcome);
|
|
396
|
-
}
|
|
543
|
+
/** Return a workspace-relative diagnostic file path for telemetry identity. */
|
|
544
|
+
function relativeDiagnosticFile(cwd: string | undefined, filePath: string): string | undefined {
|
|
545
|
+
if (cwd === undefined) return undefined;
|
|
546
|
+
return path.relative(cwd, path.resolve(cwd, filePath));
|
|
397
547
|
}
|