Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for workspace folders #153

Merged
merged 38 commits into from
May 13, 2021
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8320bc8
feat: add support for workspace folders
aminya May 6, 2021
de845ff
chore: add getWorkspaceFolders function to AutoLanguageClient itself
aminya May 6, 2021
54003e4
chore: move getWorkspaceFolders to ServerManager
aminya May 6, 2021
9a4e39b
refactor: projectPathToWorkspaceFolder
aminya May 6, 2021
51fc9ff
feat: send didChangeWorkspaceFolders notification
aminya May 6, 2021
9cfced0
test: add getWorkspaceFolders tests
aminya May 6, 2021
4bce862
fix: calculate pathsRemoved and pathsAdded for didChangeWorkspaceFolders
aminya May 6, 2021
7f8cfba
fix: fix ServerManager._isStarted was never set to true
aminya May 6, 2021
d5d865f
fix: deprecate ServerManager.normalizePath and make it a free function
aminya May 6, 2021
626d972
test: add createFakeLanguageServerProcess
aminya May 6, 2021
55ec58d
test: add FakeAutoLanguageClient
aminya May 6, 2021
5fc541f
test: add tests for getWorkspaceFolders
aminya May 6, 2021
587803f
test: add test for didChangeWorkspaceFolders
aminya May 6, 2021
2239841
chore: update spawk
aminya May 6, 2021
a00b28a
test: deactivate the client inside afterEach
aminya May 6, 2021
10154ac
chore: rename getProjectPath methods
aminya May 6, 2021
6f355e5
chore: move the const didChangeWorkspaceFoldersParams out of the loop
aminya May 6, 2021
f7b3d47
fix: use getPaths inside updateNormalizedProjectPaths
aminya May 6, 2021
ccbfc7d
fix: add readonly type to the methods for getting servers and paths
aminya May 6, 2021
f7949d2
test: add test for removing project path
aminya May 6, 2021
a063bed
fix: hold a separate cache for _previousNormalizedProjectPaths
aminya May 6, 2021
58b6d7c
chore: move the deprecated method to the end
aminya May 6, 2021
f52f6ba
chore: remove duplicate code
aminya May 6, 2021
764b050
chore: remove excess cast
aminya May 6, 2021
ab0b861
Merge branch 'master' into workspaceFolders
aminya May 8, 2021
da688ac
test: convert the tests to use Jasmine
aminya May 8, 2021
ceec07c
test: run beforeEach/afterEach for each it
aminya May 8, 2021
47f8824
test: close all the editors and projects inside beforeEach
aminya May 8, 2021
a347f23
test: fix jasmine spy
aminya May 8, 2021
bae1d60
test: fix server projectPath was initialized two times
aminya May 8, 2021
ea66c7f
test: call beforeEach callback manually after the tests
aminya May 8, 2021
43d6eaa
test: make createSpyConnection more realistic by adding return values
aminya May 12, 2021
cc2a82b
Merge branch 'master' into workspaceFolders
aminya May 12, 2021
1d55e07
chore: update lock file
aminya May 12, 2021
9599d7d
test: remove async functions from afterEachCallback
aminya May 13, 2021
7c7fefd
test: use sleep inside the mock for lsp process
aminya May 13, 2021
20be4c3
test: skip the ServerManager tests on MacOS
aminya May 13, 2021
cda0807
chore: clean up beforeEach and afterEach functions
UziTech May 13, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ npm-debug.log
build/
yarn.lock
package-lock.json
.pnpm-debug.log
10 changes: 7 additions & 3 deletions lib/auto-languageclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { ConsoleLogger, FilteredLogger, Logger } from "./logger"
import { LanguageServerProcess, ServerManager, ActiveServer } from "./server-manager.js"
import { Disposable, CompositeDisposable, Point, Range, TextEditor } from "atom"
import * as ac from "atom/autocomplete-plus"
import { basename } from "path"

