diff --git a/package.json b/package.json
index 950827877..8f7972c98 100644
--- a/package.json
+++ b/package.json
@@ -131,6 +131,11 @@
"type": "boolean",
"default": false,
"description": "Automatically start ReScript's code analysis."
+ },
+ "rescript.settings.binaryPath": {
+ "type": ["string", "null"],
+ "default": null,
+ "description": "Path to the directory where ReScript binaries are. You can use it if you haven't or don't want to use the installed ReScript from node_modules in your project."
}
}
},
diff --git a/server/src/constants.ts b/server/src/constants.ts
index 223a00f7b..f6e34e327 100644
--- a/server/src/constants.ts
+++ b/server/src/constants.ts
@@ -32,13 +32,20 @@ export let analysisProdPath = path.join(
"rescript-editor-analysis.exe"
);
+export let rescriptBinName = "rescript";
+
+export let bsbBinName = "bsb";
+
+export let bscBinName = "bsc";
+
+export let nodeModulesBinDir = path.join("node_modules", ".bin");
+
// can't use the native bsb/rescript since we might need the watcher -w flag, which is only in the JS wrapper
export let rescriptNodePartialPath = path.join(
- "node_modules",
- ".bin",
- "rescript"
+ nodeModulesBinDir,
+ rescriptBinName
);
-export let bsbNodePartialPath = path.join("node_modules", ".bin", "bsb");
+export let bsbNodePartialPath = path.join(nodeModulesBinDir, bsbBinName);
export let bsbLock = ".bsb.lock";
export let bsconfigPartialPath = "bsconfig.json";
diff --git a/server/src/server.ts b/server/src/server.ts
index 855a7d84a..5f6d9ecf3 100644
--- a/server/src/server.ts
+++ b/server/src/server.ts
@@ -4,6 +4,7 @@ import * as v from "vscode-languageserver";
import * as rpc from "vscode-jsonrpc/node";
import * as path from "path";
import fs from "fs";
+import os from "os";
// TODO: check DidChangeWatchedFilesNotification.
import {
DidOpenTextDocumentNotification,
@@ -24,9 +25,11 @@ import { filesDiagnostics } from "./utils";
interface extensionConfiguration {
askToStartBuild: boolean;
+ binaryPath: string | null;
}
let extensionConfiguration: extensionConfiguration = {
askToStartBuild: true,
+ binaryPath: null,
};
let pullConfigurationPeriodically: NodeJS.Timeout | null = null;
@@ -62,6 +65,21 @@ let codeActionsFromDiagnostics: codeActions.filesCodeActions = {};
// will be properly defined later depending on the mode (stdio/node-rpc)
let send: (msg: p.Message) => void = (_) => {};
+let findBuildBinary = (projectRootPath: p.DocumentUri) =>
+ extensionConfiguration.binaryPath === null
+ ? utils.findBuildBinaryFromProjectRoot(projectRootPath)
+ : utils.findBuildBinaryFromConfig(extensionConfiguration.binaryPath);
+
+let findBscBinary = (filePath: p.DocumentUri) =>
+ extensionConfiguration.binaryPath === null
+ ? utils.findBscBinaryFromProjectRoot(filePath)
+ : utils.findBscBinaryFromConfig(extensionConfiguration.binaryPath);
+
+let getConjecturalDirOfBuildBinary = (projectRootPath: p.DocumentUri) =>
+ extensionConfiguration.binaryPath === null
+ ? path.join(projectRootPath, c.nodeModulesBinDir)
+ : extensionConfiguration.binaryPath;
+
interface CreateInterfaceRequestParams {
uri: string;
}
@@ -232,7 +250,7 @@ let openedFile = (fileUri: string, fileContent: string) => {
// TODO: sometime stale .bsb.lock dangling. bsb -w knows .bsb.lock is
// stale. Use that logic
// TODO: close watcher when lang-server shuts down
- if (utils.findNodeBuildOfProjectRoot(projectRootPath) != null) {
+ if (findBuildBinary(projectRootPath) != null) {
let payload: clientSentBuildAction = {
title: c.startBuildAction,
projectRootPath: projectRootPath,
@@ -254,7 +272,17 @@ let openedFile = (fileUri: string, fileContent: string) => {
// handle in the isResponseMessage check in the message handling way
// below
} else {
- // we should send something to say that we can't find bsb.exe. But right now we'll silently not do anything
+ let request: p.NotificationMessage = {
+ jsonrpc: c.jsonrpcVersion,
+ method: "window/showMessage",
+ params: {
+ type: p.MessageType.Error,
+ message: `Can't find ReScript binary in the directory ${getConjecturalDirOfBuildBinary(
+ projectRootPath
+ )}`,
+ },
+ };
+ send(request);
}
}
@@ -593,7 +621,8 @@ function format(msg: p.RequestMessage): Array
{
} else {
// code will always be defined here, even though technically it can be undefined
let code = getOpenedFileContent(params.textDocument.uri);
- let formattedResult = utils.formatCode(filePath, code);
+ let bscBinaryPath = findBscBinary(filePath);
+ let formattedResult = utils.formatCode(bscBinaryPath, filePath, code);
if (formattedResult.kind === "success") {
let max = code.length;
let result: p.TextEdit[] = [
@@ -933,6 +962,17 @@ function onMessage(msg: p.Message) {
if (initialConfiguration != null) {
extensionConfiguration = initialConfiguration;
+ if (
+ extensionConfiguration.binaryPath !== null &&
+ extensionConfiguration.binaryPath[0] === "~"
+ ) {
+ // What should happen if the path contains the home directory symbol?
+ // This situation is handled below, but maybe it isn't the best option.
+ extensionConfiguration.binaryPath = path.join(
+ os.homedir(),
+ extensionConfiguration.binaryPath.slice(1)
+ );
+ }
}
send(response);
@@ -1041,7 +1081,7 @@ function onMessage(msg: p.Message) {
// TODO: close watcher when lang-server shuts down. However, by Node's
// default, these subprocesses are automatically killed when this
// language-server process exits
- let found = utils.findNodeBuildOfProjectRoot(projectRootPath);
+ let found = findBuildBinary(projectRootPath);
if (found != null) {
let bsbProcess = utils.runBuildWatcherUsingValidBuildPath(
found.buildPath,
diff --git a/server/src/utils.ts b/server/src/utils.ts
index 0e814e470..344c37879 100644
--- a/server/src/utils.ts
+++ b/server/src/utils.ts
@@ -48,7 +48,7 @@ export let findProjectRootOfFile = (
// Also, if someone's ever formatting a regular project setup's dependency
// (which is weird but whatever), they'll at least find an upward bs-platform
// from the dependent.
-export let findBscNativeOfFile = (
+export let findBscBinaryFromProjectRoot = (
source: p.DocumentUri
): null | p.DocumentUri => {
let dir = path.dirname(source);
@@ -66,25 +66,50 @@ export let findBscNativeOfFile = (
// reached the top
return null;
} else {
- return findBscNativeOfFile(dir);
+ return findBscBinaryFromProjectRoot(dir);
}
};
+export let findBscBinaryFromConfig = (
+ pathToBinaryDirFromConfig: p.DocumentUri
+): null | p.DocumentUri => {
+ let bscPath = path.join(pathToBinaryDirFromConfig, c.bscBinName);
+ if (fs.existsSync(bscPath)) {
+ return bscPath;
+ }
+ return null;
+};
+
+// The function is to check that the build binary file exists
+// and also determine what exactly the user is using ReScript or BuckleScript.
// TODO: this doesn't handle file:/// scheme
-export let findNodeBuildOfProjectRoot = (
- projectRootPath: p.DocumentUri
-): null | { buildPath: p.DocumentUri; isReScript: boolean } => {
- let rescriptNodePath = path.join(projectRootPath, c.rescriptNodePartialPath);
- let bsbNodePath = path.join(projectRootPath, c.bsbNodePartialPath);
-
- if (fs.existsSync(rescriptNodePath)) {
- return { buildPath: rescriptNodePath, isReScript: true };
- } else if (fs.existsSync(bsbNodePath)) {
- return { buildPath: bsbNodePath, isReScript: false };
+let findBuildBinaryBase = ({
+ rescriptPath,
+ bsbPath,
+}: {
+ rescriptPath: p.DocumentUri;
+ bsbPath: p.DocumentUri;
+}): null | { buildPath: p.DocumentUri; isReScript: boolean } => {
+ if (fs.existsSync(rescriptPath)) {
+ return { buildPath: rescriptPath, isReScript: true };
+ } else if (fs.existsSync(bsbPath)) {
+ return { buildPath: bsbPath, isReScript: false };
}
return null;
};
+export let findBuildBinaryFromConfig = (pathToBinaryDirFromConfig: p.DocumentUri) =>
+ findBuildBinaryBase({
+ rescriptPath: path.join(pathToBinaryDirFromConfig, c.rescriptBinName),
+ bsbPath: path.join(pathToBinaryDirFromConfig, c.bsbBinName),
+ });
+
+export let findBuildBinaryFromProjectRoot = (projectRootPath: p.DocumentUri) =>
+ findBuildBinaryBase({
+ rescriptPath: path.join(projectRootPath, c.rescriptNodePartialPath),
+ bsbPath: path.join(projectRootPath, c.bsbNodePartialPath),
+ });
+
type execResult =
| {
kind: "success";
@@ -94,21 +119,22 @@ type execResult =
kind: "error";
error: string;
};
-export let formatCode = (filePath: string, code: string): execResult => {
+
+export let formatCode = (
+ bscPath: p.DocumentUri | null,
+ filePath: string,
+ code: string
+): execResult => {
let extension = path.extname(filePath);
let formatTempFileFullPath = createFileInTempDir(extension);
fs.writeFileSync(formatTempFileFullPath, code, {
encoding: "utf-8",
});
try {
- // See comment on findBscNativeDirOfFile for why we need
- // to recursively search for bsc.exe upward
- let bscNativePath = findBscNativeOfFile(filePath);
-
- // Default to using the project formatter. If not, use the one we ship with
- // the analysis binary in the extension itself.
- if (bscNativePath != null) {
- let result = childProcess.execFileSync(bscNativePath, [
+ // It will try to use the user formatting binary.
+ // If not, use the one we ship with the analysis binary in the extension itself.
+ if (bscPath != null) {
+ let result = childProcess.execFileSync(bscPath, [
"-color",
"never",
"-format",
@@ -589,7 +615,7 @@ export let parseCompilerLogOutput = (
code: undefined,
severity: t.DiagnosticSeverity.Error,
tag: undefined,
- content: [lines[i], lines[i+1]],
+ content: [lines[i], lines[i + 1]],
});
i++;
} else if (/^ +([0-9]+| +|\.) (│|┆)/.test(line)) {
@@ -603,9 +629,9 @@ export let parseCompilerLogOutput = (
// 10 ┆
} else if (line.startsWith(" ")) {
// part of the actual diagnostics message
- parsedDiagnostics[parsedDiagnostics.length - 1].content.push(
- line.slice(2)
- );
+ parsedDiagnostics[parsedDiagnostics.length - 1].content.push(
+ line.slice(2)
+ );
} else if (line.trim() != "") {
// We'll assume that everything else is also part of the diagnostics too.
// Most of these should have been indented 2 spaces; sadly, some of them