/** * Cloud9 Language Foundation * * @copyright 2013, Ajax.org B.V. */ /** * Language Worker * This code runs in a WebWorker in the browser. Its main job is to * delegate messages it receives to the various handlers that have registered * themselves with the worker. */ define(function(require, exports, module) { require("ace/lib/es5-shim"); require("ace/lib/es6-shim"); var oop = require("ace/lib/oop"); var Mirror = require("ace/worker/mirror").Mirror; var tree = require('treehugger/tree'); var EventEmitter = require("ace/lib/event_emitter").EventEmitter; var syntaxDetector = require("plugins/c9.ide.language.core/syntax_detector"); var completeUtil = require("plugins/c9.ide.language/complete_util"); var localCompleter = require("plugins/c9.ide.language.generic/local_completer"); var openFilesCompleter = require("plugins/c9.ide.language.generic/open_files_local_completer"); var base_handler = require("plugins/c9.ide.language/base_handler"); var assert = require("c9/assert"); var isInWebWorker = typeof window == "undefined" || !window.location || !window.document; var WARNING_LEVELS = { error: 3, warning: 2, info: 1 }; var UPDATE_TIMEOUT_MIN = !isInWebWorker && window.c9Test ? 5 : 200; var UPDATE_TIMEOUT_MAX = 15000; var DEBUG = !isInWebWorker; // set to true by setDebug() for c9.dev/cloud9beta.com var STATS = false; // Leaking into global namespace of worker, to allow handlers to have access /*global disabledFeatures: true*/ disabledFeatures = {}; var ServerProxy = function(sender) { this.emitter = Object.create(EventEmitter); this.emitter.emit = this.emitter._dispatchEvent; this.send = function(data) { sender.emit("serverProxy", data); }; this.once = function(messageType, messageSubtype, callback) { var channel = messageType; if (messageSubtype) channel += (":" + messageSubtype); this.emitter.once(channel, callback); }; this.subscribe = function(messageType, messageSubtype, callback) { var channel = messageType; if (messageSubtype) channel += (":" + messageSubtype); this.emitter.addEventListener(channel, callback); }; this.unsubscribe = function(messageType, messageSubtype, f) { var channel = messageType; if (messageSubtype) channel += (":" + messageSubtype); this.emitter.removeEventListener(channel, f); }; this.onMessage = function(msg) { var channel = msg.type; if (msg.subtype) channel += (":" + msg.subtype); // console.log("publish to: " + channel); this.emitter.emit(channel, msg.body); }; }; exports.createUIWorkerClient = function() { var emitter = Object.create(require("ace/lib/event_emitter").EventEmitter); var result = new LanguageWorker(emitter); result.on = function(name, f) { emitter.on.call(result, name, f); }; result.once = function(name, f) { emitter.once.call(result, name, f); }; result.removeEventListener = function(f) { emitter.removeEventListener.call(result, f); }; result.call = function(cmd, args, callback) { if (callback) { var id = this.callbackId++; this.callbacks[id] = callback; args.push(id); } this.send(cmd, args); }; result.send = function(cmd, args) { setTimeout(function() { result[cmd].apply(result, args); }, 0); }; result.emit = function(event, data) { emitter._dispatchEvent.call(emitter, event, data); }; emitter.emit = function(event, data) { emitter._dispatchEvent.call(result, event, { data: data }); }; result.changeListener = function(e) { this.emit("change", { data: [e.data]}); }; return result; }; var LanguageWorker = exports.LanguageWorker = function(sender) { var _self = this; this.$keys = {}; this.handlers = []; this.$warningLevel = "info"; this.$openDocuments = {}; this.$initedRegexes = {}; this.lastUpdateTime = 0; sender.once = EventEmitter.once; this.serverProxy = new ServerProxy(sender); Mirror.call(this, sender); this.setTimeout(0); exports.sender = sender; exports.$lastWorker = this; sender.on("hierarchy", function(event) { _self.hierarchy(event); }); sender.on("code_format", function(event) { _self.codeFormat(); }); sender.on("outline", applyEventOnce(function(event) { _self.outline(event); })); sender.on("complete", applyEventOnce(function(data) { _self.complete(data); }), true); sender.on("documentClose", function(event) { _self.documentClose(event); }); sender.on("analyze", applyEventOnce(function(event) { _self.analyze(false, function() {}); })); sender.on("cursormove", function(event) { _self.onCursorMove(event); }); sender.on("inspect", applyEventOnce(function(event) { _self.inspect(event); })); sender.on("jumpToDefinition", applyEventOnce(function(event) { _self.jumpToDefinition(event); })); sender.on("quickfixes", applyEventOnce(function(event) { _self.quickfix(event); })); sender.on("isJumpToDefinitionAvailable", applyEventOnce(function(event) { _self.isJumpToDefinitionAvailable(event); })); sender.on("refactorings", function(event) { _self.getRefactorings(event); }); sender.on("renamePositions", function(event) { _self.getRenamePositions(event); }); sender.on("onRenameBegin", function(event) { _self.onRenameBegin(event); }); sender.on("commitRename", function(event) { _self.commitRename(event); }); sender.on("onRenameCancel", function(event) { _self.onRenameCancel(event); }); sender.on("serverProxy", function(event) { _self.serverProxy.onMessage(event.data); }); sender.on("quickfix_key", function(e) { _self.$keys.quickfix = e.data; }); }; /** * Ensure that an event handler is called only once if multiple * events are received at roughly the same time. **/ function applyEventOnce(eventHandler, waitForMirror) { var timer; var mirror = this; return function(e) { var _arguments = [].slice.apply(arguments); if (timer && !(e && e.data.predictOnly)) clearTimeout(timer); timer = setTimeout(function() { if (waitForMirror && mirror.isPending()) return setTimeout(function() { applyEventOnce(eventHandler, true); }, 0); eventHandler.apply(eventHandler, _arguments); }, 0); }; } oop.inherits(LanguageWorker, Mirror); var asyncForEach = module.exports.asyncForEach = function(array, fn, test, callback) { if (!callback) { callback = test; test = null; } array = array.slice(); // copy before use var nested = false, callNext = true; loop(); function loop() { while (callNext && !nested) { callNext = false; while (array.length > 0 && test && !test(array[0])) array.shift(); var item = array.shift(); // TODO: implement proper err argument? if (!item) return callback && callback(); nested = true; fn(item, loop); nested = false; } callNext = true; } }; function startTime() { if (!STATS) return; return Date.now(); } function endTime(t, message, indent) { if (!STATS) return; var spaces = indent ? indent * 2 : 0; var time = String(Date.now() - t); spaces += Math.max(4 - time.length, 0); var prefix = ""; for (var i = 0; i < spaces; i++) prefix += " "; console.log(prefix + time, message); } (function() { var identifierRegexes = {}; var cacheCompletionRegexes = {}; this.enableFeature = function(name, value) { disabledFeatures[name] = !value; }; this.setWarningLevel = function(level) { this.$warningLevel = level; }; this.setStaticPrefix = completeUtil.setStaticPrefix; this.setDebug = function(value) { DEBUG = value; }; /** * Registers a handler by loading its code and adding it the handler array */ this.register = function(path, contents, callback) { var _self = this; function onRegistered(handler) { handler.$source = path; handler.proxy = _self.serverProxy; handler.sender = _self.sender; handler.$isInited = false; handler.getEmitter = function(overridePath) { return _self.$createEmitter(overridePath || path); }; _self.completionCache = _self.completionPrediction = null; _self.handlers.push(handler); _self.$initHandler(handler, null, true, function() { // Note: may not return for a while for asynchronous workers, // don't use this for queueing other tasks _self.sender.emit("registered", { path: path }); callback && callback(); }); } if (contents) { // In the context of this worker, we can't use the standard // require.js approach of using