Skip to content

Commit 86d858e

Browse files
authored
Language client cleanup (#98)
* Add `focus`, `unfocus` events for workspace folders * Start/stop LSP server as we move between workspace folders * Add fileEvents fileSystemWatcher so we send workspace/didChangeWatchedFiles events to LSP server * re-instated "sourcekit-lsp.serverPath" config option * Re-integrate inlay hints * Rename `extension.ts` to `LanguageClientManager.ts` * Move language client into workspace so other systems can use it * Make languageClient public * No need to have synchronize section in language client If server registers workspace/didChangeWorkspaceFolders message then VSCode automatically sends messages * Catch if LSP server supports workspace/didChangeWatchedFiles * Fixup for FolderEvent change to enum * Add LSP config to configuration.ts * Disable onRequest call ... ... as it stops registering capabilities
1 parent 5b0963a commit 86d858e

9 files changed

+230
-106
lines changed

package-lock.json

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+4
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@
6565
],
6666
"order": 3
6767
},
68+
"sourcekit-lsp.serverPath": {
69+
"type": "string",
70+
"description": "The path of the sourcekit-lsp executable. The default is to look in the path where swift is found."
71+
},
6872
"sourcekit-lsp.serverArguments": {
6973
"type": "array",
7074
"default": [],

src/SwiftOutputChannel.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,23 @@ export class SwiftOutputChannel {
2929
if (label) {
3030
label += ": ";
3131
}
32-
this.channel.appendLine(`${this.nowFormatted}: ${label ?? ""}${message}`);
32+
const line = `${this.nowFormatted}: ${label ?? ""}${message}`;
33+
this.channel.appendLine(line);
34+
console.log(line);
3335
}
3436

3537
logStart(message: string, label?: string) {
3638
if (label !== undefined) {
3739
label += ": ";
3840
}
39-
this.channel.append(`${this.nowFormatted}: ${label ?? ""}${message}`);
41+
const line = `${this.nowFormatted}: ${label ?? ""}${message}`;
42+
this.channel.append(line);
43+
console.log(line);
4044
}
4145

4246
logEnd(message: string) {
4347
this.channel.appendLine(message);
48+
console.log(message);
4449
}
4550

4651
get nowFormatted(): string {

src/WorkspaceContext.ts

+4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { StatusItem } from "./StatusItem";
1818
import { SwiftOutputChannel } from "./SwiftOutputChannel";
1919
import { execSwift, getSwiftExecutable, getXCTestPath } from "./utilities";
2020
import { getLLDBLibPath } from "./lldb";
21+
import { LanguageClientManager } from "./sourcekit-lsp/LanguageClientManager";
2122

2223
/**
2324
* Context for whole workspace. Holds array of contexts for each workspace folder
@@ -29,14 +30,17 @@ export class WorkspaceContext implements vscode.Disposable {
2930
public outputChannel: SwiftOutputChannel;
3031
public statusItem: StatusItem;
3132
public xcTestPath?: string;
33+
public languageClientManager: LanguageClientManager;
3234

3335
public constructor(public extensionContext: vscode.ExtensionContext) {
3436
this.outputChannel = new SwiftOutputChannel();
3537
this.statusItem = new StatusItem();
38+
this.languageClientManager = new LanguageClientManager(this);
3639
}
3740

3841
dispose() {
3942
this.folders.forEach(f => f.dispose());
43+
this.languageClientManager.dispose();
4044
this.outputChannel.dispose();
4145
this.statusItem.dispose();
4246
}

src/configuration.ts

+35-3
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,58 @@
1414

1515
import * as vscode from "vscode";
1616

17+
/** sourcekit-lsp configuration */
18+
export interface LSPConfiguration {
19+
/** Path to sourcekit-lsp executable */
20+
readonly serverPath: string;
21+
/** Arguments to pass to sourcekit-lsp executable */
22+
readonly serverArguments: string[];
23+
/** Toolchain to use with sourcekit-lsp */
24+
readonly toolchainPath: string;
25+
}
26+
1727
/**
1828
* Type-safe wrapper around configuration settings.
1929
*/
2030
const configuration = {
21-
/**
22-
* Files and directories to exclude from the Package Dependencies view.
23-
*/
31+
/** sourcekit-lsp configuration */
32+
get lsp(): LSPConfiguration {
33+
return {
34+
get serverPath(): string {
35+
return vscode.workspace
36+
.getConfiguration("sourcekit-lsp")
37+
.get<string>("serverPath", "");
38+
},
39+
get serverArguments(): string[] {
40+
return vscode.workspace
41+
.getConfiguration("sourcekit-lsp")
42+
.get<string[]>("serverArguments", []);
43+
},
44+
get toolchainPath(): string {
45+
return vscode.workspace
46+
.getConfiguration("sourcekit-lsp")
47+
.get<string>("toolchainPath", "");
48+
},
49+
};
50+
},
51+
52+
/** Files and directories to exclude from the Package Dependencies view. */
2453
get excludePathsFromPackageDependencies(): string[] {
2554
return vscode.workspace
2655
.getConfiguration("swift")
2756
.get<string[]>("excludePathsFromPackageDependencies", []);
2857
},
58+
/** Folders to exclude from package dependency view */
2959
set excludePathsFromPackageDependencies(value: string[]) {
3060
vscode.workspace
3161
.getConfiguration("swift")
3262
.update("excludePathsFromPackageDependencies", value);
3363
},
64+
/** Path to folder that include swift executable */
3465
get path(): string {
3566
return vscode.workspace.getConfiguration("swift").get<string>("path", "");
3667
},
68+
/** swift build arguments */
3769
get buildArguments(): string[] {
3870
return vscode.workspace.getConfiguration("swift").get<string[]>("buildArguments", []);
3971
},

src/extension.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import * as debug from "./debug";
1818
import { PackageDependenciesProvider } from "./PackageDependencyProvider";
1919
import { SwiftTaskProvider } from "./SwiftTaskProvider";
2020
import { FolderEvent, WorkspaceContext } from "./WorkspaceContext";
21-
import { activate as activateSourceKitLSP } from "./sourcekit-lsp/extension";
2221

2322
/**
2423
* Activate the extension. This is the main entry point.
@@ -38,8 +37,6 @@ export async function activate(context: vscode.ExtensionContext) {
3837
// listen for workspace folder changes and active text editor changes
3938
workspaceContext.setupEventListeners();
4039

41-
await activateSourceKitLSP(context);
42-
4340
// Register commands.
4441
const taskProvider = vscode.tasks.registerTaskProvider(
4542
"swift",
@@ -68,8 +65,8 @@ export async function activate(context: vscode.ExtensionContext) {
6865
});
6966

7067
// observer that will resolve package for root folder
71-
const resolvePackageObserver = workspaceContext.observeFolders(async (folder, operation) => {
72-
if (operation === FolderEvent.add && folder.swiftPackage.foundPackage) {
68+
const resolvePackageObserver = workspaceContext.observeFolders(async (folder, event) => {
69+
if (event === FolderEvent.add && folder.swiftPackage.foundPackage) {
7370
// Create launch.json files based on package description.
7471
await debug.makeDebugConfigurations(folder);
7572
if (folder.isRootFolder) {
+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the VSCode Swift open source project
4+
//
5+
// Copyright (c) 2021 the VSCode Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of VSCode Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
"use strict";
16+
import * as vscode from "vscode";
17+
import * as langclient from "vscode-languageclient/node";
18+
import configuration from "../configuration";
19+
import { getSwiftExecutable } from "../utilities";
20+
import { FolderEvent, WorkspaceContext } from "../WorkspaceContext";
21+
import { activateInlayHints } from "./inlayHints";
22+
23+
/** Manages the creation and destruction of Language clients as we move between
24+
* workspace folders
25+
*/
26+
export class LanguageClientManager {
27+
private observeFoldersDisposable: vscode.Disposable;
28+
/** current running client */
29+
public languageClient?: langclient.LanguageClient;
30+
private inlayHints?: vscode.Disposable;
31+
private supportsDidChangedWatchedFiles: boolean;
32+
33+
constructor(workspaceContext: WorkspaceContext) {
34+
// stop and start server for each folder based on which file I am looking at
35+
this.observeFoldersDisposable = workspaceContext.observeFolders(
36+
async (folderContext, event) => {
37+
switch (event) {
38+
case FolderEvent.focus:
39+
await this.setupLanguageClient(folderContext.folder);
40+
break;
41+
case FolderEvent.unfocus:
42+
this.inlayHints?.dispose();
43+
this.inlayHints = undefined;
44+
if (this.languageClient) {
45+
const client = this.languageClient;
46+
this.languageClient = undefined;
47+
client.stop();
48+
}
49+
break;
50+
}
51+
}
52+
);
53+
this.supportsDidChangedWatchedFiles = false;
54+
}
55+
56+
dispose() {
57+
this.observeFoldersDisposable.dispose();
58+
this.inlayHints?.dispose();
59+
this.languageClient?.stop();
60+
}
61+
62+
private async setupLanguageClient(folder: vscode.WorkspaceFolder) {
63+
const client = await this.createLSPClient(folder);
64+
client.start();
65+
66+
console.log(`SourceKit-LSP setup for ${folder.name}`);
67+
68+
this.supportsDidChangedWatchedFiles = false;
69+
this.languageClient = client;
70+
71+
client.onReady().then(() => {
72+
this.inlayHints = activateInlayHints(client);
73+
/* client.onRequest(langclient.RegistrationRequest.type, request => {
74+
console.log(p);
75+
const index = request.registrations.findIndex(
76+
value => value.method === "workspace/didChangeWatchedFiles"
77+
);
78+
if (index !== -1) {
79+
console.log("LSP Server supports workspace/didChangeWatchedFiles");
80+
this.supportsDidChangedWatchedFiles = true;
81+
}
82+
});*/
83+
});
84+
}
85+
86+
private async createLSPClient(
87+
folder: vscode.WorkspaceFolder
88+
): Promise<langclient.LanguageClient> {
89+
const serverPathConfig = configuration.lsp.serverPath;
90+
const serverPath =
91+
serverPathConfig.length > 0 ? serverPathConfig : getSwiftExecutable("sourcekit-lsp");
92+
const sourcekit: langclient.Executable = {
93+
command: serverPath,
94+
args: configuration.lsp.serverArguments,
95+
};
96+
97+
const toolchain = configuration.lsp.toolchainPath;
98+
if (toolchain.length > 0) {
99+
// eslint-disable-next-line @typescript-eslint/naming-convention
100+
sourcekit.options = { env: { ...process.env, SOURCEKIT_TOOLCHAIN_PATH: toolchain } };
101+
}
102+
103+
const serverOptions: langclient.ServerOptions = sourcekit;
104+
105+
const clientOptions: langclient.LanguageClientOptions = {
106+
// all the other LSP extensions have this in the form
107+
// {scheme: "file", language: "swift"}. Need to work out how this
108+
// is meant to work
109+
documentSelector: ["swift", "cpp", "c", "objective-c", "objective-cpp"],
110+
revealOutputChannelOn: langclient.RevealOutputChannelOn.Never,
111+
workspaceFolder: folder,
112+
};
113+
114+
return new langclient.LanguageClient(
115+
"sourcekit-lsp",
116+
"SourceKit Language Server",
117+
serverOptions,
118+
clientOptions
119+
);
120+
}
121+
}

src/sourcekit-lsp/extension.ts

-57
This file was deleted.

0 commit comments

Comments
 (0)