@@ -130,18 +130,29 @@ export namespace LSP {
130130 }
131131 }
132132
133+ const keyFor = ( root : string , serverID : string ) => `${ root } \u0000${ serverID } `
134+
135+ const keyMatchesServer = ( key : string , serverID : string ) => {
136+ const split = key . lastIndexOf ( "\u0000" )
137+ if ( split === - 1 ) return false
138+ return key . slice ( split + 1 ) === serverID
139+ }
140+
133141 type LocInput = { file : string ; line : number ; character : number }
134142
135143 interface State {
136144 clients : LSPClient . Info [ ]
137145 servers : Record < string , LSPServer . Info >
138146 broken : Set < string >
139147 spawning : Map < string , Promise < LSPClient . Info | undefined > >
148+ generation : Map < string , number >
140149 }
141150
142151 export interface Interface {
143152 readonly init : ( ) => Effect . Effect < void >
144153 readonly status : ( ) => Effect . Effect < Status [ ] >
154+ readonly kill : ( name : string ) => Effect . Effect < boolean >
155+ readonly killAll : ( ) => Effect . Effect < boolean >
145156 readonly hasClients : ( file : string ) => Effect . Effect < boolean >
146157 readonly touchFile : ( input : string , waitForDiagnostics ?: boolean ) => Effect . Effect < void >
147158 readonly diagnostics : ( ) => Effect . Effect < Record < string , LSPClient . Diagnostic [ ] > >
@@ -212,6 +223,7 @@ export namespace LSP {
212223 servers,
213224 broken : new Set ( ) ,
214225 spawning : new Map ( ) ,
226+ generation : new Map ( ) ,
215227 }
216228
217229 yield * Effect . addFinalizer ( ( ) =>
@@ -232,37 +244,47 @@ export namespace LSP {
232244 const result : LSPClient . Info [ ] = [ ]
233245
234246 async function schedule ( server : LSPServer . Info , root : string , key : string ) {
247+ const generation = s . generation . get ( key ) ?? 0
235248 const handle = await server
236249 . spawn ( root )
237250 . then ( ( value ) => {
238- if ( ! value ) s . broken . add ( key )
251+ if ( ! value && ( s . generation . get ( key ) ?? 0 ) === generation ) s . broken . add ( key )
239252 return value
240253 } )
241254 . catch ( ( err ) => {
242- s . broken . add ( key )
255+ if ( ( s . generation . get ( key ) ?? 0 ) === generation ) s . broken . add ( key )
243256 log . error ( `Failed to spawn LSP server ${ server . id } ` , { error : err } )
244257 return undefined
245258 } )
246259
247260 if ( ! handle ) return undefined
261+ if ( ( s . generation . get ( key ) ?? 0 ) !== generation ) {
262+ handle . process . kill ( )
263+ return undefined
264+ }
248265 log . info ( "spawned lsp server" , { serverID : server . id } )
249266
250267 const client = await LSPClient . create ( {
251268 serverID : server . id ,
252269 server : handle ,
253270 root,
254271 } ) . catch ( async ( err ) => {
255- s . broken . add ( key )
272+ if ( ( s . generation . get ( key ) ?? 0 ) === generation ) s . broken . add ( key )
256273 await Process . stop ( handle . process )
257274 log . error ( `Failed to initialize LSP client ${ server . id } ` , { error : err } )
258275 return undefined
259276 } )
260277
261278 if ( ! client ) return undefined
262279
280+ if ( ( s . generation . get ( key ) ?? 0 ) !== generation ) {
281+ await client . shutdown ( ) . catch ( ( ) => { } )
282+ return undefined
283+ }
284+
263285 const existing = s . clients . find ( ( x ) => x . root === root && x . serverID === server . id )
264286 if ( existing ) {
265- await Process . stop ( handle . process )
287+ await client . shutdown ( ) . catch ( ( ) => { } )
266288 return existing
267289 }
268290
@@ -275,28 +297,29 @@ export namespace LSP {
275297
276298 const root = await server . root ( file )
277299 if ( ! root ) continue
278- if ( s . broken . has ( root + server . id ) ) continue
300+ const key = keyFor ( root , server . id )
301+ if ( s . broken . has ( key ) ) continue
279302
280303 const match = s . clients . find ( ( x ) => x . root === root && x . serverID === server . id )
281304 if ( match ) {
282305 result . push ( match )
283306 continue
284307 }
285308
286- const inflight = s . spawning . get ( root + server . id )
309+ const inflight = s . spawning . get ( key )
287310 if ( inflight ) {
288311 const client = await inflight
289312 if ( ! client ) continue
290313 result . push ( client )
291314 continue
292315 }
293316
294- const task = schedule ( server , root , root + server . id )
295- s . spawning . set ( root + server . id , task )
317+ const task = schedule ( server , root , key )
318+ s . spawning . set ( key , task )
296319
297320 task . finally ( ( ) => {
298- if ( s . spawning . get ( root + server . id ) === task ) {
299- s . spawning . delete ( root + server . id )
321+ if ( s . spawning . get ( key ) === task ) {
322+ s . spawning . delete ( key )
300323 }
301324 } )
302325
@@ -339,6 +362,78 @@ export namespace LSP {
339362 return result
340363 } )
341364
365+ const kill = Effect . fn ( "LSP.kill" ) ( function * ( name : string ) {
366+ const s = yield * InstanceState . get ( state )
367+ return yield * Effect . promise ( async ( ) => {
368+ const matches = s . clients . filter ( ( client ) => client . serverID === name )
369+
370+ if ( matches . length > 0 ) {
371+ s . clients = s . clients . filter ( ( client ) => client . serverID !== name )
372+ }
373+
374+ for ( const client of matches ) {
375+ const key = keyFor ( client . root , client . serverID )
376+ s . generation . set ( key , ( s . generation . get ( key ) ?? 0 ) + 1 )
377+ }
378+
379+ await Promise . all (
380+ matches . map ( ( client ) =>
381+ client . shutdown ( ) . catch ( ( error ) => {
382+ log . error ( `Failed to shutdown LSP client ${ name } ` , { error } )
383+ } ) ,
384+ ) ,
385+ )
386+
387+ const spawning = [ ...s . spawning . keys ( ) ] . filter ( ( key ) => keyMatchesServer ( key , name ) )
388+ for ( const key of spawning ) {
389+ s . generation . set ( key , ( s . generation . get ( key ) ?? 0 ) + 1 )
390+ s . spawning . delete ( key )
391+ }
392+
393+ const broken = [ ...s . broken ] . filter ( ( key ) => keyMatchesServer ( key , name ) )
394+ for ( const key of broken ) {
395+ s . broken . delete ( key )
396+ }
397+
398+ const changed = matches . length > 0 || spawning . length > 0 || broken . length > 0
399+ if ( ! changed ) return false
400+ await Bus . publish ( Event . Updated , { } )
401+ return true
402+ } )
403+ } )
404+
405+ const killAll = Effect . fn ( "LSP.killAll" ) ( function * ( ) {
406+ const s = yield * InstanceState . get ( state )
407+ return yield * Effect . promise ( async ( ) => {
408+ const clients = [ ...s . clients ]
409+ if ( clients . length === 0 && s . spawning . size === 0 && s . broken . size === 0 ) return false
410+
411+ s . clients = [ ]
412+
413+ for ( const client of clients ) {
414+ const key = keyFor ( client . root , client . serverID )
415+ s . generation . set ( key , ( s . generation . get ( key ) ?? 0 ) + 1 )
416+ }
417+
418+ await Promise . all (
419+ clients . map ( ( client ) =>
420+ client . shutdown ( ) . catch ( ( error ) => {
421+ log . error ( `Failed to shutdown LSP client ${ client . serverID } ` , { error } )
422+ } ) ,
423+ ) ,
424+ )
425+
426+ for ( const key of s . spawning . keys ( ) ) {
427+ s . generation . set ( key , ( s . generation . get ( key ) ?? 0 ) + 1 )
428+ }
429+ s . spawning . clear ( )
430+ s . broken . clear ( )
431+
432+ await Bus . publish ( Event . Updated , { } )
433+ return true
434+ } )
435+ } )
436+
342437 const hasClients = Effect . fn ( "LSP.hasClients" ) ( function * ( file : string ) {
343438 const s = yield * InstanceState . get ( state )
344439 return yield * Effect . promise ( async ( ) => {
@@ -347,7 +442,7 @@ export namespace LSP {
347442 if ( server . extensions . length && ! server . extensions . includes ( extension ) ) continue
348443 const root = await server . root ( file )
349444 if ( ! root ) continue
350- if ( s . broken . has ( root + server . id ) ) continue
445+ if ( s . broken . has ( keyFor ( root , server . id ) ) ) continue
351446 return true
352447 }
353448 return false
@@ -490,6 +585,8 @@ export namespace LSP {
490585 return Service . of ( {
491586 init,
492587 status,
588+ kill,
589+ killAll,
493590 hasClients,
494591 touchFile,
495592 diagnostics,
@@ -514,6 +611,10 @@ export namespace LSP {
514611
515612 export const status = async ( ) => runPromise ( ( svc ) => svc . status ( ) )
516613
614+ export const kill = async ( name : string ) => runPromise ( ( svc ) => svc . kill ( name ) )
615+
616+ export const killAll = async ( ) => runPromise ( ( svc ) => svc . killAll ( ) )
617+
517618 export const hasClients = async ( file : string ) => runPromise ( ( svc ) => svc . hasClients ( file ) )
518619
519620 export const touchFile = async ( input : string , waitForDiagnostics ?: boolean ) =>
0 commit comments