@mrclrchtr/supi-lsp 3.2.0 → 4.1.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.
Files changed (58) hide show
  1. package/README.md +72 -62
  2. package/node_modules/@mrclrchtr/supi-code-runtime/README.md +8 -0
  3. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +2 -4
  4. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/api.ts +0 -4
  5. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/evidence-badge.ts +3 -2
  6. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/index.ts +0 -4
  7. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/llm.ts +0 -10
  8. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +19 -15
  9. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
  10. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
  11. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
  12. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  13. package/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +10 -1
  14. package/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +19 -29
  15. package/node_modules/@mrclrchtr/supi-code-runtime/src/index.ts +7 -1
  16. package/node_modules/@mrclrchtr/supi-code-runtime/src/query-result.ts +42 -0
  17. package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +15 -10
  18. package/node_modules/@mrclrchtr/supi-core/package.json +2 -4
  19. package/node_modules/@mrclrchtr/supi-core/src/api.ts +0 -4
  20. package/node_modules/@mrclrchtr/supi-core/src/evidence-badge.ts +3 -2
  21. package/node_modules/@mrclrchtr/supi-core/src/index.ts +0 -4
  22. package/node_modules/@mrclrchtr/supi-core/src/llm.ts +0 -10
  23. package/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +19 -15
  24. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
  25. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
  26. package/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
  27. package/package.json +4 -5
  28. package/src/api.ts +9 -9
  29. package/src/client/client.ts +70 -41
  30. package/src/client/transport.ts +16 -13
  31. package/src/config/config.ts +2 -2
  32. package/src/config/lsp-settings.ts +0 -2
  33. package/src/index.ts +6 -6
  34. package/src/manager/manager-diagnostics.ts +5 -95
  35. package/src/manager/manager-workspace-recovery.ts +4 -3
  36. package/src/manager/manager-workspace-symbol.ts +44 -6
  37. package/src/manager/manager.ts +47 -65
  38. package/src/provider/lsp-semantic-provider.ts +132 -79
  39. package/src/provider/refactor-planning.ts +6 -14
  40. package/src/session/readiness.ts +38 -0
  41. package/src/session/runtime-controller.ts +87 -75
  42. package/src/session/runtime-registration.ts +5 -5
  43. package/src/session/{service-registry.ts → runtime-registry.ts} +163 -71
  44. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/abort-utils.ts +0 -31
  45. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/substrate-types.ts +0 -11
  46. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/types.ts +0 -2
  47. package/node_modules/@mrclrchtr/supi-core/src/abort-utils.ts +0 -31
  48. package/node_modules/@mrclrchtr/supi-core/src/substrate-types.ts +0 -11
  49. package/node_modules/@mrclrchtr/supi-core/src/types.ts +0 -2
  50. package/src/diagnostics/diagnostic-augmentation.ts +0 -82
  51. package/src/diagnostics/diagnostic-context.ts +0 -116
  52. package/src/diagnostics/diagnostic-display.ts +0 -69
  53. package/src/manager/capability-index.ts +0 -24
  54. package/src/manager/client-pool.ts +0 -33
  55. package/src/manager/diagnostic-store.ts +0 -51
  56. package/src/manager/manager-stale-resync.ts +0 -47
  57. package/src/manager/recovery-coordinator.ts +0 -37
  58. package/src/manager/workspace-router.ts +0 -44
@@ -3,108 +3,18 @@ import type { LspClient } from "../client/client.ts";
3
3
  import type { Diagnostic } from "../config/types.ts";
4
4
  import { relativeFilePathFromUri } from "../diagnostics/diagnostic-summary.ts";
5
5
  import { shouldIgnoreLspPath } from "../summary.ts";
6
- import { fileToUri, uriToFile } from "../utils.ts";
7
6
  import { isExcludedByPattern } from "./manager-helpers.ts";
8
7
 
