diff --git a/ensime-connector.js b/ensime-connector.js new file mode 100644 index 0000000..3875733 --- /dev/null +++ b/ensime-connector.js @@ -0,0 +1,195 @@ +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 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."); + 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 = atou(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(utoa(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 + }); + } +}); diff --git a/ensime.js b/ensime.js index 2a55d31..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({ @@ -247,6 +245,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 +263,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", @@ -289,25 +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_ensime_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") - }); - } - 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); @@ -334,6 +320,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) { @@ -375,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"); @@ -391,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."); @@ -414,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); @@ -545,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. */ @@ -605,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 ee79f18..ab92c9e 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": [ { @@ -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..ce0e71f 100644 --- a/server/ensime-runner.js +++ b/server/ensime-runner.js @@ -31,39 +31,87 @@ var ec = new Controller(dotEnsime, "/tmp/ensime", { }); process.stdout.setEncoding("ascii"); +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 connect() { +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); - 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 = atou(buffer.substr(0, delim)); + 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(); -} \ No newline at end of file + start(); +} diff --git a/worker/ensime_connector.js b/worker/ensime_connector.js deleted file mode 100644 index 2989ef7..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_ensime_config", function(config) { - dotEnsime = config.ensimeFile; - sbt = config.sbt || sbt; - node = config.node || node; - pluginDir = config.plugin || 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/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; 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", 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);