Skip to content

Commit a8ca9d8

Browse files
committed
Support experimental language server
1 parent 2bc69d2 commit a8ca9d8

2 files changed

Lines changed: 93 additions & 6 deletions

File tree

client/src/extension.ts

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
ServerOptions,
2424
State,
2525
TransportKind,
26+
DidChangeConfigurationNotification
2627
} from "vscode-languageclient/node";
2728

2829
import * as customCommands from "./commands";
@@ -90,6 +91,75 @@ export function activate(context: ExtensionContext) {
9091
"rescript",
9192
);
9293

94+
const useExperimentalServer = workspace.getConfiguration("rescript").get<boolean>("useExperimentalServer", false);
95+
96+
function createExperimentalLanguageClient() {
97+
const binaryPathFromNodeModules = Uri.joinPath(
98+
context.extensionUri,
99+
"node_modules",
100+
".bin",
101+
"rescript-language-server",
102+
).fsPath;
103+
104+
const userServerPath = workspace.getConfiguration("rescript").get<string | null>("experimentalServerPath", null);
105+
106+
const binaryPath = userServerPath ? userServerPath : binaryPathFromNodeModules;
107+
108+
let serverOptions: ServerOptions = {
109+
run: {
110+
command: binaryPath,
111+
transport: TransportKind.stdio,
112+
},
113+
debug: {
114+
command: binaryPath,
115+
transport: TransportKind.stdio,
116+
},
117+
};
118+
119+
// Options to control the language client
120+
let clientOptions: LanguageClientOptions = {
121+
documentSelector: [{ scheme: "file", language: "rescript" }],
122+
outputChannel,
123+
markdown: {
124+
isTrusted: true,
125+
},
126+
};
127+
128+
const client = new LanguageClient(
129+
"ReScriptLSP",
130+
"ReScript Experimental Language Server",
131+
serverOptions,
132+
clientOptions,
133+
);
134+
135+
// This sets up a listener that, if we're in code analysis mode, triggers
136+
// code analysis as the LS server reports that ReScript compilation has
137+
// finished. This is needed because code analysis must wait until
138+
// compilation has finished, and the most reliable source for that is the LS
139+
// server, that already keeps track of when the compiler finishes in order to
140+
// other provide fresh diagnostics.
141+
context.subscriptions.push(
142+
client.onDidChangeState(({ newState }) => {
143+
if (newState === State.Running) {
144+
context.subscriptions.push(
145+
client.onNotification("rescript/compilationFinished", () => {
146+
if (inCodeAnalysisState.active === true) {
147+
customCommands.codeAnalysisWithReanalyze(
148+
diagnosticsCollection,
149+
diagnosticsResultCodeActions,
150+
outputChannel,
151+
codeAnalysisRunningStatusBarItem,
152+
);
153+
}
154+
}),
155+
);
156+
}
157+
}),
158+
);
159+
160+
return client;
161+
}
162+
93163
function createLanguageClient() {
94164
// The server is implemented in node
95165
let serverModule = context.asAbsolutePath(
@@ -172,7 +242,7 @@ export function activate(context: ExtensionContext) {
172242
}
173243

174244
// Create the language client and start the client.
175-
client = createLanguageClient();
245+
client = useExperimentalServer ? createExperimentalLanguageClient(): createLanguageClient();
176246

177247
// Create a custom diagnostics collection, for cases where we want to report
178248
// diagnostics programatically from inside of the extension. The reason this
@@ -263,7 +333,7 @@ export function activate(context: ExtensionContext) {
263333
// Compact success display: project label plus a green check emoji
264334
compilationStatusBarItem.text = `$(check) ReScript: Ok`;
265335
compilationStatusBarItem.backgroundColor = undefined;
266-
compilationStatusBarItem.color = null;
336+
compilationStatusBarItem.color = undefined;
267337
compilationStatusBarItem.command = undefined;
268338
const projects = successes.map((e) => e.project).join(", ");
269339
compilationStatusBarItem.tooltip = projects
@@ -333,7 +403,7 @@ export function activate(context: ExtensionContext) {
333403
const removeAllCodeAction = new CodeAction("Remove all unused in file");
334404
const edit = new WorkspaceEdit();
335405
allRemoveActionEdits.forEach((subEdit) => {
336-
subEdit.codeAction.edit.entries().forEach(([uri, [textEdit]]) => {
406+
subEdit.codeAction.edit?.entries().forEach(([uri, [textEdit]]) => {
337407
edit.replace(uri, textEdit.range, textEdit.newText);
338408
});
339409
});
@@ -361,7 +431,7 @@ export function activate(context: ExtensionContext) {
361431

362432
const document = editor.document;
363433
const diagnostics = diagnosticsCollection.get(document.uri);
364-
const newDiagnostics = diagnostics.filter((d) => d !== diagnostic);
434+
const newDiagnostics = diagnostics?.filter((d) => d !== diagnostic);
365435
diagnosticsCollection.set(document.uri, newDiagnostics);
366436
},
367437
);
@@ -513,7 +583,7 @@ export function activate(context: ExtensionContext) {
513583

514584
commands.registerCommand("rescript-vscode.restart_language_server", () => {
515585
client.stop().then(() => {
516-
client = createLanguageClient();
586+
client = useExperimentalServer ? createExperimentalLanguageClient(): createLanguageClient();;
517587
client.start();
518588
});
519589
});
@@ -548,8 +618,12 @@ export function activate(context: ExtensionContext) {
548618
}
549619
// Send a general message that configuration has updated. Clients
550620
// interested can then pull the new configuration as they see fit.
621+
// The spec say params DidChangeConfigurationParams can contain the settings fields
622+
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#didChangeConfigurationParams
551623
client
552-
.sendNotification("workspace/didChangeConfiguration")
624+
.sendNotification(DidChangeConfigurationNotification.type, {
625+
settings: null,
626+
})
553627
.catch((err) => {
554628
window.showErrorMessage(String(err));
555629
});

package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,19 @@
158158
"type": "object",
159159
"title": "ReScript",
160160
"properties": {
161+
"rescript.useExperimentalServer": {
162+
"type": "boolean",
163+
"default": false,
164+
"description": "Whether to use the experimental ReScript language server (OCaml implementation) instead of the stable one (Node.js implementation). You can switch back and forth without affecting your project or settings."
165+
},
166+
"rescript.experimentalServerPath": {
167+
"type": [
168+
"string",
169+
"null"
170+
],
171+
"default": null,
172+
"description": "Path to the experimental ReScript language server binary. You can set this if you want to use a custom-built version of the experimental server or if the extension is unable to automatically locate the server binary in your system."
173+
},
161174
"rescript.settings.askToStartBuild": {
162175
"scope": "language-overridable",
163176
"type": "boolean",

0 commit comments

Comments
 (0)