9
- export interface DiagnosticSnapshotEntry {
10
- receivedAt: number;
11
- diagnostics: Diagnostic[];
12
- }
13
-
14
- export interface CascadeDiagnosticEntry {
15
- uri: string;
16
- diagnostics: Diagnostic[];
17
- }
18
-
19
- export interface CascadingDiagnosticsResult {
20
- primary: Diagnostic[];
21
- cascade: CascadeDiagnosticEntry[];
22
- }
23
-
24
- /**
25
- * Find diagnostics for open files updated during a sync of another file.
26
- *
27
- * Files are considered cascade-affected when their diagnostic cache entry is
28
- * new or has a newer `receivedAt` timestamp than the pre-sync snapshot.
29
- */
30
- export function findCascadeDiagnosticEntries(
31
- preSnapshot: Map<string, DiagnosticSnapshotEntry>,
32
- postEntries: Map<string, DiagnosticSnapshotEntry>,
33
- editedUri: string,
34
- maxSeverity: number,
35
- ): CascadeDiagnosticEntry[] {
36
- const result: CascadeDiagnosticEntry[] = [];
37
-
38
- for (const [uri, entry] of postEntries) {
39
- if (uri === editedUri) continue;
40
-
41
- const previous = preSnapshot.get(uri);
42
- if (previous && entry.receivedAt <= previous.receivedAt) continue;
43
-
44
- const diagnostics = filterDiagnosticsBySeverity(entry.diagnostics, maxSeverity);
45
- if (diagnostics.length === 0) continue;
46
-
47
- result.push({ uri, diagnostics });
48
- }
49
-
50
- return result.sort((a, b) => a.uri.localeCompare(b.uri));
51
- }
52
-
53
- /** Snapshot current diagnostic cache entries for the client's open URIs. */
54
- export function snapshotOpenClientDiagnostics(
55
- client: Pick<LspClient, "openUris" | "getDiagnosticCacheEntry">,
56
- ): Map<string, DiagnosticSnapshotEntry> {
57
- const snapshot = new Map<string, DiagnosticSnapshotEntry>();
58
-
59
- for (const uri of client.openUris) {
60
- const entry = client.getDiagnosticCacheEntry(uri);
61
- if (!entry) continue;
62
- snapshot.set(uri, {
63
- receivedAt: entry.receivedAt,
64
- diagnostics: entry.diagnostics,
65
- });
66
- }
67
-
68
- return snapshot;
69
- }
70
-
71
- /** Build post-sync entries map for the client's open URIs. */
72
- export function collectOpenClientDiagnosticEntries(
73
- client: Pick<LspClient, "openUris" | "getDiagnosticCacheEntry">,
74
- ): Map<string, DiagnosticSnapshotEntry> {
75
- return snapshotOpenClientDiagnostics(client);
76
- }
77
-
78
- /** Sync a file and return its diagnostics plus cascade-affected open files. */
79
- export async function syncClientFileAndGetCascadingDiagnostics(
80
- client: Pick<
81
- LspClient,
82
- "openUris" | "getDiagnosticCacheEntry" | "syncAndWaitForDiagnostics" | "clearPullResultIds"
83
- >,
8
+ /** Sync one file and return diagnostics up to the supplied severity threshold. */
9
+ export async function syncClientFileAndGetDiagnostics(
10
+ client: Pick<LspClient, "syncAndWaitForDiagnostics">,
84
11
  filePath: string,
85
12
  maxSeverity: number,
86
- ): Promise<CascadingDiagnosticsResult> {
87
- const preSnapshot = snapshotOpenClientDiagnostics(client);
88
- const primary = filterDiagnosticsBySeverity(
13
+ ): Promise<Diagnostic[]> {
14
+ return filterDiagnosticsBySeverity(
89
15
  await client.syncAndWaitForDiagnostics(filePath, readFileSync(filePath, "utf-8")),
90
16
  maxSeverity,
91
17
  );
92
- client.clearPullResultIds();
93
-
94
- const cascade = findCascadeDiagnosticEntries(
95
- preSnapshot,
96
- collectOpenClientDiagnosticEntries(client),
97
- fileToUri(filePath),
98
- maxSeverity,
99
- );
100
-
101
- return { primary, cascade };
102
- }
103
-
104
- export function mapCascadeDiagnosticsToFiles(
105
- entries: CascadeDiagnosticEntry[],
106
- ): Array<{ file: string; diagnostics: Diagnostic[] }> {
107
- return entries.map((entry) => ({ file: uriToFile(entry.uri), diagnostics: entry.diagnostics }));
108
18
  }
109
19
 
110
20
  export function collectOutstandingDiagnosticsDetailed(
@@ -5,7 +5,8 @@ import {
5
5
  } from "../diagnostics/stale-diagnostics.ts";
6
6
 
7
7
  export interface WorkspaceRecoveryResult {
8
- refreshedClients: number;
8
+ /** Active clients targeted by the best-effort refresh, not confirmed successful refreshes. */
9
+ attemptedClients: number;
9
10
  restartedClients: number;
10
11
  staleAssessment: StaleDiagnosticAssessment;
11
12
  }
@@ -41,7 +42,7 @@ export async function recoverWorkspaceDiagnostics(
41
42
  quietMs?: number;
42
43
  } = {},
43
44
  ): Promise<WorkspaceRecoveryResult> {
44
- const refreshedClients = softRecoverWorkspaceDiagnostics(host, options.changes ?? []);
45
+ const attemptedClients = softRecoverWorkspaceDiagnostics(host, options.changes ?? []);
45
46
 
46
47
  try {
47
48
  await host.refreshOpenDiagnostics({ maxWaitMs: options.maxWaitMs, quietMs: options.quietMs });
@@ -72,7 +73,7 @@ export async function recoverWorkspaceDiagnostics(
72
73
  }
73
74
 
74
75
  return {
75
- refreshedClients,
76
+ attemptedClients,
76
77
  restartedClients,
77
78
  staleAssessment,
78
79
  };
@@ -1,5 +1,11 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
+ import {
4
+ type CodeQueryResult,
5
+ completedCodeQuery,
6
+ partialCodeQuery,
7
+ unavailableCodeQuery,
8
+ } from "@mrclrchtr/supi-code-runtime/api";
3
9
  import { walkProject } from "@mrclrchtr/supi-core/project";
4
10
  import type { LspClient } from "../client/client.ts";
5
11
  import type {
@@ -39,30 +45,62 @@ export function getWorkspaceSymbolWarmPosition(
39
45
  return null;
40
46
  }
41
47
 
48
+ export interface WorkspaceSymbolCollection {
49
+ results: WorkspaceSymbolLike[];
50
+ hasSupport: boolean;
51
+ completedClientCount: number;
52
+ failures: string[];
53
+ }
54
+
42
55
  export async function collectWorkspaceSymbols(
43
56
  clients: Iterable<LspClient>,
44
57
  query: string,
45
- ): Promise<{ results: WorkspaceSymbolLike[]; hasSupport: boolean }> {
58
+ ): Promise<WorkspaceSymbolCollection> {
46
59
  const results: WorkspaceSymbolLike[] = [];
60
+ const failures: string[] = [];
47
61
  let hasSupport = false;
62
+ let completedClientCount = 0;
48
63
 
49
64
  for (const client of clients) {
50
65
  if (client.status !== "running") continue;
51
66
  if (!client.serverCapabilities?.workspaceSymbolProvider) continue;
52
67
  hasSupport = true;
53
68
  const result = await client.workspaceSymbol(query);
54
- if (result) results.push(...result);
69
+ if (result.kind === "unavailable") {
70
+ failures.push(result.reason);
71
+ continue;
72
+ }
73
+ completedClientCount++;
74
+ results.push(...(result.data ?? []));
75
+ if (result.kind === "partial") failures.push(result.reason);
55
76
  }
56
77
 
57
- return { results, hasSupport };
78
+ return { results, hasSupport, completedClientCount, failures };
79
+ }
80
+
81
+ /** Project one multi-client collection into the shared typed query contract. */
82
+ export function workspaceSymbolCollectionResult(
83
+ collection: WorkspaceSymbolCollection,
84
+ ): CodeQueryResult<WorkspaceSymbolLike[]> {
85
+ if (!collection.hasSupport) {
86
+ return unavailableCodeQuery("No active LSP client supports workspace-symbol requests.");
87
+ }
88
+ if (collection.completedClientCount === 0) {
89
+ return unavailableCodeQuery(
90
+ collection.failures.join("; ") || "No workspace-symbol request completed.",
91
+ );
92
+ }
93
+ if (collection.failures.length > 0) {
94
+ return partialCodeQuery(collection.results, collection.failures.join("; "));
95
+ }
96
+ return completedCodeQuery(collection.results);
58
97
  }
59
98
 
60
99
  export async function managerWorkspaceSymbol(
61
100
  clients: Iterable<LspClient>,
62
101
  query: string,
63
- ): Promise<WorkspaceSymbolLike[] | null> {
64
- const { results, hasSupport } = await collectWorkspaceSymbols(clients, query);
65
- return hasSupport ? results : null;
102
+ ): Promise<CodeQueryResult<WorkspaceSymbolLike[]>> {
103
+ return workspaceSymbolCollectionResult(await collectWorkspaceSymbols(clients, query));
66
104
  }
67
105
 
68
106
  export function findWorkspaceSymbolWarmTargets(
@@ -2,6 +2,7 @@
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 { type CodeQueryResult, unavailableCodeQuery } from "@mrclrchtr/supi-code-runtime/api";
5
6
  import * as projectRoots from "@mrclrchtr/supi-core/project";
6
7
  import { LspClient } from "../client/client.ts";
7
8
  import { getServerForFile } from "../config/config.ts";
@@ -36,8 +37,7 @@ import {
36
37
  } from "./manager-client-state.ts";
37
38
  import {
38
39
  collectOutstandingDiagnosticsDetailed,
39
- mapCascadeDiagnosticsToFiles,
40
- syncClientFileAndGetCascadingDiagnostics,
40
+ syncClientFileAndGetDiagnostics,
41
41
  } from "./manager-diagnostics.ts";
42
42
  import {
43
43
  clientKey,
@@ -56,8 +56,11 @@ import type {
56
56
  } from "./manager-types.ts";
57
57
  import { recoverWorkspaceDiagnostics as recoverWorkspaceDiagnosticsImpl } from "./manager-workspace-recovery.ts";
58
58
  import {
59
+ collectWorkspaceSymbols,
59
60
  findWorkspaceSymbolWarmTargets,
60
61
  getWorkspaceSymbolWarmPosition,
62
+ type WorkspaceSymbolCollection,
63
+ workspaceSymbolCollectionResult,
61
64
  } from "./manager-workspace-symbol.ts";
62
65
 
63
66
  type UnavailableReason = "missing-command" | "start-failed" | "runtime-error";
@@ -361,8 +364,11 @@ export class LspManager {
361
364
  return client;
362
365
  }
363
366
 
364
- /** Wait until all started clients are query-ready, then warm one project file per client/root. */
365
- async waitUntilWorkspaceReady(): Promise<void> {
367
+ /**
368
+ * Wait until all started clients are query-ready, then warm one project file per client/root.
369
+ * Returns the number of concrete clients that reached readiness.
370
+ */
371
+ async waitUntilWorkspaceReady(): Promise<number> {
366
372
  const activeClients = Array.from(this.clients.values()).filter(
367
373
  (client) => client.status === "running",
368
374
  );
@@ -379,38 +385,26 @@ export class LspManager {
379
385
  if (!target) continue;
380
386
  await this.warmSemanticProject(client, target.file);
381
387
  }
388
+ return activeClients.length;
382
389
  }
383
390
  async syncFileAndGetDiagnostics(
384
391
  filePath: string,
385
392
  maxSeverity: number = 1,
386
- ): Promise<Diagnostic[]> {
387
- const resolvedPath = resolveSessionPath(this.cwd, filePath);
388
- return (
389
- (await this.syncFileAndGetCascadingDiagnostics(resolvedPath, maxSeverity)).find(
390
- (entry) => entry.file === resolvedPath,
391
- )?.diagnostics ?? []
392
- );
393
- }
394
- async syncFileAndGetCascadingDiagnostics(
395
- filePath: string,
396
- maxSeverity: number = 1,
397
- ): Promise<Array<{ file: string; diagnostics: Diagnostic[] }>> {
393
+ ): Promise<CodeQueryResult<Diagnostic[]>> {
398
394
  const resolvedPath = resolveSessionPath(this.cwd, filePath);
399
395
  const client = await this.getClientForFile(resolvedPath);
400
- if (!client) return [];
396
+ if (!client) {
397
+ return unavailableCodeQuery(`No LSP client can collect diagnostics for ${resolvedPath}.`);
398
+ }
401
399
  try {
402
- const { primary, cascade } = await syncClientFileAndGetCascadingDiagnostics(
403
- client,
404
- resolvedPath,
405
- maxSeverity,
406
- );
407
- return [
408
- ...(primary.length > 0 ? [{ file: resolvedPath, diagnostics: primary }] : []),
409
- ...mapCascadeDiagnosticsToFiles(cascade),
410
- ];
411
- } catch {
400
+ return {
401
+ kind: "completed",
402
+ data: await syncClientFileAndGetDiagnostics(client, resolvedPath, maxSeverity),
403
+ };
404
+ } catch (error) {
412
405
  this.closeFile(resolvedPath);
413
- return [];
406
+ const detail = error instanceof Error ? error.message : String(error);
407
+ return unavailableCodeQuery(`Diagnostic collection failed for ${resolvedPath}: ${detail}`);
414
408
  }
415
409
  }
416
410
  /** Close a file across any active LSP clients and clear its cached diagnostics. */
@@ -447,7 +441,7 @@ export class LspManager {
447
441
  maxWaitMs?: number;
448
442
  quietMs?: number;
449
443
  }): Promise<{
450
- refreshedClients: number;
444
+ attemptedClients: number;
451
445
  restartedClients: number;
452
446
  staleAssessment: {
453
447
  suspected: boolean;
@@ -613,21 +607,21 @@ export class LspManager {
613
607
  maxSeverity,
614
608
  );
615
609
  }
616
- async workspaceSymbol(query: string): Promise<(SymbolInformation | WorkspaceSymbol)[] | null> {
617
- const helper = await import("./manager-workspace-symbol.ts");
618
- const initial = await helper.collectWorkspaceSymbols(this.clients.values(), query);
619
- if (!initial.hasSupport) return null;
620
- if (initial.results.length > 0) return initial.results;
610
+ async workspaceSymbol(
611
+ query: string,
612
+ ): Promise<CodeQueryResult<(SymbolInformation | WorkspaceSymbol)[]>> {
613
+ const initial = await collectWorkspaceSymbols(this.clients.values(), query);
614
+ if (!initial.hasSupport || initial.results.length > 0) {
615
+ return workspaceSymbolCollectionResult(initial);
616
+ }
621
617
 
622
618
  const warmed = await this.warmWorkspaceSymbolProjectsUntilResult(
623
- helper.findWorkspaceSymbolWarmTargets,
624
- helper.getWorkspaceSymbolWarmPosition,
625
- helper.collectWorkspaceSymbols,
619
+ findWorkspaceSymbolWarmTargets,
620
+ getWorkspaceSymbolWarmPosition,
621
+ collectWorkspaceSymbols,
626
622
  query,
627
623
  );
628
- if (warmed.results) return warmed.results;
629
-
630
- return initial.results;
624
+ return workspaceSymbolCollectionResult(warmed.collection ?? initial);
631
625
  }
632
626
  async ensureFileOpen(filePath: string): Promise<LspClient | null> {
633
627
  const resolvedPath = resolveSessionPath(this.cwd, filePath);
@@ -650,14 +644,11 @@ export class LspManager {
650
644
  fileTypes: string[],
651
645
  ) => Array<{ projectRoot: string; file: string }>,
652
646
  getWarmPosition: (
653
- symbols: Awaited<ReturnType<LspClient["documentSymbols"]>>,
647
+ symbols: import("../config/types.ts").DocumentSymbol[] | SymbolInformation[] | null,
654
648
  ) => import("../config/types.ts").Position | null,
655
- collect: (
656
- clients: Iterable<LspClient>,
657
- query: string,
658
- ) => Promise<{ results: (SymbolInformation | WorkspaceSymbol)[]; hasSupport: boolean }>,
649
+ collect: (clients: Iterable<LspClient>, query: string) => Promise<WorkspaceSymbolCollection>,
659
650
  query: string,
660
- ): Promise<{ warmedAny: boolean; results: (SymbolInformation | WorkspaceSymbol)[] | null }> {
651
+ ): Promise<{ warmedAny: boolean; collection: WorkspaceSymbolCollection | null }> {
661
652
  let warmedAny = false;
662
653
 
663
654
  for (const client of Array.from(this.clients.values())) {
@@ -687,24 +678,20 @@ export class LspManager {
687
678
  this.warmedWorkspaceSymbolProjects.add(projectKey);
688
679
  warmedAny = true;
689
680
 
690
- try {
691
- const symbols = await openedClient.documentSymbols(target.file);
692
- const hoverPosition = getWarmPosition(symbols);
693
- if (hoverPosition) {
694
- await openedClient.hover(target.file, hoverPosition);
695
- }
696
- } catch {
697
- // Best-effort warm-up only.
681
+ const symbols = await openedClient.documentSymbols(target.file);
682
+ if (symbols.kind !== "unavailable") {
683
+ const hoverPosition = getWarmPosition(symbols.data);
684
+ if (hoverPosition) await openedClient.hover(target.file, hoverPosition);
698
685
  }
699
686
 
700
687
  const collected = await collect(this.clients.values(), query);
701
688
  if (collected.hasSupport && collected.results.length > 0) {
702
- return { warmedAny, results: collected.results };
689
+ return { warmedAny, collection: collected };
703
690
  }
704
691
  }
705
692
  }
706
693
 
707
- return { warmedAny, results: null };
694
+ return { warmedAny, collection: null };
708
695
  }
709
696
 
710
697
  private async warmSemanticProject(
@@ -750,15 +737,10 @@ export class LspManager {
750
737
 
751
738
  this.warmedSemanticProjects.add(projectKey);
752
739
 
753
- try {
754
- const symbols = await openedClient.documentSymbols(resolvedFile);
755
- const hoverPosition = getWorkspaceSymbolWarmPosition(symbols);
756
- if (hoverPosition) {
757
- await openedClient.hover(resolvedFile, hoverPosition);
758
- }
759
- } catch {
760
- // Best-effort warm-up only.
761
- }
740
+ const symbols = await openedClient.documentSymbols(resolvedFile);
741
+ if (symbols.kind === "unavailable") return;
742
+ const hoverPosition = getWorkspaceSymbolWarmPosition(symbols.data);
743
+ if (hoverPosition) await openedClient.hover(resolvedFile, hoverPosition);
762
744
  }
763
745
 
764
746
  private hasOpenFileInProject(client: LspClient, projectRoot: string): boolean {