@@ -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-
245243interface 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
251262interface 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 ( / ^ f i l e : \/ \/ / , "" ) ) ;
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
0 commit comments