@mrclrchtr/supi-lsp 9.1.1 → 9.1.2

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-runtime",
3
- "version": "9.1.1",
3
+ "version": "9.1.2",
4
4
  "description": "Shared workspace context and capability contracts for code intelligence",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "9.1.1",
3
+ "version": "9.1.2",
4
4
  "description": "Shared settings, configuration, reporting, and session infrastructure",
5
5
  "license": "MIT",
6
6
  "repository": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-lsp",
3
- "version": "9.1.1",
3
+ "version": "9.1.2",
4
4
  "description": "Language Server Protocol runtime for SuPi code intelligence",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -37,8 +37,8 @@
37
37
  "vscode-jsonrpc": "^9.0.0",
38
38
  "vscode-languageserver-protocol": "^3.18.0",
39
39
  "vscode-languageserver-types": "^3.18.0",
40
- "@mrclrchtr/supi-code-runtime": "9.1.1",
41
- "@mrclrchtr/supi-core": "9.1.1"
40
+ "@mrclrchtr/supi-core": "9.1.2",
41
+ "@mrclrchtr/supi-code-runtime": "9.1.2"
42
42
  },
43
43
  "bundledDependencies": [
44
44
  "@mrclrchtr/supi-code-runtime",
@@ -1,3 +1,4 @@
1
+ import { statSync } from "node:fs";
1
2
  import * as path from "node:path";
2
3
  import {
3
4
  type CodeQueryResult,
@@ -6,6 +7,7 @@ import {
6
7
  partialCodeQuery,
7
8
  unavailableCodeQuery,
8
9
  } from "@mrclrchtr/supi-code-runtime/api";
10
+ import { isWithinOrEqual } from "@mrclrchtr/supi-core/project";
9
11
  import type { LspClient } from "../client/client.ts";
10
12
  import type {
11
13
  DocumentSymbol,
@@ -34,6 +36,8 @@ export interface WorkspaceSymbolWarmOptions {
34
36
  maxMarkerDepth?: number;
35
37
  /** Runtime-owned automatic path policy. */
36
38
  policy?: AutomaticLspPathPolicy;
39
+ /** Absolute request scopes. Search only their intersection with the route root. */
40
+ scopes?: readonly string[];
37
41
  }
38
42
 
39
43
  export function getWorkspaceSymbolWarmPosition(
@@ -144,6 +148,31 @@ export function findWorkspaceSymbolWarmTargets(
144
148
  const policy = options.policy ?? createDefaultAutomaticLspPathPolicy(resolvedRoot);
145
149
  if (allowed.size === 0) return [];
146
150
 
151
+ if (options.scopes?.length) {
152
+ const targets = options.scopes.flatMap((scope) => {
153
+ const searchRoot = isWithinOrEqual(resolvedRoot, scope)
154
+ ? scope
155
+ : isWithinOrEqual(scope, resolvedRoot)
156
+ ? resolvedRoot
157
+ : null;
158
+ return searchRoot
159
+ ? findWorkspaceSymbolWarmTargets(searchRoot, rootMarkers, fileTypes, {
160
+ ...options,
161
+ policy,
162
+ scopes: undefined,
163
+ })
164
+ : [];
165
+ });
166
+ return [...new Map(targets.map((target) => [target.file, target])).values()];
167
+ }
168
+
169
+ if (statSync(resolvedRoot, { throwIfNoEntry: false })?.isFile()) {
170
+ return allowed.has(path.extname(resolvedRoot).slice(1).toLowerCase()) &&
171
+ policy.isEligible(resolvedRoot, "file")
172
+ ? [{ projectRoot: path.dirname(resolvedRoot), file: resolvedRoot }]
173
+ : [];
174
+ }
175
+
147
176
  const markerRoots = collectMarkerRoots(resolvedRoot, rootMarkers, maxMarkerDepth, policy);
148
177
  const targets = markerRoots
149
178
  .map((entry) => {
@@ -334,8 +334,8 @@ export class LspManager {
334
334
  private invalidationEpoch = 0;
335
335
  /** Invalidation generation in which each route last received a recovery restart. */
336
336
  private recoveryRestartEpochs = new Map<string, number>();
337
- /** Project roots already warmed for workspace-symbol queries. */
338
- private warmedWorkspaceSymbolProjects = new Set<string>();
337
+ /** Files already probed for workspace-symbol warm-up. */
338
+ private warmedWorkspaceSymbolFiles = new Set<string>();
339
339
  /** Project roots whose semantic state was warmed with a readiness probe. */
340
340
  private warmedSemanticProjects = new Set<string>();
341
341
  /** In-flight warm-up probes keyed by project key so concurrent callers share one probe. */
@@ -527,7 +527,7 @@ export class LspManager {
527
527
  root,
528
528
  failureMessage: "The LSP client is not running.",
529
529
  });
530
- this.clearWarmedWorkspaceSymbolProjects(existing.name, existing.root);
530
+ this.clearWarmedWorkspaceSymbolFiles(existing.name, existing.root);
531
531
  this.clearWarmedSemanticProjects(existing.name, existing.root);
532
532
  this.clearPendingWarmProbes(existing.name, existing.root);
533
533
  if (!retryFailedStart) return null;
@@ -1246,7 +1246,7 @@ export class LspManager {
1246
1246
  ): LspClient {
1247
1247
  const key = route.key;
1248
1248
  if (this.clients.get(key) === recovery.failedClient) this.clients.delete(key);
1249
- this.clearWarmedWorkspaceSymbolProjects(route.serverName, route.root);
1249
+ this.clearWarmedWorkspaceSymbolFiles(route.serverName, route.root);
1250
1250
  this.clearWarmedSemanticProjects(route.serverName, route.root);
1251
1251
  this.clearPendingWarmProbes(route.serverName, route.root);
1252
1252
 
@@ -1402,7 +1402,7 @@ export class LspManager {
1402
1402
 
1403
1403
  // Spawn new client
1404
1404
  const client = this.createClient(serverName, serverConfig, root, key);
1405
- this.clearWarmedWorkspaceSymbolProjects(serverName, root);
1405
+ this.clearWarmedWorkspaceSymbolFiles(serverName, root);
1406
1406
  this.clearWarmedSemanticProjects(serverName, root);
1407
1407
  this.clearPendingWarmProbes(serverName, root);
1408
1408
  this.clients.set(key, client);
@@ -1532,7 +1532,7 @@ export class LspManager {
1532
1532
  if (this.clients.get(key) !== client) return { files, restarted: false };
1533
1533
  this.clients.delete(key);
1534
1534
  this.unavailable.delete(key);
1535
- this.clearWarmedWorkspaceSymbolProjects(client.name, client.root);
1535
+ this.clearWarmedWorkspaceSymbolFiles(client.name, client.root);
1536
1536
  this.clearWarmedSemanticProjects(client.name, client.root);
1537
1537
  this.clearPendingWarmProbes(client.name, client.root);
1538
1538
 
@@ -2307,7 +2307,7 @@ export class LspManager {
2307
2307
  this.recoveryRestartEpochs.clear();
2308
2308
  this.unavailable.clear();
2309
2309
  this.knownRoots.clear();
2310
- this.warmedWorkspaceSymbolProjects.clear();
2310
+ this.warmedWorkspaceSymbolFiles.clear();
2311
2311
  this.warmedSemanticProjects.clear();
2312
2312
  this.pendingWarmProbes.clear();
2313
2313
  this.pendingRestarts.clear();
@@ -2671,6 +2671,7 @@ export class LspManager {
2671
2671
  findWarmTargets: (root, rootMarkers, fileTypes) =>
2672
2672
  findWorkspaceSymbolWarmTargets(root, rootMarkers, fileTypes, {
2673
2673
  policy: this.automaticPathPolicy,
2674
+ scopes: resolvedScopes,
2674
2675
  }),
2675
2676
  getWarmPosition: getWorkspaceSymbolWarmPosition,
2676
2677
  collect,
@@ -2696,22 +2697,21 @@ export class LspManager {
2696
2697
  collection: WorkspaceSymbolCollection,
2697
2698
  scopes: readonly string[] | undefined,
2698
2699
  ): WorkspaceSymbolCollection {
2699
- const excludedExamples: string[] = [];
2700
+ const excludedExamples = new Set(collection.excludedExamples ?? []);
2700
2701
  let excludedCount = 0;
2701
2702
  const results = collection.results.filter((result) => {
2702
2703
  const file = uriToFile(result.location.uri);
2703
2704
  if (this.isWorkspaceSymbolResultAllowed(file, scopes)) return true;
2704
2705
  excludedCount++;
2705
- if (excludedExamples.length < 5) {
2706
- excludedExamples.push(displayRelativeFilePath(file, this.cwd));
2707
- }
2706
+ const example = displayRelativeFilePath(file, this.cwd);
2707
+ if (excludedExamples.size < 5) excludedExamples.add(example);
2708
2708
  return false;
2709
2709
  });
2710
2710
  return {
2711
2711
  ...collection,
2712
2712
  results,
2713
2713
  excludedCount: (collection.excludedCount ?? 0) + excludedCount,
2714
- excludedExamples: [...(collection.excludedExamples ?? []), ...excludedExamples].slice(0, 5),
2714
+ excludedExamples: [...excludedExamples].slice(0, 5),
2715
2715
  };
2716
2716
  }
2717
2717
 
@@ -2803,6 +2803,7 @@ export class LspManager {
2803
2803
  }): Promise<{ warmedAny: boolean; collection: WorkspaceSymbolCollection | null }> {
2804
2804
  const { findWarmTargets, getWarmPosition, collect, routeMatches, query, control } = options;
2805
2805
  let warmedAny = false;
2806
+ let latestCollection: WorkspaceSymbolCollection | null = null;
2806
2807
 
2807
2808
  for (const client of Array.from(this.clients.values())) {
2808
2809
  if (routeMatches && !routeMatches(client)) continue;
@@ -2819,8 +2820,9 @@ export class LspManager {
2819
2820
  ).slice(0, 24);
2820
2821
 
2821
2822
  for (const target of warmTargets) {
2822
- const projectKey = this.workspaceSymbolProjectKey(client.name, target.projectRoot);
2823
- if (this.warmedWorkspaceSymbolProjects.has(projectKey)) continue;
2823
+ // A file probe does not load every program under the route root.
2824
+ const fileKey = `${client.name}:${path.resolve(target.file)}`;
2825
+ if (this.warmedWorkspaceSymbolFiles.has(fileKey)) continue;
2824
2826
 
2825
2827
  const openedClient = await this.ensureFileOpen(target.file);
2826
2828
  if (!openedClient) continue;
@@ -2834,18 +2836,19 @@ export class LspManager {
2834
2836
  control,
2835
2837
  );
2836
2838
  if (available) {
2837
- this.warmedWorkspaceSymbolProjects.add(projectKey);
2839
+ this.warmedWorkspaceSymbolFiles.add(fileKey);
2838
2840
  warmedAny = true;
2839
2841
  }
2840
2842
 
2841
- const collected = await collect(this.clients.values(), query);
2842
- if (collected.hasSupport && collected.results.length > 0) {
2843
- return { warmedAny, collection: collected };
2843
+ latestCollection = await collect(this.clients.values(), query);
2844
+ if (latestCollection.hasSupport && latestCollection.results.length > 0) {
2845
+ return { warmedAny, collection: latestCollection };
2844
2846
  }
2845
2847
  }
2846
2848
  }
2847
2849
 
2848
- return { warmedAny, collection: null };
2850
+ // A completed-empty retry replaces an earlier failure too.
2851
+ return { warmedAny, collection: latestCollection };
2849
2852
  }
2850
2853
 
2851
2854
  private async warmSemanticProject(
@@ -2936,11 +2939,11 @@ export class LspManager {
2936
2939
  return `${serverName}:${path.resolve(projectRoot)}`;
2937
2940
  }
2938
2941
 
2939
- private clearWarmedWorkspaceSymbolProjects(serverName: string, root: string): void {
2942
+ private clearWarmedWorkspaceSymbolFiles(serverName: string, root: string): void {
2940
2943
  const prefix = `${serverName}:${path.resolve(root)}`;
2941
- for (const key of Array.from(this.warmedWorkspaceSymbolProjects)) {
2944
+ for (const key of Array.from(this.warmedWorkspaceSymbolFiles)) {
2942
2945
  if (key === prefix || key.startsWith(`${prefix}${path.sep}`)) {
2943
- this.warmedWorkspaceSymbolProjects.delete(key);
2946
+ this.warmedWorkspaceSymbolFiles.delete(key);
2944
2947
  }
2945
2948
  }
2946
2949
  }