export { ActiveServer, LanguageClientConnection, LanguageServerProcess }
export type ConnectionType = "stdio" | "socket" | "ipc"
Expand Down Expand Up @@ -106,12 +107,13 @@ export default class AutoLanguageClient {

/** (Optional) Return the parameters used to initialize a client - you may want to extend capabilities */
protected getInitializeParams(projectPath: string, lsProcess: LanguageServerProcess): ls.InitializeParams {
const rootUri = Convert.pathToUri(projectPath)
return {
processId: lsProcess.pid,
rootPath: projectPath,
rootUri: Convert.pathToUri(projectPath),
rootUri,
locale: atom.config.get("atom-i18n.locale") || "en",
workspaceFolders: null,
workspaceFolders: [{ uri: rootUri, name: basename(projectPath) }],
// The capabilities supported.
// TODO the capabilities set to false/undefined are TODO. See {ls.ServerCapabilities} for a full list.
capabilities: {
Expand All @@ -124,7 +126,7 @@ export default class AutoLanguageClient {
changeAnnotationSupport: undefined,
resourceOperations: ["create", "rename", "delete"],
},
workspaceFolders: false,
workspaceFolders: true,
didChangeConfiguration: {
dynamicRegistration: false,
},
Expand Down Expand Up @@ -568,6 +570,8 @@ export default class AutoLanguageClient {
})

ShowDocumentAdapter.attach(server.connection)

server.connection.onWorkspaceFolders(() => this._serverManager.getWorkspaceFolders())
}

public shouldSyncForEditor(editor: TextEditor, projectPath: string): boolean {
Expand Down
21 changes: 21 additions & 0 deletions lib/languageclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,27 @@ export class LanguageClientConnection extends EventEmitter {
this._sendNotification(lsp.DidChangeWatchedFilesNotification.type, params)
}

/**
* Public: Register a callback for the `workspace.workspaceFolders` request. This request is sent from the server to
* Atom to fetch the current open list of workspace folders
*
* @param A Callback which returns a {Promise} containing an {Array} of {lsp.WorkspaceFolder[]} or {null} if only a
* single file is open in the tool.
*/
public onWorkspaceFolders(callback: () => Promise<lsp.WorkspaceFolder[] | null>): void {
return this._onRequest(lsp.WorkspaceFoldersRequest.type, callback)
}

/**
* Public: Send a `workspace/didChangeWorkspaceFolders` notification.
*
* @param {DidChangeWorkspaceFoldersParams} params An object that contains the actual workspace folder change event
* ({WorkspaceFoldersChangeEvent}) in its {event} property
*/
public didChangeWorkspaceFolders(params: lsp.DidChangeWorkspaceFoldersParams): void {
this._sendNotification(lsp.DidChangeWorkspaceFoldersNotification.type, params)
}

/**
* Public: Register a callback for the `textDocument/publishDiagnostics` message.
*
Expand Down
75 changes: 67 additions & 8 deletions lib/server-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Logger } from "./logger"
import { CompositeDisposable, FilesystemChangeEvent, TextEditor } from "atom"
import { ReportBusyWhile } from "./utils"

type MinimalLanguageServerProcess = Pick<ChildProcess, "stdin" | "stdout" | "stderr" | "pid" | "kill" | "on">
export type MinimalLanguageServerProcess = Pick<ChildProcess, "stdin" | "stdout" | "stderr" | "pid" | "kill" | "on">

/**
* Public: Defines a language server process which is either a ChildProcess, or it is a minimal object that resembles a
Expand Down Expand Up @@ -38,6 +38,7 @@ export class ServerManager {
private _disposable: CompositeDisposable = new CompositeDisposable()
private _editorToServer: Map<TextEditor, ActiveServer> = new Map()
private _normalizedProjectPaths: string[] = []
private _previousNormalizedProjectPaths: string[] | undefined = undefined // TODO we should not hold a separate cache
private _isStarted = false

constructor(
Expand All @@ -60,6 +61,7 @@ export class ServerManager {
this._disposable.add(atom.project.onDidChangeFiles(this.projectFilesChanged.bind(this)))
}
}
this._isStarted = true
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was an irrelevant bug that I discovered here.

}

public stopListening(): void {
Expand Down Expand Up @@ -111,8 +113,8 @@ export class ServerManager {
}
}

public getActiveServers(): ActiveServer[] {
return this._activeServers.slice()
public getActiveServers(): Readonly<ActiveServer[]> {
return this._activeServers
UziTech marked this conversation as resolved.
Show resolved Hide resolved
}

public async getServer(
Expand Down Expand Up @@ -252,17 +254,55 @@ export class ServerManager {
}

public updateNormalizedProjectPaths(): void {
this._normalizedProjectPaths = atom.project.getDirectories().map((d) => this.normalizePath(d.getPath()))
this._normalizedProjectPaths = atom.project.getPaths().map(normalizePath)
}

public normalizePath(projectPath: string): string {
return !projectPath.endsWith(path.sep) ? path.join(projectPath, path.sep) : projectPath
public getNormalizedProjectPaths(): Readonly<string[]> {
return this._normalizedProjectPaths
}

/**
* Public: fetch the current open list of workspace folders
*
* @returns A {Promise} containing an {Array} of {lsp.WorkspaceFolder[]} or {null} if only a single file is open in the tool.
*/
public getWorkspaceFolders(): Promise<ls.WorkspaceFolder[] | null> {
// NOTE the method must return a Promise based on the specification
const projectPaths = this.getNormalizedProjectPaths()
if (projectPaths.length === 0) {
// only a single file is open
return Promise.resolve(null)
} else {
return Promise.resolve(projectPaths.map(normalizedProjectPathToWorkspaceFolder))
}
}

public async projectPathsChanged(projectPaths: string[]): Promise<void> {
const pathsSet = new Set(projectPaths.map(this.normalizePath))
const serversToStop = this._activeServers.filter((s) => !pathsSet.has(s.projectPath))
const pathsAll = projectPaths.map(normalizePath)

const previousPaths = this._previousNormalizedProjectPaths ?? this.getNormalizedProjectPaths()
const pathsRemoved = previousPaths.filter((projectPath) => !pathsAll.includes(projectPath))
const pathsAdded = pathsAll.filter((projectPath) => !previousPaths.includes(projectPath))

// update cache
this._previousNormalizedProjectPaths = pathsAll

// send didChangeWorkspaceFolders
const didChangeWorkspaceFoldersParams = {
event: {
added: pathsAdded.map(normalizedProjectPathToWorkspaceFolder),
removed: pathsRemoved.map(normalizedProjectPathToWorkspaceFolder),
},
}
for (const activeServer of this._activeServers) {
activeServer.connection.didChangeWorkspaceFolders(didChangeWorkspaceFoldersParams)
}

// stop the servers that don't have projectPath
const serversToStop = this._activeServers.filter((server) => pathsRemoved.includes(server.projectPath))
await Promise.all(serversToStop.map((s) => this.stopServer(s)))

// update this._normalizedProjectPaths
this.updateNormalizedProjectPaths()
}

Expand Down Expand Up @@ -290,4 +330,23 @@ export class ServerManager {
}
}
}

/** @deprecated Use the exported `normalizePath` function */
public normalizePath = normalizePath
}

export function projectPathToWorkspaceFolder(projectPath: string): ls.WorkspaceFolder {
const normalizedProjectPath = normalizePath(projectPath)
return normalizedProjectPathToWorkspaceFolder(normalizedProjectPath)
}

export function normalizedProjectPathToWorkspaceFolder(normalizedProjectPath: string): ls.WorkspaceFolder {
return {
uri: Convert.pathToUri(normalizedProjectPath),
name: path.basename(normalizedProjectPath),
}
}

export function normalizePath(projectPath: string): string {
return !projectPath.endsWith(path.sep) ? path.join(projectPath, path.sep) : projectPath
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@
},
"devDependencies": {
"@types/atom": "^1.40.10",
"@types/jasmine": "^3.7.1",
"@types/node": "15.0.2",
"atom-jasmine3-test-runner": "^5.2.4",
"eslint-config-atomic": "^1.14.3",
"prettier-config-atomic": "^2.0.4",
"shx": "^0.3.3",
"spawk": "^1.4.0",
"typescript": "~4.2.4"
}
}
Loading