From 2ba20141d32d249dd4ced01073500e6b81810686 Mon Sep 17 00:00:00 2001 From: Mario Siegenthaler Date: Tue, 8 Nov 2016 16:01:53 +0100 Subject: [PATCH 1/6] Make plugin directory configurable so it works with the SDK. --- ensime.js | 13 ++++++++++--- worker/ensime_connector.js | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/ensime.js b/ensime.js index 2a55d31..1b88d2c 100644 --- a/ensime.js +++ b/ensime.js @@ -247,6 +247,7 @@ define(function(require, exports, module) { settings.on("read", function(e) { settings.setDefaults("project/ensime", [ ["ensimeFile", "/home/ubuntu/workspace/.ensime"], + ["pluginDir", "/home/ubuntu/.c9/plugins/c9.ide.language.scala"], ["sbt", "/usr/bin/sbt"], ["noExecAnalysis", true], ["node", "/home/ubuntu/.nvm/versions/node/v4.2.4/bin/node"] @@ -264,15 +265,20 @@ define(function(require, exports, module) { setting: "project/ensime/@ensimeFile", position: 100 }, + "Plugin Directory": { + type: "textbox", + setting: "project/ensime/@pluginDir", + position: 101 + }, "SBT Executable": { type: "textbox", setting: "project/ensime/@sbt", - position: 101 + position: 102 }, "Node Executable": { type: "textbox", setting: "project/ensime/@node", - position: 102 + position: 103 }, "Don't use execAnalysis": { type: "checkbox", @@ -299,7 +305,8 @@ define(function(require, exports, module) { ensimeFile: settings.get("project/ensime/@ensimeFile"), sbt: settings.get("project/ensime/@sbt"), node: settings.get("project/ensime/@node"), - noExecAnalysis: settings.get("project/ensime/@noExecAnalysis") + noExecAnalysis: settings.get("project/ensime/@noExecAnalysis"), + pluginDir: settings.get("project/ensime/@pluginDir") }); } settings.on("project/ensime", sendSettings.bind(null, handler), plugin); diff --git a/worker/ensime_connector.js b/worker/ensime_connector.js index 2989ef7..8c45a7c 100644 --- a/worker/ensime_connector.js +++ b/worker/ensime_connector.js @@ -46,7 +46,7 @@ define(function(require, exports, module) { dotEnsime = config.ensimeFile; sbt = config.sbt || sbt; node = config.node || node; - pluginDir = config.plugin || pluginDir; + pluginDir = config.pluginDir || pluginDir; node = config.node || node; noExecAnalysis = config.noExecAnalysis || noExecAnalysis; }); From 185d893930e1c0b51f26308bdb9febc57a743495 Mon Sep 17 00:00:00 2001 From: Mario Siegenthaler Date: Tue, 8 Nov 2016 16:02:22 +0100 Subject: [PATCH 2/6] Different reference to path (not sure why - might cause troubles). --- worker/scala_jumptodefinition.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker/scala_jumptodefinition.js b/worker/scala_jumptodefinition.js index 5c0ee1a..df32ffb 100644 --- a/worker/scala_jumptodefinition.js +++ b/worker/scala_jumptodefinition.js @@ -3,7 +3,7 @@ define(function(require, exports, module) { var baseHandler = require("plugins/c9.ide.language/base_handler"); var workerUtil = require("plugins/c9.ide.language/worker_util"); var util = require("./util"); - var path = require("path"); + var path = require("/static/lib/path"); var handler = module.exports = Object.create(baseHandler); var emitter; From 6ffad264c9de548a63bd6ffbb9e3567fff0afb3a Mon Sep 17 00:00:00 2001 From: Mario Siegenthaler Date: Wed, 2 Mar 2016 19:13:40 +0000 Subject: [PATCH 3/6] Publish 0.4.0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ee79f18..0d39dc8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "c9.ide.language.scala", "description": "plugin for Cloud9 providing the Scala language based on ENSIME", - "version": "0.3.0", + "version": "0.4.0", "author": "Mario Siegenthaler", "contributors": [ { From 87575ac000ef2620be46eef637dae2b00564cd70 Mon Sep 17 00:00:00 2001 From: Mario Siegenthaler Date: Tue, 8 Nov 2016 16:35:27 +0100 Subject: [PATCH 4/6] Use settings in tooltips so node and plugin get found. --- ensime.js | 12 +++++++++++- worker/ensime_connector.js | 2 +- worker/scala_tooltip.js | 6 ++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/ensime.js b/ensime.js index 1b88d2c..4cdecd2 100644 --- a/ensime.js +++ b/ensime.js @@ -301,7 +301,7 @@ define(function(require, exports, module) { ensimeConnector = handler; function sendSettings(handler) { - handler.emit("set_ensime_config", { + handler.emit("set_config", { ensimeFile: settings.get("project/ensime/@ensimeFile"), sbt: settings.get("project/ensime/@sbt"), node: settings.get("project/ensime/@node"), @@ -341,6 +341,16 @@ define(function(require, exports, module) { }); language.registerLanguageHandler("plugins/c9.ide.language.scala/worker/scala_tooltip", function(err, handler) { if (err) return console.error(err); + + function sendSettings(handler) { + handler.emit("set_config", { + node: settings.get("project/ensime/@node"), + pluginDir: settings.get("project/ensime/@pluginDir") + }); + } + settings.on("project/ensime", sendSettings.bind(null, handler), plugin); + sendSettings(handler); + setupConnectorBridge(handler); }); language.registerLanguageHandler("plugins/c9.ide.language.scala/worker/scala_jumptodefinition", function(err, handler) { diff --git a/worker/ensime_connector.js b/worker/ensime_connector.js index 8c45a7c..aa5fac1 100644 --- a/worker/ensime_connector.js +++ b/worker/ensime_connector.js @@ -42,7 +42,7 @@ define(function(require, exports, module) { console.log("Initializing ensime-connector..."); emitter = handler.getEmitter(); - emitter.on("set_ensime_config", function(config) { + emitter.on("set_config", function(config) { dotEnsime = config.ensimeFile; sbt = config.sbt || sbt; node = config.node || node; diff --git a/worker/scala_tooltip.js b/worker/scala_tooltip.js index b724b1c..b8b07f7 100644 --- a/worker/scala_tooltip.js +++ b/worker/scala_tooltip.js @@ -6,6 +6,7 @@ define(function(require, exports, module) { var formatting = require("./formatting"); var pluginDir = "/home/ubuntu/.c9/plugins/c9.ide.language.scala"; + var node = "/home/ubuntu/.nvm/versions/node/v4.1.1/bin/node"; var handler = module.exports = Object.create(baseHandler); var emitter; @@ -20,7 +21,8 @@ define(function(require, exports, module) { console.log("Scala tooltip initialized."); emitter.on("set_config", function(config) { - pluginDir = config.plugin || pluginDir; + pluginDir = config.pluginDir || pluginDir; + node = config.node || node; }); if (!handler.workspaceDir) { handler.workspaceDir = "/home/ubuntu/workspace"; @@ -35,7 +37,7 @@ define(function(require, exports, module) { function loadDocumentation(declPos, callback) { //TODO handle unsaved workspace files - workerUtil.execFile("node", { + workerUtil.execFile(node, { cwd: handler.workspaceDir, args: [ pluginDir + "/server/doc-fetcher.js", From b3411043b5921a50a9323bf7ce54085770b293b2 Mon Sep 17 00:00:00 2001 From: Mario Siegenthaler Date: Wed, 9 Nov 2016 11:45:19 +0100 Subject: [PATCH 5/6] Use websockets for everything. Add a second plugin that handles the communication with the ensime server instead of a worker. --- ensime-connector.js | 187 +++++++++++++++++++++++++++++++++ ensime.js | 98 ++++++------------ package.json | 3 + server/ensime-caller.js | 71 ------------- server/ensime-runner.js | 65 +++++++++--- worker/ensime_connector.js | 207 ------------------------------------- worker/util.js | 3 +- 7 files changed, 273 insertions(+), 361 deletions(-) create mode 100644 ensime-connector.js delete mode 100644 server/ensime-caller.js delete mode 100644 worker/ensime_connector.js diff --git a/ensime-connector.js b/ensime-connector.js new file mode 100644 index 0000000..1bca656 --- /dev/null +++ b/ensime-connector.js @@ -0,0 +1,187 @@ +define(function(require, exports, module) { + main.consumes = ["Plugin", "installer", "settings", "proc", "c9"]; + main.provides = ["ensime-connector"]; + return main; + + function main(options, imports, register) { + var Plugin = imports.Plugin; + var installer = imports.installer; + var settings = imports.settings; + var proc = imports.proc; + var c9 = imports.c9; + + // make sure all deps are installed + installer.createSession("c9.ide.language.scala", require("./install")); + + var plugin = new Plugin("Ensime", main.consumes); + var emit = plugin.getEmitter(); + + var ensimeProcess; + var last_call_id = 0; + var pendingCalls = {}; + + //settings + var node; + var sbt; + var ensimeFile; + var pluginDir; + + //TODO maybe move the respective settings here completely? (including UI) + function updateSettings() { + ensimeFile = settings.get("project/ensime/@ensimeFile"); + sbt = settings.get("project/ensime/@sbt"); + node = settings.get("project/ensime/@node"); + // noExecAnalysis = settings.get("project/ensime/@noExecAnalysis"); + pluginDir = settings.get("project/ensime/@pluginDir"); + } + + plugin.on("load", function() { + settings.on("project/ensime", updateSettings); + updateSettings(); + }); + plugin.on("unload", function() { + node = undefined; + sbt = undefined; + ensimeFile = undefined; + pluginDir = undefined; + ensimeProcess = undefined; + pendingCalls = {}; + }); + + function handleEvent(event) { + if (event.type === "started") { + console.log("ENSIME started."); + emit("started"); + } + else if (event.type == "callResponse") { + var call = pendingCalls[event.callId]; + delete pendingCalls[event.callId]; + if (call) call(event.error, event.response); + } + else { + console.debug("ENSIME-EVENT: " + JSON.stringify(event)); + emit("event", event); + } + } + + function start(attach) { + console.log("Will start ENSIME with attach=" + attach); + console.debug(`Running: ${node} ${pluginDir}/server/ensime-runner.js ${ensimeFile} ${sbt} ${attach}`); + proc.spawn(node, { + args: [pluginDir + "/server/ensime-runner.js", + ensimeFile, sbt, + attach.toString() + ], + cwd: c9.workspaceDir + }, function(err, process) { + if (err) return console.error(err); + ensimeProcess = process; + console.log("Waiting for ENSIME to start..."); + emit("starting"); + + var buffer = ""; + + function receivedData(chunk) { + buffer += chunk; + var delim = buffer.indexOf("|"); + if (delim == -1) return; + + var decoded = window.atob(buffer.substr(0, delim)); + buffer = buffer.substr(delim + 1); + var event = JSON.parse(decoded); + handleEvent(event); + receivedData(""); + } + + process.stdout.on("data", receivedData); + process.stderr.on("data", function(chunk) { + console.error(chunk); + }); + process.stdout.on("error", function(error) { + console.error(error); + }); + process.stderr.on("error", function(error) { + console.error(error); + }); + process.on("exit", function(code) { + emit("stopped", code); + ensimeProcess = undefined; + console.log("Ensime server stopped (code " + code + ")"); + }); + }); + } + + function stop() { + console.log("Will stop ENSIME"); + if (ensimeProcess) + ensimeProcess.kill(); + ensimeProcess = undefined; + emit("stopped", -1); + } + + function update(callback) { + var calledBack = false; + + console.log("Will update ENSIME"); + proc.spawn(node, { + args: [pluginDir + "/server/ensime-update.js", + ensimeFile, sbt + ], + cwd: c9.workspaceDir + }, function(err, process) { + if (err) return console.error(err); + ensimeProcess = undefined; + console.log("Waiting for ENSIME to update..."); + emit("stopped"); + emit("updating"); + + process.stdout.on("data", function(chunk) { + //TODO chunk to message mapping might not always be 1:1 + console.debug("ENSIME-EVENT: " + chunk); + if (calledBack) return; + var event = JSON.parse(chunk); + if (event.type === "updated") { + calledBack = true; + return callback(); + } + }); + process.stderr.on("data", function(chunk) { + console.log("log", chunk); + }); + process.on("exit", function(code) { + if (calledBack) return; + if (code != 0) callback(`Update failed with code ${code}`); + else callback(); + }); + }); + } + + function call(request, callback) { + if (!ensimeProcess) return callback("ENSIME not running"); + if (!request) return callback("No request"); + + request.callId = last_call_id++; + ensimeProcess.stdin.write(window.btoa(JSON.stringify(request)) + "|"); + + pendingCalls[request.callId] = callback; + } + + plugin.freezePublicAPI({ + start: start, + stop: stop, + update: update, + call: call, + + _events: [ + "starting", + "started", + "stopped", + "updating", + "event" + ] + }); + register(null, { + "ensime-connector": plugin + }); + } +}); \ No newline at end of file diff --git a/ensime.js b/ensime.js index 4cdecd2..73aa4a8 100644 --- a/ensime.js +++ b/ensime.js @@ -3,7 +3,7 @@ define(function(require, exports, module) { "Plugin", "language", "ui", "commands", "menus", "preferences", "settings", "notification.bubble", "installer", "save", "Editor", "editors", "tabManager", "Datagrid", "format", - "language.complete", "fs", "c9", "run" + "language.complete", "fs", "c9", "run", "ensime-connector" ]; main.provides = ["ensime"]; return main; @@ -26,6 +26,7 @@ define(function(require, exports, module) { var fs = imports.fs; var c9 = imports.c9; var run = imports.run; + var ensimeConnector = imports["ensime-connector"]; var jsdiff = require("./lib/diff.js"); var path = require("path"); @@ -34,9 +35,6 @@ define(function(require, exports, module) { var ensimeRunning = false; var ensimeReady = false; - var ensimeConnector; - var call_id_prefix = "plugin"; - var last_call_id = 0; // make sure all deps are installed installer.createSession("c9.ide.language.scala", require("./install")); @@ -64,7 +62,7 @@ define(function(require, exports, module) { return !ensimeRunning; }, exec: function() { - startEnsime(false); + startEnsime(true); } }, plugin); commands.addCommand({ @@ -295,26 +293,7 @@ define(function(require, exports, module) { plugin.on("load", function() { loadSettings(); - language.registerLanguageHandler("plugins/c9.ide.language.scala/worker/ensime_connector", function(err, handler) { - if (err) return console.error(err); - console.log("ensime-connector initialized."); - ensimeConnector = handler; - - function sendSettings(handler) { - handler.emit("set_config", { - ensimeFile: settings.get("project/ensime/@ensimeFile"), - sbt: settings.get("project/ensime/@sbt"), - node: settings.get("project/ensime/@node"), - noExecAnalysis: settings.get("project/ensime/@noExecAnalysis"), - pluginDir: settings.get("project/ensime/@pluginDir") - }); - } - settings.on("project/ensime", sendSettings.bind(null, handler), plugin); - sendSettings(handler); - registerEnsimeHandlers(handler); - emit("connector.ready", handler); - }); language.registerLanguageHandler("plugins/c9.ide.language.scala/worker/scala_completer", function(err, handler) { if (err) return console.error(err); setupConnectorBridge(handler); @@ -392,13 +371,16 @@ define(function(require, exports, module) { }); }); + connectToEnsime(); + save.on("afterSave", function(event) { emit("afterSave", event.path); }); + + startEnsime(true); }); plugin.on("unload", function() { - ensimeConnector = null; ensimeRunning = false; ensimeReady = false; language.unregisterLanguageHandler("plugins/c9.ide.language.scala/worker/scala_refactor"); @@ -408,22 +390,15 @@ define(function(require, exports, module) { language.unregisterLanguageHandler("plugins/c9.ide.language.scala/worker/scala_completer"); language.unregisterLanguageHandler("plugins/c9.ide.language.scala/worker/scala_outline"); language.unregisterLanguageHandler("plugins/c9.ide.language.scala/worker/scala_markers"); - language.unregisterLanguageHandler("plugins/c9.ide.language.scala/worker/ensime_connector"); - }); - plugin.on("connector.ready", function() { - startEnsime(true); }); - function registerEnsimeHandlers(handler) { - handler.on("log", function(data) { - console.log("ENSIME: " + data); - }); - handler.on("starting", function() { + function connectToEnsime() { + ensimeConnector.on("starting", function() { ensimeRunning = true; ensimeReady = false; bubble.popup("ENSIME is starting..."); }); - handler.on("started", function() { + ensimeConnector.on("started", function() { ensimeRunning = true; ensimeReady = true; bubble.popup("ENSIME started."); @@ -431,25 +406,22 @@ define(function(require, exports, module) { if (err) return bubble.popup("Typecheck not successful"); }); }); - handler.on("stopped", function(code) { + ensimeConnector.on("stopped", function(code) { ensimeRunning = false; ensimeReady = false; bubble.popup("ENSIME stopped."); }); - handler.on("updated", function() { - bubble.popup("ENSIME was updated."); - }); - handler.on("updateFailed", function(error) { - bubble.popup("ENSIME could not be updated: " + error); - }); } function setupConnectorBridge(handler) { - handler.on("call", function(event) { - ensimeConnector.emit("call", event); - }); - ensimeConnector.on("call.result", function(event) { - handler.emit("call.result", event); + handler.on("call", function(req) { + ensimeConnector.call(req.request, function(err, resp){ + handler.emit("call.result", { + id: req.id, + err: err, + result: resp + }); + }); }); ensimeConnector.on("event", function(event) { handler.emit("event", event); @@ -562,34 +534,28 @@ define(function(require, exports, module) { /** Ensime-server handling */ function startEnsime(attach) { - if (!ensimeConnector) return console.error("ensime-connector not started."); if (ensimeRunning) return; - ensimeConnector.emit("start", attach); + ensimeConnector.start(true); } function stopEnsime() { - if (!ensimeConnector) return console.error("ensime-connector not started."); if (!ensimeRunning) return; - ensimeConnector.emit("stop"); + ensimeConnector.stop(); } function updateEnsime() { - if (!ensimeConnector) return console.error("ensime-connector not started."); - ensimeConnector.emit("update"); + ensimeConnector.update(function(err) { + if (err) { + bubble.popup("ENSIME could not be updated: " + err); + } + else { + bubble.popup("ENSIME was updated."); + } + }); } function executeEnsime(req, callback) { - if (!ensimeConnector) return callback("ensime-connector not started."); - var reqId = call_id_prefix + (last_call_id++); - ensimeConnector.on("call.result", function hdlr(event) { - if (event.id !== reqId) return; - plugin.off("call.result", hdlr); - callback(event.error, event.result); - }); - ensimeConnector.emit("call", { - id: reqId, - request: req, - }); + ensimeConnector.call(req, callback); } /** Ensime commands. */ @@ -622,10 +588,6 @@ define(function(require, exports, module) { }); } - /** - * This is an example of an implementation of a plugin. - * @singleton - */ plugin.freezePublicAPI({}); register(null, { diff --git a/package.json b/package.json index 0d39dc8..ab92c9e 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,9 @@ "plugins": { "ensime": { "packagePath": "plugins/c9.ide.language.scala/ensime" + }, + "ensime-connector": { + "packagePath": "plugins/c9.ide.language.scala/ensime-connector" } }, "engines": { diff --git a/server/ensime-caller.js b/server/ensime-caller.js deleted file mode 100644 index 40e421d..0000000 --- a/server/ensime-caller.js +++ /dev/null @@ -1,71 +0,0 @@ -var http = require("http"); -var fs = require("fs"); - -if (process.argv.length < 3) { - console.error("Missing argument 1: ENSIME port."); - return process.exit(3); -} -if (process.argv.length < 4) { - console.error("Missing argument 2: JSON."); - return process.exit(3); -} -var port = process.argv[2]; -var inData = process.argv[3]; -var data = JSON.parse(inData); - -if (data.fileInfo && data.fileInfo.currentContents) { - delete data.fileInfo.currentContents; - - var chunks = []; - process.stdin.on('data', function(chunk) { - chunks.push(chunk); - }); - process.stdin.on('end', function() { - var body = Buffer.concat(chunks); - data.fileInfo.contents = body.toString("utf-8"); - callEnsime(data); - }); -} -else - callEnsime(data); - -function callEnsime(data) { - var postData = new Buffer(JSON.stringify(data)); - - var req = http.request({ - hostname: "localhost", - port: port, - path: "/rpc", - method: "POST", - headers: { - "Content-Type": "application/json", - "Content-Length": postData.length - } - }, function(response) { - response.setEncoding("utf-8"); - if (response.statusCode == 200) { - response.on("data", function(chunk) { - process.stdout.write(chunk); - }); - response.on("end", function() { - process.exit(0); - }); - } - else { - console.error(`Request failed with status code ${response.statusCode}`); - response.on("data", function(chunk) { - console.error(chunk); - }); - response.on("end", function() { - process.exit(1); - }); - } - }); - req.on("error", function(err) { - console.error("Call to ensime failed"); - console.error(err); - process.exit(2); - }); - req.write(postData); - req.end(); -} \ No newline at end of file diff --git a/server/ensime-runner.js b/server/ensime-runner.js index 4f75345..2fc70e9 100644 --- a/server/ensime-runner.js +++ b/server/ensime-runner.js @@ -31,6 +31,7 @@ var ec = new Controller(dotEnsime, "/tmp/ensime", { }); process.stdout.setEncoding("ascii"); +process.stdin.setEncoding("ascii"); ec.handleGeneral = function(msg) { var string = JSON.stringify(msg); @@ -38,32 +39,70 @@ ec.handleGeneral = function(msg) { process.stdout.write(buffer.toString("base64") + "|"); }; -function connect() { +function start() { ec.connect(output, function(err, res) { if (err) return console.error(err); - ec.handleGeneral({ - type: "started", - port: res.ports.http - }); + connected(res); console.info("========= ENSIME is now running ============\n"); }); } -if (allowAttach) { - console.info("Checking for running instance..."); +function attach() { ec.attach(function(err, res) { if (err) { console.info("Attach failed, starting new instance."); - return connect(); // start if we cannot attach + return start(); // start if we cannot attach } console.info("Attach successful, gut a response: " + JSON.stringify(res)); - ec.handleGeneral({ - type: "started", - port: res.ports.http - }); + connected(res); console.info("========= Attached to running ENSIME ============\n"); }); } + +function connected(res) { + process.stdin.on("error", function(err) { + console.error("Error reading from stdin: " + err); + process.exit(4); + }); + process.stdin.on("data", receivedData); + + ec.handleGeneral({ + type: "started", + port: res.ports.http + }); +} + +var buffer = ""; + +function receivedData(chunk) { + buffer += chunk; + var delim = buffer.indexOf("|"); + if (delim == -1) return; + + var decoded = new Buffer(buffer.substr(0, delim), "base64").toString("ascii"); + buffer = buffer.substr(delim + 1); + var req = JSON.parse(decoded); + handleRequest(req); + receivedData(""); +} + +function handleRequest(req) { + ec.send(req, function(err, resp) { + var msg = { + type: "callResponse", + callId: req.callId + }; + if (err) msg.error = err; + else msg.response = resp; + ec.handleGeneral(msg); + }); +} + + +if (allowAttach) { + console.info("Checking for running instance..."); + attach(); +} else { - connect(); + start(); } \ No newline at end of file diff --git a/worker/ensime_connector.js b/worker/ensime_connector.js deleted file mode 100644 index aa5fac1..0000000 --- a/worker/ensime_connector.js +++ /dev/null @@ -1,207 +0,0 @@ -/** - * Manages the ENSIME process and passes message to/from ENSIME. - * Incoming: - * - set_ensime_config - * - start - * - stop - * - update - * - call - * - * Outgoing: - * - starting - * - started - * - stopped - * - updated - * - updateFailed - * - log - * - event - * - call.result - */ -define(function(require, exports, module) { - - var baseHandler = require("plugins/c9.ide.language/base_handler"); - var workerUtil = require("plugins/c9.ide.language/worker_util"); - - var dotEnsime; - var node = "/home/ubuntu/.nvm/versions/node/v4.1.1/bin/node"; - var sbt = "/usr/bin/sbt"; - var pluginDir = "/home/ubuntu/.c9/plugins/c9.ide.language.scala"; - var noExecAnalysis = false; - - var handler = module.exports = Object.create(baseHandler); - var emitter; - - var ensimeProcess; - var ensimePort; - - handler.handlesLanguage = function(language) { - return language === "scala"; - }; - - handler.init = function(callback) { - console.log("Initializing ensime-connector..."); - emitter = handler.getEmitter(); - - emitter.on("set_config", function(config) { - dotEnsime = config.ensimeFile; - sbt = config.sbt || sbt; - node = config.node || node; - pluginDir = config.pluginDir || pluginDir; - node = config.node || node; - noExecAnalysis = config.noExecAnalysis || noExecAnalysis; - }); - - emitter.on("start", start); - emitter.on("stop", stop); - emitter.on("update", update); - emitter.on("call", call); - callback(); - }; - - - function start(attach) { - console.log("Start requested with attach=" + attach); - workerUtil.spawn(node, { - args: [pluginDir + "/server/ensime-runner.js", - dotEnsime, sbt, - attach.toString() - ] - }, function(err, process) { - if (err) return console.error(err); - ensimeProcess = process; - console.log("Waiting for ENSIME to start..."); - emitter.emit("starting"); - - var buffer = ""; - - function receivedData(chunk) { - buffer += chunk; - var delim = buffer.indexOf("|"); - if (delim == -1) return; - - var decoded = window.atob(buffer.substr(0, delim)); - buffer = buffer.substr(delim + 1); - console.debug("ENSIME-EVENT: " + decoded); - var event = JSON.parse(decoded); - if (event.type === "started") { - console.log("ENSIME started."); - ensimePort = event.port; - emitter.emit("started"); - } - else { - emitter.emit("event", event); - } - receivedData(""); - } - - process.stdout.on("data", receivedData); - process.stderr.on("data", function(chunk) { - emitter.emit("log", chunk); - }); - process.on("exit", function(code) { - emitter.emit("stopped", code); - ensimeProcess = undefined; - ensimePort = undefined; - console.log("Ensime server stopped (code " + code + ")"); - }); - }); - } - - function stop() { - if (ensimeProcess) - ensimeProcess.kill(); - ensimeProcess = undefined; - ensimePort = undefined; - emitter.emit("stopped", -1); - } - - function update() { - workerUtil.spawn(node, { - args: [pluginDir + "/server/ensime-update.js", - dotEnsime, sbt - ] - }, function(err, process) { - if (err) return console.error(err); - ensimeProcess = undefined; - ensimePort = undefined; - console.log("Waiting for ENSIME to update..."); - emitter.emit("stopped"); - emitter.emit("updating"); - - process.stdout.on("data", function(chunk) { - //TODO chunk to message mapping might not always be 1:1 - console.debug("ENSIME-EVENT: " + chunk); - var event = JSON.parse(chunk); - if (event.type === "updated") { - console.log("ENSIME updated."); - emitter.emit("updated"); - } - }); - process.stderr.on("data", function(chunk) { - emitter.emit("log", chunk); - }); - process.on("exit", function(code) { - if (code != 0) { - emitter.emit("updateFailed", "Code: " + code); - console.log("Ensime update failed (code " + code + ")"); - } - }); - }); - - } - - function call(request) { - if (!ensimePort) - return console.warn("Could not execute call to ENSIME, since it is not running."); - - console.debug("ENSIME-REQ: " + JSON.stringify(request)); - - function handler(err, data, stderr) { - var result = { - id: request.id - }; - if (err) { - result.error = "Call failed: " + JSON.stringify(err); - result.stderr = stderr; - } - else { - if (typeof data === "string") { - result.result = JSON.parse(data); - } - else result.result = data; - } - console.debug("ENSIME-RESP: " + JSON.stringify(result)); - emitter.emit("call.result", result); - } - - if (!noExecAnalysis && - request.request.fileInfo && - request.request.fileInfo.currentContents) { - // Optimize by not sending all the contents to the server - - delete request.request.fileInfo.contents; - workerUtil.execAnalysis(node, { - args: [ - pluginDir + "/server/ensime-caller.js", - ensimePort, - JSON.stringify(request.request) - ], - mode: "stdin", - json: false, - semaphore: request.id, - maxCallInterval: 1 - }, handler); - } - else { - if (request.request.fileInfo && request.request.fileInfo.currentContents) - delete request.request.fileInfo.currentContents; - workerUtil.execFile("node", { - args: [ - pluginDir + "/server/ensime-caller.js", - ensimePort, - JSON.stringify(request.request) - ], - }, handler); - } - } -}); \ No newline at end of file diff --git a/worker/util.js b/worker/util.js index 99a0597..7ce5ce7 100644 --- a/worker/util.js +++ b/worker/util.js @@ -1,10 +1,9 @@ define(function(require, exports, module) { - var call_id_prefix = "worker"; var last_call_id = 0; function executeEnsime(emitter, req, callback) { - var reqId = call_id_prefix + (last_call_id++); + var reqId = last_call_id++; emitter.on("call.result", function hdlr(event) { if (event.id !== reqId) return; emitter.off("call.result", hdlr); From f62d54b226d464e92bc2db1277b491b5dcd266fc Mon Sep 17 00:00:00 2001 From: Mario Siegenthaler Date: Wed, 9 Nov 2016 11:16:50 +0000 Subject: [PATCH 6/6] Fix problem with unicode content in files. --- ensime-connector.js | 14 +++++++++++--- server/ensime-runner.js | 17 +++++++++++++---- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/ensime-connector.js b/ensime-connector.js index 1bca656..3875733 100644 --- a/ensime-connector.js +++ b/ensime-connector.js @@ -48,6 +48,14 @@ define(function(require, exports, module) { pendingCalls = {}; }); + function utoa(str) { + return window.btoa(encodeURIComponent(str)); + } + + function atou(str) { + return decodeURIComponent(window.atob(str)); + } + function handleEvent(event) { if (event.type === "started") { console.log("ENSIME started."); @@ -86,7 +94,7 @@ define(function(require, exports, module) { var delim = buffer.indexOf("|"); if (delim == -1) return; - var decoded = window.atob(buffer.substr(0, delim)); + var decoded = atou(buffer.substr(0, delim)); buffer = buffer.substr(delim + 1); var event = JSON.parse(decoded); handleEvent(event); @@ -161,7 +169,7 @@ define(function(require, exports, module) { if (!request) return callback("No request"); request.callId = last_call_id++; - ensimeProcess.stdin.write(window.btoa(JSON.stringify(request)) + "|"); + ensimeProcess.stdin.write(utoa(JSON.stringify(request)) + "|"); pendingCalls[request.callId] = callback; } @@ -184,4 +192,4 @@ define(function(require, exports, module) { "ensime-connector": plugin }); } -}); \ No newline at end of file +}); diff --git a/server/ensime-runner.js b/server/ensime-runner.js index 2fc70e9..ce0e71f 100644 --- a/server/ensime-runner.js +++ b/server/ensime-runner.js @@ -35,10 +35,19 @@ process.stdin.setEncoding("ascii"); ec.handleGeneral = function(msg) { var string = JSON.stringify(msg); - var buffer = new Buffer(string, "binary"); - process.stdout.write(buffer.toString("base64") + "|"); + process.stdout.write(utoa(string) + "|"); }; +function utoa(str) { + var buffer = new Buffer(encodeURIComponent(str), "binary"); + return buffer.toString("base64"); +} + +function atou(str) { + var buffer = new Buffer(str, "base64"); + return decodeURIComponent(buffer.toString("binary")); +} + function start() { ec.connect(output, function(err, res) { if (err) return console.error(err); @@ -79,7 +88,7 @@ function receivedData(chunk) { var delim = buffer.indexOf("|"); if (delim == -1) return; - var decoded = new Buffer(buffer.substr(0, delim), "base64").toString("ascii"); + var decoded = atou(buffer.substr(0, delim)); buffer = buffer.substr(delim + 1); var req = JSON.parse(decoded); handleRequest(req); @@ -105,4 +114,4 @@ if (allowAttach) { } else { start(); -} \ No newline at end of file +}