@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
|
@@ -2,10 +2,27 @@
|
|
|
2
2
|
// biome-ignore-all lint/style/noExcessiveLinesPerFile: LspManager stays cohesive; recovery and sync helpers are split into manager-*.ts modules.
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
type CodeQueryResult,
|
|
7
|
+
type CodeRequestControl,
|
|
8
|
+
isCodeRequestInterruption,
|
|
9
|
+
throwIfCodeRequestInterrupted,
|
|
10
|
+
unavailableCodeQuery,
|
|
11
|
+
} from "@mrclrchtr/supi-code-runtime/api";
|
|
6
12
|
import * as projectRoots from "@mrclrchtr/supi-core/project";
|
|
7
|
-
import
|
|
13
|
+
import * as ts from "typescript";
|
|
14
|
+
import {
|
|
15
|
+
LspClient,
|
|
16
|
+
type LspClientLifecycleTransitionKind,
|
|
17
|
+
RECOVERY_CLIENT_STARTUP_BOUND_MS,
|
|
18
|
+
withTimeout,
|
|
19
|
+
} from "../client/client.ts";
|
|
20
|
+
import type { ClientDiagnosticSnapshot } from "../client/client-document-state.ts";
|
|
21
|
+
|
|
22
|
+
export { RECOVERY_CLIENT_STARTUP_BOUND_MS } from "../client/client.ts";
|
|
23
|
+
|
|
8
24
|
import { getServerForFile } from "../config/config.ts";
|
|
25
|
+
import { getFileScopeDecision } from "../config/tsconfig-scope.ts";
|
|
9
26
|
import type {
|
|
10
27
|
DetectedProjectServer,
|
|
11
28
|
Diagnostic,
|
|
@@ -22,6 +39,17 @@ import {
|
|
|
22
39
|
createOutstandingDiagnosticSummary,
|
|
23
40
|
relativeFilePathFromUri,
|
|
24
41
|
} from "../diagnostics/diagnostic-summary.ts";
|
|
42
|
+
import {
|
|
43
|
+
type DiagnosticEvidenceDocument,
|
|
44
|
+
type DiagnosticEvidenceSummary,
|
|
45
|
+
summarizeDiagnosticEvidence,
|
|
46
|
+
} from "../diagnostics/evidence.ts";
|
|
47
|
+
import { raceRequestControl } from "../session/readiness.ts";
|
|
48
|
+
import type {
|
|
49
|
+
ScopeDecisionEntry,
|
|
50
|
+
ScopeDecisionSummary,
|
|
51
|
+
WorkspaceDiagnosticReport,
|
|
52
|
+
} from "../session/runtime-diagnostics.ts";
|
|
25
53
|
import {
|
|
26
54
|
displayRelativeFilePath,
|
|
27
55
|
formatCoverageSummaryText,
|
|
@@ -30,7 +58,7 @@ import {
|
|
|
30
58
|
normalizeRelevantPaths,
|
|
31
59
|
shouldIgnoreLspPath,
|
|
32
60
|
} from "../summary.ts";
|
|
33
|
-
import { commandExists, resolveSessionPath } from "../utils.ts";
|
|
61
|
+
import { commandExists, resolveSessionPath, uriToFile } from "../utils.ts";
|
|
34
62
|
import {
|
|
35
63
|
closeFileAcrossClients,
|
|
36
64
|
pruneMissingFilesFromClients,
|
|
@@ -55,7 +83,10 @@ import type {
|
|
|
55
83
|
OutstandingDiagnosticSummaryEntry,
|
|
56
84
|
ServerStatus,
|
|
57
85
|
} from "./manager-types.ts";
|
|
58
|
-
import {
|
|
86
|
+
import {
|
|
87
|
+
recoverWorkspaceDiagnostics as recoverWorkspaceDiagnosticsImpl,
|
|
88
|
+
type WorkspaceRecoveryResult,
|
|
89
|
+
} from "./manager-workspace-recovery.ts";
|
|
59
90
|
import {
|
|
60
91
|
collectWorkspaceSymbols,
|
|
61
92
|
findWorkspaceSymbolWarmTargets,
|
|
@@ -66,6 +97,35 @@ import {
|
|
|
66
97
|
|
|
67
98
|
type UnavailableReason = "missing-command" | "start-failed" | "runtime-error";
|
|
68
99
|
|
|
100
|
+
/** Maximum tracked-file entries retained in one scope-decision telemetry summary. */
|
|
101
|
+
const SCOPE_DECISION_MAX_ENTRIES = 24;
|
|
102
|
+
|
|
103
|
+
interface ScopeDecisionAccumulator {
|
|
104
|
+
entries: ScopeDecisionEntry[];
|
|
105
|
+
counts: ScopeDecisionSummary["counts"];
|
|
106
|
+
basisCounts: Record<string, number>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function accumulateScopeDecision(
|
|
110
|
+
accumulator: ScopeDecisionAccumulator,
|
|
111
|
+
file: string,
|
|
112
|
+
decision: ReturnType<typeof getFileScopeDecision>,
|
|
113
|
+
): void {
|
|
114
|
+
const statusKey =
|
|
115
|
+
decision.status === "no-config"
|
|
116
|
+
? "noConfig"
|
|
117
|
+
: decision.status === "out-of-tree"
|
|
118
|
+
? "outOfTree"
|
|
119
|
+
: decision.status;
|
|
120
|
+
accumulator.counts[statusKey]++;
|
|
121
|
+
if (decision.basis) {
|
|
122
|
+
accumulator.basisCounts[decision.basis] = (accumulator.basisCounts[decision.basis] ?? 0) + 1;
|
|
123
|
+
}
|
|
124
|
+
if (accumulator.entries.length < SCOPE_DECISION_MAX_ENTRIES) {
|
|
125
|
+
accumulator.entries.push({ file, status: decision.status, basis: decision.basis });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
69
129
|
type FileRoute = {
|
|
70
130
|
serverName: string;
|
|
71
131
|
serverConfig: ServerConfig;
|
|
@@ -73,6 +133,24 @@ type FileRoute = {
|
|
|
73
133
|
key: string;
|
|
74
134
|
};
|
|
75
135
|
|
|
136
|
+
type ClientRestartResult = {
|
|
137
|
+
key: string;
|
|
138
|
+
serverName: string;
|
|
139
|
+
files: string[];
|
|
140
|
+
restarted: boolean;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export type ManagerLifecycleTransitionKind = LspClientLifecycleTransitionKind | "recovery";
|
|
144
|
+
|
|
145
|
+
/** Package-internal aggregate lifecycle snapshot for the runtime controller. */
|
|
146
|
+
export interface ManagerLifecycleTransition {
|
|
147
|
+
readonly kind: ManagerLifecycleTransitionKind;
|
|
148
|
+
readonly semanticReady: boolean;
|
|
149
|
+
readonly projectServers: readonly ProjectServerInfo[];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
type ManagerLifecycleListener = (transition: ManagerLifecycleTransition) => void;
|
|
153
|
+
|
|
76
154
|
// ── LspManager ────────────────────────────────────────────────────────
|
|
77
155
|
export class LspManager {
|
|
78
156
|
/** Active clients keyed by "serverName:root" */
|
|
@@ -83,10 +161,16 @@ export class LspManager {
|
|
|
83
161
|
private commandAvailability = new Map<string, boolean>();
|
|
84
162
|
/** Guards against concurrent client creation for the same server:root key */
|
|
85
163
|
private pendingStarts = new Map<string, Promise<LspClient | null>>();
|
|
164
|
+
/** Monotonic ownership generation for each replaceable client route. */
|
|
165
|
+
private clientGenerations = new Map<string, number>();
|
|
86
166
|
/** Preferred project roots discovered by proactive scan or lazy startup */
|
|
87
167
|
private knownRoots = new Map<string, string[]>();
|
|
88
168
|
/** User-configured gitignore-style exclude patterns */
|
|
89
169
|
private excludePatterns: string[] = [];
|
|
170
|
+
/** Monotonic workspace invalidation generation advanced by each invalidation event. */
|
|
171
|
+
private invalidationEpoch = 0;
|
|
172
|
+
/** Invalidation generation in which each route last received a recovery restart. */
|
|
173
|
+
private recoveryRestartEpochs = new Map<string, number>();
|
|
90
174
|
/** Project roots already warmed for workspace-symbol queries. */
|
|
91
175
|
private warmedWorkspaceSymbolProjects = new Set<string>();
|
|
92
176
|
/** Project roots whose semantic state was warmed with a readiness probe. */
|
|
@@ -96,6 +180,7 @@ export class LspManager {
|
|
|
96
180
|
constructor(
|
|
97
181
|
private readonly config: LspConfig,
|
|
98
182
|
private readonly cwd: string,
|
|
183
|
+
private readonly onLifecycleTransition?: ManagerLifecycleListener,
|
|
99
184
|
) {}
|
|
100
185
|
getCwd(): string {
|
|
101
186
|
return this.cwd;
|
|
@@ -152,6 +237,15 @@ export class LspManager {
|
|
|
152
237
|
|
|
153
238
|
return reason;
|
|
154
239
|
}
|
|
240
|
+
|
|
241
|
+
/** Return the open document version without starting a server. */
|
|
242
|
+
getOpenDocumentVersion(filePath: string): number | null {
|
|
243
|
+
const resolvedPath = resolveSessionPath(this.cwd, filePath);
|
|
244
|
+
return (
|
|
245
|
+
this.getExistingClientForFile(resolvedPath)?.getOpenDocumentVersion(resolvedPath) ?? null
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
155
249
|
/** Get or create an LSP client for the given file. */
|
|
156
250
|
async getClientForFile(filePath: string): Promise<LspClient | null> {
|
|
157
251
|
const route = this.resolveFileRoute(filePath);
|
|
@@ -194,6 +288,53 @@ export class LspManager {
|
|
|
194
288
|
}
|
|
195
289
|
}
|
|
196
290
|
|
|
291
|
+
private createClient(
|
|
292
|
+
serverName: string,
|
|
293
|
+
serverConfig: ServerConfig,
|
|
294
|
+
root: string,
|
|
295
|
+
key: string,
|
|
296
|
+
): LspClient {
|
|
297
|
+
const generation = (this.clientGenerations.get(key) ?? 0) + 1;
|
|
298
|
+
this.clientGenerations.set(key, generation);
|
|
299
|
+
let client: LspClient;
|
|
300
|
+
client = new LspClient(
|
|
301
|
+
serverName,
|
|
302
|
+
serverConfig,
|
|
303
|
+
root,
|
|
304
|
+
(kind) => {
|
|
305
|
+
this.handleClientLifecycle(key, generation, client, kind);
|
|
306
|
+
},
|
|
307
|
+
this.cwd,
|
|
308
|
+
);
|
|
309
|
+
return client;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private handleClientLifecycle(
|
|
313
|
+
key: string,
|
|
314
|
+
generation: number,
|
|
315
|
+
client: LspClient,
|
|
316
|
+
kind: LspClientLifecycleTransitionKind,
|
|
317
|
+
): void {
|
|
318
|
+
if (this.clientGenerations.get(key) !== generation) return;
|
|
319
|
+
if (this.clients.get(key) !== client) return;
|
|
320
|
+
const aggregateKind =
|
|
321
|
+
kind === "readiness" && generation > 1 && client.ready ? "recovery" : kind;
|
|
322
|
+
this.publishLifecycle(aggregateKind);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private publishLifecycle(kind: ManagerLifecycleTransitionKind): void {
|
|
326
|
+
if (!this.onLifecycleTransition) return;
|
|
327
|
+
const projectServers = this.getKnownProjectServers([]);
|
|
328
|
+
const semanticReady = projectServers.some(
|
|
329
|
+
(server) => server.status === "running" && server.ready,
|
|
330
|
+
);
|
|
331
|
+
try {
|
|
332
|
+
this.onLifecycleTransition({ kind, semanticReady, projectServers });
|
|
333
|
+
} catch {
|
|
334
|
+
// Runtime lifecycle consumers must not alter manager behavior.
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
197
338
|
/**
|
|
198
339
|
* Perform the actual server start — extracted so the public method can
|
|
199
340
|
* deduplicate via pendingStarts without wrapping the entire body.
|
|
@@ -211,7 +352,7 @@ export class LspManager {
|
|
|
211
352
|
}
|
|
212
353
|
|
|
213
354
|
// Spawn new client
|
|
214
|
-
const client =
|
|
355
|
+
const client = this.createClient(serverName, serverConfig, root, key);
|
|
215
356
|
this.clearWarmedWorkspaceSymbolProjects(serverName, root);
|
|
216
357
|
this.clearWarmedSemanticProjects(serverName, root);
|
|
217
358
|
this.clearPendingWarmProbes(serverName, root);
|
|
@@ -247,33 +388,51 @@ export class LspManager {
|
|
|
247
388
|
return route ? (this.clients.get(route.key) ?? null) : null;
|
|
248
389
|
}
|
|
249
390
|
|
|
250
|
-
/**
|
|
251
|
-
|
|
252
|
-
|
|
391
|
+
/**
|
|
392
|
+
* Restart the clients that own the supplied file paths, if any are active.
|
|
393
|
+
*
|
|
394
|
+
* Each route restarts at most once per invalidation generation. Pass
|
|
395
|
+
* `pushOnly` to restrict restarts to clients without pull diagnostics.
|
|
396
|
+
* The loop observes request cancellation between route restarts.
|
|
397
|
+
*/
|
|
398
|
+
async restartClientsForFiles(
|
|
399
|
+
filePaths: string[],
|
|
400
|
+
options?: { pushOnly?: boolean; control?: CodeRequestControl },
|
|
401
|
+
): Promise<ClientRestartResult[]> {
|
|
402
|
+
const restarted: ClientRestartResult[] = [];
|
|
253
403
|
const seen = new Set<string>();
|
|
254
404
|
|
|
255
405
|
for (const filePath of filePaths) {
|
|
256
406
|
const client = this.getExistingClientForFile(filePath);
|
|
257
407
|
if (!client) continue;
|
|
408
|
+
if (options?.pushOnly && client.hasDiagnosticProvider) continue;
|
|
258
409
|
|
|
259
410
|
const key = clientKey(client.name, client.root);
|
|
260
411
|
if (seen.has(key)) continue;
|
|
261
412
|
seen.add(key);
|
|
413
|
+
if (this.recoveryRestartEpochs.get(key) === this.invalidationEpoch) continue;
|
|
262
414
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
415
|
+
// Observe cancellation between route restarts so a cancelled pass does
|
|
416
|
+
// not keep restarting further clients.
|
|
417
|
+
throwIfCodeRequestInterrupted(options?.control);
|
|
418
|
+
const result = await this.restartClient(client);
|
|
419
|
+
this.recoveryRestartEpochs.set(key, this.invalidationEpoch);
|
|
420
|
+
restarted.push({ key, serverName: client.name, ...result });
|
|
266
421
|
}
|
|
267
422
|
|
|
268
423
|
return restarted;
|
|
269
424
|
}
|
|
270
425
|
|
|
271
|
-
private async restartClient(client: LspClient): Promise<boolean> {
|
|
426
|
+
private async restartClient(client: LspClient): Promise<{ files: string[]; restarted: boolean }> {
|
|
272
427
|
const key = clientKey(client.name, client.root);
|
|
273
428
|
const serverConfig = this.config.servers[client.name];
|
|
274
|
-
if (!serverConfig) return false;
|
|
429
|
+
if (!serverConfig) return { files: [], restarted: false };
|
|
275
430
|
|
|
276
431
|
const openFiles = client.openFiles;
|
|
432
|
+
const trackedDiagnosticFiles = client
|
|
433
|
+
.getDiagnosticSnapshot()
|
|
434
|
+
.documents.map((document) => uriToFile(document.uri));
|
|
435
|
+
const files = [...new Set([...openFiles, ...trackedDiagnosticFiles])];
|
|
277
436
|
try {
|
|
278
437
|
await client.shutdown();
|
|
279
438
|
} catch {
|
|
@@ -286,25 +445,37 @@ export class LspManager {
|
|
|
286
445
|
this.clearWarmedSemanticProjects(client.name, client.root);
|
|
287
446
|
this.clearPendingWarmProbes(client.name, client.root);
|
|
288
447
|
|
|
289
|
-
const replacement =
|
|
448
|
+
const replacement = this.createClient(client.name, serverConfig, client.root, key);
|
|
290
449
|
this.clients.set(key, replacement);
|
|
291
450
|
rememberKnownRoot(this.knownRoots, client.name, client.root);
|
|
292
451
|
|
|
293
452
|
try {
|
|
294
|
-
await
|
|
295
|
-
|
|
296
|
-
|
|
453
|
+
await withTimeout(
|
|
454
|
+
replacement.start(),
|
|
455
|
+
RECOVERY_CLIENT_STARTUP_BOUND_MS,
|
|
456
|
+
"replacement client startup bound exceeded",
|
|
457
|
+
);
|
|
458
|
+
const failedFiles: string[] = [];
|
|
459
|
+
for (const filePath of files) {
|
|
460
|
+
if (!fs.existsSync(filePath)) {
|
|
461
|
+
failedFiles.push(filePath);
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
297
464
|
try {
|
|
298
465
|
replacement.didOpen(filePath, fs.readFileSync(filePath, "utf-8"));
|
|
299
466
|
} catch {
|
|
300
|
-
|
|
467
|
+
failedFiles.push(filePath);
|
|
301
468
|
}
|
|
302
469
|
}
|
|
303
|
-
|
|
470
|
+
for (const filePath of failedFiles) replacement.markFailedFile(filePath);
|
|
471
|
+
return { files, restarted: true };
|
|
304
472
|
} catch {
|
|
305
473
|
this.clients.delete(key);
|
|
306
474
|
this.unavailable.set(key, "start-failed");
|
|
307
|
-
|
|
475
|
+
// A bound-exceeded start may have spawned a live process; terminate
|
|
476
|
+
// the tree so the orphan cannot outlive the failed restart.
|
|
477
|
+
void replacement.forceKill().catch(() => {});
|
|
478
|
+
return { files, restarted: false };
|
|
308
479
|
}
|
|
309
480
|
}
|
|
310
481
|
|
|
@@ -347,41 +518,68 @@ export class LspManager {
|
|
|
347
518
|
}
|
|
348
519
|
|
|
349
520
|
/** Wait until the client that owns a file is query-ready, then run a light warm-up probe. */
|
|
350
|
-
async waitUntilFileReady(
|
|
521
|
+
async waitUntilFileReady(
|
|
522
|
+
filePath: string,
|
|
523
|
+
control?: CodeRequestControl,
|
|
524
|
+
): Promise<LspClient | null> {
|
|
525
|
+
throwIfCodeRequestInterrupted(control);
|
|
351
526
|
const resolvedPath = resolveSessionPath(this.cwd, filePath);
|
|
352
527
|
const client = await this.getClientForFile(resolvedPath);
|
|
353
528
|
if (!client) return null;
|
|
354
|
-
|
|
355
|
-
|
|
529
|
+
// The caller's wait stops on cancellation even though the shared
|
|
530
|
+
// readiness state keeps its own lifecycle.
|
|
531
|
+
await client.getReady(control);
|
|
532
|
+
await this.warmSemanticProject(client, resolvedPath, true, control);
|
|
533
|
+
throwIfCodeRequestInterrupted(control);
|
|
356
534
|
return client;
|
|
357
535
|
}
|
|
358
536
|
|
|
359
537
|
/**
|
|
360
|
-
* Wait until
|
|
361
|
-
* Returns the number of concrete clients that
|
|
538
|
+
* Wait until one started client is query-ready, then warm its project.
|
|
539
|
+
* Returns the number of concrete clients that are ready after the warm-up.
|
|
362
540
|
*/
|
|
363
|
-
async waitUntilWorkspaceReady(): Promise<number> {
|
|
541
|
+
async waitUntilWorkspaceReady(control?: CodeRequestControl): Promise<number> {
|
|
542
|
+
throwIfCodeRequestInterrupted(control);
|
|
364
543
|
const activeClients = Array.from(this.clients.values()).filter(
|
|
365
544
|
(client) => client.status === "running",
|
|
366
545
|
);
|
|
367
|
-
|
|
546
|
+
if (activeClients.length === 0) return 0;
|
|
368
547
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
548
|
+
const alreadyReady = activeClients.find((client) => client.ready);
|
|
549
|
+
const firstReady =
|
|
550
|
+
alreadyReady ??
|
|
551
|
+
(await Promise.any(
|
|
552
|
+
activeClients.map(async (client) => {
|
|
553
|
+
await client.getReady(control);
|
|
554
|
+
if (client.status !== "running" || !client.ready) {
|
|
555
|
+
throw new Error("LSP client did not reach concrete readiness.");
|
|
556
|
+
}
|
|
557
|
+
return client;
|
|
558
|
+
}),
|
|
559
|
+
).catch(() => null));
|
|
560
|
+
// Promise.any swallows individual rejections, so an interruption that
|
|
561
|
+
// raced the readiness wait must be re-raised before it degrades into a
|
|
562
|
+
// zero-ready outcome.
|
|
563
|
+
throwIfCodeRequestInterrupted(control);
|
|
564
|
+
if (!firstReady) return 0;
|
|
565
|
+
|
|
566
|
+
const serverConfig = this.config.servers[firstReady.name];
|
|
567
|
+
if (serverConfig) {
|
|
372
568
|
const target = findWorkspaceSymbolWarmTargets(
|
|
373
|
-
|
|
569
|
+
firstReady.root,
|
|
374
570
|
serverConfig.rootMarkers,
|
|
375
571
|
serverConfig.fileTypes,
|
|
376
572
|
)[0];
|
|
377
|
-
if (
|
|
378
|
-
await this.warmSemanticProject(client, target.file);
|
|
573
|
+
if (target) await this.warmSemanticProject(firstReady, target.file, false, control);
|
|
379
574
|
}
|
|
380
|
-
|
|
575
|
+
|
|
576
|
+
throwIfCodeRequestInterrupted(control);
|
|
577
|
+
return activeClients.filter((client) => client.status === "running" && client.ready).length;
|
|
381
578
|
}
|
|
382
579
|
async syncFileAndGetDiagnostics(
|
|
383
580
|
filePath: string,
|
|
384
581
|
maxSeverity: number = 1,
|
|
582
|
+
control?: CodeRequestControl,
|
|
385
583
|
): Promise<CodeQueryResult<Diagnostic[]>> {
|
|
386
584
|
const resolvedPath = resolveSessionPath(this.cwd, filePath);
|
|
387
585
|
const client = await this.getClientForFile(resolvedPath);
|
|
@@ -389,11 +587,11 @@ export class LspManager {
|
|
|
389
587
|
return unavailableCodeQuery(`No LSP client can collect diagnostics for ${resolvedPath}.`);
|
|
390
588
|
}
|
|
391
589
|
try {
|
|
392
|
-
return
|
|
393
|
-
kind: "completed",
|
|
394
|
-
data: await syncClientFileAndGetDiagnostics(client, resolvedPath, maxSeverity),
|
|
395
|
-
};
|
|
590
|
+
return await syncClientFileAndGetDiagnostics(client, resolvedPath, maxSeverity, control);
|
|
396
591
|
} catch (error) {
|
|
592
|
+
// Cancellation must propagate; a cancelled caller no longer awaits a
|
|
593
|
+
// diagnostic result or the file cleanup side effects.
|
|
594
|
+
if (isCodeRequestInterruption(error, control)) throw error;
|
|
397
595
|
this.closeFile(resolvedPath);
|
|
398
596
|
const detail = error instanceof Error ? error.message : String(error);
|
|
399
597
|
return unavailableCodeQuery(`Diagnostic collection failed for ${resolvedPath}: ${detail}`);
|
|
@@ -407,9 +605,12 @@ export class LspManager {
|
|
|
407
605
|
pruneMissingFiles(): string[] {
|
|
408
606
|
return pruneMissingFilesFromClients(this.clients.values());
|
|
409
607
|
}
|
|
410
|
-
/** Re-sync all open documents across active clients and
|
|
411
|
-
async refreshOpenDiagnostics(
|
|
412
|
-
|
|
608
|
+
/** Re-sync all open documents across active clients and return exact evidence coverage. */
|
|
609
|
+
async refreshOpenDiagnostics(
|
|
610
|
+
options?: { maxWaitMs?: number; quietMs?: number } & CodeRequestControl,
|
|
611
|
+
): Promise<DiagnosticEvidenceSummary> {
|
|
612
|
+
const summary = await refreshOpenDiagnosticsForClients(this.clients.values(), options);
|
|
613
|
+
return this.toWorkspaceDiagnosticEvidence(summary.documents);
|
|
413
614
|
}
|
|
414
615
|
|
|
415
616
|
/** Clear cached pull-diagnostic result IDs across all clients. */
|
|
@@ -426,21 +627,59 @@ export class LspManager {
|
|
|
426
627
|
}
|
|
427
628
|
}
|
|
428
629
|
|
|
630
|
+
/**
|
|
631
|
+
* Record one workspace invalidation event and forward it to clients.
|
|
632
|
+
*
|
|
633
|
+
* Every call with a non-empty change batch advances the invalidation
|
|
634
|
+
* generation, so a repeated edit to the same file opens a fresh
|
|
635
|
+
* generation. Recovery passes forward changes through
|
|
636
|
+
* {@link notifyWorkspaceFileChanges} and never advance the generation.
|
|
637
|
+
*/
|
|
638
|
+
noteWorkspaceChanges(changes: FileEvent[]): void {
|
|
639
|
+
if (changes.length > 0) this.invalidationEpoch++;
|
|
640
|
+
this.notifyWorkspaceFileChanges(changes);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/** Return per-route diagnostic evidence capability for recovery targeting. */
|
|
644
|
+
getClientDiagnosticRoutes(): Array<{
|
|
645
|
+
key: string;
|
|
646
|
+
supportsPull: boolean;
|
|
647
|
+
unconfirmedFiles: string[];
|
|
648
|
+
stallSignal: ReturnType<LspClient["getRecoveryStallSignal"]>;
|
|
649
|
+
}> {
|
|
650
|
+
const routes: Array<{
|
|
651
|
+
key: string;
|
|
652
|
+
supportsPull: boolean;
|
|
653
|
+
unconfirmedFiles: string[];
|
|
654
|
+
stallSignal: ReturnType<LspClient["getRecoveryStallSignal"]>;
|
|
655
|
+
}> = [];
|
|
656
|
+
for (const [key, client] of this.clients) {
|
|
657
|
+
if (client.status !== "running") continue;
|
|
658
|
+
const unconfirmedFiles = client
|
|
659
|
+
.getDiagnosticSnapshot()
|
|
660
|
+
.documents.filter((document) => document.status === "unconfirmed")
|
|
661
|
+
.map((document) => uriToFile(document.uri))
|
|
662
|
+
.filter((file) => this.isDiagnosticFile(file));
|
|
663
|
+
routes.push({
|
|
664
|
+
key,
|
|
665
|
+
supportsPull: client.hasDiagnosticProvider,
|
|
666
|
+
unconfirmedFiles,
|
|
667
|
+
stallSignal: client.getRecoveryStallSignal(),
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
return routes;
|
|
671
|
+
}
|
|
672
|
+
|
|
429
673
|
/** Force a workspace-wide diagnostic recovery pass. */
|
|
430
674
|
async recoverWorkspaceDiagnostics(options?: {
|
|
431
675
|
changes?: FileEvent[];
|
|
432
676
|
restartIfStillStale?: boolean;
|
|
433
677
|
maxWaitMs?: number;
|
|
434
678
|
quietMs?: number;
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
suspected: boolean;
|
|
440
|
-
matchedFiles: Array<{ file: string; diagnostics: Diagnostic[] }>;
|
|
441
|
-
warning: string | null;
|
|
442
|
-
};
|
|
443
|
-
}> {
|
|
679
|
+
/** Evidence from a refresh the caller already completed; skips this pass's own refresh when no watched-file changes apply. */
|
|
680
|
+
initialEvidence?: DiagnosticEvidenceSummary;
|
|
681
|
+
control?: CodeRequestControl;
|
|
682
|
+
}): Promise<WorkspaceRecoveryResult> {
|
|
444
683
|
return recoverWorkspaceDiagnosticsImpl(this, options);
|
|
445
684
|
}
|
|
446
685
|
|
|
@@ -448,7 +687,12 @@ export class LspManager {
|
|
|
448
687
|
async shutdownAll(): Promise<void> {
|
|
449
688
|
const shutdowns = Array.from(this.clients.values()).map((c) => c.shutdown().catch(() => {}));
|
|
450
689
|
await Promise.all(shutdowns);
|
|
690
|
+
// Dispose every client so pending or active progress tokens and
|
|
691
|
+
// readiness promises cannot outlive the manager.
|
|
692
|
+
for (const client of this.clients.values()) client.dispose();
|
|
451
693
|
this.clients.clear();
|
|
694
|
+
this.clientGenerations.clear();
|
|
695
|
+
this.recoveryRestartEpochs.clear();
|
|
452
696
|
this.unavailable.clear();
|
|
453
697
|
this.knownRoots.clear();
|
|
454
698
|
this.warmedWorkspaceSymbolProjects.clear();
|
|
@@ -456,6 +700,30 @@ export class LspManager {
|
|
|
456
700
|
this.pendingWarmProbes.clear();
|
|
457
701
|
}
|
|
458
702
|
/** Get status of all servers. */
|
|
703
|
+
getRunningClientCount(): number {
|
|
704
|
+
return Array.from(this.clients.values()).filter((client) => client.status === "running").length;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/** Get the server names of all running clients, for recovery telemetry. */
|
|
708
|
+
getRunningClientNames(): string[] {
|
|
709
|
+
return Array.from(this.clients.values())
|
|
710
|
+
.filter((client) => client.status === "running")
|
|
711
|
+
.map((client) => client.name);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/** Check whether a path belongs to the configured diagnostic evidence scope. */
|
|
715
|
+
isDiagnosticFile(filePath: string): boolean {
|
|
716
|
+
const relative = path.relative(this.cwd, path.resolve(this.cwd, filePath));
|
|
717
|
+
return (
|
|
718
|
+
!shouldIgnoreLspPath(relative, this.cwd) &&
|
|
719
|
+
!isExcludedByPattern(relative, this.excludePatterns)
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
getDiagnosticEvidence(): DiagnosticEvidenceSummary {
|
|
724
|
+
return this.getDiagnosticSnapshot().evidence;
|
|
725
|
+
}
|
|
726
|
+
|
|
459
727
|
getStatus(): ManagerStatus {
|
|
460
728
|
this.pruneMissingFiles();
|
|
461
729
|
const servers: ServerStatus[] = [];
|
|
@@ -538,41 +806,117 @@ export class LspManager {
|
|
|
538
806
|
.filter((entry) => entry.openFiles.length > 0);
|
|
539
807
|
return formatCoverageSummaryText(relevantEntries, maxServers, maxFiles);
|
|
540
808
|
}
|
|
809
|
+
/**
|
|
810
|
+
* Capture one coherent diagnostic report from the current client snapshots.
|
|
811
|
+
*
|
|
812
|
+
* Summary and detailed projections share the same cache observation so a
|
|
813
|
+
* caller cannot combine entries from one generation with evidence from
|
|
814
|
+
* another.
|
|
815
|
+
*/
|
|
816
|
+
getWorkspaceDiagnosticReport(maxSeverity: number = 2): WorkspaceDiagnosticReport {
|
|
817
|
+
this.pruneMissingFiles();
|
|
818
|
+
const snapshots = Array.from(this.clients.values()).map((client) =>
|
|
819
|
+
client.getDiagnosticSnapshot(),
|
|
820
|
+
);
|
|
821
|
+
const evidence = this.toWorkspaceDiagnosticEvidence(
|
|
822
|
+
snapshots.flatMap((snapshot) =>
|
|
823
|
+
snapshot.documents.map((document) => ({
|
|
824
|
+
file: relativeFilePathFromUri(document.uri, this.cwd),
|
|
825
|
+
status: document.status,
|
|
826
|
+
})),
|
|
827
|
+
),
|
|
828
|
+
);
|
|
829
|
+
const current =
|
|
830
|
+
snapshots.every((snapshot) => snapshot.current) &&
|
|
831
|
+
evidence.documents.every((document) => document.status === "confirmed");
|
|
832
|
+
const fileDiags = new Map<string, { errors: number; warnings: number }>();
|
|
833
|
+
for (const snapshot of snapshots) {
|
|
834
|
+
for (const entry of snapshot.entries) {
|
|
835
|
+
collectDiagnosticSummaryCounts(fileDiags, entry, this.cwd, this.excludePatterns);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
return {
|
|
840
|
+
summary: {
|
|
841
|
+
entries: Array.from(fileDiags.entries()).map(([file, counts]) => ({ file, ...counts })),
|
|
842
|
+
current,
|
|
843
|
+
evidence,
|
|
844
|
+
},
|
|
845
|
+
outstanding: {
|
|
846
|
+
entries: collectOutstandingDiagnosticsDetailed(
|
|
847
|
+
snapshots.map((snapshot) => snapshot.entries),
|
|
848
|
+
this.cwd,
|
|
849
|
+
this.excludePatterns,
|
|
850
|
+
maxSeverity,
|
|
851
|
+
),
|
|
852
|
+
current,
|
|
853
|
+
evidence,
|
|
854
|
+
},
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
|
|
541
858
|
/** Get a diagnostic summary across all servers and files. */
|
|
542
859
|
getDiagnosticSummary(): DiagnosticSummary[] {
|
|
860
|
+
return this.getDiagnosticSnapshot().entries;
|
|
861
|
+
}
|
|
862
|
+
getDiagnosticSnapshot(): {
|
|
863
|
+
entries: DiagnosticSummary[];
|
|
864
|
+
current: boolean;
|
|
865
|
+
evidence: DiagnosticEvidenceSummary;
|
|
866
|
+
} {
|
|
543
867
|
this.pruneMissingFiles();
|
|
544
868
|
const fileDiags = new Map<string, { errors: number; warnings: number }>();
|
|
869
|
+
const evidenceDocuments: DiagnosticEvidenceDocument[] = [];
|
|
870
|
+
let clientCurrent = true;
|
|
545
871
|
for (const client of this.clients.values()) {
|
|
546
|
-
|
|
872
|
+
const snapshot = client.getDiagnosticSnapshot();
|
|
873
|
+
clientCurrent &&= snapshot.current;
|
|
874
|
+
for (const document of snapshot.documents) {
|
|
875
|
+
evidenceDocuments.push({
|
|
876
|
+
file: relativeFilePathFromUri(document.uri, this.cwd),
|
|
877
|
+
status: document.status,
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
for (const entry of snapshot.entries) {
|
|
547
881
|
collectDiagnosticSummaryCounts(fileDiags, entry, this.cwd, this.excludePatterns);
|
|
548
882
|
}
|
|
549
883
|
}
|
|
550
|
-
|
|
884
|
+
const evidence = this.toWorkspaceDiagnosticEvidence(evidenceDocuments);
|
|
885
|
+
return {
|
|
886
|
+
entries: Array.from(fileDiags.entries()).map(([file, counts]) => ({ file, ...counts })),
|
|
887
|
+
current:
|
|
888
|
+
clientCurrent && evidence.documents.every((document) => document.status === "confirmed"),
|
|
889
|
+
evidence,
|
|
890
|
+
};
|
|
551
891
|
}
|
|
552
892
|
/** Get outstanding diagnostics at or above the configured inline threshold. */
|
|
553
893
|
getOutstandingDiagnosticSummary(maxSeverity: number = 1): OutstandingDiagnosticSummaryEntry[] {
|
|
894
|
+
return this.getOutstandingDiagnosticSummarySnapshot(maxSeverity).entries;
|
|
895
|
+
}
|
|
896
|
+
getOutstandingDiagnosticSummarySnapshot(maxSeverity: number = 1): {
|
|
897
|
+
entries: OutstandingDiagnosticSummaryEntry[];
|
|
898
|
+
current: boolean;
|
|
899
|
+
evidence: DiagnosticEvidenceSummary;
|
|
900
|
+
} {
|
|
554
901
|
this.pruneMissingFiles();
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
for (const entry of client.getAllDiagnostics()) {
|
|
558
|
-
const file = relativeFilePathFromUri(entry.uri, this.cwd);
|
|
559
|
-
if (shouldIgnoreLspPath(file, this.cwd)) continue;
|
|
560
|
-
if (isExcludedByPattern(file, this.excludePatterns)) continue;
|
|
561
|
-
const current = fileDiags.get(file) ?? createOutstandingDiagnosticSummary(file);
|
|
562
|
-
const next = accumulateOutstandingDiagnostics(current, entry.diagnostics, maxSeverity);
|
|
563
|
-
if (next.total > 0) {
|
|
564
|
-
fileDiags.set(file, next);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
return Array.from(fileDiags.values()).sort(
|
|
569
|
-
(a, b) =>
|
|
570
|
-
b.errors - a.errors ||
|
|
571
|
-
b.warnings - a.warnings ||
|
|
572
|
-
b.information - a.information ||
|
|
573
|
-
b.hints - a.hints ||
|
|
574
|
-
a.file.localeCompare(b.file),
|
|
902
|
+
const snapshots = Array.from(this.clients.values()).map((client) =>
|
|
903
|
+
client.getDiagnosticSnapshot(),
|
|
575
904
|
);
|
|
905
|
+
const evidence = this.toWorkspaceDiagnosticEvidence(
|
|
906
|
+
collectClientDiagnosticEvidenceDocuments(snapshots, this.cwd),
|
|
907
|
+
);
|
|
908
|
+
const clientCurrent = snapshots.every((snapshot) => snapshot.current);
|
|
909
|
+
return {
|
|
910
|
+
entries: collectOutstandingDiagnosticSummaryEntries(
|
|
911
|
+
snapshots,
|
|
912
|
+
this.cwd,
|
|
913
|
+
this.excludePatterns,
|
|
914
|
+
maxSeverity,
|
|
915
|
+
),
|
|
916
|
+
current:
|
|
917
|
+
clientCurrent && evidence.documents.every((document) => document.status === "confirmed"),
|
|
918
|
+
evidence,
|
|
919
|
+
};
|
|
576
920
|
}
|
|
577
921
|
getRelevantOutstandingDiagnosticsSummaryText(
|
|
578
922
|
relevantPaths: string[],
|
|
@@ -591,28 +935,104 @@ export class LspManager {
|
|
|
591
935
|
getOutstandingDiagnostics(
|
|
592
936
|
maxSeverity: number = 1,
|
|
593
937
|
): Array<{ file: string; diagnostics: Diagnostic[] }> {
|
|
938
|
+
return this.getOutstandingDiagnosticsSnapshot(maxSeverity).entries;
|
|
939
|
+
}
|
|
940
|
+
getOutstandingDiagnosticsSnapshot(maxSeverity: number = 1): {
|
|
941
|
+
entries: Array<{ file: string; diagnostics: Diagnostic[] }>;
|
|
942
|
+
current: boolean;
|
|
943
|
+
evidence: DiagnosticEvidenceSummary;
|
|
944
|
+
} {
|
|
594
945
|
this.pruneMissingFiles();
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
946
|
+
const snapshots = Array.from(this.clients.values()).map((client) =>
|
|
947
|
+
client.getDiagnosticSnapshot(),
|
|
948
|
+
);
|
|
949
|
+
const evidenceDocuments = snapshots.flatMap((snapshot) =>
|
|
950
|
+
snapshot.documents.map((document) => ({
|
|
951
|
+
file: relativeFilePathFromUri(document.uri, this.cwd),
|
|
952
|
+
status: document.status,
|
|
953
|
+
})),
|
|
954
|
+
);
|
|
955
|
+
const evidence = this.toWorkspaceDiagnosticEvidence(evidenceDocuments);
|
|
956
|
+
return {
|
|
957
|
+
entries: collectOutstandingDiagnosticsDetailed(
|
|
958
|
+
snapshots.map((snapshot) => snapshot.entries),
|
|
959
|
+
this.cwd,
|
|
960
|
+
this.excludePatterns,
|
|
961
|
+
maxSeverity,
|
|
962
|
+
),
|
|
963
|
+
current:
|
|
964
|
+
snapshots.every((snapshot) => snapshot.current) &&
|
|
965
|
+
evidence.documents.every((document) => document.status === "confirmed"),
|
|
966
|
+
evidence,
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/**
|
|
971
|
+
* Aggregate the tsconfig scope decision for every tracked file.
|
|
972
|
+
*
|
|
973
|
+
* The recovery telemetry surface uses this to report why tracked files are
|
|
974
|
+
* in or out of scope without re-deriving the scope logic. The returned
|
|
975
|
+
* entry list is bounded; counts are exact.
|
|
976
|
+
*/
|
|
977
|
+
getScopeDecisionSummary(): ScopeDecisionSummary {
|
|
978
|
+
this.pruneMissingFiles();
|
|
979
|
+
const accumulator: ScopeDecisionAccumulator = {
|
|
980
|
+
entries: [],
|
|
981
|
+
counts: { included: 0, excluded: 0, noConfig: 0, outOfTree: 0 },
|
|
982
|
+
basisCounts: {},
|
|
983
|
+
};
|
|
984
|
+
let totalFiles = 0;
|
|
985
|
+
|
|
986
|
+
for (const client of this.clients.values()) {
|
|
987
|
+
for (const document of client.getDiagnosticSnapshot().documents) {
|
|
988
|
+
const file = relativeFilePathFromUri(document.uri, this.cwd);
|
|
989
|
+
totalFiles++;
|
|
990
|
+
accumulateScopeDecision(accumulator, file, getFileScopeDecision(file, this.cwd));
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
return {
|
|
995
|
+
caseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
|
|
996
|
+
counts: accumulator.counts,
|
|
997
|
+
basisCounts: accumulator.basisCounts,
|
|
998
|
+
entries: accumulator.entries,
|
|
999
|
+
totalFiles,
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
private toWorkspaceDiagnosticEvidence(
|
|
1003
|
+
documents: readonly DiagnosticEvidenceDocument[],
|
|
1004
|
+
): DiagnosticEvidenceSummary {
|
|
1005
|
+
const byFile = new Map<string, DiagnosticEvidenceDocument>();
|
|
1006
|
+
for (const document of documents) {
|
|
1007
|
+
const file = path.relative(this.cwd, path.resolve(this.cwd, document.file));
|
|
1008
|
+
if (shouldIgnoreLspPath(file, this.cwd)) continue;
|
|
1009
|
+
if (isExcludedByPattern(file, this.excludePatterns)) continue;
|
|
1010
|
+
const existing = byFile.get(file);
|
|
1011
|
+
if (!existing || evidenceStatusRank(document.status) > evidenceStatusRank(existing.status)) {
|
|
1012
|
+
byFile.set(file, { file, status: document.status });
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
return summarizeDiagnosticEvidence(
|
|
1016
|
+
Array.from(byFile.values()).sort((a, b) => a.file.localeCompare(b.file)),
|
|
600
1017
|
);
|
|
601
1018
|
}
|
|
1019
|
+
|
|
602
1020
|
async workspaceSymbol(
|
|
603
1021
|
query: string,
|
|
1022
|
+
control?: CodeRequestControl,
|
|
604
1023
|
): Promise<CodeQueryResult<(SymbolInformation | WorkspaceSymbol)[]>> {
|
|
605
|
-
const initial = await collectWorkspaceSymbols(this.clients.values(), query);
|
|
1024
|
+
const initial = await collectWorkspaceSymbols(this.clients.values(), query, control);
|
|
606
1025
|
if (!initial.hasSupport || initial.results.length > 0) {
|
|
607
1026
|
return workspaceSymbolCollectionResult(initial);
|
|
608
1027
|
}
|
|
609
1028
|
|
|
610
|
-
const warmed = await this.warmWorkspaceSymbolProjectsUntilResult(
|
|
611
|
-
findWorkspaceSymbolWarmTargets,
|
|
612
|
-
getWorkspaceSymbolWarmPosition,
|
|
613
|
-
collectWorkspaceSymbols,
|
|
1029
|
+
const warmed = await this.warmWorkspaceSymbolProjectsUntilResult({
|
|
1030
|
+
findWarmTargets: findWorkspaceSymbolWarmTargets,
|
|
1031
|
+
getWarmPosition: getWorkspaceSymbolWarmPosition,
|
|
1032
|
+
collect: (clients, value) => collectWorkspaceSymbols(clients, value, control),
|
|
614
1033
|
query,
|
|
615
|
-
|
|
1034
|
+
control,
|
|
1035
|
+
});
|
|
616
1036
|
return workspaceSymbolCollectionResult(warmed.collection ?? initial);
|
|
617
1037
|
}
|
|
618
1038
|
async ensureFileOpen(filePath: string): Promise<LspClient | null> {
|
|
@@ -629,18 +1049,20 @@ export class LspManager {
|
|
|
629
1049
|
}
|
|
630
1050
|
|
|
631
1051
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: warm-up coordinates client/project iteration, targeted semantic nudges, and early-return queries in one place.
|
|
632
|
-
private async warmWorkspaceSymbolProjectsUntilResult(
|
|
1052
|
+
private async warmWorkspaceSymbolProjectsUntilResult(options: {
|
|
633
1053
|
findWarmTargets: (
|
|
634
1054
|
root: string,
|
|
635
1055
|
rootMarkers: string[],
|
|
636
1056
|
fileTypes: string[],
|
|
637
|
-
) => Array<{ projectRoot: string; file: string }
|
|
1057
|
+
) => Array<{ projectRoot: string; file: string }>;
|
|
638
1058
|
getWarmPosition: (
|
|
639
1059
|
symbols: import("../config/types.ts").DocumentSymbol[] | SymbolInformation[] | null,
|
|
640
|
-
) => import("../config/types.ts").Position | null
|
|
641
|
-
collect: (clients: Iterable<LspClient>, query: string) => Promise<WorkspaceSymbolCollection
|
|
642
|
-
query: string
|
|
643
|
-
|
|
1060
|
+
) => import("../config/types.ts").Position | null;
|
|
1061
|
+
collect: (clients: Iterable<LspClient>, query: string) => Promise<WorkspaceSymbolCollection>;
|
|
1062
|
+
query: string;
|
|
1063
|
+
control?: CodeRequestControl;
|
|
1064
|
+
}): Promise<{ warmedAny: boolean; collection: WorkspaceSymbolCollection | null }> {
|
|
1065
|
+
const { findWarmTargets, getWarmPosition, collect, query, control } = options;
|
|
644
1066
|
let warmedAny = false;
|
|
645
1067
|
|
|
646
1068
|
for (const client of Array.from(this.clients.values())) {
|
|
@@ -667,13 +1089,20 @@ export class LspManager {
|
|
|
667
1089
|
const openedClient = await this.ensureFileOpen(target.file);
|
|
668
1090
|
if (!openedClient) continue;
|
|
669
1091
|
|
|
1092
|
+
// A cancelled pass must not mark projects warmed on its way out.
|
|
1093
|
+
throwIfCodeRequestInterrupted(control);
|
|
670
1094
|
this.warmedWorkspaceSymbolProjects.add(projectKey);
|
|
671
1095
|
warmedAny = true;
|
|
672
1096
|
|
|
673
|
-
const symbols =
|
|
1097
|
+
const symbols = control
|
|
1098
|
+
? await openedClient.documentSymbols(target.file, control)
|
|
1099
|
+
: await openedClient.documentSymbols(target.file);
|
|
674
1100
|
if (symbols.kind !== "unavailable") {
|
|
675
1101
|
const hoverPosition = getWarmPosition(symbols.data);
|
|
676
|
-
if (hoverPosition)
|
|
1102
|
+
if (hoverPosition) {
|
|
1103
|
+
if (control) await openedClient.hover(target.file, hoverPosition, control);
|
|
1104
|
+
else await openedClient.hover(target.file, hoverPosition);
|
|
1105
|
+
}
|
|
677
1106
|
}
|
|
678
1107
|
|
|
679
1108
|
const collected = await collect(this.clients.values(), query);
|
|
@@ -690,6 +1119,7 @@ export class LspManager {
|
|
|
690
1119
|
client: LspClient,
|
|
691
1120
|
filePath: string,
|
|
692
1121
|
preferExactFile: boolean = false,
|
|
1122
|
+
control?: CodeRequestControl,
|
|
693
1123
|
): Promise<void> {
|
|
694
1124
|
const serverConfig = this.config.servers[client.name];
|
|
695
1125
|
if (!serverConfig) return;
|
|
@@ -706,14 +1136,16 @@ export class LspManager {
|
|
|
706
1136
|
|
|
707
1137
|
const pending = this.pendingWarmProbes.get(projectKey);
|
|
708
1138
|
if (pending) {
|
|
709
|
-
|
|
1139
|
+
// The shared probe keeps running for other consumers; only this
|
|
1140
|
+
// caller's wait stops when it cancels.
|
|
1141
|
+
await raceRequestControl(pending, control);
|
|
710
1142
|
return;
|
|
711
1143
|
}
|
|
712
1144
|
|
|
713
|
-
const probe = this.performWarmProbe(client, resolvedFile, projectKey);
|
|
1145
|
+
const probe = this.performWarmProbe(client, resolvedFile, projectKey, control);
|
|
714
1146
|
this.pendingWarmProbes.set(projectKey, probe);
|
|
715
1147
|
try {
|
|
716
|
-
await probe;
|
|
1148
|
+
await raceRequestControl(probe, control);
|
|
717
1149
|
} finally {
|
|
718
1150
|
this.pendingWarmProbes.delete(projectKey);
|
|
719
1151
|
}
|
|
@@ -723,16 +1155,23 @@ export class LspManager {
|
|
|
723
1155
|
_client: LspClient,
|
|
724
1156
|
resolvedFile: string,
|
|
725
1157
|
projectKey: string,
|
|
1158
|
+
control?: CodeRequestControl,
|
|
726
1159
|
): Promise<void> {
|
|
727
1160
|
const openedClient = await this.ensureFileOpen(resolvedFile);
|
|
728
1161
|
if (!openedClient) return;
|
|
729
1162
|
|
|
730
1163
|
this.warmedSemanticProjects.add(projectKey);
|
|
731
1164
|
|
|
732
|
-
|
|
1165
|
+
// The probe is shared state for concurrent callers. One caller's
|
|
1166
|
+
// cancellation must not cancel the shared in-flight queries, so only the
|
|
1167
|
+
// Debug Operation ID is forwarded; each caller races its own wait above.
|
|
1168
|
+
const sharedControl = { operationId: control?.operationId };
|
|
1169
|
+
const symbols = await openedClient.documentSymbols(resolvedFile, sharedControl);
|
|
733
1170
|
if (symbols.kind === "unavailable") return;
|
|
734
1171
|
const hoverPosition = getWorkspaceSymbolWarmPosition(symbols.data);
|
|
735
|
-
if (hoverPosition)
|
|
1172
|
+
if (hoverPosition) {
|
|
1173
|
+
await openedClient.hover(resolvedFile, hoverPosition, sharedControl);
|
|
1174
|
+
}
|
|
736
1175
|
}
|
|
737
1176
|
|
|
738
1177
|
private hasOpenFileInProject(client: LspClient, projectRoot: string): boolean {
|
|
@@ -770,3 +1209,54 @@ export class LspManager {
|
|
|
770
1209
|
}
|
|
771
1210
|
}
|
|
772
1211
|
}
|
|
1212
|
+
|
|
1213
|
+
function collectClientDiagnosticEvidenceDocuments(
|
|
1214
|
+
snapshots: Iterable<ClientDiagnosticSnapshot>,
|
|
1215
|
+
cwd: string,
|
|
1216
|
+
): DiagnosticEvidenceDocument[] {
|
|
1217
|
+
return Array.from(snapshots).flatMap((snapshot) =>
|
|
1218
|
+
snapshot.documents.map((document) => ({
|
|
1219
|
+
file: relativeFilePathFromUri(document.uri, cwd),
|
|
1220
|
+
status: document.status,
|
|
1221
|
+
})),
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
function collectOutstandingDiagnosticSummaryEntries(
|
|
1226
|
+
snapshots: Iterable<ClientDiagnosticSnapshot>,
|
|
1227
|
+
cwd: string,
|
|
1228
|
+
excludePatterns: string[],
|
|
1229
|
+
maxSeverity: number,
|
|
1230
|
+
): OutstandingDiagnosticSummaryEntry[] {
|
|
1231
|
+
const fileDiags = new Map<string, OutstandingDiagnosticSummaryEntry>();
|
|
1232
|
+
for (const snapshot of snapshots) {
|
|
1233
|
+
for (const entry of snapshot.entries) {
|
|
1234
|
+
const file = relativeFilePathFromUri(entry.uri, cwd);
|
|
1235
|
+
if (shouldIgnoreLspPath(file, cwd) || isExcludedByPattern(file, excludePatterns)) continue;
|
|
1236
|
+
const existing = fileDiags.get(file) ?? createOutstandingDiagnosticSummary(file);
|
|
1237
|
+
const next = accumulateOutstandingDiagnostics(existing, entry.diagnostics, maxSeverity);
|
|
1238
|
+
if (next.total > 0) fileDiags.set(file, next);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
return Array.from(fileDiags.values()).sort(
|
|
1242
|
+
(a, b) =>
|
|
1243
|
+
b.errors - a.errors ||
|
|
1244
|
+
b.warnings - a.warnings ||
|
|
1245
|
+
b.information - a.information ||
|
|
1246
|
+
b.hints - a.hints ||
|
|
1247
|
+
a.file.localeCompare(b.file),
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
function evidenceStatusRank(status: DiagnosticEvidenceDocument["status"]): number {
|
|
1252
|
+
switch (status) {
|
|
1253
|
+
case "confirmed":
|
|
1254
|
+
return 1;
|
|
1255
|
+
case "unconfirmed":
|
|
1256
|
+
return 2;
|
|
1257
|
+
case "failed":
|
|
1258
|
+
return 3;
|
|
1259
|
+
case "removed":
|
|
1260
|
+
return 4;
|
|
1261
|
+
}
|
|
1262
|
+
}
|