Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .ocamlformat-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ analysis/vendor/ext/map.cppo.ml
analysis/vendor/ext/ordered_hash_map.cppo.ml
analysis/vendor/ext/set.cppo.ml
analysis/vendor/ext/vec.cppo.ml
**/node_modules/**
**/node_modules/**
124 changes: 93 additions & 31 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as path from "path";
import * as fs from "fs";
import {
workspace,
ExtensionContext,
Expand All @@ -23,6 +24,7 @@ import {
ServerOptions,
State,
TransportKind,
DidChangeConfigurationNotification,
} from "vscode-languageclient/node";

import * as customCommands from "./commands";
Expand Down Expand Up @@ -90,6 +92,58 @@ export function activate(context: ExtensionContext) {
"rescript",
);

const useExperimentalServer = workspace
.getConfiguration("rescript")
.get<string | null>("experimentalServerPath", null);

function createExperimentalLanguageClient(serverPath: string) {
if (!fs.existsSync(serverPath)) {
const message = `The experimental server is enabled with \`experimentalServerPath\`, but no language server was found at ${serverPath}. See the instructions for using the experimental server in [ADD_LINK_TO_DOCS_HERE].`;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Remove See the instructions...

outputChannel.appendLine(message);
window.showErrorMessage(message);
outputChannel.show();
throw new Error(message);
}

let serverOptions: ServerOptions = {
run: {
command: serverPath,
transport: TransportKind.stdio,
},
debug: {
command: serverPath,
transport: TransportKind.stdio,
},
};

// Options to control the language client
let clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: "file", language: "rescript" }],
outputChannel,
markdown: {
isTrusted: true,
},
middleware: {
workspace: {
configuration: async (_params, _token, _next) => {
// For the experimental server, we don't want to send the full configuration
// We send only setting inside rescript.settings, i.e, server settings
return [workspace.getConfiguration("rescript.settings")];
},
},
},
Comment on lines +126 to +134

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If we don't use middleware, the server will retrieve all fields within rescript, but we only want rescript.settings. Otherwise, the server fails to parse the received JSON.

};

const client = new LanguageClient(
"ReScriptLSP",
"Experimental ReScript Language Server",
serverOptions,
clientOptions,
);

return client;
}

function createLanguageClient() {
// The server is implemented in node
let serverModule = context.asAbsolutePath(
Expand Down Expand Up @@ -143,36 +197,42 @@ export function activate(context: ExtensionContext) {
clientOptions,
);

// This sets up a listener that, if we're in code analysis mode, triggers
// code analysis as the LS server reports that ReScript compilation has
// finished. This is needed because code analysis must wait until
// compilation has finished, and the most reliable source for that is the LS
// server, that already keeps track of when the compiler finishes in order to
// other provide fresh diagnostics.
context.subscriptions.push(
client.onDidChangeState(({ newState }) => {
if (newState === State.Running) {
context.subscriptions.push(
client.onNotification("rescript/compilationFinished", () => {
if (inCodeAnalysisState.active === true) {
customCommands.codeAnalysisWithReanalyze(
diagnosticsCollection,
diagnosticsResultCodeActions,
outputChannel,
codeAnalysisRunningStatusBarItem,
);
}
}),
);
}
}),
);

return client;
}

function createClient() {
return useExperimentalServer != null
? createExperimentalLanguageClient(useExperimentalServer)
: createLanguageClient();
}

// Create the language client and start the client.
client = createLanguageClient();
client = createClient();

// This sets up a listener that, if we're in code analysis mode, triggers
// code analysis as the LS server reports that ReScript compilation has
// finished. This is needed because code analysis must wait until
// compilation has finished, and the most reliable source for that is the LS
// server, that already keeps track of when the compiler finishes in order to
// other provide fresh diagnostics.
context.subscriptions.push(
client.onDidChangeState(({ newState }) => {
if (newState === State.Running) {
context.subscriptions.push(
client.onNotification("rescript/compilationFinished", () => {
if (inCodeAnalysisState.active === true) {
customCommands.codeAnalysisWithReanalyze(
diagnosticsCollection,
diagnosticsResultCodeActions,
outputChannel,
codeAnalysisRunningStatusBarItem,
);
}
}),
);
}
}),
);

// Create a custom diagnostics collection, for cases where we want to report
// diagnostics programatically from inside of the extension. The reason this
Expand Down Expand Up @@ -263,7 +323,7 @@ export function activate(context: ExtensionContext) {
// Compact success display: project label plus a green check emoji
compilationStatusBarItem.text = `$(check) ReScript: Ok`;
compilationStatusBarItem.backgroundColor = undefined;
compilationStatusBarItem.color = null;
compilationStatusBarItem.color = undefined;
compilationStatusBarItem.command = undefined;
const projects = successes.map((e) => e.project).join(", ");
compilationStatusBarItem.tooltip = projects
Expand Down Expand Up @@ -333,7 +393,7 @@ export function activate(context: ExtensionContext) {
const removeAllCodeAction = new CodeAction("Remove all unused in file");
const edit = new WorkspaceEdit();
allRemoveActionEdits.forEach((subEdit) => {
subEdit.codeAction.edit.entries().forEach(([uri, [textEdit]]) => {
subEdit.codeAction.edit?.entries().forEach(([uri, [textEdit]]) => {
edit.replace(uri, textEdit.range, textEdit.newText);
});
});
Expand Down Expand Up @@ -361,7 +421,7 @@ export function activate(context: ExtensionContext) {

const document = editor.document;
const diagnostics = diagnosticsCollection.get(document.uri);
const newDiagnostics = diagnostics.filter((d) => d !== diagnostic);
const newDiagnostics = diagnostics?.filter((d) => d !== diagnostic);
diagnosticsCollection.set(document.uri, newDiagnostics);
},
);
Expand Down Expand Up @@ -513,7 +573,7 @@ export function activate(context: ExtensionContext) {

commands.registerCommand("rescript-vscode.restart_language_server", () => {
client.stop().then(() => {
client = createLanguageClient();
client = createClient();
client.start();
});
});
Expand Down Expand Up @@ -549,7 +609,9 @@ export function activate(context: ExtensionContext) {
// Send a general message that configuration has updated. Clients
// interested can then pull the new configuration as they see fit.
client
.sendNotification("workspace/didChangeConfiguration")
.sendNotification(DidChangeConfigurationNotification.type, {
settings: null,
})
Comment on lines +612 to +614

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The notification workspace/didChangeConfiguration params must have settings field. https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#didChangeConfigurationParams

This was breaking the experimental server.

.catch((err) => {
window.showErrorMessage(String(err));
});
Expand Down
13 changes: 13 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,19 @@
],
"default": "info",
"description": "Verbosity of ReScript language server logs sent to the Output channel."
},
"rescript.experimentalServerPath": {
"type": [
"string",
"null"
],
"default": null,
"description": "Path to the experimental ReScript language server binary."
},
"rescript.settings.hover.supportMarkdownLinks": {
"type": "boolean",
"default": true,
"description": "Enable markdown links in hover responses. Experimental server setting is required for this to work."
}
}
},
Expand Down
Loading