Skip to content

Commit b8ddbd0

Browse files
authored
Merge branch 'Acode-Foundation:main' into strings/id-lang
2 parents 9a990c8 + 25f86d7 commit b8ddbd0

5 files changed

Lines changed: 235 additions & 66 deletions

File tree

src/cm/lsp/clientManager.ts

Lines changed: 152 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import type {
3636
FormattingOptions,
3737
LspServerDefinition,
3838
LspRuntimeConnection,
39+
LspRuntimeProvider,
40+
LspClientScope,
3941
NormalizedRootUri,
4042
ParsedUri,
4143
RootUriContext,
@@ -238,14 +240,23 @@ function buildBuiltinExtensions(
238240
return { extensions, diagnosticsExtension };
239241
}
240242

241-
interface LSPError extends Error {
242-
code?: string;
243-
}
244-
245243
interface InitContext {
246244
key: string;
247245
normalizedRootUri: string | null;
248246
originalRootUri: string | null;
247+
originalDocumentUri: string;
248+
documentUri: string;
249+
runtimeProvider: LspRuntimeProvider;
250+
scope: LspClientScope;
251+
}
252+
253+
interface ResolvedRuntimeTarget {
254+
originalDocumentUri: string;
255+
documentUri: string;
256+
normalizedRootUri: string | null;
257+
originalRootUri: string | null;
258+
runtimeProvider: LspRuntimeProvider;
259+
scope: LspClientScope;
249260
}
250261

251262
interface ExtendedLSPClient extends LSPClient {
@@ -295,19 +306,20 @@ export class LspClientManager {
295306
if (isSettingsOrKeybindingsFile(server, originalUri, file)) {
296307
continue;
297308
}
298-
const normalizedUri = await this.#resolveDocumentUri(server, {
309+
const target = await this.#resolveRuntimeTarget(server, {
299310
uri: originalUri,
300311
file,
301312
view,
302313
languageId: effectiveLang,
303314
rootUri,
304315
});
305-
if (!normalizedUri) {
316+
if (!target) {
306317
console.warn(
307-
`Cannot resolve document URI for LSP server ${server.id}: ${originalUri}`,
318+
`Cannot resolve runtime or document URI for LSP server ${server.id}: ${originalUri}`,
308319
);
309320
continue;
310321
}
322+
const normalizedUri = target.documentUri;
311323
let targetLanguageId = effectiveLang;
312324
if (server.resolveLanguageId) {
313325
try {
@@ -327,13 +339,17 @@ export class LspClientManager {
327339
}
328340

329341
try {
330-
const clientState = await this.#ensureClient(server, {
331-
uri: normalizedUri,
332-
file,
333-
view,
334-
languageId: targetLanguageId,
335-
rootUri,
336-
});
342+
const clientState = await this.#ensureClient(
343+
server,
344+
{
345+
uri: normalizedUri,
346+
file,
347+
view,
348+
languageId: targetLanguageId,
349+
rootUri: target.normalizedRootUri ?? undefined,
350+
},
351+
target,
352+
);
337353
const plugin = clientState.client.plugin(
338354
normalizedUri,
339355
targetLanguageId,
@@ -343,13 +359,6 @@ export class LspClientManager {
343359
clientState.attach(normalizedUri, view as EditorView, aliases);
344360
lspExtensions.push(plugin);
345361
} catch (error) {
346-
const lspError = error as LSPError;
347-
if (lspError?.code === "LSP_SERVER_UNAVAILABLE") {
348-
console.info(
349-
`Skipping LSP client for ${server.id}: ${lspError.message}`,
350-
);
351-
continue;
352-
}
353362
console.error(
354363
`Failed to initialize LSP client for ${server.id}`,
355364
error,
@@ -382,27 +391,28 @@ export class LspClientManager {
382391
}
383392
if (!supportsBuiltinFormatting(server)) continue;
384393
try {
385-
const normalizedUri = await this.#resolveDocumentUri(server, {
394+
const target = await this.#resolveRuntimeTarget(server, {
386395
uri: originalUri,
387396
file,
388397
view,
389398
languageId: effectiveLang,
390399
rootUri: metadata.rootUri,
391400
});
392-
if (!normalizedUri) {
401+
if (!target) {
393402
console.warn(
394403
`Cannot resolve document URI for formatting with ${server.id}: ${originalUri}`,
395404
);
396405
continue;
397406
}
407+
const normalizedUri = target.documentUri;
398408
const context: RootUriContext = {
399409
uri: normalizedUri,
400410
languageId: effectiveLang,
401411
view,
402412
file,
403-
rootUri: metadata.rootUri,
413+
rootUri: target.normalizedRootUri ?? undefined,
404414
};
405-
const state = await this.#ensureClient(server, context);
415+
const state = await this.#ensureClient(server, context, target);
406416
const capabilities = state.client.serverCapabilities;
407417
if (!capabilities?.documentFormattingProvider) continue;
408418
state.attach(normalizedUri, view);
@@ -498,16 +508,24 @@ export class LspClientManager {
498508
async #ensureClient(
499509
server: LspServerDefinition,
500510
context: RootUriContext,
511+
target: ResolvedRuntimeTarget,
501512
): Promise<ClientState> {
502-
const useWsFolders = server.useWorkspaceFolders === true;
503-
const resolvedRoot = await this.#resolveRootUri(server, context);
504-
const { normalizedRootUri, originalRootUri } = normalizeRootUriForServer(
505-
server,
506-
resolvedRoot,
507-
);
508-
509-
// For workspace folders mode, use a shared key based on server ID only
510-
const key = pluginKey(server.id, normalizedRootUri, useWsFolders);
513+
const {
514+
documentUri,
515+
normalizedRootUri,
516+
originalRootUri,
517+
runtimeProvider,
518+
scope,
519+
} = target;
520+
const useWsFolders =
521+
scope === "workspace" && server.useWorkspaceFolders === true;
522+
const runtimeServerKey = `${server.id}@${runtimeProvider.id}`;
523+
524+
// Workspace-folder clients are shared only within the selected runtime.
525+
const key =
526+
scope === "document"
527+
? `${runtimeServerKey}::__document__::${documentUri}`
528+
: pluginKey(runtimeServerKey, normalizedRootUri, useWsFolders);
511529

512530
// Return existing client if already initialized
513531
if (this.#clients.has(key)) {
@@ -532,6 +550,10 @@ export class LspClientManager {
532550
key,
533551
normalizedRootUri: useWsFolders ? null : normalizedRootUri,
534552
originalRootUri: useWsFolders ? null : originalRootUri,
553+
originalDocumentUri: target.originalDocumentUri,
554+
documentUri,
555+
runtimeProvider,
556+
scope,
535557
});
536558
this.#pendingClients.set(key, initPromise);
537559

@@ -547,7 +569,15 @@ export class LspClientManager {
547569
context: RootUriContext,
548570
initContext: InitContext,
549571
): Promise<ClientState> {
550-
const { key, normalizedRootUri, originalRootUri } = initContext;
572+
const {
573+
key,
574+
normalizedRootUri,
575+
originalRootUri,
576+
originalDocumentUri,
577+
documentUri,
578+
runtimeProvider,
579+
scope,
580+
} = initContext;
551581

552582
const workspaceOptions = {
553583
displayFile: this.options.displayFile,
@@ -813,24 +843,15 @@ export class LspClientManager {
813843
try {
814844
const runtimeContext = {
815845
...context,
846+
uri: documentUri,
847+
documentUri,
848+
originalDocumentUri,
816849
rootUri: normalizedRootUri ?? null,
817850
originalRootUri: originalRootUri ?? undefined,
818851
serverId: server.id,
819852
allowNonTerminalWorkspace:
820853
this.options.allowNonTerminalWorkspace === true,
821854
};
822-
const runtimeProvider = await selectRuntimeProvider(
823-
server,
824-
runtimeContext,
825-
);
826-
if (!runtimeProvider) {
827-
const unavailable: LSPError = new Error(
828-
`No LSP runtime provider can handle ${server.id}.`,
829-
);
830-
unavailable.code = "LSP_SERVER_UNAVAILABLE";
831-
throw unavailable;
832-
}
833-
834855
const connection = await runtimeProvider.start(server, runtimeContext);
835856
const connectionDispose = connection.dispose;
836857
connection.dispose = async () => {
@@ -892,7 +913,7 @@ export class LspClientManager {
892913
client,
893914
transportHandle,
894915
normalizedRootUri,
895-
originalRootUri,
916+
originalRootUri: scope === "document" ? null : originalRootUri,
896917
});
897918

898919
this.#clients.set(key, state);
@@ -1018,26 +1039,95 @@ export class LspClientManager {
10181039
return null;
10191040
}
10201041

1042+
async #resolveRuntimeTarget(
1043+
server: LspServerDefinition,
1044+
context: RootUriContext,
1045+
): Promise<ResolvedRuntimeTarget | null> {
1046+
const originalDocumentUri = context.uri;
1047+
if (!originalDocumentUri) return null;
1048+
1049+
const originalRootUri = await this.#resolveRootUri(server, context);
1050+
const { normalizedRootUri } = normalizeRootUriForServer(
1051+
server,
1052+
originalRootUri,
1053+
);
1054+
const normalizedDocumentUri = await this.#resolveDocumentUri(
1055+
server,
1056+
context,
1057+
);
1058+
const providerContext = {
1059+
...context,
1060+
uri: originalDocumentUri,
1061+
documentUri: normalizedDocumentUri,
1062+
originalDocumentUri,
1063+
rootUri: originalRootUri,
1064+
originalRootUri: originalRootUri ?? undefined,
1065+
serverId: server.id,
1066+
allowNonTerminalWorkspace:
1067+
this.options.allowNonTerminalWorkspace === true,
1068+
};
1069+
const runtimeProvider = await selectRuntimeProvider(server, providerContext);
1070+
if (!runtimeProvider) {
1071+
console.warn(
1072+
`No LSP runtime provider selected for ${server.id}: uri=${originalDocumentUri}, root=${originalRootUri ?? "none"}, normalizedUri=${normalizedDocumentUri ?? "none"}`,
1073+
);
1074+
return null;
1075+
}
1076+
1077+
let documentUri = normalizedDocumentUri;
1078+
let rootUri = normalizedRootUri;
1079+
let scope: LspClientScope = "workspace";
1080+
1081+
if (runtimeProvider.resolveUris) {
1082+
try {
1083+
const resolution = await runtimeProvider.resolveUris(server, {
1084+
...providerContext,
1085+
originalRootUri,
1086+
normalizedDocumentUri,
1087+
normalizedRootUri,
1088+
});
1089+
if (resolution) {
1090+
if (Object.prototype.hasOwnProperty.call(resolution, "documentUri")) {
1091+
documentUri = resolution.documentUri || null;
1092+
}
1093+
if (Object.prototype.hasOwnProperty.call(resolution, "rootUri")) {
1094+
rootUri = resolution.rootUri || null;
1095+
}
1096+
if (resolution.scope) scope = resolution.scope;
1097+
}
1098+
} catch (error) {
1099+
console.warn(
1100+
`LSP runtime provider ${runtimeProvider.id} failed to resolve URIs for ${server.id}`,
1101+
error,
1102+
);
1103+
return null;
1104+
}
1105+
}
1106+
1107+
if (!documentUri) {
1108+
console.warn(
1109+
`LSP runtime provider ${runtimeProvider.id} produced no document URI for ${server.id}: uri=${originalDocumentUri}, normalizedUri=${normalizedDocumentUri ?? "none"}`,
1110+
);
1111+
return null;
1112+
}
1113+
return {
1114+
originalDocumentUri,
1115+
documentUri,
1116+
normalizedRootUri: rootUri,
1117+
originalRootUri,
1118+
runtimeProvider,
1119+
scope,
1120+
};
1121+
}
1122+
10211123
async #resolveDocumentUri(
10221124
server: LspServerDefinition,
10231125
context: RootUriContext,
10241126
): Promise<string | null> {
10251127
const originalUri = context?.uri;
10261128
if (!originalUri) return null;
10271129

1028-
let normalizedUri = normalizeDocumentUri(originalUri);
1029-
if (!normalizedUri) {
1030-
// Fall back to cache file path for providers that do not expose a file:// URI.
1031-
const cacheFile = context.file?.cacheFile;
1032-
if (cacheFile && typeof cacheFile === "string") {
1033-
normalizedUri = buildFileUri(cacheFile.replace(/^file:\/\//, ""));
1034-
if (normalizedUri) {
1035-
console.info(
1036-
`LSP using cache path for unrecognized URI: ${originalUri} -> ${normalizedUri}`,
1037-
);
1038-
}
1039-
}
1040-
}
1130+
const normalizedUri = normalizeDocumentUri(originalUri);
10411131

10421132
if (typeof server.documentUri === "function") {
10431133
try {
@@ -1054,7 +1144,7 @@ export class LspClientManager {
10541144
}
10551145
}
10561146

1057-
return normalizedUri || originalUri;
1147+
return normalizedUri;
10581148
}
10591149
}
10601150

src/cm/lsp/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,12 @@ export type {
117117
LSPFormattingOptions,
118118
LSPPluginAPI,
119119
LspDiagnostic,
120+
LspClientScope,
120121
LspRuntimeConnection,
121122
LspRuntimeContext,
122123
LspRuntimeProvider,
124+
LspRuntimeUriResolution,
125+
LspRuntimeUriResolutionContext,
123126
LspServerDefinition,
124127
Position,
125128
Range,

src/cm/lsp/runtimeProviders.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ function normalizeProvider(
3636
throw new Error(`LSP runtime provider ${id} requires start()`);
3737
}
3838
for (const method of [
39+
"resolveUris",
3940
"checkInstallation",
4041
"install",
4142
"uninstall",
@@ -202,7 +203,7 @@ export function isBuiltinAlpineAccessible(
202203
const scheme = schemeMatch ? schemeMatch[1].toLowerCase() : null;
203204

204205
if (!scheme) return uri.startsWith("/");
205-
if (scheme === "file" || scheme === "untitled") return true;
206+
if (scheme === "file") return true;
206207
if (scheme !== "content") return false;
207208

208209
return /^content:\/\/com\.foxdebug\.acode(?:free)?\.documents\//i.test(uri);

0 commit comments

Comments
 (0)