@mrclrchtr/supi-code-intelligence 6.1.0 → 6.2.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 +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
- package/node_modules/@mrclrchtr/supi-lsp/README.md +21 -3
- 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-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
- package/node_modules/@mrclrchtr/supi-lsp/package.json +4 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +12 -7
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +0 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +6 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +2 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +5 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +2 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +17 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +40 -18
- package/node_modules/@mrclrchtr/supi-lsp/src/config/config.ts +164 -30
- package/node_modules/@mrclrchtr/supi-lsp/src/config/lsp-settings.ts +9 -87
- package/node_modules/@mrclrchtr/supi-lsp/src/config/server-config.ts +0 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +4 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/config/types.ts +0 -29
- package/node_modules/@mrclrchtr/supi-lsp/src/debug-telemetry.ts +7 -21
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-severity.ts +6 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-summary.ts +11 -14
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +0 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/stale-diagnostics.ts +0 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +29 -49
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +4 -8
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-helpers.ts +1 -9
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +6 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +9 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +26 -41
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +275 -99
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-semantic-provider.ts +4 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-symbol-mapper.ts +59 -28
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-controller.ts +27 -19
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +26 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registration.ts +0 -17
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +83 -19
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-transition-debug.ts +5 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/session/scanner.ts +24 -21
- package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +29 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/summary.ts +19 -40
- package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +13 -19
- package/node_modules/@mrclrchtr/supi-lsp/src/workspace-path-policy.ts +287 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +6 -18
- 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-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -5
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/api.ts +1 -11
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/coordinates.ts +19 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/language.ts +5 -23
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/operation-support.ts +3 -21
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +5 -9
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-controller.ts +10 -20
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/session.ts +7 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client.ts +9 -5
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-protocol.ts +134 -9
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-name.ts +96 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-sites.ts +11 -12
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +39 -56
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/exports.ts +9 -7
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/js-binding-pattern.ts +26 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/node-at.ts +2 -21
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-html-sql.ts +144 -12
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline.ts +50 -37
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/scope.ts +138 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +0 -7
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store.ts +1 -14
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime.ts +0 -10
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/service.ts +6 -8
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/worker-main.ts +34 -10
- package/package.json +5 -5
- package/src/analysis/capability/capability-warnings.ts +4 -22
- package/src/analysis/search/ast-scan-timing.ts +4 -2
- package/src/analysis/search/ast-scan.ts +1 -1
- package/src/api.ts +1 -0
- package/src/extension.ts +4 -2
- package/src/session/health-refresh.ts +24 -2
- package/src/session/health-types.ts +19 -0
- package/src/session/input/health-refactor.ts +16 -14
- package/src/session/refactor-types.ts +14 -1
- package/src/session/refactor-workflow.ts +21 -16
- package/src/substrate/lsp/lifecycle.ts +0 -3
- package/src/substrate/lsp/maintenance.ts +4 -5
- package/src/substrate/lsp/settings.ts +6 -9
- package/src/substrate/lsp/state.ts +0 -2
- package/src/substrate/workspace-provider-host.ts +2 -2
- package/src/tool/code_health/file-scope-markdown.ts +43 -0
- package/src/tool/code_health/markdown.ts +66 -66
- package/src/tool/code_health/refresh-outcome.ts +46 -0
- package/src/tool/code_health/refresh-status.ts +166 -23
- package/src/tool/code_health/result.ts +1 -0
- package/src/tool/code_health/tui.ts +18 -5
- package/src/tool/code_refactor_apply/guidance.ts +3 -2
- package/src/tool/code_refactor_plan/guidance.ts +1 -1
- package/src/tool/register.ts +5 -2
- package/src/tool/schemas.ts +21 -11
- package/node_modules/@mrclrchtr/supi-lsp/src/config/server-actions.ts +0 -59
- package/node_modules/@mrclrchtr/supi-lsp/src/pattern-matcher.ts +0 -24
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-kotlin-wasm.mjs +0 -132
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-sql-wasm.mjs +0 -158
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/vendor-wasm.mjs +0 -167
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +0 -42
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-registration.ts +0 -37
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/service-registry.ts +0 -30
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/structure.ts +0 -8
|
@@ -21,9 +21,18 @@ import type { ClientDiagnosticSnapshot } from "../client/client-document-state.t
|
|
|
21
21
|
|
|
22
22
|
export { RECOVERY_CLIENT_STARTUP_BOUND_MS } from "../client/client.ts";
|
|
23
23
|
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
recordDebugEvent,
|
|
26
|
+
truncateDebugIdentity as truncateIdentity,
|
|
27
|
+
} from "@mrclrchtr/supi-core/debug";
|
|
28
|
+
import { resolveToolPath as resolveSessionPath, uriToFile } from "@mrclrchtr/supi-core/path";
|
|
25
29
|
import { getServerForFile } from "../config/config.ts";
|
|
26
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
getFileScopeDecision,
|
|
32
|
+
invalidateTsconfigCacheForConfig,
|
|
33
|
+
invalidateTsconfigCacheForConfigDir,
|
|
34
|
+
isProjectConfigFileName,
|
|
35
|
+
} from "../config/tsconfig-scope.ts";
|
|
27
36
|
import type {
|
|
28
37
|
DetectedProjectServer,
|
|
29
38
|
Diagnostic,
|
|
@@ -35,7 +44,8 @@ import type {
|
|
|
35
44
|
SymbolInformation,
|
|
36
45
|
WorkspaceSymbol,
|
|
37
46
|
} from "../config/types.ts";
|
|
38
|
-
import {
|
|
47
|
+
import { FileChangeType } from "../config/types.ts";
|
|
48
|
+
import { boundCwd } from "../debug-telemetry.ts";
|
|
39
49
|
import {
|
|
40
50
|
accumulateOutstandingDiagnostics,
|
|
41
51
|
collectDiagnosticSummaryCounts,
|
|
@@ -47,11 +57,19 @@ import {
|
|
|
47
57
|
type DiagnosticEvidenceSummary,
|
|
48
58
|
summarizeDiagnosticEvidence,
|
|
49
59
|
} from "../diagnostics/evidence.ts";
|
|
60
|
+
import {
|
|
61
|
+
scanWorkspaceSentinels as scanAutomaticWorkspaceSentinels,
|
|
62
|
+
syncWorkspaceSentinelSnapshot as syncAutomaticWorkspaceSentinelSnapshot,
|
|
63
|
+
type WorkspaceSentinelScanOptions,
|
|
64
|
+
type WorkspaceSentinelSyncResult,
|
|
65
|
+
} from "../diagnostics/workspace-sentinels.ts";
|
|
50
66
|
import { raceRequestControl } from "../session/readiness.ts";
|
|
51
|
-
import
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
67
|
+
import {
|
|
68
|
+
emptyProcessCrashRecoverySummary,
|
|
69
|
+
type ProcessCrashRecoverySummary,
|
|
70
|
+
type ScopeDecisionEntry,
|
|
71
|
+
type ScopeDecisionSummary,
|
|
72
|
+
type WorkspaceDiagnosticReport,
|
|
55
73
|
} from "../session/runtime-diagnostics.ts";
|
|
56
74
|
import {
|
|
57
75
|
displayRelativeFilePath,
|
|
@@ -61,7 +79,11 @@ import {
|
|
|
61
79
|
normalizeRelevantPaths,
|
|
62
80
|
shouldIgnoreLspPath,
|
|
63
81
|
} from "../summary.ts";
|
|
64
|
-
import { commandExists
|
|
82
|
+
import { commandExists } from "../utils.ts";
|
|
83
|
+
import {
|
|
84
|
+
type AutomaticLspPathPolicy,
|
|
85
|
+
createDefaultAutomaticLspPathPolicy,
|
|
86
|
+
} from "../workspace-path-policy.ts";
|
|
65
87
|
import {
|
|
66
88
|
closeFileAcrossClients,
|
|
67
89
|
pruneMissingFilesFromClients,
|
|
@@ -71,12 +93,7 @@ import {
|
|
|
71
93
|
collectOutstandingDiagnosticsDetailed,
|
|
72
94
|
syncClientFileAndGetDiagnostics,
|
|
73
95
|
} from "./manager-diagnostics.ts";
|
|
74
|
-
import {
|
|
75
|
-
clientKey,
|
|
76
|
-
isExcludedByPattern,
|
|
77
|
-
rememberKnownRoot,
|
|
78
|
-
resolveRootForFile,
|
|
79
|
-
} from "./manager-helpers.ts";
|
|
96
|
+
import { clientKey, rememberKnownRoot, resolveRootForFile } from "./manager-helpers.ts";
|
|
80
97
|
import { buildProjectServerInfo } from "./manager-project-info.ts";
|
|
81
98
|
import type {
|
|
82
99
|
ActiveCoverageSummaryEntry,
|
|
@@ -161,6 +178,11 @@ type FileClientRequestOptions = {
|
|
|
161
178
|
control?: CodeRequestControl;
|
|
162
179
|
};
|
|
163
180
|
|
|
181
|
+
interface FileReadinessResult {
|
|
182
|
+
client: LspClient | null;
|
|
183
|
+
processCrashRecovery: ProcessCrashRecoverySummary;
|
|
184
|
+
}
|
|
185
|
+
|
|
164
186
|
interface ProcessCrashRecoveryState {
|
|
165
187
|
/** The one automatic attempt is consumed when a replacement starts. */
|
|
166
188
|
attemptConsumed: boolean;
|
|
@@ -181,6 +203,8 @@ type ProcessCrashRecoveryOutcome = "attempt" | "success" | "failure" | "exhauste
|
|
|
181
203
|
type ProcessCrashDemandResult = {
|
|
182
204
|
/** Whether at least one required crashed route supports the operation. */
|
|
183
205
|
hasSupport: boolean;
|
|
206
|
+
/** Separate route-level result for this explicit process-crash demand. */
|
|
207
|
+
processCrashRecovery: ProcessCrashRecoverySummary;
|
|
184
208
|
/** Required routes that remain unavailable after shared recovery settles. */
|
|
185
209
|
failures: string[];
|
|
186
210
|
/** In-scope tracked files from required routes that remain unavailable. */
|
|
@@ -193,6 +217,20 @@ type RequiredProcessCrashDemand = {
|
|
|
193
217
|
failedFiles: readonly string[];
|
|
194
218
|
};
|
|
195
219
|
|
|
220
|
+
type ProcessCrashRecoveryRouteOutcome = "pending" | "recovered" | "failed";
|
|
221
|
+
|
|
222
|
+
function summarizeProcessCrashRecoveryRoutes(
|
|
223
|
+
outcomes: Iterable<ProcessCrashRecoveryRouteOutcome>,
|
|
224
|
+
): ProcessCrashRecoverySummary {
|
|
225
|
+
const summary = emptyProcessCrashRecoverySummary();
|
|
226
|
+
for (const outcome of outcomes) {
|
|
227
|
+
summary.attemptedRoutes++;
|
|
228
|
+
if (outcome === "recovered") summary.recoveredRoutes++;
|
|
229
|
+
if (outcome === "failed") summary.failedRoutes++;
|
|
230
|
+
}
|
|
231
|
+
return summary;
|
|
232
|
+
}
|
|
233
|
+
|
|
196
234
|
// ── LspManager ────────────────────────────────────────────────────────
|
|
197
235
|
export class LspManager {
|
|
198
236
|
/** Active clients keyed by "serverName:root" */
|
|
@@ -201,14 +239,14 @@ export class LspManager {
|
|
|
201
239
|
private unavailable = new Map<string, UnavailableReason>();
|
|
202
240
|
/** Memoized per-command availability of LSP server binaries on PATH */
|
|
203
241
|
private commandAvailability = new Map<string, boolean>();
|
|
204
|
-
/** Guards against concurrent client creation for the same server:root key */
|
|
242
|
+
/** Guards against concurrent client creation for the same server:root key. */
|
|
205
243
|
private pendingStarts = new Map<string, Promise<LspClient | null>>();
|
|
244
|
+
/** Shared diagnostic-recovery restart for each replaceable client route. */
|
|
245
|
+
private pendingRestarts = new Map<string, Promise<{ files: string[]; restarted: boolean }>>();
|
|
206
246
|
/** Monotonic ownership generation for each replaceable client route. */
|
|
207
247
|
private clientGenerations = new Map<string, number>();
|
|
208
248
|
/** Preferred project roots discovered by proactive scan or lazy startup */
|
|
209
249
|
private knownRoots = new Map<string, string[]>();
|
|
210
|
-
/** User-configured gitignore-style exclude patterns */
|
|
211
|
-
private excludePatterns: string[] = [];
|
|
212
250
|
/** Monotonic workspace invalidation generation advanced by each invalidation event. */
|
|
213
251
|
private invalidationEpoch = 0;
|
|
214
252
|
/** Invalidation generation in which each route last received a recovery restart. */
|
|
@@ -231,17 +269,19 @@ export class LspManager {
|
|
|
231
269
|
private readonly config: LspConfig,
|
|
232
270
|
private readonly cwd: string,
|
|
233
271
|
private readonly onLifecycleTransition?: ManagerLifecycleListener,
|
|
272
|
+
private readonly automaticPathPolicy: AutomaticLspPathPolicy = createDefaultAutomaticLspPathPolicy(
|
|
273
|
+
cwd,
|
|
274
|
+
),
|
|
234
275
|
) {}
|
|
235
276
|
getCwd(): string {
|
|
236
277
|
return this.cwd;
|
|
237
278
|
}
|
|
238
|
-
setExcludePatterns(patterns: string[]): void {
|
|
239
|
-
this.excludePatterns = patterns;
|
|
240
|
-
}
|
|
241
279
|
|
|
242
280
|
// ── Public API ────────────────────────────────────────────────────
|
|
243
281
|
registerDetectedServers(detected: DetectedProjectServer[]): void {
|
|
244
|
-
this.knownRoots = projectRoots.buildKnownRootsMap(
|
|
282
|
+
this.knownRoots = projectRoots.buildKnownRootsMap(
|
|
283
|
+
detected.filter((entry) => this.automaticPathPolicy.isEligible(entry.root, "directory")),
|
|
284
|
+
);
|
|
245
285
|
}
|
|
246
286
|
/** Check whether a file path has an available LSP server for explicit semantic operations. */
|
|
247
287
|
canServeFile(filePath: string): boolean {
|
|
@@ -252,18 +292,56 @@ export class LspManager {
|
|
|
252
292
|
}
|
|
253
293
|
|
|
254
294
|
/**
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
* and
|
|
295
|
+
* Return the consumed process-crash outcome for a file readiness result.
|
|
296
|
+
* The result supports unavailable and non-resolved waits; it is null before
|
|
297
|
+
* an attempt is consumed and after successful recovery, so old success is
|
|
298
|
+
* not reported as a new outcome. This is a passive read and never starts
|
|
299
|
+
* recovery.
|
|
300
|
+
*/
|
|
301
|
+
getProcessCrashRecoverySummaryForFile(filePath: string): ProcessCrashRecoverySummary | null {
|
|
302
|
+
const route = this.resolveFileRoute(resolveSessionPath(this.cwd, filePath));
|
|
303
|
+
const recovery = route ? this.processCrashRecoveries.get(route.key) : undefined;
|
|
304
|
+
if (!recovery?.attemptConsumed || recovery.statusReason === undefined) return null;
|
|
305
|
+
|
|
306
|
+
const outcome: ProcessCrashRecoveryRouteOutcome =
|
|
307
|
+
recovery.statusReason === "process-crash-recovery-exhausted" ? "failed" : "pending";
|
|
308
|
+
return summarizeProcessCrashRecoveryRoutes([outcome]);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Check whether a file should participate in automatic LSP work.
|
|
313
|
+
* This is stricter than {@link canServeFile} and applies the workspace path
|
|
314
|
+
* policy plus tsconfig diagnostic suppression.
|
|
258
315
|
*/
|
|
259
316
|
isSupportedSourceFile(filePath: string): boolean {
|
|
260
|
-
|
|
261
|
-
// tracking and diagnostic summaries (shouldIgnoreLspPath). Keep runtime
|
|
262
|
-
// guidance activation consistent: reading or editing a file under
|
|
263
|
-
// node_modules / .pnpm must not arm LSP guidance for dependency sources.
|
|
264
|
-
if (shouldIgnoreLspPath(filePath, this.cwd)) return false;
|
|
317
|
+
if (!this.isAutomaticScopePath(filePath)) return false;
|
|
265
318
|
return this.canServeFile(filePath);
|
|
266
319
|
}
|
|
320
|
+
|
|
321
|
+
/** Inventory automatic workspace sentinel and source paths with the runtime policy. */
|
|
322
|
+
scanWorkspaceSentinels(options: WorkspaceSentinelScanOptions = {}): Map<string, number> {
|
|
323
|
+
return scanAutomaticWorkspaceSentinels(this.cwd, {
|
|
324
|
+
...options,
|
|
325
|
+
policy: this.automaticPathPolicy,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** Refresh the automatic workspace inventory with the runtime policy. */
|
|
330
|
+
syncWorkspaceSentinelSnapshot(
|
|
331
|
+
previous: Map<string, number>,
|
|
332
|
+
options: WorkspaceSentinelScanOptions = {},
|
|
333
|
+
): WorkspaceSentinelSyncResult {
|
|
334
|
+
return syncAutomaticWorkspaceSentinelSnapshot(this.cwd, previous, {
|
|
335
|
+
...options,
|
|
336
|
+
policy: this.automaticPathPolicy,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** Apply automatic path exclusions plus diagnostic tsconfig suppression. */
|
|
341
|
+
private isAutomaticScopePath(filePath: string): boolean {
|
|
342
|
+
return !shouldIgnoreLspPath(filePath, this.cwd, this.automaticPathPolicy);
|
|
343
|
+
}
|
|
344
|
+
|
|
267
345
|
private isServerCommandAvailable(command: string): boolean {
|
|
268
346
|
// Only memoize positive lookups. A negative result may become stale if the
|
|
269
347
|
// user installs the binary mid-session (e.g. `mise install`), and
|
|
@@ -481,7 +559,14 @@ export class LspManager {
|
|
|
481
559
|
return failedFiles === null ? [] : [{ key, recovery, failedFiles }];
|
|
482
560
|
},
|
|
483
561
|
);
|
|
484
|
-
if (required.length === 0)
|
|
562
|
+
if (required.length === 0) {
|
|
563
|
+
return {
|
|
564
|
+
hasSupport: false,
|
|
565
|
+
processCrashRecovery: emptyProcessCrashRecoverySummary(),
|
|
566
|
+
failures: [],
|
|
567
|
+
failedFiles: [],
|
|
568
|
+
};
|
|
569
|
+
}
|
|
485
570
|
|
|
486
571
|
await Promise.all(
|
|
487
572
|
required.map(async ({ key, recovery }) => {
|
|
@@ -501,8 +586,13 @@ export class LspManager {
|
|
|
501
586
|
);
|
|
502
587
|
|
|
503
588
|
const failedDemands = required.filter(({ recovery }) => recovery.statusReason !== undefined);
|
|
589
|
+
const outcomes = required.map(
|
|
590
|
+
({ recovery }): ProcessCrashRecoveryRouteOutcome =>
|
|
591
|
+
recovery.statusReason === undefined ? "recovered" : "failed",
|
|
592
|
+
);
|
|
504
593
|
return {
|
|
505
594
|
hasSupport: true,
|
|
595
|
+
processCrashRecovery: summarizeProcessCrashRecoveryRoutes(outcomes),
|
|
506
596
|
failures: failedDemands.map(({ recovery }) => this.formatProcessCrashDemandFailure(recovery)),
|
|
507
597
|
failedFiles: Array.from(new Set(failedDemands.flatMap(({ failedFiles }) => failedFiles))),
|
|
508
598
|
};
|
|
@@ -808,32 +898,60 @@ export class LspManager {
|
|
|
808
898
|
const seen = new Set<string>();
|
|
809
899
|
|
|
810
900
|
for (const filePath of filePaths) {
|
|
811
|
-
const
|
|
812
|
-
if (!
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
const key = clientKey(client.name, client.root);
|
|
816
|
-
const processRecovery = this.processCrashRecoveries.get(key);
|
|
817
|
-
if (processRecovery?.pending) continue;
|
|
818
|
-
if (seen.has(key)) continue;
|
|
819
|
-
seen.add(key);
|
|
820
|
-
if (this.recoveryRestartEpochs.get(key) === this.invalidationEpoch) continue;
|
|
901
|
+
const candidate = this.getRestartCandidate(filePath, options?.pushOnly === true, seen);
|
|
902
|
+
if (!candidate) continue;
|
|
903
|
+
const { client, key } = candidate;
|
|
821
904
|
|
|
822
905
|
// Observe cancellation between route restarts so a cancelled pass does
|
|
823
906
|
// not keep restarting further clients.
|
|
824
907
|
throwIfCodeRequestInterrupted(options?.control);
|
|
825
|
-
const
|
|
826
|
-
|
|
908
|
+
const restartPromise = this.pendingRestarts.get(key) ?? this.startSharedRestart(client, key);
|
|
909
|
+
const result = await raceRequestControl(restartPromise, options?.control);
|
|
827
910
|
restarted.push({ key, serverName: client.name, ...result });
|
|
828
911
|
}
|
|
829
912
|
|
|
830
913
|
return restarted;
|
|
831
914
|
}
|
|
832
915
|
|
|
916
|
+
private startSharedRestart(
|
|
917
|
+
client: LspClient,
|
|
918
|
+
key: string,
|
|
919
|
+
): Promise<{ files: string[]; restarted: boolean }> {
|
|
920
|
+
const restartEpoch = this.invalidationEpoch;
|
|
921
|
+
const restartPromise = this.restartClient(client).then((result) => {
|
|
922
|
+
this.recoveryRestartEpochs.set(key, restartEpoch);
|
|
923
|
+
return result;
|
|
924
|
+
});
|
|
925
|
+
this.pendingRestarts.set(key, restartPromise);
|
|
926
|
+
void restartPromise
|
|
927
|
+
.finally(() => {
|
|
928
|
+
if (this.pendingRestarts.get(key) === restartPromise) this.pendingRestarts.delete(key);
|
|
929
|
+
})
|
|
930
|
+
.catch(() => {});
|
|
931
|
+
return restartPromise;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
private getRestartCandidate(
|
|
935
|
+
filePath: string,
|
|
936
|
+
pushOnly: boolean,
|
|
937
|
+
seen: Set<string>,
|
|
938
|
+
): { client: LspClient; key: string } | null {
|
|
939
|
+
const client = this.getExistingClientForFile(filePath);
|
|
940
|
+
if (!client || (pushOnly && client.hasDiagnosticProvider)) return null;
|
|
941
|
+
|
|
942
|
+
const key = clientKey(client.name, client.root);
|
|
943
|
+
if (this.processCrashRecoveries.get(key)?.pending || seen.has(key)) return null;
|
|
944
|
+
seen.add(key);
|
|
945
|
+
if (this.recoveryRestartEpochs.get(key) === this.invalidationEpoch) return null;
|
|
946
|
+
return { client, key };
|
|
947
|
+
}
|
|
948
|
+
|
|
833
949
|
private async restartClient(client: LspClient): Promise<{ files: string[]; restarted: boolean }> {
|
|
834
950
|
const key = clientKey(client.name, client.root);
|
|
835
951
|
const serverConfig = this.config.servers[client.name];
|
|
836
|
-
if (!serverConfig
|
|
952
|
+
if (!serverConfig || this.clients.get(key) !== client) {
|
|
953
|
+
return { files: [], restarted: false };
|
|
954
|
+
}
|
|
837
955
|
|
|
838
956
|
const openFiles = client.openFiles;
|
|
839
957
|
const trackedDiagnosticFiles = client
|
|
@@ -846,6 +964,7 @@ export class LspManager {
|
|
|
846
964
|
// Ignore shutdown failures when forcing a restart.
|
|
847
965
|
}
|
|
848
966
|
|
|
967
|
+
if (this.clients.get(key) !== client) return { files, restarted: false };
|
|
849
968
|
this.clients.delete(key);
|
|
850
969
|
this.unavailable.delete(key);
|
|
851
970
|
this.clearWarmedWorkspaceSymbolProjects(client.name, client.root);
|
|
@@ -862,12 +981,18 @@ export class LspManager {
|
|
|
862
981
|
RECOVERY_CLIENT_STARTUP_BOUND_MS,
|
|
863
982
|
"replacement client startup bound exceeded",
|
|
864
983
|
);
|
|
984
|
+
if (this.clients.get(key) !== replacement || this.shuttingDown) {
|
|
985
|
+
void replacement.forceKill().catch(() => {});
|
|
986
|
+
return { files, restarted: false };
|
|
987
|
+
}
|
|
865
988
|
this.restoreTrackedDocuments(replacement, files);
|
|
866
989
|
this.clearUnconsumedProcessCrashState(key, client);
|
|
867
990
|
return { files, restarted: true };
|
|
868
991
|
} catch {
|
|
869
|
-
this.clients.
|
|
870
|
-
|
|
992
|
+
if (this.clients.get(key) === replacement) {
|
|
993
|
+
this.clients.delete(key);
|
|
994
|
+
this.unavailable.set(key, "start-failed");
|
|
995
|
+
}
|
|
871
996
|
// A bound-exceeded start may have spawned a live process; terminate
|
|
872
997
|
// the tree so the orphan cannot outlive the failed restart.
|
|
873
998
|
void replacement.forceKill().catch(() => {});
|
|
@@ -893,6 +1018,7 @@ export class LspManager {
|
|
|
893
1018
|
unavailableReason:
|
|
894
1019
|
this.getUnavailableReason(key, this.config.servers[serverName]?.command) ?? undefined,
|
|
895
1020
|
statusReason: this.processCrashRecoveries.get(key)?.statusReason,
|
|
1021
|
+
includeOpenFile: (file) => this.isAutomaticScopePath(file),
|
|
896
1022
|
},
|
|
897
1023
|
this.cwd,
|
|
898
1024
|
);
|
|
@@ -900,6 +1026,7 @@ export class LspManager {
|
|
|
900
1026
|
getKnownProjectServers(detected: DetectedProjectServer[]): ProjectServerInfo[] {
|
|
901
1027
|
const known = new Map<string, DetectedProjectServer>();
|
|
902
1028
|
for (const entry of detected) {
|
|
1029
|
+
if (!this.automaticPathPolicy.isEligible(entry.root, "directory")) continue;
|
|
903
1030
|
known.set(clientKey(entry.name, entry.root), entry);
|
|
904
1031
|
}
|
|
905
1032
|
for (const client of this.clients.values()) {
|
|
@@ -938,20 +1065,31 @@ export class LspManager {
|
|
|
938
1065
|
async waitUntilFileReady(
|
|
939
1066
|
filePath: string,
|
|
940
1067
|
control?: CodeRequestControl,
|
|
941
|
-
|
|
1068
|
+
onProcessCrashRecovery?: (summary: ProcessCrashRecoverySummary) => void,
|
|
1069
|
+
): Promise<FileReadinessResult> {
|
|
942
1070
|
throwIfCodeRequestInterrupted(control);
|
|
943
1071
|
const resolvedPath = resolveSessionPath(this.cwd, filePath);
|
|
1072
|
+
const route = this.resolveFileRoute(resolvedPath);
|
|
1073
|
+
const recovery = route ? this.processCrashRecoveries.get(route.key) : undefined;
|
|
1074
|
+
const processCrashRecoveryRequested =
|
|
1075
|
+
recovery !== undefined && (recovery.pending !== null || recovery.statusReason !== undefined);
|
|
1076
|
+
if (processCrashRecoveryRequested) {
|
|
1077
|
+
onProcessCrashRecovery?.(summarizeProcessCrashRecoveryRoutes(["pending"]));
|
|
1078
|
+
}
|
|
944
1079
|
const client = await this.getClientForFile(resolvedPath, {
|
|
945
1080
|
recoverProcessCrash: true,
|
|
946
1081
|
control,
|
|
947
1082
|
});
|
|
948
|
-
|
|
1083
|
+
const processCrashRecovery = processCrashRecoveryRequested
|
|
1084
|
+
? summarizeProcessCrashRecoveryRoutes([client ? "recovered" : "failed"])
|
|
1085
|
+
: emptyProcessCrashRecoverySummary();
|
|
1086
|
+
if (!client) return { client: null, processCrashRecovery };
|
|
949
1087
|
// The caller's wait stops on cancellation even though the shared
|
|
950
1088
|
// readiness state keeps its own lifecycle.
|
|
951
1089
|
await client.getReady(control);
|
|
952
1090
|
await this.warmSemanticProject(client, resolvedPath, true, control);
|
|
953
1091
|
throwIfCodeRequestInterrupted(control);
|
|
954
|
-
return client;
|
|
1092
|
+
return { client, processCrashRecovery };
|
|
955
1093
|
}
|
|
956
1094
|
|
|
957
1095
|
/**
|
|
@@ -989,6 +1127,7 @@ export class LspManager {
|
|
|
989
1127
|
firstReady.root,
|
|
990
1128
|
serverConfig.rootMarkers,
|
|
991
1129
|
serverConfig.fileTypes,
|
|
1130
|
+
{ policy: this.automaticPathPolicy },
|
|
992
1131
|
)[0];
|
|
993
1132
|
if (target) await this.warmSemanticProject(firstReady, target.file, false, control);
|
|
994
1133
|
}
|
|
@@ -1060,6 +1199,7 @@ export class LspManager {
|
|
|
1060
1199
|
*/
|
|
1061
1200
|
noteWorkspaceChanges(changes: FileEvent[]): void {
|
|
1062
1201
|
if (changes.length > 0) this.invalidationEpoch++;
|
|
1202
|
+
invalidateProjectConfigCaches(changes);
|
|
1063
1203
|
this.notifyWorkspaceFileChanges(changes);
|
|
1064
1204
|
}
|
|
1065
1205
|
|
|
@@ -1110,12 +1250,20 @@ export class LspManager {
|
|
|
1110
1250
|
const files = this.processCrashDiagnosticFiles(recovery, options.processCrashDemand);
|
|
1111
1251
|
return files.length > 0 ? files : null;
|
|
1112
1252
|
}, options.control)
|
|
1113
|
-
: {
|
|
1253
|
+
: {
|
|
1254
|
+
hasSupport: false,
|
|
1255
|
+
processCrashRecovery: emptyProcessCrashRecoverySummary(),
|
|
1256
|
+
failures: [],
|
|
1257
|
+
failedFiles: [],
|
|
1258
|
+
};
|
|
1114
1259
|
const recoveryOptions = demand.hasSupport
|
|
1115
1260
|
? { ...options, initialEvidence: undefined }
|
|
1116
1261
|
: options;
|
|
1117
1262
|
const result = await recoverWorkspaceDiagnosticsImpl(this, recoveryOptions);
|
|
1118
|
-
return this.addProcessCrashDiagnosticEvidence(
|
|
1263
|
+
return this.addProcessCrashDiagnosticEvidence(
|
|
1264
|
+
{ ...result, processCrashRecovery: demand.processCrashRecovery },
|
|
1265
|
+
demand,
|
|
1266
|
+
);
|
|
1119
1267
|
}
|
|
1120
1268
|
|
|
1121
1269
|
private processCrashDiagnosticFiles(
|
|
@@ -1184,6 +1332,7 @@ export class LspManager {
|
|
|
1184
1332
|
this.warmedWorkspaceSymbolProjects.clear();
|
|
1185
1333
|
this.warmedSemanticProjects.clear();
|
|
1186
1334
|
this.pendingWarmProbes.clear();
|
|
1335
|
+
this.pendingRestarts.clear();
|
|
1187
1336
|
}
|
|
1188
1337
|
/** Get status of all servers. */
|
|
1189
1338
|
getRunningClientCount(): number {
|
|
@@ -1197,13 +1346,9 @@ export class LspManager {
|
|
|
1197
1346
|
.map((client) => client.name);
|
|
1198
1347
|
}
|
|
1199
1348
|
|
|
1200
|
-
/** Check whether a path belongs to the
|
|
1349
|
+
/** Check whether a path belongs to the automatic diagnostic evidence scope. */
|
|
1201
1350
|
isDiagnosticFile(filePath: string): boolean {
|
|
1202
|
-
|
|
1203
|
-
return (
|
|
1204
|
-
!shouldIgnoreLspPath(relative, this.cwd) &&
|
|
1205
|
-
!isExcludedByPattern(relative, this.excludePatterns)
|
|
1206
|
-
);
|
|
1351
|
+
return this.isAutomaticScopePath(filePath);
|
|
1207
1352
|
}
|
|
1208
1353
|
|
|
1209
1354
|
getDiagnosticEvidence(): DiagnosticEvidenceSummary {
|
|
@@ -1218,7 +1363,7 @@ export class LspManager {
|
|
|
1218
1363
|
name: client.name,
|
|
1219
1364
|
status: client.status === "running" ? "running" : "error",
|
|
1220
1365
|
root: client.root,
|
|
1221
|
-
openFiles: client.openFiles,
|
|
1366
|
+
openFiles: client.openFiles.filter((file) => this.isAutomaticScopePath(file)),
|
|
1222
1367
|
});
|
|
1223
1368
|
}
|
|
1224
1369
|
return { servers };
|
|
@@ -1259,8 +1404,7 @@ export class LspManager {
|
|
|
1259
1404
|
const openFiles = activeServers.get(server.name) ?? new Set<string>();
|
|
1260
1405
|
for (const file of server.openFiles) {
|
|
1261
1406
|
const relativeFile = displayRelativeFilePath(file, this.cwd);
|
|
1262
|
-
if (
|
|
1263
|
-
if (isExcludedByPattern(relativeFile, this.excludePatterns)) continue;
|
|
1407
|
+
if (!this.isAutomaticScopePath(relativeFile)) continue;
|
|
1264
1408
|
openFiles.add(relativeFile);
|
|
1265
1409
|
}
|
|
1266
1410
|
activeServers.set(server.name, openFiles);
|
|
@@ -1286,7 +1430,7 @@ export class LspManager {
|
|
|
1286
1430
|
.map((entry) => ({
|
|
1287
1431
|
...entry,
|
|
1288
1432
|
openFiles: entry.openFiles.filter((file) =>
|
|
1289
|
-
isPathRelevant(file, normalizedPaths, this.cwd),
|
|
1433
|
+
isPathRelevant(file, normalizedPaths, this.cwd, this.automaticPathPolicy),
|
|
1290
1434
|
),
|
|
1291
1435
|
}))
|
|
1292
1436
|
.filter((entry) => entry.openFiles.length > 0);
|
|
@@ -1318,7 +1462,9 @@ export class LspManager {
|
|
|
1318
1462
|
const fileDiags = new Map<string, { errors: number; warnings: number }>();
|
|
1319
1463
|
for (const snapshot of snapshots) {
|
|
1320
1464
|
for (const entry of snapshot.entries) {
|
|
1321
|
-
collectDiagnosticSummaryCounts(fileDiags, entry, this.cwd,
|
|
1465
|
+
collectDiagnosticSummaryCounts(fileDiags, entry, this.cwd, (file) =>
|
|
1466
|
+
this.isAutomaticScopePath(file),
|
|
1467
|
+
);
|
|
1322
1468
|
}
|
|
1323
1469
|
}
|
|
1324
1470
|
|
|
@@ -1332,7 +1478,7 @@ export class LspManager {
|
|
|
1332
1478
|
entries: collectOutstandingDiagnosticsDetailed(
|
|
1333
1479
|
snapshots.map((snapshot) => snapshot.entries),
|
|
1334
1480
|
this.cwd,
|
|
1335
|
-
this.
|
|
1481
|
+
(file) => this.isAutomaticScopePath(file),
|
|
1336
1482
|
maxSeverity,
|
|
1337
1483
|
),
|
|
1338
1484
|
current,
|
|
@@ -1364,7 +1510,9 @@ export class LspManager {
|
|
|
1364
1510
|
});
|
|
1365
1511
|
}
|
|
1366
1512
|
for (const entry of snapshot.entries) {
|
|
1367
|
-
collectDiagnosticSummaryCounts(fileDiags, entry, this.cwd,
|
|
1513
|
+
collectDiagnosticSummaryCounts(fileDiags, entry, this.cwd, (file) =>
|
|
1514
|
+
this.isAutomaticScopePath(file),
|
|
1515
|
+
);
|
|
1368
1516
|
}
|
|
1369
1517
|
}
|
|
1370
1518
|
const evidence = this.toWorkspaceDiagnosticEvidence(evidenceDocuments);
|
|
@@ -1396,7 +1544,7 @@ export class LspManager {
|
|
|
1396
1544
|
entries: collectOutstandingDiagnosticSummaryEntries(
|
|
1397
1545
|
snapshots,
|
|
1398
1546
|
this.cwd,
|
|
1399
|
-
this.
|
|
1547
|
+
(file) => this.isAutomaticScopePath(file),
|
|
1400
1548
|
maxSeverity,
|
|
1401
1549
|
),
|
|
1402
1550
|
current:
|
|
@@ -1413,7 +1561,7 @@ export class LspManager {
|
|
|
1413
1561
|
if (normalizedPaths.length === 0) return null;
|
|
1414
1562
|
return formatOutstandingDiagnosticsSummaryText(
|
|
1415
1563
|
this.getOutstandingDiagnosticSummary(maxSeverity).filter((entry) =>
|
|
1416
|
-
isPathRelevant(entry.file, normalizedPaths, this.cwd),
|
|
1564
|
+
isPathRelevant(entry.file, normalizedPaths, this.cwd, this.automaticPathPolicy),
|
|
1417
1565
|
),
|
|
1418
1566
|
maxFiles,
|
|
1419
1567
|
);
|
|
@@ -1443,7 +1591,7 @@ export class LspManager {
|
|
|
1443
1591
|
entries: collectOutstandingDiagnosticsDetailed(
|
|
1444
1592
|
snapshots.map((snapshot) => snapshot.entries),
|
|
1445
1593
|
this.cwd,
|
|
1446
|
-
this.
|
|
1594
|
+
(file) => this.isAutomaticScopePath(file),
|
|
1447
1595
|
maxSeverity,
|
|
1448
1596
|
),
|
|
1449
1597
|
current:
|
|
@@ -1472,6 +1620,7 @@ export class LspManager {
|
|
|
1472
1620
|
for (const client of this.clients.values()) {
|
|
1473
1621
|
for (const document of client.getDiagnosticSnapshot().documents) {
|
|
1474
1622
|
const file = relativeFilePathFromUri(document.uri, this.cwd);
|
|
1623
|
+
if (!this.isAutomaticScopePath(file)) continue;
|
|
1475
1624
|
totalFiles++;
|
|
1476
1625
|
accumulateScopeDecision(accumulator, file, getFileScopeDecision(file, this.cwd));
|
|
1477
1626
|
}
|
|
@@ -1491,8 +1640,7 @@ export class LspManager {
|
|
|
1491
1640
|
const byFile = new Map<string, DiagnosticEvidenceDocument>();
|
|
1492
1641
|
for (const document of documents) {
|
|
1493
1642
|
const file = path.relative(this.cwd, path.resolve(this.cwd, document.file));
|
|
1494
|
-
if (
|
|
1495
|
-
if (isExcludedByPattern(file, this.excludePatterns)) continue;
|
|
1643
|
+
if (!this.isAutomaticScopePath(file)) continue;
|
|
1496
1644
|
const existing = byFile.get(file);
|
|
1497
1645
|
if (!existing || evidenceStatusRank(document.status) > evidenceStatusRank(existing.status)) {
|
|
1498
1646
|
byFile.set(file, { file, status: document.status });
|
|
@@ -1539,7 +1687,10 @@ export class LspManager {
|
|
|
1539
1687
|
}
|
|
1540
1688
|
|
|
1541
1689
|
const warmed = await this.warmWorkspaceSymbolProjectsUntilResult({
|
|
1542
|
-
findWarmTargets:
|
|
1690
|
+
findWarmTargets: (root, rootMarkers, fileTypes) =>
|
|
1691
|
+
findWorkspaceSymbolWarmTargets(root, rootMarkers, fileTypes, {
|
|
1692
|
+
policy: this.automaticPathPolicy,
|
|
1693
|
+
}),
|
|
1543
1694
|
getWarmPosition: getWorkspaceSymbolWarmPosition,
|
|
1544
1695
|
collect,
|
|
1545
1696
|
routeMatches,
|
|
@@ -1637,28 +1788,21 @@ export class LspManager {
|
|
|
1637
1788
|
for (const target of warmTargets) {
|
|
1638
1789
|
const projectKey = this.workspaceSymbolProjectKey(client.name, target.projectRoot);
|
|
1639
1790
|
if (this.warmedWorkspaceSymbolProjects.has(projectKey)) continue;
|
|
1640
|
-
if (this.hasOpenFileInProject(client, target.projectRoot)) {
|
|
1641
|
-
this.warmedWorkspaceSymbolProjects.add(projectKey);
|
|
1642
|
-
continue;
|
|
1643
|
-
}
|
|
1644
1791
|
|
|
1645
1792
|
const openedClient = await this.ensureFileOpen(target.file);
|
|
1646
1793
|
if (!openedClient) continue;
|
|
1647
1794
|
|
|
1648
1795
|
// A cancelled pass must not mark projects warmed on its way out.
|
|
1649
1796
|
throwIfCodeRequestInterrupted(control);
|
|
1650
|
-
this.
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
if (
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
if (control) await openedClient.hover(target.file, hoverPosition, control);
|
|
1660
|
-
else await openedClient.hover(target.file, hoverPosition);
|
|
1661
|
-
}
|
|
1797
|
+
const available = await this.probeDocumentSymbols(
|
|
1798
|
+
openedClient,
|
|
1799
|
+
target.file,
|
|
1800
|
+
getWarmPosition,
|
|
1801
|
+
control,
|
|
1802
|
+
);
|
|
1803
|
+
if (available) {
|
|
1804
|
+
this.warmedWorkspaceSymbolProjects.add(projectKey);
|
|
1805
|
+
warmedAny = true;
|
|
1662
1806
|
}
|
|
1663
1807
|
|
|
1664
1808
|
const collected = await collect(this.clients.values(), query);
|
|
@@ -1703,35 +1847,56 @@ export class LspManager {
|
|
|
1703
1847
|
try {
|
|
1704
1848
|
await raceRequestControl(probe, control);
|
|
1705
1849
|
} finally {
|
|
1706
|
-
this.pendingWarmProbes.
|
|
1850
|
+
if (this.pendingWarmProbes.get(projectKey) === probe) {
|
|
1851
|
+
this.pendingWarmProbes.delete(projectKey);
|
|
1852
|
+
}
|
|
1707
1853
|
}
|
|
1708
1854
|
}
|
|
1709
1855
|
|
|
1710
1856
|
private async performWarmProbe(
|
|
1711
|
-
|
|
1857
|
+
client: LspClient,
|
|
1712
1858
|
resolvedFile: string,
|
|
1713
1859
|
projectKey: string,
|
|
1714
1860
|
control?: CodeRequestControl,
|
|
1715
1861
|
): Promise<void> {
|
|
1862
|
+
const key = clientKey(client.name, client.root);
|
|
1863
|
+
if (this.clients.get(key) !== client) return;
|
|
1716
1864
|
const openedClient = await this.ensureFileOpen(resolvedFile);
|
|
1717
|
-
if (!openedClient) return;
|
|
1718
|
-
|
|
1719
|
-
this.warmedSemanticProjects.add(projectKey);
|
|
1865
|
+
if (!openedClient || openedClient !== client || this.clients.get(key) !== client) return;
|
|
1720
1866
|
|
|
1721
1867
|
// The probe is shared state for concurrent callers. One caller's
|
|
1722
1868
|
// cancellation must not cancel the shared in-flight queries, so only the
|
|
1723
1869
|
// Debug Operation ID is forwarded; each caller races its own wait above.
|
|
1724
1870
|
const sharedControl = { operationId: control?.operationId };
|
|
1725
|
-
const
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1871
|
+
const available = await this.probeDocumentSymbols(
|
|
1872
|
+
openedClient,
|
|
1873
|
+
resolvedFile,
|
|
1874
|
+
getWorkspaceSymbolWarmPosition,
|
|
1875
|
+
sharedControl,
|
|
1876
|
+
);
|
|
1877
|
+
if (available && this.clients.get(key) === client) {
|
|
1878
|
+
this.warmedSemanticProjects.add(projectKey);
|
|
1730
1879
|
}
|
|
1731
1880
|
}
|
|
1732
1881
|
|
|
1733
|
-
private
|
|
1734
|
-
|
|
1882
|
+
private async probeDocumentSymbols(
|
|
1883
|
+
client: LspClient,
|
|
1884
|
+
file: string,
|
|
1885
|
+
getWarmPosition: (
|
|
1886
|
+
symbols: import("../config/types.ts").DocumentSymbol[] | SymbolInformation[] | null,
|
|
1887
|
+
) => import("../config/types.ts").Position | null,
|
|
1888
|
+
control?: CodeRequestControl,
|
|
1889
|
+
): Promise<boolean> {
|
|
1890
|
+
const symbols = control
|
|
1891
|
+
? await client.documentSymbols(file, control)
|
|
1892
|
+
: await client.documentSymbols(file);
|
|
1893
|
+
if (symbols.kind === "unavailable") return false;
|
|
1894
|
+
const hoverPosition = getWarmPosition(symbols.data);
|
|
1895
|
+
if (hoverPosition) {
|
|
1896
|
+
if (control) await client.hover(file, hoverPosition, control);
|
|
1897
|
+
else await client.hover(file, hoverPosition);
|
|
1898
|
+
}
|
|
1899
|
+
return true;
|
|
1735
1900
|
}
|
|
1736
1901
|
|
|
1737
1902
|
private workspaceSymbolProjectKey(serverName: string, projectRoot: string): string {
|
|
@@ -1766,6 +1931,17 @@ export class LspManager {
|
|
|
1766
1931
|
}
|
|
1767
1932
|
}
|
|
1768
1933
|
|
|
1934
|
+
function invalidateProjectConfigCaches(changes: readonly FileEvent[]): void {
|
|
1935
|
+
for (const change of changes) {
|
|
1936
|
+
const configPath = uriToFile(change.uri);
|
|
1937
|
+
if (!isProjectConfigFileName(path.basename(configPath))) continue;
|
|
1938
|
+
invalidateTsconfigCacheForConfig(configPath);
|
|
1939
|
+
if (change.type === FileChangeType.Created) {
|
|
1940
|
+
invalidateTsconfigCacheForConfigDir(path.dirname(configPath));
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1769
1945
|
function collectClientDiagnosticEvidenceDocuments(
|
|
1770
1946
|
snapshots: Iterable<ClientDiagnosticSnapshot>,
|
|
1771
1947
|
cwd: string,
|
|
@@ -1781,14 +1957,14 @@ function collectClientDiagnosticEvidenceDocuments(
|
|
|
1781
1957
|
function collectOutstandingDiagnosticSummaryEntries(
|
|
1782
1958
|
snapshots: Iterable<ClientDiagnosticSnapshot>,
|
|
1783
1959
|
cwd: string,
|
|
1784
|
-
|
|
1960
|
+
includeFile: (file: string) => boolean,
|
|
1785
1961
|
maxSeverity: number,
|
|
1786
1962
|
): OutstandingDiagnosticSummaryEntry[] {
|
|
1787
1963
|
const fileDiags = new Map<string, OutstandingDiagnosticSummaryEntry>();
|
|
1788
1964
|
for (const snapshot of snapshots) {
|
|
1789
1965
|
for (const entry of snapshot.entries) {
|
|
1790
1966
|
const file = relativeFilePathFromUri(entry.uri, cwd);
|
|
1791
|
-
if (
|
|
1967
|
+
if (!includeFile(file)) continue;
|
|
1792
1968
|
const existing = fileDiags.get(file) ?? createOutstandingDiagnosticSummary(file);
|
|
1793
1969
|
const next = accumulateOutstandingDiagnostics(existing, entry.diagnostics, maxSeverity);
|
|
1794
1970
|
if (next.total > 0) fileDiags.set(file, next);
|