From 0e03ed2d295e2446d1418799d819589056fb07df Mon Sep 17 00:00:00 2001 From: Wojtek Czekalski Date: Tue, 31 Dec 2019 08:24:35 +0100 Subject: [PATCH] Add support for multi workspace workflow --- .vscode/launch.json | 16 +++++++-- bsconfig.json | 3 -- src/Extension.bs.js | 18 +++++------ src/Extension.re | 40 ++++++++++++----------- src/LSP.bs.js | 77 ++++++++++++++++++++++++++++++++++++++++++-- src/LSP.re | 77 ++++++++++++++++++++++++++++++++++++++++++++ src/bindings/Node.re | 2 +- 7 files changed, 197 insertions(+), 36 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index dec9fad..00bd731 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,6 +11,18 @@ "--extensionTestsPath=${workspaceFolder}/test/suite/index.js" ], "outFiles": ["${workspaceFolder}/out/test/**/*.js"] - } + }, + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": [ + "${workspaceFolder}/src/**/*.bs.js" + ] + } ] -} \ No newline at end of file +} diff --git a/bsconfig.json b/bsconfig.json index dd850d3..d1edc04 100644 --- a/bsconfig.json +++ b/bsconfig.json @@ -5,9 +5,6 @@ "react-jsx": 3 }, "sources": [ - { - "dir": "vendor" - }, { "subdirs": true, "dir": "src" diff --git a/src/Extension.bs.js b/src/Extension.bs.js index 574ba5c..c7affb4 100644 --- a/src/Extension.bs.js +++ b/src/Extension.bs.js @@ -3,21 +3,21 @@ var LSP = require("./LSP.bs.js"); var $$Node = require("./bindings/Node.bs.js"); -var Vscode = require("vscode"); +var Block = require("bs-platform/lib/js/block.js"); var VscodeLanguageclient = require("vscode-languageclient"); function createClient(id, name, folder) { - return LSP.Server.make(folder).then((function (serverOptions) { - return Promise.resolve(new VscodeLanguageclient.LanguageClient(id, name, serverOptions, LSP.Client.make(/* () */0))); + return LSP.Server.make(folder.uri.fsPath).then((function (serverOptions) { + return Promise.resolve(/* Ok */Block.__(0, [new VscodeLanguageclient.LanguageClient(id, name, serverOptions, LSP.Client.make(/* () */0))])); })); } -function activate(_context) { - return createClient("merlin-language-server", "Merlin Language Server", Vscode.workspace.rootPath).then((function (client) { - return Promise.resolve(client.start()); - })).catch((function (e) { - var message = $$Node.$$Error.ofPromiseError(e); - return Vscode.window.showErrorMessage("Error: " + (String(message) + "")); +function activate(context) { + return LSP.MultiWorkspace.start(context, /* array */[], (function (_document, folder) { + return createClient("merlin-language-server", "Merlin Language Server", folder).catch((function (e) { + var message = $$Node.$$Error.ofPromiseError(e); + return Promise.resolve(/* Error */Block.__(1, [message])); + })); })); } diff --git a/src/Extension.re b/src/Extension.re index a6fa491..6cff366 100644 --- a/src/Extension.re +++ b/src/Extension.re @@ -3,29 +3,33 @@ open LSP; let createClient = (~id, ~name, ~folder) => Js.Promise.( - Server.make(folder) + Server.make(folder.Folder.uri.fsPath) |> then_(serverOptions => { - LanguageClient.make( - ~id, - ~name, - ~serverOptions, - ~clientOptions=Client.make(), + Ok( + LanguageClient.make( + ~id, + ~name, + ~serverOptions, + ~clientOptions=Client.make(), + ), ) |> resolve }) ); -let activate = _context => { - Js.Promise.( - createClient( - ~id="merlin-language-server", - ~name="Merlin Language Server", - ~folder=Workspace.rootPath, +let activate = context => { + MultiWorkspace.start( + ~context, ~commands=[||], ~createClient=(_document, folder) => { + Js.Promise.( + createClient( + ~id="merlin-language-server", + ~name="Merlin Language Server", + ~folder, + ) + |> catch(e => { + let message = Bindings.Error.ofPromiseError(e); + Js.Promise.resolve(Error(message)); + }) ) - |> then_((client: LanguageClient.t) => client.start(.) |> resolve) - |> catch(e => { - let message = Bindings.Error.ofPromiseError(e); - Window.showErrorMessage({j|Error: $message|j}); - }) - ); + }); }; diff --git a/src/LSP.bs.js b/src/LSP.bs.js index e108c94..23b6867 100644 --- a/src/LSP.bs.js +++ b/src/LSP.bs.js @@ -5,8 +5,11 @@ var Esy = require("./Esy.bs.js"); var $$Node = require("./bindings/Node.bs.js"); var Path = require("path"); var Block = require("bs-platform/lib/js/block.js"); +var Curry = require("bs-platform/lib/js/curry.js"); var Utils = require("./Utils.bs.js"); -var Vscode = require("vscode"); +var Vscode = require("./bindings/Vscode.bs.js"); +var Vscode$1 = require("vscode"); +var Belt_HashMapString = require("bs-platform/lib/js/belt_HashMapString.js"); var Caml_builtin_exceptions = require("bs-platform/lib/js/caml_builtin_exceptions.js"); function detect(folder) { @@ -69,7 +72,7 @@ function make(folder) { setupPromise = typeof projectType === "number" ? Promise.resolve(/* () */0) : ( projectType.tag ? ( projectType[/* readyForDev */0] ? Promise.resolve(/* () */0) : Esy.setup(Path.join(folder, "package.json")).then((function (param) { - return Vscode.window.withProgress({ + return Vscode$1.window.withProgress({ location: 15, title: "Setting up toolchain..." }, (function (progress) { @@ -85,7 +88,7 @@ function make(folder) { })); })) ) : ( - projectType[/* readyForDev */0] ? Promise.resolve(/* () */0) : Vscode.window.withProgress({ + projectType[/* readyForDev */0] ? Promise.resolve(/* () */0) : Vscode$1.window.withProgress({ location: 15, title: "Setting up toolchain..." }, (function (progress) { @@ -176,8 +179,76 @@ var Client = { var LanguageClient = { }; +function start(context, commands, createClient) { + var workspaceFolders = Belt_HashMapString.make(1); + var startClient = function ($$document) { + var uri = $$document.uri; + if (uri.scheme === "file") { + var folder = Vscode$1.workspace.getWorkspaceFolder(uri); + if (folder !== undefined) { + var folder$1 = folder; + if (Belt_HashMapString.has(workspaceFolders, folder$1.uri.fsPath)) { + return Promise.resolve(/* () */0); + } else { + return Curry._2(createClient, $$document, folder$1).then((function (client) { + if (client.tag) { + return Vscode$1.window.showErrorMessage("Error: " + (String(client[0]) + "")); + } else { + var client$1 = client[0]; + Belt_HashMapString.set(workspaceFolders, Vscode.Folder.key(folder$1), client$1); + return Promise.resolve(client$1.start()); + } + })); + } + } else { + return Promise.resolve(/* () */0); + } + } else { + return Promise.resolve(/* () */0); + } + }; + Vscode$1.workspace.onDidOpenTextDocument((function ($$document) { + startClient($$document); + return /* () */0; + })); + var openTasks = Promise.all(Vscode$1.workspace.textDocuments.map(startClient)).then((function (param) { + return Promise.resolve(/* () */0); + })); + Vscode$1.workspace.onDidChangeWorkspaceFolders((function ($$event) { + $$event.removed.forEach((function (folder) { + var match = Belt_HashMapString.get(workspaceFolders, Vscode.Folder.key(folder)); + if (match !== undefined) { + Belt_HashMapString.remove(workspaceFolders, Vscode.Folder.key(folder)); + return match.stop(); + } else { + return /* () */0; + } + })); + return /* () */0; + })); + commands.forEach((function (param) { + Vscode$1.commands.registerCommand(param[0], param[1]); + return /* () */0; + })); + context.subscriptions.push({ + dispose: (function () { + Belt_HashMapString.forEach(workspaceFolders, (function (param, client) { + return client.stop(); + })); + return Belt_HashMapString.clear(workspaceFolders); + }) + }); + return openTasks; +} + +var MultiWorkspace = { + FoldersMap: /* alias */0, + start: start +}; + exports.ProjectType = ProjectType; exports.Server = Server; exports.Client = Client; exports.LanguageClient = LanguageClient; +exports.MultiWorkspace = MultiWorkspace; /* Esy Not a pure module */ diff --git a/src/LSP.re b/src/LSP.re index b4d2a89..20e625b 100644 --- a/src/LSP.re +++ b/src/LSP.re @@ -255,3 +255,80 @@ module LanguageClient = { t = "LanguageClient"; }; + +module MultiWorkspace = { + module FoldersMap = Belt.HashMap.String; + let start = (~context, ~commands, ~createClient): Js.Promise.t(unit) => { + let workspaceFolders = FoldersMap.make(~hintSize=1); + let startClient = (document: TextDocument.event) => { + let uri = document.uri; + if (uri.scheme === "file") { + let folder = Workspace.getWorkspaceFolder(uri); + switch (folder) { + | Some(folder) + when FoldersMap.has(workspaceFolders, folder.uri.fsPath) => + Js.Promise.resolve() + | Some(folder) => + createClient(document, folder) + |> Js.Promise.then_(client => + switch (client) { + | Ok(client) => + FoldersMap.set( + workspaceFolders, + Folder.key(folder), + client, + ); + client.LanguageClient.start(.) |> Js.Promise.resolve; + | Error(message) => + Window.showErrorMessage({j|Error: $message|j}) + } + ) + | None => Js.Promise.resolve() + }; + } else { + Js.Promise.resolve(); + }; + }; + Workspace.onDidOpenTextDocument(document => + startClient(document) |> ignore + ); + let openTasks = + Workspace.textDocuments + |> Js.Array.map(startClient) + |> Js.Promise.all + |> Js.Promise.then_(_ => Js.Promise.resolve()); + Workspace.onDidChangeWorkspaceFolders(event => { + event.removed + |> Js.Array.forEach(folder => { + switch (FoldersMap.get(workspaceFolders, Folder.key(folder))) { + | Some(client) => + FoldersMap.remove(workspaceFolders, Folder.key(folder)); + client.LanguageClient.stop(.); + | None => () + } + }) + }); + + commands + |> Js.Array.forEach(((cmd, handler)) => { + Commands.register(~command=cmd, ~handler) + }); + + ExtensionContext.( + Js.Array.push( + { + dispose: + (.) => { + FoldersMap.forEach(workspaceFolders, (_, client) => { + client.LanguageClient.stop(.) + }); + FoldersMap.clear(workspaceFolders); + }, + }, + context.subscriptions, + ) + ) + |> ignore; + openTasks; + }; +}; diff --git a/src/bindings/Node.re b/src/bindings/Node.re index cfd0437..9be6074 100644 --- a/src/bindings/Node.re +++ b/src/bindings/Node.re @@ -8,7 +8,7 @@ external processEnv: Js.Dict.t(string) = "env"; module Error = { type t; [@bs.new] external make: string => t = "Error"; - let ofPromiseError = [%raw + let ofPromiseError: Js.Promise.error => string = [%raw error => "return error.message || 'Unknown error'" ]; };