diff --git a/common/content/autocommands.js b/common/content/autocommands.js index fd9f4e0f..899019f7 100755 --- a/common/content/autocommands.js +++ b/common/content/autocommands.js @@ -83,7 +83,7 @@ const AutoCommands = Module("autocommands", { } }); - let list = template.commandOutput( + let list = template.commandOutput(commandline.command, @@ -257,8 +257,6 @@ const AutoCommands = Module("autocommands", { }); }, completion: function () { - JavaScript.setCompleter(this.get, [function () Iterator(config.autocommands)]); - completion.autocmdEvent = function autocmdEvent(context) { context.completions = Iterator(config.autocommands); }; @@ -268,6 +266,9 @@ const AutoCommands = Module("autocommands", { context.completions = [item for (item in events.getMacros())]; }; }, + javascript: function () { + JavaScript.setCompleter(this.get, [function () Iterator(config.autocommands)]); + }, options: function () { options.add(["eventignore", "ei"], "List of autocommand event names which should be ignored", diff --git a/common/content/base.js b/common/content/base.js index 848f22d6..e4a58bbb 100644 --- a/common/content/base.js +++ b/common/content/base.js @@ -9,14 +9,6 @@ const Ci = Components.interfaces; const Cr = Components.results; const Cu = Components.utils; -function array(obj) { - if (isgenerator(obj)) - obj = [k for (k in obj)]; - else if (obj.length) - obj = Array.slice(obj); - return util.Array(obj); -} - function allkeys(obj) { let ret = {}; try { @@ -45,7 +37,7 @@ function allkeys(obj) { } function debuggerProperties(obj) { - if (modules.services && options["jsdebugger"]) { + if (modules.services && services.get("debugger").isOn) { let ret = {}; services.get("debugger").wrapValue(obj).getProperties(ret, {}); return ret.value; @@ -125,9 +117,9 @@ function iter(obj) { } catch (e) {} })(); - if (isinstance(obj, [HTMLCollection, NodeList])) - return util.Array.iteritems(obj); - if (obj instanceof NamedNodeMap) + if (isinstance(obj, [Ci.nsIDOMHTMLCollection, Ci.nsIDOMNodeList])) + return array.iteritems(obj); + if (obj instanceof Ci.nsIDOMNamedNodeMap) return (function () { for (let i = 0; i < obj.length; i++) yield [obj.name, obj]; @@ -208,6 +200,13 @@ function call(fn) { return fn; } +function memoize(obj, key, getter) { + obj.__defineGetter__(key, function () { + delete obj[key]; + return obj[key] = getter(obj, key); + }); +} + /** * Curries a function to the given number of arguments. Each * call of the resulting function returns a new function. When @@ -312,13 +311,8 @@ function update(target) { * @param {Object} overrides @optional */ function extend(subclass, superclass, overrides) { - subclass.prototype = {}; + subclass.prototype = { __proto__: superclass.prototype }; update(subclass.prototype, overrides); - // This is unduly expensive. Unfortunately necessary since - // we apparently can't rely on the presence of the - // debugger to enumerate properties when we have - // __iterators__ attached to prototypes. - subclass.prototype.__proto__ = superclass.prototype; subclass.superclass = superclass.prototype; subclass.prototype.constructor = subclass; @@ -421,8 +415,10 @@ Class.prototype = { */ setTimeout: function (callback, timeout) { const self = this; - function target() callback.call(self); - return window.setTimeout(target, timeout); + let notify = { notify: function notify(timer) { callback.call(self) } }; + let timer = services.create("timer"); + timer.initWithCallback(notify, timeout, timer.TYPE_ONE_SHOT); + return timer; } }; @@ -493,4 +489,136 @@ for (let k in values(["concat", "every", "filter", "forEach", "indexOf", "join", "map", "reduce", "reduceRight", "reverse", "slice", "some", "sort"])) StructBase.prototype[k] = Array.prototype[k]; +/** + * Array utility methods. + */ +const array = Class("util.Array", Array, { + init: function (ary) { + if (isgenerator(ary)) + ary = [k for (k in ary)]; + else if (ary.length) + ary = Array.slice(ary); + + return { + __proto__: ary, + __iterator__: function () this.iteritems(), + __noSuchMethod__: function (meth, args) { + var res = array[meth].apply(null, [this.__proto__].concat(args)); + + if (array.isinstance(res)) + return array(res); + return res; + }, + toString: function () this.__proto__.toString(), + concat: function () this.__proto__.concat.apply(this.__proto__, arguments), + map: function () this.__noSuchMethod__("map", Array.slice(arguments)) + }; + } +}, { + isinstance: function isinstance(obj) { + return Object.prototype.toString.call(obj) == "[object Array]"; + }, + + /** + * Converts an array to an object. As in lisp, an assoc is an + * array of key-value pairs, which maps directly to an object, + * as such: + * [["a", "b"], ["c", "d"]] -> { a: "b", c: "d" } + * + * @param {Array[]} assoc + * @... {string} 0 - Key + * @... 1 - Value + */ + toObject: function toObject(assoc) { + let obj = {}; + assoc.forEach(function ([k, v]) { obj[k] = v; }); + return obj; + }, + + /** + * Compacts an array, removing all elements that are null or undefined: + * ["foo", null, "bar", undefined] -> ["foo", "bar"] + * + * @param {Array} ary + * @returns {Array} + */ + compact: function compact(ary) ary.filter(function (item) item != null), + + /** + * Flattens an array, such that all elements of the array are + * joined into a single array: + * [["foo", ["bar"]], ["baz"], "quux"] -> ["foo", ["bar"], "baz", "quux"] + * + * @param {Array} ary + * @returns {Array} + */ + flatten: function flatten(ary) ary.length ? Array.concat.apply([], ary) : [], + + /** + * Returns an Iterator for an array's values. + * + * @param {Array} ary + * @returns {Iterator(Object)} + */ + itervalues: function itervalues(ary) { + let length = ary.length; + for (let i = 0; i < length; i++) + yield ary[i]; + }, + + /** + * Returns an Iterator for an array's indices and values. + * + * @param {Array} ary + * @returns {Iterator([{number}, {Object}])} + */ + iteritems: function iteritems(ary) { + let length = ary.length; + for (let i = 0; i < length; i++) + yield [i, ary[i]]; + }, + + /** + * Filters out all duplicates from an array. If + * unsorted is false, the array is sorted before + * duplicates are removed. + * + * @param {Array} ary + * @param {boolean} unsorted + * @returns {Array} + */ + uniq: function uniq(ary, unsorted) { + let ret = []; + if (unsorted) { + for (let [, item] in Iterator(ary)) + if (ret.indexOf(item) == -1) + ret.push(item); + } + else { + for (let [, item] in Iterator(ary.sort())) { + if (item != last || !ret.length) + ret.push(item); + var last = item; + } + } + return ret; + }, + + /** + * Zips the contents of two arrays. The resulting array is twice the + * length of ary1, with any shortcomings of ary2 replaced with null + * strings. + * + * @param {Array} ary1 + * @param {Array} ary2 + * @returns {Array} + */ + zip: function zip(ary1, ary2) { + let res = [] + for(let [i, item] in Iterator(ary1)) + res.push(item, i in ary2 ? ary2[i] : ""); + return res; + } +}); + // vim: set fdm=marker sw=4 ts=4 et: diff --git a/common/content/bookmarks.js b/common/content/bookmarks.js index 7f7f546b..fff84480 100644 --- a/common/content/bookmarks.js +++ b/common/content/bookmarks.js @@ -464,7 +464,7 @@ const Bookmarks = Module("bookmarks", { args.completeFilter = have.pop(); let prefix = filter.substr(0, filter.length - args.completeFilter.length); - let tags = util.Array.uniq(util.Array.flatten([b.tags for ([k, b] in Iterator(this._cache.bookmarks))])); + let tags = array.uniq(util.Array.flatten([b.tags for ([k, b] in Iterator(this._cache.bookmarks))])); return [[prefix + tag, tag] for ([i, tag] in Iterator(tags)) if (have.indexOf(tag) < 0)]; } diff --git a/common/content/buffer.js b/common/content/buffer.js index bedfb4d7..cb50f16f 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -1003,7 +1003,7 @@ const Buffer = Module("buffer", { let values = ZoomManager.zoomValues; let cur = values.indexOf(ZoomManager.snap(ZoomManager.zoom)); - let i = util.Math.constrain(cur + steps, 0, values.length - 1); + let i = Math.constrain(cur + steps, 0, values.length - 1); if (i == cur && fullZoom == ZoomManager.useFullZoom) dactyl.beep(); @@ -1286,7 +1286,7 @@ const Buffer = Module("buffer", { level = buffer.textZoom + parseInt(arg, 10); // relative args shouldn't take us out of range - level = util.Math.constrain(level, Buffer.ZOOM_MIN, Buffer.ZOOM_MAX); + level = Math.constrain(level, Buffer.ZOOM_MIN, Buffer.ZOOM_MAX); } else dactyl.assert(false, "E488: Trailing characters"); @@ -1519,14 +1519,14 @@ const Buffer = Module("buffer", { let xpath = ["input", "textarea[not(@disabled) and not(@readonly)]"]; let elements = [m for (m in util.evaluateXPath(xpath))].filter(function (elem) { - if (elem.readOnly || elem instanceof HTMLInputElement && set.has(Events.editableInputs, elem.type)) + if (elem.readOnly || elem instanceof HTMLInputElement && !set.has(Events.editableInputs, elem.type)) return false; let computedStyle = util.computedStyle(elem); return computedStyle.visibility != "hidden" && computedStyle.display != "none"; }); dactyl.assert(elements.length > 0); - let elem = elements[util.Math.constrain(count, 1, elements.length) - 1]; + let elem = elements[Math.constrain(count, 1, elements.length) - 1]; buffer.focusElement(elem); util.scrollIntoView(elem); } diff --git a/common/content/commandline.js b/common/content/commandline.js index f3088985..974d7446 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -131,26 +131,24 @@ const CommandLine = Module("commandline", { this._startHints = false; // whether we're waiting to start hints mode this._lastSubstring = ""; - // the containing box for the this._promptWidget and this._commandWidget - this._commandlineWidget = document.getElementById("dactyl-commandline"); - // the prompt for the current command, for example : or /. Can be blank - this._promptWidget = document.getElementById("dactyl-commandline-prompt"); - // the command bar which contains the current command - this._commandWidget = document.getElementById("dactyl-commandline-command"); + this.widgets = { + commandline: document.getElementById("dactyl-commandline"), + prompt: document.getElementById("dactyl-commandline-prompt"), + command: document.getElementById("dactyl-commandline-command"), - this._messageBox = document.getElementById("dactyl-message"); + message: document.getElementById("dactyl-message"), - this._commandWidget.inputField.QueryInterface(Ci.nsIDOMNSEditableElement); - this._messageBox.inputField.QueryInterface(Ci.nsIDOMNSEditableElement); + multilineOutput: document.getElementById("dactyl-multiline-output"), + multilineInput: document.getElementById("dactyl-multiline-input"), + }; + + this.widgets.command.inputField.QueryInterface(Ci.nsIDOMNSEditableElement); + this.widgets.message.inputField.QueryInterface(Ci.nsIDOMNSEditableElement); // the widget used for multiline output - this._multilineOutputWidget = document.getElementById("dactyl-multiline-output"); - this._outputContainer = this._multilineOutputWidget.parentNode; + this._outputContainer = this.widgets.multilineOutput.parentNode; - this._multilineOutputWidget.contentDocument.body.id = "dactyl-multiline-output-content"; - - // the widget used for multiline intput - this._multilineInputWidget = document.getElementById("dactyl-multiline-input"); + this.widgets.multilineOutput.contentDocument.body.id = "dactyl-multiline-output-content"; // we need to save the mode which were in before opening the command line // this is then used if we focus the command line again without the "official" @@ -208,7 +206,7 @@ const CommandLine = Module("commandline", { * Highlight the messageBox according to group. */ _setHighlightGroup: function (group) { - this._messageBox.setAttributeNS(NS.uri, "highlight", group); + this.widgets.message.setAttributeNS(NS.uri, "highlight", group); }, /** @@ -226,10 +224,10 @@ const CommandLine = Module("commandline", { * @param {string} highlightGroup */ _setPrompt: function (val, highlightGroup) { - this._promptWidget.value = val; - this._promptWidget.size = val.length; - this._promptWidget.collapsed = (val == ""); - this._promptWidget.setAttributeNS(NS.uri, "highlight", highlightGroup || commandline.HL_NORMAL); + this.widgets.prompt.value = val; + this.widgets.prompt.size = val.length; + this.widgets.prompt.collapsed = (val == ""); + this.widgets.prompt.setAttributeNS(NS.uri, "highlight", highlightGroup || commandline.HL_NORMAL); }, /** @@ -239,9 +237,9 @@ const CommandLine = Module("commandline", { * @param {string} cmd */ _setCommand: function (cmd) { - this._commandWidget.value = cmd; - this._commandWidget.selectionStart = cmd.length; - this._commandWidget.selectionEnd = cmd.length; + this.widgets.command.value = cmd; + this.widgets.command.selectionStart = cmd.length; + this.widgets.command.selectionEnd = cmd.length; }, /** @@ -254,14 +252,14 @@ const CommandLine = Module("commandline", { */ _echoLine: function (str, highlightGroup, forceSingle) { this._setHighlightGroup(highlightGroup); - this._messageBox.value = str; + this.widgets.message.value = str; dactyl.triggerObserver("echoLine", str, highlightGroup, forceSingle); if (!this._commandShown()) commandline.hide(); - let field = this._messageBox.inputField; + let field = this.widgets.message.inputField; if (!forceSingle && field.editor.rootElement.scrollWidth > field.scrollWidth) this._echoMultiline({str}, highlightGroup); }, @@ -274,8 +272,8 @@ const CommandLine = Module("commandline", { */ // TODO: resize upon a window resize _echoMultiline: function (str, highlightGroup) { - let doc = this._multilineOutputWidget.contentDocument; - let win = this._multilineOutputWidget.contentWindow; + let doc = this.widgets.multilineOutput.contentDocument; + let win = this.widgets.multilineOutput.contentWindow; dactyl.triggerObserver("echoMultiline", str, highlightGroup); @@ -316,9 +314,9 @@ const CommandLine = Module("commandline", { * Ensure that the multiline input widget is the correct size. */ _autosizeMultilineInputWidget: function () { - let lines = this._multilineInputWidget.value.split("\n").length - 1; + let lines = this.widgets.multilineInput.value.split("\n").length - 1; - this._multilineInputWidget.setAttribute("rows", Math.max(lines, 1)); + this.widgets.multilineInput.setAttribute("rows", Math.max(lines, 1)); }, HL_NORMAL: "Normal", @@ -386,15 +384,15 @@ const CommandLine = Module("commandline", { try { // The long path is because of complications with the // completion preview. - return this._commandWidget.inputField.editor.rootElement.firstChild.textContent; + return this.widgets.command.inputField.editor.rootElement.firstChild.textContent; } catch (e) { - return this._commandWidget.value; + return this.widgets.command.value; } }, - set command(cmd) this._commandWidget.value = cmd, + set command(cmd) this.widgets.command.value = cmd, - get message() this._messageBox.value, + get message() this.widgets.message.value, /** * Open the command line. The main mode is set to @@ -416,14 +414,14 @@ const CommandLine = Module("commandline", { this._setPrompt(this._currentPrompt); this._setCommand(this._currentCommand); - this._commandlineWidget.collapsed = false; + this.widgets.commandline.collapsed = false; modes.set(modes.COMMAND_LINE, this._currentExtendedMode); - this._commandWidget.focus(); + this.widgets.command.focus(); - this._history = CommandLine.History(this._commandWidget.inputField, (modes.extended == modes.EX) ? "command" : "search"); - this._completions = CommandLine.Completions(this._commandWidget.inputField); + this._history = CommandLine.History(this.widgets.command.inputField, (modes.extended == modes.EX) ? "command" : "search"); + this._completions = CommandLine.Completions(this.widgets.command.inputField); // open the completion list automatically if wanted if (cmd.length) @@ -450,7 +448,7 @@ const CommandLine = Module("commandline", { statusline.updateProgress(""); // we may have a "match x of y" visible dactyl.focusContent(false); - this._multilineInputWidget.collapsed = true; + this.widgets.multilineInput.collapsed = true; this._completionList.hide(); if (!this._keepCommand || this._silent || this._quiet) { @@ -470,7 +468,7 @@ const CommandLine = Module("commandline", { * are under it. */ hide: function hide() { - this._commandlineWidget.collapsed = true; + this.widgets.commandline.collapsed = true; }, /** @@ -509,7 +507,7 @@ const CommandLine = Module("commandline", { return; // The DOM isn't threadsafe. It must only be accessed from the main thread. - dactyl.callInMainThread(function () { + util.callInMainThread(function () { if ((flags & this.DISALLOW_MULTILINE) && !this._outputContainer.collapsed) return; @@ -518,7 +516,7 @@ const CommandLine = Module("commandline", { // TODO: this is all a bit convoluted - clean up. // assume that FORCE_MULTILINE output is fully styled - if (!(flags & this.FORCE_MULTILINE) && !single && (!this._outputContainer.collapsed || this._messageBox.value == this._lastEcho)) { + if (!(flags & this.FORCE_MULTILINE) && !single && (!this._outputContainer.collapsed || this.widgets.message.value == this._lastEcho)) { highlightGroup += " Message"; action = this._echoMultiline; } @@ -529,9 +527,9 @@ const CommandLine = Module("commandline", { if (single) this._lastEcho = null; else { - if (this._messageBox.value == this._lastEcho) + if (this.widgets.message.value == this._lastEcho) this._echoMultiline({this._lastEcho}, - this._messageBox.getAttributeNS(NS.uri, "highlight")); + this.widgets.message.getAttributeNS(NS.uri, "highlight")); this._lastEcho = (action == this._echoLine) && str; } @@ -571,10 +569,10 @@ const CommandLine = Module("commandline", { this._setPrompt(prompt, extra.promptHighlight || this.HL_QUESTION); this._setCommand(extra.default || ""); - this._commandlineWidget.collapsed = false; - this._commandWidget.focus(); + this.widgets.commandline.collapsed = false; + this.widgets.command.focus(); - this._completions = CommandLine.Completions(this._commandWidget.inputField); + this._completions = CommandLine.Completions(this.widgets.command.inputField); }, /** @@ -588,7 +586,7 @@ const CommandLine = Module("commandline", { // FIXME: Buggy, especially when pasting. Shouldn't use a RegExp. inputMultiline: function inputMultiline(untilRegexp, callbackFunc) { // Kludge. - let cmd = !this._commandWidget.collapsed && this.command; + let cmd = !this.widgets.command.collapsed && this.command; modes.push(modes.COMMAND_LINE, modes.INPUT_MULTILINE); if (cmd != false) this._echoLine(cmd, this.HL_NORMAL); @@ -597,11 +595,11 @@ const CommandLine = Module("commandline", { this._multilineRegexp = untilRegexp; this._multilineCallback = callbackFunc; - this._multilineInputWidget.collapsed = false; - this._multilineInputWidget.value = ""; + this.widgets.multilineInput.collapsed = false; + this.widgets.multilineInput.value = ""; this._autosizeMultilineInputWidget(); - this.setTimeout(function () { this._multilineInputWidget.focus(); }, 10); + this.setTimeout(function () { this.widgets.multilineInput.focus(); }, 10); }, /** @@ -619,12 +617,12 @@ const CommandLine = Module("commandline", { if (event.type == "blur") { // prevent losing focus, there should be a better way, but it just didn't work otherwise this.setTimeout(function () { - if (this._commandShown() && event.originalTarget == this._commandWidget.inputField) - this._commandWidget.inputField.focus(); + if (this._commandShown() && event.originalTarget == this.widgets.command.inputField) + this.widgets.command.inputField.focus(); }, 0); } else if (event.type == "focus") { - if (!this._commandShown() && event.target == this._commandWidget.inputField) { + if (!this._commandShown() && event.target == this.widgets.command.inputField) { event.target.blur(); dactyl.beep(); } @@ -703,22 +701,22 @@ const CommandLine = Module("commandline", { if (event.type == "keypress") { let key = events.toString(event); if (events.isAcceptKey(key)) { - let text = this._multilineInputWidget.value.substr(0, this._multilineInputWidget.selectionStart); + let text = this.widgets.multilineInput.value.substr(0, this.widgets.multilineInput.selectionStart); if (text.match(this._multilineRegexp)) { text = text.replace(this._multilineRegexp, ""); modes.pop(); - this._multilineInputWidget.collapsed = true; + this.widgets.multilineInput.collapsed = true; this._multilineCallback.call(this, text); } } else if (events.isCancelKey(key)) { modes.pop(); - this._multilineInputWidget.collapsed = true; + this.widgets.multilineInput.collapsed = true; } } else if (event.type == "blur") { if (modes.extended & modes.INPUT_MULTILINE) - this.setTimeout(function () { this._multilineInputWidget.inputField.focus(); }, 0); + this.setTimeout(function () { this.widgets.multilineInput.inputField.focus(); }, 0); } else if (event.type == "input") this._autosizeMultilineInputWidget(); @@ -735,7 +733,7 @@ const CommandLine = Module("commandline", { // FIXME: if 'more' is set and the MOW is not scrollable we should still // allow a down motion after an up rather than closing onMultilineOutputEvent: function onMultilineOutputEvent(event) { - let win = this._multilineOutputWidget.contentWindow; + let win = this.widgets.multilineOutput.contentWindow; let showMoreHelpPrompt = false; let showMorePrompt = false; @@ -934,7 +932,7 @@ const CommandLine = Module("commandline", { }, getSpaceNeeded: function getSpaceNeeded() { - let rect = this._commandlineWidget.getBoundingClientRect(); + let rect = this.widgets.commandline.getBoundingClientRect(); let offset = rect.bottom - window.innerHeight; return Math.max(0, offset); }, @@ -953,7 +951,7 @@ const CommandLine = Module("commandline", { return; } - let win = this._multilineOutputWidget.contentWindow; + let win = this.widgets.multilineOutput.contentWindow; function isScrollable() !win.scrollMaxY == 0; function atEnd() win.scrollY / win.scrollMaxY >= 1; @@ -975,12 +973,12 @@ const CommandLine = Module("commandline", { if (!open && this._outputContainer.collapsed) return; - let doc = this._multilineOutputWidget.contentDocument; + let doc = this.widgets.multilineOutput.contentDocument; let availableHeight = config.outputHeight; if (!this._outputContainer.collapsed) availableHeight += parseFloat(this._outputContainer.height); - doc.body.style.minWidth = this._commandlineWidget.scrollWidth + "px"; + doc.body.style.minWidth = this.widgets.commandline.scrollWidth + "px"; this._outputContainer.height = Math.min(doc.height, availableHeight) + "px"; doc.body.style.minWidth = ""; this._outputContainer.collapsed = false; @@ -1086,7 +1084,7 @@ const CommandLine = Module("commandline", { while (true) { this.index += diff; if (this.index < 0 || this.index > this.store.length) { - this.index = util.Math.constrain(this.index, 0, this.store.length); + this.index = Math.constrain(this.index, 0, this.store.length); dactyl.beep(); // I don't know why this kludge is needed. It // prevents the caret from moving to the end of @@ -1146,8 +1144,8 @@ const CommandLine = Module("commandline", { // Change the completion text. // The second line is a hack to deal with some substring // preview corner cases. - commandline._commandWidget.value = this.prefix + completion + this.suffix; - this.editor.selection.focusNode.textContent = commandline._commandWidget.value; + commandline.widgets.command.value = this.prefix + completion + this.suffix; + this.editor.selection.focusNode.textContent = commandline.widgets.command.value; // Reset the caret to one position after the completion. this.caret = this.prefix.length + completion.length; @@ -1155,8 +1153,8 @@ const CommandLine = Module("commandline", { get caret() this.editor.selection.focusOffset, set caret(offset) { - commandline._commandWidget.selectionStart = offset; - commandline._commandWidget.selectionEnd = offset; + commandline.widgets.command.selectionStart = offset; + commandline.widgets.command.selectionEnd = offset; }, get start() this.context.allItems.start, @@ -1236,9 +1234,9 @@ const CommandLine = Module("commandline", { } else if (this.removeSubstring) { let str = this.removeSubstring; - let cmd = commandline._commandWidget.value; + let cmd = commandline.widgets.command.value; if (cmd.substr(cmd.length - str.length) == str) - commandline._commandWidget.value = cmd.substr(0, cmd.length - str.length); + commandline.widgets.command.value = cmd.substr(0, cmd.length - str.length); } delete this.removeSubstring; }, @@ -1289,7 +1287,7 @@ const CommandLine = Module("commandline", { idx = null; break; default: - idx = util.Math.constrain(idx, 0, this.items.length - 1); + idx = Math.constrain(idx, 0, this.items.length - 1); break; } @@ -1310,7 +1308,7 @@ const CommandLine = Module("commandline", { for (let [, context] in Iterator(list)) { function done() !(idx >= n + context.items.length || idx == -2 && !context.items.length); while (context.incomplete && !done()) - dactyl.threadYield(false, true); + util.threadYield(false, true); if (done()) break; @@ -1369,7 +1367,7 @@ const CommandLine = Module("commandline", { if (this.type.list) this.itemList.show(); - this.wildIndex = util.Math.constrain(this.wildIndex + 1, 0, this.wildtypes.length - 1); + this.wildIndex = Math.constrain(this.wildIndex + 1, 0, this.wildtypes.length - 1); this.preview(); commandline._statusTimer.tell(); @@ -1651,7 +1649,7 @@ const ItemList = Class("ItemList", { let off = 0; let end = this._startIndex + options["maxitems"]; function getRows(context) { - function fix(n) util.Math.constrain(n, 0, len); + function fix(n) Math.constrain(n, 0, len); let len = context.items.length; let start = off; end -= !!context.message + context.incomplete; diff --git a/common/content/commands.js b/common/content/commands.js index a3c81d97..1cda7784 100644 --- a/common/content/commands.js +++ b/common/content/commands.js @@ -315,7 +315,7 @@ const Commands = Module("commands", { } this._exCommands.push(command); - for(let [,name] in Iterator(command.names)) + for (let [,name] in Iterator(command.names)) this._exMap[name] = command; return true; @@ -765,9 +765,9 @@ const Commands = Module("commands", { * any of the command's names. */ removeUserCommand: function (name) { - for(let [,cmd] in Iterator(this._exCommands)) - if(cmd.user && cmd.hasName(name)) - for(let [,name] in Iterator(cmd.names)) + for (let [,cmd] in Iterator(this._exCommands)) + if (cmd.user && cmd.hasName(name)) + for (let [,name] in Iterator(cmd.names)) delete this._exMap[name]; this._exCommands = this._exCommands.filter(function (cmd) !(cmd.user && cmd.hasName(name))); }, @@ -858,23 +858,7 @@ const Commands = Module("commands", { return [len - str.length, arg, quote]; } }, { - mappings: function () { - mappings.add(config.browserModes, - ["@:"], "Repeat the last Ex command", - function (count) { - if (commands.repeat) { - for (let i in util.interruptibleRange(0, Math.max(count, 1), 100)) - dactyl.execute(commands.repeat); - } - else - dactyl.echoerr("E30: No previous command line"); - }, - { count: true }); - }, - completion: function () { - JavaScript.setCompleter(this.get, [function () ([c.name, c.description] for (c in commands))]); - completion.command = function command(context) { context.title = ["Command"]; context.keys = { text: "longNames", description: "description" }; @@ -1105,6 +1089,22 @@ const Commands = Module("commands", { argCount: "1", completer: function (context) completion.userCommand(context) }); + }, + javascript: function () { + JavaScript.setCompleter(this.get, [function () ([c.name, c.description] for (c in commands))]); + }, + mappings: function () { + mappings.add(config.browserModes, + ["@:"], "Repeat the last Ex command", + function (count) { + if (commands.repeat) { + for (let i in util.interruptibleRange(0, Math.max(count, 1), 100)) + dactyl.execute(commands.repeat); + } + else + dactyl.echoerr("E30: No previous command line"); + }, + { count: true }); } }); diff --git a/common/content/completion.js b/common/content/completion.js index f902669b..9a9268a0 100755 --- a/common/content/completion.js +++ b/common/content/completion.js @@ -260,7 +260,7 @@ const CompletionContext = Class("CompletionContext", { this._completions = items; let self = this; if (this.updateAsync && !this.noUpdate) - dactyl.callInMainThread(function () { self.onUpdate.call(self); }); + util.callInMainThread(function () { self.onUpdate.call(self); }); }, get createRow() this._createRow || template.completionRow, // XXX @@ -326,7 +326,7 @@ const CompletionContext = Class("CompletionContext", { this.cache.backgroundLock = lock; this.incomplete = true; let thread = this.getCache("backgroundThread", dactyl.newThread); - dactyl.callAsync(thread, this, function () { + util.callAsync(thread, this, function () { if (this.cache.backgroundLock != lock) return; let items = this.generate(); @@ -609,7 +609,7 @@ const CompletionContext = Class("CompletionContext", { wait: function wait(interruptable, timeout) { let end = Date.now() + timeout; while (this.incomplete && (!timeout || Date.now() > end)) - dactyl.threadYield(false, interruptable); + util.threadYield(false, interruptable); return this.incomplete; } }, { @@ -668,7 +668,7 @@ const Completion = Module("completion", { context = context.contexts["/list"]; context.wait(); - let list = template.commandOutput( + let list = template.commandOutput(commandline.command,
{ template.map(context.contextList.filter(function (c) c.hasItems), function (context) @@ -765,7 +765,7 @@ const Completion = Module("completion", { commands.add(["contexts"], "List the completion contexts used during completion of an ex command", function (args) { - commandline.echo(template.commandOutput( + commandline.echo(template.commandOutput(commandline.command,
{ template.completionRow(["Context", "Title"], "CompTitle") } { template.map(completion.contextList || [], function (item) template.completionRow(item, "CompItem")) } diff --git a/common/content/configbase.js b/common/content/configbase.js index e43033f9..9005f122 100644 --- a/common/content/configbase.js +++ b/common/content/configbase.js @@ -7,6 +7,15 @@ "use strict"; const ConfigBase = Class(ModuleBase, { + /** + * Called on dactyl startup to allow for any arbitrary application-specific + * initialization code. Must call superclass's init function. + */ + init: function () { + highlight.styleableChrome = this.styleableChrome; + highlight.loadCSS(this.CSS); + }, + /** * @property {[["string", "string"]]} A sequence of names and descriptions * of the autocommands available in this application. Primarily used @@ -14,8 +23,8 @@ const ConfigBase = Class(ModuleBase, { */ autocommands: [], - browser: window.gBrowser, - tabbrowser: window.gBrowser, + get browser() window.gBrowser, + get tabbrowser() window.gBrowser, get browserModes() [modes.NORMAL], @@ -55,12 +64,6 @@ const ConfigBase = Class(ModuleBase, { */ hostApplication: null, - /** - * @property {function} Called on dactyl startup to allow for any - * arbitrary application-specific initialization code. - */ - init: function () {}, - /** * @property {Object} A map between key names for key events should be ignored, * and a mask of the modes in which they should be ignored. @@ -102,8 +105,229 @@ const ConfigBase = Class(ModuleBase, { * @property {string} The leaf name of any temp files created by * {@link io.createTempFile}. */ - get tempFile() this.name.toLowerCase() + ".tmp" + get tempFile() this.name.toLowerCase() + ".tmp", + /** + * @constant + * @property {string} The default highlighting rules. They have the + * form: + * rule ::= selector space space+ css + * selector ::= group + * | group "," css-selector + * | group "," css-selector "," scope + * group ::= groupname + * | groupname css-selector + */ + // + CSS: * font-family: monospace; padding: 1px; + CmdOutput white-space: pre; + + CompGroup + CompGroup:not(:first-of-type) margin-top: .5em; + CompTitle color: magenta; background: white; font-weight: bold; + CompTitle>* padding: 0 .5ex; + CompMsg font-style: italic; margin-left: 16px; + CompItem + CompItem[selected] background: yellow; + CompItem>* padding: 0 .5ex; + CompIcon width: 16px; min-width: 16px; display: inline-block; margin-right: .5ex; + CompIcon>img max-width: 16px; max-height: 16px; vertical-align: middle; + CompResult width: 45%; overflow: hidden; + CompDesc color: gray; width: 50%; + CompLess text-align: center; height: 0; line-height: .5ex; padding-top: 1ex; + CompLess::after content: "\2303" /* Unicode up arrowhead */ + CompMore text-align: center; height: .5ex; line-height: .5ex; margin-bottom: -.5ex; + CompMore::after content: "\2304" /* Unicode down arrowhead */ + + Gradient height: 1px; margin-bottom: -1px; margin-top: -1px; + GradientLeft background-color: magenta; + GradientRight background-color: white; + + Indicator color: blue; + Filter font-weight: bold; + + Keyword color: red; + Tag color: blue; + + LineNr color: orange; background: white; + Question color: green; background: white; font-weight: bold; + + StatusLine color: white; background: black; + StatusLineBroken color: black; background: #FFa0a0 /* light-red */ + StatusLineSecure color: black; background: #a0a0FF /* light-blue */ + StatusLineExtended color: black; background: #a0FFa0 /* light-green */ + + TabClose,.tab-close-button + TabIcon,.tab-icon + TabText,.tab-text + TabNumber font-weight: bold; margin: 0px; padding-right: .3ex; + TabIconNumber { + font-weight: bold; + color: white; + text-align: center; + text-shadow: black -1px 0 1px, black 0 1px 1px, black 1px 0 1px, black 0 -1px 1px; + } + + Title color: magenta; background: white; font-weight: bold; + URL text-decoration: none; color: green; background: inherit; + URL:hover text-decoration: underline; cursor: pointer; + + FrameIndicator,,* { + background-color: red; + opacity: 0.5; + z-index: 999; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + } + + Bell border: none; background-color: black; + Hint,,* { + font-family: monospace; + font-size: 10px; + font-weight: bold; + color: white; + background-color: red; + border-color: ButtonShadow; + border-width: 0px; + border-style: solid; + padding: 0px 1px 0px 1px; + } + Hint::after,,* content: attr(number); + HintElem,,* background-color: yellow; color: black; + HintActive,,* background-color: #88FF00; color: black; + HintImage,,* opacity: .5; + + Help font-size: 8pt; line-height: 1.4em; font-family: -moz-fixed; + + HelpArg color: #6A97D4; + HelpOptionalArg color: #6A97D4; + + HelpBody display: block; margin: 1em auto; max-width: 100ex; + HelpBorder,*,dactyl://help/* border-color: silver; border-width: 0px; border-style: solid; + HelpCode display: block; white-space: pre; margin-left: 2em; font-family: Terminus, Fixed, monospace; + + HelpDefault margin-right: 1ex; white-space: pre; + + HelpDescription display: block; + HelpEm,html|em,dactyl://help/* font-weight: bold; font-style: normal; + + HelpEx display: inline-block; color: #527BBD; font-weight: bold; + + HelpExample display: block; margin: 1em 0; + HelpExample::before content: "Example: "; font-weight: bold; + + HelpInfo display: block; width: 20em; margin-left: auto; + HelpInfoLabel display: inline-block; width: 6em; color: magenta; font-weight: bold; vertical-align: text-top; + HelpInfoValue display: inline-block; width: 14em; text-decoration: none; vertical-align: text-top; + + HelpItem display: block; margin: 1em 1em 1em 10em; clear: both; + + HelpKey color: #102663; + + HelpLink,html|a,dactyl://help/* text-decoration: none; + HelpLink[href]:hover text-decoration: underline; + + HelpList,html|ul,dactyl://help/* display: block; list-style: outside disc; + HelpOrderedList,html|ol,dactyl://help/* display: block; list-style: outside decimal; + HelpListItem,html|li,dactyl://help/* display: list-item; + + HelpNote color: red; font-weight: bold; + + HelpOpt color: #106326; + HelpOptInfo display: inline-block; margin-bottom: 1ex; + + HelpParagraph,html|p,dactyl://help/* display: block; margin: 1em 0em; + HelpParagraph:first-child margin-top: 0; + HelpSpec display: block; margin-left: -10em; float: left; clear: left; color: #527BBD; + + HelpString display: inline-block; color: green; font-weight: normal; vertical-align: text-top; + HelpString::before content: '"'; + HelpString::after content: '"'; + HelpString[delim]::before content: attr(delim); + HelpString[delim]::after content: attr(delim); + + HelpHead,html|h1,dactyl://help/* { + display: block; + margin: 1em 0; + padding-bottom: .2ex; + border-bottom-width: 1px; + font-size: 2em; + font-weight: bold; + color: #527BBD; + clear: both; + } + HelpSubhead,html|h2,dactyl://help/* { + display: block; + margin: 1em 0; + padding-bottom: .2ex; + border-bottom-width: 1px; + font-size: 1.2em; + font-weight: bold; + color: #527BBD; + clear: both; + } + HelpSubsubhead,html|h3,dactyl://help/* { + display: block; + margin: 1em 0; + padding-bottom: .2ex; + font-size: 1.1em; + font-weight: bold; + color: #527BBD; + clear: both; + } + + HelpTOC + HelpTOC>ol ol margin-left: -1em; + + HelpTab,html|dl,dactyl://help/* display: table; width: 100%; margin: 1em 0; border-bottom-width: 1px; border-top-width: 1px; padding: .5ex 0; table-layout: fixed; + HelpTabColumn,html|column,dactyl://help/* display: table-column; + HelpTabColumn:first-child width: 25%; + HelpTabTitle,html|dt,dactyl://help/* display: table-cell; padding: .1ex 1ex; font-weight: bold; + HelpTabDescription,html|dd,dactyl://help/* display: table-cell; padding: .1ex 1ex; border-width: 0px; + HelpTabRow,html|dl>html|tr,dactyl://help/* display: table-row; + + HelpTag display: inline-block; color: #527BBD; margin-left: 1ex; font-size: 8pt; font-weight: bold; + HelpTags display: block; float: right; clear: right; + HelpTopic color: #102663; + HelpType margin-right: 2ex; + + HelpWarning color: red; font-weight: bold; + + Logo + + Search,,* { + font-size: inherit; + padding: 0; + color: black; + background-color: yellow; + } + ]]>.toString() }); // vim: set fdm=marker sw=4 ts=4 et: diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 26bb2461..320d6304 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -38,13 +38,6 @@ const Storage = Module("storage", { } }); -function Runnable(self, func, args) { - return { - QueryInterface: XPCOMUtils.generateQI([Ci.nsIRunnable]), - run: function () { func.apply(self, args || []); } - }; -} - const FailedAssertion = Class("FailedAssertion", Error, { init: function (message) { this.message = message; @@ -92,40 +85,6 @@ const Dactyl = Module("dactyl", { /** @property {Element} The currently focused element. */ get focus() document.commandDispatcher.focusedElement, - get extensions() { - const rdf = services.get("rdf"); - const extensionManager = services.get("extensionManager"); - - let extensions = extensionManager.getItemList(Ci.nsIUpdateItem.TYPE_EXTENSION, {}); - - function getRdfProperty(item, property) { - let resource = rdf.GetResource("urn:mozilla:item:" + item.id); - let value = ""; - - if (resource) { - let target = extensionManager.datasource.GetTarget(resource, - rdf.GetResource("http://www.mozilla.org/2004/em-rdf#" + property), true); - if (target && target instanceof Ci.nsIRDFLiteral) - value = target.Value; - } - - return value; - } - - //const Extension = Struct("id", "name", "description", "icon", "enabled", "version"); - return extensions.map(function (e) ({ - id: e.id, - name: e.name, - description: getRdfProperty(e, "description"), - enabled: getRdfProperty(e, "isDisabled") != "true", - icon: e.iconURL, - options: getRdfProperty(e, "optionsURL"), - version: e.version - })); - }, - - getExtension: function (name) this.extensions.filter(function (e) e.name == name)[0], - // Global constants CURRENT_TAB: [], NEW_TAB: [], @@ -183,7 +142,7 @@ const Dactyl = Module("dactyl", { beep: function () { // FIXME: popups clear the command line if (options["visualbell"]) { - dactyl.callInMainThread(function () { + util.callInMainThread(function () { // flash the visual bell let popup = document.getElementById("dactyl-visualbell"); let win = config.visualbellWindow; @@ -204,45 +163,6 @@ const Dactyl = Module("dactyl", { return false; // so you can do: if (...) return dactyl.beep(); }, - /** - * Creates a new thread. - */ - newThread: function () services.get("threadManager").newThread(0), - - /** - * Calls a function asynchronously on a new thread. - * - * @param {nsIThread} thread The thread to call the function on. If no - * thread is specified a new one is created. - * @optional - * @param {Object} self The 'this' object used when executing the - * function. - * @param {function} func The function to execute. - * - */ - callAsync: function (thread, self, func) { - thread = thread || services.get("threadManager").newThread(0); - thread.dispatch(Runnable(self, func, Array.slice(arguments, 3)), thread.DISPATCH_NORMAL); - }, - - /** - * Calls a function synchronously on a new thread. - * - * NOTE: Be sure to call GUI related methods like alert() or dump() - * ONLY in the main thread. - * - * @param {nsIThread} thread The thread to call the function on. If no - * thread is specified a new one is created. - * @optional - * @param {function} func The function to execute. - */ - callFunctionInThread: function (thread, func) { - thread = thread || services.get("threadManager").newThread(0); - - // DISPATCH_SYNC is necessary, otherwise strange things will happen - thread.dispatch(Runnable(null, func, Array.slice(arguments, 2)), thread.DISPATCH_SYNC); - }, - /** * Prints a message to the console. If msg is an object it is * pretty printed. @@ -488,18 +408,6 @@ const Dactyl = Module("dactyl", { */ has: function (feature) config.features.indexOf(feature) >= 0, - /** - * Returns whether the host application has the specified extension - * installed. - * - * @param {string} name The extension name. - * @returns {boolean} - */ - hasExtension: function (name) { - let extensions = services.get("extensionManager").getItemList(Ci.nsIUpdateItem.TYPE_EXTENSION, {}); - return extensions.some(function (e) e.name == name); - }, - /** * Returns the URL of the specified help topic if it exists. * @@ -622,6 +530,99 @@ const Dactyl = Module("dactyl", { } }, + exportHelp: function (path) { + const FILE = io.File(path); + const PATH = FILE.leafName.replace(/\..*/, "") + "/"; + const TIME = Date.now(); + + dactyl.initHelp(); + let zip = services.create("zipWriter"); + zip.open(FILE, File.MODE_CREATE | File.MODE_WRONLY | File.MODE_TRUNCATE); + function addURIEntry(file, uri) + zip.addEntryChannel(PATH + file, TIME, 9, + services.get("io").newChannel(uri, null, null), false); + function addDataEntry(file, data) // Inideal to an extreme. + addURIEntry(file, "data:text/plain;charset=UTF-8," + encodeURI(data)); + + let empty = util.Array.toObject( + "area base basefont br col frame hr img input isindex link meta param" + .split(" ").map(Array.concat)); + + let chrome = {}; + for (let [file,] in Iterator(services.get("dactyl:").FILE_MAP)) { + dactyl.open("dactyl://help/" + file); + dactyl.modules.events.waitForPageLoad(); + let data = [ + '\n', + '\n' + ]; + function fix(node) { + switch(node.nodeType) { + case Node.ELEMENT_NODE: + if (node instanceof HTMLScriptElement) + return; + + data.push("<"); data.push(node.localName); + if (node instanceof HTMLHtmlElement) + data.push(" xmlns=" + XHTML.uri.quote()); + + for (let { name: name, value: value } in util.Array.itervalues(node.attributes)) { + if (name == "dactyl:highlight") { + name = "class"; + value = "hl-" + value; + } + if (name == "href") { + if (value.indexOf("dactyl://help-tag/") == 0) + value = services.get("io").newChannel(value, null, null).originalURI.path.substr(1); + if (!/[#\/]/.test(value)) + value += ".xhtml"; + } + if (name == "src" && value.indexOf(":") > 0) { + chrome[value] = value.replace(/.*\//, "");; + value = value.replace(/.*\//, ""); + } + data.push(" "); + data.push(name); + data.push('="'); + data.push(<>{value}.toXMLString()); + data.push('"') + } + if (node.localName in empty) + data.push(" />"); + else { + data.push(">"); + if (node instanceof HTMLHeadElement) + data.push(.toXMLString()); + Array.map(node.childNodes, arguments.callee); + data.push(""); + } + break; + case Node.TEXT_NODE: + data.push(<>{node.textContent}.toXMLString()); + } + } + fix(content.document.documentElement); + addDataEntry(file + ".xhtml", data.join("")); + } + + let data = [h.selector.replace(/^\[.*?=(.*?)\]/, ".hl-$1").replace(/html\|/, "") + + "\t{" + h.value + "}" + for (h in highlight) if (/^Help|^Logo/.test(h.class))]; + + data = data.join("\n"); + addDataEntry("help.css", data.replace(/chrome:[^ ")]+\//g, "")); + + let re = /(chrome:[^ ");]+\/)([^ ");]+)/g; + while ((m = re.exec(data))) + chrome[m[0]] = m[2]; + + for (let [uri, leaf] in Iterator(chrome)) + addURIEntry(leaf, uri); + + zip.close(); + }, + /** * Opens the help page containing the specified topic if it * exists. @@ -1016,34 +1017,6 @@ const Dactyl = Module("dactyl", { return commands.parseArgs(cmdline, options, "*"); }, - sleep: function (delay) { - let mainThread = services.get("threadManager").mainThread; - - let end = Date.now() + delay; - while (Date.now() < end) - mainThread.processNextEvent(true); - return true; - }, - - callInMainThread: function (callback, self) { - let mainThread = services.get("threadManager").mainThread; - if (!services.get("threadManager").isMainThread) - mainThread.dispatch(Runnable(self, callback), mainThread.DISPATCH_NORMAL); - else - callback.call(self); - }, - - threadYield: function (flush, interruptable) { - let mainThread = services.get("threadManager").mainThread; - dactyl.interrupted = false; - do { - mainThread.processNextEvent(!flush); - if (dactyl.interrupted) - throw new Error("Interrupted"); - } - while (flush === true && mainThread.hasPendingEvents()); - }, - variableReference: function (string) { if (!string) return [null, null, null]; @@ -1097,7 +1070,7 @@ const Dactyl = Module("dactyl", { // return the platform normalized to Vim values getPlatformFeature: function () { - let platform = navigator.platform; + let platform = services.get("runtime").OS; return /^Mac/.test(platform) ? "MacUnix" : platform == "Win32" ? "Win32" : "Unix"; }, @@ -1521,7 +1494,6 @@ const Dactyl = Module("dactyl", { literal: 0 }); - // TODO: maybe indicate pending status too? commands.add(["extens[ions]", "exts"], "List available extensions", function (args) { @@ -1558,8 +1530,6 @@ const Dactyl = Module("dactyl", { }, { argCount: "?" }); - /////////////////////////////////////////////////////////////////////////// - commands.add(["exu[sage]"], "List all Ex commands with a short description", function (args) { Dactyl.showHelpIndex("ex-cmd-index", commands, args.bang); }, { @@ -1726,7 +1696,7 @@ const Dactyl = Module("dactyl", { else totalUnits = "msec"; - let str = template.commandOutput( + let str = template.commandOutput(commandline.command,
----- Auto Commands -----
@@ -1797,7 +1767,8 @@ const Dactyl = Module("dactyl", { if (args.bang) dactyl.open("about:"); else - dactyl.echo(template.commandOutput(<>{config.name} {dactyl.version} running on:
{navigator.userAgent})); + dactyl.echo(template.commandOutput(commandline.command, + <>{config.name} {dactyl.version} running on:
{navigator.userAgent})); }, { argCount: "0", bang: true diff --git a/common/content/editor.js b/common/content/editor.js index 3ea5a53b..ee77772f 100644 --- a/common/content/editor.js +++ b/common/content/editor.js @@ -333,7 +333,7 @@ const Editor = Module("editor", { dactyl.assert(args.length >= 1, "No editor specified"); args.push(path); - dactyl.callFunctionInThread(null, io.run, io.expandPath(args.shift()), args, true); + util.callInThread(null, io.run, io.expandPath(args.shift()), args, true); }, // TODO: clean up with 2 functions for textboxes and currentEditor? diff --git a/common/content/eval.js b/common/content/eval.js index 67db9523..7fc899f4 100644 --- a/common/content/eval.js +++ b/common/content/eval.js @@ -1,8 +1,6 @@ -try { __dactyl_eval_result = eval(__dactyl_eval_string); -} -catch (e) { - __dactyl_eval_error = e; -} +try { __dactyl_eval_result = eval(__dactyl_eval_string); } +catch (e) { __dactyl_eval_error = e; } + // IMPORTANT: The eval statement *must* remain on the first line // in order for line numbering in any errors to remain correct. diff --git a/common/content/events.js b/common/content/events.js index 7db54940..574dd3f9 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -288,7 +288,7 @@ const Events = Module("events", { commandline.quiet = quiet; try { - dactyl.threadYield(1, true); + util.threadYield(1, true); for (let [, evt_obj] in Iterator(events.fromString(keys))) { let elem = dactyl.focus || window.content; @@ -614,7 +614,7 @@ const Events = Module("events", { */ waitForPageLoad: function () { //dactyl.dump("start waiting in loaded state: " + buffer.loaded); - dactyl.threadYield(true); // clear queue + util.threadYield(true); // clear queue if (buffer.loaded == 1) return true; @@ -624,7 +624,7 @@ const Events = Module("events", { let end = start + (maxWaitTime * 1000); // maximum time to wait - TODO: add option let now; while (now = Date.now(), now < end) { - dactyl.threadYield(); + util.threadYield(); //if ((now - start) % 1000 < 10) // dactyl.dump("waited: " + (now - start) + " ms"); @@ -632,7 +632,7 @@ const Events = Module("events", { return false; if (buffer.loaded > 0) { - dactyl.sleep(250); + util.sleep(250); break; } else @@ -799,7 +799,7 @@ const Events = Module("events", { let urlbar = document.getElementById("urlbar"); if (elem == null && urlbar && urlbar.inputField == this._lastFocus) - dactyl.threadYield(true); + util.threadYield(true); if (dactyl.mode & (modes.EMBED | modes.INSERT | modes.TEXTAREA | modes.VISUAL)) modes.reset(); @@ -837,7 +837,7 @@ const Events = Module("events", { } if (key == "") - dactyl.interrupted = true; + util.interrupted = true; // feedingKeys needs to be separate from interrupted so // we can differentiate between a recorded diff --git a/common/content/finder.js b/common/content/finder.js index 5f139efc..463506ff 100644 --- a/common/content/finder.js +++ b/common/content/finder.js @@ -326,10 +326,10 @@ const RangeFind = Class("RangeFind", { }, focus: function() { - if(this.lastRange) + if (this.lastRange) var node = util.evaluateXPath(RangeFind.selectNodePath, this.range.document, this.lastRange.commonAncestorContainer).snapshotItem(0); - if(node) { + if (node) { node.focus(); // Rehighlight collapsed selection this.selectedRange = this.lastRange; diff --git a/common/content/hints.js b/common/content/hints.js index 8c10e4b8..4ffe9ca7 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -77,7 +77,7 @@ const Hints = Module("hints", { hints.escNumbers = false; if (this._activeTimeout) - clearTimeout(this._activeTimeout); + this._activeTimeout.cancel(); this._activeTimeout = null; }, @@ -504,7 +504,7 @@ const Hints = Module("hints", { // clear any timeout which might be active after pressing a number if (this._activeTimeout) { - clearTimeout(this._activeTimeout); + this._activeTimeout.cancel(); this._activeTimeout = null; } @@ -728,7 +728,7 @@ const Hints = Module("hints", { this._generate(win); // get all keys from the input queue - dactyl.threadYield(true); + util.threadYield(true); this._canUpdate = true; this._showHints(); @@ -761,7 +761,7 @@ const Hints = Module("hints", { // clear any timeout which might be active after pressing a number if (this._activeTimeout) { - clearTimeout(this._activeTimeout); + this._activeTimeout.cancel(); this._activeTimeout = null; } @@ -864,142 +864,107 @@ const Hints = Module("hints", { //}}} }, { - indexOf: (function () { - const table = [ - [0x00c0, 0x00c6, ["A"]], - [0x00c7, 0x00c7, ["C"]], - [0x00c8, 0x00cb, ["E"]], - [0x00cc, 0x00cf, ["I"]], - [0x00d1, 0x00d1, ["N"]], - [0x00d2, 0x00d6, ["O"]], - [0x00d8, 0x00d8, ["O"]], - [0x00d9, 0x00dc, ["U"]], - [0x00dd, 0x00dd, ["Y"]], - [0x00e0, 0x00e6, ["a"]], - [0x00e7, 0x00e7, ["c"]], - [0x00e8, 0x00eb, ["e"]], - [0x00ec, 0x00ef, ["i"]], - [0x00f1, 0x00f1, ["n"]], - [0x00f2, 0x00f6, ["o"]], - [0x00f8, 0x00f8, ["o"]], - [0x00f9, 0x00fc, ["u"]], - [0x00fd, 0x00fd, ["y"]], - [0x00ff, 0x00ff, ["y"]], - [0x0100, 0x0105, ["A", "a"]], - [0x0106, 0x010d, ["C", "c"]], - [0x010e, 0x0111, ["D", "d"]], - [0x0112, 0x011b, ["E", "e"]], - [0x011c, 0x0123, ["G", "g"]], - [0x0124, 0x0127, ["H", "h"]], - [0x0128, 0x0130, ["I", "i"]], - [0x0132, 0x0133, ["IJ", "ij"]], - [0x0134, 0x0135, ["J", "j"]], - [0x0136, 0x0136, ["K", "k"]], - [0x0139, 0x0142, ["L", "l"]], - [0x0143, 0x0148, ["N", "n"]], - [0x0149, 0x0149, ["n"]], - [0x014c, 0x0151, ["O", "o"]], - [0x0152, 0x0153, ["OE", "oe"]], - [0x0154, 0x0159, ["R", "r"]], - [0x015a, 0x0161, ["S", "s"]], - [0x0162, 0x0167, ["T", "t"]], - [0x0168, 0x0173, ["U", "u"]], - [0x0174, 0x0175, ["W", "w"]], - [0x0176, 0x0178, ["Y", "y", "Y"]], - [0x0179, 0x017e, ["Z", "z"]], - [0x0180, 0x0183, ["b", "B", "B", "b"]], - [0x0187, 0x0188, ["C", "c"]], - [0x0189, 0x0189, ["D"]], + get translitTable() function () { + const table = {}; + [ + [0x00c0, 0x00c6, ["A"]], [0x00c7, 0x00c7, ["C"]], + [0x00c8, 0x00cb, ["E"]], [0x00cc, 0x00cf, ["I"]], + [0x00d1, 0x00d1, ["N"]], [0x00d2, 0x00d6, ["O"]], + [0x00d8, 0x00d8, ["O"]], [0x00d9, 0x00dc, ["U"]], + [0x00dd, 0x00dd, ["Y"]], [0x00e0, 0x00e6, ["a"]], + [0x00e7, 0x00e7, ["c"]], [0x00e8, 0x00eb, ["e"]], + [0x00ec, 0x00ef, ["i"]], [0x00f1, 0x00f1, ["n"]], + [0x00f2, 0x00f6, ["o"]], [0x00f8, 0x00f8, ["o"]], + [0x00f9, 0x00fc, ["u"]], [0x00fd, 0x00fd, ["y"]], + [0x00ff, 0x00ff, ["y"]], [0x0100, 0x0105, ["A", "a"]], + [0x0106, 0x010d, ["C", "c"]], [0x010e, 0x0111, ["D", "d"]], + [0x0112, 0x011b, ["E", "e"]], [0x011c, 0x0123, ["G", "g"]], + [0x0124, 0x0127, ["H", "h"]], [0x0128, 0x0130, ["I", "i"]], + [0x0132, 0x0133, ["IJ", "ij"]], [0x0134, 0x0135, ["J", "j"]], + [0x0136, 0x0136, ["K", "k"]], [0x0139, 0x0142, ["L", "l"]], + [0x0143, 0x0148, ["N", "n"]], [0x0149, 0x0149, ["n"]], + [0x014c, 0x0151, ["O", "o"]], [0x0152, 0x0153, ["OE", "oe"]], + [0x0154, 0x0159, ["R", "r"]], [0x015a, 0x0161, ["S", "s"]], + [0x0162, 0x0167, ["T", "t"]], [0x0168, 0x0173, ["U", "u"]], + [0x0174, 0x0175, ["W", "w"]], [0x0176, 0x0178, ["Y", "y", "Y"]], + [0x0179, 0x017e, ["Z", "z"]], [0x0180, 0x0183, ["b", "B", "B", "b"]], + [0x0187, 0x0188, ["C", "c"]], [0x0189, 0x0189, ["D"]], [0x018a, 0x0192, ["D", "D", "d", "F", "f"]], [0x0193, 0x0194, ["G"]], [0x0197, 0x019b, ["I", "K", "k", "l", "l"]], [0x019d, 0x01a1, ["N", "n", "O", "O", "o"]], - [0x01a4, 0x01a5, ["P", "p"]], - [0x01ab, 0x01ab, ["t"]], + [0x01a4, 0x01a5, ["P", "p"]], [0x01ab, 0x01ab, ["t"]], [0x01ac, 0x01b0, ["T", "t", "T", "U", "u"]], - [0x01b2, 0x01d2, ["V", "Y", "y", "Z", "z", "D", "L", "N", "A", "a", "I", "i", "O", "o"]], - [0x01d3, 0x01dc, ["U", "u"]], - [0x01de, 0x01e1, ["A", "a"]], + [0x01b2, 0x01d2, ["V", "Y", "y", "Z", "z", "D", "L", "N", "A", "a", + "I", "i", "O", "o"]], + [0x01d3, 0x01dc, ["U", "u"]], [0x01de, 0x01e1, ["A", "a"]], [0x01e2, 0x01e3, ["AE", "ae"]], [0x01e4, 0x01ed, ["G", "g", "G", "g", "K", "k", "O", "o", "O", "o"]], [0x01f0, 0x01f5, ["j", "D", "G", "g"]], - [0x01fa, 0x01fb, ["A", "a"]], - [0x01fc, 0x01fd, ["AE", "ae"]], - [0x01fe, 0x0217, ["O", "o", "A", "a", "A", "a", "E", "e", "E", "e", "I", "i", "I", "i", "O", "o", "O", "o", "R", "r", "R", "r", "U", "u", "U", "u"]], + [0x01fa, 0x01fb, ["A", "a"]], [0x01fc, 0x01fd, ["AE", "ae"]], + [0x01fe, 0x0217, ["O", "o", "A", "a", "A", "a", "E", "e", "E", "e", + "I", "i", "I", "i", "O", "o", "O", "o", "R", "r", "R", "r", "U", + "u", "U", "u"]], [0x0253, 0x0257, ["b", "c", "d", "d"]], [0x0260, 0x0269, ["g", "h", "h", "i", "i"]], [0x026b, 0x0273, ["l", "l", "l", "l", "m", "n", "n"]], [0x027c, 0x028b, ["r", "r", "r", "r", "s", "t", "u", "u", "v"]], - [0x0290, 0x0291, ["z"]], - [0x029d, 0x02a0, ["j", "q"]], + [0x0290, 0x0291, ["z"]], [0x029d, 0x02a0, ["j", "q"]], [0x1e00, 0x1e09, ["A", "a", "B", "b", "B", "b", "B", "b", "C", "c"]], - [0x1e0a, 0x1e13, ["D", "d"]], - [0x1e14, 0x1e1d, ["E", "e"]], - [0x1e1e, 0x1e21, ["F", "f", "G", "g"]], - [0x1e22, 0x1e2b, ["H", "h"]], - [0x1e2c, 0x1e8f, ["I", "i", "I", "i", "K", "k", "K", "k", "K", "k", "L", "l", "L", "l", "L", "l", "L", "l", "M", "m", "M", "m", "M", "m", "N", "n", "N", "n", "N", "n", "N", "n", "O", "o", "O", "o", "O", "o", "O", "o", "P", "p", "P", "p", "R", "r", "R", "r", "R", "r", "R", "r", "S", "s", "S", "s", "S", "s", "S", "s", "S", "s", "T", "t", "T", "t", "T", "t", "T", "t", "U", "u", "U", "u", "U", "u", "U", "u", "U", "u", "V", "v", "V", "v", "W", "w", "W", "w", "W", "w", "W", "w", "W", "w", "X", "x", "X", "x", "Y", "y"]], + [0x1e0a, 0x1e13, ["D", "d"]], [0x1e14, 0x1e1d, ["E", "e"]], + [0x1e1e, 0x1e21, ["F", "f", "G", "g"]], [0x1e22, 0x1e2b, ["H", "h"]], + [0x1e2c, 0x1e8f, ["I", "i", "I", "i", "K", "k", "K", "k", "K", "k", + "L", "l", "L", "l", "L", "l", "L", "l", "M", "m", "M", "m", "M", + "m", "N", "n", "N", "n", "N", "n", "N", "n", "O", "o", "O", "o", + "O", "o", "O", "o", "P", "p", "P", "p", "R", "r", "R", "r", "R", + "r", "R", "r", "S", "s", "S", "s", "S", "s", "S", "s", "S", "s", + "T", "t", "T", "t", "T", "t", "T", "t", "U", "u", "U", "u", "U", + "u", "U", "u", "U", "u", "V", "v", "V", "v", "W", "w", "W", "w", + "W", "w", "W", "w", "W", "w", "X", "x", "X", "x", "Y", "y"]], [0x1e90, 0x1e9a, ["Z", "z", "Z", "z", "Z", "z", "h", "t", "w", "y", "a"]], - [0x1ea0, 0x1eb7, ["A", "a"]], - [0x1eb8, 0x1ec7, ["E", "e"]], - [0x1ec8, 0x1ecb, ["I", "i"]], - [0x1ecc, 0x1ee3, ["O", "o"]], - [0x1ee4, 0x1ef1, ["U", "u"]], - [0x1ef2, 0x1ef9, ["Y", "y"]], - [0x2071, 0x2071, ["i"]], - [0x207f, 0x207f, ["n"]], - [0x249c, 0x24b5, "a"], - [0x24b6, 0x24cf, "A"], + [0x1ea0, 0x1eb7, ["A", "a"]], [0x1eb8, 0x1ec7, ["E", "e"]], + [0x1ec8, 0x1ecb, ["I", "i"]], [0x1ecc, 0x1ee3, ["O", "o"]], + [0x1ee4, 0x1ef1, ["U", "u"]], [0x1ef2, 0x1ef9, ["Y", "y"]], + [0x2071, 0x2071, ["i"]], [0x207f, 0x207f, ["n"]], + [0x249c, 0x24b5, "a"], [0x24b6, 0x24cf, "A"], [0x24d0, 0x24e9, "a"], [0xfb00, 0xfb06, ["ff", "fi", "fl", "ffi", "ffl", "st", "st"]], - [0xff21, 0xff3a, "A"], - [0xff41, 0xff5a, "a"], - ].map(function (a) { - if (typeof a[2] == "string") - a[3] = function (chr) String.fromCharCode(this[2].charCodeAt(0) + chr - this[0]); - else - a[3] = function (chr) this[2][(chr - this[0]) % this[2].length]; - return a; + [0xff21, 0xff3a, "A"], [0xff41, 0xff5a, "a"], + ].forEach(function (start, stop, val) { + if (typeof a[2] != "string") + for (i=start; i <= stop; i++) + table[String.fromCharCode(i)] = val[(i - start) % val.length]; + else { + let n = val.charCodeAt(0); + for (i=start; i <= stop; i++) + table[String.fromCharCode(i)] = String.fromCharCode(n + i - start); + } }); - function translate(chr) { - var m, c = chr.charCodeAt(0); - var n = table.length; - var i = 0; - while (n) { - m = Math.floor(n / 2); - var t = table[i + m]; - if (c >= t[0] && c <= t[1]) - return t[3](c); - if (c < t[0] || m == 0) - n = m; - else { - i += m; - n = n - m; - } - } - return chr; - } - - return function indexOf(dest, src) { - var end = dest.length - src.length; - if (src.length == 0) - return 0; - outer: - for (var i = 0; i < end; i++) { - var j = i; - for (var k = 0; k < src.length;) { - var s = translate(dest[j++]); - for (var l = 0; l < s.length; l++, k++) { - if (s[l] != src[k]) - continue outer; - if (k == src.length - 1) - return i; - } + delete this.translitTable; + return this.translitTable = table; + }, + indexOf: function indexOf(dest, src) { + let table = this.translitTable; + var end = dest.length - src.length; + if (src.length == 0) + return 0; + outer: + for (var i = 0; i < end; i++) { + var j = i; + for (var k = 0; k < src.length;) { + var s = dest[j++]; + s = table[s] || s; + for (var l = 0; l < s.length; l++, k++) { + if (s[l] != src[k]) + continue outer; + if (k == src.length - 1) + return i; } } - return -1; - } - })(), + } + return -1; + }, Mode: Struct("prompt", "action", "tags") }, { diff --git a/common/content/history.js b/common/content/history.js index 262df4c3..abed5893 100644 --- a/common/content/history.js +++ b/common/content/history.js @@ -65,7 +65,7 @@ const History = Module("history", { if (current == start && steps < 0 || current == end && steps > 0) dactyl.beep(); else { - let index = util.Math.constrain(current + steps, start, end); + let index = Math.constrain(current + steps, start, end); window.getWebNavigation().gotoIndex(index); } }, diff --git a/common/content/io.js b/common/content/io.js index dfe16e5c..51a00e37 100755 --- a/common/content/io.js +++ b/common/content/io.js @@ -612,7 +612,6 @@ lookup: dactyl.echomsg("Cannot source a directory: " + filename.quote(), 0); else dactyl.echomsg("could not source: " + filename.quote(), 1); - dactyl.echoerr("E484: Can't open file " + filename); } @@ -621,7 +620,6 @@ lookup: dactyl.echomsg("sourcing " + filename.quote(), 2); - let str = file.read(); let uri = services.get("io").newFileURI(file); // handle pure JavaScript files specially @@ -643,6 +641,7 @@ lookup: else { let heredoc = ""; let heredocEnd = null; // the string which ends the heredoc + let str = file.read(); let lines = str.split(/\r\n|[\r\n]/); function execute(args) { command.execute(args, special, count, { setFrom: file }); } @@ -968,7 +967,8 @@ lookup: let output = io.system(arg); commandline.command = "!" + arg; - commandline.echo(template.commandOutput({output})); + commandline.echo(template.commandOutput(commandline.command, + {output})); autocommands.trigger("ShellCmdPost", {}); }, { @@ -979,12 +979,6 @@ lookup: }); }, completion: function () { - JavaScript.setCompleter([this.File, File.expandPath], - [function (context, obj, args) { - context.quote[2] = ""; - completion.file(context, true); - }]); - completion.charset = function (context) { context.anchored = false; context.generate = function () { @@ -1070,6 +1064,14 @@ lookup: completion.file(context, full); }); }, + javascript: function () { + JavaScript.setCompleter([this.File, File.expandPath], + [function (context, obj, args) { + context.quote[2] = ""; + completion.file(context, true); + }]); + + }, options: function () { var shell, shellcmdflag; if (dactyl.has("Win32")) { diff --git a/common/content/javascript.js b/common/content/javascript.js index f721e761..91c18a72 100644 --- a/common/content/javascript.js +++ b/common/content/javascript.js @@ -37,7 +37,7 @@ const JavaScript = Module("javascript", { "use strict"; const self = this; - if(obj == null) + if (obj == null) return; let orig = obj; @@ -319,7 +319,7 @@ const JavaScript = Module("javascript", { _complete: function (objects, key, compl, string, last) { const self = this; - if(!options["jsdebugger"] && !this.context.message) + if (!options["jsdebugger"] && !this.context.message) this.context.message = "For better completion data, please enable the JavaScript debugger (:set jsdebugger)"; let orig = compl; diff --git a/common/content/mappings.js b/common/content/mappings.js index 614982b2..eb595c05 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -127,6 +127,13 @@ const Mappings = Module("mappings", { init: function () { this._main = []; // default mappings this._user = []; // user created mappings + + dactyl.registerObserver("mode-add", function (mode) { + if (!(mode.mask in this._user || mode.mask in this._main)) { + this._main[mode.mask] = []; + this._user[mode.mask] = []; + } + }); }, _addMap: function (map) { @@ -189,13 +196,6 @@ const Mappings = Module("mappings", { */ getUserIterator: function (mode) this._mappingsIterator(mode, this._user), - addMode: function (mode) { - if (!(mode in this._user || mode in this._main)) { - this._main[mode] = []; - this._user[mode] = []; - } - }, - /** * Adds a new default key mapping. * @@ -477,6 +477,17 @@ const Mappings = Module("mappings", { [mode.disp.toLowerCase()]); }, completion: function () { + completion.userMapping = function userMapping(context, args, modes) { + // FIXME: have we decided on a 'standard' way to handle this clash? --djk + modes = modes || [modules.modes.NORMAL]; + + if (args.completeArg == 0) { + let maps = [[m.names[0], ""] for (m in mappings.getUserIterator(modes))]; + context.completions = maps; + } + }; + }, + javascript: function () { JavaScript.setCompleter(this.get, [ null, @@ -489,16 +500,6 @@ const Mappings = Module("mappings", { ]); } ]); - - completion.userMapping = function userMapping(context, args, modes) { - // FIXME: have we decided on a 'standard' way to handle this clash? --djk - modes = modes || [modules.modes.NORMAL]; - - if (args.completeArg == 0) { - let maps = [[m.names[0], ""] for (m in mappings.getUserIterator(modes))]; - context.completions = maps; - } - }; }, modes: function () { for (let mode in modes) { diff --git a/common/content/modes.js b/common/content/modes.js index dece106b..fd7611b4 100644 --- a/common/content/modes.js +++ b/common/content/modes.js @@ -139,7 +139,7 @@ const Modes = Module("modes", { options = extended; extended = false; } - this._modeMap[name] = this._modeMap[this[name]] = util.extend({ + let mode = util.extend({ extended: extended, count: true, input: false, @@ -147,11 +147,12 @@ const Modes = Module("modes", { name: name, disp: disp }, options); - this._modeMap[name].display = this._modeMap[name].display || function () disp; + mode.display = mode.display || function () disp; + this._modeMap[name] = mode; + this._modeMap[this[name]] = mode; if (!extended) this._mainModes.push(this[name]); - if ("mappings" in modules) - mappings.addMode(this[name]); + dactyl.triggerObserver("mode-add", mode); }, getMode: function (name) this._modeMap[name], diff --git a/common/content/modules.js b/common/content/modules.js index e5d5a2a5..b485157b 100644 --- a/common/content/modules.js +++ b/common/content/modules.js @@ -85,7 +85,7 @@ window.addEventListener("load", function () { set.add(seen, module.name); for (let dep in values(module.requires)) - load(Module.constructors[dep], module.name); + load(Module.constructors[dep], module.name, dep); dump("Load" + (isstring(prereq) ? " " + prereq + " dependency: " : ": ") + module.name); modules[module.name] = module(); diff --git a/common/content/options.js b/common/content/options.js index c7cda5c8..d60a773b 100644 --- a/common/content/options.js +++ b/common/content/options.js @@ -1269,10 +1269,6 @@ const Options = Module("options", { }); }, completion: function () { - JavaScript.setCompleter(this.get, [function () ([o.name, o.description] for (o in options))]); - JavaScript.setCompleter([this.getPref, this.safeSetPref, this.setPref, this.resetPref, this.invertPref], - [function () options.allPrefs().map(function (pref) [pref, ""])]); - completion.option = function option(context, scope) { context.title = ["Option"]; context.keys = { text: "names", description: "description" }; @@ -1350,6 +1346,11 @@ const Options = Module("options", { context.keys = { text: function (item) item, description: function (item) options.getPref(item) }; context.completions = options.allPrefs(); }; + }, + javascript: function () { + JavaScript.setCompleter(this.get, [function () ([o.name, o.description] for (o in options))]); + JavaScript.setCompleter([this.getPref, this.safeSetPref, this.setPref, this.resetPref, this.invertPref], + [function () options.allPrefs().map(function (pref) [pref, ""])]); } }); diff --git a/common/content/services.js b/common/content/services.js index 33b6acab..19cbfb09 100644 --- a/common/content/services.js +++ b/common/content/services.js @@ -29,16 +29,19 @@ const Services = Module("services", { this.add("environment", "@mozilla.org/process/environment;1", Ci.nsIEnvironment); this.add("extensionManager", "@mozilla.org/extensions/manager;1", Ci.nsIExtensionManager); this.add("favicon", "@mozilla.org/browser/favicon-service;1", Ci.nsIFaviconService); - this.add("history", "@mozilla.org/browser/global-history;2", [Ci.nsIGlobalHistory3, Ci.nsINavHistoryService, Ci.nsIBrowserHistory]); + this.add("history", "@mozilla.org/browser/global-history;2", [Ci.nsIBrowserHistory, Ci.nsIGlobalHistory3, Ci.nsINavHistoryService]); this.add("io", "@mozilla.org/network/io-service;1", Ci.nsIIOService); this.add("json", "@mozilla.org/dom/json;1", Ci.nsIJSON, "createInstance"); this.add("livemark", "@mozilla.org/browser/livemark-service;2", Ci.nsILivemarkService); this.add("observer", "@mozilla.org/observer-service;1", Ci.nsIObserverService); - this.add("pref", "@mozilla.org/preferences-service;1", [Ci.nsIPrefService, Ci.nsIPrefBranch, Ci.nsIPrefBranch2]); + this.add("pref", "@mozilla.org/preferences-service;1", [Ci.nsIPrefBranch, Ci.nsIPrefBranch2, Ci.nsIPrefService]); this.add("profile", "@mozilla.org/toolkit/profile-service;1", Ci.nsIToolkitProfileService); + this.add("runtime", "@mozilla.org/xre/runtime;1", [Ci.nsIXULAppInfo, Ci.nsIXULRuntime]); this.add("rdf", "@mozilla.org/rdf/rdf-service;1", Ci.nsIRDFService); this.add("sessionStore", "@mozilla.org/browser/sessionstore;1", Ci.nsISessionStore); + this.add("stylesheet", "@mozilla.org/content/style-sheet-service;1", Ci.nsIStyleSheetService); this.add("subscriptLoader", "@mozilla.org/moz/jssubscript-loader;1", Ci.mozIJSSubScriptLoader); + this.add("tagging", "@mozilla.org/browser/tagging-service;1", Ci.nsITaggingService); this.add("threadManager", "@mozilla.org/thread-manager;1", Ci.nsIThreadManager); this.add("windowMediator", "@mozilla.org/appshell/window-mediator;1", Ci.nsIWindowMediator); this.add("windowWatcher", "@mozilla.org/embedcomp/window-watcher;1", Ci.nsIWindowWatcher); @@ -48,6 +51,8 @@ const Services = Module("services", { this.addClass("file:", "@mozilla.org/network/protocol;1?name=file", Ci.nsIFileProtocolHandler); this.addClass("find", "@mozilla.org/embedcomp/rangefind;1", Ci.nsIFind); this.addClass("process", "@mozilla.org/process/util;1", Ci.nsIProcess); + this.addClass("timer", "@mozilla.org/timer;1", Ci.nsITimer); + this.addClass("xmlhttp", "@mozilla.org/xmlextras/xmlhttprequest;1", Ci.nsIXMLHttpRequest); this.addClass("zipWriter", "@mozilla.org/zipwriter;1", Ci.nsIZipWriter); if (!this.get("extensionManager")) @@ -81,7 +86,11 @@ const Services = Module("services", { * the service. */ add: function (name, class_, ifaces, meth) { - return this.services[name] = this._create(class_, ifaces, meth); + const self = this; + this.services.__defineGetter__(name, function () { + delete this[name]; + return this[name] = self._create(class_, ifaces, meth); + }); }, /** @@ -111,11 +120,9 @@ const Services = Module("services", { */ create: function (name) this.classes[name]() }, { -}, { - completion: function () { + javascript: function (dactyl, modules) { JavaScript.setCompleter(this.get, [function () services.services]); JavaScript.setCompleter(this.create, [function () [[c, ""] for (c in services.classes)]]); - } }); diff --git a/common/content/tabs.js b/common/content/tabs.js index a05a2979..a1b53949 100644 --- a/common/content/tabs.js +++ b/common/content/tabs.js @@ -819,7 +819,7 @@ const Tabs = Module("tabs", { let last = browser.mTabs.length - 1; - browser.moveTabTo(dummy, util.Math.constrain(tabIndex || last, 0, last)); + browser.moveTabTo(dummy, Math.constrain(tabIndex || last, 0, last)); browser.selectedTab = dummy; // required browser.swapBrowsersAndCloseOther(dummy, config.tabbrowser.mCurrentTab); }, { diff --git a/common/content/template.js b/common/content/template.js index 199f39e4..ffe9ea1e 100644 --- a/common/content/template.js +++ b/common/content/template.js @@ -12,7 +12,7 @@ const Template = Module("template", { map: function map(iter, func, sep, interruptable) { if (iter.length) // FIXME: Kludge? - iter = util.Array.itervalues(iter); + iter = array.itervalues(iter); let ret = <>; let n = 0; for each (let i in Iterator(iter)) { @@ -22,7 +22,7 @@ const Template = Module("template", { if (sep && n++) ret += sep; if (interruptable && n % interruptable == 0) - dactyl.threadYield(true, true); + util.threadYield(true, true); ret += val; } return ret; @@ -93,7 +93,8 @@ const Template = Module("template", {
Code execution summary
{ template.map(util.range(0, 100), function (i) -
) } + ) }
, @@ -118,6 +119,7 @@ const Template = Module("template", { // Using /foo*(:?)/ instead. if (processStrings) return {str.replace(/\{(.|\n)*(?:)/g, "{ ... }")}; + <>}; /* Vim */ return <>{arg}; case "undefined": return {arg}; @@ -136,7 +138,7 @@ const Template = Module("template", { } } catch (e) { - return]]>; + return ]]>; } }, @@ -190,8 +192,8 @@ const Template = Module("template", { return str; }, - commandOutput: function generic(xml) { - return <>:{commandline.command}
{xml}; + commandOutput: function generic(command, xml) { + return <>:{command}
{xml}; }, genericTable: function genericTable(items, format) { diff --git a/common/content/util.js b/common/content/util.js index a3393f2b..a0ae92c7 100644 --- a/common/content/util.js +++ b/common/content/util.js @@ -15,7 +15,16 @@ default xml namespace = XHTML; const Util = Module("util", { init: function () { - this.Array = Util.Array; + this.Array = array; + }, + + get activeWindow() services.get("windowWatcher").activeWindow, + callInMainThread: function (callback, self) { + let mainThread = services.get("threadManager").mainThread; + if (!services.get("threadManager").isMainThread) + mainThread.dispatch({ run: callback.call(self) }, mainThread.DISPATCH_NORMAL); + else + callback.call(self); }, /** @@ -25,7 +34,7 @@ const Util = Module("util", { * @returns {Object} */ cloneObject: function cloneObject(obj) { - if (obj instanceof Array) + if (isarray(obj)) return obj.slice(); let newObj = {}; for (let [k, v] in Iterator(obj)) @@ -62,7 +71,7 @@ const Util = Module("util", { * @returns {Object} */ computedStyle: function computedStyle(node) { - while (node instanceof Text && node.parentNode) + while (node instanceof Ci.nsIDOMText && node.parentNode) node = node.parentNode; return node.ownerDocument.defaultView.getComputedStyle(node, null); }, @@ -151,13 +160,7 @@ const Util = Module("util", { * @returns {string} */ escapeHTML: function escapeHTML(str) { - // XXX: the following code is _much_ slower than a simple .replace() - // :history display went down from 2 to 1 second after changing - // - // var e = window.content.document.createElement("div"); - // e.appendChild(window.content.document.createTextNode(str)); - // return e.innerHTML; - return str.replace(/&/g, "&").replace(//g, ">"); + return str.replace(/&/g, "&").replace(/doc + * @param {boolean} asIterator Whether to return the results as an + * XPath iterator. + */ + evaluateXPath: function (expression, doc, elem, asIterator) { + if (!doc) + doc = content.document; + if (!elem) + elem = doc; + if (isarray(expression)) + expression = util.makeXPath(expression); + + let result = doc.evaluate(expression, elem, + function lookupNamespaceURI(prefix) { + return { + xul: XUL.uri, + xhtml: XHTML.uri, + xhtml2: "http://www.w3.org/2002/06/xhtml2", + dactyl: NS.uri + }[prefix] || null; + }, + asIterator ? Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE : Ci.nsIDOMXPathResult.ORDERED_NODE_SNAPSHOT_TYPE, + null + ); + + return { + __proto__: result, + __iterator__: asIterator + ? function () { let elem; while ((elem = this.iterateNext())) yield elem; } + : function () { for (let i = 0; i < this.snapshotLength; i++) yield this.snapshotItem(i); } + } + }, + extend: function extend(dest) { Array.slice(arguments, 1).filter(util.identity).forEach(function (src) { for (let [k, v] in Iterator(src)) { @@ -201,38 +246,6 @@ const Util = Module("util", { return dest; }, - /** - * Returns an XPath union expression constructed from the specified node - * tests. An expression is built with node tests for both the null and - * XHTML namespaces. See {@link Buffer#evaluateXPath}. - * - * @param nodes {Array(string)} - * @returns {string} - */ - makeXPath: function makeXPath(nodes) { - return util.Array(nodes).map(util.debrace).flatten() - .map(function (node) [node, "xhtml:" + node]).flatten() - .map(function (node) "//" + node).join(" | "); - }, - - /** - * Memoize the lookup of a property in an object. - * - * @param {object} obj The object to alter. - * @param {string} key The name of the property to memoize. - * @param {function} getter A function of zero to two arguments which - * will return the property's value. obj is - * passed as the first argument, key as the - * second. - */ - memoize: function memoize(obj, key, getter) { - obj.__defineGetter__(key, function () { - delete obj[key]; - obj[key] = getter(obj, key); - return obj[key]; - }); - }, - /** * Returns the selection controller for the given window. * @@ -247,37 +260,6 @@ const Util = Module("util", { .getInterface(Ci.nsISelectionDisplay) .QueryInterface(Ci.nsISelectionController), - /** - * Split a string on literal occurrences of a marker. - * - * Specifically this ignores occurrences preceded by a backslash, or - * contained within 'single' or "double" quotes. - * - * It assumes backslash escaping on strings, and will thus not count quotes - * that are preceded by a backslash or within other quotes as starting or - * ending quoted sections of the string. - * - * @param {string} str - * @param {RegExp} marker - */ - splitLiteral: function splitLiteral(str, marker) { - let results = []; - let resep = RegExp(/^(([^\\'"]|\\.|'([^\\']|\\.)*'|"([^\\"]|\\.)*")*?)/.source + marker.source); - let cont = true; - - while (cont) { - cont = false; - str = str.replace(resep, function (match, before) { - results.push(before); - cont = true; - return ""; - }); - } - - results.push(str); - return results; - }, - /** * Converts bytes to a pretty printed data size string. * @@ -319,99 +301,6 @@ const Util = Module("util", { return strNum[0] + " " + unitVal[unitIndex]; }, - exportHelp: function (path) { - const FILE = io.File(path); - const PATH = FILE.leafName.replace(/\..*/, "") + "/"; - const TIME = Date.now(); - - dactyl.initHelp(); - let zip = services.create("zipWriter"); - zip.open(FILE, File.MODE_CREATE | File.MODE_WRONLY | File.MODE_TRUNCATE); - function addURIEntry(file, uri) - zip.addEntryChannel(PATH + file, TIME, 9, - services.get("io").newChannel(uri, null, null), false); - function addDataEntry(file, data) // Inideal to an extreme. - addURIEntry(file, "data:text/plain;charset=UTF-8," + encodeURI(data)); - - let empty = util.Array.toObject( - "area base basefont br col frame hr img input isindex link meta param" - .split(" ").map(Array.concat)); - - let chrome = {}; - for (let [file,] in Iterator(services.get("dactyl:").FILE_MAP)) { - dactyl.open("dactyl://help/" + file); - events.waitForPageLoad(); - let data = [ - '\n', - '\n' - ]; - function fix(node) { - switch(node.nodeType) { - case Node.ELEMENT_NODE: - if (node instanceof HTMLScriptElement) - return; - - data.push("<"); data.push(node.localName); - if (node instanceof HTMLHtmlElement) - data.push(" xmlns=" + XHTML.uri.quote()); - - for (let { name: name, value: value } in util.Array.itervalues(node.attributes)) { - if (name == "dactyl:highlight") { - name = "class"; - value = "hl-" + value; - } - if (name == "href") { - if (value.indexOf("dactyl://help-tag/") == 0) - value = services.get("io").newChannel(value, null, null).originalURI.path.substr(1); - if (!/[#\/]/.test(value)) - value += ".xhtml"; - } - if (name == "src" && value.indexOf(":") > 0) { - chrome[value] = value.replace(/.*\//, "");; - value = value.replace(/.*\//, ""); - } - data.push(" "); - data.push(name); - data.push('="'); - data.push(<>{value}.toXMLString()); - data.push('"') - } - if (node.localName in empty) - data.push(" />"); - else { - data.push(">"); - if (node instanceof HTMLHeadElement) - data.push(.toXMLString()); - Array.map(node.childNodes, arguments.callee); - data.push(""); - } - break; - case Node.TEXT_NODE: - data.push(<>{node.textContent}.toXMLString()); - } - } - fix(content.document.documentElement); - addDataEntry(file + ".xhtml", data.join("")); - } - - let data = [h.selector.replace(/^\[.*?=(.*?)\]/, ".hl-$1").replace(/html\|/, "") + - "\t{" + h.value + "}" - for (h in highlight) if (/^Help|^Logo/.test(h.class))]; - - data = data.join("\n"); - addDataEntry("help.css", data.replace(/chrome:[^ ")]+\//g, "")); - - let re = /(chrome:[^ ");]+\/)([^ ");]+)/g; - while ((m = re.exec(data))) - chrome[m[0]] = m[2]; - - for (let [uri, leaf] in Iterator(chrome)) - addURIEntry(leaf, uri); - - zip.close(); - }, - /** * Sends a synchronous or asynchronous HTTP request to url and * returns the XMLHttpRequest object. If callback is specified the @@ -424,7 +313,7 @@ const Util = Module("util", { */ httpGet: function httpGet(url, callback) { try { - let xmlhttp = new XMLHttpRequest(); + let xmlhttp = services.create("xmlhttp"); xmlhttp.mozBackgroundRequest = true; if (callback) { xmlhttp.onreadystatechange = function () { @@ -437,53 +326,11 @@ const Util = Module("util", { return xmlhttp; } catch (e) { - dactyl.log("Error opening " + url + ": " + e, 1); + dactyl.log("Error opening " + String.quote(url) + ": " + e, 1); return null; } }, - /** - * Evaluates an XPath expression in the current or provided - * document. It provides the xhtml, xhtml2 and dactyl XML - * namespaces. The result may be used as an iterator. - * - * @param {string} expression The XPath expression to evaluate. - * @param {Document} doc The document to evaluate the expression in. - * @default The current document. - * @param {Node} elem The context element. - * @default doc - * @param {boolean} asIterator Whether to return the results as an - * XPath iterator. - */ - evaluateXPath: function (expression, doc, elem, asIterator) { - if (!doc) - doc = window.content.document; - if (!elem) - elem = doc; - if (isarray(expression)) - expression = util.makeXPath(expression); - - let result = doc.evaluate(expression, elem, - function lookupNamespaceURI(prefix) { - return { - xul: XUL.uri, - xhtml: XHTML.uri, - xhtml2: "http://www.w3.org/2002/06/xhtml2", - dactyl: NS.uri - }[prefix] || null; - }, - asIterator ? XPathResult.ORDERED_NODE_ITERATOR_TYPE : XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, - null - ); - - return { - __proto__: result, - __iterator__: asIterator - ? function () { let elem; while ((elem = this.iterateNext())) yield elem; } - : function () { for (let i = 0; i < this.snapshotLength; i++) yield this.snapshotItem(i); } - } - }, - /** * The identity function. * @@ -508,6 +355,20 @@ const Util = Module("util", { bottom: Math.min(r1.bottom, r2.bottom) }), + /** + * Returns an XPath union expression constructed from the specified node + * tests. An expression is built with node tests for both the null and + * XHTML namespaces. See {@link Buffer#evaluateXPath}. + * + * @param nodes {Array(string)} + * @returns {string} + */ + makeXPath: function makeXPath(nodes) { + return util.Array(nodes).map(util.debrace).flatten() + .map(function (node) [node, "xhtml:" + node]).flatten() + .map(function (node) "//" + node).join(" | "); + }, + /** * Returns the array that results from applying func to each * property of obj. @@ -524,21 +385,16 @@ const Util = Module("util", { }, /** - * Math utility methods. - * @singleton + * Memoize the lookup of a property in an object. + * + * @param {object} obj The object to alter. + * @param {string} key The name of the property to memoize. + * @param {function} getter A function of zero to two arguments which + * will return the property's value. obj is + * passed as the first argument, key as the + * second. */ - Math: { - /** - * Returns the specified value constrained to the range min - - * max. - * - * @param {number} value The value to constrain. - * @param {number} min The minimum constraint. - * @param {number} max The maximum constraint. - * @returns {number} - */ - constrain: function constrain(value, min, max) Math.min(Math.max(min, value), max) - }, + memoize: memoize, /** * Converts a URI string into a URI object. @@ -578,7 +434,7 @@ const Util = Module("util", { [XHTML, 'html'], [XUL, 'xul'] ]); - if (object instanceof Element) { + if (object instanceof Ci.nsIDOMElement) { let elem = object; if (elem.nodeType == elem.TEXT_NODE) return elem.data; @@ -616,10 +472,12 @@ const Util = Module("util", { let keys = []; try { // window.content often does not want to be queried with "var i in object" let hasValue = !("__iterator__" in object); + /* if (modules.isPrototypeOf(object)) { object = Iterator(object); hasValue = false; } + */ for (let i in object) { let value = ]]>; try { @@ -627,7 +485,7 @@ const Util = Module("util", { } catch (e) {} if (!hasValue) { - if (i instanceof Array && i.length == 2) + if (isarray(i) && i.length == 2) [i, value] = i; else var noVal = true; @@ -689,7 +547,7 @@ const Util = Module("util", { let endTime = Date.now() + time; while (start < end) { if (Date.now() > endTime) { - dactyl.threadYield(true, true); + util.threadYield(true, true); endTime = Date.now() + time; } yield start++; @@ -746,6 +604,58 @@ const Util = Module("util", { elem.scrollIntoView(); }, + sleep: function (delay) { + let mainThread = services.get("threadManager").mainThread; + + let end = Date.now() + delay; + while (Date.now() < end) + mainThread.processNextEvent(true); + return true; + }, + + /** + * Split a string on literal occurrences of a marker. + * + * Specifically this ignores occurrences preceded by a backslash, or + * contained within 'single' or "double" quotes. + * + * It assumes backslash escaping on strings, and will thus not count quotes + * that are preceded by a backslash or within other quotes as starting or + * ending quoted sections of the string. + * + * @param {string} str + * @param {RegExp} marker + */ + splitLiteral: function splitLiteral(str, marker) { + let results = []; + let resep = RegExp(/^(([^\\'"]|\\.|'([^\\']|\\.)*'|"([^\\"]|\\.)*")*?)/.source + marker.source); + let cont = true; + + while (cont) { + cont = false; + str = str.replace(resep, function (match, before) { + results.push(before); + cont = true; + return ""; + }); + } + + results.push(str); + return results; + }, + + threadYield: function (flush, interruptable) { + let mainThread = services.get("threadManager").mainThread; + /* FIXME */ + util.interrupted = false; + do { + mainThread.processNextEvent(!flush); + if (util.interrupted) + throw new Error("Interrupted"); + } + while (flush === true && mainThread.hasPendingEvents()); + }, + /** * Converts an E4X XML literal to a DOM node. * @@ -780,133 +690,26 @@ const Util = Module("util", { } } }, { - // TODO: Why don't we just push all util.BuiltinType up into modules? --djk - /** - * Array utility methods. - */ - Array: Class("Array", Array, { - init: function (ary) { - return { - __proto__: ary, - __iterator__: function () this.iteritems(), - __noSuchMethod__: function (meth, args) { - var res = util.Array[meth].apply(null, [this.__proto__].concat(args)); - - if (util.Array.isinstance(res)) - return util.Array(res); - return res; - }, - toString: function () this.__proto__.toString(), - concat: function () this.__proto__.concat.apply(this.__proto__, arguments), - map: function () this.__noSuchMethod__("map", Array.slice(arguments)) - }; - } - }, { - isinstance: function isinstance(obj) { - return Object.prototype.toString.call(obj) == "[object Array]"; - }, - - /** - * Converts an array to an object. As in lisp, an assoc is an - * array of key-value pairs, which maps directly to an object, - * as such: - * [["a", "b"], ["c", "d"]] -> { a: "b", c: "d" } - * - * @param {Array[]} assoc - * @... {string} 0 - Key - * @... 1 - Value - */ - toObject: function toObject(assoc) { - let obj = {}; - assoc.forEach(function ([k, v]) { obj[k] = v; }); - return obj; - }, - - /** - * Compacts an array, removing all elements that are null or undefined: - * ["foo", null, "bar", undefined] -> ["foo", "bar"] - * - * @param {Array} ary - * @returns {Array} - */ - compact: function compact(ary) ary.filter(function (item) item != null), - - /** - * Flattens an array, such that all elements of the array are - * joined into a single array: - * [["foo", ["bar"]], ["baz"], "quux"] -> ["foo", ["bar"], "baz", "quux"] - * - * @param {Array} ary - * @returns {Array} - */ - flatten: function flatten(ary) ary.length ? Array.concat.apply([], ary) : [], - - /** - * Returns an Iterator for an array's values. - * - * @param {Array} ary - * @returns {Iterator(Object)} - */ - itervalues: function itervalues(ary) { - let length = ary.length; - for (let i = 0; i < length; i++) - yield ary[i]; - }, - - /** - * Returns an Iterator for an array's indices and values. - * - * @param {Array} ary - * @returns {Iterator([{number}, {Object}])} - */ - iteritems: function iteritems(ary) { - let length = ary.length; - for (let i = 0; i < length; i++) - yield [i, ary[i]]; - }, - - /** - * Filters out all duplicates from an array. If - * unsorted is false, the array is sorted before - * duplicates are removed. - * - * @param {Array} ary - * @param {boolean} unsorted - * @returns {Array} - */ - uniq: function uniq(ary, unsorted) { - let ret = []; - if (unsorted) { - for (let [, item] in Iterator(ary)) - if (ret.indexOf(item) == -1) - ret.push(item); - } - else { - for (let [, item] in Iterator(ary.sort())) { - if (item != last || !ret.length) - ret.push(item); - var last = item; - } - } - return ret; - }, - - /** - * Zips the contents of two arrays. The resulting array is twice the - * length of ary1, with any shortcomings of ary2 replaced with null - * strings. - * - * @param {Array} ary1 - * @param {Array} ary2 - * @returns {Array} - */ - zip: function zip(ary1, ary2) { - let res = [] - for(let [i, item] in Iterator(ary1)) - res.push(item, i in ary2 ? ary2[i] : ""); - return res; - } - }) + Array: array }); +/** + * Math utility methods. + * @singleton + */ +var Math = { + __proto__: window.Math, + + /** + * Returns the specified value constrained to the range min - + * max. + * + * @param {number} value The value to constrain. + * @param {number} min The minimum constraint. + * @param {number} max The maximum constraint. + * @returns {number} + */ + constrain: function constrain(value, min, max) Math.min(Math.max(min, value), max) +}; + // vim: set fdm=marker sw=4 ts=4 et: