diff --git a/common/bootstrap.js b/common/bootstrap.js index 92b5133b..226a69ad 100755 --- a/common/bootstrap.js +++ b/common/bootstrap.js @@ -46,6 +46,11 @@ function debug(...args) { dump(name + ": " + args.join(", ") + "\n"); } +function* entries(obj) { + for (let key of Object.keys(obj)) + yield [key, obj[key]] +} + function httpGet(uri) { let xmlhttp = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest); xmlhttp.overrideMimeType("text/plain"); @@ -57,8 +62,8 @@ function httpGet(uri) { let moduleName; let initialized = false; var addon = null; -let addonData = null; -let basePath = null; +var addonData = null; +var basePath = null; let bootstrap; let bootstrap_jsm; let components = {}; @@ -239,10 +244,10 @@ function init() { if (!manifest.categories) manifest.categories = []; - for (let [classID, { contract, path, categories }] of Iterator(manifest.components || {})) { + for (let [classID, { contract, path, categories }] of entries(manifest.components || {})) { components[classID] = new FactoryProxy(getURI(path).spec, classID, contract); if (categories) - for (let [category, id] in Iterator(categories)) + for (let [category, id] of entries(categories)) manifest.categories.push([category, id, contract]); } @@ -250,7 +255,7 @@ function init() { categoryManager.addCategoryEntry(category, id, value, false, true); - for (let [pkg, path] in Iterator(manifest.resources || {})) { + for (let [pkg, path] of entries(manifest.resources || {})) { moduleName = moduleName || pkg; resourceProto.setSubstitution(pkg, getURI(path)); } diff --git a/common/content/commandline.js b/common/content/commandline.js index bb1d12c9..3b3bce56 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -317,8 +317,11 @@ var CommandWidgets = Class("CommandWidgets", { return document.getElementById("dactyl-contextmenu"); }), + multilineOutputReady: false, + multilineOutput: Class.Memoize(function () { return this._whenReady("dactyl-multiline-output", elem => { + this.multilineOutputReady = true; highlight.highlightNode(elem.contentDocument.body, "MOW"); }); }, true), diff --git a/common/content/key-processors.js b/common/content/key-processors.js index 3e57944c..74ebe8a5 100644 --- a/common/content/key-processors.js +++ b/common/content/key-processors.js @@ -18,15 +18,14 @@ var ProcessorStack = Class("ProcessorStack", { events.dbg("STACK " + mode); let main = { __proto__: mode.main, params: mode.params }; - this.modes = Ary([mode.params.keyModes, - main, - mode.main.allBases.slice(1)]).flatten().compact(); + this.modes = [...(mode.params.keyModes || []), + main, + ...mode.main.allBases.slice(1)]; if (builtin) hives = hives.filter(h => h.name === "builtin"); - this.processors = this.modes.map(m => hives.map(h => KeyProcessor(m, h))) - .flatten().array; + this.processors = this.modes.flatMap(mode => hives.map(hive => KeyProcessor(mode, hive))) this.ownsBuffer = !this.processors.some(p => p.main.ownsBuffer); for (let input of this.processors) { @@ -83,7 +82,7 @@ var ProcessorStack = Class("ProcessorStack", { if (this.ownsBuffer) statusline.inputBuffer = this.processors.length ? this.buffer : ""; - if (!this.processors.some(p => !p.extended) && this.actions.length) { + if (this.processors.every(p => p.extended) && this.actions.length) { // We have matching actions and no processors other than // those waiting on further arguments. Execute actions as // long as they continue to return PASS. @@ -115,9 +114,8 @@ var ProcessorStack = Class("ProcessorStack", { } else if (result !== Events.KILL && !this.actions.length && !(this.events[0].isReplay || this.passUnknown || - this.modes.some(function (m) { - return m.passEvent(this); - }, this.events[0]))) { + this.modes.some(mode => mode.passEvent(this.events[0])))) { + // No patching processors, this isn't a fake, pass-through // event, we're not in pass-through mode, and we're not // choosing to pass unknown keys. Kill the event and beep. diff --git a/common/content/mow.js b/common/content/mow.js index fe26b3af..d648c68e 100644 --- a/common/content/mow.js +++ b/common/content/mow.js @@ -306,6 +306,11 @@ var MOW = Module("mow", { set: function set_mowVisible(value) { this.widgets.mowContainer.collapsed = !value; + // Don't block on the MOW iframe loading if we don't + // actually need it. + if (!value && !commandline.multilineOutputReady) + return; + let elem = this.widget; if (!value && elem && elem.contentWindow == document.commandDispatcher.focusedWindow) { diff --git a/common/modules/config.jsm b/common/modules/config.jsm index d9bfb88c..2f4db697 100644 --- a/common/modules/config.jsm +++ b/common/modules/config.jsm @@ -60,6 +60,33 @@ var ConfigBase = Class("ConfigBase", { "resource://dactyl-content/"))); }); + if (this.VCSPath) { + this.branch = new Promise(resolve => { + this.timeout(() => { + io.system(["hg", "-R", this.VCSPath, "branch"], "", true) + .then(result => { + resolve(result.output); + }); + }, 1000); + }); + + this._version = new Promise(resolve => { + this.timeout(() => { + io.system(["hg", "-R", this.VCSPath, "log", "-r.", + "--template=hg{rev}-{branch}"], "", true) + .then(result => { + this.version = result.output; + resolve(this.version); + }); + }, 1000); + }); + + } + else { + this.branch = Promise.resolve((/pre-hg\d+-(\S*)/.exec(this.version) || [])[1]); + this._version = null; + } + this.protocolLoaded = true; this.timeout(function () { cache.register("config.dtd", () => util.makeDTD(config.dtd), @@ -208,8 +235,19 @@ var ConfigBase = Class("ConfigBase", { get addonID() { return this.name + "@dactyl.googlecode.com"; }, addon: Class.Memoize(function () { - return (JSMLoader.bootstrap || {}).addon || - AddonManager.getAddonByID(this.addonID); + return (JSMLoader.bootstrap || {}).addon; + }), + + addonData: Class.Memoize(function () { + return (JSMLoader.bootstrap || {}).addonData; + }), + + basePath: Class.Memoize(function () { + return (JSMLoader.bootstrap || {}).basePath; + }), + + resourceURI: Class.Memoize(function () { + return this.addonData.resourceURI; }), get styleableChrome() { return Object.keys(this.overlays); }, @@ -379,8 +417,10 @@ var ConfigBase = Class("ConfigBase", { * proxy file. */ VCSPath: Class.Memoize(function () { - if (/pre$/.test(this.addon.version)) { - let uri = util.newURI(this.addon.getResourceURI("").spec + "../.hg"); + if (/pre$/.test(this.addonData.version)) { + // XXX: Sync. + let uri = util.newURI("../.hg", null, this.resourceURI); + if (uri instanceof Ci.nsIFileURL && uri.file.exists() && io.pathSearch("hg")) @@ -389,17 +429,6 @@ var ConfigBase = Class("ConfigBase", { return null; }), - /** - * @property {string} The name of the VCS branch that the application is - * running from if using an extension proxy file or was built from if - * installed as an XPI. - */ - branch: Class.Memoize(function () { - if (this.VCSPath) - return io.system(["hg", "-R", this.VCSPath, "branch"]).output; - return (/pre-hg\d+-(\S*)/.exec(this.version) || [])[1]; - }), - /** @property {string} The name of the current user profile. */ profileName: Class.Memoize(function () { // NOTE: services.profile.selectedProfile.name doesn't return @@ -417,10 +446,6 @@ var ConfigBase = Class("ConfigBase", { /** @property {string} The Dactyl version string. */ version: Class.Memoize(function () { - if (this.VCSPath) - return io.system(["hg", "-R", this.VCSPath, "log", "-r.", - "--template=hg{rev}-{branch}"]).output; - return this.addon.version; }), @@ -659,10 +684,13 @@ config.INIT = update(Object.create(config.INIT), config.INIT, { load: function load(dactyl, modules, window) { load.superapply(this, arguments); - this.timeout(function () { - if (this.branch && this.branch !== "default" && - modules.yes_i_know_i_should_not_report_errors_in_these_branches_thanks.indexOf(this.branch) === -1) - dactyl.warn(_("warn.notDefaultBranch", config.appName, this.branch)); + this.timeout(() => { + let list = modules.yes_i_know_i_should_not_report_errors_in_these_branches_thanks; + + this.branch.then(branch => { + if (branch && branch !== "default" && !list.includes(branch)) + dactyl.warn(_("warn.notDefaultBranch", config.appName, branch)); + }); }, 1000); } }); diff --git a/common/modules/io.jsm b/common/modules/io.jsm index 883c85c4..ccfc0b1a 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -219,6 +219,20 @@ var IO = Module("io", { }; }, + shell: Class.Memoize(() => { + if (config.OS.isWindows) + return "cmd.exe"; + else + return services.environment.get("SHELL") || "sh"; + }), + + shellcmdflag: Class.Memoize(() => { + if (config.OS.isWindows) + return "/c"; + else + return "-c"; + }), + charsets: Class.Memoize(function () { const BASE = "@mozilla.org/intl/unicode/decoder;1?charset="; return Object.keys(Cc).filter(k.startsWith(BASE)) @@ -513,12 +527,14 @@ var IO = Module("io", { * command string or an array of strings (a command and arguments) * which will be escaped and concatenated. * @param {string} input Any input to be provided to the command on stdin. - * @param {function(object)} callback A callback to be called when - * the command completes. @optional + * @param {function(object) | boolean} async A callback to be called when + * the command completes, or a boolean indicating that a + * promise should be returned. @optional * @returns {object|null} */ - system: function system(command, input, callback) { - util.dactyl.echomsg(_("io.callingShell", command), 4); + system: function system(command, input = "", async = false) { + if (loaded.overlay) + util.dactyl.echomsg(_("io.callingShell", command), 4); let { shellEscape } = util.bound; @@ -545,16 +561,28 @@ var IO = Module("io", { }); } - function async(status) { - let output = stdout.read(); - for (let f of [stdin, stdout, cmd]) - if (f.exists()) - f.remove(false); - callback(result(status, output)); + let deferred; + let promise = new Promise((resolve, reject) => { + deferred = { resolve, reject }; + }); + if (callable(async)) + promise.then(async); + + function handleResult(status) { + stdout.async.read().then(output => { + deferred.resolve(result(status, output)); + }); } - let shell = io.pathSearch(storage["options"].get("shell").value); - let shcf = storage["options"].get("shellcmdflag").value; + if (!storage["options"]) + var { shell, shellcmdflag } = this; + else { + shell = storage["options"].get("shell").value; + shellcmdflag = storage["options"].get("shellcmdflag").value; + } + + shell = io.pathSearch(shell); + util.assert(shell, _("error.invalid", "'shell'")); if (isArray(command)) @@ -563,16 +591,18 @@ var IO = Module("io", { // TODO: implement 'shellredir' if (config.OS.isWindows && !/sh/.test(shell.leafName)) { command = "cd /D " + this.cwd.path + " && " + command + " > " + stdout.path + " 2>&1" + " < " + stdin.path; - var res = this.run(shell, shcf.split(/\s+/).concat(command), callback ? async : true); + var res = this.run(shell, shellcmdflag.split(/\s+/).concat(command), async ? handleResult : true); } else { cmd.write("cd " + shellEscape(this.cwd.path) + "\n" + ["exec", ">" + shellEscape(stdout.path), "2>&1", "<" + shellEscape(stdin.path), - shellEscape(shell.path), shcf, shellEscape(command)].join(" ")); - res = this.run("/bin/sh", ["-e", cmd.path], callback ? async : true); + shellEscape(shell.path), shellcmdflag, shellEscape(command)].join(" ")); + res = this.run("/bin/sh", ["-e", cmd.path], async ? handleResult : true); } - return callback ? true : result(res, stdout.read()); + if (async) + return promise; + return result(res, stdout.read()); }, this, true); }, @@ -591,6 +621,11 @@ var IO = Module("io", { let args = Array.from(util.range(0, func.length), () => this.createTempFile(ext, label)); + function cleanup() { + // XXX: Sync. + args.forEach(f => { f.remove(false); }); + } + try { if (!args.every(identity)) return false; @@ -598,8 +633,10 @@ var IO = Module("io", { var res = func.apply(self || this, args); } finally { - if (!checked || res !== true) - args.forEach(f => { f.remove(false); }); + if (res && typeof res === "object" && "then" in res && callable(res.then)) + res.then(cleanup, cleanup); + else if (!checked || res !== true) + cleanup(); } return res; } @@ -1180,15 +1217,7 @@ unlet s:cpo_save options: function initOptions(dactyl, modules, window) { const { completion, options } = modules; - var shell, shellcmdflag; - if (config.OS.isWindows) { - shell = "cmd.exe"; - shellcmdflag = "/c"; - } - else { - shell = services.environment.get("SHELL") || "sh"; - shellcmdflag = "-c"; - } + let { shell, shellcmdflag } = io; options.add(["banghist", "bh"], "Replace occurrences of ! with the previous command when executing external commands", diff --git a/common/modules/storage.jsm b/common/modules/storage.jsm index bf3b8ac6..7f3bafeb 100644 --- a/common/modules/storage.jsm +++ b/common/modules/storage.jsm @@ -920,9 +920,9 @@ var AsyncFile = Class("AsyncFile", File, { }), _setEncoding: function _setEncoding(options) { - if (this.encoding != null && !("encoding" in options)) + if (this.charset != null && !("encoding" in options)) options = update({}, options, - { encoding: this.encoding }); + { encoding: this.charset }); return options; }, diff --git a/common/modules/util.jsm b/common/modules/util.jsm index a3d62171..b064f442 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -122,15 +122,14 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), global = Class.objectGlobal(obj); return (global && global.dactyl || - overlay.activeWindow && overlay.activeWindow.dactyl || + loaded.overlay && overlay.activeWindow && overlay.activeWindow.dactyl || anythingObjectHack); }, { get(target, prop) { if (prop in target) return target[prop]; - if (loaded.overlay) - return target()[prop]; + return target()[prop]; }, }),