Skip to content
This repository was archived by the owner on Mar 21, 2024. It is now read-only.
Closed
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
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@
"default": "ocamlmerlin",
"description": "The path to the `ocamlmerlin` binary."
},
"reason.path.ocamlmerlin-lsp": {
"type": "string",
"default": null,
"description": "The path to the `ocamlmerlin-lsp` binary."
},
"reason.path.ocpindent": {
"type": "string",
"default": "ocp-indent",
Expand Down
134 changes: 134 additions & 0 deletions src/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import * as fs from "fs";
import flatMap = require("lodash.flatmap");
import * as path from "path";
import { promisify } from "util";
import * as vscode from "vscode";
import * as client from "vscode-languageclient";
import * as command from "./command";
import * as request from "./request";

const exists = promisify(fs.exists);
const readFile = promisify(fs.readFile);

const isWin = process.platform === "win32";

class ClientWindow implements vscode.Disposable {
public readonly merlin: vscode.StatusBarItem;
constructor() {
Expand All @@ -29,6 +36,133 @@ class ErrorHandler {
}

export async function launch(context: vscode.ExtensionContext): Promise<void> {
if (await isEsyProject()) {
return launchMerlinLsp(context, true);
} else {
return launchOCamlLanguageServer(context);
}
}

async function isEsyProject() {
const reasonConfig = vscode.workspace.getConfiguration("reason");
const forceEsy = reasonConfig.get<boolean>("forceEsy", false);
if (forceEsy) {
return true;
}

// TODO: we need to use workspace.workspaceFolders here and run LSP server per
// root. For now we'll just run LSP per workspace.
const root = vscode.workspace.rootPath;
if (root == null) {
return false;
}

const esyJson = path.join(root, "esy.json");
const packageJson = path.join(root, "package.json");
if (await exists(esyJson)) {
return true;
} else if (await exists(packageJson)) {
// package.json could be unrelated to esy, check if it has "esy" config
// then.
try {
const data = await readFile(packageJson, "utf8");
const json = JSON.parse(data);
return json.esy != null;
} catch (_e) {
return false;
}
}

return false;
}

function getMerlinLspOptions(useEsy: boolean) {
const reasonConfig = vscode.workspace.getConfiguration("reason");
let ocamlmerlinLsp = reasonConfig.get<string | null>("path.ocamlmerlin-lsp", null);
if (ocamlmerlinLsp == null) {
ocamlmerlinLsp = isWin ? "ocamlmerlin-lsp.exe" : "ocamlmerlin-lsp";
}

let run;
if (useEsy) {
run = {
args: ["exec-command", "--include-current-env", ocamlmerlinLsp],
command: process.platform === "win32" ? "esy.cmd" : "esy",
};
} else {
run = {
args: [],
command: ocamlmerlinLsp,
};
}

const serverOptions: client.ServerOptions = {
debug: {
...run,
options: {
env: {
...process.env,
MERLIN_LOG: "-",
OCAMLRUNPARAM: "b",
},
},
},
run: {
...run,
options: {
env: {
...process.env,
MERLIN_LOG: "-",
OCAMLRUNPARAM: "b",
},
},
},
};
return serverOptions;
}

export async function launchMerlinLsp(context: vscode.ExtensionContext, useEsy: boolean): Promise<void> {
const serverOptions = getMerlinLspOptions(useEsy);
const reasonConfig = vscode.workspace.getConfiguration("reason");

const languages = reasonConfig.get<string[]>("server.languages", ["ocaml", "reason"]);
const documentSelector = flatMap(languages, (language: string) => [
{ language, scheme: "file" },
{ language, scheme: "untitled" },
]);
const clientOptions: client.LanguageClientOptions = {
diagnosticCollectionName: "ocamlmerlin-lsp",
documentSelector,
errorHandler: new ErrorHandler(),
initializationOptions: reasonConfig,
outputChannelName: "Merlin Language Server",
stdioEncoding: "utf8",
synchronize: {
configurationSection: "reason",
fileEvents: [
vscode.workspace.createFileSystemWatcher("**/.merlin"),
vscode.workspace.createFileSystemWatcher("**/*.ml"),
vscode.workspace.createFileSystemWatcher("**/*.re"),
vscode.workspace.createFileSystemWatcher("**/command-exec"),
vscode.workspace.createFileSystemWatcher("**/command-exec.bat"),
vscode.workspace.createFileSystemWatcher("**/_build"),
vscode.workspace.createFileSystemWatcher("**/_build/*"),
],
},
};
const languageClient = new client.LanguageClient("Reason", serverOptions, clientOptions);
const window = new ClientWindow();
const session = languageClient.start();
context.subscriptions.push(window);
context.subscriptions.push(session);
await languageClient.onReady();
command.registerAll(context, languageClient);
request.registerAll(context, languageClient);
window.merlin.text = "$(hubot) [merlin]";
window.merlin.tooltip = "merlin server online";
}

export async function launchOCamlLanguageServer(context: vscode.ExtensionContext): Promise<void> {
const reasonConfig = vscode.workspace.getConfiguration("reason");
const module = context.asAbsolutePath(path.join("node_modules", "ocaml-language-server", "bin", "server"));
const options = { execArgv: ["--nolazy", "--inspect=6009"] };
Expand Down