diff options
Diffstat (limited to 'common/content')
31 files changed, 1306 insertions, 980 deletions
diff --git a/common/content/autocommands.js b/common/content/autocommands.js index 8acf6c36..a9675f42 100644 --- a/common/content/autocommands.js +++ b/common/content/autocommands.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k@gmail.com> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -18,7 +18,7 @@ const AutoCommands = Module("autocommands", { this._store = []; }, - __iterator__: function () util.Array.itervalues(this._store), + __iterator__: function () array.itervalues(this._store), /** * Adds a new autocommand. <b>cmd</b> will be executed when one of the @@ -94,7 +94,7 @@ const AutoCommands = Module("autocommands", { + template.map(items, function (item) <tr> - <td> {item.pattern.source}</td> + <td> {item.pattern.source}</td> <td>{item.command}</td> </tr>)) } diff --git a/common/content/bookmarks.js b/common/content/bookmarks.js index ca83d81e..45c3ae8c 100644 --- a/common/content/bookmarks.js +++ b/common/content/bookmarks.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k@gmail.com> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -11,13 +11,11 @@ const DEFAULT_FAVICON = "chrome://mozapps/skin/places/defaultFavicon.png"; // also includes methods for dealing with keywords and search engines const Bookmarks = Module("bookmarks", { init: function () { - let bookmarkObserver = function (key, event, arg) { + storage.addObserver("bookmark-cache", function (key, event, arg) { if (event == "add") autocommands.trigger("BookmarkAdd", arg); statusline.updateUrl(); - }; - - storage.addObserver("bookmark-cache", bookmarkObserver, window); + }, window); }, get format() ({ @@ -36,7 +34,8 @@ const Bookmarks = Module("bookmarks", { add: function add(starOnly, title, url, keyword, tags, force) { try { let uri = util.createURI(url); - if (!force) { + if (!force && bookmarks.isBookmarked(uri.spec)) { + // WTF? This seems wrong... --Kris for (let bmark in bookmarkcache) { if (bmark[0] == uri.spec) { var id = bmark[5]; @@ -74,21 +73,22 @@ const Bookmarks = Module("bookmarks", { let count = this.remove(url); if (count > 0) - commandline.echo("Removed bookmark: " + url, commandline.HL_NORMAL, commandline.FORCE_SINGLELINE); + dactyl.echomsg({ domains: [util.getHost(url)], message: "Removed bookmark: " + url }); else { let title = buffer.title || url; let extra = ""; if (title != url) extra = " (" + title + ")"; this.add(true, title, url); - commandline.echo("Added bookmark: " + url + extra, commandline.HL_NORMAL, commandline.FORCE_SINGLELINE); + dactyl.echomsg({ domains: [util.getHost(url)], message: "Added bookmark: " + url + extra }); } }, isBookmarked: function isBookmarked(url) { try { - return services.get("bookmarks").getBookmarkIdsForURI(makeURI(url), {}) - .some(bookmarkcache.isRegularBookmark); + return services.get("bookmarks") + .getBookmarkIdsForURI(makeURI(url), {}) + .some(bookmarkcache.closure.isRegularBookmark); } catch (e) { return false; @@ -99,13 +99,14 @@ const Bookmarks = Module("bookmarks", { remove: function remove(url) { try { let uri = util.newURI(url); - let bmarks = services.get("bookmarks").getBookmarkIdsForURI(uri, {}) - .filter(bookmarkcache.isRegularBookmark); + let bmarks = services.get("bookmarks") + .getBookmarkIdsForURI(uri, {}) + .filter(bookmarkcache.closure.isRegularBookmark); bmarks.forEach(services.get("bookmarks").removeItem); return bmarks.length; } catch (e) { - dactyl.log(e, 0); + dactyl.reportError(e); return 0; } }, @@ -287,7 +288,7 @@ const Bookmarks = Module("bookmarks", { args.completeFilter = have.pop(); let prefix = filter.substr(0, filter.length - args.completeFilter.length); - let tags = array.uniq(util.Array.flatten([b.tags for ([k, b] in Iterator(bookmarkcache.bookmarks))])); + let tags = array.uniq(array.flatten([b.tags for ([k, b] in Iterator(bookmarkcache.bookmarks))])); return [[prefix + tag, tag] for ([i, tag] in Iterator(tags)) if (have.indexOf(tag) < 0)]; }, @@ -330,7 +331,8 @@ const Bookmarks = Module("bookmarks", { if (bookmarks.add(false, title, url, keyword, tags, args.bang)) { let extra = (title == url) ? "" : " (" + title + ")"; - dactyl.echomsg("Added bookmark: " + url + extra, 1, commandline.FORCE_SINGLELINE); + dactyl.echomsg({ domains: [util.getHost(url)], message: "Added bookmark: " + url + extra }, + 1, commandline.FORCE_SINGLELINE); } else dactyl.echoerr("Exxx: Could not add bookmark " + title.quote(), commandline.FORCE_SINGLELINE); @@ -385,7 +387,8 @@ const Bookmarks = Module("bookmarks", { let url = args.string || buffer.URL; let deletedCount = bookmarks.remove(url); - dactyl.echomsg(deletedCount + " bookmark(s) with url " + url.quote() + " deleted", 1, commandline.FORCE_SINGLELINE); + dactyl.echomsg({ domains: [util.getHost(url)], message: deletedCount + " bookmark(s) with url " + url.quote() + " deleted" }, + 1, commandline.FORCE_SINGLELINE); } }, @@ -493,13 +496,12 @@ const Bookmarks = Module("bookmarks", { return history.get({ uri: window.makeURI(begin), uriIsPrefix: true }).map(function (item) { let rest = item.url.length - end.length; let query = item.url.substring(begin.length, rest); - if (item.url.substr(rest) == end && query.indexOf("&") == -1) { + if (item.url.substr(rest) == end && query.indexOf("&") == -1) try { - item.url = decodeURIComponent(query.replace(/#.*/, "")); + item.url = decodeURIComponent(query.replace(/#.*/, "").replace(/\+/g, " ")); return item; } catch (e) {} - } return null; }).filter(util.identity); }; diff --git a/common/content/browser.js b/common/content/browser.js index 4334ffc6..e709cca7 100644 --- a/common/content/browser.js +++ b/common/content/browser.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k at Gmail> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. diff --git a/common/content/buffer.js b/common/content/buffer.js index 4cc84adb..3000d059 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k at Gmail> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -84,7 +84,7 @@ const Buffer = Module("buffer", { const ACCESS_READ = Ci.nsICache.ACCESS_READ; let cacheKey = doc.location.toString().replace(/#.*$/, ""); - for (let proto in util.Array.itervalues(["HTTP", "FTP"])) { + for (let proto in array.itervalues(["HTTP", "FTP"])) { try { var cacheEntryDescriptor = services.get("cache").createSession(proto, 0, true) .openCacheEntry(cacheKey, ACCESS_READ, false); @@ -174,9 +174,11 @@ const Buffer = Module("buffer", { // event listener which is is called on each page load, even if the // page is loaded in a background tab onPageLoad: function onPageLoad(event) { - if (!dactyl.helpInitialized && event.originalTarget instanceof Document) - if (/^dactyl:/.test(event.originalTarget.location.href)) + if (event.originalTarget instanceof Document) + if (/^dactyl:/.test(event.originalTarget.location.href)) { dactyl.initHelp(); + config.styleHelp(); + } if (event.originalTarget instanceof HTMLDocument) { let doc = event.originalTarget; @@ -197,7 +199,7 @@ const Buffer = Module("buffer", { doc.pageIsFullyLoaded = 1; if (doc != config.browser.contentDocument) - dactyl.echomsg("Background tab loaded: " + doc.title || doc.location.href, 3); + dactyl.echomsg({ domains: [util.getHost(doc.location.href)], message: "Background tab loaded: " + doc.title || doc.location.href }, 3); this._triggerLoadAutocmd("PageLoad", doc); } @@ -206,7 +208,11 @@ const Buffer = Module("buffer", { /** * @property {Object} The document loading progress listener. */ - progressListener: update({ __proto__: window.XULBrowserWindow }, { + progressListener: update(Object.create(window.XULBrowserWindow), { + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIWebProgressListener]), + + loadCount: 0, + // XXX: function may later be needed to detect a canceled synchronous openURL() onStateChange: function onStateChange(webProgress, request, flags, status) { onStateChange.superapply(this, arguments); @@ -223,7 +229,7 @@ const Buffer = Module("buffer", { // don't reset mode if a frame of the frameset gets reloaded which // is not the focused frame - if (document.commandDispatcher.focusedWindow == webProgress.DOMWindow) { + if (document.commandDispatcher.focusedWindow == webProgress.DOMWindow && this.loadCount++) { util.timeout(function () { modes.reset(false); }, dactyl.mode == modes.HINTS ? 500 : 0); } @@ -1012,7 +1018,7 @@ const Buffer = Module("buffer", { if (win.scrollMaxX > 0 || win.scrollMaxY > 0) return win; - for (let frame in util.Array.itervalues(win.frames)) + for (let frame in array.itervalues(win.frames)) if (frame.scrollMaxX > 0 || frame.scrollMaxY > 0) return frame; @@ -1314,39 +1320,39 @@ const Buffer = Module("buffer", { group[1].push([i, tab.linkedBrowser]); }); - let orig = context; - for (let [id, [name, browsers]] in Iterator(tabGroups)) { - context = orig.fork(id, 0); - context.anchored = false; - context.title = [name || "Buffers"]; - context.keys = { text: "text", description: "url", icon: "icon" }; - context.compare = CompletionContext.Sort.number; - let process = context.process[0]; - context.process = [function (item, text) - <> - <span highlight="Indicator" style="display: inline-block;">{item.item.indicator}</span> - { process.call(this, item, text) } - </>]; - - context.completions = util.map(util.Array.itervalues(browsers), function ([i, browser]) { - let indicator = " "; - if (i == tabs.index()) - indicator = "%" - else if (i == tabs.index(tabs.alternate)) - indicator = "#"; - - let tab = tabs.getTab(i); - let url = browser.contentDocument.location.href; - i = i + 1; - - return { - text: [i + ": " + (tab.label || "(Untitled)"), i + ": " + url], - url: template.highlightURL(url), - indicator: indicator, - icon: tab.image || DEFAULT_FAVICON - }; - }); - } + context.pushProcessor(0, function (item, text, next) <> + <span highlight="Indicator" style="display: inline-block;">{item.item.indicator}</span> + { next.call(this, item, text) } + </>); + context.process[1] = function (item, text) template.highlightURL(text); + + context.anchored = false; + context.keys = { text: "text", description: "url", icon: "icon" }; + context.compare = CompletionContext.Sort.number; + + for (let [id, vals] in Iterator(tabGroups)) + context.fork(id, 0, this, function (context, [name, browsers]) { + context.title = [name || "Buffers"]; + context.generate = function () + util.map(array.itervalues(browsers), function ([i, browser]) { + let indicator = " "; + if (i == tabs.index()) + indicator = "%" + else if (i == tabs.index(tabs.alternate)) + indicator = "#"; + + let tab = tabs.getTab(i); + let url = browser.contentDocument.location.href; + i = i + 1; + + return { + text: [i + ": " + (tab.label || "(Untitled)"), i + ": " + url], + url: url, + indicator: indicator, + icon: tab.image || DEFAULT_FAVICON + }; + }); + }, vals); }; }, events: function () { @@ -1354,6 +1360,15 @@ const Buffer = Module("buffer", { config.browser.removeProgressListener(window.XULBrowserWindow); } catch (e) {} // Why? --djk + + // I hate this whole hack. --Kris + let obj = window.XULBrowserWindow, getter; + for (let p in properties(obj)) + if ((getter = obj.__lookupGetter__(p)) && !obj.__lookupSetter__(p)) { + this.progressListener.__defineGetter__(p, getter); + delete obj[p]; + } + config.browser.addProgressListener(this.progressListener, Ci.nsIWebProgress.NOTIFY_ALL); window.XULBrowserWindow = this.progressListener; window.QueryInterface(Ci.nsIInterfaceRequestor) @@ -1503,7 +1518,8 @@ const Buffer = Module("buffer", { 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"; + return computedStyle.visibility != "hidden" && computedStyle.display != "none" && + computedStyle.MozUserFocus != "ignore"; }); dactyl.assert(elements.length > 0); @@ -1617,13 +1633,13 @@ const Buffer = Module("buffer", { function () { buffer.showPageInfo(true); }); }, options: function () { - options.add(["nextpattern"], // \u00BB is » (>> in a single char) + options.add(["nextpattern"], "Patterns to use when guessing the 'next' page in a document sequence", - "stringlist", "\\bnext\\b,^>$,^(>>|\u00BB)$,^(>|\u00BB),(>|\u00BB)$,\\bmore\\b"); + "stringlist", UTF8("\\bnext\\b,^>$,^(>>|»)$,^(>|»),(>|»)$,\\bmore\\b")); - options.add(["previouspattern"], // \u00AB is « (<< in a single char) + options.add(["previouspattern"], "Patterns to use when guessing the 'previous' page in a document sequence", - "stringlist", "\\bprev|previous\\b,^<$,^(<<|\u00AB)$,^(<|\u00AB),(<|\u00AB)$"); + "stringlist", UTF8("\\bprev|previous\\b,^<$,^(<<|«)$,^(<|«),(<|«)$")); options.add(["pageinfo", "pa"], "Desired info in the :pageinfo output", diff --git a/common/content/commandline.js b/common/content/commandline.js index c7552aaf..9039b7b1 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k@gmail.com> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -103,31 +103,32 @@ const CommandLine = Module("commandline", { ////////////////////// VARIABLES /////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ - this._completionList = ItemList("dactyl-completions"); + memoize(this, "_completionList", function () ItemList("dactyl-completions")); this._completions = null; this._history = null; this._startHints = false; // whether we're waiting to start hints mode this._lastSubstring = ""; - this.widgets = { - commandline: document.getElementById("dactyl-commandline"), - prompt: document.getElementById("dactyl-commandline-prompt"), - command: document.getElementById("dactyl-commandline-command"), + memoize(this, "widgets", function () { + let widgets = { + commandline: document.getElementById("dactyl-commandline"), + prompt: document.getElementById("dactyl-commandline-prompt"), + command: document.getElementById("dactyl-commandline-command"), - message: document.getElementById("dactyl-message"), + message: document.getElementById("dactyl-message"), - 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); + multilineOutput: document.getElementById("dactyl-multiline-output"), + multilineInput: document.getElementById("dactyl-multiline-input"), + }; - // the widget used for multiline output - this._outputContainer = this.widgets.multilineOutput.parentNode; + widgets.command.inputField.QueryInterface(Ci.nsIDOMNSEditableElement); + widgets.message.inputField.QueryInterface(Ci.nsIDOMNSEditableElement); + widgets.mowContainer = widgets.multilineOutput.parentNode; - this.widgets.multilineOutput.contentDocument.body.id = "dactyl-multiline-output-content"; + widgets.multilineOutput.contentDocument.body.id = "dactyl-multiline-output-content"; + return widgets; + }); // 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" @@ -181,12 +182,14 @@ const CommandLine = Module("commandline", { } }, + /** * Highlight the messageBox according to <b>group</b>. */ - _setHighlightGroup: function (group) { - this.widgets.message.setAttributeNS(NS.uri, "highlight", group); + set highlightGroup(group) { + highlight.highlightNode(this.widgets.message, group); }, + get highlightGroup() this.widgets.message.getAttributeNS(NS.uri, "highlight"), /** * Determines whether the command line should be visible. @@ -206,7 +209,7 @@ const CommandLine = Module("commandline", { 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); + highlight.highlightNode(this.widgets.prompt, highlightGroup || commandline.HL_NORMAL); }, /** @@ -229,8 +232,8 @@ const CommandLine = Module("commandline", { * @param {boolean} forceSingle If provided, don't let over-long * messages move to the MOW. */ - _echoLine: function (str, highlightGroup, forceSingle) { - this._setHighlightGroup(highlightGroup); + _echoLine: function echoLine(str, highlightGroup, forceSingle) { + this.highlightGroup = highlightGroup; this.widgets.message.value = str; dactyl.triggerObserver("echoLine", str, highlightGroup, forceSingle); @@ -250,7 +253,7 @@ const CommandLine = Module("commandline", { * @param {string} highlightGroup */ // TODO: resize upon a window resize - _echoMultiline: function (str, highlightGroup) { + _echoMultiline: function echoMultiline(str, highlightGroup) { let doc = this.widgets.multilineOutput.contentDocument; let win = this.widgets.multilineOutput.contentWindow; @@ -260,14 +263,15 @@ const CommandLine = Module("commandline", { // Otherwise, white space is significant. // The problem elsewhere is that E4X tends to insert new lines // after interpolated data. - XML.ignoreWhitespace = typeof str != "xml"; - this._lastMowOutput = <div class="ex-command-output" style="white-space: nowrap" highlight={highlightGroup}>{template.maybeXML(str)}</div>; + XML.ignoreWhitespace = false; + XML.prettyPrinting = false; + let style = typeof str === "string" ? "pre" : "nowrap"; + this._lastMowOutput = <div class="ex-command-output" style={"white-space: " + style} highlight={highlightGroup}>{str}</div>; let output = util.xmlToDom(this._lastMowOutput, doc); - XML.ignoreWhitespace = true; // FIXME: need to make sure an open MOW is closed when commands // that don't generate output are executed - if (this._outputContainer.collapsed) + if (this.widgets.mowContainer.collapsed) doc.body.innerHTML = ""; doc.body.appendChild(output); @@ -417,6 +421,8 @@ const CommandLine = Module("commandline", { this._currentExtendedMode = null; commandline.triggerCallback("cancel", mode); + if (this._completions) + this._completions.previewClear(); if (this._history) this._history.save(); @@ -431,11 +437,11 @@ const CommandLine = Module("commandline", { this._completionList.hide(); if (!this._keepCommand || this._silent || this._quiet) { - this._outputContainer.collapsed = true; + this.widgets.mowContainer.collapsed = true; commandline.updateMorePrompt(); this.hide(); } - if (!this._outputContainer.collapsed) { + if (!this.widgets.mowContainer.collapsed) { modes.set(modes.COMMAND_LINE, modes.OUTPUT_MULTILINE); commandline.updateMorePrompt(); } @@ -492,23 +498,35 @@ const CommandLine = Module("commandline", { if (flags & this.APPEND_TO_MESSAGES) { let message = isobject(str) ? str : { message: str }; - this._messageHistory.add(update({ highlight: highlightGroup }, str)); + this._messageHistory.add(update({ highlight: highlightGroup }, message)); str = message.message; } + if ((flags & this.ACTIVE_WINDOW) && window != services.get("windowWatcher").activeWindow && services.get("windowWatcher").activeWindow.dactyl) return; - if ((flags & this.DISALLOW_MULTILINE) && !this._outputContainer.collapsed) + if ((flags & this.DISALLOW_MULTILINE) && !this.widgets.mowContainer.collapsed) return; let single = flags & (this.FORCE_SINGLELINE | this.DISALLOW_MULTILINE); let action = this._echoLine; + if (single) + this._lastEcho = null; + else { + if (this.widgets.message.value == this._lastEcho) + this._echoMultiline(<span highlight="Message">{this._lastEcho}</span>, + this.highlightGroup); + this._lastEcho = (action == this._echoLine) && str; + } + // 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.widgets.message.value == this._lastEcho)) { + if (!(flags & this.FORCE_MULTILINE) && !single + && (!this.widgets.mowContainer.collapsed || this.widgets.message.value == this._lastEcho)) { + highlightGroup += " Message"; action = this._echoMultiline; } @@ -516,15 +534,6 @@ const CommandLine = Module("commandline", { if ((flags & this.FORCE_MULTILINE) || (/\n/.test(str) || typeof str == "xml") && !(flags & this.FORCE_SINGLELINE)) action = this._echoMultiline; - if (single) - this._lastEcho = null; - else { - if (this.widgets.message.value == this._lastEcho) - this._echoMultiline(<span highlight="Message">{this._lastEcho}</span>, - this.widgets.message.getAttributeNS(NS.uri, "highlight")); - this._lastEcho = (action == this._echoLine) && str; - } - if (action) action.call(this, str, highlightGroup, single); }), @@ -937,7 +946,7 @@ const CommandLine = Module("commandline", { * and what they do. */ updateMorePrompt: function updateMorePrompt(force, showHelp) { - if (this._outputContainer.collapsed) { + if (this.widgets.mowContainer.collapsed) { this._echoLine("", this.HL_NORMAL); return; } @@ -960,19 +969,25 @@ const CommandLine = Module("commandline", { * @param {boolean} open If true, the widget will be opened if it's not * already so. */ - updateOutputHeight: function updateOutputHeight(open) { - if (!open && this._outputContainer.collapsed) + updateOutputHeight: function updateOutputHeight(open, extra) { + if (!open && this.widgets.mowContainer.collapsed) return; let doc = this.widgets.multilineOutput.contentDocument; let availableHeight = config.outputHeight; - if (!this._outputContainer.collapsed) - availableHeight += parseFloat(this._outputContainer.height); + if (!this.widgets.mowContainer.collapsed) + availableHeight += parseFloat(this.widgets.mowContainer.height); + availableHeight -= extra || 0; + doc.body.style.minWidth = this.widgets.commandline.scrollWidth + "px"; - this._outputContainer.height = Math.min(doc.height, availableHeight) + "px"; + this.widgets.mowContainer.height = Math.min(doc.height, availableHeight) + "px"; + this.timeout(function () + this.widgets.mowContainer.height = Math.min(doc.height, availableHeight) + "px", + 0); + doc.body.style.minWidth = ""; - this._outputContainer.collapsed = false; + this.widgets.mowContainer.collapsed = false; }, resetCompletions: function resetCompletions() { @@ -1015,7 +1030,12 @@ const CommandLine = Module("commandline", { if (/^\s*$/.test(str)) return; this.store.mutate("filter", function (line) (line.value || line) != str); - this.store.push({ value: str, timestamp: Date.now()*1000, privateData: this.checkPrivate(str) }); + try { + this.store.push({ value: str, timestamp: Date.now()*1000, privateData: this.checkPrivate(str) }); + } + catch (e) { + dactyl.reportError(e); + } this.store.truncate(options["history"], true); }, /** @@ -1142,6 +1162,8 @@ const CommandLine = Module("commandline", { get wildtype() this.wildtypes[this.wildIndex] || "", + get wildtypes() this.wildmode.values, + complete: function complete(show, tabPressed) { this.context.reset(); this.context.tabPressed = tabPressed; @@ -1162,7 +1184,7 @@ const CommandLine = Module("commandline", { let substring = ""; switch (this.wildtype.replace(/.*:/, "")) { case "": - substring = this.items[0].text; + substring = this.items[0].result; break; case "longest": if (this.items.length > 1) { @@ -1173,7 +1195,7 @@ const CommandLine = Module("commandline", { case "full": let item = this.items[this.selected != null ? this.selected + 1 : 0]; if (item) - substring = item.text; + substring = item.result; break; } @@ -1227,14 +1249,14 @@ const CommandLine = Module("commandline", { this.wildIndex = 0; } - this.wildtypes = this.wildmode.values; this.preview(); }, _reset: function _reset() { - this.prefix = this.context.value.substring(0, this.start); - this.value = this.context.value.substring(this.start, this.caret); - this.suffix = this.context.value.substring(this.caret); + let value = this.editor.selection.focusNode.textContent; + this.prefix = value.substring(0, this.start); + this.value = value.substring(this.start, this.caret); + this.suffix = value.substring(this.caret); this.itemList.reset(); this.itemList.selectItem(this.selected); @@ -1301,7 +1323,7 @@ const CommandLine = Module("commandline", { return; this.selected = idx; - this.completion = this.items[idx].text; + this.completion = this.items[idx].result; } this.itemList.selectItem(idx); @@ -1320,6 +1342,8 @@ const CommandLine = Module("commandline", { return; while (this.tabs.length) { + this.wildIndex = Math.min(this.wildIndex, this.wildtypes.length - 1); + reverse = this.tabs.shift(); switch (this.wildtype.replace(/.*:/, "")) { case "": @@ -1340,7 +1364,7 @@ const CommandLine = Module("commandline", { if (this.haveType("list")) this.itemList.show(); - this.wildIndex = Math.constrain(this.wildIndex + 1, 0, this.wildtypes.length - 1); + this.wildIndex++; this.preview(); commandline._statusTimer.tell(); @@ -1373,7 +1397,7 @@ const CommandLine = Module("commandline", { if (typeof arg === "object") arg = util.objectToString(arg, useColor); - else if (typeof arg == "string" && /\n/.test(arg)) + else if (typeof arg === "string" && /\n/.test(arg)) arg = <span highlight="CmdOutput">{arg}</span>; else arg = String(arg); @@ -1533,7 +1557,7 @@ const CommandLine = Module("commandline", { styles: function () { let fontSize = util.computedStyle(document.getElementById(config.mainWindowId)).fontSize; styles.registerSheet("chrome://dactyl/skin/dactyl.css"); - let error = styles.addSheet(true, "font-size", "chrome://dactyl/content/buffer.xhtml", + styles.addSheet(true, "font-size", "chrome://dactyl/content/buffer.xhtml", "body { font-size: " + fontSize + "; }"); } }); @@ -1542,7 +1566,7 @@ const CommandLine = Module("commandline", { * The list which is used for the completion box (and QuickFix window in * future). * - * @param {string} id The id of the <iframe> which will display the list. It + * @param {string} id The id of the iframe which will display the list. It * must be in its own container element, whose height it will update as * necessary. */ @@ -1579,12 +1603,15 @@ const ItemList = Class("ItemList", { this._minHeight = Math.max(this._minHeight, this._win.scrollY + this._divNodes.completions.getBoundingClientRect().bottom); - this._container.height = this._minHeight; if (this._container.collapsed) this._div.style.minWidth = ""; // FIXME: Belongs elsewhere. + commandline.updateOutputHeight(false, Math.max(0, this._minHeight - this._container.height)); + + this._container.height = this._minHeight; + this._container.height -= commandline.getSpaceNeeded() commandline.updateOutputHeight(false); this.timeout(function () { this._container.height -= commandline.getSpaceNeeded(); }, 0); }, @@ -1678,7 +1705,7 @@ const ItemList = Class("ItemList", { for (let [i, row] in Iterator(context.getRows(start, end, this._doc))) nodes[i] = row; - for (let [i, row] in util.Array.iteritems(nodes)) { + for (let [i, row] in array.iteritems(nodes)) { if (!row) continue; let display = (i >= start && i < end); diff --git a/common/content/commands.js b/common/content/commands.js index b19653f3..1dd25d93 100644 --- a/common/content/commands.js +++ b/common/content/commands.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k at Gmail> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -171,16 +171,10 @@ const Command = Class("Command", { * @returns {boolean} */ hasName: function (name) { - for (let [, spec] in Iterator(this.specs)) { - let fullName = spec.replace(/\[(\w+)]$/, "$1"); - let index = spec.indexOf("["); - let min = index == -1 ? fullName.length : index; - - if (fullName.indexOf(name) == 0 && name.length >= min) - return true; - } - - return false; + return this.specs.some(function (spec) { + let [, head, tail] = spec.match(/([^[]+)(?:\[(.*)])?/); + return name.indexOf(head) == 0 && (head + (tail || "")).indexOf(name) == 0; + }); }, /** @@ -340,25 +334,29 @@ const Commands = Module("commands", { /** @property {Iterator(Command)} @private */ __iterator__: function () { let sorted = this._exCommands.sort(function (a, b) a.name > b.name); - return util.Array.itervalues(sorted); + return array.itervalues(sorted); }, /** @property {string} The last executed Ex command line. */ repeat: null, - _addCommand: function (command, replace) { - if (command.name in this._exMap) { - if (command.user && replace) - commands.removeUserCommand(command.name); - else { - dactyl.log("Warning: :" + command.name + " already exists, NOT replacing existing command.", 1); - return false; - } - } - - this._exCommands.push(command); - for (let [,name] in Iterator(command.names)) - this._exMap[name] = command; + _addCommand: function (args, replace) { + let names = array.flatten(Command.parseSpecs(args[0])); + dactyl.assert(!names.some(function (name) name in this._exMap && !this._exMap[name].user, this), + "E182: Can't replace non-user command: " + args[0]); + if (!replace && args[3] && args[3].user) + dactyl.assert(!names.some(function (name) name in this._exMap, this), + "Not replacing command " + args[0]); + for (let name in names) + if (name in this._exMap) + commands.removeUserCommand(name); + + let name = names[0]; + let closure = function () commands._exMap[name]; + memoize(this._exMap, name, function () Command.apply(null, args)); + memoize(this._exCommands, this._exCommands.length, closure); + for (let alias in values(names.slice(1))) + memoize(this._exMap, alias, closure); return true; }, @@ -375,7 +373,7 @@ const Commands = Module("commands", { * @optional */ add: function (names, description, action, extra) { - return this._addCommand(Command(names, description, action, extra), false); + return this._addCommand([names, description, action, extra], false); }, /** @@ -395,7 +393,7 @@ const Commands = Module("commands", { extra.user = true; description = description || "User defined command"; - return this._addCommand(Command(names, description, action, extra), replace); + return this._addCommand([names, description, action, extra], replace); }, /** @@ -407,7 +405,7 @@ const Commands = Module("commands", { */ commandToString: function (args) { let res = [args.command + (args.bang ? "!" : "")]; - function quote(str) Commands.quoteArg[/[\s"'\\]|^$/.test(str) ? '"' : ""](str); + function quote(str) Commands.quoteArg[/[\s"'\\]|^$/.test(str) ? "'" : ""](str); for (let [opt, val] in Iterator(args.options || {})) { let chr = /^-.$/.test(opt) ? " " : "="; @@ -431,8 +429,8 @@ const Commands = Module("commands", { * any of the command's names. * @returns {Command} */ - get: function (name) { - return this._exMap[name] || this._exCommands.filter(function (cmd) cmd.hasName(name))[0] || null; + get: function (name, full) { + return this._exMap[name] || !full && this._exCommands.filter(function (cmd) cmd.hasName(name))[0] || null; }, /** @@ -573,7 +571,7 @@ const Commands = Module("commands", { argCount = "*"; var args = []; // parsed options - args.__iterator__ = function () util.Array.iteritems(this); + args.__iterator__ = function () array.iteritems(this); args.string = str; // for access to the unparsed string args.literalArg = ""; @@ -833,11 +831,11 @@ 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)) - delete this._exMap[name]; - this._exCommands = this._exCommands.filter(function (cmd) !(cmd.user && cmd.hasName(name))); + let cmd = this.get(name); + dactyl.assert(cmd.user, "E184: No such user-defined command: " + name); + for (let name in values(cmd.names)) + delete this._exMap[name]; + this._exCommands = this._exCommands.filter(function (c) c != cmd); }, // FIXME: still belong here? Also used for autocommand parameters. @@ -867,8 +865,6 @@ const Commands = Module("commands", { }); } }, { - QUOTE_STYLE: "rc-ish", - // returns [count, parsed_argument] parseArg: function (str) { let arg = ""; @@ -918,7 +914,7 @@ const Commands = Module("commands", { // dynamically get completions as specified with the command's completer function let command = commands.get(cmd); if (!command) { - context.highlight(0, cmd.length, "SPELLCHECK"); + context.highlight(0, cmd && cmd.length, "SPELLCHECK"); return; } @@ -936,7 +932,8 @@ const Commands = Module("commands", { cmdContext.filter = args.completeFilter; try { let compObject = command.completer.call(command, cmdContext, args); - if (compObject instanceof Array) // for now at least, let completion functions return arrays instead of objects + + if (isarray(compObject)) // for now at least, let completion functions return arrays instead of objects compObject = { start: compObject[0], items: compObject[1] }; if (compObject != null) { cmdContext.advance(compObject.start); @@ -1041,14 +1038,13 @@ const Commands = Module("commands", { function completerToString(completer) { if (completer) return [k for ([k, v] in Iterator(completeOptionMap)) if (completer == completion[v])][0] || "custom"; - else - return ""; + return ""; } // TODO: using an array comprehension here generates flakey results across repeated calls // : perhaps we shouldn't allow options in a list call but just ignore them for now // : No, array comprehensions are fine, generator statements aren't. --Kris - let cmds = this._exCommands.filter(function (c) c.user && (!cmd || c.name.match("^" + cmd))); + let cmds = commands._exCommands.filter(function (c) c.user && (!cmd || c.name.match("^" + cmd))); if (cmds.length > 0) commandline.commandOutput( @@ -1101,7 +1097,7 @@ const Commands = Module("commands", { serialize: function () [ { command: this.name, bang: true, - options: util.Array.toObject( + options: array.toObject( [[v, typeof cmd[k] == "boolean" ? null : cmd[k]] // FIXME: this map is expressed multiple times for ([k, v] in Iterator({ argCount: "-nargs", bang: "-bang", count: "-count", description: "-description" })) @@ -1162,17 +1158,20 @@ const Commands = Module("commands", { }; function quote(q, list) { let re = RegExp("[" + list + "]", "g"); - return function (str) q + String.replace(str, re, function ($0) $0 in Commands.quoteMap ? Commands.quoteMap[$0] : ("\\" + $0)) + q; + let res = function (str) q + String.replace(str, re, function ($0) $0 in Commands.quoteMap ? Commands.quoteMap[$0] : ("\\" + $0)) + q; + res.list = list; + return res; }; - Commands.complQuote = { // FIXME + Commands.complQuote = { '"': ['"', quote("", '\n\t"\\\\'), '"'], "'": ["'", quote("", "\\\\'"), "'"], - "": ["", quote("", "\\\\ "), ""] + "": ["", quote("", "\\\\ '\""), ""] }; + Commands.quoteArg = { '"': quote('"', '\n\t"\\\\'), "'": quote("'", "\\\\'"), - "": quote("", "\\\\ ") + "": quote("", "\\\\ '\"") }; Commands.parseBool = function (arg) { diff --git a/common/content/completion.js b/common/content/completion.js index be2a5ab6..68a5dc89 100644 --- a/common/content/completion.js +++ b/common/content/completion.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k@gmail.com> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -54,7 +54,7 @@ const CompletionContext = Class("CompletionContext", { ["filters", "keys", "title", "quote"].forEach(function (key) self[key] = parent[key] && util.cloneObject(parent[key])); - ["anchored", "compare", "editor", "_filter", "filterFunc", "keys", "_process", "top"].forEach(function (key) + ["anchored", "compare", "editor", "_filter", "filterFunc", "keys", "process", "top"].forEach(function (key) self[key] = parent[key]); self.__defineGetter__("value", function () this.top.value); @@ -90,8 +90,14 @@ const CompletionContext = Class("CompletionContext", { this._value = editor; else this.editor = editor; - this.compare = function (a, b) String.localeCompare(a.text, b.text); + /** + * @property {boolean} Specifies whether this context results must + * match the filter at the beginning of the string. + * @default true + */ + this.anchored = true; + this.compare = function (a, b) String.localeCompare(a.text, b.text); /** * @property {function} This function is called when we close * a completion window with Esc or Ctrl-c. Usually this callback @@ -99,6 +105,20 @@ const CompletionContext = Class("CompletionContext", { */ this.cancel = null; /** + * @property {[CompletionContext]} A list of active + * completion contexts, in the order in which they were + * instantiated. + */ + this.contextList = []; + /** + * @property {Object} A map of all contexts, keyed on their names. + * Names are assigned when a context is forked, with its specified + * name appended, after a '/', to its parent's name. May + * contain inactive contexts. For active contexts, see + * {@link #contextList}. + */ + this.contexts = { "": this }; + /** * @property {function} The function used to filter the results. * @default Selects all results which match every predicate in the * {@link #filters} array. @@ -115,20 +135,6 @@ const CompletionContext = Class("CompletionContext", { */ this.filters = [CompletionContext.Filter.text]; /** - * @property {boolean} Specifies whether this context results must - * match the filter at the beginning of the string. - * @default true - */ - this.anchored = true; - /** - * @property {Object} A map of all contexts, keyed on their names. - * Names are assigned when a context is forked, with its specified - * name appended, after a '/', to its parent's name. May - * contain inactive contexts. For active contexts, see - * {@link #contextList}. - */ - this.contexts = { "": this }; - /** * @property {Object} A mapping of keys, for {@link #getKey}. Given * { key: value }, getKey(item, key) will return values as such: * if value is a string, it will return item.item[value]. If it's a @@ -146,6 +152,9 @@ const CompletionContext = Class("CompletionContext", { * {@link #updateAsync} is true. */ this.onUpdate = function () true; + + this.runCount = 0; + /** * @property {CompletionContext} The top-level completion context. */ @@ -190,6 +199,7 @@ const CompletionContext = Class("CompletionContext", { : item.item[key]; return this; }, + // Temporary /** * @property {Object} @@ -202,7 +212,7 @@ const CompletionContext = Class("CompletionContext", { get allItems() { try { let self = this; - let minStart = Math.min.apply(Math, [context.offset for ([k, context] in Iterator(this.contexts)) if (context.items.length && context.hasItems)]); + let minStart = Math.min.apply(Math, [context.offset for ([k, context] in Iterator(this.contexts)) if (context.hasItems && context.items.length)]); if (minStart == Infinity) minStart = 0; let items = this.contextList.map(function (context) { @@ -214,7 +224,7 @@ const CompletionContext = Class("CompletionContext", { __proto__: item })); }); - return { start: minStart, items: util.Array.flatten(items), longestSubstring: this.longestAllSubstring }; + return { start: minStart, items: array.flatten(items), longestSubstring: this.longestAllSubstring }; } catch (e) { dactyl.reportError(e); @@ -235,7 +245,7 @@ const CompletionContext = Class("CompletionContext", { lists.pop()); if (!substrings) // FIXME: How is this undefined? return []; - return util.Array.uniq(Array.slice(substrings)); + return array.uniq(Array.slice(substrings)); }, // Temporary get longestAllSubstring() { @@ -253,14 +263,16 @@ const CompletionContext = Class("CompletionContext", { // Accept a generator if (!isarray(items)) items = [x for (x in Iterator(items))]; - delete this.cache.filtered; - delete this.cache.filter; - this.cache.rows = []; - this.hasItems = items.length > 0; - this._completions = items; - let self = this; + if (this._completions !== items) { + delete this.cache.filtered; + delete this.cache.filter; + this.cache.rows = []; + this.hasItems = items.length > 0; + this._completions = items; + this.itemCache[this.key] = items; + } if (this.updateAsync && !this.noUpdate) - util.callInMainThread(function () { self.onUpdate.call(self); }); + util.callInMainThread(function () { this.onUpdate(); }, this); }, get createRow() this._createRow || template.completionRow, // XXX @@ -293,14 +305,18 @@ const CompletionContext = Class("CompletionContext", { get proto() { let res = {}; - for (let i in Iterator(this.keys)) { + function result(quote) { + yield ["result", quote ? function () quote[0] + quote[1](this.text) + quote[2] + : function () this.text] + }; + for (let i in iterall(this.keys, result(this.quote))) { let [k, v] = i; if (typeof v == "string" && /^[.[]/.test(v)) // This is only allowed to be a simple accessor, and shouldn't // reference any variables. Don't bother with eval context. v = Function("i", "return i" + v); if (typeof v == "function") - res.__defineGetter__(k, function () Class.replaceProperty(this, k, v(this.item))); + res.__defineGetter__(k, function () Class.replaceProperty(this, k, v.call(this, this.item))); else res.__defineGetter__(k, function () Class.replaceProperty(this, k, this.item[v])); res.__defineSetter__(k, function (val) Class.replaceProperty(this, k, val)); @@ -312,11 +328,16 @@ const CompletionContext = Class("CompletionContext", { set regenerate(val) { if (val) delete this.itemCache[this.key]; }, get generate() !this._generate ? null : function () { - if (this.offset != this.cache.offset) + if (this.offset != this.cache.offset || this.lastActivated != this.top.runCount) { this.itemCache = {}; - this.cache.offset = this.offset; - if (!this.itemCache[this.key]) - this.itemCache[this.key] = this._generate.call(this) || []; + this.cache.offset = this.offset; + this.lastActivated = this.top.runCount; + } + if (!this.itemCache[this.key]) { + let res = this._generate.call(this) || []; + if (res != null) + this.itemCache[this.key] = res; + } return this.itemCache[this.key]; }, set generate(arg) { @@ -354,16 +375,27 @@ const CompletionContext = Class("CompletionContext", { get items() { if (!this.hasItems || this.backgroundLock) return []; - if (this.cache.filtered && this.cache.filter == this.filter) - return this.cache.filtered; - this.cache.rows = []; - let items = this.completions; + + // Regenerate completions if we must if (this.generate && !this.background) { // XXX this.noUpdate = true; - this.completions = items = this.generate(); + this.completions = this.generate(); this.noUpdate = false; } + let items = this.completions; + + // Check for cache miss + if (this.cache.completions !== this.completions) { + this.cache.completions = this.completions; + this.cache.constructed = null; + this.cache.filtered = null; + } + + if (this.cache.filtered && this.cache.filter == this.filter) + return this.cache.filtered; + + this.cache.rows = []; this.cache.filter = this.filter; if (items == null) return items; @@ -371,6 +403,7 @@ const CompletionContext = Class("CompletionContext", { let self = this; delete this._substrings; + // Item matchers if (this.ignoreCase) this.matchString = this.anchored ? function (filter, str) String.toLowerCase(str).indexOf(filter.toLowerCase()) == 0 : @@ -380,36 +413,28 @@ const CompletionContext = Class("CompletionContext", { function (filter, str) String.indexOf(str, filter) == 0 : function (filter, str) String.indexOf(str, filter) >= 0; + // Item formatters + this.processor = Array.slice(this.process); + if (!this.anchored) + this.processor[0] = function (item, text) self.process[0].call(self, item, + template.highlightFilter(item.text, self.filter)); + + // Item prototypes let proto = this.proto; - let filtered = this.filterFunc(items.map(function (item) ({ __proto__: proto, item: item }))); + if (!this.cache.constructed) + this.cache.constructed = items.map(function (item) Object.create(proto, { item: { value: item, enumerable: true } })); + + // Filters + let filtered = this.filterFunc(this.cache.constructed); if (this.maxItems) filtered = filtered.slice(0, this.maxItems); + // Sorting if (this.sortResults && this.compare) filtered.sort(this.compare); - let quote = this.quote; - if (quote) - filtered.forEach(function (item) { - item.unquoted = item.text; - item.text = quote[0] + quote[1](item.text) + quote[2]; - }); return this.cache.filtered = filtered; }, - get process() { // FIXME - let self = this; - let process = this._process; - process = [process[0] || template.icon, process[1] || function (item, k) k]; - let first = process[0]; - let filter = this.filter; - if (!this.anchored) - process[0] = function (item, text) first.call(self, item, template.highlightFilter(item.text, filter)); - return process; - }, - set process(process) { - this._process = process; - }, - get substrings() { let items = this.items; if (items.length == 0 || !this.hasItems) @@ -418,7 +443,10 @@ const CompletionContext = Class("CompletionContext", { return this._substrings; let fixCase = this.ignoreCase ? String.toLowerCase : util.identity; - let text = fixCase(items[0].unquoted || items[0].text); + let text = fixCase(items[0].text); + // Exceedingly long substrings cause Gecko to go into convulsions + if (text.length > 100) + text = text.substr(0, 100); let filter = fixCase(this.filter); if (this.anchored) { var compare = function compare(text, s) text.substr(0, s.length) == s; @@ -521,7 +549,6 @@ const CompletionContext = Class("CompletionContext", { context.waitingForTab = true; else if (completer) return completer.apply(self || this, [context].concat(Array.slice(arguments, fork.length))); - if (completer) return null; return context; @@ -535,20 +562,24 @@ const CompletionContext = Class("CompletionContext", { }, highlight: function highlight(start, length, type) { - try { // Gecko < 1.9.1 doesn't have repaintSelection - this.selectionTypes[type] = null; + if (arguments.length == 0) { + for (let type in this.selectionTypes) + this.highlight(0, 0, type); + this.selectionTypes = {}; + } + try { + // Requires Gecko >= 1.9.1 + this.selectionTypes[type] = true; const selType = Ci.nsISelectionController["SELECTION_" + type]; - const editor = this.editor; - let sel = editor.selectionController.getSelection(selType); + let sel = this.editor.selectionController.getSelection(selType); if (length == 0) sel.removeAllRanges(); else { - let range = editor.selection.getRangeAt(0).cloneRange(); + let range = this.editor.selection.getRangeAt(0).cloneRange(); range.setStart(range.startContainer, this.offset + start); range.setEnd(range.startContainer, this.offset + start + length); sel.addRange(range); } - editor.selectionController.repaintSelection(selType); } catch (e) {} }, @@ -557,23 +588,19 @@ const CompletionContext = Class("CompletionContext", { return this.matchString(this.filter, str); }, + pushProcessor: function pushProcess(i, fn) { + let next = this.process[i]; + this.process[i] = function (item, text) fn(item, text, next); + }, + reset: function reset() { let self = this; if (this.parent) throw Error(); - // Not ideal. - for (let type in this.selectionTypes) - this.highlight(0, 0, type); - /** - * @property {[CompletionContext]} A list of active - * completion contexts, in the order in which they were - * instantiated. - */ - this.contextList = []; this.offset = 0; - this.process = []; - this.selectionTypes = {}; + this.process = [template.icon, function (item, k) k]; + this.filters = [CompletionContext.Filter.text]; this.tabPressed = false; this.title = ["Completions"]; this.updateAsync = false; @@ -595,6 +622,10 @@ const CompletionContext = Class("CompletionContext", { if (context != context.top) context.incomplete = false; } + this.runCount++; + for each (let context in this.contextList) + context.lastActivated = this.runCount; + this.contextList = []; }, /** @@ -668,7 +699,7 @@ const Completion = Module("completion", { commandline.commandOutput( <div highlight="Completions"> - { template.map(context.contextList.filter(function (c) c.hasItems), + { template.map(context.contextList.filter(function (c) c.hasItems && c.items.length), function (context) template.completionRow(context.title, "CompTitle") + template.map(context.items, function (item) context.createRow(item), null, 100)) } @@ -747,7 +778,7 @@ const Completion = Module("completion", { let re = RegExp(tokens.filter(util.identity).map(util.escapeRegex).join("|"), "g"); function highlight(item, text, i) process[i].call(this, item, template.highlightRegexp(text, re)); - let process = [template.icon, function (item, k) k]; + let process = context.process; context.process = [ function (item, text) highlight.call(this, item, item.text, 0), function (item, text) highlight.call(this, item, text, 1) diff --git a/common/content/configbase.js b/common/content/configbase.js index a8a79847..03fd84b0 100644 --- a/common/content/configbase.js +++ b/common/content/configbase.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k@gmail.com> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -12,8 +12,33 @@ const ConfigBase = Class(ModuleBase, { * initialization code. Must call superclass's init function. */ init: function () { + this.name = services.get("dactyl:").name; + this.appname = services.get("dactyl:").appname; + this.host = services.get("dactyl:").host; + highlight.styleableChrome = this.styleableChrome; highlight.loadCSS(this.CSS); + highlight.loadCSS(this.helpCSS); + + let img = Image(); + img.src = this.logo || "chrome://" + this.name + "/content/logo.png"; + img.onload = function () { + highlight.set("Logo", String(<> + display: inline-block; + background: url({img.src}); + width: {img.width}px; + height: {img.height}px; + </>)); + img = null; + } + }, + + styleHelp: function () { + if (!this.helpStyled) + for (let k in keys(highlight.loaded)) + if (/^(Help|StatusLine)|^(Boolean|Indicator|MoreMsg|Number|Logo|Key(word)?|String)$/.test(k)) + highlight.loaded[k] = true; + this.helpCSS = true; }, /** @@ -92,7 +117,7 @@ const ConfigBase = Class(ModuleBase, { * @property {number} The height (px) that is available to the output * window. */ - get outputHeight() config.browser.mPanelContainer.boxObject.height, + get outputHeight() this.browser.mPanelContainer.boxObject.height, /** * @property {[string]} A list of extra scripts in the dactyl or @@ -105,45 +130,41 @@ 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 + ".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 + * @property {string} The default highlighting rules. + * See {@link Highlights#loadCSS} for details. */ - // <css> - CSS: <![CDATA[ - Boolean color: red; - Function color: navy; - Null color: blue; - Number color: blue; - Object color: maroon; - String color: green; - - Key font-weight: bold; - - Enabled color: blue; - Disabled color: red; - - Normal color: black; background: white; - ErrorMsg color: white; background: red; font-weight: bold; - InfoMsg color: black; background: white; - ModeMsg color: black; background: white; - MoreMsg color: green; background: white; - WarningMsg color: red; background: white; - Message white-space: normal; min-width: 100%; padding-left: 2em; text-indent: -2em; display: block; - NonText color: blue; min-height: 16px; padding-left: 2px; - Preview color: gray; - - CmdLine,>* font-family: monospace; padding: 1px; - CmdOutput white-space: pre; + CSS: UTF8(<><![CDATA[ + // <css> + Boolean color: red; + Function color: navy; + Null color: blue; + Number color: blue; + Object color: maroon; + String color: green; + + Key font-weight: bold; + + Enabled color: blue; + Disabled color: red; + + !Normal color: black !important; background: white !important; + ErrorMsg color: white !important; background: red !important; font-weight: bold !important; + InfoMsg color: black !important; background: white !important; + LineNr color: orange !important; background: white !important; + ModeMsg color: black !important; background: white !important; + MoreMsg color: green !important; background: white !important; + Message white-space: normal; min-width: 100%; padding-left: 2em; text-indent: -2em; display: block; + NonText color: blue; min-height: 16px; padding-left: 2px; + *Preview color: gray; + Question color: green !important; background: white !important; font-weight: bold !important; + WarningMsg color: red !important; background: white !important; + + !CmdLine;>* font-family: monospace !important; padding: 1px !important; + CmdOutput white-space: pre; CompGroup CompGroup:not(:first-of-type) margin-top: .5em; @@ -158,9 +179,10 @@ const ConfigBase = Class(ModuleBase, { 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 */ + CompLess::after content: "⌃"; CompMore text-align: center; height: .5ex; line-height: .5ex; margin-bottom: -.5ex; - CompMore::after content: "\2304" /* Unicode down arrowhead */ + CompMore::after content: "⌄"; + CompGroup:last-of-type padding-bottom: 1.5ex; Gradient height: 1px; margin-bottom: -1px; margin-top: -1px; GradientLeft background-color: magenta; @@ -172,19 +194,16 @@ const ConfigBase = Class(ModuleBase, { 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 */ + !StatusLine color: white !important; background: black !important + StatusLineBroken color: black !important; background: #FFa0a0 !important /* light-red */ + StatusLineSecure color: black !important; background: #a0a0FF !important /* light-blue */ + StatusLineExtended color: black !important; background: #a0FFa0 !important /* light-green */ - TabClose,.tab-close-button - TabIcon,.tab-icon - TabText,.tab-text - TabNumber font-weight: bold; margin: 0px; padding-right: .3ex; - TabIconNumber { + 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; @@ -195,47 +214,52 @@ const ConfigBase = Class(ModuleBase, { 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; + FrameIndicator;;* { + /* This gets released into the wild, so everything is important */ + background-color: red !important; + opacity: 0.5 !important; + z-index: 999999 !important; + position: fixed !important; + top: 0 !important; + bottom: 0 !important; + left: 0 !important; + right: 0 !important; } - 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; + !Bell border: none; background-color: black; + Hint;;* { + /* This gets released into the wild, so everything is important */ + font: bold 10px monospace !important; + background-color: red !important; + color: white !important; + border: 0px solid ButtonShadow !important; + padding: 0px 1px !important; } - Hint::after,,* content: attr(number); - HintElem,,* background-color: yellow; color: black; - HintActive,,* background-color: #88FF00; color: black; - HintImage,,* opacity: .5; + !Hint::after;;* content: attr(number) !important; + !HintElem;;* background-color: yellow !important; color: black !important; + !HintActive;;* background-color: #88FF00 !important; color: black !important; + !HintImage;;* opacity: .5 !important; - Help font-size: 8pt; line-height: 1.4em; font-family: -moz-fixed; + !Logo + // </css> + ]]></>), + + helpCSS: UTF8(<><![CDATA[ + // <css> + Help font-size: 8pt; line-height: 1.4em; font-family: -moz-fixed, monospace; 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; + HelpBody display: block; margin: 1em auto; max-width: 100ex; padding-bottom: 1em; margin-bottom: 4em; border-bottom-width: 1px; + HelpBorder;*;dactyl://help/* border-color: silver; border-width: 0px; border-style: solid; + HelpCode display: block; white-space: pre; margin-left: 2em; font-family: monospace; - HelpDefault margin-right: 1ex; white-space: pre; + HelpDefault display: inline-block; margin-right: 1ex; white-space: pre; - HelpDescription display: block; - HelpEm,html|em,dactyl://help/* font-weight: bold; font-style: normal; + HelpDescription display: block; clear: right; + HelpDescription[short] clear: none; + HelpEm;html|em;dactyl://help/* font-weight: bold; font-style: normal; HelpEx display: inline-block; color: #527BBD; font-weight: bold; @@ -249,32 +273,42 @@ const ConfigBase = Class(ModuleBase, { HelpItem display: block; margin: 1em 1em 1em 10em; clear: both; HelpKey color: #102663; + HelpKeyword font-weight: bold; color: navy; + + HelpLink;html|a;dactyl://help/* text-decoration: none !important; + HelpLink[href]:hover text-decoration: underline !important; + HelpLink[href^="mailto:"]::after content: "✉"; padding-left: .2em; + HelpLink[rel=external] { + /* Thanks, Wikipedia */ + background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAMAAAC67D+PAAAAFVBMVEVmmcwzmcyZzP8AZswAZv////////9E6giVAAAAB3RSTlP///////8AGksDRgAAADhJREFUGFcly0ESAEAEA0Ei6/9P3sEcVB8kmrwFyni0bOeyyDpy9JTLEaOhQq7Ongf5FeMhHS/4AVnsAZubxDVmAAAAAElFTkSuQmCC) no-repeat scroll right center; + padding-right: 13px; + } - 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; + HelpOrderedList;ol[level="1"],ol;dactyl://help/* display: block; list-style: outside decimal; + HelpOrderedList2;ol[level="2"],ol ol;dactyl://help/* list-style: outside upper-alpha; + HelpOrderedList3;ol[level="3"],ol ol ol;dactyl://help/* list-style: outside lower-roman; + HelpList;html|ul;dactyl://help/* display: block; list-style: outside disc; + HelpListItem;html|li;dactyl://help/* display: list-item; HelpNote color: red; font-weight: bold; HelpOpt color: #106326; - HelpOptInfo display: inline-block; margin-bottom: 1ex; + HelpOptInfo display: block; margin-bottom: 1ex; padding-left: 4em; - HelpParagraph,html|p,dactyl://help/* display: block; margin: 1em 0em; + 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; margin-right: 2em; + HelpParagraph:last-child margin-bottom: 0; + HelpSpec display: block; margin-left: -10em; float: left; clear: left; color: #527BBD; margin-right: 1em; - HelpString display: inline-block; color: green; font-weight: normal; vertical-align: text-top; + HelpString color: green; font-weight: normal; HelpString::before content: '"'; HelpString::after content: '"'; HelpString[delim]::before content: attr(delim); HelpString[delim]::after content: attr(delim); - HelpHead,html|h1,dactyl://help/* { + HelpHead;html|h1;dactyl://help/* { display: block; - margin: 1em 0; + margin: 2em 0 1em; padding-bottom: .2ex; border-bottom-width: 1px; font-size: 2em; @@ -282,9 +316,9 @@ const ConfigBase = Class(ModuleBase, { color: #527BBD; clear: both; } - HelpSubhead,html|h2,dactyl://help/* { + HelpSubhead;html|h2;dactyl://help/* { display: block; - margin: 1em 0; + margin: 2em 0 1em; padding-bottom: .2ex; border-bottom-width: 1px; font-size: 1.2em; @@ -292,7 +326,7 @@ const ConfigBase = Class(ModuleBase, { color: #527BBD; clear: both; } - HelpSubsubhead,html|h3,dactyl://help/* { + HelpSubsubhead;html|h3;dactyl://help/* { display: block; margin: 1em 0; padding-bottom: .2ex; @@ -305,12 +339,20 @@ const ConfigBase = Class(ModuleBase, { 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; + 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; @@ -319,15 +361,31 @@ const ConfigBase = Class(ModuleBase, { HelpWarning color: red; font-weight: bold; - Logo - - Search,,* { - font-size: inherit; - padding: 0; - color: black; - background-color: yellow; + HelpXML color: #C5F779; background-color: #444444; font-family: Terminus, Fixed, monospace; + HelpXMLBlock { white-space: pre; color: #C5F779; background-color: #444444; + border: 1px dashed #aaaaaa; + display: block; + margin-left: 2em; + font-family: Terminus, Fixed, monospace; } - ]]>.toString() + HelpXMLAttribute color: #C5F779; + HelpXMLAttribute::after color: #E5E5E5; content: "="; + HelpXMLComment color: #444444; + HelpXMLComment::before content: "<!--"; + HelpXMLComment::after content: "-->"; + HelpXMLProcessing color: #C5F779; + HelpXMLProcessing::before color: #444444; content: "<?"; + HelpXMLProcessing::after color: #444444; content: "?>"; + HelpXMLString color: #C5F779; white-space: pre; + HelpXMLString::before content: '"'; + HelpXMLString::after content: '"'; + HelpXMLNamespace color: #FFF796; + HelpXMLNamespace::after color: #777777; content: ":"; + HelpXMLTagStart color: #FFF796; white-space: normal; display: inline-block; text-indent: -1.5em; padding-left: 1.5em; + HelpXMLTagEnd color: #71BEBE; + HelpXMLText color: #E5E5E5; + // </css> + ]]></>) }); // vim: set fdm=marker sw=4 ts=4 et: diff --git a/common/content/dactyl-overlay.js b/common/content/dactyl-overlay.js index af0cc3be..219da375 100644 --- a/common/content/dactyl-overlay.js +++ b/common/content/dactyl-overlay.js @@ -20,8 +20,10 @@ return; } catch (e) { - if (e !== "Error opening input stream (invalid filename?)") + if (e !== "Error opening input stream (invalid filename?)") { dump("dactyl: Trying: " + (base + script + ".js") + ": " + e + "\n" + e.stack); + Components.utils.reportError(e); + } } } try { @@ -30,6 +32,7 @@ catch (e) { dump("dactyl: Loading script " + script + ": " + e.result + " " + e + "\n"); dump(Error().stack + "\n"); + Components.utils.reportError(e); } }; @@ -64,7 +67,7 @@ "template", ].forEach(modules.load); - prefix.unshift("chrome://" + modules.Config.prototype.name.toLowerCase() + "/content/"); + prefix.unshift("chrome://" + modules.services.get("dactyl:").name + "/content/"); modules.Config.prototype.scripts.forEach(modules.load); })(); diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 93608912..2c96ac96 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k@gmail.com> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -204,14 +204,14 @@ const Dactyl = Module("dactyl", { * * @param {string|Object} msg The message to print. */ - dump: function () { + dump: function dump() { let msg = Array.map(arguments, function (msg) { if (typeof msg == "object") msg = util.objectToString(msg); return msg; }).join(", "); msg = String.replace(msg, /\n?$/, "\n"); - window.dump(msg.replace(/^./gm, ("config" in modules && config.name.toLowerCase()) + ": $&")); + window.dump(msg.replace(/^./gm, ("config" in modules && config.name) + ": $&")); }, /** @@ -220,7 +220,7 @@ const Dactyl = Module("dactyl", { * @param {string} msg The trace message. * @param {number} frames The number of frames to print. */ - dumpStack: function (msg, frames) { + dumpStack: function dumpStack(msg, frames) { let stack = Error().stack.replace(/(?:.*\n){2}/, ""); if (frames != null) [stack] = stack.match(RegExp("(?:.*\n){0," + frames + "}")); @@ -234,7 +234,7 @@ const Dactyl = Module("dactyl", { * @param {number} flags These control the multiline message behaviour. * See {@link CommandLine#echo}. */ - echo: function (str, flags) { + echo: function echo(str, flags) { commandline.echo(str, commandline.HL_NORMAL, flags); }, @@ -246,7 +246,7 @@ const Dactyl = Module("dactyl", { * @param {number} flags These control the multiline message behaviour. * See {@link CommandLine#echo}. */ - echoerr: function (str, flags) { + echoerr: function echoerr(str, flags) { flags |= commandline.APPEND_TO_MESSAGES; if (isinstance(str, ["Error", "Exception"])) @@ -293,8 +293,6 @@ const Dactyl = Module("dactyl", { * should be loaded. */ loadScript: function (uri, context) { - XML.ignoreWhiteSpace = false; - XML.prettyPrinting = false; services.get("subscriptLoader").loadSubScript(uri, context); }, @@ -395,7 +393,7 @@ const Dactyl = Module("dactyl", { let command = commands.get(cmd); if (command === null) { - err = "E492: Not a " + config.name.toLowerCase() + " command: " + str; + err = "E492: Not a " + config.name + " command: " + str; dactyl.focusContent(); } else if (command.action === null) @@ -486,27 +484,19 @@ const Dactyl = Module("dactyl", { * Initialize the help system. */ initHelp: function () { - if ("noscriptOverlay" in window) { - noscriptOverlay.safeAllow("chrome-data:", true, false); - noscriptOverlay.safeAllow("dactyl:", true, false); - } + if (!this.helpInitialized) { + if ("noscriptOverlay" in window) { + noscriptOverlay.safeAllow("chrome-data:", true, false); + noscriptOverlay.safeAllow("dactyl:", true, false); + } - if(!this.helpInitialized) { - let namespaces = [config.name.toLowerCase(), "dactyl"]; + let namespaces = [config.name, "dactyl"]; services.get("dactyl:").init({}); let tagMap = services.get("dactyl:").HELP_TAGS; let fileMap = services.get("dactyl:").FILE_MAP; let overlayMap = services.get("dactyl:").OVERLAY_MAP; - // Left as an XPCOM instantiation so it can easilly be moved - // into XPCOM code. - function XSLTProcessor(sheet) { - let xslt = Cc["@mozilla.org/document-transformer;1?type=xslt"].createInstance(Ci.nsIXSLTProcessor); - xslt.importStylesheet(util.httpGet(sheet).responseXML); - return xslt; - } - // Find help and overlay files with the given name. function findHelpFile(file) { let result = []; @@ -525,23 +515,20 @@ const Dactyl = Module("dactyl", { } // Find the tags in the document. function addTags(file, doc) { - doc = XSLT.transformToDocument(doc); - for (let elem in util.evaluateXPath("//xhtml:a/@id", doc)) - tagMap[elem.value] = file; + for (let elem in util.evaluateXPath("//@tag|//dactyl:tags/text()|//dactyl:tag/text()", doc)) + for (let tag in array((elem.value || elem.textContent).split(/\s+/)).compact().itervalues()) + tagMap[tag] = file; } - const XSLT = XSLTProcessor("chrome://dactyl/content/help-single.xsl"); - // Scrape the list of help files from all.xml - // Always process main and overlay files, since XSLTProcessor and + // Manually process main and overlay files, since XSLTProcessor and // XMLHttpRequest don't allow access to chrome documents. tagMap.all = "all"; let files = findHelpFile("all").map(function (doc) - [f.value for (f in util.evaluateXPath( - "//dactyl:include/@href", doc))]); + [f.value for (f in util.evaluateXPath("//dactyl:include/@href", doc))]); // Scrape the tags from the rest of the help files. - util.Array.flatten(files).forEach(function (file) { + array.flatten(files).forEach(function (file) { findHelpFile(file).forEach(function (doc) { addTags(file, doc); }); @@ -550,7 +537,6 @@ const Dactyl = Module("dactyl", { // Process plugin help entries. XML.ignoreWhiteSpace = false; XML.prettyPrinting = false; - XML.prettyIndent = 4; let body = XML(); for (let [, context] in Iterator(plugins.contexts)) @@ -558,11 +544,12 @@ const Dactyl = Module("dactyl", { body += <h2 xmlns={NS.uri} tag={context.INFO.@name + '-plugin'}>{context.INFO.@summary}</h2> + context.INFO; - let help = '<?xml version="1.0"?>\n' + - '<?xml-stylesheet type="text/xsl" href="chrome://dactyl/content/help.xsl"?>\n' + - '<!DOCTYPE document SYSTEM "chrome://dactyl/content/dactyl.dtd">' + + let help = + '<?xml version="1.0"?>\n' + + '<?xml-stylesheet type="text/xsl" href="chrome://dactyl/content/help.xsl"?>\n' + + '<!DOCTYPE document SYSTEM "chrome://dactyl/content/dactyl.dtd">\n' + <document xmlns={NS} - name="plugins" title={config.name + " Plugins"}> + name="plugins" title={config.appname + " Plugins"}> <h1 tag="using-plugins">Using Plugins</h1> <toc start="2"/> @@ -589,11 +576,11 @@ const Dactyl = Module("dactyl", { 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 empty = set("area base basefont br col frame hr img input isindex link meta param" + .split(" ")); let chrome = {}; + let styles = {}; for (let [file,] in Iterator(services.get("dactyl:").FILE_MAP)) { dactyl.open("dactyl://help/" + file); dactyl.modules.events.waitForPageLoad(); @@ -612,10 +599,11 @@ const Dactyl = Module("dactyl", { if (node instanceof HTMLHtmlElement) data.push(" xmlns=" + XHTML.uri.quote()); - for (let { name: name, value: value } in util.Array.itervalues(node.attributes)) { + for (let { name, value } in array.itervalues(node.attributes)) { if (name == "dactyl:highlight") { name = "class"; value = "hl-" + value; + set.add(styles, value); } if (name == "href") { if (value.indexOf("dactyl://help-tag/") == 0) @@ -651,11 +639,11 @@ const Dactyl = Module("dactyl", { 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"); + let data = [h for (h in highlight) if (set.has(styles, h.class) || /^Help/.test(h.class))] + .map(function (h) + h.selector.replace(/^\[.*?=(.*?)\]/, ".hl-$1").replace(/html\|/, "") + "\t" + + "{" + h.value + "}") + .join("\n"); addDataEntry("help.css", data.replace(/chrome:[^ ")]+\//g, "")); let re = /(chrome:[^ ");]+\/)([^ ");]+)/g; @@ -669,6 +657,49 @@ const Dactyl = Module("dactyl", { }, /** + * Generates a help entry. + * + * @param {Command|Map|Option} obj A dactyl <b>Command</b>, + * <b>Map</b> or <b>Option</b> object + * @param {XMLList} extraHelp Extra help text beyond the description. + * @returns {string} + */ + generateHelp: function generateHelp(obj, extraHelp) + { + default xml namespace = ""; + let spec = util.identity; + let tag = util.identity; + if (obj instanceof Command) + tag = spec = function (cmd) <>:{cmd}</>; + else if (obj instanceof Map && obj.count) + spec = function (map) <><oa>count</oa>{map}</>; + else if (obj instanceof Option) + tag = spec = function (opt) <>'{opt}'</>; + + XML.prettyPrinting = false; + XML.ignoreWhitespace = false; + + // E4X has its warts. + let br = <> + </>; + + return <> + <item> + <tags>{template.map(obj.names, tag, " ")}</tags> + <spec>{spec((obj.specs || obj.names)[0])}</spec>{ + !obj.type ? "" : <> + <type>{obj.type}</type> + <default>{obj.defaultValue}</default></>} + <description>{ + obj.description ? br+<p>{obj.description.replace(/\.?$/, ".")}</p> : "" }{ + extraHelp ? br+extraHelp : "" }{ + !(extraHelp || obj.description) ? br+<p>Sorry, no help available.</p> : "" } + </description> + </item></>.toXMLString(); + }, + + + /** * Opens the help page containing the specified <b>topic</b> if it * exists. * @@ -692,8 +723,6 @@ const Dactyl = Module("dactyl", { dactyl.assert(page != null, "E149: Sorry, no help for " + topic); dactyl.open("dactyl://help/" + page, { from: "help" }); - if (options.get("activate").has("all", "help")) - content.postMessage("fragmentChange", "*"); }, /** @@ -723,15 +752,15 @@ const Dactyl = Module("dactyl", { }); } - let dirs = io.getRuntimeDirectories("plugin"); + let dirs = io.getRuntimeDirectories("plugins"); if (dirs.length == 0) { dactyl.log("No user plugin directory found", 3); return; } - dactyl.echomsg('Searching for "plugin/**/*.{js,vimp}" in ' - + [dir.path.replace(/.plugin$/, "") for ([, dir] in Iterator(dirs))] + dactyl.echomsg('Searching for "plugins/**/*.{js,vimp}" in ' + + [dir.path.replace(/.plugins$/, "") for ([, dir] in Iterator(dirs))] .join(",").quote(), 2); dirs.forEach(function (dir) { @@ -765,20 +794,33 @@ const Dactyl = Module("dactyl", { if (typeof msg == "object") msg = util.objectToString(msg, false); - services.get("console").logStringMessage(config.name.toLowerCase() + ": " + msg); + services.get("console").logStringMessage(config.name + ": " + msg); }, /** * Opens one or more URLs. Returns true when load was initiated, or * false on error. * - * @param {string|string[]} urls Either a URL string or an array of URLs. - * The array can look like this: - * ["url1", "url2", "url3", ...] - * or: - * [["url1", postdata1], ["url2", postdata2], ...] - * @param {number|Object} where If ommited, CURRENT_TAB is assumed but NEW_TAB - * is set when dactyl.forceNewTab is true. + * @param {string|Array} urls A representation of the URLs to open. May be + * either a string, which will be bassed to + * {@see Dactyl#stringToURLArray}, or an array in the same format as + * would be returned by the same. + * @param {object} params A set of parameters specifing to open the + * URLs. The following properties are recognized: + * + * • background If true, new tabs are opened in the background. + * + * • from The desgination of the opener, as appears in + * 'activate' and 'newtab' options. If present, + * the newtab option provides the default 'where' + * parameter, and the value of the 'activate' + * parameter is inverted if 'background' is true. + * + * • where One of CURRENT_TAB, NEW_TAB, or NEW_WINDOW + * + * As a deprecated special case, the where paramater may be provided + * by itself, in which case it is transformed into { where: params }. + * * @param {boolean} force Don't prompt whether to open more than 20 * tabs. * @returns {boolean} @@ -787,30 +829,29 @@ const Dactyl = Module("dactyl", { if (typeof urls == "string") urls = dactyl.stringToURLArray(urls); - if (urls.length > 20 && !force) { - commandline.input("This will open " + urls.length + " new tabs. Would you like to continue? (yes/[no]) ", + if (urls.length > 20 && !force) + return commandline.input("This will open " + urls.length + " new tabs. Would you like to continue? (yes/[no]) ", function (resp) { if (resp && resp.match(/^y(es)?$/i)) dactyl.open(urls, params, true); }); - return; - } - let flags = 0; params = params || {}; if (isarray(params)) params = { where: params }; + let flags = 0; for (let [opt, flag] in Iterator({ replace: "REPLACE_HISTORY", hide: "BYPASS_HISTORY" })) - if (params[opt]) - flags |= Ci.nsIWebNavigation["LOAD_FLAGS_" + flag]; + flags |= params[opt] && Ci.nsIWebNavigation["LOAD_FLAGS_" + flag]; let where = params.where || dactyl.CURRENT_TAB; - let background = ("background" in params) ? params.background : params.where == dactyl.NEW_BACKGROUND_TAB; - if ("from" in params && dactyl.has("tabs")) { - if (!('where' in params) && options.get("newtab").has("all", params.from)) + let background = ("background" in params) ? params.background + : params.where == dactyl.NEW_BACKGROUND_TAB; + + if (params.from && dactyl.has("tabs")) { + if (!params.where && options.get("newtab").has("all", params.from)) where = dactyl.NEW_TAB; - background = !options.get("activate").has("all", params.from); + background ^= !options.get("activate").has("all", params.from); } if (urls.length == 0) @@ -829,10 +870,8 @@ const Dactyl = Module("dactyl", { break; case dactyl.NEW_TAB: - if (!dactyl.has("tabs")) { - open(urls, dactyl.NEW_WINDOW); - return; - } + if (!dactyl.has("tabs")) + return open(urls, dactyl.NEW_WINDOW); options.withContext(function () { options.setPref("browser.tabs.loadInBackground", true); @@ -849,6 +888,9 @@ const Dactyl = Module("dactyl", { } } catch(e) {} + // Unfortunately, failed page loads throw exceptions and + // cause a lot of unwanted noise. This solution means that + // any genuine errors go unreported. } if (dactyl.forceNewTab) @@ -860,6 +902,7 @@ const Dactyl = Module("dactyl", { for (let [, url] in Iterator(urls)) { open(url, where); + where = dactyl.NEW_TAB; background = true; } }, @@ -1037,12 +1080,10 @@ const Dactyl = Module("dactyl", { services.get("observer").notifyObservers(null, "quit-application-granted", null); // enumerate all windows and call shutdown handlers - let windows = services.get("windowMediator").getEnumerator(null); - while (windows.hasMoreElements()) { - let win = windows.getNext(); + for (let win in iter(services.get("windowMediator").getEnumerator(null))) if (("tryToClose" in win) && !win.tryToClose()) return; - } + services.get("appStartup").quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit); }, @@ -1092,14 +1133,7 @@ const Dactyl = Module("dactyl", { * @property {Window[]} Returns an array of all the host application's * open windows. */ - get windows() { - let windows = []; - let enumerator = services.get("windowMediator").getEnumerator("navigator:browser"); - while (enumerator.hasMoreElements()) - windows.push(enumerator.getNext()); - - return windows; - } + get windows() [win for (win in iter(services.get("windowMediator").getEnumerator("navigator:browser")))], }, { // initially hide all GUI elements, they are later restored unless the user @@ -1193,26 +1227,27 @@ const Dactyl = Module("dactyl", { this); let class_ = dir.map(function (dir) "html|html > xul|scrollbar[orient=" + dir + "]"); - if (class_.length) - styles.addSheet(true, "scrollbar", "*", class_.join(", ") + " { visibility: collapse !important; }", true); - else - styles.removeSheet(true, "scrollbar"); + styles.addSheet(true, "scrollbar", "*", + class_.length ? class_.join(", ") + " { visibility: collapse !important; }" : ""); + options.safeSetPref("layout.scrollbar.side", opts.indexOf("l") >= 0 ? 3 : 2, "See 'guioptions' scrollbar flags."); }, validator: function (opts) (opts.indexOf("l") < 0 || opts.indexOf("r") < 0) }, tab: { + feature: "tabs", opts: { n: ["Tab number", highlight.selector("TabNumber")], N: ["Tab number over icon", highlight.selector("TabIconNumber")] }, setter: function (opts) { - const self = this; let classes = [v[1] for ([k, v] in Iterator(this.opts)) if (opts.indexOf(k) < 0)]; - let css = classes.length ? classes.join(",") + "{ display: none; }" : ""; - styles.addSheet(true, "taboptions", "chrome://*", css); - tabs.tabsBound = Array.some(opts, function (k) k in self.opts); + + styles.addSheet(true, "taboptions", "chrome://*", + classes.length ? classes.join(",") + "{ display: none; }" : ""); + + tabs.tabBinding.enabled = Array.some(opts, function (k) k in this.opts, this); statusline.updateTabCount(); } } @@ -1230,13 +1265,14 @@ const Dactyl = Module("dactyl", { "charlist", config.defaults.guioptions || "", { setter: function (value) { for (let [, group] in Iterator(groups)) - group.setter(value); + if (!group.feature || dactyl.has(group.feature)) + group.setter(value); return value; }, completer: function (context) { - let opts = [v.opts for ([k, v] in Iterator(groups))]; + let opts = [v.opts for ([k, v] in Iterator(groups)) if (!v.feature || dactyl.has(v.feature))]; opts = opts.map(function (opt) [[k, v[0]] for ([k, v] in Iterator(opt))]); - return util.Array.flatten(opts); + return array.flatten(opts); }, validator: function (val) Option.validateCompleter.call(this, val) && [v for ([k, v] in Iterator(groups))].every(function (g) !g.validator || g.validator(val)) @@ -1252,7 +1288,7 @@ const Dactyl = Module("dactyl", { options.add(["titlestring"], "Change the title of the window", - "string", config.defaults.titlestring || config.hostApplication, + "string", config.defaults.titlestring || config.host, { setter: function (value) { let win = document.documentElement; @@ -1330,13 +1366,13 @@ const Dactyl = Module("dactyl", { { argCount: "0" }); commands.add(["dia[log]"], - "Open a " + config.name + " dialog", + "Open a " + config.appname + " dialog", function (args) { - let arg = args[0]; + let dialog = args[0]; + dactyl.assert(dialog in config.dialogs, "E475: Invalid argument: " + dialog); try { - dactyl.assert(args[0] in config.dialogs, "E475: Invalid argument: " + arg); - config.dialogs[args[0]][1](); + config.dialogs[dialog][1](); } catch (e) { dactyl.echoerr("Error opening " + arg.quote() + ": " + e); @@ -1383,15 +1419,8 @@ const Dactyl = Module("dactyl", { /////////////////////////////////////////////////////////////////////////// - if (typeof AddonManager == "undefined") { + if (typeof AddonManager == "undefined") modules.AddonManager = { - getInstallForFile: function (file, callback, mimetype) { - callback({ - install: function () { - services.get("extensionManager").installItemFromFile(file, "app-profile"); - } - }); - }, getAddonById: function (id, callback) { let addon = id; if (!isobject(addon)) @@ -1438,11 +1467,19 @@ const Dactyl = Module("dactyl", { .getItemList(Ci.nsIUpdateItem["TYPE_" + type.toUpperCase()], {}))) res.append(this.getAddonById(item)); return res; - } + }, + getInstallForFile: function (file, callback, mimetype) { + callback({ + install: function () { + services.get("extensionManager").installItemFromFile(file, "app-profile"); + } + }); + }, + getInstallForURL: function (url, callback, mimetype) { + dactyl.assert(false, "Install by URL not implimented"); + }, }; - } - /////////////////////////////////////////////////////////////////////////// function callResult(method) { @@ -1467,7 +1504,7 @@ const Dactyl = Module("dactyl", { }, { argCount: "1", completer: function (context) { - context.filters.push(function ({ item: f }) f.isDirectory() || /\.xpi$/.test(f.leafName)); + context.filters.push(function ({ item }) item.isDirectory() || /\.xpi$/.test(item.leafName)); completion.file(context); } }); @@ -1483,13 +1520,13 @@ const Dactyl = Module("dactyl", { name: "exte[nable]", description: "Enable an extension", action: function (addon) addon.userDisabled = false, - filter: function ({ item: e }) e.userDisabled + filter: function ({ item }) item.userDisabled }, { name: "extd[isable]", description: "Disable an extension", action: function (addon) addon.userDisabled = true, - filter: function ({ item: e }) !e.userDisabled + filter: function ({ item }) !item.userDisabled } ].forEach(function (command) { commands.add([command.name], @@ -1535,7 +1572,7 @@ const Dactyl = Module("dactyl", { bang: true, completer: function (context) { completion.extension(context); - context.filters.push(function ({ item: e }) e.isActive && e.optionsURL); + context.filters.push(function ({ item }) item.isActive && item.optionsURL); }, literal: 0 }); @@ -1543,6 +1580,23 @@ const Dactyl = Module("dactyl", { commands.add(["extens[ions]", "exts"], "List available extensions", function (args) { + function addonExtra(e) { + let extra; + if (e.pendingOperations & AddonManager.PENDING_UNINSTALL) + extra = ["Disabled", "uninstalled"]; + else if (e.pendingOperations & AddonManager.PENDING_DISABLE) + extra = ["Disabled", "disabled"]; + else if (e.pendingOperations & AddonManager.PENDING_INSTALL) + extra = ["Enabled", "installed"]; + else if (e.pendingOperations & AddonManager.PENDING_ENABLE) + extra = ["Enabled", "enabled"]; + else if (e.pendingOperations & AddonManager.PENDING_UPGRADE) + extra = ["Enabled", "upgraded"]; + if (extra) + return <> (<span highlight={extra[0]}>{extra[1]}</span> +  on restart)</>; + return <></>; + } AddonManager.getAddonsByTypes(["extension"], function (extensions) { if (args[0]) extensions = extensions.filter(function (extension) extension.name.indexOf(args[0]) >= 0); @@ -1555,12 +1609,7 @@ const Dactyl = Module("dactyl", { e.version, (e.isActive ? <span highlight="Enabled">enabled</span> : <span highlight="Disabled">disabled</span>) + - ((e.userDisabled || e.appDisabled) == !e.isActive ? XML() : - <> ({e.userDisabled || e.appDisabled - ? <span highlight="Disabled">disabled</span> - : <span highlight="Enabled">enabled</span>} - on restart) - </>), + addonExtra(e), e.description] for ([, e] in Iterator(extensions))))); else if (filter) @@ -1658,7 +1707,7 @@ const Dactyl = Module("dactyl", { }); commands.add(["res[tart]"], - "Force " + config.name + " to restart", + "Force " + config.appname + " to restart", function () { dactyl.restart(); }, { argCount: "0" }); @@ -1810,7 +1859,7 @@ const Dactyl = Module("dactyl", { dactyl.open("about:"); else commandline.commandOutput(<> - {config.name} {dactyl.version} running on:<br/>{navigator.userAgent} + {config.appname} {dactyl.version} running on:<br/>{navigator.userAgent} </>); }, { argCount: "0", @@ -1836,11 +1885,13 @@ const Dactyl = Module("dactyl", { context.title = ["Extension"]; context.anchored = false; context.keys = { text: "name", description: "description", icon: "iconURL" }, - context.incomplete = true; - AddonManager.getAddonsByTypes(["extension"], function (addons) { - context.incomplete = false; - context.completions = addons; - }); + context.generate = function () { + context.incomplete = true; + AddonManager.getAddonsByTypes(["extension"], function (addons) { + context.incomplete = false; + context.completions = addons; + }); + }; }; completion.help = function help(context, unchunked) { @@ -1877,7 +1928,7 @@ const Dactyl = Module("dactyl", { dactyl.log("All modules loaded", 3); - services.add("commandLineHandler", "@mozilla.org/commandlinehandler/general-startup;1?type=" + config.name.toLowerCase()); + services.add("commandLineHandler", "@mozilla.org/commandlinehandler/general-startup;1?type=" + config.name); let commandline = services.get("commandLineHandler").optionValue; if (commandline) { @@ -1892,7 +1943,7 @@ const Dactyl = Module("dactyl", { dactyl.log("Command-line options: " + util.objectToString(dactyl.commandLineOptions), 3); // first time intro message - const firstTime = "extensions." + config.name.toLowerCase() + ".firsttime"; + const firstTime = "extensions." + config.name + ".firsttime"; if (options.getPref(firstTime, true)) { util.timeout(function () { dactyl.help(); @@ -1904,7 +1955,7 @@ const Dactyl = Module("dactyl", { modes.reset(); // TODO: we should have some class where all this guioptions stuff fits well - Dactyl.hideGUI(); + // Dactyl.hideGUI(); if (dactyl.commandLineOptions.preCommands) dactyl.commandLineOptions.preCommands.forEach(function (cmd) { @@ -1914,7 +1965,7 @@ const Dactyl = Module("dactyl", { // finally, read the RC file and source plugins // make sourcing asynchronous, otherwise commands that open new tabs won't work util.timeout(function () { - let extensionName = config.name.toUpperCase(); + let extensionName = config.idname; let init = services.get("environment").get(extensionName + "_INIT"); let rcFile = io.getRCFile("~"); @@ -1951,13 +2002,18 @@ const Dactyl = Module("dactyl", { // after sourcing the initialization files, this function will set // all gui options to their default values, if they have not been // set before by any RC file - for (let option in options) { + for (let option in values(options.needInit)) + // FIXME: // 'encoding' option should not be set at this timing. // Probably a wrong value is set into the option, // if current page's encoging is not UTF-8. - if (option.name != "encoding" && option.setter) - option.value = option.value; - } + try { + if (option.name != "encoding"); + option.value = option.value; + } + catch (e) { + dactyl.reportError(e); + } if (dactyl.commandLineOptions.postCommands) dactyl.commandLineOptions.postCommands.forEach(function (cmd) { @@ -1969,7 +2025,7 @@ const Dactyl = Module("dactyl", { }, 0); statusline.update(); - dactyl.log(config.name + " fully initialized", 0); + dactyl.log(config.appname + " fully initialized", 0); dactyl.initialized = true; } }); diff --git a/common/content/dactyl.xul b/common/content/dactyl.xul index 1483e7cc..49e15ff5 100644 --- a/common/content/dactyl.xul +++ b/common/content/dactyl.xul @@ -23,11 +23,6 @@ <script type="application/x-javascript;version=1.8" src="&dactyl.content;dactyl-overlay.js"/> <window id="&dactyl.mainWindow;"> - <stringbundleset id="dactyl-stringbundles"> - <stringbundle id="dactyl-charset-bundle" - src="chrome://global/locale/charsetTitles.properties"/> - </stringbundleset> - <keyset id="mainKeyset"> <key id="key_open_vimbar" key=":" oncommand="window.dactyl ∧ dactyl.modules.commandline.open(':', '', dactyl.modules.modes.EX);" modifiers=""/> <key id="key_stop" keycode="VK_ESCAPE" oncommand="window.dactyl ∧ dactyl.modules.events.onEscape();"/> @@ -43,13 +38,13 @@ <commandset id="onPentadactylFocus" commandupdater="true" events="focus" - oncommandupdate="if (window.dactyl ∧ dactyl.modules.events != undefined) dactyl.modules.events.onFocusChange(event);"/> + oncommandupdate="if (window.dactyl ∧ dactyl.modules.loaded.events) dactyl.modules.events.onFocusChange(event);"/> <commandset id="onPentadactylSelect" commandupdater="true" events="select" - oncommandupdate="if (window.dactyl ∧ dactyl.modules.events != undefined) dactyl.modules.events.onSelectionChange(event);"/> + oncommandupdate="if (window.dactyl ∧ dactyl.modules.loaded.events) dactyl.modules.events.onSelectionChange(event);"/> - <!-- As of Firefox 3.1pre, <iframe>.height changes do not seem to have immediate effect, + <!-- As of Firefox 3.1pre, iframe.height changes do not seem to have immediate effect, therefore we need to put them into a <vbox> for which that works just fine --> <vbox class="dactyl-container" hidden="false" collapsed="true"> <iframe id="dactyl-multiline-output" src="chrome://dactyl/content/buffer.xhtml" @@ -81,7 +76,6 @@ oninput="window.dactyl ∧ dactyl.modules.commandline.onMultilineInputEvent(event);" onblur="window.dactyl ∧ dactyl.modules.commandline.onMultilineInputEvent(event);"/> </vbox> - </window> <statusbar id="status-bar" dactyl:highlight="StatusLine"> @@ -98,7 +92,6 @@ <statusbarpanel id="statusbar-display" hidden="true"/> <statusbarpanel id="statusbar-progresspanel" hidden="true"/> </statusbar> - </overlay> <!-- vim: set fdm=marker sw=4 ts=4 et: --> diff --git a/common/content/editor.js b/common/content/editor.js index 25b4f461..2dc6844c 100644 --- a/common/content/editor.js +++ b/common/content/editor.js @@ -569,7 +569,7 @@ const Editor = Module("editor", { let list = this.getAbbreviations(filter, lhs); if (!list.length) - dactyl.echomsg("No this._abbreviations found"); + dactyl.echomsg("No abbreviations found"); else if (list.length == 1) { let [mode, lhs, rhs] = list[0]; diff --git a/common/content/eval.js b/common/content/eval.js index 7fc899f4..49298d92 100644 --- a/common/content/eval.js +++ b/common/content/eval.js @@ -4,7 +4,7 @@ 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. -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k at Gmail> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. diff --git a/common/content/events.js b/common/content/events.js index ddfd4697..81fdb5f1 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k at Gmail> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -1173,7 +1173,7 @@ const Events = Module("events", { function () { document.commandDispatcher.rewindFocus(); }); mappings.add(modes.all, - ["<C-z>"], "Temporarily ignore all " + config.name + " key bindings", + ["<C-z>"], "Temporarily ignore all " + config.appname + " key bindings", function () { modes.passAllKeys = true; }); mappings.add(modes.all, diff --git a/common/content/finder.js b/common/content/finder.js index 95c2356d..dbbe38b1 100644 --- a/common/content/finder.js +++ b/common/content/finder.js @@ -27,20 +27,25 @@ const RangeFinder = Module("rangefinder", { let highlighted = this.rangeFind && this.rangeFind.highlighted; let selections = this.rangeFind && this.rangeFind.selections; + let regex = false; let matchCase = !(options["ignorecase"] || options["smartcase"] && !/[A-Z]/.test(str)); let linksOnly = options["linksearch"]; str = str.replace(/\\(.|$)/g, function (m, n1) { - if (n1 == "l") - linksOnly = true; - else if (n1 == "L") - linksOnly = false; - else if (n1 == "c") + if (n1 == "c") matchCase = false; else if (n1 == "C") matchCase = true; + else if (n1 == "l") + linksOnly = true; + else if (n1 == "L") + linksOnly = false; + else if (n1 == "r") + regex = true; + else if (n1 == "R") + regex = false; else - return n1; + return m; return ""; }); @@ -49,12 +54,13 @@ const RangeFinder = Module("rangefinder", { if (!this.rangeFind || this.rangeFind.window.get() != window || linksOnly != !!this.rangeFind.elementPath + || regex != this.rangeFind.regex || matchCase != this.rangeFind.matchCase || !!backward != this.rangeFind.reverse) { if (this.rangeFind) this.rangeFind.cancel(); - this.rangeFind = RangeFind(matchCase, backward, linksOnly && options["hinttags"]); + this.rangeFind = RangeFind(matchCase, backward, linksOnly && options["hinttags"], regex); this.rangeFind.highlighted = highlighted; this.rangeFind.selections = selections; } @@ -201,7 +207,7 @@ const RangeFinder = Module("rangefinder", { }, options: function () { - options.safeSetPref("accessibility.typeaheadfind.autostart", false); + // options.safeSetPref("accessibility.typeaheadfind.autostart", false); // The above should be sufficient, but: https://bugzilla.mozilla.org/show_bug.cgi?id=348187 options.safeSetPref("accessibility.typeaheadfind", false); @@ -262,13 +268,14 @@ const RangeFinder = Module("rangefinder", { * large amounts of data are concerned (e.g., for API documents). */ const RangeFind = Class("RangeFind", { - init: function (matchCase, backward, elementPath) { + init: function (matchCase, backward, elementPath, regex) { this.window = Cu.getWeakReference(window); this.elementPath = elementPath || null; - this.matchCase = Boolean(matchCase); this.reverse = Boolean(backward); + this.finder = services.create("find"); - this.finder.caseSensitive = this.matchCase; + this.matchCase = Boolean(matchCase); + this.regex = Boolean(regex); this.ranges = this.makeFrameList(content); @@ -281,6 +288,12 @@ const RangeFind = Class("RangeFind", { get backward() this.finder.findBackwards, + get matchCase() this.finder.caseSensitive, + set matchCase(val) this.finder.caseSensitive = Boolean(val), + + get regex() this.finder.regularExpression, + set regex(val) this.finder.regularExpression = Boolean(val), + get searchString() this.lastString, get selectedRange() { @@ -437,7 +450,7 @@ const RangeFind = Class("RangeFind", { let pageStart = RangeFind.endpoint(pageRange, true); let pageEnd = RangeFind.endpoint(pageRange, false); - for (let frame in util.Array.itervalues(win.frames)) { + for (let frame in array.itervalues(win.frames)) { let range = doc.createRange(); if (util.computedStyle(frame.frameElement).visibility == "visible") { range.selectNode(frame.frameElement); diff --git a/common/content/help-single.xsl b/common/content/help-single.xsl deleted file mode 100644 index 7367a6f2..00000000 --- a/common/content/help-single.xsl +++ /dev/null @@ -1,36 +0,0 @@ -<!DOCTYPE document SYSTEM "chrome://dactyl/content/dactyl.dtd"> - -<xsl:stylesheet version="1.0" - xmlns="http://www.w3.org/1999/xhtml" - xmlns:html="http://www.w3.org/1999/xhtml" - xmlns:dactyl="http://vimperator.org/namespaces/liberator" - xmlns:xsl="http://www.w3.org/1999/XSL/Transform" - xmlns:str="http://exslt.org/strings" - xmlns:exsl="http://exslt.org/common" - extension-element-prefixes="exsl str"> - - <xsl:output method="xml" indent="no"/> - - <xsl:variable name="root" select="/dactyl:document"/> - <xsl:variable name="tags"> - <xsl:text> </xsl:text> - <xsl:for-each select="$root//@tag|$root//dactyl:tags/text()|$root//dactyl:tag/text()"> - <xsl:value-of select="concat(., ' ')"/> - </xsl:for-each> - </xsl:variable> - - <xsl:template name="parse-tags"> - <xsl:param name="text"/> - <div dactyl:highlight="HelpTags"> - <xsl:for-each select="str:tokenize($text)"> - <a id="{.}" dactyl:highlight="HelpTag"><xsl:value-of select="."/></a> - </xsl:for-each> - </div> - </xsl:template> - - <xsl:template match="/"> - <xsl:call-template name="parse-tags"> - <xsl:with-param name="text" select="$tags"/> - </xsl:call-template> - </xsl:template> -</xsl:stylesheet> diff --git a/common/content/help.js b/common/content/help.js index ed1dd22a..092678aa 100644 --- a/common/content/help.js +++ b/common/content/help.js @@ -1,4 +1,4 @@ -// Copyright (c) 2009 by Kris Maglione <kris@vimperator.org> +// Copyright (c) 2009-2010 by Kris Maglione <kris@vimperator.org> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -13,9 +13,6 @@ function checkFragment() { } document.addEventListener("load", checkFragment, true); -window.addEventListener("message", function (event) { - if (event.data == "fragmentChange") - checkFragment(); -}, true); +document.addEventListener("hashChange", checkFragment, true); // vim: set fdm=marker sw=4 ts=4 et: diff --git a/common/content/help.xsl b/common/content/help.xsl index 0fe24524..ff03cf20 100644 --- a/common/content/help.xsl +++ b/common/content/help.xsl @@ -6,40 +6,27 @@ xmlns:html="http://www.w3.org/1999/xhtml" xmlns:dactyl="http://vimperator.org/namespaces/liberator" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" - xmlns:str="http://exslt.org/strings" xmlns:exsl="http://exslt.org/common" - extension-element-prefixes="exsl str"> + xmlns:regexp="http://exslt.org/regular-expressions" + xmlns:str="http://exslt.org/strings" + extension-element-prefixes="exsl regexp str"> <xsl:output method="xml" indent="no"/> <!-- Variable Definitions {{{1 --> - <xsl:variable name="doc"> - <xsl:apply-templates select="/dactyl:document" mode="overlay"/> - </xsl:variable> - <xsl:variable name="root" select="exsl:node-set($doc)"/> - - <xsl:variable name="tags"> - <xsl:text> </xsl:text> - <xsl:for-each select="$root//@tag|$root//dactyl:tags/text()|$root//dactyl:tag/text()"> - <xsl:value-of select="concat(., ' ')"/> - </xsl:for-each> - </xsl:variable> <!-- Process Overlays {{{1 --> - <xsl:variable name="overlay" select="concat('dactyl://help-overlay/', /dactyl:document/@name)"/> - <xsl:variable name="overlaydoc" select="document($overlay)/dactyl:overlay"/> - <xsl:template name="splice-overlays"> <xsl:param name="elem"/> <xsl:param name="tag"/> - <xsl:for-each select="$overlaydoc/*[@insertbefore=$tag]"> + <xsl:for-each select="ancestor::*/dactyl:overlay/*[@insertbefore=$tag]"> <xsl:apply-templates select="." mode="overlay"/> </xsl:for-each> <xsl:choose> - <xsl:when test="$overlaydoc/*[@replace=$tag] and not($elem[@replace])"> - <xsl:for-each select="$overlaydoc/*[@replace=$tag]"> + <xsl:when test="ancestor::*/dactyl:overlay/*[@replace=$tag] and not($elem[@replace])"> + <xsl:for-each select="ancestor::*/dactyl:overlay/*[@replace=$tag]"> <xsl:apply-templates select="." mode="overlay-2"/> </xsl:for-each> </xsl:when> @@ -49,7 +36,7 @@ </xsl:for-each> </xsl:otherwise> </xsl:choose> - <xsl:for-each select="$overlaydoc/*[@insertafter=$tag]"> + <xsl:for-each select="ancestor::*/dactyl:overlay/*[@insertafter=$tag]"> <xsl:apply-templates select="." mode="overlay"/> </xsl:for-each> </xsl:template> @@ -75,9 +62,32 @@ <!-- Process Inclusions {{{1 --> + <xsl:template name="include"> + <xsl:param name="root-node" select="."/> + <xsl:param name="overlay" select="concat('dactyl://help-overlay/', $root-node/@name)"/> + + <!-- Ridiculous three-pass processing is needed to deal with + - lack of dynamic variable scope in XSL 1.0. --> + + <!-- Store a copy of the overlay for the current document. --> + <xsl:variable name="doc"> + <dactyl:document> + <xsl:copy-of select="document($overlay)/dactyl:overlay"/> + <xsl:copy-of select="$root-node/node()"/> + </dactyl:document> + </xsl:variable> + + <xsl:call-template name="parse-tags"> + <xsl:with-param name="text" select="concat($root-node/@name, '.xml')"/> + </xsl:call-template> + <xsl:apply-templates select="exsl:node-set($doc)/dactyl:document/node()[position() != 1]" mode="overlay"/> + </xsl:template> + <xsl:template match="dactyl:include" mode="overlay-2"> <div dactyl:highlight="HelpInclude"> - <xsl:apply-templates select="document(@href)/dactyl:document/node()" mode="overlay"/> + <xsl:call-template name="include"> + <xsl:with-param name="root-node" select="document(@href)/dactyl:document"/> + </xsl:call-template> </div> </xsl:template> @@ -93,22 +103,39 @@ <!-- Root {{{1 --> <xsl:template match="/"> - <xsl:for-each select="$root/dactyl:document"> - <html dactyl:highlight="Help"> - <head> - <title><xsl:value-of select="@title"/></title> - <script type="text/javascript" - src="chrome://dactyl/content/help.js"/> - </head> - <body dactyl:highlight="HelpBody"> - <div dactyl:highlight="Logo"/> - <xsl:call-template name="parse-tags"> - <xsl:with-param name="text" select="concat(@name, '.html')"/> - </xsl:call-template> - <xsl:apply-templates/> - </body> - </html> - </xsl:for-each> + + <!-- Ridiculous three-pass processing is needed to deal with + - lack of dynamic variable scope in XSL 1.0. --> + + <xsl:variable name="doc1"> + <xsl:call-template name="include"> + <xsl:with-param name="root-node" select="dactyl:document"/> + </xsl:call-template> + </xsl:variable> + <xsl:variable name="root" select="exsl:node-set($doc1)"/> + + <!-- Store a cache of all tags defined --> + <xsl:variable name="doc2"> + <dactyl:document> + <xsl:attribute name="document-tags"> + <xsl:text> </xsl:text> + <xsl:for-each select="$root//@tag|$root//dactyl:tags/text()|$root//dactyl:tag/text()"> + <xsl:value-of select="concat(., ' ')"/> + </xsl:for-each> + </xsl:attribute> + <xsl:copy-of select="$root/node()"/> + </dactyl:document> + </xsl:variable> + + <html dactyl:highlight="Help"> + <head> + <title><xsl:value-of select="@title"/></title> + <script type="text/javascript" src="chrome://dactyl/content/help.js"/> + </head> + <body dactyl:highlight="HelpBody"> + <xsl:apply-templates select="exsl:node-set($doc2)/dactyl:document/node()" mode="help-1"/> + </body> + </html> </xsl:template> <!-- Table of Contents {{{1 --> @@ -125,14 +152,14 @@ local-name() = $tag and not(preceding::*[local-name() = $lasttag][position() = 1 and not(.=$context)])]"/> <xsl:if test="$nodes"> - <ol dactyl:highlight="HelpOrderedList"> + <ol level="{$level}" dactyl:highlight="HelpOrderedList"> <xsl:for-each select="$nodes"> <li> <a> <xsl:if test="@tag"> <xsl:attribute name="href"><xsl:value-of select="concat('#', substring-before(concat(@tag, ' '), ' '))"/></xsl:attribute> </xsl:if> - <xsl:apply-templates select="node()"/> + <xsl:apply-templates select="node()" mode="help-1"/> </a> <xsl:call-template name="toc"> <xsl:with-param name="level" select="$level + 1"/> @@ -144,7 +171,7 @@ </ol> </xsl:if> </xsl:template> - <xsl:template match="dactyl:toc" mode="pass-2"> + <xsl:template match="dactyl:toc" mode="help-2"> <xsl:variable name="TOC"> <context/> <xsl:for-each @@ -174,36 +201,38 @@ <!-- Items {{{1 --> - <xsl:template match="dactyl:strut" mode="pass-2"> + <xsl:template match="dactyl:strut" mode="help-2"> <div style="clear: both"/> </xsl:template> - <xsl:template match="dactyl:item" mode="pass-2"> + <xsl:template match="dactyl:item" mode="help-2"> <div dactyl:highlight="HelpItem"> - <xsl:apply-templates select="dactyl:tags|dactyl:spec|dactyl:strut"/> + <xsl:apply-templates select="dactyl:tags|dactyl:spec|dactyl:strut" mode="help-1"/> <xsl:if test="not(dactyl:description/@short)"> <hr style="border: 0; height: 0; margin: 0; width: 100%; float: right;"/> - <div dactyl:highlight="HelpOptInfo"> - <xsl:apply-templates select="dactyl:type|dactyl:default"/> - <div style="clear: both;"/> - </div> + <xsl:if test="dactyl:type|dactyl:default"> + <div dactyl:highlight="HelpOptInfo"> + <xsl:apply-templates select="dactyl:type|dactyl:default" mode="help-1"/> + <div style="clear: both;"/> + </div> + </xsl:if> </xsl:if> - <xsl:apply-templates select="dactyl:description"/> + <xsl:apply-templates select="dactyl:description" mode="help-1"/> <div style="clear: both;"/> </div> </xsl:template> - <xsl:template match="dactyl:spec[preceding-sibling::dactyl:spec]" mode="pass-2"> + <!-- + <xsl:template match="dactyl:item/dactyl:spec[position() = last()]" mode="help-2"> <div style="clear: both;"/> - <div dactyl:highlight="HelpSpec"> - <xsl:apply-templates/> - </div> + <div dactyl:highlight="HelpSpec"><xsl:apply-templates mode="help-1"/></div> </xsl:template> + --> - <xsl:template match="dactyl:default[not(@type='plain')]" mode="pass-2"> + <xsl:template match="dactyl:default[not(@type='plain')]" mode="help-2"> <xsl:variable name="type" select="preceding-sibling::dactyl:type[1] | following-sibling::dactyl:type[1]"/> <span dactyl:highlight="HelpDefault">(default:<xsl:text> </xsl:text> <xsl:choose> <xsl:when test="starts-with($type, 'string') or starts-with($type, 'regex')"> - <span dactyl:highlight="HelpString"><xsl:apply-templates/></span> + <span dactyl:highlight="HelpString"><xsl:apply-templates mode="help-1"/></span> </xsl:when> <xsl:otherwise> <span> @@ -214,22 +243,32 @@ <xsl:when test="$type = 'charlist'">String</xsl:when> </xsl:choose> </xsl:attribute> - <xsl:apply-templates/> + <xsl:apply-templates select="node()" mode="help-1"/> </span> </xsl:otherwise> - </xsl:choose>) - </span> + </xsl:choose>)</span> </xsl:template> <!-- Tag Definitions {{{1 --> - <xsl:template match="dactyl:tags" mode="pass-2"> + <xsl:template match="dactyl:item/dactyl:tags[position() = last()]" mode="help-2"> <div style="clear: right"/> <xsl:call-template name="parse-tags"> <xsl:with-param name="text" select="."/> </xsl:call-template> </xsl:template> - <xsl:template match="dactyl:tag|@tag" mode="pass-2"> + <xsl:template match="dactyl:tags" mode="help-2"> + <xsl:call-template name="parse-tags"> + <xsl:with-param name="text" select="."/> + </xsl:call-template> + </xsl:template> + <xsl:template match="@tag[parent::dactyl:p]" mode="help-2"> + <xsl:call-template name="parse-tags"> + <xsl:with-param name="text" select="."/> + </xsl:call-template> + <div style="clear: right"/> + </xsl:template> + <xsl:template match="dactyl:tag|@tag" mode="help-2"> <xsl:call-template name="parse-tags"> <xsl:with-param name="text" select="."/> </xsl:call-template> @@ -249,31 +288,40 @@ <xsl:param name="contents" select="text()"/> <xsl:variable name="tag" select="str:tokenize($contents, ' [!')[1]"/> <a href="dactyl://help-tag/{$tag}" style="color: inherit;"> - <xsl:if test="contains($tags, concat(' ', $tag, ' '))"> + <xsl:if test="contains(ancestor::*/@document-tags, concat(' ', $tag, ' '))"> <xsl:attribute name="href">#<xsl:value-of select="$tag"/></xsl:attribute> </xsl:if> <xsl:value-of select="$contents"/> </a> </xsl:template> - <xsl:template match="dactyl:o" mode="pass-2"> - <span dactyl:highlight="HelpOption"> + <xsl:template match="dactyl:o" mode="help-2"> + <span dactyl:highlight="HelpOpt"> <xsl:call-template name="linkify-tag"> <xsl:with-param name="contents" select='concat("'", text(), "'")'/> </xsl:call-template> </span> </xsl:template> - <xsl:template match="dactyl:t" mode="pass-2"> + <xsl:template match="dactyl:pref" mode="help-2"> + <a href="http://kb.mozillazine.org/{text()}" dactyl:highlight="HelpOpt" + >'<xsl:apply-templates select="@*|node()" mode="help-1"/>'</a> + </xsl:template> + <xsl:template match="dactyl:t" mode="help-2"> <span dactyl:highlight="HelpTopic"> <xsl:call-template name="linkify-tag"/> </span> </xsl:template> - <xsl:template match="dactyl:k" mode="pass-2"> + <xsl:template match="dactyl:k" mode="help-2"> <span dactyl:highlight="HelpKey"> <xsl:call-template name="linkify-tag"/> </span> </xsl:template> - <xsl:template match="dactyl:k[@name]" mode="pass-2"> + <xsl:template match="dactyl:kwd" mode="help-2"> + <span dactyl:highlight="HelpKeyword"> + <xsl:apply-templates select="@*|node()" mode="help-1"/> + </span> + </xsl:template> + <xsl:template match="dactyl:k[@name]" mode="help-2"> <span dactyl:highlight="HelpKey"> <xsl:call-template name="linkify-tag"> <xsl:with-param name="contents" select="concat('<', @name, '>', .)"/> @@ -283,100 +331,117 @@ <!-- HTML-ish elements {{{1 --> - <xsl:template match="dactyl:dl" mode="pass-2"> + <xsl:template match="dactyl:dl" mode="help-2"> <dl> <column/> <column/> <xsl:for-each select="dactyl:dt"> <tr> - <xsl:apply-templates select="."/> - <xsl:apply-templates select="following-sibling::dactyl:dd[1]"/> + <xsl:apply-templates select="." mode="help-1"/> + <xsl:apply-templates select="following-sibling::dactyl:dd[1]" mode="help-1"/> </tr> </xsl:for-each> </dl> </xsl:template> - <xsl:template match="dactyl:link" mode="pass-2"> - <a href="{@topic}"><xsl:apply-templates select="@*|node()"/></a> + <xsl:template match="dactyl:link" mode="help-2"> + <a href="{@topic}"> + <xsl:if test="regexp:match(@topic, '^[a-zA-Z]*:', '') + and not(starts-with(@topic, 'mailto:'))"> + <xsl:attribute name="rel">external</xsl:attribute> + </xsl:if> + <xsl:apply-templates select="@*|node()" mode="help-1"/> + </a> </xsl:template> + <xsl:template match="dactyl:hl" mode="help-2"> + <span dactyl:highlight="{@key}"><xsl:apply-templates select="@*|node()" mode="help-1"/></span> + </xsl:template> + <xsl:template match="dactyl:h" mode="help-2"> + <em><xsl:apply-templates select="@*|node()" mode="help-1"/></em> + </xsl:template> <xsl:template match="dactyl:em | dactyl:tt | dactyl:p | dactyl:dt | dactyl:dd | dactyl:ol | dactyl:ul | dactyl:li | dactyl:h1 | dactyl:h2 | dactyl:h3" - mode="pass-2"> - <xsl:element name="html:{local-name()}"> - <xsl:apply-templates select="@*|node()"/> + mode="help-2"> + <xsl:element name="{local-name()}"> + <xsl:apply-templates select="@*|node()" mode="help-1"/> </xsl:element> </xsl:template> - <xsl:template match="dactyl:code" mode="pass-2"> - <pre dactyl:highlight="HelpCode"><xsl:apply-templates select="@*|node()"/></pre> + <xsl:template match="dactyl:code" mode="help-2"> + <pre dactyl:highlight="HelpCode"><xsl:apply-templates select="@*|node()" mode="help-1"/></pre> </xsl:template> <!-- Help elements {{{1 --> - <xsl:template match="dactyl:a" mode="pass-2"> - <span dactyl:highlight="HelpArg">{<xsl:apply-templates select="@*|node()"/>}</span> + <xsl:template match="dactyl:a" mode="help-2"> + <span dactyl:highlight="HelpArg">{<xsl:apply-templates select="@*|node()" mode="help-1"/>}</span> </xsl:template> - <xsl:template match="dactyl:oa" mode="pass-2"> - <span dactyl:highlight="HelpOptionalArg">[<xsl:apply-templates select="@*|node()"/>]</span> + <xsl:template match="dactyl:oa" mode="help-2"> + <span dactyl:highlight="HelpOptionalArg">[<xsl:apply-templates select="@*|node()" mode="help-1"/>]</span> </xsl:template> - <xsl:template match="dactyl:note" mode="pass-2"> + <xsl:template match="dactyl:note" mode="help-2"> <p style="clear: both;"> - <xsl:apply-templates select="@*"/> + <xsl:apply-templates select="@*" mode="help-1"/> <div style="clear: both;"/> <span dactyl:highlight="HelpNote">Note:</span> <xsl:text> </xsl:text> - <xsl:apply-templates select="node()"/> + <xsl:apply-templates select="node()" mode="help-1"/> </p> </xsl:template> - <xsl:template match="dactyl:warning" mode="pass-2"> + <xsl:template match="dactyl:warning" mode="help-2"> <p style="clear: both;"> - <xsl:apply-templates select="@*"/> + <xsl:apply-templates select="@*" mode="help-1"/> <div style="clear: both;"/> <span dactyl:highlight="HelpWarning">Warning:</span> <xsl:text> </xsl:text> - <xsl:apply-templates select="node()"/> + <xsl:apply-templates select="node()" mode="help-1"/> </p> </xsl:template> - <xsl:template match="dactyl:default" mode="pass-2"> - <span dactyl:highlight="HelpDefault"> - (default:<xsl:text> </xsl:text><xsl:apply-templates select="@*|node()"/>) - </span> + <xsl:template match="dactyl:default" mode="help-2"> + <span dactyl:highlight="HelpDefault">(default:<xsl:text> </xsl:text><xsl:apply-templates select="@*|node()" mode="help-1"/>)</span> </xsl:template> <!-- HTML-ify other elements {{{1 --> - <xsl:template match="dactyl:ex" mode="pass-2"> + <xsl:template match="dactyl:ex" mode="help-2"> <span dactyl:highlight="HelpEx"> <xsl:variable name="tag" select="str:tokenize(text(), ' [!')[1]"/> <a href="dactyl://help-tag/{$tag}" style="color: inherit;"> - <xsl:if test="contains($tags, concat(' ', $tag, ' '))"> + <xsl:if test="contains(ancestor::*/@document-tags, concat(' ', $tag, ' '))"> <xsl:attribute name="href">#<xsl:value-of select="$tag"/></xsl:attribute> </xsl:if> - <xsl:apply-templates/> + <xsl:apply-templates mode="help-1"/> </a> </span> </xsl:template> - <xsl:template match="dactyl:description | dactyl:example | dactyl:spec" mode="pass-2"> + <xsl:template match="dactyl:description | dactyl:example | dactyl:spec" mode="help-2"> <div> <xsl:if test="self::dactyl:description"><xsl:attribute name="dactyl:highlight">HelpDescription</xsl:attribute></xsl:if> <xsl:if test="self::dactyl:example"><xsl:attribute name="dactyl:highlight">HelpExample</xsl:attribute></xsl:if> <xsl:if test="self::dactyl:spec"><xsl:attribute name="dactyl:highlight">HelpSpec</xsl:attribute></xsl:if> - <xsl:apply-templates select="@*|node()"/> + <xsl:apply-templates select="@*|node()" mode="help-1"/> </div> </xsl:template> - <xsl:template match="dactyl:str | dactyl:t | dactyl:type" mode="pass-2"> + <xsl:template match="dactyl:str | dactyl:type" mode="help-2"> <span> <xsl:if test="self::dactyl:str"><xsl:attribute name="dactyl:highlight">HelpString</xsl:attribute></xsl:if> - <xsl:if test="self::dactyl:t"><xsl:attribute name="dactyl:highlight">HelpTopic</xsl:attribute></xsl:if> <xsl:if test="self::dactyl:type"><xsl:attribute name="dactyl:highlight">HelpType</xsl:attribute></xsl:if> - <xsl:apply-templates select="@*|node()"/> + <xsl:apply-templates select="@*|node()" mode="help-1"/> </span> </xsl:template> + <xsl:template match="dactyl:xml-block" mode="help-2"> + <div dactyl:highlight="HelpXMLBlock"> + <xsl:call-template name="xml-highlight"/> + </div> + </xsl:template> + <xsl:template match="dactyl:xml-highlight" mode="help-2"> + <xsl:call-template name="xml-highlight"/> + </xsl:template> <!-- Plugins {{{1 --> @@ -400,59 +465,133 @@ </span> </div> </xsl:template> - <xsl:template match="dactyl:author[@email]" mode="pass-2"> + <xsl:template match="dactyl:author[@email]" mode="help-2"> <xsl:call-template name="info"> <xsl:with-param name="label" select="'Author'"/> <xsl:with-param name="extra"> - <xsl:text> </xsl:text><a href="mailto:{@email}">✉</a> + <xsl:text> </xsl:text><a href="mailto:{@email}"></a> </xsl:with-param> </xsl:call-template> </xsl:template> - <xsl:template match="dactyl:author" mode="pass-2"> + <xsl:template match="dactyl:author" mode="help-2"> <xsl:call-template name="info"> <xsl:with-param name="label" select="'Author'"/> </xsl:call-template> </xsl:template> - <xsl:template match="dactyl:license" mode="pass-2"> + <xsl:template match="dactyl:license" mode="help-2"> <xsl:call-template name="info"> <xsl:with-param name="label" select="'License'"/> </xsl:call-template> </xsl:template> - <xsl:template match="dactyl:plugin" mode="pass-2"> + <xsl:template match="dactyl:plugin" mode="help-2"> <xsl:call-template name="info"> <xsl:with-param name="label" select="'Plugin'"/> <xsl:with-param name="nodes"> <span><xsl:value-of select="@name"/></span> </xsl:with-param> </xsl:call-template> - <xsl:apply-templates/> + <xsl:if test="@version"> + <xsl:call-template name="info"> + <xsl:with-param name="label" select="'Version'"/> + <xsl:with-param name="link" select="''"/> + <xsl:with-param name="nodes"> + <span><xsl:value-of select="@version"/></span> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + <xsl:apply-templates mode="help-1"/> </xsl:template> <!-- Special Element Templates {{{1 --> - <xsl:template match="dactyl:logo"> + <xsl:template match="dactyl:logo" mode="help-1"> <span dactyl:highlight="Logo"/> </xsl:template> - <xsl:template match="dactyl:pan[dactyl:handle]"> - <form style="text-align:center" xmlns="http://www.w3.org/1999/xhtml" - action="https://www.paypal.com/cgi-bin/webscr" method="post"> - <input type="hidden" name="cmd" value="_s-xclick"/> - <input type="image" src="chrome://dactyl/content/x-click-but21.png" border="0" name="submit" alt="Donate with PayPal"/> - <input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----MIIHPwYJKoZIhvcNAQcEoIIHMDCCBywCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYAUOJADCwiik68MpIUKcMAtNfs4Cx6RY7604ZujgKj7WVaiELWyhUUDSaq8+iLYaNkRUq+dDld96KwhfodqP3MEmIzpQ/qKvh5+4JzTWSBU5G1lHzc4NJQw6TpXKloPxxXhuGKzZ84/asKZIZpLfkP5i8VtqVFecu7qYc0q1U2KoDELMAkGBSsOAwIaBQAwgbwGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIWR7nX4WwgcqAgZgO41g/NtgfBwI14LlJx3p5Hc4nHsQD2wyu5l4BMndkc3mc0uRTXvzutcfPBxYC4aGV5UDn6c+XPzsne+OAdSs4/0a2DJe85SBDOlVyOekz3rRhy5+6XKpKQ7qfiMpKROladi4opfMac/aDUPhGeVsY0jtQCtelIE199iaVKhlbiDvfE7nzV5dLU4d3VZwSDuWBIrIIi9GMtKCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA4MDYwNTE0NDk1OFowIwYJKoZIhvcNAQkEMRYEFBpY8FafLq7i3V0czWS9TbR/RjyQMA0GCSqGSIb3DQEBAQUABIGAPvYR9EC2ynooWAvX0iw9aZYTrpX2XrTl6lYkZaLrhM1zKn4RuaiL33sPtq0o0uSKm98gQHzh4P6wmzES0jzHucZjCU4VlpW0fC+/pJxswbW7Qux+ObsNx3f45OcvprqMMZyJiEOULcNhxkm9pCeXQMUGwlHoRRtAxYK2T8L/rQQ=-----END PKCS7----- - "/> - </form> - </xsl:template> - <!-- Process Tree {{{1 --> - <xsl:template match="@*|node()" mode="pass-2"> + <xsl:template match="@*|node()" mode="help-2"> <xsl:copy> - <xsl:apply-templates select="@*|node()"/> + <xsl:apply-templates select="@*|node()" mode="help-1"/> </xsl:copy> </xsl:template> - <xsl:template match="@*|node()"> - <xsl:apply-templates select="." mode="pass-2"/> + <xsl:template match="@*|node()" mode="help-1"> + <xsl:apply-templates select="." mode="help-2"/> + </xsl:template> + + <!-- XML Highlighting (xsl:import doesn't work in Firefox 3.x) {{{1 --> + <xsl:template name="xml-highlight"> + <div dactyl:highlight="HelpXML"> + <xsl:apply-templates mode="xml-highlight"/> + </div> + </xsl:template> + + <xsl:template name="xml-namespace"> + <xsl:param name="node" select="."/> + <xsl:if test="name($node) != local-name($node)"> + <span dactyl:highlight="HelpXMLNamespace"> + <xsl:value-of select="substring-before(name($node), ':')"/> + </span> + </xsl:if> + <xsl:value-of select="local-name($node)"/> + </xsl:template> + + <xsl:template match="*" mode="xml-highlight"> + <span dactyl:highlight="HelpXMLTagStart"> + <xsl:text><</xsl:text> + <xsl:call-template name="xml-namespace"/> + <xsl:apply-templates select="@*" mode="xml-highlight"/> + <xsl:text>/></xsl:text> + </span> + </xsl:template> + + <xsl:template match="*[node()]" mode="xml-highlight"> + <span dactyl:highlight="HelpXMLTagStart"> + <xsl:text><</xsl:text> + <xsl:call-template name="xml-namespace"/> + <xsl:apply-templates select="@*" mode="xml-highlight"/> + <xsl:text>></xsl:text> + </span> + <xsl:apply-templates select="node()" mode="xml-highlight"/> + <span dactyl:highlight="HelpXMLTagEnd"> + <xsl:text></</xsl:text> + <xsl:call-template name="xml-namespace"/> + <xsl:text>></xsl:text> + </span> + </xsl:template> + + <xsl:template match="dactyl:escape | dactyl:escape[node()]" mode="xml-highlight"> + <span dactyl:highlight="HelpXMLText"> + <xsl:apply-templates mode="help-1"/> + </span> + </xsl:template> + + <xsl:template match="@*" mode="xml-highlight"> + <xsl:text> </xsl:text> + <span dactyl:highlight="HelpXMLAttribute"> + <xsl:call-template name="xml-namespace"/> + </span> + <span dactyl:highlight="HelpXMLString"> + <xsl:value-of select="regexp:replace(., '"', 'g', '&quot;')"/> + </span> + </xsl:template> + + <xsl:template match="comment()" mode="xml-highlight"> + <span dactyl:highlight="HelpXMLComment"> + <xsl:value-of select="."/> + </span> + </xsl:template> + + <xsl:template match="processing-instruction()" mode="xml-highlight"> + <span dactyl:highlight="HelpXMLProcessing"> + <xsl:value-of select="."/> + </span> + </xsl:template> + + <xsl:template match="text()" mode="xml-highlight"> + <span dactyl:highlight="HelpXMLText"> + <xsl:value-of select="regexp:replace(., '<', 'g', '&lt;')"/> + </span> </xsl:template> </xsl:stylesheet> diff --git a/common/content/hints.js b/common/content/hints.js index 5bb503a7..4095eddd 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k@gmail.com> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -36,26 +36,26 @@ const Hints = Module("hints", { function images() util.makeXPath(["img"]); this._hintModes = { - ";": Mode("Focus hint", function (elem) buffer.focusElement(elem), extended), - "?": Mode("Show information for hint", function (elem) buffer.showElementInfo(elem), extended), + ";": Mode("Focus hint", function (elem) buffer.focusElement(elem), extended), + "?": Mode("Show information for hint", function (elem) buffer.showElementInfo(elem), extended), s: Mode("Save hint", function (elem) buffer.saveLink(elem, true)), a: Mode("Save hint with prompt", function (elem) buffer.saveLink(elem, false)), f: Mode("Focus frame", function (elem) elem.ownerDocument.defaultView.focus(), function () ["body"]), o: Mode("Follow hint", function (elem) buffer.followLink(elem, dactyl.CURRENT_TAB)), t: Mode("Follow hint in a new tab", function (elem) buffer.followLink(elem, dactyl.NEW_TAB)), b: Mode("Follow hint in a background tab", function (elem) buffer.followLink(elem, dactyl.NEW_BACKGROUND_TAB)), - w: Mode("Follow hint in a new window", function (elem) buffer.followLink(elem, dactyl.NEW_WINDOW), extended), + w: Mode("Follow hint in a new window", function (elem) buffer.followLink(elem, dactyl.NEW_WINDOW), extended), F: Mode("Open multiple hints in tabs", function (elem) { buffer.followLink(elem, dactyl.NEW_BACKGROUND_TAB); hints.show("F") }), O: Mode("Generate an ':open URL' using hint", function (elem, loc) commandline.open(":", "open " + loc, modes.EX)), T: Mode("Generate a ':tabopen URL' using hint", function (elem, loc) commandline.open(":", "tabopen " + loc, modes.EX)), W: Mode("Generate a ':winopen URL' using hint", function (elem, loc) commandline.open(":", "winopen " + loc, modes.EX)), - v: Mode("View hint source", function (elem, loc) buffer.viewSource(loc, false), extended), - V: Mode("View hint source in external editor", function (elem, loc) buffer.viewSource(loc, true), extended), + v: Mode("View hint source", function (elem, loc) buffer.viewSource(loc, false), extended), + V: Mode("View hint source in external editor", function (elem, loc) buffer.viewSource(loc, true), extended), y: Mode("Yank hint location", function (elem, loc) dactyl.clipboardWrite(loc, true)), Y: Mode("Yank hint description", function (elem) dactyl.clipboardWrite(elem.textContent || "", true), extended), - c: Mode("Open context menu", function (elem) buffer.openContextMenu(elem), extended), - i: Mode("Show image", function (elem) dactyl.open(elem.src), images), - I: Mode("Show image in a new tab", function (elem) dactyl.open(elem.src, dactyl.NEW_TAB), images) + c: Mode("Open context menu", function (elem) buffer.openContextMenu(elem), extended), + i: Mode("Show image", function (elem) dactyl.open(elem.src), images), + I: Mode("Show image in a new tab", function (elem) dactyl.open(elem.src, dactyl.NEW_TAB), images) }; }, @@ -351,7 +351,7 @@ const Hints = Module("hints", { if (hint.text == "" && hint.elem.firstChild && hint.elem.firstChild instanceof HTMLImageElement) { if (!hint.imgSpan) { - var rect = hint.elem.firstChild.getBoundingClientRect(); + let rect = hint.elem.firstChild.getBoundingClientRect(); if (!rect) continue; @@ -1054,7 +1054,7 @@ const Hints = Module("hints", { ["wordstartswith", "The typed characters are split on whitespace. The resulting groups must all match the beginings of words, in order."], ["firstletters", "Behaves like wordstartswith, but all groups much match a sequence of words."], ["custom", "Delegate to a custom function: dactyl.plugins.customHintMatcher(hintString)"], - ["transliterated", "When true, special latin characters are translated to their ascii equivalent (e.g., \u00e9 -> e)"] + ["transliterated", UTF8("When true, special latin characters are translated to their ascii equivalent (e.g., é -> e)")] ] }); diff --git a/common/content/history.js b/common/content/history.js index 691810ed..a8c28ff6 100644 --- a/common/content/history.js +++ b/common/content/history.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k@gmail.com> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -45,11 +45,12 @@ const History = Module("history", { let sh = window.getWebNavigation().sessionHistory; let obj = []; obj.index = sh.index; - obj.__iterator__ = function () util.Array.iteritems(this); + obj.__iterator__ = function () array.iteritems(this); for (let i in util.range(0, sh.count)) { - obj[i] = { index: i, __proto__: sh.getEntryAtIndex(i, false) }; - util.memoize(obj[i], "icon", - function (obj) services.get("favicon").getFaviconImageForPage(obj.URI).spec); + obj[i] = update(Object.create(sh.getEntryAtIndex(i, false)), + { index: i }); + memoize(obj[i], "icon", + function () services.get("favicon").getFaviconImageForPage(this.URI).spec); } return obj; }, @@ -64,7 +65,10 @@ const History = Module("history", { dactyl.beep(); else { let index = Math.constrain(current + steps, start, end); - window.getWebNavigation().gotoIndex(index); + try { + window.getWebNavigation().gotoIndex(index); + } + catch (e) {} // We get NS_ERROR_FILE_NOT_FOUND if files in history don't exist } }, diff --git a/common/content/io.js b/common/content/io.js index ce092f87..9f114e5a 100644 --- a/common/content/io.js +++ b/common/content/io.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k@gmail.com> // Some code based on Venkman // // This work is licensed for reuse under an MIT license. Details are @@ -19,16 +19,15 @@ function Script(file) { } self = { __proto__: plugins }; plugins.contexts[file.path] = self; + plugins[file.path] = self; self.NAME = file.leafName.replace(/\..*/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase()); self.PATH = file.path; self.__context__ = self; - self.__proto__ = plugins; // This belongs elsewhere - for (let [, dir] in Iterator(io.getRuntimeDirectories("plugin"))) { - if (dir.contains(file, false)) - plugins[self.NAME] = self; - } + if (io.getRuntimeDirectories("plugins").some( + function (dir) dir.contains(file, false))) + plugins[self.NAME] = self; return self; } @@ -54,7 +53,8 @@ const IO = Module("io", { let file = download.targetFile.path; let size = download.size; - dactyl.echomsg("Download of " + title + " to " + file + " finished", 1, commandline.ACTIVE_WINDOW); + dactyl.echomsg({ domains: [util.getHost(url)], message: "Download of " + title + " to " + file + " finished" }, + 1, commandline.ACTIVE_WINDOW); autocommands.trigger("DownloadPost", { url: url, title: title, file: file, size: size }); } }, @@ -179,8 +179,8 @@ const IO = Module("io", { getRCFile: function (dir, always) { dir = dir || "~"; - let rcFile1 = File.joinPaths(dir, "." + config.name.toLowerCase() + "rc"); - let rcFile2 = File.joinPaths(dir, "_" + config.name.toLowerCase() + "rc"); + let rcFile1 = File.joinPaths(dir, "." + config.name + "rc"); + let rcFile2 = File.joinPaths(dir, "_" + config.name + "rc"); if (dactyl.has("Win32")) [rcFile1, rcFile2] = [rcFile2, rcFile1]; @@ -346,6 +346,8 @@ lookup: dactyl.helpInitialized = false; } catch (e) { + if (isstring(e)) + e = { message: e }; let err = new Error(); for (let [k, v] in Iterator(e)) err[k] = v; @@ -508,10 +510,10 @@ lookup: * variable. */ get runtimePath() { - const rtpvar = config.name.toUpperCase() + "_RUNTIME"; + const rtpvar = config.idname + "_RUNTIME"; let rtp = services.get("environment").get(rtpvar); if (!rtp) { - rtp = "~/" + (dactyl.has("Win32") ? "" : ".") + config.name.toLowerCase(); + rtp = "~/" + (dactyl.has("Win32") ? "" : ".") + config.name; services.get("environment").set(rtpvar, rtp); } return rtp; @@ -582,7 +584,7 @@ lookup: { argCount: "0" }); // "mkv[imperatorrc]" or "mkm[uttatorrc]" - commands.add([config.name.toLowerCase().replace(/(.)(.*)/, "mk$1[$2rc]")], + commands.add([config.name.replace(/(.)(.*)/, "mk$1[$2rc]")], "Write current key mappings and changed options to the config file", function (args) { dactyl.assert(args.length <= 1, "E172: Only one file name allowed"); @@ -595,7 +597,7 @@ lookup: // TODO: Use a set/specifiable list here: let lines = [cmd.serialize().map(commands.commandToString) for (cmd in commands) if (cmd.serialize)]; - lines = util.Array.flatten(lines); + lines = array.flatten(lines); // source a user .pentadactylrc file lines.unshift('"' + dactyl.version + "\n"); @@ -608,7 +610,7 @@ lookup: arguments: [filename + ".local"] })); - lines.push("\n\" vim: set ft=" + config.name.toLowerCase() + ":"); + lines.push("\n\" vim: set ft=" + config.name + ":"); try { file.write(lines.join("\n")); @@ -669,9 +671,13 @@ lookup: // NOTE: Vim doesn't replace ! preceded by 2 or more backslashes and documents it - desirable? // pass through a raw bang when escaped or substitute the last command - arg = arg.replace(/(\\)*!/g, - function (m) /^\\(\\\\)*!$/.test(m) ? m.replace("\\!", "!") : m.replace("!", io._lastRunCommand) - ); + + // This is an asinine and irritating feature when we have searchable + // command-line history. --Kris + if (options["banghist"]) + arg = arg.replace(/(\\)*!/g, + function (m) /^\\(\\\\)*!$/.test(m) ? m.replace("\\!", "!") : m.replace("!", io._lastRunCommand) + ); io._lastRunCommand = arg; @@ -691,19 +697,20 @@ lookup: completion: function () { completion.charset = function (context) { context.anchored = false; - context.generate = function () { - let names = util.Array( - "more1 more2 more3 more4 more5 unicode".split(" ").map(function (key) - options.getPref("intl.charsetmenu.browser." + key).split(', ')) - ).flatten().uniq(); - let bundle = document.getElementById("dactyl-charset-bundle"); - return names.map(function (name) [name, bundle.getString(name.toLowerCase() + ".title")]); + let bundle = services.get("stringBundle").createBundle( + "chrome://global/locale/charsetTitles.properties"); + context.keys = { + text: util.identity, + description: function (charset) bundle.GetStringFromName(charset.toLowerCase() + ".title") }; + context.generate = function () array("more1 more2 more3 more4 more5 unicode".split(" ")) + .map(function (key) options.getPref("intl.charsetmenu.browser." + key).split(', ')) + .flatten().uniq().array; }; completion.directory = function directory(context, full) { this.file(context, full); - context.filters.push(function ({ item: f }) f.isDirectory()); + context.filters.push(function ({ item }) item.isDirectory()); }; completion.environment = function environment(context) { @@ -737,7 +744,7 @@ lookup: if (options["wildignore"]) { let wig = options.get("wildignore"); - context.filters.push(function ({item: f}) f.isDirectory() || !wig.getKey(this.name)); + context.filters.push(function ({ item }) item.isDirectory() || !wig.getKey(this.name)); } // context.background = true; @@ -765,7 +772,7 @@ lookup: } } - return util.Array.flatten(commands); + return array.flatten(commands); }; }; @@ -795,6 +802,10 @@ lookup: shellcmdflag = "-c"; } + options.add(["banghist", "bh"], + "Replace occurances of ! with the previous command when executing external commands", + "banghist", true); + options.add(["fileencoding", "fenc"], "Sets the character encoding of read and written files", "string", "UTF-8", { diff --git a/common/content/javascript.js b/common/content/javascript.js index 18fa2719..349e438d 100644 --- a/common/content/javascript.js +++ b/common/content/javascript.js @@ -1,4 +1,4 @@ -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k at Gmail> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -41,11 +41,13 @@ const JavaScript = Module("javascript", { let seen = {}; for (let key in properties(obj, !toplevel)) { set.add(seen, key); - yield [key, this.getKey(obj, key)]; + yield key; } + // Properties aren't visible in an XPCNativeWrapper until + // they're accessed. for (let key in properties(this.getKey(obj, "wrappedJSObject"), !toplevel)) - if (!set.has(seen, key)) - yield [key, this.getKey(obj, key)]; + if (key in obj && !set.has(seen, key)) + yield key; }, objectKeys: function objectKeys(obj, toplevel) { @@ -110,15 +112,14 @@ const JavaScript = Module("javascript", { }, _pop: function pop(arg) { + if (this._i == this.context.caret - 1) + this.context.highlight(this._top.offset, 1, "FIND"); + if (this._top.char != arg) { this.context.highlight(this._top.offset, this._i - this._top.offset, "SPELLCHECK"); - this.context.highlight(this._top.offset, 1, "FIND"); - throw new Error("Invalid JS"); + throw Error("Invalid JS"); } - if (this._i == this.context.caret - 1) - this.context.highlight(this._top.offset, 1, "FIND"); - // The closing character of this stack frame will have pushed a new // statement, leaving us with an empty statement. This doesn't matter, // now, as we simply throw away the frame when we pop it, but it may later. @@ -139,11 +140,13 @@ const JavaScript = Module("javascript", { // Reuse the old stack. if (this._str && filter.substr(0, this._str.length) == this._str) { + this.context.highlight(0, 0, "FIND"); this._i = this._str.length; if (this.popStatement) this._top.statements.pop(); } else { + this.context.highlight(); this._stack = []; this._functions = []; this._push("#root"); @@ -239,7 +242,7 @@ const JavaScript = Module("javascript", { _getObj: function (frame, stop) { let statement = this._get(frame, 0, "statements") || 0; // Current statement. let prev = statement; - let obj; + let obj = null; let cacheKey; for (let [, dot] in Iterator(this._get(frame).dots.concat(stop))) { if (dot < statement) @@ -285,19 +288,19 @@ const JavaScript = Module("javascript", { return [dot + 1 + space.length, obj, key]; }, - _fill: function (context, obj, name, compl, anchored, key, last, offset) { - context.title = [name]; - context.anchored = anchored; - context.filter = key; + _fill: function (context, args) { + context.title = [args.name]; + context.anchored = args.anchored; + context.filter = args.filter; context.itemCache = context.parent.itemCache; - context.key = name + last; + context.key = args.name + args.last; - if (last != null) - context.quote = [last, function (text) util.escapeString(text.substr(offset), ""), last]; + if (args.last != null) + context.quote = [args.last, function (text) util.escapeString(text.substr(args.offset), ""), args.last]; else // We're not looking for a quoted string, so filter out anything that's not a valid identifier context.filters.push(function (item) /^[a-zA-Z_$][\w$]*$/.test(item.text)); - compl.call(self, context, obj); + args.completer.call(self, context, args.obj); }, _complete: function (objects, key, compl, string, last) { @@ -309,7 +312,10 @@ const JavaScript = Module("javascript", { let orig = compl; if (!compl) { compl = function (context, obj, recurse) { - context.process = [null, function highlight(item, v) template.highlight(typeof v == "xml" ? new String(v.toXMLString()) : v, true)]; + + context.process[1] = function highlight(item, v) + template.highlight(typeof v == "xml" ? new String(v.toXMLString()) : v, true); + // Sort in a logical fashion for object keys: // Numbers are sorted as numbers, rather than strings, and appear first. // Constants are unsorted, and appear before other non-null strings. @@ -321,14 +327,15 @@ const JavaScript = Module("javascript", { return a.key - b.key; return isnan(b.key) - isnan(a.key) || compare(a, b); }; - context.keys = { text: 0, description: 1, + context.keys = { + text: util.identity, + description: function (item) self.getKey(obj, item), key: function (item) { - let key = item[0]; if (!isNaN(key)) return parseInt(key); - else if (/^[A-Z_][A-Z0-9_]*$/.test(key)) + if (/^[A-Z_][A-Z0-9_]*$/.test(key)) return "" - return key; + return item; } }; @@ -339,37 +346,51 @@ const JavaScript = Module("javascript", { context.generate = function () self.objectKeys(obj, !recurse); }; } + + let args = { + completer: compl, + anchored: true, + filter: key + (string || ""), + last: last, + offset: key.length + }; + // TODO: Make this a generic completion helper function. - let filter = key + (string || ""); - for (let [, obj] in Iterator(objects)) { + for (let [, obj] in Iterator(objects)) this.context.fork(obj[1], this._top.offset, this, this._fill, - obj[0], obj[1], compl, - true, filter, last, key.length); - } + update(args, { + obj: obj[0], + name: obj[1], + })); if (orig) return; - for (let [, obj] in Iterator(objects)) { - let name = obj[1] + " (prototypes)"; - this.context.fork(name, this._top.offset, this, this._fill, - obj[0], name, function (a, b) compl(a, b, true), - true, filter, last, key.length); - } - - for (let [, obj] in Iterator(objects)) { - let name = obj[1] + " (substrings)"; - this.context.fork(name, this._top.offset, this, this._fill, - obj[0], name, compl, - false, filter, last, key.length); - } - - for (let [, obj] in Iterator(objects)) { - let name = obj[1] + " (prototype substrings)"; - this.context.fork(name, this._top.offset, this, this._fill, - obj[0], name, function (a, b) compl(a, b, true), - false, filter, last, key.length); - } + for (let [, obj] in Iterator(objects)) + this.context.fork(obj[1] + "/prototypes", this._top.offset, this, this._fill, + update(args, { + obj: obj[0], + name: obj[1] + " (prototypes)", + completer: function (a, b) compl(a, b, true) + })); + + for (let [, obj] in Iterator(objects)) + this.context.fork(obj[1] + "/substrings", this._top.offset, this, this._fill, + update(args, { + obj: obj[0], + name: obj[1] + " (substrings)", + anchored: false, + completer: compl + })); + + for (let [, obj] in Iterator(objects)) + this.context.fork(obj[1] + "/prototypes/substrings", this._top.offset, this, this._fill, + update(args, { + obj: obj[0], + name: obj[1] + " (prototype substrings)", + anchored: false, + completer: function (a, b) compl(a, b, true) + })); }, _getKey: function () { @@ -398,7 +419,7 @@ const JavaScript = Module("javascript", { } this.context.getCache("evalled", Object); - this.context.getCache("evalContext", function () ({ __proto__: userContext }));; + this.context.getCache("evalContext", function () ({ __proto__: userContext })); // Okay, have parse stack. Figure out what we're completing. @@ -478,7 +499,7 @@ const JavaScript = Module("javascript", { for (let [i, idx] in Iterator(this._get(-2).comma)) { let arg = this._str.substring(prev + 1, idx); prev = idx; - util.memoize(args, i, function () self.evalled(arg)); + memoize(args, i, function () self.evalled(arg)); } let key = this._getKey(); args.push(key + string); diff --git a/common/content/mappings.js b/common/content/mappings.js index a8cfebe1..74489a17 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k at Gmail> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -383,7 +383,7 @@ const Mappings = Module("mappings", { let [lhs, rhs] = args; if (!rhs) // list the mapping - mappings.list(modes, this._expandLeader(lhs)); + mappings.list(modes, mappings._expandLeader(lhs)); else { // this matches Vim's behaviour if (/^<Nop>$/i.test(rhs)) @@ -467,7 +467,7 @@ const Mappings = Module("mappings", { addMapCommands("", [modes.NORMAL, modes.VISUAL], ""); for (let mode in modes.mainModes) - if (mode.char && !commands.get(mode.char + "map")) + if (mode.char && !commands.get(mode.char + "map", true)) addMapCommands(mode.char, [m.mask for (m in modes.mainModes) if (m.char == mode.char)], [mode.disp.toLowerCase()]); @@ -489,8 +489,7 @@ const Mappings = Module("mappings", { null, function (context, obj, args) { let mode = args[0]; - return util.Array.flatten( - [ + return array.flatten([ [[name, map.description] for ([i, name] in Iterator(map.names))] for ([i, map] in Iterator(mappings._user[mode].concat(mappings._main[mode]))) ]); diff --git a/common/content/marks.js b/common/content/marks.js index eeea047b..e2c5a9e1 100644 --- a/common/content/marks.js +++ b/common/content/marks.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k@gmail.com> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -17,7 +17,7 @@ const Marks = Module("marks", { this._urlMarks = storage.newMap("url-marks", { privateData: true, replacer: replacer, store: true }); try { - if(isarray(Iterator(this._localMarks).next())); + if(isarray(Iterator(this._localMarks).next()[1])) this._localMarks.clear(); } catch(e) {} @@ -31,7 +31,7 @@ const Marks = Module("marks", { */ get all() { let lmarks = array(Iterator(this._localMarks.get(this.localURI) || {})); - let umarks = array(Iterator(this._urlMarks)).__proto__; + let umarks = array(Iterator(this._urlMarks)).array; return lmarks.concat(umarks).sort(function (a, b) String.localeCompare(a[0], b[0])); }, @@ -54,9 +54,7 @@ const Marks = Module("marks", { let win = window.content; let doc = win.document; - if (!doc.body) - return; - if (doc.body instanceof HTMLFrameSetElement) { + if (doc.body && doc.body instanceof HTMLFrameSetElement) { if (!silent) dactyl.echoerr("Marks support for frameset pages not implemented yet"); return; @@ -72,8 +70,8 @@ const Marks = Module("marks", { dactyl.log("Adding URL mark: " + Marks.markToString(mark, res), 5); } else if (Marks.isLocalMark(mark)) { - let marks = this._localMarks.get(doc.URL, {}); - marks[mark] = { location: doc.URL, position: position, timestamp: Date.now()*1000 }; + let marks = this._localMarks.get(doc.documentURI, {}); + marks[mark] = { location: doc.documentURI, position: position, timestamp: Date.now()*1000 }; this._localMarks.changed(); if (!silent) dactyl.log("Adding local mark: " + Marks.markToString(mark, marks[mark]), 5); @@ -237,7 +235,7 @@ const Marks = Module("marks", { // NOTE: this currently differs from Vim's behavior which // deletes any valid marks in the arg list, up to the first // invalid arg, as well as giving the error message. - dactyl.assert(!matches, "E475: Invalid argument: " + matches[0]); + dactyl.assert(!matches, "E475: Invalid argument: " + (matches && matches[0])); // check for illegal ranges - only allow a-z A-Z 0-9 if ((matches = args.match(/[a-zA-Z0-9]-[a-zA-Z0-9]/g))) { diff --git a/common/content/modes.js b/common/content/modes.js index d9aa994b..52fe6911 100644 --- a/common/content/modes.js +++ b/common/content/modes.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k@gmail.com> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -120,7 +120,7 @@ const Modes = Module("modes", { NONE: 0, - __iterator__: function () util.Array.itervalues(this.all), + __iterator__: function () array.itervalues(this.all), get all() this._mainModes.slice(), @@ -155,7 +155,8 @@ const Modes = Module("modes", { getCharModes: function (chr) [m for (m in values(this._modeMap)) if (m.char == chr)], - matchModes: function (obj) [m for (m in values(this._modeMap)) if (Object.keys(obj).every(function (k) obj[k] == (m[k] || false)))], + matchModes: function (obj) + [m for (m in values(this._modeMap)) if (Object.keys(obj).every(function (k) obj[k] == (m[k] || false)))], // show the current mode string in the command line show: function () { diff --git a/common/content/modules.js b/common/content/modules.js index 5f9594a4..cfc58511 100644 --- a/common/content/modules.js +++ b/common/content/modules.js @@ -1,4 +1,4 @@ -// Copyright (c) 2009 by Kris Maglione <maglione.k@gmail.com> +// Copyright (c) 2009-2010 by Kris Maglione <maglione.k@gmail.com> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -15,7 +15,7 @@ const ModuleBase = Class("ModuleBase", { */ requires: [], - toString: function () "[module " + this.constructor.name + "]" + toString: function () "[module " + this.constructor.classname + "]" }); /** @@ -76,26 +76,29 @@ window.addEventListener("load", function onLoad() { window.removeEventListener("load", onLoad, false); Module.list.forEach(function(module) { - modules.__defineGetter__(module.name, function() { - delete modules[module.name]; - return load(module.name, null, Components.stack.caller); + modules.__defineGetter__(module.classname, function() { + delete modules[module.classname]; + return load(module.classname, null, Components.stack.caller); }); }); - function dump(str) window.dump(String.replace(str, /\n?$/, "\n").replace(/^/m, Config.prototype.name.toLowerCase() + ": ")); + function dump(str) window.dump(String.replace(str, /\n?$/, "\n").replace(/^/m, services.get("dactyl:").name + ": ")); const start = Date.now(); const deferredInit = { load: [] }; const seen = set(); const loaded = set(["init"]); + modules.loaded = loaded; function init(module) { function init(func, mod) - function () defmodule.time(module.name || module.constructor.name, mod, func, module, dactyl, modules, window); + function () defmodule.time(module.classname || module.constructor.classname, mod, + func, module, + dactyl, modules, window); - set.add(loaded, module.constructor.name); + set.add(loaded, module.constructor.classname); for (let [mod, func] in Iterator(module.INIT)) { if (mod in loaded) - init(func)(); + init(func, mod)(); else { deferredInit[mod] = deferredInit[mod] || []; deferredInit[mod].push(init(func, mod)); @@ -112,34 +115,35 @@ window.addEventListener("load", function onLoad() { } try { - if (module.name in loaded) + if (module.classname in loaded) return; - if (module.name in seen) + if (module.classname in seen) throw Error("Module dependency loop."); - set.add(seen, module.name); + set.add(seen, module.classname); for (let dep in values(module.requires)) - load(Module.constructors[dep], module.name); + load(Module.constructors[dep], module.classname); - defmodule.loadLog.push("Load" + (isstring(prereq) ? " " + prereq + " dependency: " : ": ") + module.name); + defmodule.loadLog.push("Load" + (isstring(prereq) ? " " + prereq + " dependency: " : ": ") + module.classname); if (frame && frame.filename) defmodule.loadLog.push(" from: " + frame.filename + ":" + frame.lineNumber); - delete modules[module.name]; - modules[module.name] = defmodule.time(module.name, "init", module); + delete modules[module.classname]; + modules[module.classname] = defmodule.time(module.classname, "init", module); - init(modules[module.name]); - for (let [, fn] in iter(deferredInit[module.name] || [])) + init(modules[module.classname]); + for (let [, fn] in iter(deferredInit[module.classname] || [])) fn(); } catch (e) { - dump("Loading " + (module && module.name) + ": " + e + "\n" + (e.stack || "")); + dump("Loading " + (module && module.classname) + ": " + e + "\n" + (e.stack || "")); } - return modules[module.name]; + return modules[module.classname]; } Module.list.forEach(load); deferredInit["load"].forEach(call); + modules.times = update({}, defmodule.times); dump("Loaded in " + (Date.now() - start) + "ms"); }, false); diff --git a/common/content/options.js b/common/content/options.js index b925787a..6346cf3d 100644 --- a/common/content/options.js +++ b/common/content/options.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k@gmail.com> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -23,6 +23,8 @@ * completer - see {@link Option#completer} * domains - see {@link Option#domains} * getter - see {@link Option#getter} + * initialValue - Initial value is loaded from getter + * persist - see {@link Option#persist} * privateData - see {@link Option#privateData} * scope - see {@link Option#scope} * setter - see {@link Option#setter} @@ -56,9 +58,9 @@ const Option = Class("Option", { // add no{option} variant of boolean {option} to this.names if (this.type == "boolean") - this.names = array([name, "no" + name] for (name in values(names))).flatten().__proto__; + this.names = array([name, "no" + name] for (name in values(names))).flatten().array; - if (this.globalValue == undefined) + if (this.globalValue == undefined && !this.initialValue) this.globalValue = this.parseValues(this.defaultValue); }, @@ -296,6 +298,13 @@ const Option = Class("Option", { getter: null, /** + * @property {boolean} When true, this options values will be saved + * when generating a configuration file. + * @default true + */ + persist: true, + + /** * @property {boolean|function(values)} When true, values of this * option may contain private data which should be purged from * saved histories when clearing private data. If a function, it @@ -363,18 +372,23 @@ const Option = Class("Option", { let re = RegExp(val); re.bang = bang; re.result = arguments.length == 2 ? result : !bang; + re.toString = function () Option.unparseRegex(this); return re; }, - unparseRegex: function (re) re.bang + re.source + (typeof re.result == "string" ? ":" + re.result : ""), + unparseRegex: function (re) re.bang + re.source.replace(/\\(.)/g, function (m, n1) n1 == "/" ? n1 : m) + + (typeof re.result == "string" ? ":" + re.result : ""), getKey: { stringlist: function (k) this.values.indexOf(k) >= 0, + get charlist() this.stringlist, + regexlist: function (k) { for (let re in values(this.values)) if (re.test(k)) return re.result; return null; - } + }, + get regexmap() this.regexlist }, joinValues: { @@ -382,6 +396,7 @@ const Option = Class("Option", { stringlist: function (vals) vals.join(","), stringmap: function (vals) [k + ":" + v for ([k, v] in Iterator(vals))].join(","), regexlist: function (vals) vals.map(Option.unparseRegex).join(","), + get regexmap() this.regexlist }, parseValues: { @@ -430,10 +445,10 @@ const Option = Class("Option", { switch (operator) { case "+": - return util.Array.uniq(Array.concat(orig, values), true); + return array.uniq(Array.concat(orig, values), true); case "^": // NOTE: Vim doesn't prepend if there's a match in the current value - return util.Array.uniq(Array.concat(values, orig), true); + return array.uniq(Array.concat(values, orig), true); case "-": return orig.filter(function (item) values.indexOf(item) == -1); case "=": @@ -452,10 +467,10 @@ const Option = Class("Option", { values = Array.concat(values); switch (operator) { case "+": - return util.Array.uniq(Array.concat(this.values, values), true); + return array.uniq(Array.concat(this.values, values), true); case "^": // NOTE: Vim doesn't prepend if there's a match in the current value - return util.Array.uniq(Array.concat(values, this.values), true); + return array.uniq(Array.concat(values, this.values), true); case "-": return this.values.filter(function (item) values.indexOf(item) == -1); case "=": @@ -468,6 +483,9 @@ const Option = Class("Option", { } return null; }, + get charlist() this.stringlist, + get regexlist() this.stringlist, + get regexmap() this.stringlist, string: function (operator, values, scope, invert) { switch (operator) { @@ -503,21 +521,14 @@ const Option = Class("Option", { } }); -Option.joinValues["regexmap"] = Option.joinValues["regexlist"]; - -Option.getKey["charlist"] = Option.getKey["stringlist"]; -Option.getKey["regexmap"] = Option.getKey["regexlist"]; - -Option.ops["charlist"] = Option.ops["stringlist"]; -Option.ops["regexlist"] = Option.ops["stringlist"]; -Option.ops["regexmap"] = Option.ops["stringlist"]; - /** * @instance options */ const Options = Module("options", { init: function () { - this._optionHash = {}; + this.needInit = []; + this._options = []; + this._optionMap = {}; this._prefContexts = []; for (let [, pref] in Iterator(this.allPrefs(Options.OLD_SAVED))) { @@ -541,45 +552,28 @@ const Options = Module("options", { }); } - function optionObserver(key, event, option) { + storage.newMap("options", { store: false }); + storage.addObserver("options", function optionObserver(key, event, option) { // Trigger any setters. let opt = options.get(option); if (event == "change" && opt) opt.setValues(opt.globalValue, Option.SCOPE_GLOBAL, true); - } - - storage.newMap("options", { store: false }); - storage.addObserver("options", optionObserver, window); + }, window); - this.prefObserver.register(); + this._branch = services.get("pref").getBranch("").QueryInterface(Ci.nsIPrefBranch2); + this._branch.addObserver("", this, false); }, destroy: function () { - this.prefObserver.unregister(); + this._branch.removeObserver("", this); }, /** @property {Iterator(Option)} @private */ __iterator__: function () - array(values(this._optionHash)).sort(function (a, b) String.localeCompare(a.name, b.name)) - .itervalues(), - - /** @property {Object} Observes preference value changes. */ - prefObserver: { - register: function () { - // better way to monitor all changes? - this._branch = services.get("pref").getBranch("").QueryInterface(Ci.nsIPrefBranch2); - this._branch.addObserver("", this, false); - }, - - unregister: function () { - if (this._branch) - this._branch.removeObserver("", this); - }, - - observe: function (subject, topic, data) { - if (topic != "nsPref:changed") - return; + values(this._options.sort(function (a, b) String.localeCompare(a.name, b.name))), + observe: function (subject, topic, data) { + if (topic == "nsPref:changed") { // subject is the nsIPrefBranch we're observing (after appropriate QI) // data is the name of the pref that's been changed (relative to subject) switch (data) { @@ -588,7 +582,7 @@ const Options = Module("options", { dactyl.mode = value ? modes.CARET : modes.NORMAL; break; } - } + } }, /** @@ -601,29 +595,28 @@ const Options = Module("options", { * @param {Object} extra An optional extra configuration hash (see * {@link Map#extraInfo}). * @optional - * @returns {boolean} Whether the option was created. */ add: function (names, description, type, defaultValue, extraInfo) { if (!extraInfo) extraInfo = {}; - let option = Option(names, description, type, defaultValue, extraInfo); - - if (!option) - return false; - - if (option.name in this._optionHash) { - // never replace for now - dactyl.log("Warning: " + names[0].quote() + " already exists, NOT replacing existing option.", 1); - return false; + let name = names[0]; + if (name in this._optionMap) { + dactyl.log("Warning: " + name.quote() + " already exists: replacing existing option.", 1); + this.remove(name); } - // quickly access options with options["wildmode"]: - this.__defineGetter__(option.name, function () option.value); - this.__defineSetter__(option.name, function (value) { option.value = value; }); + let closure = function () options._optionMap[name]; + memoize(this._options, this._options.length, closure); + memoize(this._optionMap, name, function () Option(names, description, type, defaultValue, extraInfo)); + for (let alias in values(names.slice(1))) + memoize(this._optionMap, alias, closure); + if (extraInfo.setter) + memoize(this.needInit, this.needInit.length, closure); - this._optionHash[option.name] = option; - return true; + // quickly access options with options["wildmode"]: + this.__defineGetter__(name, function () this._optionMap[name].value); + this.__defineSetter__(name, function (value) { this._optionMap[name].value = value; }); }, /** @@ -646,12 +639,8 @@ const Options = Module("options", { if (!scope) scope = Option.SCOPE_BOTH; - if (name in this._optionHash) - return (this._optionHash[name].scope & scope) && this._optionHash[name]; - - for (let opt in values(this._optionHash)) - if (opt.hasName(name)) - return (opt.scope & scope) && opt; + if (name in this._optionMap && (this._optionMap[name].scope & scope)) + return this._optionMap[name]; return null; }, @@ -734,7 +723,7 @@ const Options = Module("options", { }; commandline.commandOutput( - template.options(config.hostApplication + " Options", prefs())); + template.options(config.host + " Options", prefs())); }, /** @@ -751,7 +740,7 @@ const Options = Module("options", { let matches, prefix, postfix, valueGiven; [matches, prefix, ret.name, postfix, valueGiven, ret.operator, ret.value] = - args.match(/^\s*(no|inv)?([a-z_]*)([?&!])?\s*(([-+^]?)=(.*))?\s*$/) || []; + args.match(/^\s*(no|inv)?([a-z_-]*?)([?&!])?\s*(([-+^]?)=(.*))?\s*$/) || []; ret.args = args; ret.onlyNonDefault = false; // used for :set to print non-default options @@ -760,8 +749,13 @@ const Options = Module("options", { ret.onlyNonDefault = true; } - if (matches) + if (matches) { ret.option = options.get(ret.name, ret.scope); + if (!ret.option && (ret.option = options.get(prefix + ret.name, ret.scope))) { + ret.name = prefix + ret.name; + prefix = ""; + } + } ret.prefix = prefix; ret.postfix = postfix; @@ -795,10 +789,10 @@ const Options = Module("options", { * any of the options's names. */ remove: function (name) { - for each (let option in this._optionHash) { - if (option.hasName(name)) - delete this._optionHash[option.name]; - } + let opt = this.get(name); + for (let name in values(opt.names)) + delete this._optionMap[name]; + this._options = this._options.filter(function (o) o != opt); }, /** @property {Object} The options store. */ @@ -1023,7 +1017,7 @@ const Options = Module("options", { } if (name == "all" && reset) - commandline.input("Warning: Resetting all preferences may make " + config.hostApplication + " unusable. Continue (yes/[no]): ", + commandline.input("Warning: Resetting all preferences may make " + config.host + " unusable. Continue (yes/[no]): ", function (resp) { if (resp == "yes") for (let pref in values(options.allPrefs())) @@ -1037,20 +1031,14 @@ const Options = Module("options", { else if (invertBoolean) options.invertPref(name); else if (valueGiven) { - switch (value) { - case undefined: + if (value == undefined) value = ""; - break; - case "true": + else if (value == "true") value = true; - break; - case "false": - value = false; - break; - default: - if (/^\d+$/.test(value)) - value = parseInt(value, 10); - } + else if (value == "false") + value = true; + else if (/^\d+$/.test(value)) + value = parseInt(value, 10); options.setPref(name, value); } else @@ -1123,7 +1111,7 @@ const Options = Module("options", { context.completions = [ [options._loadPreference(filter, null, false), "Current Value"], [options._loadPreference(filter, null, true), "Default Value"] - ].filter(function ([k]) k != null); + ].filter(function ([k]) k != null && k.length < 200); return null; } @@ -1134,15 +1122,10 @@ const Options = Module("options", { let prefix = opt.prefix; if (context.filter.indexOf("=") == -1) { - if (prefix) - context.filters.push(function ({ item: opt }) opt.type == "boolean" || prefix == "inv" && opt.values instanceof Array); - return completion.option(context, opt.scope); + if (false && prefix) + context.filters.push(function ({ item }) item.type == "boolean" || prefix == "inv" && isarray(item.values)); + return completion.option(context, opt.scope, prefix); } - else if (prefix == "no") - return null; - - if (prefix) - context.advance(prefix.length); let option = opt.option; context.advance(context.filter.indexOf("=") + 1); @@ -1155,17 +1138,33 @@ const Options = Module("options", { if (opt.get || opt.reset || !option || prefix) return null; - if (!opt.value) { + if (!opt.value && !opt.operator && !opt.invert) { context.fork("default", 0, this, function (context) { context.title = ["Extra Completions"]; context.completions = [ [option.value, "Current value"], [option.defaultValue, "Default value"] - ].filter(function (f) f[0] != ""); + ].filter(function (f) f[0] != "" && f[0].length < 200); }); } - return context.fork("values", 0, completion, "optionValue", opt.name, opt.operator); + let optcontext = context.fork("values"); + completion.optionValue(optcontext, opt.name, opt.operator); + + // Fill in the current values if we're removing + if (opt.operator == "-" && isarray(opt.values)) { + let have = set([i.text for (i in context.allItems)]); + context = context.fork("current-values", 0); + context.anchored = optcontext.anchored + context.maxItems = optcontext.maxItems + + context.filters.push(function (i) !set.has(have, i.text)); + completion.optionValue(context, opt.name, opt.operator, null, + function (context) { + context.generate = function () option.values.map(function (o) [o, ""]) + }); + context.title = ["Current values"]; + } } commands.add(["let"], @@ -1321,17 +1320,21 @@ const Options = Module("options", { }); }, completion: function () { - completion.option = function option(context, scope) { + completion.option = function option(context, scope, prefix) { context.title = ["Option"]; context.keys = { text: "names", description: "description" }; context.completions = options; + if (prefix == "inv") + context.keys.text = function (opt) + opt.type == "boolean" || isarray(opt.values) ? opt.names.map(function (n) "inv" + n) + : opt.names; if (scope) - context.filters.push(function ({ item: opt }) opt.scope & scope); + context.filters.push(function ({ item }) item.scope & scope); }; - completion.optionValue = function (context, name, op, curValue) { + completion.optionValue = function (context, name, op, curValue, completer) { let opt = options.get(name); - let completer = opt.completer; + completer = completer || opt.completer; if (!completer) return; @@ -1372,31 +1375,24 @@ const Options = Module("options", { context.advance(context.filter.length - len); context.title = ["Option Value"]; - let completions = completer(context); - if (!isarray(completions)) - completions = array(completions).__proto__; - if (!completions) - return; - // Not Vim compatible, but is a significant enough improvement // that it's worth breaking compatibility. if (isarray(newValues)) { - completions = completions.filter(function (val) newValues.indexOf(val[0]) == -1); - switch (op) { - case "+": - completions = completions.filter(function (val) curValues.indexOf(val[0]) == -1); - break; - case "-": - completions = completions.filter(function (val) curValues.indexOf(val[0]) > -1); - break; - } + context.filters.push(function (i) newValues.indexOf(i.text) == -1); + if (op == "+") + context.filters.push(function (i) curValues.indexOf(i.text) == -1); + if (op == "-") + context.filters.push(function (i) curValues.indexOf(i.text) > -1); } - context.completions = completions; + + let res = completer.call(opt, context); + if (res) + context.completions = res; }; completion.preference = function preference(context) { context.anchored = false; - context.title = [config.hostApplication + " Preference", "Value"]; + context.title = [config.host + " Preference", "Value"]; context.keys = { text: function (item) item, description: function (item) options.getPref(item) }; context.completions = options.allPrefs(); }; @@ -1404,14 +1400,14 @@ const Options = Module("options", { 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, ""])]); + [function (context) (context.anchored=false, options.allPrefs().map(function (pref) [pref, ""]))]); }, sanitizer: function () { sanitizer.addItem("options", { description: "Options containing hostname data", action: function (timespan, host) { if (host) - for (let opt in values(options._optionHash)) + for (let opt in values(options._options)) if (timespan.contains(opt.lastSet * 1000) && opt.domains) try { opt.values = opt.filterDomain(host, opt.values); @@ -1421,12 +1417,12 @@ const Options = Module("options", { } }, privateEnter: function () { - for (let opt in values(options._optionHash)) + for (let opt in values(options._options)) if (opt.privateData && (!callable(opt.privateData) || opt.privateData(opt.values))) opt.oldValue = opt.value; }, privateLeave: function () { - for (let opt in values(options._optionHash)) + for (let opt in values(options._options)) if (opt.oldValue != null) { opt.value = opt.oldValue; opt.oldValue = null; diff --git a/common/content/quickmarks.js b/common/content/quickmarks.js index 70dc1b92..d806e8ed 100644 --- a/common/content/quickmarks.js +++ b/common/content/quickmarks.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k@gmail.com> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -26,7 +26,7 @@ const QuickMarks = Module("quickmarks", { */ add: function add(qmark, location) { this._qmarks.set(qmark, location); - dactyl.echomsg("Added Quick Mark '" + qmark + "': " + location, 1); + dactyl.echomsg({ domains: [util.getHost(location)], message: "Added Quick Mark '" + qmark + "': " + location }, 1); }, /** diff --git a/common/content/statusline.js b/common/content/statusline.js index f88aa79a..e7985887 100644 --- a/common/content/statusline.js +++ b/common/content/statusline.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k@gmail.com> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -14,8 +14,9 @@ const StatusLine = Module("statusline", { this._statusBar.collapsed = true; // it is later restored unless the user sets laststatus=0 // our status bar fields - this.widgets = dict(["status", "url", "inputbuffer", "progress", "tabcount", "bufferposition", "zoomlevel"].map( - function (field) [field, document.getElementById("dactyl-statusline-field-" + field)])); + this.widgets = array(["status", "url", "inputbuffer", "progress", "tabcount", "bufferposition", "zoomlevel"] + .map(function (field) [field, document.getElementById("dactyl-statusline-field-" + field)])) + .toObject(); }, /** @@ -36,7 +37,7 @@ const StatusLine = Module("statusline", { insecure: "StatusLine" }; - this._statusBar.setAttributeNS(NS.uri, "highlight", highlightGroup[type]); + highlight.highlightNode(this._statusBar, highlightGroup[type]); }, // update all fields of the statusline @@ -115,8 +116,8 @@ const StatusLine = Module("statusline", { } if (modules.bookmarks) { if (bookmarks.isBookmarked(buffer.URL)) - modified += "\u2764"; // a heart symbol: ❤ - //modified += "\u2665"; // a heart symbol: ♥ + modified += UTF8("❤"); + //modified += UTF8("♥"); } if (modified) @@ -206,7 +207,9 @@ const StatusLine = Module("statusline", { let win = document.commandDispatcher.focusedWindow; if (!win) return; - percent = win.scrollMaxY == 0 ? -1 : win.scrollY / win.scrollMaxY; + win.scrollY; + percent = win.scrollY == 0 ? 0 : // This prevents a forced rendering + win.scrollMaxY == 0 ? -1 : win.scrollY / win.scrollMaxY; } let bufferPositionStr = ""; @@ -255,11 +258,11 @@ const StatusLine = Module("statusline", { { setter: function setter(value) { if (value == 0) - document.getElementById("status-bar").collapsed = true; + statusline._statusBar.collapsed = true; else if (value == 1) dactyl.echoerr("show status line only with > 1 window not implemented yet"); else - document.getElementById("status-bar").collapsed = false; + statusline._statusBar.collapsed = false; return value; }, diff --git a/common/content/tabs.js b/common/content/tabs.js index 5312054e..8949850c 100644 --- a/common/content/tabs.js +++ b/common/content/tabs.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> -// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail> +// Copyright (c) 2008-2010 by Kris Maglione <maglione.k at Gmail> // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -21,6 +21,13 @@ const Tabs = Module("tabs", { this._lastBufferSwitchArgs = ""; this._lastBufferSwitchSpecial = true; + let fragment = dactyl.has("MacUnix") ? "tab-mac" : "tab"; + this.tabBinding = styles.addSheet(true, "tab-binding", "chrome://browser/content/browser.xul", + ".tabbrowser-tab { -moz-binding: url(chrome://dactyl/content/bindings.xml#" + fragment + ") !important; }" + + // FIXME: better solution for themes? + ".tabbrowser-tab[busy] > .tab-icon > .tab-icon-image { list-style-image: url('chrome://global/skin/icons/loading_16.png') !important; }", + false, true); + // hide tabs initially to prevent flickering when 'stal' would hide them // on startup if (config.hasTabbrowser) @@ -74,22 +81,6 @@ const Tabs = Module("tabs", { return store.options; }, - /** - * @property {boolean} Whether the tab numbering XBL binding has been - * applied. - */ - get tabsBound() Boolean(styles.get(true, "tab-binding")), - set tabsBound(val) { - let fragment = dactyl.has("MacUnix") ? "tab-mac" : "tab"; - if (!val) - styles.removeSheet(true, "tab-binding"); - else if (!this.tabsBound) - styles.addSheet(true, "tab-binding", "chrome://browser/content/browser.xul", - ".tabbrowser-tab { -moz-binding: url(chrome://dactyl/content/bindings.xml#" + fragment + ") !important; }" + - // FIXME: better solution for themes? - ".tabbrowser-tab[busy] > .tab-icon > .tab-icon-image { list-style-image: url('chrome://global/skin/icons/loading_16.png') !important; }"); - }, - get visibleTabs() config.tabbrowser.visibleTabs || this.allTabs.filter(function (tab) !tab.hidden), /** @@ -721,7 +712,7 @@ const Tabs = Module("tabs", { }); commands.add(["quita[ll]", "qa[ll]"], - "Quit " + config.name, + "Quit " + config.appname, function (args) { dactyl.quit(false, args.bang); }, { argCount: "0", bang: true @@ -831,7 +822,7 @@ const Tabs = Module("tabs", { argCount: "+", completer: function (context, args) { if (args.completeArg == 0) { - context.filters.push(function ({ item: win }) win != window); + context.filters.push(function ({ item }) item != window); completion.window(context); } } diff --git a/common/content/x-click-but21.png b/common/content/x-click-but21.png Binary files differdeleted file mode 100644 index 2c4632b0..00000000 --- a/common/content/x-click-but21.png +++ /dev/null |