@@ -6,7 +6,7 @@ import { Logger } from "./logger"
6
6
import { CompositeDisposable , FilesystemChangeEvent , TextEditor } from "atom"
7
7
import { ReportBusyWhile } from "./utils"
8
8
9
- type MinimalLanguageServerProcess = Pick < ChildProcess , "stdin" | "stdout" | "stderr" | "pid" | "kill" | "on" >
9
+ export type MinimalLanguageServerProcess = Pick < ChildProcess , "stdin" | "stdout" | "stderr" | "pid" | "kill" | "on" >
10
10
11
11
/**
12
12
* Public: Defines a language server process which is either a ChildProcess, or it is a minimal object that resembles a
@@ -38,6 +38,7 @@ export class ServerManager {
38
38
private _disposable : CompositeDisposable = new CompositeDisposable ( )
39
39
private _editorToServer : Map < TextEditor , ActiveServer > = new Map ( )
40
40
private _normalizedProjectPaths : string [ ] = [ ]
41
+ private _previousNormalizedProjectPaths : string [ ] | undefined = undefined // TODO we should not hold a separate cache
41
42
private _isStarted = false
42
43
43
44
constructor (
@@ -60,6 +61,7 @@ export class ServerManager {
60
61
this . _disposable . add ( atom . project . onDidChangeFiles ( this . projectFilesChanged . bind ( this ) ) )
61
62
}
62
63
}
64
+ this . _isStarted = true
63
65
}
64
66
65
67
public stopListening ( ) : void {
@@ -111,8 +113,8 @@ export class ServerManager {
111
113
}
112
114
}
113
115
114
- public getActiveServers ( ) : ActiveServer [ ] {
115
- return this . _activeServers . slice ( )
116
+ public getActiveServers ( ) : Readonly < ActiveServer [ ] > {
117
+ return this . _activeServers
116
118
}
117
119
118
120
public async getServer (
@@ -252,17 +254,55 @@ export class ServerManager {
252
254
}
253
255
254
256
public updateNormalizedProjectPaths ( ) : void {
255
- this . _normalizedProjectPaths = atom . project . getDirectories ( ) . map ( ( d ) => this . normalizePath ( d . getPath ( ) ) )
257
+ this . _normalizedProjectPaths = atom . project . getPaths ( ) . map ( normalizePath )
256
258
}
257
259
258
- public normalizePath ( projectPath : string ) : string {
259
- return ! projectPath . endsWith ( path . sep ) ? path . join ( projectPath , path . sep ) : projectPath
260
+ public getNormalizedProjectPaths ( ) : Readonly < string [ ] > {
261
+ return this . _normalizedProjectPaths
262
+ }
263
+
264
+ /**
265
+ * Public: fetch the current open list of workspace folders
266
+ *
267
+ * @returns A {Promise} containing an {Array} of {lsp.WorkspaceFolder[]} or {null} if only a single file is open in the tool.
268
+ */
269
+ public getWorkspaceFolders ( ) : Promise < ls . WorkspaceFolder [ ] | null > {
270
+ // NOTE the method must return a Promise based on the specification
271
+ const projectPaths = this . getNormalizedProjectPaths ( )
272
+ if ( projectPaths . length === 0 ) {
273
+ // only a single file is open
274
+ return Promise . resolve ( null )
275
+ } else {
276
+ return Promise . resolve ( projectPaths . map ( normalizedProjectPathToWorkspaceFolder ) )
277
+ }
260
278
}
261
279
262
280
public async projectPathsChanged ( projectPaths : string [ ] ) : Promise < void > {
263
- const pathsSet = new Set ( projectPaths . map ( this . normalizePath ) )
264
- const serversToStop = this . _activeServers . filter ( ( s ) => ! pathsSet . has ( s . projectPath ) )
281
+ const pathsAll = projectPaths . map ( normalizePath )
282
+
283
+ const previousPaths = this . _previousNormalizedProjectPaths ?? this . getNormalizedProjectPaths ( )
284
+ const pathsRemoved = previousPaths . filter ( ( projectPath ) => ! pathsAll . includes ( projectPath ) )
285
+ const pathsAdded = pathsAll . filter ( ( projectPath ) => ! previousPaths . includes ( projectPath ) )
286
+
287
+ // update cache
288
+ this . _previousNormalizedProjectPaths = pathsAll
289
+
290
+ // send didChangeWorkspaceFolders
291
+ const didChangeWorkspaceFoldersParams = {
292
+ event : {
293
+ added : pathsAdded . map ( normalizedProjectPathToWorkspaceFolder ) ,
294
+ removed : pathsRemoved . map ( normalizedProjectPathToWorkspaceFolder ) ,
295
+ } ,
296
+ }
297
+ for ( const activeServer of this . _activeServers ) {
298
+ activeServer . connection . didChangeWorkspaceFolders ( didChangeWorkspaceFoldersParams )
299
+ }
300
+
301
+ // stop the servers that don't have projectPath
302
+ const serversToStop = this . _activeServers . filter ( ( server ) => pathsRemoved . includes ( server . projectPath ) )
265
303
await Promise . all ( serversToStop . map ( ( s ) => this . stopServer ( s ) ) )
304
+
305
+ // update this._normalizedProjectPaths
266
306
this . updateNormalizedProjectPaths ( )
267
307
}
268
308
@@ -290,4 +330,23 @@ export class ServerManager {
290
330
}
291
331
}
292
332
}
333
+
334
+ /** @deprecated Use the exported `normalizePath` function */
335
+ public normalizePath = normalizePath
336
+ }
337
+
338
+ export function projectPathToWorkspaceFolder ( projectPath : string ) : ls . WorkspaceFolder {
339
+ const normalizedProjectPath = normalizePath ( projectPath )
340
+ return normalizedProjectPathToWorkspaceFolder ( normalizedProjectPath )
341
+ }
342
+
343
+ export function normalizedProjectPathToWorkspaceFolder ( normalizedProjectPath : string ) : ls . WorkspaceFolder {
344
+ return {
345
+ uri : Convert . pathToUri ( normalizedProjectPath ) ,
346
+ name : path . basename ( normalizedProjectPath ) ,
347
+ }
348
+ }
349
+
350
+ export function normalizePath ( projectPath : string ) : string {
351
+ return ! projectPath . endsWith ( path . sep ) ? path . join ( projectPath , path . sep ) : projectPath
293
352
}
0 commit comments