]> git.donarmstrong.com Git - dactyl.git/commitdiff
New upstream release 1.0~b7.1
authorMichael Schutte <michi@uiae.at>
Sat, 8 Oct 2011 16:42:46 +0000 (18:42 +0200)
committerMichael Schutte <michi@uiae.at>
Sat, 8 Oct 2011 16:42:46 +0000 (18:42 +0200)
95 files changed:
.hg_archival.txt [deleted file]
HACKING
common/Makefile
common/bootstrap.js
common/components/protocols.js
common/content/abbreviations.js
common/content/autocommands.js
common/content/bookmarks.js
common/content/browser.js
common/content/buffer.js
common/content/commandline.js
common/content/dactyl.js
common/content/disable-acr.jsm
common/content/editor.js
common/content/events.js
common/content/help.xsl
common/content/hints.js
common/content/history.js
common/content/mappings.js
common/content/marks.js
common/content/modes.js
common/content/mow.js
common/content/quickmarks.js
common/content/statusline.js
common/content/tabs.js
common/javascript.vim [new file with mode: 0644]
common/locale/en-US/autocommands.xml
common/locale/en-US/browsing.xml
common/locale/en-US/buffer.xml
common/locale/en-US/cmdline.xml
common/locale/en-US/faq.xml
common/locale/en-US/gui.xml
common/locale/en-US/hints.xml
common/locale/en-US/map.xml
common/locale/en-US/messages.properties
common/locale/en-US/options.xml
common/locale/en-US/pattern.xml
common/locale/en-US/privacy.xml
common/locale/en-US/repeat.xml
common/locale/en-US/starting.xml
common/locale/en-US/styling.xml
common/locale/en-US/tabs.xml
common/locale/en-US/various.xml
common/modules/addons.jsm
common/modules/base.jsm
common/modules/bookmarkcache.jsm
common/modules/bootstrap.jsm
common/modules/commands.jsm
common/modules/completion.jsm
common/modules/config.jsm
common/modules/contexts.jsm
common/modules/downloads.jsm
common/modules/finder.jsm
common/modules/highlight.jsm
common/modules/io.jsm
common/modules/javascript.jsm
common/modules/messages.jsm
common/modules/options.jsm
common/modules/overlay.jsm
common/modules/prefs.jsm
common/modules/sanitizer.jsm
common/modules/services.jsm
common/modules/storage.jsm
common/modules/styles.jsm
common/modules/template.jsm
common/modules/util.jsm
common/skin/dactyl.css
common/tests/functional/dactyl.jsm
common/tests/functional/testCommands.js
common/tests/functional/testOptions.js
melodactyl/content/config.js
melodactyl/content/library.js
melodactyl/content/player.js
melodactyl/locale/en-US/gui.xml
melodactyl/locale/en-US/intro.xml
melodactyl/locale/en-US/messages.properties [new file with mode: 0644]
pentadactyl/AUTHORS
pentadactyl/Makefile
pentadactyl/NEWS
pentadactyl/TODO
pentadactyl/content/config.js
pentadactyl/install.rdf
pentadactyl/locale/en-US/autocommands.xml
pentadactyl/locale/en-US/gui.xml [deleted file]
pentadactyl/locale/en-US/intro.xml
pentadactyl/locale/en-US/messages.properties [new file with mode: 0644]
pentadactyl/locale/en-US/tutorial.xml
teledactyl/bootstrap.js [new symlink]
teledactyl/content/addressbook.js
teledactyl/content/config.js
teledactyl/content/mail.js
teledactyl/install.rdf
teledactyl/locale/en-US/gui.xml [deleted file]
teledactyl/locale/en-US/intro.xml
teledactyl/locale/en-US/messages.properties [new file with mode: 0644]

diff --git a/.hg_archival.txt b/.hg_archival.txt
deleted file mode 100644 (file)
index a19df6b..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-repo: 373f1649c80dea9be7b5bc9c57e8395f94f93ab1
-node: b83bb8e6d273f71b1278c4ee03376594ad6dd039
-branch: pentadactyl-1.0b6
-tag: pentadactyl-1.0b6
diff --git a/HACKING b/HACKING
index 6b4ca69f36499035d809d02d96f211c3f4299689..76536fd6916e6fc91926d8986dc4a8af89ed1118 100644 (file)
--- a/HACKING
+++ b/HACKING
@@ -87,7 +87,7 @@ In general: Just look at the existing source code!
   https://developer.mozilla.org/en/New_in_JavaScript_1.7#Block_scope_with_let
 
 * Reuse common local variable names E.g. "elem" is generally used for element,
-  "win" for windows, "func" for functions,  "ret" for return values etc.
+  "win" for windows, "func" for functions,  "res" for return values etc.
 
 * Prefer // over /* */ comments (exceptions for big comments are usually OK)
   Right: if (HACK) // TODO: remove hack
@@ -138,12 +138,7 @@ In general: Just look at the existing source code!
 Functional tests are implemented using the Mozmill automated testing framework
 -- https://developer.mozilla.org/en/Mozmill_Tests.
 
-A fresh profile is created for the duration of the test run, however, passing
-arguments to the host application won't be supported until Mozmill 1.5.2, the
-next release, so any user RC and plugin files should be temporarily disabled.
-This can be done by adding the following to the head of the RC file:
-set loadplugins=
-finish
+A fresh profile is created for the duration of the test run.
 
 The host application binary tested can be overridden via the HOSTAPP_PATH
 makefile variable. E.g.,
index 955d358a7b9acb1925afca94585634387b0b8ca5..b9e8022805f97598f6781d79fe4696f62c3e1d92 100644 (file)
@@ -1,13 +1,21 @@
 #### configuration
 
+
+AWK       ?= awk
+B64ENCODE ?= base64
+CURL      ?= curl
+SED       := $(shell if [ "xoo" = x$$(echo foo | sed -E 's/f(o)/\1/' 2>/dev/null) ]; \
+                    then echo sed -E; else echo sed -r;                             \
+                    fi)
+
 TOP           = $(shell pwd)
 OS            = $(shell uname -s)
 BUILD_DATE    = $(shell date "+%Y/%m/%d %H:%M:%S")
 BASE          = $(TOP)/../common
 GOOGLE_PROJ   = dactyl
 GOOGLE       = https://$(GOOGLE_PROJ).googlecode.com/files
-VERSION             ?= $(shell sed -n 's/.*em:version\(>\|="\)\(.*\)["<].*/\2/p' $(TOP)/install.rdf | sed 1q)
-UUID                := $(shell sed -n 's/.*em:id\(>\|="\)\(.*\)["<].*/\2/p' $(TOP)/install.rdf | sed 1q)
+VERSION             ?= $(shell $(SED) -n 's/.*em:version(>|=")(.*)["<].*/\2/p' $(TOP)/install.rdf | sed 1q)
+UUID                := $(shell $(SED) -n 's/.*em:id(>|=")(.*)["<].*/\2/p' $(TOP)/install.rdf | sed 1q)
 MANGLE      := $(shell date '+%s' | awk '{ printf "%x", $$1 }')
 MOZMILL       = mozmill
 HOSTAPP_PATH  = $(shell which $(HOSTAPP))
@@ -46,13 +54,6 @@ RDF_IN        = $(RDF).in
 
 BUILD_DIR     = build.$(VERSION).$(OS)
 
-AWK       ?= awk
-B64ENCODE ?= base64
-CURL      ?= curl
-SED       := $(shell if [ "xoo" = x$$(echo foo | sed -E 's/f(o)/\1/' 2>/dev/null) ]; \
-                    then echo sed -E; else echo sed -r;                             \
-                    fi)
-
 .SILENT:
 
 #### rules
@@ -121,7 +122,7 @@ install:
        done;                                                                   \
                                                                                \
        profile=$$(sed 's/^$$/\#/' "$$dir/profiles.ini" |                       \
-               awk -v"profile=$(PROFILE)"                                      \
+               awk -v "profile=$(PROFILE)"                                     \
                        'BEGIN { RS="#" }                                       \
                        index($$0, "\nName=" profile "\n") { print; exit }      \
                        !profile && /\nName=default\n/ { args["name=default"] = $$0 }   \
@@ -177,11 +178,11 @@ test: xpi
 xpi: $(CHROME)
        @echo "Building XPI..."
        mkdir -p "$(XPI_PATH)"
-       
+
        $(AWK) -v 'name=$(NAME)' -v 'suffix=$(MANGLE)' \
               -f $(BASE)/process_manifest.awk \
               "$(TOP)/chrome.manifest" >"$(XPI_PATH)/chrome.manifest"
-       
+
        version="$(VERSION)";                   \
        hg root >/dev/null 2>&1 &&              \
        case "$$version" in                     \
@@ -189,7 +190,7 @@ xpi: $(CHROME)
        esac;                                   \
        $(SED) -e 's/(em:version(>|="))([^"<]+)/\1'"$$version/" \
                <"$(TOP)/install.rdf" >"$(XPI_PATH)/install.rdf"
-       
+
        $(MAKE_JAR) "$(XPI)" "$(XPI_BASES)" "$(XPI_DIRS)" "$(XPI_TEXTS)" "$(XPI_BINS)" "$(XPI_FILES)"
        rm -r -- $(CHROME)
        @echo "Built XPI: $(XPI)"
index b17cba4c3be23af9c6d3e9a4a4d504dbeee81a78..ccf100761e2f56b331f9dfa664ec72b6098aa6ac 100755 (executable)
@@ -29,6 +29,8 @@ const resourceProto = Services.io.getProtocolHandler("resource")
 const categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
 const manager = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
 
+const BOOTSTRAP_JSM = "resource://dactyl/bootstrap.jsm";
+
 const BOOTSTRAP_CONTRACT = "@dactyl.googlecode.com/base/bootstrap";
 JSMLoader = JSMLoader || BOOTSTRAP_CONTRACT in Cc && Cc[BOOTSTRAP_CONTRACT].getService().wrappedJSObject.loader;
 
@@ -41,7 +43,6 @@ if (!JSMLoader && "@mozilla.org/fuel/application;1" in Components.classes)
                           .getService(Components.interfaces.extIApplication)
                           .storage.get("dactyl.JSMLoader", null);
 
-
 function reportError(e) {
     dump("\ndactyl: bootstrap: " + e + "\n" + (e.stack || Error().stack) + "\n");
     Cu.reportError(e);
@@ -156,7 +157,6 @@ function init() {
     let chars = "0123456789abcdefghijklmnopqrstuv";
     for (let n = Date.now(); n; n = Math.round(n / chars.length))
         suffix += chars[n % chars.length];
-    suffix = "";
 
     for each (let line in manifest.split("\n")) {
         let fields = line.split(/\s+/);
@@ -173,12 +173,22 @@ function init() {
             break;
 
         case "resource":
+            var hardSuffix = /^[^\/]*/.exec(fields[2])[0];
+
             resources.push(fields[1], fields[1] + suffix);
             resourceProto.setSubstitution(fields[1], getURI(fields[2]));
             resourceProto.setSubstitution(fields[1] + suffix, getURI(fields[2]));
         }
     }
 
+    // Flush the cache if necessary, just to be paranoid
+    let pref = "extensions.dactyl.cacheFlushCheck";
+    let val  = addon.version + "-" + hardSuffix;
+    if (!Services.prefs.prefHasUserValue(pref) || Services.prefs.getCharPref(pref) != val) {
+        Services.obs.notifyObservers(null, "startupcache-invalidate", "");
+        Services.prefs.setCharPref(pref, val);
+    }
+
     try {
         module("resource://dactyl-content/disable-acr.jsm").init(addon.id);
     }
@@ -186,16 +196,23 @@ function init() {
         reportError(e);
     }
 
-    if (JSMLoader && JSMLoader.bump !== 4) // Temporary hack
-        Services.scriptloader.loadSubScript("resource://dactyl" + suffix + "/bootstrap.jsm",
-            Cu.import("resource://dactyl/bootstrap.jsm", global));
+    if (JSMLoader) {
+        if (Cu.unload) {
+            Cu.unload(BOOTSTRAP_JSM);
+            for (let [name] in Iterator(JSMLoader.globals))
+                Cu.unload(~name.indexOf(":") ? name : "resource://dactyl" + JSMLoader.suffix + "/" + name);
+        }
+        else if (JSMLoader.bump != 5) // Temporary hack
+            Services.scriptloader.loadSubScript("resource://dactyl" + suffix + "/bootstrap.jsm",
+                Cu.import(BOOTSTRAP_JSM, global));
+    }
 
-    if (!JSMLoader || JSMLoader.bump !== 4)
-        Cu.import("resource://dactyl/bootstrap.jsm", global);
+    if (!JSMLoader || JSMLoader.bump !== 5 || Cu.unload)
+        Cu.import(BOOTSTRAP_JSM, global);
 
     JSMLoader.bootstrap = this;
 
-    JSMLoader.load("resource://dactyl/bootstrap.jsm", global);
+    JSMLoader.load(BOOTSTRAP_JSM, global);
 
     JSMLoader.init(suffix);
     JSMLoader.load("base.jsm", global);
@@ -211,9 +228,9 @@ function init() {
                 wrappedJSObject: {}
             },
             createInstance: function () this.instance
-        })
+        });
 
-    Cc[BOOTSTRAP_CONTRACT].getService().wrappedJSObject.loader = JSMLoader;
+    Cc[BOOTSTRAP_CONTRACT].getService().wrappedJSObject.loader = !Cu.unload && JSMLoader;
 
     for each (let component in components)
         component.register();
@@ -233,11 +250,11 @@ function shutdown(data, reason) {
             reportError(e);
         }
 
-        if ([ADDON_UPGRADE, ADDON_DOWNGRADE, ADDON_UNINSTALL].indexOf(reason) >= 0)
+        if (~[ADDON_UPGRADE, ADDON_DOWNGRADE, ADDON_UNINSTALL].indexOf(reason))
             Services.obs.notifyObservers(null, "dactyl-purge", null);
 
-        Services.obs.notifyObservers(null, "dactyl-cleanup", null);
-        Services.obs.notifyObservers(null, "dactyl-cleanup-modules", null);
+        Services.obs.notifyObservers(null, "dactyl-cleanup", reasonToString(reason));
+        Services.obs.notifyObservers(null, "dactyl-cleanup-modules", reasonToString(reason));
 
         JSMLoader.purge();
         for each (let [category, entry] in categories)
index d14b3e4e804d5262a886415ea61c8d411e40cc93..94eab5d7afd914b8fa1fcc7d0a0b6bbb148c5a23 100644 (file)
@@ -248,7 +248,7 @@ function LocaleChannel(pkg, path, orig) {
 
 function StringChannel(data, contentType, uri) {
     let channel = services.StreamChannel(uri);
-    channel.contentStream = services.StringStream(data);
+    channel.contentStream = services.CharsetConv("UTF-8").convertToInputStream(data);
     if (contentType)
         channel.contentType = contentType;
     channel.contentCharset = "UTF-8";
@@ -293,12 +293,12 @@ function XMLChannel(uri, contentType) {
     if (doctype) {
         this.writes.push(doctype + "[\n");
         try {
-            this.writes.push(services.io.newChannel(url, null, null).open())
+            this.writes.push(services.io.newChannel(url, null, null).open());
         }
         catch (e) {}
         if (!open)
             this.writes.push("\n]");
-        this.writes.push(post)
+        this.writes.push(post);
     }
     this.writes.push(channelStream);
 
index 9a76fa372df310d733ca85f6326a66ec501a7730..71e6c93b969391810617c96bdfa3256eaf8d49ee 100644 (file)
@@ -8,6 +8,23 @@
 
 /** @scope modules */
 
+/**
+ * A user-defined input mode binding of a typed string to an automatically
+ * inserted expansion string.
+ *
+ * Abbreviations have a left-hand side (LHS) whose text is replaced by that of
+ * the right-hand side (RHS) when triggered by an Input mode expansion key.
+ * E.g. an abbreviation with a LHS of "gop" and RHS of "Grand Old Party" will
+ * replace the former with the latter.
+ *
+ * @param {[Mode]} modes The modes in which this abbreviation is active.
+ * @param {string} lhs The left hand side of the abbreviation; the text to
+ *     be replaced.
+ * @param {string|function(nsIEditor):string} rhs The right hand side of
+ *     the abbreviation; the replacement text. This may either be a string
+ *     literal or a function that will be passed the appropriate nsIEditor.
+ * @private
+ */
 var Abbreviation = Class("Abbreviation", {
     init: function (modes, lhs, rhs) {
         this.modes = modes.sort();
@@ -15,20 +32,61 @@ var Abbreviation = Class("Abbreviation", {
         this.rhs = rhs;
     },
 
+    /**
+     * Returns true if this abbreviation's LHS and RHS are equal to those in
+     * *other*.
+     *
+     * @param {Abbreviation} other The abbreviation to test.
+     * @returns {boolean} The result of the comparison.
+     */
     equals: function (other) this.lhs == other.lhs && this.rhs == other.rhs,
 
+    /**
+     * Returns the abbreviation's expansion text.
+     *
+     * @param {nsIEditor} editor The editor in which abbreviation expansion is
+     *     occurring.
+     * @returns {string}
+     */
     expand: function (editor) String(callable(this.rhs) ? this.rhs(editor) : this.rhs),
 
+    /**
+     * Returns true if this abbreviation is defined for all *modes*.
+     *
+     * @param {[Mode]} modes The modes to test.
+     * @returns {boolean} The result of the comparison.
+     */
     modesEqual: function (modes) array.equals(this.modes, modes),
 
+    /**
+     * Returns true if this abbreviation is defined for *mode*.
+     *
+     * @param {Mode} mode The mode to test.
+     * @returns {boolean} The result of the comparison.
+     */
     inMode: function (mode) this.modes.some(function (_mode) _mode == mode),
 
+    /**
+     * Returns true if this abbreviation is defined in any of *modes*.
+     *
+     * @param {[Modes]} modes The modes to test.
+     * @returns {boolean} The result of the comparison.
+     */
     inModes: function (modes) modes.some(function (mode) this.inMode(mode), this),
 
+    /**
+     * Remove *mode* from the list of supported modes for this abbreviation.
+     *
+     * @param {Mode} mode The mode to remove.
+     */
     removeMode: function (mode) {
         this.modes = this.modes.filter(function (m) m != mode).sort();
     },
 
+    /**
+     * @property {string} The mode display characters associated with the
+     *     supported mode combination.
+     */
     get modeChar() Abbreviation.modeChar(this.modes)
 }, {
     modeChar: function (_modes) {
@@ -45,6 +103,7 @@ var AbbrevHive = Class("AbbrevHive", Contexts.Hive, {
         this._store = {};
     },
 
+    /** @property {boolean} True if there are no abbreviations. */
     get empty() !values(this._store).nth(util.identity, 0),
 
     /**
@@ -68,14 +127,15 @@ var AbbrevHive = Class("AbbrevHive", Contexts.Hive, {
      *
      * @param {Mode} mode The mode of the abbreviation.
      * @param {string} lhs The LHS of the abbreviation.
+     * @returns {Abbreviation} The matching abbreviation.
      */
     get: function (mode, lhs) {
         let abbrevs = this._store[mode];
-        return abbrevs && set.has(abbrevs, lhs) ? abbrevs[lhs] : null;
+        return abbrevs && Set.has(abbrevs, lhs) ? abbrevs[lhs] : null;
     },
 
     /**
-     * @property {Abbreviation[]} The list of the abbreviations merged from
+     * @property {[Abbreviation]} The list of the abbreviations merged from
      *     each mode.
      */
     get merged() {
@@ -189,13 +249,15 @@ var Abbreviations = Module("abbreviations", {
     },
 
     /**
-     * Lists all abbreviations matching *modes* and *lhs*.
+     * Lists all abbreviations matching *modes*, *lhs* and optionally *hives*.
      *
      * @param {Array} modes List of modes.
      * @param {string} lhs The LHS of the abbreviation.
+     * @param {[Hive]} hives List of hives.
+     * @optional
      */
-    list: function (modes, lhs) {
-        let hives = contexts.allGroups.abbrevs.filter(function (h) !h.empty);
+    list: function (modes, lhs, hives) {
+        let hives = hives || contexts.allGroups.abbrevs.filter(function (h) !h.empty);
 
         function abbrevs(hive)
             hive.merged.filter(function (abbr) (abbr.inModes(modes) && abbr.lhs.indexOf(lhs) == 0));
@@ -203,9 +265,9 @@ var Abbreviations = Module("abbreviations", {
         let list = <table>
                 <tr highlight="Title">
                     <td/>
-                    <td style="padding-right: 1em;">Mode</td>
-                    <td style="padding-right: 1em;">Abbrev</td>
-                    <td style="padding-right: 1em;">Replacement</td>
+                    <td style="padding-right: 1em;">{_("title.Mode")}</td>
+                    <td style="padding-right: 1em;">{_("title.Abbrev")}</td>
+                    <td style="padding-right: 1em;">{_("title.Replacement")}</td>
                 </tr>
                 <col style="min-width: 6em; padding-right: 1em;"/>
                 {
@@ -224,7 +286,7 @@ var Abbreviations = Module("abbreviations", {
 
         // TODO: Move this to an ItemList to show this automatically
         if (list.*.length() === list.text().length() + 2)
-            dactyl.echomsg(_("abbrev.none"));
+            dactyl.echomsg(_("abbreviation.none"));
         else
             commandline.commandOutput(list);
     }
@@ -245,7 +307,6 @@ var Abbreviations = Module("abbreviations", {
             context.completions = group.merged.filter(fn);
         };
     },
-
     commands: function () {
         function addAbbreviationCommands(modes, ch, modeDescription) {
             modes.sort();
@@ -258,14 +319,17 @@ var Abbreviations = Module("abbreviations", {
                     dactyl.assert(!args.length || abbreviations._check.test(lhs),
                                   _("error.invalidArgument"));
 
-                    if (!rhs)
-                        abbreviations.list(modes, lhs || "");
+                    if (!rhs) {
+                        let hives = args.explicitOpts["-group"] ? [args["-group"]] : null;
+                        abbreviations.list(modes, lhs || "", hives);
+                    }
                     else {
                         if (args["-javascript"])
                             rhs = contexts.bindMacro({ literalArg: rhs }, "-javascript", ["editor"]);
                         args["-group"].add(modes, lhs, rhs);
                     }
                 }, {
+                    identifier: "abbreviate",
                     completer: function (context, args) {
                         if (args.length == 1)
                             return completion.abbreviation(context, modes, args["-group"]);
@@ -301,7 +365,7 @@ var Abbreviations = Module("abbreviations", {
                     if (args.bang)
                         args["-group"].clear(modes);
                     else if (!args["-group"].remove(modes, args[0]))
-                        return dactyl.echoerr(_("abbrev.noSuch"));
+                        return dactyl.echoerr(_("abbreviation.noSuch"));
                 }, {
                     argCount: "?",
                     bang: true,
@@ -312,8 +376,9 @@ var Abbreviations = Module("abbreviations", {
         }
 
         addAbbreviationCommands([modes.INSERT, modes.COMMAND_LINE], "", "");
-        addAbbreviationCommands([modes.INSERT], "i", "insert");
-        addAbbreviationCommands([modes.COMMAND_LINE], "c", "command line");
+        [modes.INSERT, modes.COMMAND_LINE].forEach(function (mode) {
+            addAbbreviationCommands([mode], mode.char, mode.displayName);
+        });
     }
 });
 
index 2d960c835be66b75f59a9c4fb274eea449fe38d3..beb8fc52e2a199bd5fefab0ed1fa3f1e2d452679 100644 (file)
@@ -47,7 +47,7 @@ var AutoCmdHive = Class("AutoCmdHive", Contexts.Hive, {
      *
      * @param {string} event The event name filter.
      * @param {string} pattern The URL pattern filter.
-     * @returns {AutoCommand[]}
+     * @returns {[AutoCommand]}
      */
     get: function (event, pattern) {
         return this._store.filter(function (autoCmd) autoCmd.match(event, regexp));
@@ -84,12 +84,17 @@ var AutoCommands = Module("autocommands", {
     remove: deprecated("group.autocmd.remove", { get: function remove() autocommands.user.closure.remove }),
 
     /**
-     * Lists all autocommands with a matching *event* and *regexp*.
+     * Lists all autocommands with a matching *event*, *regexp* and optionally
+     * *hives*.
      *
      * @param {string} event The event name filter.
      * @param {string} regexp The URL pattern filter.
+     * @param {[Hive]} hives List of hives.
+     * @optional
      */
-    list: function (event, regexp) {
+    list: function (event, regexp, hives) {
+
+        let hives = hives || this.activeHives;
 
         function cmds(hive) {
             let cmds = {};
@@ -108,7 +113,7 @@ var AutoCommands = Module("autocommands", {
                     <td colspan="3">----- Auto Commands -----</td>
                 </tr>
                 {
-                    template.map(this.activeHives, function (hive)
+                    template.map(hives, function (hive)
                         <tr highlight="Title">
                             <td colspan="3">{hive.name}</td>
                         </tr> +
@@ -143,7 +148,7 @@ var AutoCommands = Module("autocommands", {
         let lastPattern = null;
         var { url, doc } = args;
         if (url)
-            uri = util.newURI(url);
+            uri = util.createURI(url);
         else
             var { uri, doc } = buffer;
 
@@ -200,7 +205,7 @@ var AutoCommands = Module("autocommands", {
                             args["-group"].remove(event, regexp); // remove all
                     }
                     else
-                        autocommands.list(event, regexp); // list all
+                        autocommands.list(event, regexp, args.explicitOpts["-group"] ? [args["-group"]] : null); // list all
                 }
             }, {
                 bang: true,
index ade210df2b7cac3033ced3a768d1ab1516a9ef21..1ce6ee11a1bd2ff156ade600f6e8230e218dbcdf 100644 (file)
@@ -11,6 +11,10 @@ var DEFAULT_FAVICON = "chrome://mozapps/skin/places/defaultFavicon.png";
 // also includes methods for dealing with keywords and search engines
 var Bookmarks = Module("bookmarks", {
     init: function () {
+        this.timer = Timer(0, 100, function () {
+            this.checkBookmarked(buffer.uri);
+        }, this);
+
         storage.addObserver("bookmark-cache", function (key, event, arg) {
             if (["add", "change", "remove"].indexOf(event) >= 0)
                 autocommands.trigger("Bookmark" + event[0].toUpperCase() + event.substr(1),
@@ -20,10 +24,17 @@ var Bookmarks = Module("bookmarks", {
                              valueOf: function () arg
                          }
                      }, arg));
-            statusline.updateStatus();
+            bookmarks.timer.tell();
         }, window);
     },
 
+    signals: {
+        "browser.locationChange": function (webProgress, request, uri) {
+            statusline.bookmarked = false;
+            this.checkBookmarked(uri);
+        }
+    },
+
     get format() ({
         anchored: false,
         title: ["URL", "Info"],
@@ -103,7 +114,7 @@ var Bookmarks = Module("bookmarks", {
      *
      * @param {Element} elem A form element for which to add a keyword.
      */
-    addSearchKeyword: function (elem) {
+    addSearchKeyword: function addSearchKeyword(elem) {
         if (elem instanceof HTMLFormElement || elem.form)
             var [url, post, charset] = util.parseForm(elem);
         else
@@ -119,6 +130,17 @@ var Bookmarks = Module("bookmarks", {
             commands.commandToString({ command: "bmark", options: options, arguments: [url] }) + " -keyword ");
     },
 
+    checkBookmarked: function checkBookmarked(uri) {
+        if (PlacesUtils.asyncGetBookmarkIds)
+            PlacesUtils.asyncGetBookmarkIds(uri, function (ids) {
+                statusline.bookmarked = ids.length;
+            });
+        else
+            this.timeout(function () {
+                statusline.bookmarked = bookmarkcache.isBookmarked(uri);
+            });
+    },
+
     /**
      * Toggles the bookmarked state of the given URL. If the URL is
      * bookmarked, all bookmarks for said URL are removed.
@@ -197,7 +219,7 @@ var Bookmarks = Module("bookmarks", {
             if (!alias)
                 alias = "search"; // for search engines which we can't find a suitable alias
 
-            if (set.has(aliases, alias))
+            if (Set.has(aliases, alias))
                 alias += ++aliases[alias];
             else
                 aliases[alias] = 0;
@@ -225,7 +247,7 @@ var Bookmarks = Module("bookmarks", {
     getSuggestions: function getSuggestions(engineName, query, callback) {
         const responseType = "application/x-suggestions+json";
 
-        let engine = set.has(this.searchEngines, engineName) && this.searchEngines[engineName];
+        let engine = Set.has(this.searchEngines, engineName) && this.searchEngines[engineName];
         if (engine && engine.supportsResponseType(responseType))
             var queryURI = engine.getSubmission(query, responseType).uri.spec;
         if (!queryURI)
@@ -274,7 +296,7 @@ var Bookmarks = Module("bookmarks", {
             param = query.substr(offset + 1);
         }
 
-        var engine = set.has(bookmarks.searchEngines, keyword) && bookmarks.searchEngines[keyword];
+        var engine = Set.has(bookmarks.searchEngines, keyword) && bookmarks.searchEngines[keyword];
         if (engine) {
             if (engine.searchForm && !param)
                 return engine.searchForm;
@@ -381,8 +403,8 @@ var Bookmarks = Module("bookmarks", {
             completer: function title(context, args) {
                 let frames = buffer.allFrames();
                 if (!args.bang)
-                    return  [
-                        [win.document.title, frames.length == 1 ? "Current Location" : "Frame: " + win.location.href]
+                    return [
+                        [win.document.title, frames.length == 1 ? /*L*/"Current Location" : /*L*/"Frame: " + win.location.href]
                         for ([, win] in Iterator(frames))];
                 context.keys.text = "title";
                 context.keys.description = "url";
@@ -442,7 +464,7 @@ var Bookmarks = Module("bookmarks", {
                         context.title = ["Page URL"];
                         let frames = buffer.allFrames();
                         context.completions = [
-                            [win.document.documentURI, frames.length == 1 ? "Current Location" : "Frame: " + win.document.title]
+                            [win.document.documentURI, frames.length == 1 ? /*L*/"Current Location" : /*L*/"Frame: " + win.document.title]
                             for ([, win] in Iterator(frames))];
                         return;
                     }
@@ -485,11 +507,11 @@ var Bookmarks = Module("bookmarks", {
             "Delete a bookmark",
             function (args) {
                 if (args.bang)
-                    commandline.input("This will delete all bookmarks. Would you like to continue? (yes/[no]) ",
+                    commandline.input(_("bookmark.prompt.deleteAll") + " ",
                         function (resp) {
                             if (resp && resp.match(/^y(es)?$/i)) {
                                 bookmarks.remove(Object.keys(bookmarkcache.bookmarks));
-                                dactyl.echomsg(_("bookmark.allGone"));
+                                dactyl.echomsg(_("bookmark.allDeleted"));
                             }
                         });
                 else {
@@ -499,7 +521,7 @@ var Bookmarks = Module("bookmarks", {
                         let context = CompletionContext(args.join(" "));
                         context.fork("bookmark", 0, completion, "bookmark",
                                      args["-tags"], { keyword: args["-keyword"], title: args["-title"] });
-                        var deletedCount = bookmarks.remove(context.allItems.items.map(function (item) item.item.id));
+                        deletedCount = bookmarks.remove(context.allItems.items.map(function (item) item.item.id));
                     }
 
                     dactyl.echomsg({ message: _("bookmark.deleted", deletedCount) });
@@ -604,7 +626,7 @@ var Bookmarks = Module("bookmarks", {
             if (item && item.url.indexOf("%s") > -1)
                 context.fork("keyword/" + keyword, keyword.length + space.length, null, function (context) {
                     context.format = history.format;
-                    context.title = [keyword + " Quick Search"];
+                    context.title = [/*L*/keyword + " Quick Search"];
                     // context.background = true;
                     context.compare = CompletionContext.Sort.unsorted;
                     context.generate = function () {
@@ -649,7 +671,7 @@ var Bookmarks = Module("bookmarks", {
                     return;
                 let ctxt = context.fork(name, 0);
 
-                ctxt.title = [engine.description + " Suggestions"];
+                ctxt.title = [/*L*/engine.description + " Suggestions"];
                 ctxt.keys = { text: util.identity, description: function () "" };
                 ctxt.compare = CompletionContext.Sort.unsorted;
                 ctxt.filterFunc = null;
index 746b46ec01352d65b0524660bc190d2d73b6da6d..5a6ba5ef15a599c84f4a2e44acbfa9fa81e3271e 100644 (file)
@@ -28,8 +28,11 @@ var Browser = Module("browser", XPCOM(Ci.nsISupportsWeakReference, ModuleBase),
         "content-document-global-created": function (win, uri) {
             let top = util.topWindow(win);
 
-            if (top == window)
-                this._triggerLoadAutocmd("PageLoadPre", win.document, win.location.href != "null" ? window.location.href : uri);
+            if (uri == "null")
+                uri = null;
+
+            if (top == window && (win.location.href || uri))
+                this._triggerLoadAutocmd("PageLoadPre", win.document, win.location.href || uri);
         }
     },
 
@@ -43,12 +46,18 @@ var Browser = Module("browser", XPCOM(Ci.nsISupportsWeakReference, ModuleBase),
             title: doc.title
         };
 
-        if (dactyl.has("tabs")) {
+        if (!dactyl.has("tabs"))
+            update(args, { doc: doc, win: doc.defaultView });
+        else {
             args.tab = tabs.getContentIndex(doc) + 1;
             args.doc = {
                 valueOf: function () doc,
                 toString: function () "tabs.getTab(" + (args.tab - 1) + ").linkedBrowser.contentDocument"
             };
+            args.win = {
+                valueOf: function () doc.defaultView,
+                toString: function () "tabs.getTab(" + (args.tab - 1) + ").linkedBrowser.contentWindow"
+            };
         }
 
         autocommands.trigger(name, args);
@@ -95,26 +104,29 @@ var Browser = Module("browser", XPCOM(Ci.nsISupportsWeakReference, ModuleBase),
     progressListener: {
         // XXX: function may later be needed to detect a canceled synchronous openURL()
         onStateChange: util.wrapCallback(function onStateChange(webProgress, request, flags, status) {
-            onStateChange.superapply(this, arguments);
-            // STATE_IS_DOCUMENT | STATE_IS_WINDOW is important, because we also
-            // receive statechange events for loading images and other parts of the web page
-            if (flags & (Ci.nsIWebProgressListener.STATE_IS_DOCUMENT | Ci.nsIWebProgressListener.STATE_IS_WINDOW)) {
+            const L = Ci.nsIWebProgressListener;
+
+            if (request)
                 dactyl.applyTriggerObserver("browser.stateChange", arguments);
+
+            if (flags & (L.STATE_IS_DOCUMENT | L.STATE_IS_WINDOW)) {
                 // This fires when the load event is initiated
                 // only thrown for the current tab, not when another tab changes
-                if (flags & Ci.nsIWebProgressListener.STATE_START) {
+                if (flags & L.STATE_START) {
                     while (document.commandDispatcher.focusedWindow == webProgress.DOMWindow
                            && modes.have(modes.INPUT))
                         modes.pop();
 
                 }
-                else if (flags & Ci.nsIWebProgressListener.STATE_STOP) {
+                else if (flags & L.STATE_STOP) {
                     // Workaround for bugs 591425 and 606877, dactyl bug #81
                     config.browser.mCurrentBrowser.collapsed = false;
                     if (!dactyl.focusedElement || dactyl.focusedElement === document.documentElement)
                         dactyl.focusContent();
                 }
             }
+
+            onStateChange.superapply(this, arguments);
         }),
         onSecurityChange: util.wrapCallback(function onSecurityChange(webProgress, request, state) {
             onSecurityChange.superapply(this, arguments);
@@ -200,9 +212,13 @@ var Browser = Module("browser", XPCOM(Ci.nsISupportsWeakReference, ModuleBase),
             ["o"], "Open one or more URLs",
             function () { CommandExMode().open("open "); });
 
+        function decode(uri) util.losslessDecodeURI(uri)
+                                 .replace(/%20(?!(?:%20)*$)/g, " ")
+                                 .replace(RegExp(options["urlseparator"], "g"), encodeURIComponent);
+
         mappings.add([modes.NORMAL], ["O"],
             "Open one or more URLs, based on current location",
-            function () { CommandExMode().open("open " + buffer.uri.spec); });
+            function () { CommandExMode().open("open " + decode(buffer.uri.spec)); });
 
         mappings.add([modes.NORMAL], ["t"],
             "Open one or more URLs in a new tab",
@@ -210,7 +226,7 @@ var Browser = Module("browser", XPCOM(Ci.nsISupportsWeakReference, ModuleBase),
 
         mappings.add([modes.NORMAL], ["T"],
             "Open one or more URLs in a new tab, based on current location",
-            function () { CommandExMode().open("tabopen " + buffer.uri.spec); });
+            function () { CommandExMode().open("tabopen " + decode(buffer.uri.spec)); });
 
         mappings.add([modes.NORMAL], ["w"],
             "Open one or more URLs in a new window",
@@ -218,24 +234,24 @@ var Browser = Module("browser", XPCOM(Ci.nsISupportsWeakReference, ModuleBase),
 
         mappings.add([modes.NORMAL], ["W"],
             "Open one or more URLs in a new window, based on current location",
-            function () { CommandExMode().open("winopen " + buffer.uri.spec); });
+            function () { CommandExMode().open("winopen " + decode(buffer.uri.spec)); });
 
-        mappings.add([modes.NORMAL], ["~"],
+        mappings.add([modes.NORMAL], ["<open-home-directory>", "~"],
             "Open home directory",
             function () { dactyl.open("~"); });
 
-        mappings.add([modes.NORMAL], ["gh"],
+        mappings.add([modes.NORMAL], ["<open-homepage>", "gh"],
             "Open homepage",
             function () { BrowserHome(); });
 
-        mappings.add([modes.NORMAL], ["gH"],
+        mappings.add([modes.NORMAL], ["<tab-open-homepage>", "gH"],
             "Open homepage in a new tab",
             function () {
                 let homepages = gHomeButton.getHomePage();
                 dactyl.open(homepages, { from: "homepage", where: dactyl.NEW_TAB });
             });
 
-        mappings.add([modes.MAIN], ["<C-l>"],
+        mappings.add([modes.MAIN], ["<redraw-screen>", "<C-l>"],
             "Redraw the screen",
             function () { ex.redraw(); });
     }
index af099e747e8d58bde657158747088141187b5654..d0c732cd10fa9983cda1940b77eb110646841c14 100644 (file)
@@ -19,6 +19,24 @@ var Buffer = Module("buffer", {
         this.evaluateXPath = util.evaluateXPath;
         this.pageInfo = {};
 
+        this.addPageInfoSection("e", "Search Engines", function (verbose) {
+
+            let n = 1;
+            let nEngines = 0;
+            for (let { document: doc } in values(buffer.allFrames())) {
+                let engines = util.evaluateXPath(["link[@href and @rel='search' and @type='application/opensearchdescription+xml']"], doc);
+                nEngines += engines.snapshotLength;
+
+                if (verbose)
+                    for (let link in engines)
+                        yield [link.title || /*L*/ "Engine " + n++,
+                               <a xmlns={XHTML} href={link.href} onclick="if (event.button == 0) { window.external.AddSearchProvider(this.href); return false; }" highlight="URL">{link.href}</a>];
+            }
+
+            if (!verbose && nEngines)
+                yield nEngines + /*L*/" engine" + (nEngines > 1 ? "s" : "");
+        });
+
         this.addPageInfoSection("f", "Feeds", function (verbose) {
             const feedTypes = {
                 "application/rss+xml": "RSS",
@@ -75,7 +93,7 @@ var Buffer = Module("buffer", {
             }
 
             if (!verbose && nFeed)
-                yield nFeed + " feed" + (nFeed > 1 ? "s" : "");
+                yield nFeed + /*L*/" feed" + (nFeed > 1 ? "s" : "");
         });
 
         this.addPageInfoSection("g", "General Info", function (verbose) {
@@ -110,7 +128,7 @@ var Buffer = Module("buffer", {
 
             if (!verbose) {
                 if (pageSize[0])
-                    yield (pageSize[1] || pageSize[0]) + " bytes";
+                    yield (pageSize[1] || pageSize[0]) + /*L*/" bytes";
                 yield lastMod;
                 return;
             }
@@ -134,6 +152,9 @@ var Buffer = Module("buffer", {
         });
 
         this.addPageInfoSection("m", "Meta Tags", function (verbose) {
+            if (!verbose)
+                return [];
+
             // get meta tag data, sort and put into pageMeta[]
             let metaNodes = buffer.focusedFrame.document.getElementsByTagName("meta");
 
@@ -141,9 +162,48 @@ var Buffer = Module("buffer", {
                         .sort(function (a, b) util.compareIgnoreCase(a[0], b[0]));
         });
 
+        let identity = window.gIdentityHandler;
+        this.addPageInfoSection("s", "Security", function (verbose) {
+            if (!verbose || !identity)
+                return; // For now
+
+            // Modified from Firefox
+            function location(data) array.compact([
+                data.city, data.state, data.country
+            ]).join(", ");
+
+            switch (statusline.security) {
+            case "secure":
+            case "extended":
+                var data = identity.getIdentityData();
+
+                yield ["Host", identity.getEffectiveHost()];
+
+                if (statusline.security === "extended")
+                    yield ["Owner", data.subjectOrg];
+                else
+                    yield ["Owner", _("pageinfo.s.ownerUnverified", data.subjectOrg)];
+
+                if (location(data).length)
+                    yield ["Location", location(data)];
+
+                yield ["Verified by", data.caOrg];
+
+                if (identity._overrideService.hasMatchingOverride(identity._lastLocation.hostname,
+                                                              (identity._lastLocation.port || 443),
+                                                              data.cert, {}, {}))
+                    yield ["User exception", /*L*/"true"];
+                break;
+            }
+        });
+
         dactyl.commands["buffer.viewSource"] = function (event) {
             let elem = event.originalTarget;
-            buffer.viewSource([elem.getAttribute("href"), Number(elem.getAttribute("line"))]);
+            let obj = { url: elem.getAttribute("href"), line: Number(elem.getAttribute("line")) };
+            if (elem.hasAttribute("column"))
+                obj.column = elem.getAttribute("column");
+
+            buffer.viewSource(obj);
         };
     },
 
@@ -311,7 +371,7 @@ var Buffer = Module("buffer", {
     allFrames: function allFrames(win, focusedFirst) {
         let frames = [];
         (function rec(frame) {
-            if (frame.document.body instanceof HTMLBodyElement)
+            if (true || frame.document.body instanceof HTMLBodyElement)
                 frames.push(frame);
             Array.forEach(frame.frames, rec);
         })(win || content);
@@ -340,7 +400,7 @@ var Buffer = Module("buffer", {
      * @returns {string}
      */
     get currentWord() Buffer.currentWord(this.focusedFrame),
-    getCurrentWord: deprecated("buffer.currentWord", function getCurrentWord() this.currentWord),
+    getCurrentWord: deprecated("buffer.currentWord", function getCurrentWord() Buffer.currentWord(this.focusedFrame, true)),
 
     /**
      * Returns true if a scripts are allowed to focus the given input
@@ -352,8 +412,16 @@ var Buffer = Module("buffer", {
     focusAllowed: function focusAllowed(elem) {
         if (elem instanceof Window && !Editor.getEditor(elem))
             return true;
+
         let doc = elem.ownerDocument || elem.document || elem;
-        return !options["strictfocus"] || doc.dactylFocusAllowed;
+        switch (options.get("strictfocus").getKey(doc.documentURIObject || util.newURI(doc.documentURI), "moderate")) {
+        case "despotic":
+            return elem.dactylFocusAllowed || elem.frameElement && elem.frameElement.dactylFocusAllowed;
+        case "moderate":
+            return doc.dactylFocusAllowed || elem.frameElement && elem.frameElement.ownerDocument.dactylFocusAllowed;
+        default:
+            return true;
+        }
     },
 
     /**
@@ -365,6 +433,7 @@ var Buffer = Module("buffer", {
      */
     focusElement: function focusElement(elem) {
         let win = elem.ownerDocument && elem.ownerDocument.defaultView || elem;
+        elem.dactylFocusAllowed = true;
         win.document.dactylFocusAllowed = true;
 
         if (isinstance(elem, [HTMLFrameElement, HTMLIFrameElement]))
@@ -413,7 +482,7 @@ var Buffer = Module("buffer", {
     },
 
     /**
-     * Find the counth last link on a page matching one of the given
+     * Find the *count*th last link on a page matching one of the given
      * regular expressions, or with a @rel or @rev attribute matching
      * the given relation. Each frame is searched beginning with the
      * last link and progressing to the first, once checking for
@@ -483,7 +552,7 @@ var Buffer = Module("buffer", {
      */
     followLink: function followLink(elem, where) {
         let doc = elem.ownerDocument;
-        let view = doc.defaultView;
+        let win = doc.defaultView;
         let { left: offsetX, top: offsetY } = elem.getBoundingClientRect();
 
         if (isinstance(elem, [HTMLFrameElement, HTMLIFrameElement]))
@@ -526,6 +595,8 @@ var Buffer = Module("buffer", {
                     ctrlKey: ctrlKey, shiftKey: shiftKey, metaKey: ctrlKey
                 }));
             });
+            let sel = util.selectionController(win);
+            sel.getSelection(sel.SELECTION_FOCUS_REGION).collapseToStart();
         });
     },
 
@@ -533,9 +604,7 @@ var Buffer = Module("buffer", {
      * @property {nsISelectionController} The current document's selection
      *     controller.
      */
-    get selectionController() config.browser.docShell
-            .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsISelectionDisplay)
-            .QueryInterface(Ci.nsISelectionController),
+    get selectionController() util.selectionController(this.focusedFrame),
 
     /**
      * Opens the appropriate context menu for *elem*.
@@ -561,7 +630,7 @@ var Buffer = Module("buffer", {
         try {
             window.urlSecurityCheck(uri.spec, doc.nodePrincipal);
 
-            io.CommandFileMode("Save link: ", {
+            io.CommandFileMode(_("buffer.prompt.saveLink") + " ", {
                 onSubmit: function (path) {
                     let file = io.File(path);
                     if (file.exists() && file.isDirectory())
@@ -598,16 +667,16 @@ var Buffer = Module("buffer", {
                              | persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
 
         let downloadListener = new window.DownloadListener(window,
-                services.Transfer(uri, services.io.newFileURI(file), "",
+                services.Transfer(uri, File(file).URI, "",
                                   null, null, null, persist));
 
         persist.progressListener = update(Object.create(downloadListener), {
-            onStateChange: function onStateChange(progress, request, flag, status) {
-                if (callback && (flag & Ci.nsIWebProgressListener.STATE_STOP) && status == 0)
-                    dactyl.trapErrors(callback, self, uri, file, progress, request, flag, status);
+            onStateChange: util.wrapCallback(function onStateChange(progress, request, flags, status) {
+                if (callback && (flags & Ci.nsIWebProgressListener.STATE_STOP) && status == 0)
+                    dactyl.trapErrors(callback, self, uri, file, progress, request, flags, status);
 
                 return onStateChange.superapply(this, arguments);
-            }
+            })
         });
 
         persist.saveURI(uri, null, null, null, null, file);
@@ -672,7 +741,7 @@ var Buffer = Module("buffer", {
      */
     findScrollable: function findScrollable(dir, horizontal) {
         function find(elem) {
-            while (!(elem instanceof Element) && elem.parentNode)
+            while (elem && !(elem instanceof Element) && elem.parentNode)
                 elem = elem.parentNode;
             for (; elem && elem.parentNode instanceof Element; elem = elem.parentNode)
                 if (Buffer.isScrollable(elem, dir, horizontal))
@@ -702,7 +771,7 @@ var Buffer = Module("buffer", {
                         doc.documentElement);
         }
         let doc = this.focusedFrame.document;
-        return elem || doc.body || doc.documentElement;
+        return dactyl.assert(elem || doc.body || doc.documentElement);
     },
 
     /**
@@ -728,12 +797,43 @@ var Buffer = Module("buffer", {
         return win;
     },
 
+    /**
+     * Finds the next visible element for the node path in 'jumptags'
+     * for *arg*.
+     *
+     * @param {string} arg The element in 'jumptags' to use for the search.
+     * @param {number} count The number of elements to jump.
+     *      @optional
+     * @param {boolean} reverse If true, search backwards. @optional
+     */
+    findJump: function findJump(arg, count, reverse) {
+        const FUDGE = 10;
+
+        let path = options["jumptags"][arg];
+        dactyl.assert(path, _("error.invalidArgument", arg));
+
+        let distance = reverse ? function (rect) -rect.top : function (rect) rect.top;
+        let elems = [[e, distance(e.getBoundingClientRect())] for (e in path.matcher(this.focusedFrame.document))]
+                        .filter(function (e) e[1] > FUDGE)
+                        .sort(function (a, b) a[1] - b[1])
+
+        let idx = Math.min((count || 1) - 1, elems.length);
+        dactyl.assert(idx in elems);
+
+        let elem = elems[idx][0];
+        elem.scrollIntoView(true);
+
+        let sel = elem.ownerDocument.defaultView.getSelection();
+        sel.removeAllRanges();
+        sel.addRange(RangeFind.endpoint(RangeFind.nodeRange(elem), true));
+    },
+
     // TODO: allow callback for filtering out unwanted frames? User defined?
     /**
      * Shifts the focus to another frame within the buffer. Each buffer
      * contains at least one frame.
      *
-     * @param {number} count The number of frames to skip through.  A negative
+     * @param {number} count The number of frames to skip through. A negative
      *     count skips backwards.
      */
     shiftFrameFocus: function shiftFrameFocus(count) {
@@ -785,7 +885,7 @@ var Buffer = Module("buffer", {
      * @param {Node} elem The element to query.
      */
     showElementInfo: function showElementInfo(elem) {
-        dactyl.echo(<>Element:<br/>{util.objectToString(elem, true)}</>, commandline.FORCE_MULTILINE);
+        dactyl.echo(<><!--L-->Element:<br/>{util.objectToString(elem, true)}</>, commandline.FORCE_MULTILINE);
     },
 
     /**
@@ -798,15 +898,15 @@ var Buffer = Module("buffer", {
     showPageInfo: function showPageInfo(verbose, sections) {
         // Ctrl-g single line output
         if (!verbose) {
-            let file = content.location.pathname.split("/").pop() || "[No Name]";
-            let title = content.document.title || "[No Title]";
+            let file = content.location.pathname.split("/").pop() || _("buffer.noName");
+            let title = content.document.title || _("buffer.noTitle");
 
-            let info = template.map("gf",
+            let info = template.map(sections || options["pageinfo"],
                 function (opt) template.map(buffer.pageInfo[opt].action(), util.identity, ", "),
                 ", ");
 
             if (bookmarkcache.isBookmarked(this.URL))
-                info += ", bookmarked";
+                info += ", " + _("buffer.bookmarked");
 
             let pageInfoText = <>{file.quote()} [{info}] {title}</>;
             dactyl.echo(pageInfoText, commandline.FORCE_SINGLELINE);
@@ -852,26 +952,34 @@ var Buffer = Module("buffer", {
      * specified *url*. Either the default viewer or the configured external
      * editor is used.
      *
-     * @param {string} url The URL of the source.
-     * @default The current buffer.
+     * @param {string|object|null} loc If a string, the URL of the source,
+     *      otherwise an object with some or all of the following properties:
+     *
+     *          url: The URL to view.
+     *          doc: The document to view.
+     *          line: The line to select.
+     *          column: The column to select.
+     *
+     *      If no URL is provided, the current document is used.
+     *  @default The current buffer.
      * @param {boolean} useExternalEditor View the source in the external editor.
      */
-    viewSource: function viewSource(url, useExternalEditor) {
+    viewSource: function viewSource(loc, useExternalEditor) {
         let doc = this.focusedFrame.document;
 
-        if (isArray(url)) {
-            if (options.get("editor").has("line"))
-                this.viewSourceExternally(url[0] || doc, url[1]);
+        if (isObject(loc)) {
+            if (options.get("editor").has("line") || !loc.url)
+                this.viewSourceExternally(loc.doc || loc.url || doc, loc);
             else
                 window.openDialog("chrome://global/content/viewSource.xul",
                                   "_blank", "all,dialog=no",
-                                  url[0], null, null, url[1]);
+                                  loc.url, null, null, loc.line);
         }
         else {
             if (useExternalEditor)
-                this.viewSourceExternally(url || doc);
+                this.viewSourceExternally(loc || doc);
             else {
-                url = url || doc.location.href;
+                let url = loc || doc.location.href;
                 const PREFIX = "view-source:";
                 if (url.indexOf(PREFIX) == 0)
                     url = url.substr(PREFIX.length);
@@ -894,13 +1002,19 @@ var Buffer = Module("buffer", {
      * immediately.
      *
      * @param {Document} doc The document to view.
+     * @param {function|object} callback If a function, the callback to be
+     *      called with two arguments: the nsIFile of the file, and temp, a
+     *      boolean which is true if the file is temporary. Otherwise, an object
+     *      with line and column properties used to determine where to open the
+     *      source.
+     *      @optional
      */
     viewSourceExternally: Class("viewSourceExternally",
         XPCOM([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]), {
         init: function init(doc, callback) {
             this.callback = callable(callback) ? callback :
                 function (file, temp) {
-                    editor.editFileExternally({ file: file.path, line: callback },
+                    editor.editFileExternally(update({ file: file.path }, callback || {}),
                                               function () { temp && file.remove(false); });
                     return true;
                 };
@@ -928,8 +1042,8 @@ var Buffer = Module("buffer", {
             return null;
         },
 
-        onStateChange: function onStateChange(progress, request, flag, status) {
-            if ((flag & this.STATE_STOP) && status == 0) {
+        onStateChange: function onStateChange(progress, request, flags, status) {
+            if ((flags & this.STATE_STOP) && status == 0) {
                 try {
                     var ok = this.callback(this.file, true);
                 }
@@ -997,14 +1111,14 @@ var Buffer = Module("buffer", {
      * Adjusts the page zoom of the current buffer relative to the
      * current zoom level.
      *
-     * @param {number} steps The integral number of natural fractions by
-     *   which to adjust the current page zoom. If positive, the zoom
-     *   level is increased, if negative it is decreased.
+     * @param {number} steps The integral number of natural fractions by which
+     *     to adjust the current page zoom. If positive, the zoom level is
+     *     increased, if negative it is decreased.
      * @param {boolean} fullZoom If true, zoom all content of the page,
-     *   including raster images. If false, zoom only text. If omitted,
-     *   use the current zoom function. @optional
-     * @throws {FailedAssertion} if the buffer's zoom level is already
-     *  at its extreme in the given direction.
+     *     including raster images. If false, zoom only text. If omitted, use
+     *     the current zoom function. @optional
+     * @throws {FailedAssertion} if the buffer's zoom level is already at its
+     *     extreme in the given direction.
      */
     bumpZoomLevel: function bumpZoomLevel(steps, fullZoom) {
         if (fullZoom === undefined)
@@ -1019,7 +1133,7 @@ var Buffer = Module("buffer", {
         this.setZoom(Math.round(values[i] * 100), fullZoom);
     },
 
-    getAllFrames: deprecated("buffer.allFrames", function getAllFrames() buffer.getAllFrames.apply(buffer, arguments)),
+    getAllFrames: deprecated("buffer.allFrames", "allFrames"),
     scrollTop: deprecated("buffer.scrollToPercent", function scrollTop() buffer.scrollToPercent(null, 0)),
     scrollBottom: deprecated("buffer.scrollToPercent", function scrollBottom() buffer.scrollToPercent(null, 100)),
     scrollStart: deprecated("buffer.scrollToPercent", function scrollStart() buffer.scrollToPercent(0, null)),
@@ -1027,7 +1141,7 @@ var Buffer = Module("buffer", {
     scrollColumns: deprecated("buffer.scrollHorizontal", function scrollColumns(cols) buffer.scrollHorizontal("columns", cols)),
     scrollPages: deprecated("buffer.scrollHorizontal", function scrollPages(pages) buffer.scrollVertical("pages", pages)),
     scrollTo: deprecated("Buffer.scrollTo", function scrollTo(x, y) content.scrollTo(x, y)),
-    textZoom: deprecated("buffer.zoomValue and buffer.fullZoom", function textZoom() config.browser.markupDocumentViewer.textZoom * 100)
+    textZoom: deprecated("buffer.zoomValue/buffer.fullZoom", function textZoom() config.browser.markupDocumentViewer.textZoom * 100)
 }, {
     PageInfo: Struct("PageInfo", "name", "title", "action")
                         .localize("title"),
@@ -1044,17 +1158,21 @@ var Buffer = Module("buffer", {
      *
      * @returns {string}
      */
-    currentWord: function currentWord(win) {
+    currentWord: function currentWord(win, select) {
         let selection = win.getSelection();
         if (selection.rangeCount == 0)
             return "";
 
         let range = selection.getRangeAt(0).cloneRange();
-        if (range.collapsed) {
+        if (range.collapsed && range.startContainer instanceof Text) {
             let re = options.get("iskeyword").regexp;
             Editor.extendRange(range, true,  re, true);
             Editor.extendRange(range, false, re, true);
         }
+        if (select) {
+            selection.removeAllRanges();
+            selection.addRange(range);
+        }
         return util.domToString(range);
     },
 
@@ -1079,13 +1197,13 @@ var Buffer = Module("buffer", {
 
         var names = [];
         if (node.title)
-            names.push([node.title, "Page Name"]);
+            names.push([node.title, /*L*/"Page Name"]);
 
         if (node.alt)
-            names.push([node.alt, "Alternate Text"]);
+            names.push([node.alt, /*L*/"Alternate Text"]);
 
         if (!isinstance(node, Document) && node.textContent)
-            names.push([node.textContent, "Link Text"]);
+            names.push([node.textContent, /*L*/"Link Text"]);
 
         names.push([decodeURIComponent(url.replace(/.*?([^\/]*)\/*$/, "$1")), "File Name"]);
 
@@ -1133,6 +1251,11 @@ var Buffer = Module("buffer", {
             elem.scrollLeft = left;
         if (top != null)
             elem.scrollTop = top;
+
+        if (util.haveGecko("2.0") && !util.haveGecko("7.*"))
+            elem.ownerDocument.defaultView
+                .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
+                .redraw();
     },
 
     /**
@@ -1156,10 +1279,11 @@ var Buffer = Module("buffer", {
         else
             throw Error();
 
+        dactyl.assert(number < 0 ? elem.scrollLeft > 0 : elem.scrollLeft < elem.scrollWidth - elem.clientWidth);
+
         let left = elem.dactylScrollDestX !== undefined ? elem.dactylScrollDestX : elem.scrollLeft;
         elem.dactylScrollDestX = undefined;
 
-        dactyl.assert(number < 0 ? left > 0 : left < elem.scrollWidth - elem.clientWidth);
         Buffer.scrollTo(elem, left + number * increment, null);
     },
 
@@ -1184,10 +1308,11 @@ var Buffer = Module("buffer", {
         else
             throw Error();
 
+        dactyl.assert(number < 0 ? elem.scrollTop > 0 : elem.scrollTop < elem.scrollHeight - elem.clientHeight);
+
         let top = elem.dactylScrollDestY !== undefined ? elem.dactylScrollDestY : elem.scrollTop;
         elem.dactylScrollDestY = undefined;
 
-        dactyl.assert(number < 0 ? top > 0 : top < elem.scrollHeight - elem.clientHeight);
         Buffer.scrollTo(elem, null, top + number * increment);
     },
 
@@ -1212,7 +1337,7 @@ var Buffer = Module("buffer", {
     },
 
     openUploadPrompt: function openUploadPrompt(elem) {
-        io.CommandFileMode("Upload file: ", {
+        io.CommandFileMode(_("buffer.prompt.uploadFile") + " ", {
             onSubmit: function onSubmit(path) {
                 let file = io.File(path);
                 dactyl.assert(file.exists());
@@ -1238,12 +1363,21 @@ var Buffer = Module("buffer", {
 
                 // FIXME: arg handling is a bit of a mess, check for filename
                 dactyl.assert(!arg || arg[0] == ">" && !util.OS.isWindows,
-                              _("error.trailing"));
+                              _("error.trailingCharacters"));
+
+                const PRINTER = "PostScript/default";
+                const BRANCH  = "print.printer_" + PRINTER + ".";
 
                 prefs.withContext(function () {
                     if (arg) {
-                        prefs.set("print.print_to_file", "true");
-                        prefs.set("print.print_to_filename", io.File(arg.substr(1)).path);
+                        prefs.set("print.print_printer", PRINTER);
+
+                        prefs.set(   "print.print_to_file", true);
+                        prefs.set(BRANCH + "print_to_file", true);
+
+                        prefs.set(   "print.print_to_filename", io.File(arg.substr(1)).path);
+                        prefs.set(BRANCH + "print_to_filename", io.File(arg.substr(1)).path);
+
                         dactyl.echomsg(_("print.toFile", arg.substr(1)));
                     }
                     else
@@ -1417,7 +1551,7 @@ var Buffer = Module("buffer", {
                     level = Math.constrain(level, Buffer.ZOOM_MIN, Buffer.ZOOM_MAX);
                 }
                 else
-                    dactyl.assert(false, _("error.trailing"));
+                    dactyl.assert(false, _("error.trailingCharacters"));
 
                 buffer.setZoom(level, args.bang);
             },
@@ -1434,21 +1568,24 @@ var Buffer = Module("buffer", {
             let styles = iter([s.title, []] for (s in values(buffer.alternateStyleSheets))).toObject();
 
             buffer.alternateStyleSheets.forEach(function (style) {
-                styles[style.title].push(style.href || "inline");
+                styles[style.title].push(style.href || _("style.inline"));
             });
 
             context.completions = [[title, href.join(", ")] for ([title, href] in Iterator(styles))];
         };
 
-        completion.buffer = function buffer(context) {
+        completion.buffer = function buffer(context, visible) {
             let filter = context.filter.toLowerCase();
+
             let defItem = { parent: { getTitle: function () "" } };
+
             let tabGroups = {};
             tabs.getGroups();
-            tabs.allTabs.forEach(function (tab, i) {
+            tabs[visible ? "visibleTabs" : "allTabs"].forEach(function (tab, i) {
                 let group = (tab.tabItem || tab._tabViewTabItem || defItem).parent || defItem.parent;
-                if (!set.has(tabGroups, group.id))
+                if (!Set.has(tabGroups, group.id))
                     tabGroups[group.id] = [group.getTitle(), []];
+
                 group = tabGroups[group.id];
                 group[1].push([i, tab.linkedBrowser]);
             });
@@ -1470,7 +1607,7 @@ var Buffer = Module("buffer", {
                 command: function () "tabs.select"
             };
             context.compare = CompletionContext.Sort.number;
-            context.filters = [CompletionContext.Filter.textDescription];
+            context.filters[0] = CompletionContext.Filter.textDescription;
 
             for (let [id, vals] in Iterator(tabGroups))
                 context.fork(id, 0, this, function (context, [name, browsers]) {
@@ -1483,12 +1620,12 @@ var Buffer = Module("buffer", {
                             else if (i == tabs.index(tabs.alternate))
                                 indicator = "#";
 
-                            let tab = tabs.getTab(i);
+                            let tab = tabs.getTab(i, visible);
                             let url = browser.contentDocument.location.href;
                             i = i + 1;
 
                             return {
-                                text: [i + ": " + (tab.label || "(Untitled)"), i + ": " + url],
+                                text: [i + ": " + (tab.label || /*L*/"(Untitled)"), i + ": " + url],
                                 tab: tab,
                                 id: i - 1,
                                 url: url,
@@ -1514,21 +1651,21 @@ var Buffer = Module("buffer", {
             function () { dactyl.clipboardWrite(buffer.uri.spec, true); });
 
         mappings.add([modes.NORMAL],
-            ["<C-a>"], "Increment last number in URL",
+            ["<C-a>", "<increment-url-path>"], "Increment last number in URL",
             function (args) { buffer.incrementURL(Math.max(args.count, 1)); },
             { count: true });
 
         mappings.add([modes.NORMAL],
-            ["<C-x>"], "Decrement last number in URL",
+            ["<C-x>", "<decrement-url-path>"], "Decrement last number in URL",
             function (args) { buffer.incrementURL(-Math.max(args.count, 1)); },
             { count: true });
 
-        mappings.add([modes.NORMAL], ["gu"],
+        mappings.add([modes.NORMAL], ["gu", "<open-parent-path>"],
             "Go to parent directory",
             function (args) { buffer.climbUrlPath(Math.max(args.count, 1)); },
             { count: true });
 
-        mappings.add([modes.NORMAL], ["gU"],
+        mappings.add([modes.NORMAL], ["gU", "<open-root-path>"],
             "Go to the root of the website",
             function () { buffer.climbUrlPath(-1); });
 
@@ -1542,11 +1679,11 @@ var Buffer = Module("buffer", {
             },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["i", "<Insert>"],
-            "Start caret mode",
+        mappings.add([modes.NORMAL], ["i", "<Insert>"],
+            "Start Caret mode",
             function () { modes.push(modes.CARET); });
 
-        mappings.add([modes.COMMAND], ["<C-c>"],
+        mappings.add([modes.NORMAL], ["<C-c>", "<stop-load>"],
             "Stop loading the current web page",
             function () { ex.stop(); });
 
@@ -1579,12 +1716,12 @@ var Buffer = Module("buffer", {
             "Scroll to the absolute right of the document",
             function () { buffer.scrollToPercent(100, null); });
 
-        mappings.add([modes.COMMAND], ["gg", "<Home>"],
+        mappings.add([modes.COMMAND], ["gg", "<Home>", "<scroll-top>"],
             "Go to the top of the document",
             function (args) { buffer.scrollToPercent(null, args.count != null ? args.count : 0); },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["G", "<End>"],
+        mappings.add([modes.COMMAND], ["G", "<End>", "<scroll-bottom>"],
             "Go to the end of the document",
             function (args) { buffer.scrollToPercent(null, args.count != null ? args.count : 100); },
             { count: true });
@@ -1607,55 +1744,84 @@ var Buffer = Module("buffer", {
             function (args) { buffer._scrollByScrollSize(args.count, false); },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["<C-b>", "<PageUp>", "<S-Space>", "<scroll-page-up>"],
+        mappings.add([modes.COMMAND], ["<C-b>", "<PageUp>", "<S-Space>", "<scroll-up-page>"],
             "Scroll up a full page",
             function (args) { buffer.scrollVertical("pages", -Math.max(args.count, 1)); },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["<C-f>", "<PageDown>", "<Space>", "<scroll-page-down>"],
+        mappings.add([modes.COMMAND], ["<Space>"],
+            "Scroll down a full page",
+            function (args) {
+                if (isinstance(content.document.activeElement, [HTMLInputElement, HTMLButtonElement]))
+                    return Events.PASS;
+                buffer.scrollVertical("pages", Math.max(args.count, 1));
+            },
+            { count: true });
+
+        mappings.add([modes.COMMAND], ["<C-f>", "<PageDown>", "<scroll-down-page>"],
             "Scroll down a full page",
             function (args) { buffer.scrollVertical("pages", Math.max(args.count, 1)); },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["]f", "<previous-frame>"],
+        mappings.add([modes.NORMAL], ["]f", "<previous-frame>"],
             "Focus next frame",
             function (args) { buffer.shiftFrameFocus(Math.max(args.count, 1)); },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["[f", "<next-frame>"],
+        mappings.add([modes.NORMAL], ["[f", "<next-frame>"],
             "Focus previous frame",
             function (args) { buffer.shiftFrameFocus(-Math.max(args.count, 1)); },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["]]", "<next-page>"],
+        mappings.add([modes.NORMAL], ["["],
+            "Jump to the previous element as defined by 'jumptags'",
+            function (args) { buffer.findJump(args.arg, args.count, true); },
+            { arg: true, count: true });
+
+        mappings.add([modes.NORMAL], ["]"],
+            "Jump to the next element as defined by 'jumptags'",
+            function (args) { buffer.findJump(args.arg, args.count, false); },
+            { arg: true, count: true });
+
+        mappings.add([modes.NORMAL], ["{"],
+            "Jump to the previous paragraph",
+            function (args) { buffer.findJump("p", args.count, true); },
+            { count: true });
+
+        mappings.add([modes.NORMAL], ["}"],
+            "Jump to the next paragraph",
+            function (args) { buffer.findJump("p", args.count, false); },
+            { count: true });
+
+        mappings.add([modes.NORMAL], ["]]", "<next-page>"],
             "Follow the link labeled 'next' or '>' if it exists",
             function (args) {
                 buffer.findLink("next", options["nextpattern"], (args.count || 1) - 1, true);
             },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["[[", "<previous-page>"],
+        mappings.add([modes.NORMAL], ["[[", "<previous-page>"],
             "Follow the link labeled 'prev', 'previous' or '<' if it exists",
             function (args) {
                 buffer.findLink("previous", options["previouspattern"], (args.count || 1) - 1, true);
             },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["gf", "<view-source>"],
+        mappings.add([modes.NORMAL], ["gf", "<view-source>"],
             "Toggle between rendered and source view",
             function () { buffer.viewSource(null, false); });
 
-        mappings.add([modes.COMMAND], ["gF", "<view-source-externally>"],
+        mappings.add([modes.NORMAL], ["gF", "<view-source-externally>"],
             "View source with an external editor",
             function () { buffer.viewSource(null, true); });
 
-        mappings.add([modes.COMMAND], ["gi", "<focus-input>"],
+        mappings.add([modes.NORMAL], ["gi", "<focus-input>"],
             "Focus last used input field",
             function (args) {
                 let elem = buffer.lastInputField;
 
                 if (args.count >= 1 || !elem || !events.isContentNode(elem)) {
-                    let xpath = ["frame", "iframe", "input", "textarea[not(@disabled) and not(@readonly)]"];
+                    let xpath = ["frame", "iframe", "input", "xul:textbox", "textarea[not(@disabled) and not(@readonly)]"];
 
                     let frames = buffer.allFrames(null, true);
 
@@ -1664,13 +1830,14 @@ var Buffer = Module("buffer", {
                         if (isinstance(elem, [HTMLFrameElement, HTMLIFrameElement]))
                             return Editor.getEditor(elem.contentWindow);
 
-                        if (elem.readOnly || elem instanceof HTMLInputElement && !set.has(util.editableInputs, elem.type))
+                        if (elem.readOnly || elem instanceof HTMLInputElement && !Set.has(util.editableInputs, elem.type))
                             return false;
 
                         let computedStyle = util.computedStyle(elem);
                         let rect = elem.getBoundingClientRect();
                         return computedStyle.visibility != "hidden" && computedStyle.display != "none" &&
-                            computedStyle.MozUserFocus != "ignore" && rect.width && rect.height;
+                            (elem instanceof Ci.nsIDOMXULTextBoxElement || computedStyle.MozUserFocus != "ignore") &&
+                            rect.width && rect.height;
                     });
 
                     dactyl.assert(elements.length > 0);
@@ -1681,36 +1848,40 @@ var Buffer = Module("buffer", {
             },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["gP"],
-            "Open (]put) a URL based on the current clipboard contents in a new buffer",
+        function url() {
+            let url = dactyl.clipboardRead();
+            dactyl.assert(url, _("error.clipboardEmpty"));
+
+            let proto = /^([-\w]+):/.exec(url);
+            if (proto && "@mozilla.org/network/protocol;1?name=" + proto[1] in Cc && !RegExp(options["urlseparator"]).test(url))
+                return url.replace(/\s+/g, "");
+            return url;
+        }
+
+        mappings.add([modes.NORMAL], ["gP"],
+            "Open (put) a URL based on the current clipboard contents in a new background buffer",
             function () {
-                let url = dactyl.clipboardRead();
-                dactyl.assert(url, _("error.clipboardEmpty"));
-                dactyl.open(url, { from: "paste", where: dactyl.NEW_TAB, background: true });
+                dactyl.open(url(), { from: "paste", where: dactyl.NEW_TAB, background: true });
             });
 
-        mappings.add([modes.COMMAND], ["p", "<MiddleMouse>", "<open-clipboard-url>"],
+        mappings.add([modes.NORMAL], ["p", "<MiddleMouse>", "<open-clipboard-url>"],
             "Open (put) a URL based on the current clipboard contents in the current buffer",
             function () {
-                let url = dactyl.clipboardRead();
-                dactyl.assert(url, _("error.clipboardEmpty"));
-                dactyl.open(url);
+                dactyl.open(url());
             });
 
-        mappings.add([modes.COMMAND], ["P", "<tab-open-clipboard-url>"],
+        mappings.add([modes.NORMAL], ["P", "<tab-open-clipboard-url>"],
             "Open (put) a URL based on the current clipboard contents in a new buffer",
             function () {
-                let url = dactyl.clipboardRead();
-                dactyl.assert(url, _("error.clipboardEmpty"));
-                dactyl.open(url, { from: "paste", where: dactyl.NEW_TAB });
+                dactyl.open(url(), { from: "paste", where: dactyl.NEW_TAB });
             });
 
         // reloading
-        mappings.add([modes.COMMAND], ["r", "<reload>"],
+        mappings.add([modes.NORMAL], ["r", "<reload>"],
             "Reload the current web page",
             function () { tabs.reload(tabs.getTab(), false); });
 
-        mappings.add([modes.COMMAND], ["R", "<full-reload>"],
+        mappings.add([modes.NORMAL], ["R", "<full-reload>"],
             "Reload while skipping the cache",
             function () { tabs.reload(tabs.getTab(), true); });
 
@@ -1724,62 +1895,62 @@ var Buffer = Module("buffer", {
             });
 
         // zooming
-        mappings.add([modes.COMMAND], ["zi", "+", "<text-zoom-in>"],
+        mappings.add([modes.NORMAL], ["zi", "+", "<text-zoom-in>"],
             "Enlarge text zoom of current web page",
             function (args) { buffer.zoomIn(Math.max(args.count, 1), false); },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["zm", "<text-zoom-more>"],
+        mappings.add([modes.NORMAL], ["zm", "<text-zoom-more>"],
             "Enlarge text zoom of current web page by a larger amount",
             function (args) { buffer.zoomIn(Math.max(args.count, 1) * 3, false); },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["zo", "-", "<text-zoom-out>"],
+        mappings.add([modes.NORMAL], ["zo", "-", "<text-zoom-out>"],
             "Reduce text zoom of current web page",
             function (args) { buffer.zoomOut(Math.max(args.count, 1), false); },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["zr", "<text-zoom-reduce>"],
+        mappings.add([modes.NORMAL], ["zr", "<text-zoom-reduce>"],
             "Reduce text zoom of current web page by a larger amount",
             function (args) { buffer.zoomOut(Math.max(args.count, 1) * 3, false); },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["zz", "<text-zoom>"],
+        mappings.add([modes.NORMAL], ["zz", "<text-zoom>"],
             "Set text zoom value of current web page",
             function (args) { buffer.setZoom(args.count > 1 ? args.count : 100, false); },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["ZI", "zI", "<full-zoom-in>"],
+        mappings.add([modes.NORMAL], ["ZI", "zI", "<full-zoom-in>"],
             "Enlarge full zoom of current web page",
             function (args) { buffer.zoomIn(Math.max(args.count, 1), true); },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["ZM", "zM", "<full-zoom-more>"],
+        mappings.add([modes.NORMAL], ["ZM", "zM", "<full-zoom-more>"],
             "Enlarge full zoom of current web page by a larger amount",
             function (args) { buffer.zoomIn(Math.max(args.count, 1) * 3, true); },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["ZO", "zO", "<full-zoom-out>"],
+        mappings.add([modes.NORMAL], ["ZO", "zO", "<full-zoom-out>"],
             "Reduce full zoom of current web page",
             function (args) { buffer.zoomOut(Math.max(args.count, 1), true); },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["ZR", "zR", "<full-zoom-reduce>"],
+        mappings.add([modes.NORMAL], ["ZR", "zR", "<full-zoom-reduce>"],
             "Reduce full zoom of current web page by a larger amount",
             function (args) { buffer.zoomOut(Math.max(args.count, 1) * 3, true); },
             { count: true });
 
-        mappings.add([modes.COMMAND], ["zZ", "<full-zoom>"],
+        mappings.add([modes.NORMAL], ["zZ", "<full-zoom>"],
             "Set full zoom value of current web page",
             function (args) { buffer.setZoom(args.count > 1 ? args.count : 100, true); },
             { count: true });
 
         // page info
-        mappings.add([modes.COMMAND], ["<C-g>", "<page-info>"],
+        mappings.add([modes.NORMAL], ["<C-g>", "<page-info>"],
             "Print the current file name",
             function () { buffer.showPageInfo(false); });
 
-        mappings.add([modes.COMMAND], ["g<C-g>", "<more-page-info>"],
+        mappings.add([modes.NORMAL], ["g<C-g>", "<more-page-info>"],
             "Print file information",
             function () { buffer.showPageInfo(true); });
     },
@@ -1817,6 +1988,23 @@ var Buffer = Module("buffer", {
                 validator: function (value) RegExp(value)
             });
 
+        options.add(["jumptags", "jt"],
+            "XPath or CSS selector strings of jumpable elements for extended hint modes",
+            "stringmap", {
+                "p": "p,table,ul,ol,blockquote",
+                "h": "h1,h2,h3,h4,h5,h6"
+            },
+            {
+                keepQuotes: true,
+                setter: function (vals) {
+                    for (let [k, v] in Iterator(vals))
+                        vals[k] = update(new String(v), { matcher: util.compileMatcher(Option.splitList(v)) });
+                    return vals;
+                },
+                validator: function (value) util.validateMatcher.call(this, value)
+                    && Object.keys(value).every(function (v) v.length == 1)
+            });
+
         options.add(["nextpattern"],
             "Patterns to use when guessing the next page in a document sequence",
             "regexplist", UTF8("'\\bnext\\b',^>$,^(>>|»)$,^(>|»),(>|»)$,'\\bmore\\b'"),
@@ -1829,7 +2017,7 @@ var Buffer = Module("buffer", {
 
         options.add(["pageinfo", "pa"],
             "Define which sections are shown by the :pageinfo command",
-            "charlist", "gfm",
+            "charlist", "gesfm",
             { get values() values(buffer.pageInfo).toObject() });
 
         options.add(["scroll", "scr"],
index 69e6a3cc24e6118d23725b31754ef6ff5a350b8f..cae99423a67e83c50f1051ea9312bacb30305850 100644 (file)
@@ -152,6 +152,7 @@ var CommandWidgets = Class("CommandWidgets", {
                 return this.commandbar;
             }
         });
+        this.updateVisibility();
     },
     addElement: function addElement(obj) {
         const self = this;
@@ -174,7 +175,7 @@ var CommandWidgets = Class("CommandWidgets", {
                     if (obj.value != null)
                         return [obj.value[0],
                                 obj.get ? obj.get.call(this, elem) : elem.value]
-                                .concat(obj.value.slice(2))
+                                .concat(obj.value.slice(2));
                     return null;
                 },
 
@@ -300,19 +301,21 @@ var CommandWidgets = Class("CommandWidgets", {
 });
 
 var CommandMode = Class("CommandMode", {
-    init: function init() {
+    init: function CM_init() {
         this.keepCommand = userContext.hidden_option_command_afterimage;
     },
 
+    get autocomplete() options["autocomplete"].length,
+
     get command() this.widgets.command[1],
     set command(val) this.widgets.command = val,
 
     get prompt() this.widgets.prompt,
     set prompt(val) this.widgets.prompt = val,
 
-    open: function (command) {
+    open: function CM_open(command) {
         dactyl.assert(isinstance(this.mode, modes.COMMAND_LINE),
-                      "Not opening command line in non-command-line mode.");
+                      /*L*/"Not opening command line in non-command-line mode.");
 
         this.messageCount = commandline.messageCount;
         modes.push(this.mode, this.extendedMode, this.closure);
@@ -340,7 +343,7 @@ var CommandMode = Class("CommandMode", {
 
     get widgets() commandline.widgets,
 
-    enter: function (stack) {
+    enter: function CM_enter(stack) {
         commandline.commandSession = this;
         if (stack.pop && commandline.command) {
             this.onChange(commandline.command);
@@ -349,7 +352,7 @@ var CommandMode = Class("CommandMode", {
         }
     },
 
-    leave: function (stack) {
+    leave: function CM_leave(stack) {
         if (!stack.push) {
             commandline.commandSession = null;
             this.input.dactylKeyPress = undefined;
@@ -374,7 +377,7 @@ var CommandMode = Class("CommandMode", {
     },
 
     events: {
-        input: function onInput(event) {
+        input: function CM_onInput(event) {
             if (this.completions) {
                 this.resetCompletions();
 
@@ -382,7 +385,7 @@ var CommandMode = Class("CommandMode", {
             }
             this.onChange(commandline.command);
         },
-        keyup: function onKeyUp(event) {
+        keyup: function CM_onKeyUp(event) {
             let key = events.toString(event);
             if (/-?Tab>$/.test(key) && this.completions)
                 this.completions.tabTimer.flush();
@@ -391,23 +394,22 @@ var CommandMode = Class("CommandMode", {
 
     keepCommand: false,
 
-    onKeyPress: function onKeyPress(events) {
+    onKeyPress: function CM_onKeyPress(events) {
         if (this.completions)
             this.completions.previewClear();
 
         return true; /* Pass event */
     },
 
-    onCancel: function (value) {
-    },
+    onCancel: function (value) {},
 
-    onChange: function (value) {
-    },
+    onChange: function (value) {},
 
-    onSubmit: function (value) {
-    },
+    onHistory: function (value) {},
+
+    onSubmit: function (value) {},
 
-    resetCompletions: function resetCompletions() {
+    resetCompletions: function CM_resetCompletions() {
         if (this.completions) {
             this.completions.context.cancelAll();
             this.completions.wildIndex = -1;
@@ -426,12 +428,12 @@ var CommandExMode = Class("CommandExMode", CommandMode, {
 
     prompt: ["Normal", ":"],
 
-    complete: function complete(context) {
+    complete: function CEM_complete(context) {
         context.fork("ex", 0, completion, "ex");
     },
 
-    onSubmit: function onSubmit(command) {
-        contexts.withContext({ file: "[Command Line]", line: 1 },
+    onSubmit: function CEM_onSubmit(command) {
+        contexts.withContext({ file: /*L*/"[Command Line]", line: 1 },
                              function _onSubmit() {
             io.withSavedValues(["readHeredoc"], function _onSubmit() {
                 this.readHeredoc = commandline.readHeredoc;
@@ -449,7 +451,7 @@ var CommandPromptMode = Class("CommandPromptMode", CommandMode, {
         init.supercall(this);
     },
 
-    complete: function (context) {
+    complete: function CPM_complete(context) {
         if (this.completer)
             context.forkapply("prompt", 0, this, "completer", Array.slice(arguments, 1));
     },
@@ -586,6 +588,9 @@ var CommandLine = Module("commandline", {
 
     get completionList() {
         let node = this.widgets.active.commandline;
+        if (this.commandSession && this.commandSession.completionList)
+            node = document.getElementById(this.commandSession.completionList);
+
         if (!node.completionList) {
             let elem = document.getElementById("dactyl-completions-" + node.id);
             util.waitFor(bind(this.widgets._ready, null, elem));
@@ -648,11 +653,11 @@ var CommandLine = Module("commandline", {
      * @param {XML} xml The output as an E4X XML object.
      */
     commandOutput: function commandOutput(xml) {
-        XML.ignoreWhitespace = false;
-        XML.prettyPrinting = false;
+        XML.ignoreWhitespace = XML.prettyPrinting = false;
         if (this.command)
-            this.echo(<>:{this.command}</>, this.HIGHLIGHT_NORMAL, this.FORCE_MULTILINE);
-        this.echo(xml, this.HIGHLIGHT_NORMAL, this.FORCE_MULTILINE);
+            this.echo(<><div xmlns={XHTML}>:{this.command}</div>&#x0d;{xml}</>, this.HIGHLIGHT_NORMAL, this.FORCE_MULTILINE);
+        else
+            this.echo(xml, this.HIGHLIGHT_NORMAL, this.FORCE_MULTILINE);
         this.command = null;
     },
 
@@ -722,6 +727,9 @@ var CommandLine = Module("commandline", {
 
         if (flags & this.APPEND_TO_MESSAGES) {
             let message = isObject(data) ? data : { message: data };
+
+            // Make sure the memoized message property is an instance property.
+            message.message;
             this._messageHistory.add(update({ highlight: highlightGroup }, message));
             data = message.message;
         }
@@ -737,7 +745,7 @@ var CommandLine = Module("commandline", {
         let single = flags & (this.FORCE_SINGLELINE | this.DISALLOW_MULTILINE);
         let action = this._echoLine;
 
-        if ((flags & this.FORCE_MULTILINE) || (/\n/.test(data) || !isString(data)) && !(flags & this.FORCE_SINGLELINE))
+        if ((flags & this.FORCE_MULTILINE) || (/\n/.test(data) || !isinstance(data, [_, "String"])) && !(flags & this.FORCE_SINGLELINE))
             action = mow.closure.echo;
 
         if (single)
@@ -803,12 +811,20 @@ var CommandLine = Module("commandline", {
     // FIXME: Buggy, especially when pasting.
     inputMultiline: function inputMultiline(end, callback) {
         let cmd = this.command;
+        let self = {
+            end: "\n" + end + "\n",
+            callback: callback
+        };
+
         modes.push(modes.INPUT_MULTILINE, null, {
-            mappingSelf: {
-                end: "\n" + end + "\n",
-                callback: callback
-            }
+            holdFocus: true,
+            leave: function leave() {
+                if (!self.done)
+                    self.callback(null);
+            },
+            mappingSelf: self
         });
+
         if (cmd != false)
             this._echoLine(cmd, this.HL_NORMAL);
 
@@ -838,7 +854,7 @@ var CommandLine = Module("commandline", {
                     event.target.blur();
                     dactyl.beep();
                 }
-            },
+            }
         }
     ),
 
@@ -940,6 +956,7 @@ var CommandLine = Module("commandline", {
             if (this.completions)
                 this.completions.previewClear();
             this.input.value = val;
+            this.session.onHistory(val);
         },
 
         /**
@@ -1014,9 +1031,9 @@ var CommandLine = Module("commandline", {
             dactyl.registerObserver("events.doneFeeding", this.closure.onDoneFeeding, true);
 
             this.autocompleteTimer = Timer(200, 500, function autocompleteTell(tabPressed) {
-                if (events.feedingKeys)
+                if (events.feedingKeys && !tabPressed)
                     this.ignoredCount++;
-                if (options["autocomplete"].length) {
+                else if (this.session.autocomplete) {
                     this.itemList.visible = true;
                     this.complete(true, false);
                 }
@@ -1086,6 +1103,7 @@ var CommandLine = Module("commandline", {
         get wildtype() this.wildtypes[this.wildIndex] || "",
 
         complete: function complete(show, tabPressed) {
+            this.session.ignoredCount = 0;
             this.context.reset();
             this.context.tabPressed = tabPressed;
             this.session.complete(this.context);
@@ -1230,7 +1248,7 @@ var CommandLine = Module("commandline", {
                     for (let [, context] in Iterator(list)) {
                         let done = function done() !(idx >= n + context.items.length || idx == -2 && !context.items.length);
 
-                        util.waitFor(function () !context.incomplete || done())
+                        util.waitFor(function () !context.incomplete || done());
                         if (done())
                             break;
 
@@ -1259,6 +1277,7 @@ var CommandLine = Module("commandline", {
 
         tab: function tab(reverse, wildmode) {
             this.autocompleteTimer.flush();
+            this.ignoredCount = 0;
 
             if (this._caret != this.caret)
                 this.reset();
@@ -1301,7 +1320,7 @@ var CommandLine = Module("commandline", {
                 if (this.selected == null)
                     statusline.progress = "";
                 else
-                    statusline.progress = "match " + (this.selected + 1) + " of " + this.items.length;
+                    statusline.progress = _("completion.matchIndex", this.selected + 1, this.items.length);
             }
 
             if (this.items.length == 0)
@@ -1415,7 +1434,7 @@ var CommandLine = Module("commandline", {
     mappings: function init_mappings() {
 
         mappings.add([modes.COMMAND],
-            [":"], "Enter command-line mode",
+            [":"], "Enter Command Line mode",
             function () { CommandExMode().open(""); });
 
         mappings.add([modes.INPUT_MULTILINE],
@@ -1427,6 +1446,7 @@ var CommandLine = Module("commandline", {
 
                 let index = text.indexOf(self.end);
                 if (index >= 0) {
+                    self.done = true;
                     text = text.substring(1, index);
                     modes.pop();
 
@@ -1614,7 +1634,7 @@ var ItemList = Class("ItemList", {
     _init: function _init() {
         this._div = this._dom(
             <div class="ex-command-output" highlight="Normal" style="white-space: nowrap">
-                <div highlight="Completions" key="noCompletions"><span highlight="Title">No Completions</span></div>
+                <div highlight="Completions" key="noCompletions"><span highlight="Title">{_("completion.noCompletions")}</span></div>
                 <div key="completions"/>
                 <div highlight="Completions">
                 {
@@ -1812,7 +1832,7 @@ var ItemList = Class("ItemList", {
 
     onKeyPress: function onKeyPress(event) false
 }, {
-    WAITING_MESSAGE: "Generating results..."
+    WAITING_MESSAGE: _("completion.generating")
 });
 
 // vim: set fdm=marker sw=4 ts=4 et:
index 6296b9ac6d26a5db35ccee446d4ecd3c606de94d..1a244ff641412240d89ec6c7c4726bdcd993529e 100644 (file)
@@ -12,9 +12,6 @@ default xml namespace = XHTML;
 XML.ignoreWhitespace = false;
 XML.prettyPrinting = false;
 
-var userContext = { __proto__: modules };
-var _userContext = newContext(userContext);
-
 var EVAL_ERROR = "__dactyl_eval_error";
 var EVAL_RESULT = "__dactyl_eval_result";
 var EVAL_STRING = "__dactyl_eval_string";
@@ -41,9 +38,25 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         };
 
         styles.registerSheet("resource://dactyl-skin/dactyl.css");
+
+        this.cleanups = [];
+        this.cleanups.push(util.overlayObject(window, {
+            focusAndSelectUrlBar: function focusAndSelectUrlBar() {
+                switch (options.get("strictfocus").getKey(document.documentURIObject || util.newURI(document.documentURI), "moderate")) {
+                case "laissez-faire":
+                    if (!Events.isHidden(window.gURLBar, true))
+                        return focusAndSelectUrlBar.superapply(this, arguments);
+                default:
+                    // Evil. Ignore.
+                }
+            }
+        }));
     },
 
     cleanup: function () {
+        for (let cleanup in values(this.cleanups))
+            cleanup.call(this);
+
         delete window.dactyl;
         delete window.liberator;
 
@@ -57,21 +70,36 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         autocommands.trigger("Leave", {});
     },
 
+    // initially hide all GUI elements, they are later restored unless the user
+    // has :set go= or something similar in his config
+    hideGUI: function () {
+        let guioptions = config.guioptions;
+        for (let option in guioptions) {
+            guioptions[option].forEach(function (elem) {
+                try {
+                    document.getElementById(elem).collapsed = true;
+                }
+                catch (e) {}
+            });
+        }
+    },
+
+
     observers: {
-        "dactyl-cleanup": function dactyl_cleanup() {
+        "dactyl-cleanup": function dactyl_cleanup(subject, reason) {
             let modules = dactyl.modules;
 
             for (let mod in values(modules.moduleList.reverse())) {
                 mod.stale = true;
                 if ("cleanup" in mod)
-                    this.trapErrors("cleanup", mod);
+                    this.trapErrors("cleanup", mod, reason);
                 if ("destroy" in mod)
-                    this.trapErrors("destroy", mod);
+                    this.trapErrors("destroy", mod, reason);
             }
 
             for (let mod in values(modules.ownPropertyValues.reverse()))
                 if (mod instanceof Class && "INIT" in mod && "cleanup" in mod.INIT)
-                    this.trapErrors(mod.cleanup, mod, dactyl, modules, window);
+                    this.trapErrors(mod.cleanup, mod, dactyl, modules, window, reason);
 
             for (let name in values(Object.getOwnPropertyNames(modules).reverse()))
                 try {
@@ -98,7 +126,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
     }),
 
     /**
-     * @property {number} The current main mode.
+     * @property {Modes.Mode} The current main mode.
      * @see modes#mainModes
      */
     mode: deprecated("modes.main", {
@@ -106,7 +134,37 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         set: function mode(val) modes.main = val
     }),
 
-    get menuItems() Dactyl.getMenuItems(),
+    get menuItems() {
+        function dispatch(node, name) {
+            let event = node.ownerDocument.createEvent("Events");
+            event.initEvent(name, false, false);
+            node.dispatchEvent(event);
+        }
+
+        function addChildren(node, parent) {
+            if (~["menu", "menupopup"].indexOf(node.localName) && node.children.length)
+                dispatch(node, "popupshowing");
+
+            for (let [, item] in Iterator(node.childNodes)) {
+                if (item.childNodes.length == 0 && item.localName == "menuitem"
+                    && !item.hidden
+                    && !/rdf:http:/.test(item.getAttribute("label"))) { // FIXME
+                    item.dactylPath = parent + item.getAttribute("label");
+                    items.push(item);
+                }
+                else {
+                    let path = parent;
+                    if (item.localName == "menu")
+                        path += item.getAttribute("label") + ".";
+                    addChildren(item, path);
+                }
+            }
+        }
+
+        let items = [];
+        addChildren(document.getElementById(config.guioptions["m"][1]), "");
+        return items;
+    },
 
     // Global constants
     CURRENT_TAB: "here",
@@ -184,14 +242,16 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
     },
 
     addUsageCommand: function (params) {
+        function keys(item) (item.names || [item.name]).concat(item.description, item.columns || []);
+
         let name = commands.add(params.name, params.description,
             function (args) {
                 let results = array(params.iterate(args))
                     .sort(function (a, b) String.localeCompare(a.name, b.name));
 
-                let filters = args.map(function (arg) RegExp("\\b" + util.regexp.escape(arg) + "\\b", "i"));
+                let filters = args.map(function (arg) util.regexp("\\b" + util.regexp.escape(arg) + "\\b", "i"));
                 if (filters.length)
-                    results = results.filter(function (item) filters.every(function (re) re.test(item.name + " " + item.description)));
+                    results = results.filter(function (item) filters.every(function (re) keys(item).some(re.closure.test)));
 
                 commandline.commandOutput(
                     template.usage(results, params.format));
@@ -200,9 +260,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 argCount: "*",
                 completer: function (context, args) {
                     context.keys.text = util.identity;
-                    context.keys.description = function () seen[this.text] + " matching items";
+                    context.keys.description = function () seen[this.text] + /*L*/" matching items";
                     let seen = {};
-                    context.completions = array(item.description.toLowerCase().split(/[()\s]+/)
+                    context.completions = array(keys(item).join(" ").toLowerCase().split(/[()\s]+/)
                                                 for (item in params.iterate(args)))
                         .flatten().filter(function (w) /^\w[\w-_']+$/.test(w))
                         .map(function (k) {
@@ -221,7 +281,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 let tags = services["dactyl:"].HELP_TAGS;
                 for (let obj in values(results)) {
                     let res = dactyl.generateHelp(obj, null, null, true);
-                    if (!set.has(tags, obj.helpTag))
+                    if (!Set.has(tags, obj.helpTag))
                         res[1].@tag = obj.helpTag;
 
                     yield res;
@@ -311,7 +371,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         clipboardHelper.copyString(str);
 
         if (verbose) {
-            let message = { message: "Yanked " + str };
+            let message = { message: _("dactyl.yank", str) };
             try {
                 message.domains = [util.newURI(str).host];
             }
@@ -346,7 +406,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
     echoerr: function echoerr(str, flags) {
         flags |= commandline.APPEND_TO_MESSAGES;
 
-        if (isinstance(str, ["Error", "Exception"]))
+        if (isinstance(str, ["DOMException", "Error", "Exception"]) || isinstance(str, ["XPCWrappedNative_NoHelper"]) && /^\[Exception/.test(str))
             dactyl.reportError(str);
         if (isObject(str) && "echoerr" in str)
             str = str.echoerr;
@@ -414,7 +474,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 ({ file: fileName, line: lineNumber, context: ctxt }) = info;
 
         if (!context && fileName && fileName[0] !== "[")
-            context = _userContext || ctxt;
+            context = ctxt || _userContext;
 
         if (isinstance(context, ["Sandbox"]))
             return Cu.evalInSandbox(str, context, "1.8", fileName, lineNumber);
@@ -559,7 +619,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
      * @param {string} feature The feature name.
      * @returns {boolean}
      */
-    has: function (feature) set.has(config.features, feature),
+    has: function (feature) Set.has(config.features, feature),
 
     /**
      * Returns the URL of the specified help *topic* if it exists.
@@ -608,6 +668,10 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
      * Initialize the help system.
      */
     initHelp: function (force) {
+        // Waits for the add-on to become available, if necessary.
+        config.addon;
+        config.version;
+
         if (force || !this.helpInitialized) {
             if ("noscriptOverlay" in window) {
                 noscriptOverlay.safeAllow("chrome-data:", true, false);
@@ -665,34 +729,38 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
             let body = XML();
             for (let [, context] in Iterator(plugins.contexts))
-                if (context && context.INFO instanceof XML) {
-                    let info = context.INFO;
-                    if (info.*.@lang.length()) {
-                        let lang = config.bestLocale(String(a) for each (a in info.*.@lang));
+                try {
+                    let info = contexts.getDocs(context);
+                    if (info instanceof XML) {
+                        if (info.*.@lang.length()) {
+                            let lang = config.bestLocale(String(a) for each (a in info.*.@lang));
 
-                        info.* = info.*.(function::attribute("lang").length() == 0 || @lang == lang);
+                            info.* = info.*.(function::attribute("lang").length() == 0 || @lang == lang);
 
-                        for each (let elem in info.NS::info)
-                            for each (let attr in ["@name", "@summary", "@href"])
-                                if (elem[attr].length())
-                                    info[attr] = elem[attr];
+                            for each (let elem in info.NS::info)
+                                for each (let attr in ["@name", "@summary", "@href"])
+                                    if (elem[attr].length())
+                                        info[attr] = elem[attr];
+                        }
+                        body += <h2 xmlns={NS.uri} tag={info.@name + '-plugin'}>{info.@summary}</h2> +
+                            info;
                     }
-                    body += <h2 xmlns={NS.uri} tag={context.INFO.@name + '-plugin'}>{context.INFO.@summary}</h2> +
-                        context.INFO;
+                }
+                catch (e) {
+                    util.reportError(e);
                 }
 
             let help =
                 '<?xml version="1.0"?>\n' +
                 '<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
                 '<!DOCTYPE document SYSTEM "resource://dactyl-content/dactyl.dtd">\n' +
-                unescape(encodeURI( // UTF-8 handling hack.
                 <document xmlns={NS}
                     name="plugins" title={config.appName + " Plugins"}>
-                    <h1 tag="using-plugins">Using Plugins</h1>
+                    <h1 tag="using-plugins">{_("help.title.Using Plugins")}</h1>
                     <toc start="2"/>
 
                     {body}
-                </document>.toXMLString()));
+                </document>.toXMLString();
             fileMap["plugins"] = function () ['text/xml;charset=UTF-8', help];
 
             fileMap["versions"] = function () {
@@ -725,6 +793,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
                 default xml namespace = NS;
                 function rec(text, level, li) {
+                    XML.ignoreWhitespace = XML.prettyPrinting = false;
+
                     let res = <></>;
                     let list, space, i = 0;
 
@@ -735,7 +805,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                             if (!list)
                                 res += list = <ul/>;
                             let li = <li/>;
-                            li.* += rec(match.content.replace(RegExp("^" + match.space, "gm"), ""), level + 1, li)
+                            li.* += rec(match.content.replace(RegExp("^" + match.space, "gm"), ""), level + 1, li);
                             list.* += li;
                         }
                         else if (match.par) {
@@ -751,8 +821,10 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                             }
 
                             list = null;
-                            if (level == 0 && /^.*:\n$/.test(match.par))
-                                res += <h2>{template.linkifyHelp(par.slice(0, -1), true)}</h2>;
+                            if (level == 0 && /^.*:\n$/.test(match.par)) {
+                                let text = par.slice(0, -1);
+                                res += <h2 tag={"news-" + text}>{template.linkifyHelp(text, true)}</h2>;
+                            }
                             else {
                                 let [, a, b] = /^(IMPORTANT:?)?([^]*)/.exec(par);
                                 res += <p highlight={group + " HelpNews"}>{
@@ -774,6 +846,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                     return res;
                 }
 
+                XML.ignoreWhitespace = XML.prettyPrinting = false;
                 let body = rec(NEWS, 0);
                 for each (let li in body..li) {
                     let list = li..li.(@NS::highlight == "HelpNewsOld");
@@ -784,19 +857,17 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                     }
                 }
 
-                XML.prettyPrinting = XML.ignoreWhitespace = false;
                 return ["application/xml",
                     '<?xml version="1.0"?>\n' +
                     '<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
                     '<!DOCTYPE document SYSTEM "resource://dactyl-content/dactyl.dtd">\n' +
-                    unescape(encodeURI( // UTF-8 handling hack.
                     <document xmlns={NS} xmlns:dactyl={NS}
                         name="versions" title={config.appName + " Versions"}>
                         <h1 tag="versions news NEWS">{config.appName} Versions</h1>
                         <toc start="2"/>
 
                         {body}
-                    </document>.toXMLString()))
+                    </document>.toXMLString()
                 ];
             }
             addTags("versions", util.httpGet("dactyl://help/versions").responseXML);
@@ -806,16 +877,27 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
             overlayMap["index"] = ['text/xml;charset=UTF-8',
                 '<?xml version="1.0"?>\n' +
-                '<overlay xmlns="' + NS + '">\n' +
-                unescape(encodeURI( // UTF-8 handling hack.
+                <overlay xmlns={NS}>{
                 template.map(dactyl.indices, function ([name, iter])
                     <dl insertafter={name + "-index"}>{
                         template.map(iter(), util.identity)
-                    }</dl>, <>{"\n\n"}</>))) +
-                '\n</overlay>'];
-
+                    }</dl>, <>{"\n\n"}</>)
+                }</overlay>];
             addTags("index", util.httpGet("dactyl://help-overlay/index").responseXML);
 
+            overlayMap["gui"] = ['text/xml;charset=UTF-8',
+                '<?xml version="1.0"?>\n' +
+                <overlay xmlns={NS}>
+                    <dl insertafter="dialog-list">{
+                    template.map(config.dialogs, function ([name, val])
+                        (!val[2] || val[2]())
+                            ? <><dt>{name}</dt><dd>{val[0]}</dd></>
+                            : undefined,
+                        <>{"\n"}</>)
+                    }</dl>
+                </overlay>];
+
+
             this.helpInitialized = true;
         }
     },
@@ -850,64 +932,68 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 addURIEntry(file, "data:text/plain;charset=UTF-8," + encodeURI(data));
         }
 
-        let empty = set("area base basefont br col frame hr img input isindex link meta param"
+        let empty = Set("area base basefont br col frame hr img input isindex link meta param"
                             .split(" "));
         function fix(node) {
             switch(node.nodeType) {
-                case Node.ELEMENT_NODE:
-                    if (isinstance(node, [HTMLBaseElement]))
-                        return;
-
-                    data.push("<"); data.push(node.localName);
-                    if (node instanceof HTMLHtmlElement)
-                        data.push(" xmlns=" + XHTML.uri.quote());
-
-                    for (let { name, value } in array.iterValues(node.attributes)) {
-                        if (name == "dactyl:highlight") {
-                            set.add(styles, value);
-                            name = "class";
-                            value = "hl-" + value;
-                        }
-                        if (name == "href") {
-                            value = node.href;
-                            if (value.indexOf("dactyl://help-tag/") == 0) {
-                                let uri = services.io.newChannel(value, null, null).originalURI;
-                                value = uri.spec == value ? "javascript:;" : uri.path.substr(1);
-                            }
-                            if (!/^#|[\/](#|$)|^[a-z]+:/.test(value))
-                                value = value.replace(/(#|$)/, ".xhtml$1");
-                        }
-                        if (name == "src" && value.indexOf(":") > 0) {
-                            chromeFiles[value] = value.replace(/.*\//, "");
-                            value = value.replace(/.*\//, "");
+            case Node.ELEMENT_NODE:
+                if (isinstance(node, [HTMLBaseElement]))
+                    return;
+
+                data.push("<"); data.push(node.localName);
+                if (node instanceof HTMLHtmlElement)
+                    data.push(" xmlns=" + XHTML.uri.quote(),
+                              " xmlns:dactyl=" + NS.uri.quote());
+
+                for (let { name, value } in array.iterValues(node.attributes)) {
+                    if (name == "dactyl:highlight") {
+                        Set.add(styles, value);
+                        name = "class";
+                        value = "hl-" + value;
+                    }
+                    if (name == "href") {
+                        value = node.href || value;
+                        if (value.indexOf("dactyl://help-tag/") == 0) {
+                            let uri = services.io.newChannel(value, null, null).originalURI;
+                            value = uri.spec == value ? "javascript:;" : uri.path.substr(1);
                         }
-                        data.push(" ");
-                        data.push(name);
-                        data.push('="');
-                        data.push(<>{value}</>.toXMLString());
-                        data.push('"');
+                        if (!/^#|[\/](#|$)|^[a-z]+:/.test(value))
+                            value = value.replace(/(#|$)/, ".xhtml$1");
                     }
-                    if (node.localName in empty)
-                        data.push(" />");
-                    else {
-                        data.push(">");
-                        if (node instanceof HTMLHeadElement)
-                            data.push(<link rel="stylesheet" type="text/css" href="help.css"/>.toXMLString());
-                        Array.map(node.childNodes, fix);
-                        data.push("</"); data.push(node.localName); data.push(">");
+                    if (name == "src" && value.indexOf(":") > 0) {
+                        chromeFiles[value] = value.replace(/.*\//, "");
+                        value = value.replace(/.*\//, "");
                     }
-                    break;
-                case Node.TEXT_NODE:
-                    data.push(<>{node.textContent}</>.toXMLString());
+
+                    data.push(" ", name, '="',
+                              <>{value}</>.toXMLString().replace(/"/g, "&quot;"),
+                              '"');
+                }
+                if (node.localName in empty)
+                    data.push(" />");
+                else {
+                    data.push(">");
+                    if (node instanceof HTMLHeadElement)
+                        data.push(<link rel="stylesheet" type="text/css" href="help.css"/>.toXMLString());
+                    Array.map(node.childNodes, fix);
+                    data.push("</", node.localName, ">");
+                }
+                break;
+            case Node.TEXT_NODE:
+                data.push(<>{node.textContent}</>.toXMLString());
             }
         }
 
         let chromeFiles = {};
         let styles = {};
         for (let [file, ] in Iterator(services["dactyl:"].FILE_MAP)) {
-            dactyl.open("dactyl://help/" + file);
-            dactyl.modules.events.waitForPageLoad();
-            let data = [
+            let url = "dactyl://help/" + file;
+            dactyl.open(url);
+            util.waitFor(function () content.location.href == url && buffer.loaded
+                            && content.document.documentElement instanceof HTMLHtmlElement,
+                         15000);
+            events.waitForPageLoad();
+            var data = [
                 '<?xml version="1.0" encoding="UTF-8"?>\n',
                 '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"\n',
                 '          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
@@ -916,7 +1002,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             addDataEntry(file + ".xhtml", data.join(""));
         }
 
-        let data = [h for (h in highlight) if (set.has(styles, h.class) || /^Help/.test(h.class))]
+        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\|/g, "") + "\t" + "{" + h.cssText + "}")
@@ -954,7 +1040,13 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         if (obj instanceof Command) {
             link = function (cmd) <ex>{cmd}</ex>;
             args = obj.parseArgs("", CompletionContext(str || ""));
-            spec = function (cmd) cmd + (obj.bang ? <oa>!</oa> : <></>);
+            spec = function (cmd) <>{
+                    obj.count ? <oa>count</oa> : <></>
+                }{
+                    cmd
+                }{
+                    obj.bang ? <oa>!</oa> : <></>
+                }</>;
         }
         else if (obj instanceof Map) {
             spec = function (map) obj.count ? <><oa>count</oa>{map}</> : <>{map}</>;
@@ -969,7 +1061,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             };
         }
         else if (obj instanceof Option) {
+            tag = spec = function (name) <>'{name}'</>;
             link = function (opt, name) <o>{name}</o>;
+            args = { value: "", values: [] };
         }
 
         XML.prettyPrinting = false;
@@ -1001,7 +1095,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 <description>{
                     obj.description ? br + <p>{template.linkifyHelp(obj.description.replace(/\.?$/, "."), true)}</p> : "" }{
                         extraHelp ? br + extraHelp : "" }{
-                        !(extraHelp || obj.description) ? br + <p>Sorry, no help available.</p> : "" }
+                        !(extraHelp || obj.description) ? br + <p><!--L-->Sorry, no help available.</p> : "" }
                 </description>
             </item></>;
 
@@ -1014,7 +1108,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         }
 
         if (obj.completer)
-            add(completion._runCompleter(obj.completer, "", null, args).items
+            add(completion._runCompleter(obj.closure.completer, "", null, args).items
                           .map(function (i) [i.text, i.description]));
 
         if (obj.options && obj.options.some(function (o) o.description))
@@ -1064,7 +1158,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
      * These are set and accessed with the "g:" prefix.
      */
     _globalVariables: {},
-    globalVariables: deprecated("the options system", {
+    globalVariables: deprecated(_("deprecated.for.theOptionsSystem"), {
         get: function globalVariables() this._globalVariables
     }),
 
@@ -1076,13 +1170,14 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
             let loadplugins = options.get("loadplugins");
             if (args)
-                loadplugins = { __proto__: loadplugins, value: args.map(Option.parseRegexp) }
+                loadplugins = { __proto__: loadplugins, value: args.map(Option.parseRegexp) };
 
             dir.readDirectory(true).forEach(function (file) {
-                if (file.isFile() && loadplugins.getKey(file.path) && !(!force && file.path in dactyl.pluginFiles)) {
+                if (file.isFile() && loadplugins.getKey(file.path)
+                        && !(!force && file.path in dactyl.pluginFiles && dactyl.pluginFiles[file.path] >= file.lastModifiedTime)) {
                     try {
                         io.source(file.path);
-                        dactyl.pluginFiles[file.path] = true;
+                        dactyl.pluginFiles[file.path] = file.lastModifiedTime;
                     }
                     catch (e) {
                         dactyl.reportError(e);
@@ -1151,7 +1246,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
     onExecute: function onExecute(event) {
         let cmd = event.originalTarget.getAttribute("dactyl-execute");
         commands.execute(cmd, null, false, null,
-                         { file: "[Command Line]", line: 1 });
+                         { file: /*L*/"[Command Line]", line: 1 });
     },
 
     /**
@@ -1187,7 +1282,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             urls = dactyl.parseURLs(urls);
 
         if (urls.length > prefs.get("browser.tabs.maxOpenBeforeWarn", 20) && !force)
-            return commandline.input("This will open " + urls.length + " new tabs. Would you like to continue? (yes/[no]) ",
+            return commandline.input(_("dactyl.prompt.openMany", urls.length) + " ",
                 function (resp) {
                     if (resp && resp.match(/^y(es)?$/i))
                         dactyl.open(urls, params, true);
@@ -1270,7 +1365,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
      * ['www.google.com/search?q=bla', 'www.osnews.com']
      *
      * @param {string} str
-     * @returns {string[]}
+     * @returns {[string]}
      */
     parseURLs: function parseURLs(str) {
         let urls;
@@ -1283,7 +1378,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         return urls.map(function (url) {
             url = url.trim();
 
-            if (/^(\.{0,2}|~)(\/|$)/.test(url)) {
+            if (/^(\.{0,2}|~)(\/|$)/.test(url) || util.OS.isWindows && /^[a-z]:/i.test(url)) {
                 try {
                     // Try to find a matching file.
                     let file = io.File(url);
@@ -1296,7 +1391,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             // If it starts with a valid protocol, pass it through.
             let proto = /^([-\w]+):/.exec(url);
             if (proto && "@mozilla.org/network/protocol;1?name=" + proto[1] in Cc)
-                return url.replace(/\s+/g, "");
+                return url;
 
             // Check for a matching search keyword.
             let searchURL = this.has("bookmarks") && bookmarks.getSearchURL(url, false);
@@ -1470,48 +1565,13 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
     },
 
     /**
-     * @property {Window[]} Returns an array of all the host application's
+     * @property {[Window]} Returns an array of all the host application's
      *     open windows.
      */
-    get windows() [win for (win in iter(services.windowMediator.getEnumerator("navigator:browser")))],
+    get windows() [win for (win in iter(services.windowMediator.getEnumerator("navigator:browser"))) if (win.dactyl)],
 
 }, {
-    // initially hide all GUI elements, they are later restored unless the user
-    // has :set go= or something similar in his config
-    hideGUI: function () {
-        let guioptions = config.guioptions;
-        for (let option in guioptions) {
-            guioptions[option].forEach(function (elem) {
-                try {
-                    document.getElementById(elem).collapsed = true;
-                }
-                catch (e) {}
-            });
-        }
-    },
-
-    // TODO: move this
-    getMenuItems: function () {
-        function addChildren(node, parent) {
-            for (let [, item] in Iterator(node.childNodes)) {
-                if (item.childNodes.length == 0 && item.localName == "menuitem"
-                    && !/rdf:http:/.test(item.getAttribute("label"))) { // FIXME
-                    item.fullMenuPath = parent + item.getAttribute("label");
-                    items.push(item);
-                }
-                else {
-                    let path = parent;
-                    if (item.localName == "menu")
-                        path += item.getAttribute("label") + ".";
-                    addChildren(item, path);
-                }
-            }
-        }
-
-        let items = [];
-        addChildren(document.getElementById(config.guioptions["m"][1]), "");
-        return items;
-    }
+    toolbarHidden: function hidden(elem) (elem.getAttribute("autohide") || elem.getAttribute("collapsed")) == "true"
 }, {
     events: function () {
         events.listen(window, "click", dactyl.closure.onClick, true);
@@ -1542,7 +1602,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                     M: ["Always show messages outside of the status line"]
                 },
                 setter: function (opts) {
-                    if (loaded.commandline)
+                    if (loaded.commandline || ~opts.indexOf("c"))
                         commandline.widgets.updateVisibility();
                 }
             },
@@ -1578,7 +1638,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                                       true);
 
                     prefs.safeSet("layout.scrollbar.side", opts.indexOf("l") >= 0 ? 3 : 2,
-                                  "See 'guioptions' scrollbar flags.");
+                                  _("option.guioptions.safeSet"));
                 },
                 validator: function (opts) Option.validIf(!(opts.indexOf("l") >= 0 && opts.indexOf("r") >= 0),
                                                           UTF8("Only one of ‘l’ or ‘r’ allowed"))
@@ -1616,7 +1676,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 // FIXME: cleanup
                 cleanupValue: config.cleanups.guioptions ||
                     "r" + [k for ([k, v] in iter(groups[1].opts))
-                           if (!document.getElementById(v[1][0]).collapsed)].join(""),
+                           if (!Dactyl.toolbarHidden(document.getElementById(v[1][0])))].join(""),
 
                 values: array(groups).map(function (g) [[k, v[0]] for ([k, v] in Iterator(g.opts))]).flatten(),
 
@@ -1670,7 +1730,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
         options.add(["urlseparator", "urlsep", "us"],
             "The regular expression used to separate multiple URLs in :open and friends",
-            "string", "\\|",
+            "string", " \\| ",
             { validator: function (value) RegExp(value) });
 
         options.add(["verbose", "vbs"],
@@ -1684,18 +1744,18 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             {
                 setter: function (value) {
                     prefs.safeSet("accessibility.typeaheadfind.enablesound", !value,
-                                  "See 'visualbell' option");
+                                  _("option.visualbell.safeSet"));
                     return value;
                 }
             });
     },
 
     mappings: function () {
-        mappings.add([modes.MAIN], ["<F1>"],
+        mappings.add([modes.MAIN], ["<open-help>", "<F1>"],
             "Open the introductory help page",
             function () { dactyl.help(); });
 
-        mappings.add([modes.MAIN], ["<A-F1>"],
+        mappings.add([modes.MAIN], ["<open-single-help>", "<A-F1>"],
             "Open the single, consolidated help page",
             function () { ex.helpall(); });
 
@@ -1727,7 +1787,6 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 }
             }, {
                 argCount: "1",
-                bang: true,
                 completer: function (context) {
                     context.ignoreCase = true;
                     completion.dialog(context);
@@ -1738,14 +1797,16 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             "Execute the specified menu item from the command line",
             function (args) {
                 let arg = args[0] || "";
-                let items = Dactyl.getMenuItems();
+                let items = dactyl.menuItems;
 
-                dactyl.assert(items.some(function (i) i.fullMenuPath == arg),
+                dactyl.assert(items.some(function (i) i.dactylPath == arg),
                               _("emenu.notFound", arg));
 
                 for (let [, item] in Iterator(items)) {
-                    if (item.fullMenuPath == arg)
+                    if (item.dactylPath == arg) {
+                        dactyl.assert(!item.disabled, _("error.disabled", item.dactylPath));
                         item.doCommand();
+                    }
                 }
             }, {
                 argCount: "1",
@@ -1793,7 +1854,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         });
 
         commands.add(["loadplugins", "lpl"],
-            "Load all plugins immediately",
+            "Load all or matching plugins",
             function (args) {
                 dactyl.loadPlugins(args.length ? args : null, args.bang);
             },
@@ -1802,7 +1863,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 bang: true,
                 keepQuotes: true,
                 serialGroup: 10,
-                serialize: function ()  [
+                serialize: function () [
                     {
                         command: this.name,
                         literalArg: options["loadplugins"].join(" ")
@@ -1819,6 +1880,15 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 literal: 0
             });
 
+        commands.add(["exit", "x"],
+            "Quit " + config.appName,
+            function (args) {
+                dactyl.quit(false, args.bang);
+            }, {
+                argCount: "0",
+                bang: true
+            });
+
         commands.add(["q[uit]"],
             dactyl.has("tabs") ? "Quit current tab" : "Quit application",
             function (args) {
@@ -1837,12 +1907,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             "Reload the " + config.appName + " add-on",
             function (args) {
                 if (args.trailing)
-                    JSMLoader.rehashCmd = args.trailing; // Hack.
+                    storage.session.rehashCmd = args.trailing; // Hack.
                 args.break = true;
                 util.rehash(args);
             },
             {
-                argCount: "0",
+                argCount: "0", // FIXME
                 options: [
                     {
                         names: ["+u"],
@@ -1870,16 +1940,16 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
         commands.add(["res[tart]"],
             "Force " + config.appName + " to restart",
-            function () { dactyl.restart(); });
+            function () { dactyl.restart(); },
+            { argCount: "0" });
 
         function findToolbar(name) util.evaluateXPath(
-            "//*[@toolbarname=" + util.escapeString(name, "'") + "]",
+            "//*[@toolbarname=" + util.escapeString(name, "'") + " or " +
+                "@toolbarname=" + util.escapeString(name.trim(), "'") + "]",
             document).snapshotItem(0);
 
         var toolbox = document.getElementById("navigator-toolbox");
         if (toolbox) {
-            let hidden = function hidden(elem) (elem.getAttribute("autohide") || elem.getAttribute("collapsed")) == "true";
-
             let toolbarCommand = function (names, desc, action, filter) {
                 commands.add(names, desc,
                     function (args) {
@@ -1888,7 +1958,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                         action(toolbar);
                         events.checkFocus();
                     }, {
-                        argcount: "1",
+                        argCount: "1",
                         completer: function (context) {
                             completion.toolbar(context);
                             if (filter)
@@ -1900,12 +1970,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
             toolbarCommand(["toolbars[how]", "tbs[how]"], "Show the named toolbar",
                 function (toolbar) dactyl.setNodeVisible(toolbar, true),
-                function ({ item }) hidden(item));
+                function ({ item }) Dactyl.toolbarHidden(item));
             toolbarCommand(["toolbarh[ide]", "tbh[ide]"], "Hide the named toolbar",
                 function (toolbar) dactyl.setNodeVisible(toolbar, false),
-                function ({ item }) !hidden(item));
+                function ({ item }) !Dactyl.toolbarHidden(item));
             toolbarCommand(["toolbart[oggle]", "tbt[oggle]"], "Toggle the named toolbar",
-                function (toolbar) dactyl.setNodeVisible(toolbar, hidden(toolbar)));
+                function (toolbar) dactyl.setNodeVisible(toolbar, Dactyl.toolbarHidden(toolbar)));
         }
 
         commands.add(["time"],
@@ -1916,9 +1986,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 args = args[0] || "";
 
                 if (args[0] == ":")
-                    var method = function () commands.execute(args, null, true);
+                    var func = function () commands.execute(args, null, false);
                 else
-                    method = dactyl.userFunc(args);
+                    func = dactyl.userFunc(args);
 
                 try {
                     if (count > 1) {
@@ -1927,7 +1997,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
                         for (let i in util.interruptibleRange(0, count, 500)) {
                             let now = Date.now();
-                            method();
+                            func();
                             total += Date.now() - now;
                         }
 
@@ -1953,16 +2023,16 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                         commandline.commandOutput(
                                 <table>
                                     <tr highlight="Title" align="left">
-                                        <th colspan="3">Code execution summary</th>
+                                        <th colspan="3">{_("title.Code execution summary")}</th>
                                     </tr>
-                                    <tr><td>&#xa0;&#xa0;Executed:</td><td align="right"><span class="times-executed">{count}</span></td><td>times</td></tr>
-                                    <tr><td>&#xa0;&#xa0;Average time:</td><td align="right"><span class="time-average">{each.toFixed(2)}</span></td><td>{eachUnits}</td></tr>
-                                    <tr><td>&#xa0;&#xa0;Total time:</td><td align="right"><span class="time-total">{total.toFixed(2)}</span></td><td>{totalUnits}</td></tr>
+                                    <tr><td>&#xa0;&#xa0;{_("title.Executed")}:</td><td align="right"><span class="times-executed">{count}</span></td><td><!--L-->times</td></tr>
+                                    <tr><td>&#xa0;&#xa0;{_("title.Average time")}:</td><td align="right"><span class="time-average">{each.toFixed(2)}</span></td><td>{eachUnits}</td></tr>
+                                    <tr><td>&#xa0;&#xa0;{_("title.Total time")}:</td><td align="right"><span class="time-total">{total.toFixed(2)}</span></td><td>{totalUnits}</td></tr>
                                 </table>);
                     }
                     else {
                         let beforeTime = Date.now();
-                        method();
+                        func();
 
                         if (special)
                             return;
@@ -1979,7 +2049,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                     dactyl.echoerr(e);
                 }
             }, {
-                argCount: "+",
+                argCount: "1",
                 bang: true,
                 completer: function (context) {
                     if (/^:/.test(context.filter))
@@ -2010,7 +2080,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                     vbs.setFrom = setFrom;
                 }
             }, {
-                argCount: "+",
+                argCount: "1",
                 completer: function (context) completion.ex(context),
                 count: true,
                 literal: 0,
@@ -2052,8 +2122,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         completion.menuItem = function menuItem(context) {
             context.title = ["Menu Path", "Label"];
             context.anchored = false;
-            context.keys = { text: "fullMenuPath", description: function (item) item.getAttribute("label") };
-            context.completions = dactyl.menuItems;
+            context.keys = {
+                text: "dactylPath",
+                description: function (item) item.getAttribute("label"),
+                highlight: function (item) item.disabled ? "Disabled" : ""
+            };
+            context.generate = function () dactyl.menuItems;
         };
 
         var toolbox = document.getElementById("navigator-toolbox");
@@ -2076,7 +2150,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
         dactyl.timeout(function () {
             try {
-                var args = JSMLoader.commandlineArgs || services.commandLineHandler.optionValue;
+                var args = storage.session.commandlineArgs || services.commandLineHandler.optionValue;
                 if (isString(args))
                     args = dactyl.parseCommandLine(args);
 
@@ -2107,7 +2181,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             }
 
             // TODO: we should have some class where all this guioptions stuff fits well
-            // Dactyl.hideGUI();
+            // dactyl.hideGUI();
 
             if (dactyl.userEval("typeof document", null, "test.js") === "undefined")
                 jsmodules.__proto__ = XPCSafeJSObjectWrapper(window);
@@ -2167,9 +2241,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                     dactyl.execute(cmd);
                 });
 
-            if (JSMLoader.rehashCmd)
-                dactyl.execute(JSMLoader.rehashCmd);
-            JSMLoader.rehashCmd = null;
+            if (storage.session.rehashCmd)
+                dactyl.execute(storage.session.rehashCmd);
+            storage.session.rehashCmd = null;
 
             dactyl.fullyInitialized = true;
             dactyl.triggerObserver("enter", null);
index 80b9032f5231ec9faee0de6a41491783ad272577..3481cda8bc70755849b15c955ec24a107492caa7 100644 (file)
@@ -14,16 +14,18 @@ const Cu = Components.utils;
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
+const TOPIC = "chrome-document-global-created";
+
 function observe(window, topic, url) {
-    if (topic === "chrome-document-global-created")
+    if (topic === TOPIC)
         checkDocument(window.document);
 }
 function init(id) {
     if (id)
         ADDON_ID = id;
 
-    Services.obs[id ? "addObserver" : "removeObserver"](observe, "chrome-document-global-created", false);
-    for (let doc in chromeDocuments)
+    Services.obs[id ? "addObserver" : "removeObserver"](observe, TOPIC, false);
+    for (let doc in chromeDocuments())
         checkDocument(doc, !id);
 }
 function cleanup() { init(null); }
@@ -55,7 +57,7 @@ function checkDocument(doc, disable, force) {
 }
 
 function chromeDocuments() {
-    let windows = services.windowMediator.getXULWindowEnumerator(null);
+    let windows = Services.wm.getXULWindowEnumerator(null);
     while (windows.hasMoreElements()) {
         let window = windows.getNext().QueryInterface(Ci.nsIXULWindow);
         for each (let type in ["typeChrome", "typeContent"]) {
index e1df65370adea22d02da9706ae7bccabcc88d41c..d2fe3224b9f063b010fe42eae926f1418a41df07 100644 (file)
@@ -1,3 +1,4 @@
+// Copyright (c) 2008-2011 Kris Maglione <maglione.k at Gmail>
 // Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org>
 //
 // This work is licensed for reuse under an MIT license. Details are
@@ -24,12 +25,6 @@ var Editor = Module("editor", {
     selectedText: function () String(Editor.getEditor(null).selection),
 
     pasteClipboard: function (clipboard, toStart) {
-        // TODO: I don't think this is needed anymore? --djk
-        if (util.OS.isWindows) {
-            this.executeCommand("cmd_paste");
-            return;
-        }
-
         let elem = dactyl.focusedElement;
         if (elem.inputField)
             elem = elem.inputField;
@@ -189,61 +184,52 @@ var Editor = Module("editor", {
         }
     },
 
-    // returns the position of char
-    findCharForward: function (ch, count) {
-        if (!Editor.getEditor())
+    findChar: function (key, count, backward) {
+
+        let editor = Editor.getEditor();
+        if (!editor)
             return -1;
 
-        let text = Editor.getEditor().value;
         // XXX
         if (count == null)
             count = 1;
 
-        for (let i = Editor.getEditor().selectionEnd + 1; i < text.length; i++) {
-            if (text[i] == "\n")
-                break;
-            if (text[i] == ch)
-                count--;
-            if (count == 0)
-                return i + 1; // always position the cursor after the char
-        }
+        let code = events.fromString(key)[0].charCode;
+        util.assert(code);
+        let char = String.fromCharCode(code);
 
-        dactyl.beep();
-        return -1;
-    },
-
-    // returns the position of char
-    findCharBackward: function (ch, count) {
-        if (!Editor.getEditor())
-            return -1;
-
-        let text = Editor.getEditor().value;
-        // XXX
-        if (count == null)
-            count = 1;
+        let text = editor.value;
+        let caret = editor.selectionEnd;
+        if (backward) {
+            let end = text.lastIndexOf("\n", caret);
+            while (caret > end && caret >= 0 && count--)
+                caret = text.lastIndexOf(char, caret - 1);
+        }
+        else {
+            let end = text.indexOf("\n", caret);
+            if (end == -1)
+                end = text.length;
 
-        for (let i = Editor.getEditor().selectionStart - 1; i >= 0; i--) {
-            if (text[i] == "\n")
-                break;
-            if (text[i] == ch)
-                count--;
-            if (count == 0)
-                return i;
+            while (caret < end && caret >= 0 && count--)
+                caret = text.indexOf(char, caret + 1);
         }
 
-        dactyl.beep();
-        return -1;
+        if (count > 0)
+            caret = -1;
+        if (caret == -1)
+            dactyl.beep();
+        return caret;
     },
 
     /**
      * Edits the given file in the external editor as specified by the
      * 'editor' option.
      *
-     * @param {object|File|string} args An object specifying the file,
-     *  line, and column to edit. If a non-object is specified, it is
-     *  treated as the file parameter of the object.
+     * @param {object|File|string} args An object specifying the file, line,
+     *     and column to edit. If a non-object is specified, it is treated as
+     *     the file parameter of the object.
      * @param {boolean} blocking If true, this function does not return
-     *  until the editor exits.
+     *     until the editor exits.
      */
     editFileExternally: function (args, blocking) {
         if (!isObject(args) || args instanceof File)
@@ -252,7 +238,7 @@ var Editor = Module("editor", {
 
         let args = options.get("editor").format(args);
 
-        dactyl.assert(args.length >= 1, _("editor.noEditor"));
+        dactyl.assert(args.length >= 1, _("option.notSet", "editor"));
 
         io.run(args.shift(), args, blocking);
     },
@@ -266,7 +252,7 @@ var Editor = Module("editor", {
         let line, column;
 
         if (!forceEditing && textBox && textBox.type == "password") {
-            commandline.input("Editing a password field externally will reveal the password. Would you like to continue? (yes/[no]): ",
+            commandline.input(_("editor.prompt.editPassword") + " ",
                 function (resp) {
                     if (resp && resp.match(/^y(es)?$/i))
                         editor.editFieldExternally(true);
@@ -281,10 +267,10 @@ var Editor = Module("editor", {
             column = 1 + pre.replace(/[^]*\n/, "").length;
         }
         else {
-            var editor = window.GetCurrentEditor ? GetCurrentEditor()
-                                                 : Editor.getEditor(document.commandDispatcher.focusedWindow);
-            dactyl.assert(editor);
-            text = Array.map(editor.rootElement.childNodes, function (e) util.domToString(e, true)).join("");
+            var editor_ = window.GetCurrentEditor ? GetCurrentEditor()
+                                                  : Editor.getEditor(document.commandDispatcher.focusedWindow);
+            dactyl.assert(editor_);
+            text = Array.map(editor_.rootElement.childNodes, function (e) util.domToString(e, true)).join("");
         }
 
         let origGroup = textBox && textBox.getAttributeNS(NS, "highlight") || "";
@@ -318,19 +304,25 @@ var Editor = Module("editor", {
             lastUpdate = Date.now();
 
             let val = tmpfile.read();
-            if (textBox)
+            if (textBox) {
                 textBox.value = val;
+
+                textBox.setAttributeNS(NS, "modifiable", true);
+                util.computedStyle(textBox).MozUserInput;
+                events.dispatch(textBox, events.create(textBox.ownerDocument, "input", {}));
+                textBox.removeAttributeNS(NS, "modifiable");
+            }
             else {
-                while (editor.rootElement.firstChild)
-                    editor.rootElement.removeChild(editor.rootElement.firstChild);
-                editor.rootElement.innerHTML = val;
+                while (editor_.rootElement.firstChild)
+                    editor_.rootElement.removeChild(editor_.rootElement.firstChild);
+                editor_.rootElement.innerHTML = val;
             }
         }
 
         try {
             var tmpfile = io.createTempFile();
             if (!tmpfile)
-                throw Error("Couldn't create temporary file");
+                throw Error(_("io.cantCreateTempFile"));
 
             if (textBox) {
                 highlight.highlightNode(textBox, origGroup + " EditorEditing");
@@ -338,8 +330,7 @@ var Editor = Module("editor", {
             }
 
             if (!tmpfile.write(text))
-                throw Error("Input contains characters not valid in the current " +
-                            "file encoding");
+                throw Error(_("io.cantEncode"));
 
             var lastUpdate = Date.now();
 
@@ -414,9 +405,9 @@ var Editor = Module("editor", {
             elem = dactyl.focusedElement || document.commandDispatcher.focusedWindow;
         dactyl.assert(elem);
 
-        if (elem instanceof Element)
-            return elem.QueryInterface(Ci.nsIDOMNSEditableElement).editor;
         try {
+            if (elem instanceof Element)
+                return elem.QueryInterface(Ci.nsIDOMNSEditableElement).editor;
             return elem.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
                        .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIEditingSession)
                        .getEditorForWindow(elem);
@@ -658,19 +649,28 @@ var Editor = Module("editor", {
         mappings.add([modes.INPUT],
             ["<C-t>"], "Edit text field in Vi mode",
             function () {
+                dactyl.assert(dactyl.focusedElement);
                 dactyl.assert(!editor.isTextEdit);
                 modes.push(modes.TEXT_EDIT);
             });
 
+        // Ugh.
+        mappings.add([modes.INPUT, modes.CARET],
+            ["<*-CR>", "<*-BS>", "<*-Del>", "<*-Left>", "<*-Right>", "<*-Up>", "<*-Down>",
+             "<*-Home>", "<*-End>", "<*-PageUp>", "<*-PageDown>",
+             "<M-c>", "<M-v>", "<*-Tab>"],
+            "Handled by " + config.host,
+            function () Events.PASS_THROUGH);
+
         mappings.add([modes.INSERT],
-            ["<Space>", "<Return>"], "Expand insert mode abbreviation",
+            ["<Space>", "<Return>"], "Expand Insert mode abbreviation",
             function () {
                 editor.expandAbbreviation(modes.INSERT);
-                return Events.PASS;
+                return Events.PASS_THROUGH;
             });
 
         mappings.add([modes.INSERT],
-            ["<C-]>", "<C-5>"], "Expand insert mode abbreviation",
+            ["<C-]>", "<C-5>"], "Expand Insert mode abbreviation",
             function () { editor.expandAbbreviation(modes.INSERT); });
 
         // text edit mode
@@ -723,15 +723,15 @@ var Editor = Module("editor", {
 
         // visual mode
         mappings.add([modes.CARET, modes.TEXT_EDIT],
-            ["v"], "Start visual mode",
+            ["v"], "Start Visual mode",
             function () { modes.push(modes.VISUAL); });
 
         mappings.add([modes.VISUAL],
-            ["v", "V"], "End visual mode",
+            ["v", "V"], "End Visual mode",
             function () { modes.pop(); });
 
         mappings.add([modes.TEXT_EDIT],
-            ["V"], "Start visual line mode",
+            ["V"], "Start Visual Line mode",
             function () {
                 modes.push(modes.VISUAL, modes.LINE);
                 editor.executeCommand("cmd_beginLine", 1);
@@ -777,7 +777,7 @@ var Editor = Module("editor", {
         mappings.add([modes.TEXT_EDIT, modes.VISUAL],
             ["f"], "Move to a character on the current line after the cursor",
             function ({ arg, count }) {
-                let pos = editor.findCharForward(arg, Math.max(count, 1));
+                let pos = editor.findChar(arg, Math.max(count, 1));
                 if (pos >= 0)
                     editor.moveToPosition(pos, true, modes.main == modes.VISUAL);
             },
@@ -786,7 +786,7 @@ var Editor = Module("editor", {
         mappings.add([modes.TEXT_EDIT, modes.VISUAL],
             ["F"], "Move to a character on the current line before the cursor",
             function ({ arg, count }) {
-                let pos = editor.findCharBackward(arg, Math.max(count, 1));
+                let pos = editor.findChar(arg, Math.max(count, 1), true);
                 if (pos >= 0)
                     editor.moveToPosition(pos, false, modes.main == modes.VISUAL);
             },
@@ -795,7 +795,7 @@ var Editor = Module("editor", {
         mappings.add([modes.TEXT_EDIT, modes.VISUAL],
             ["t"], "Move before a character on the current line",
             function ({ arg, count }) {
-                let pos = editor.findCharForward(arg, Math.max(count, 1));
+                let pos = editor.findChar(arg, Math.max(count, 1));
                 if (pos >= 0)
                     editor.moveToPosition(pos - 1, true, modes.main == modes.VISUAL);
             },
@@ -804,7 +804,7 @@ var Editor = Module("editor", {
         mappings.add([modes.TEXT_EDIT, modes.VISUAL],
             ["T"], "Move before a character on the current line, backwards",
             function ({ arg, count }) {
-                let pos = editor.findCharBackward(arg, Math.max(count, 1));
+                let pos = editor.findChar(arg, Math.max(count, 1), true);
                 if (pos >= 0)
                     editor.moveToPosition(pos + 1, false, modes.main == modes.VISUAL);
             },
@@ -837,10 +837,10 @@ var Editor = Module("editor", {
         function bind() mappings.add.apply(mappings,
                                            [[modes.AUTOCOMPLETE]].concat(Array.slice(arguments)))
 
-        bind(["<Esc>"], "Return to INSERT mode",
+        bind(["<Esc>"], "Return to Insert mode",
              function () Events.PASS_THROUGH);
 
-        bind(["<C-[>"], "Return to INSERT mode",
+        bind(["<C-[>"], "Return to Insert mode",
              function () { events.feedkeys("<Esc>", { skipmap: true }); });
 
         bind(["<Up>"], "Select the previous autocomplete result",
@@ -859,7 +859,7 @@ var Editor = Module("editor", {
     options: function () {
         options.add(["editor"],
             "The external text editor",
-            "string", "gvim -f +<line> <file>", {
+            "string", 'gvim -f +<line> +"sil! call cursor(0, <column>)" <file>', {
                 format: function (obj, value) {
                     let args = commands.parseArgs(value || this.value, { argCount: "*", allowUnknownOptions: true })
                                        .map(util.compileMacro).filter(function (fmt) fmt.valid(obj))
@@ -868,7 +868,7 @@ var Editor = Module("editor", {
                         args.push(obj["file"]);
                     return args;
                 },
-                has: function (key) set.has(util.compileMacro(this.value).seen, key),
+                has: function (key) Set.has(util.compileMacro(this.value).seen, key),
                 validator: function (value) {
                     this.format({}, value);
                     return Object.keys(util.compileMacro(value).seen).every(function (k) ["column", "file", "line"].indexOf(k) >= 0);
index 0b279fd275757fc435ee6643507ea4ba4080d672..117d64b5891fcd9daf7969bd8d7f6bf238b2c92a 100644 (file)
@@ -16,22 +16,27 @@ var ProcessorStack = Class("ProcessorStack", {
         this.buffer = "";
         this.events = [];
 
+        events.dbg("STACK " + mode);
+
         let main = { __proto__: mode.main, params: mode.params };
-        let keyModes = array([mode.params.keyModes, main, mode.main.allBases]).flatten().compact();
+        this.modes = array([mode.params.keyModes, main, mode.main.allBases.slice(1)]).flatten().compact();
 
         if (builtin)
             hives = hives.filter(function (h) h.name === "builtin");
 
-        this.processors = keyModes.map(function (m) hives.map(function (h) KeyProcessor(m, h)))
-                                  .flatten().array;
+        this.processors = this.modes.map(function (m) hives.map(function (h) KeyProcessor(m, h)))
+                                    .flatten().array;
         this.ownsBuffer = !this.processors.some(function (p) p.main.ownsBuffer);
 
         for (let [i, input] in Iterator(this.processors)) {
             let params = input.main.params;
+
             if (params.preExecute)
                 input.preExecute = params.preExecute;
+
             if (params.postExecute)
                 input.postExecute = params.postExecute;
+
             if (params.onKeyPress && input.hive === mappings.builtin)
                 input.fallthrough = function fallthrough(events) {
                     return params.onKeyPress(events) === false ? Events.KILL : Events.PASS;
@@ -39,15 +44,17 @@ var ProcessorStack = Class("ProcessorStack", {
             }
 
         let hive = options.get("passkeys")[this.main.input ? "inputHive" : "commandHive"];
-        if (!builtin && hive.active
-                && (!dactyl.focusedElement || events.isContentNode(dactyl.focusedElement)))
+        if (!builtin && hive.active && (!dactyl.focusedElement || events.isContentNode(dactyl.focusedElement)))
             this.processors.unshift(KeyProcessor(modes.BASE, hive));
     },
 
+    passUnknown: Class.memoize(function () options.get("passunknown").getKey(this.modes)),
+
     notify: function () {
+        events.dbg("NOTIFY()");
         events.keyEvents = [];
         events.processor = null;
-        if (!this.execute(Events.KILL, true)) {
+        if (!this.execute(undefined, true)) {
             events.processor = this;
             events.keyEvents = this.keyEvents;
         }
@@ -60,64 +67,91 @@ var ProcessorStack = Class("ProcessorStack", {
                                 callable(result) ? result.toSource().substr(0, 50) : result),
 
     execute: function execute(result, force) {
+        events.dbg("EXECUTE(" + this._result(result) + ", " + force + ") events:" + this.events.length
+                   + " processors:" + this.processors.length + " actions:" + this.actions.length);
+
+        let processors = this.processors;
+        let length = 1;
 
-        if (force && this.actions.length)
-            this.processors.length = 0;
+        if (force)
+            this.processors = [];
 
         if (this.ownsBuffer)
             statusline.inputBuffer = this.processors.length ? this.buffer : "";
 
         if (!this.processors.some(function (p) !p.extended) && this.actions.length) {
-            if (this._actions.length == 0) {
-                dactyl.beep();
-                events.feedingKeys = false;
-            }
+            // We have matching actions and no processors other than
+            // those waiting on further arguments. Execute actions as
+            // long as they continue to return PASS.
 
             for (var action in values(this.actions)) {
                 while (callable(action)) {
+                    length = action.eventLength;
                     action = dactyl.trapErrors(action);
-                    events.dbg("ACTION RES: " + this._result(action));
+                    events.dbg("ACTION RES: " + length + " " + this._result(action));
                 }
                 if (action !== Events.PASS)
                     break;
             }
 
+            // Result is the result of the last action. Unless it's
+            // PASS, kill any remaining argument processors.
             result = action !== undefined ? action : Events.KILL;
             if (action !== Events.PASS)
                 this.processors.length = 0;
         }
         else if (this.processors.length) {
+            // We're still waiting on the longest matching processor.
+            // Kill the event, set a timeout to give up waiting if applicable.
+
             result = Events.KILL;
-            if (this.actions.length && options["timeout"])
+            if (options["timeout"] && (this.actions.length || events.hasNativeKey(this.events[0], this.main, this.passUnknown)))
                 this.timer = services.Timer(this, options["timeoutlen"], services.Timer.TYPE_ONE_SHOT);
         }
         else if (result !== Events.KILL && !this.actions.length &&
-                 (this.events.length > 1 ||
-                     this.processors.some(function (p) !p.main.passUnknown))) {
-            result = Events.KILL;
+                 !(this.events[0].isReplay || this.passUnknown
+                   || this.modes.some(function (m) m.passEvent(this), this.events[0]))) {
+            // No patching processors, this isn't a fake, pass-through
+            // event, we're not in pass-through mode, and we're not
+            // choosing to pass unknown keys. Kill the event and beep.
+
+            result = Events.ABORT;
             if (!Events.isEscape(this.events.slice(-1)[0]))
                 dactyl.beep();
             events.feedingKeys = false;
         }
         else if (result === undefined)
+            // No matching processors, we're willing to pass this event,
+            // and we don't have a default action from a processor. Just
+            // pass the event.
             result = Events.PASS;
 
-        events.dbg("RESULT: " + this._result(result));
-
-        if (result === Events.PASS || result === Events.PASS_THROUGH)
-            if (this.events[0].originalTarget)
-                this.events[0].originalTarget.dactylKeyPress = undefined;
+        events.dbg("RESULT: " + length + " " + this._result(result) + "\n\n");
 
         if (result !== Events.PASS || this.events.length > 1)
-            Events.kill(this.events[this.events.length - 1]);
+            if (result !== Events.ABORT || !this.events[0].isReplay)
+                Events.kill(this.events[this.events.length - 1]);
+
+        if (result === Events.PASS_THROUGH || result === Events.PASS && this.passUnknown)
+            events.passing = true;
+
+        if (result === Events.PASS_THROUGH && this.keyEvents.length)
+            events.dbg("PASS_THROUGH:\n\t" + this.keyEvents.map(function (e) [e.type, events.toString(e)]).join("\n\t"));
 
         if (result === Events.PASS_THROUGH)
             events.feedevents(null, this.keyEvents, { skipmap: true, isMacro: true, isReplay: true });
-        else if (result === Events.PASS || result === Events.ABORT) {
+        else {
             let list = this.events.filter(function (e) e.getPreventDefault() && !e.dactylDefaultPrevented);
-            if (list.length)
-                events.dbg("REFEED: " + list.map(events.closure.toString).join(""));
-            events.feedevents(null, list, { skipmap: true, isMacro: true, isReplay: true });
+
+            if (result === Events.PASS)
+                events.dbg("PASS THROUGH: " + list.slice(0, length).filter(function (e) e.type === "keypress").map(events.closure.toString));
+            if (list.length > length)
+                events.dbg("REFEED: " + list.slice(length).filter(function (e) e.type === "keypress").map(events.closure.toString));
+
+            if (result === Events.PASS)
+                events.feedevents(null, list.slice(0, length), { skipmap: true, isMacro: true, isReplay: true });
+            if (list.length > length && this.processors.length === 0)
+                events.feedevents(null, list.slice(length));
         }
 
         return this.processors.length === 0;
@@ -137,7 +171,7 @@ var ProcessorStack = Class("ProcessorStack", {
         let actions = [];
         let processors = [];
 
-        events.dbg("KEY: " + key + " skipmap: " + event.skipmap + " macro: " + event.isMacro + " replay: " + event.isReplay);
+        events.dbg("PROCESS(" + key + ") skipmap: " + event.skipmap + " macro: " + event.isMacro + " replay: " + event.isReplay);
 
         for (let [i, input] in Iterator(this.processors)) {
             let res = input.process(event);
@@ -149,8 +183,6 @@ var ProcessorStack = Class("ProcessorStack", {
             if (res === Events.KILL)
                 break;
 
-            buffer = buffer || input.inputBuffer;
-
             if (callable(res))
                 actions.push(res);
 
@@ -162,11 +194,15 @@ var ProcessorStack = Class("ProcessorStack", {
 
         events.dbg("RESULT: " + event.getPreventDefault() + " " + this._result(result));
         events.dbg("ACTIONS: " + actions.length + " " + this.actions.length);
-        events.dbg("PROCESSORS:", processors);
+        events.dbg("PROCESSORS:", processors, "\n");
 
         this._actions = actions;
         this.actions = actions.concat(this.actions);
 
+        for (let action in values(actions))
+            if (!("eventLength" in action))
+                action.eventLength = this.events.length;
+
         if (result === Events.KILL)
             this.actions = [];
         else if (!this.actions.length && !processors.length)
@@ -220,8 +256,10 @@ var KeyProcessor = Class("KeyProcessor", {
             function execute() {
                 if (self.preExecute)
                     self.preExecute.apply(self, args);
-                let res = map.execute.call(map, update({ self: self.main.params.mappingSelf || self.main.mappingSelf || map },
-                                                       args));
+
+                args.self = self.main.params.mappingSelf || self.main.mappingSelf || map;
+                let res = map.execute.call(map, args);
+
                 if (self.postExecute)
                     self.postExecute.apply(self, args);
                 return res;
@@ -285,6 +323,10 @@ var KeyArgProcessor = Class("KeyArgProcessor", KeyProcessor, {
     }
 });
 
+/**
+ * A hive used mainly for tracking event listeners and cleaning them up when a
+ * group is destroyed.
+ */
 var EventHive = Class("EventHive", Contexts.Hive, {
     init: function init(group) {
         init.supercall(this, group);
@@ -312,9 +354,12 @@ var EventHive = Class("EventHive", Contexts.Hive, {
             var [self, events] = [null, array.toObject([[event, callback]])];
         else {
             [self, events] = [event, event[callback || "events"]];
-            [,, capture, allowUntrusted] = arguments;
+            [, , capture, allowUntrusted] = arguments;
         }
 
+        if (Set.has(events, "input") && !Set.has(events, "dactyl-input"))
+            events["dactyl-input"] = events.input;
+
         for (let [event, callback] in Iterator(events)) {
             let args = [Cu.getWeakReference(target),
                         event,
@@ -354,7 +399,6 @@ var Events = Module("events", {
     dbg: function () {},
 
     init: function () {
-        const self = this;
         this.keyEvents = [];
 
         update(this, {
@@ -369,9 +413,7 @@ var Events = Module("events", {
         util.overlayWindow(window, {
             append: <e4x xmlns={XUL}>
                 <window id={document.documentElement.id}>
-                    <!--this notifies us also of focus events in the XUL
-                        from: http://developer.mozilla.org/en/docs/XUL_Tutorial:Updating_Commands !-->
-                    <!-- I don't think we really need this. ––Kris -->
+                    <!-- http://developer.mozilla.org/en/docs/XUL_Tutorial:Updating_Commands -->
                     <commandset id="dactyl-onfocus" commandupdater="true" events="focus"
                                 oncommandupdate="dactyl.modules.events.onFocusChange(event);"/>
                     <commandset id="dactyl-onselect" commandupdater="true" events="select"
@@ -411,11 +453,12 @@ var Events = Module("events", {
             subtract: ["Minus", "Subtract"]
         };
 
-        this._pseudoKeys = set(["count", "leader", "nop", "pass"]);
+        this._pseudoKeys = Set(["count", "leader", "nop", "pass"]);
 
         this._key_key = {};
         this._code_key = {};
         this._key_code = {};
+        this._code_nativeKey = {};
 
         for (let list in values(this._keyTable))
             for (let v in values(list)) {
@@ -425,6 +468,8 @@ var Events = Module("events", {
             }
 
         for (let [k, v] in Iterator(KeyEvent)) {
+            this._code_nativeKey[v] = k.substr(4);
+
             k = k.substr(7).toLowerCase();
             let names = [k.replace(/(^|_)(.)/g, function (m, n1, n2) n2.toUpperCase())
                           .replace(/^NUMPAD/, "k")];
@@ -449,29 +494,18 @@ var Events = Module("events", {
         }
 
         this._activeMenubar = false;
-        this.listen(window, this, "events", true);
-
-        dactyl.registerObserver("modeChange", function () {
-            delete self.processor;
-        });
+        this.listen(window, this, "events");
     },
 
     signals: {
         "browser.locationChange": function (webProgress, request, uri) {
             options.get("passkeys").flush();
+        },
+        "modes.change": function (oldMode, newMode) {
+            delete this.processor;
         }
     },
 
-    /**
-     * Adds an event listener for this session and removes it on
-     * dactyl shutdown.
-     *
-     * @param {Element} target The element on which to listen.
-     * @param {string} event The event to listen for.
-     * @param {function} callback The function to call when the event is received.
-     * @param {boolean} capture When true, listen during the capture
-     *      phase, otherwise during the bubbling phase.
-     */
     get listen() this.builtin.closure.listen,
     addSessionListener: deprecated("events.listen", { get: function addSessionListener() this.listen }),
 
@@ -531,7 +565,7 @@ var Events = Module("events", {
                 timeRecorded: Date.now()
             });
 
-            dactyl.log("Recorded " + this.recording + ": " + this._macroKeys.join(""), 9);
+            dactyl.log(_("macro.recorded", this.recording, this._macroKeys.join("")), 9);
             dactyl.echomsg(_("macro.recorded", this.recording));
         }
         this._recording = macro || null;
@@ -640,10 +674,15 @@ var Events = Module("events", {
             if (quiet)
                 commandline.quiet = quiet;
 
+            keys = mappings.expandLeader(keys);
+
             for (let [, evt_obj] in Iterator(events.fromString(keys))) {
                 let now = Date.now();
-                for (let type in values(["keydown", "keyup", "keypress"])) {
+                let key = events.toString(evt_obj);
+                for (let type in values(["keydown", "keypress", "keyup"])) {
                     let evt = update({}, evt_obj, { type: type });
+                    if (type !== "keypress" && !evt.keyCode)
+                        evt.keyCode = evt._keyCode || 0;
 
                     if (isObject(noremap))
                         update(evt, noremap);
@@ -654,9 +693,18 @@ var Events = Module("events", {
                     evt.dactylSavedEvents = savedEvents;
                     this.feedingEvent = evt;
 
-                    let event = events.create(document.commandDispatcher.focusedWindow.document, type, evt);
-                    if (!evt_obj.dactylString && !evt_obj.dactylShift && !mode)
-                        events.dispatch(dactyl.focusedElement || buffer.focusedFrame, event, evt);
+                    let doc = document.commandDispatcher.focusedWindow.document;
+                    let event = events.create(doc, type, evt);
+                    let target = dactyl.focusedElement
+                              || ["complete", "interactive"].indexOf(doc.readyState) >= 0 && doc.documentElement
+                              || doc.defaultView;
+
+                    if (target instanceof Element && !Events.isInputElement(target) &&
+                        ["<Return>", "<Space>"].indexOf(key) == -1)
+                        target = target.ownerDocument.documentElement;
+
+                    if (!evt_obj.dactylString && !mode)
+                        events.dispatch(target, event, evt);
                     else if (type === "keypress")
                         events.events.keypress.call(events, event);
                 }
@@ -689,8 +737,7 @@ var Events = Module("events", {
      * @param {Object} opts The pseudo-event. @optional
      */
     create: function (doc, type, opts) {
-        opts = opts || {};
-        var DEFAULTS = {
+        const DEFAULTS = {
             HTML: {
                 type: type, bubbles: true, cancelable: false
             },
@@ -713,22 +760,31 @@ var Events = Module("events", {
                 relatedTarget: null
             }
         };
-        const TYPES = {
-            change: "", input: "", submit: "",
-            click: "Mouse", mousedown: "Mouse", mouseup: "Mouse",
-            mouseover: "Mouse", mouseout: "Mouse",
-            keypress: "Key", keyup: "Key", keydown: "Key"
-        };
-        var t = TYPES[type];
+
+        opts = opts || {};
+
+        var t = this._create_types[type];
         var evt = doc.createEvent((t || "HTML") + "Events");
 
         let defaults = DEFAULTS[t || "HTML"];
-        evt["init" + t + "Event"].apply(evt, Object.keys(defaults)
-                                                   .map(function (k) k in opts ? opts[k]
-                                                                               : defaults[k]));
+
+        let args = Object.keys(defaults)
+                         .map(function (k) k in opts ? opts[k] : defaults[k]);
+
+        evt["init" + t + "Event"].apply(evt, args);
         return evt;
     },
 
+    _create_types: Class.memoize(function () iter(
+        {
+            Mouse: "click mousedown mouseout mouseover mouseup",
+            Key:   "keydown keypress keyup",
+            "":    "change dactyl-input input submit"
+        }
+    ).map(function ([k, v]) v.split(" ").map(function (v) [v, k]))
+     .flatten()
+     .toObject()),
+
     /**
      * Converts a user-input string of keys into a canonical
      * representation.
@@ -755,11 +811,11 @@ var Events = Module("events", {
         return events.fromString(keys, unknownOk).map(events.closure.toString).join("");
     },
 
-    iterKeys: function (keys) {
+    iterKeys: function (keys) iter(function () {
         let match, re = /<.*?>?>|[^<]/g;
         while (match = re.exec(keys))
             yield match[0];
-    },
+    }()),
 
     /**
      * Dispatches an event to an element as if it were a native event.
@@ -829,34 +885,40 @@ var Events = Module("events", {
         let out = [];
         for (let match in util.regexp.iterate(/<.*?>?>|[^<]|<(?!.*>)/g, input)) {
             let evt_str = match[0];
+
             let evt_obj = { ctrlKey: false, shiftKey: false, altKey: false, metaKey: false,
                             keyCode: 0, charCode: 0, type: "keypress" };
 
-            if (evt_str.length > 1) { // <.*?>
-                let [match, modifier, keyname] = evt_str.match(/^<((?:[CSMA]-)*)(.+?)>$/i) || [false, '', ''];
-                modifier = modifier.toUpperCase();
+            if (evt_str.length == 1) {
+                evt_obj.charCode = evt_str.charCodeAt(0);
+                evt_obj._keyCode = this._key_code[evt_str[0].toLowerCase()];
+                evt_obj.shiftKey = evt_str !== evt_str.toLowerCase();
+            }
+            else {
+                let [match, modifier, keyname] = evt_str.match(/^<((?:[*12CASM⌘]-)*)(.+?)>$/i) || [false, '', ''];
+                modifier = Set(modifier.toUpperCase());
                 keyname = keyname.toLowerCase();
                 evt_obj.dactylKeyname = keyname;
                 if (/^u[0-9a-f]+$/.test(keyname))
                     keyname = String.fromCharCode(parseInt(keyname.substr(1), 16));
 
                 if (keyname && (unknownOk || keyname.length == 1 || /mouse$/.test(keyname) ||
-                                this._key_code[keyname] || set.has(this._pseudoKeys, keyname))) {
-                    evt_obj.ctrlKey  = /C-/.test(modifier);
-                    evt_obj.altKey   = /A-/.test(modifier);
-                    evt_obj.shiftKey = /S-/.test(modifier);
-                    evt_obj.metaKey  = /M-/.test(modifier);
+                                this._key_code[keyname] || Set.has(this._pseudoKeys, keyname))) {
+                    evt_obj.globKey  ="*" in modifier;
+                    evt_obj.ctrlKey  ="C" in modifier;
+                    evt_obj.altKey   ="A" in modifier;
+                    evt_obj.shiftKey ="S" in modifier;
+                    evt_obj.metaKey  ="M" in modifier || "⌘" in modifier;
+                    evt_obj.dactylShift = evt_obj.shiftKey;
 
                     if (keyname.length == 1) { // normal characters
-                        if (evt_obj.shiftKey) {
+                        if (evt_obj.shiftKey)
                             keyname = keyname.toUpperCase();
-                            if (keyname == keyname.toLowerCase())
-                                evt_obj.dactylShift = true;
-                        }
 
                         evt_obj.charCode = keyname.charCodeAt(0);
+                        evt_obj._keyCode = this._key_code[keyname.toLowerCase()];
                     }
-                    else if (set.has(this._pseudoKeys, keyname)) {
+                    else if (Set.has(this._pseudoKeys, keyname)) {
                         evt_obj.dactylString = "<" + this._key_key[keyname] + ">";
                     }
                     else if (/mouse$/.test(keyname)) { // mouse events
@@ -875,8 +937,6 @@ var Events = Module("events", {
                     continue;
                 }
             }
-            else // a simple key (no <...>)
-                evt_obj.charCode = evt_str.charCodeAt(0);
 
             // TODO: make a list of characters that need keyCode and charCode somewhere
             if (evt_obj.keyCode == 32 || evt_obj.charCode == 32)
@@ -884,10 +944,10 @@ var Events = Module("events", {
             if (evt_obj.keyCode == 60 || evt_obj.charCode == 60)
                 evt_obj.charCode = evt_obj.keyCode = 60; // <lt>
 
-            evt_obj.modifiers = (evt_obj.ctrlKey && Ci.nsIDOMNSEvent.CONTROL_MASK)
-                              | (evt_obj.altKey && Ci.nsIDOMNSEvent.ALT_MASK)
+            evt_obj.modifiers = (evt_obj.ctrlKey  && Ci.nsIDOMNSEvent.CONTROL_MASK)
+                              | (evt_obj.altKey   && Ci.nsIDOMNSEvent.ALT_MASK)
                               | (evt_obj.shiftKey && Ci.nsIDOMNSEvent.SHIFT_MASK)
-                              | (evt_obj.metaKey && Ci.nsIDOMNSEvent.META_MASK);
+                              | (evt_obj.metaKey  && Ci.nsIDOMNSEvent.META_MASK);
 
             out.push(evt_obj);
         }
@@ -911,6 +971,8 @@ var Events = Module("events", {
         let key = null;
         let modifier = "";
 
+        if (event.globKey)
+            modifier += "*-";
         if (event.ctrlKey)
             modifier += "C-";
         if (event.altKey)
@@ -931,6 +993,7 @@ var Events = Module("events", {
                             key = key.toUpperCase();
                         else
                             key = key.toLowerCase();
+
                     if (!modifier && /^[a-z0-9]$/i.test(key))
                         return key;
                 }
@@ -975,7 +1038,7 @@ var Events = Module("events", {
                 else {
                     // a shift modifier is only allowed if the key is alphabetical and used in a C-A-M- mapping in the uppercase,
                     // or if the shift has been forced for a non-alphabetical character by the user while :map-ping
-                    if (key != key.toLowerCase() && (event.ctrlKey || event.altKey || event.metaKey) || event.dactylShift)
+                    if (key !== key.toLowerCase() && (event.ctrlKey || event.altKey || event.metaKey) || event.dactylShift)
                         modifier += "S-";
                     if (/^\s$/.test(key))
                         key = let (s = charCode.toString(16)) "U" + "0000".substr(4 - s.length) + s;
@@ -983,8 +1046,11 @@ var Events = Module("events", {
                         return key;
                 }
             }
-            if (key == null)
+            if (key == null) {
+                if (event.shiftKey)
+                    modifier += "S-";
                 key = this._key_key[event.dactylKeyname] || event.dactylKeyname;
+            }
             if (key == null)
                 return null;
         }
@@ -1015,8 +1081,71 @@ var Events = Module("events", {
     },
 
     /**
-     * Whether *key* is a key code defined to accept/execute input on the
-     * command line.
+     * Returns true if there's a known native key handler for the given
+     * event in the given mode.
+     *
+     * @param {Event} event A keypress event.
+     * @param {Modes.Mode} mode The main mode.
+     * @param {boolean} passUnknown Whether unknown keys should be passed.
+     */
+    hasNativeKey: function hasNativeKey(event, mode, passUnknown) {
+        if (mode.input && event.charCode && !(event.ctrlKey || event.metaKey))
+            return true;
+
+        if (!passUnknown)
+            return false;
+
+        var elements = document.getElementsByTagNameNS(XUL, "key");
+        var filters = [];
+
+        if (event.keyCode)
+            filters.push(["keycode", this._code_nativeKey[event.keyCode]]);
+        if (event.charCode) {
+            let key = String.fromCharCode(event.charCode);
+            filters.push(["key", key.toUpperCase()],
+                         ["key", key.toLowerCase()]);
+        }
+
+        let accel = util.OS.isMacOSX ? "metaKey" : "ctrlKey";
+
+        let access = iter({ 1: "shiftKey", 2: "ctrlKey", 4: "altKey", 8: "metaKey" })
+                        .filter(function ([k, v]) this & k, prefs.get("ui.key.chromeAccess"))
+                        .map(function ([k, v]) [v, true])
+                        .toObject();
+
+    outer:
+        for (let [, key] in iter(elements))
+            if (filters.some(function ([k, v]) key.getAttribute(k) == v)) {
+                let keys = { ctrlKey: false, altKey: false, shiftKey: false, metaKey: false };
+                let needed = { ctrlKey: event.ctrlKey, altKey: event.altKey, shiftKey: event.shiftKey, metaKey: event.metaKey };
+
+                let modifiers = (key.getAttribute("modifiers") || "").trim().split(/[\s,]+/);
+                for (let modifier in values(modifiers))
+                    switch (modifier) {
+                        case "access": update(keys, access); break;
+                        case "accel":  keys[accel] = true; break;
+                        default:       keys[modifier + "Key"] = true; break;
+                        case "any":
+                            if (!iter.some(keys, function ([k, v]) v && needed[k]))
+                                continue outer;
+                            for (let [k, v] in iter(keys)) {
+                                if (v)
+                                    needed[k] = false;
+                                keys[k] = false;
+                            }
+                            break;
+                    }
+
+                if (iter(needed).every(function ([k, v]) v == keys[k]))
+                    return key;
+            }
+
+        return false;
+    },
+
+    /**
+     * Returns true if *key* is a key code defined to accept/execute input on
+     * the command line.
      *
      * @param {string} key The key code to test.
      * @returns {boolean}
@@ -1024,14 +1153,21 @@ var Events = Module("events", {
     isAcceptKey: function (key) key == "<Return>" || key == "<C-j>" || key == "<C-m>",
 
     /**
-     * Whether *key* is a key code defined to reject/cancel input on the
-     * command line.
+     * Returns true if *key* is a key code defined to reject/cancel input on
+     * the command line.
      *
      * @param {string} key The key code to test.
      * @returns {boolean}
      */
     isCancelKey: function (key) key == "<Esc>" || key == "<C-[>" || key == "<C-c>",
 
+    /**
+     * Returns true if *node* belongs to the current content document or any
+     * sub-frame thereof.
+     *
+     * @param {Node|Document|Window} node The node to test.
+     * @returns {boolean}
+     */
     isContentNode: function isContentNode(node) {
         let win = (node.ownerDocument || node).defaultView || node;
         return XPCNativeWrapper(win).top == content;
@@ -1047,11 +1183,12 @@ var Events = Module("events", {
         if (buffer.loaded)
             return true;
 
-        dactyl.echo(_("macro.loadWaiting"), commandline.DISALLOW_MULTILINE);
+        dactyl.echo(_("macro.loadWaiting"), commandline.FORCE_SINGLELINE);
 
         const maxWaitTime = (time || 25);
-        util.waitFor(function () !events.feedingKeys || buffer.loaded, this, maxWaitTime * 1000, true);
+        util.waitFor(function () buffer.loaded, this, maxWaitTime * 1000, true);
 
+        dactyl.echo("", commandline.FORCE_SINGLELINE);
         if (!buffer.loaded)
             dactyl.echoerr(_("macro.loadFailed", maxWaitTime));
 
@@ -1115,14 +1252,19 @@ var Events = Module("events", {
 
             let win = (elem.ownerDocument || elem).defaultView || elem;
 
-            if (events.isContentNode(elem) && !buffer.focusAllowed(elem)
-                && !(services.focus.getLastFocusMethod(win) & 0x7000)
+            if (!(services.focus.getLastFocusMethod(win) & 0x7000)
+                && events.isContentNode(elem)
+                && !buffer.focusAllowed(elem)
                 && isinstance(elem, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, Window])) {
+
                 if (elem.frameElement)
                     dactyl.focusContent(true);
                 else if (!(elem instanceof Window) || Editor.getEditor(elem))
                     dactyl.focus(window);
             }
+
+            if (elem instanceof Element)
+                elem.dactylFocusAllowed = undefined;
         },
 
         /*
@@ -1178,7 +1320,7 @@ var Events = Module("events", {
                     elem.dactylKeyPress = elem.value;
                     util.timeout(function () {
                         if (elem.dactylKeyPress !== undefined && elem.value !== elem.dactylKeyPress)
-                            events.dispatch(elem, events.create(elem.ownerDocument, "input"));
+                            events.dispatch(elem, events.create(elem.ownerDocument, "dactyl-input"));
                         elem.dactylKeyPress = undefined;
                     });
                 }
@@ -1214,20 +1356,19 @@ var Events = Module("events", {
 
                     let ignore = false;
 
-                    if (modes.main == modes.PASS_THROUGH)
+                    if (mode.main == modes.PASS_THROUGH)
                         ignore = !Events.isEscape(key) && key != "<C-v>";
-                    else if (modes.main == modes.QUOTE) {
+                    else if (mode.main == modes.QUOTE) {
                         if (modes.getStack(1).main == modes.PASS_THROUGH) {
-                            mode.params.mainMode = modes.getStack(2).main;
+                            mode = Modes.StackElement(modes.getStack(2).main);
                             ignore = Events.isEscape(key);
                         }
                         else if (events.shouldPass(event))
-                            mode.params.mainMode = modes.getStack(1).main;
+                            mode = Modes.StackElement(modes.getStack(1).main);
                         else
                             ignore = true;
 
-                        if (ignore && !Events.isEscape(key))
-                            modes.pop();
+                        modes.pop();
                     }
                     else if (!event.isMacro && !event.noremap && events.shouldPass(event))
                         ignore = true;
@@ -1277,25 +1418,40 @@ var Events = Module("events", {
         },
 
         keyup: function onKeyUp(event) {
-            this.keyEvents.push(event);
+            if (event.type == "keydown")
+                this.keyEvents.push(event);
+            else if (!this.processor)
+                this.keyEvents = [];
 
-            let pass = this.feedingEvent && this.feedingEvent.isReplay ||
+            let pass = this.passing && !event.isMacro ||
+                    this.feedingEvent && this.feedingEvent.isReplay ||
                     event.isReplay ||
                     modes.main == modes.PASS_THROUGH ||
                     modes.main == modes.QUOTE
                         && modes.getStack(1).main !== modes.PASS_THROUGH
                         && !this.shouldPass(event) ||
-                    !modes.passThrough && this.shouldPass(event);
+                    !modes.passThrough && this.shouldPass(event) ||
+                    !this.processor && event.type === "keydown"
+                        && options.get("passunknown").getKey(modes.main.allBases)
+                        && let (key = events.toString(event))
+                            !modes.main.allBases.some(
+                                function (mode) mappings.hives.some(
+                                    function (hive) hive.get(mode, key) || hive.getCandidates(mode, key)));
+
+            if (event.type === "keydown")
+                this.passing = pass;
 
-            events.dbg("ON " + event.type.toUpperCase() + " " + this.toString(event) + " pass: " + pass);
+            events.dbg("ON " + event.type.toUpperCase() + " " + this.toString(event) + " pass: " + pass + " replay: " + event.isReplay + " macro: " + event.isMacro);
 
             // Prevents certain sites from transferring focus to an input box
             // before we get a chance to process our key bindings on the
             // "keypress" event.
-            if (!pass && !Events.isInputElement(dactyl.focusedElement))
+            if (!pass)
                 event.stopPropagation();
         },
         keydown: function onKeyDown(event) {
+            if (!event.isMacro)
+                this.passing = false;
             this.events.keyup.call(this, event);
         },
 
@@ -1303,8 +1459,11 @@ var Events = Module("events", {
             let elem = event.target;
             let win = elem.ownerDocument && elem.ownerDocument.defaultView || elem;
 
-            for (; win; win = win != win.parent && win.parent)
+            for (; win; win = win != win.parent && win.parent) {
+                for (; elem instanceof Element; elem = elem.parentNode)
+                    elem.dactylFocusAllowed = true;
                 win.document.dactylFocusAllowed = true;
+            }
         },
 
         popupshown: function onPopupShown(event) {
@@ -1322,8 +1481,7 @@ var Events = Module("events", {
                     modes.push(modes.MENU);
         },
 
-        popuphidden: function onPopupHidden() {
-            // gContextMenu is set to NULL, when a context menu is closed
+        popuphidden: function onPopupHidden(event) {
             if (window.gContextMenu == null && !this._activeMenubar)
                 modes.remove(modes.MENU, true);
             modes.remove(modes.AUTOCOMPLETE);
@@ -1406,11 +1564,15 @@ var Events = Module("events", {
             if (elem == null && urlbar && urlbar.inputField == this._lastFocus)
                 util.threadYield(true); // Why? --Kris
 
-            while (modes.main.ownsFocus && !modes.topOfStack.params.holdFocus)
+            while (modes.main.ownsFocus && modes.topOfStack.params.ownsFocus != elem
+                    && !modes.topOfStack.params.holdFocus)
                  modes.pop(null, { fromFocus: true });
         }
         finally {
             this._lastFocus = elem;
+
+            if (modes.main.ownsFocus)
+                modes.topOfStack.params.ownsFocus = elem;
         }
     },
 
@@ -1445,20 +1607,25 @@ var Events = Module("events", {
             key === "<Esc>" || key === "<C-[>",
 
     isHidden: function isHidden(elem, aggressive) {
-        for (let e = elem; e instanceof Element; e = e.parentNode) {
-            if (util.computedStyle(e).visibility !== "visible" ||
-                    aggressive && e.boxObject && e.boxObject.height === 0)
-                return true;
-        }
+        if (util.computedStyle(elem).visibility !== "visible")
+            return true;
+
+        if (aggressive)
+            for (let e = elem; e instanceof Element; e = e.parentNode) {
+                if (!/set$/.test(e.localName) && e.boxObject && e.boxObject.height === 0)
+                    return true;
+                else if (e.namespaceURI == XUL && e.localName === "panel")
+                    break;
+            }
         return false;
     },
 
     isInputElement: function isInputElement(elem) {
-        return elem instanceof HTMLInputElement && set.has(util.editableInputs, elem.type) ||
-               isinstance(elem, [HTMLIsIndexElement, HTMLEmbedElement,
+        return elem instanceof HTMLInputElement && Set.has(util.editableInputs, elem.type) ||
+               isinstance(elem, [HTMLEmbedElement,
                                  HTMLObjectElement, HTMLSelectElement,
                                  HTMLTextAreaElement,
-                                 Ci.nsIDOMXULTreeElement, Ci.nsIDOMXULTextBoxElement]) ||
+                                 Ci.nsIDOMXULTextBoxElement]) ||
                elem instanceof Window && Editor.getEditor(elem);
     },
 
@@ -1480,12 +1647,13 @@ var Events = Module("events", {
                 else
                     dactyl.echoerr(_("error.argumentRequired"));
             }, {
+                argCount: "?",
                 bang: true,
                 completer: function (context) completion.macro(context),
                 literal: 0
             });
 
-        commands.add(["macros"],
+        commands.add(["mac[ros]"],
             "List all macros",
             function (args) { completion.listCompleter("macro", args[0]); }, {
                 argCount: "?",
@@ -1501,7 +1669,7 @@ var Events = Module("events", {
     mappings: function () {
 
         mappings.add([modes.MAIN],
-            ["<A-b>"], "Process the next key as a builtin mapping",
+            ["<A-b>", "<pass-next-key-builtin>"], "Process the next key as a builtin mapping",
             function () {
                 events.processor = ProcessorStack(modes.getStack(0), mappings.hives.array, true);
                 events.processor.keyEvents = events.keyEvents;
@@ -1511,7 +1679,7 @@ var Events = Module("events", {
             ["<C-z>", "<pass-all-keys>"], "Temporarily ignore all " + config.appName + " key bindings",
             function () { modes.push(modes.PASS_THROUGH); });
 
-        mappings.add([modes.MAIN],
+        mappings.add([modes.MAIN, modes.PASS_THROUGH, modes.QUOTE],
             ["<C-v>", "<pass-next-key>"], "Pass through next key",
             function () {
                 if (modes.main == modes.QUOTE)
@@ -1519,6 +1687,10 @@ var Events = Module("events", {
                 modes.push(modes.QUOTE);
             });
 
+        mappings.add([modes.BASE],
+            ["<CapsLock>"], "Do Nothing",
+            function () {});
+
         mappings.add([modes.BASE],
             ["<Nop>"], "Do nothing",
             function () {});
@@ -1594,12 +1766,12 @@ var Events = Module("events", {
             "sitemap", "", {
                 flush: function flush() {
                     memoize(this, "filters", function () this.value.filter(function (f) f(buffer.documentURI)));
-                    memoize(this, "pass", function () set(array.flatten(this.filters.map(function (f) f.keys))));
+                    memoize(this, "pass", function () Set(array.flatten(this.filters.map(function (f) f.keys))));
                     memoize(this, "commandHive", function hive() Hive(this.filters, "command"));
                     memoize(this, "inputHive", function hive() Hive(this.filters, "input"));
                 },
 
-                has: function (key) set.has(this.pass, key) || set.has(this.commandHive.stack.mappings, key),
+                has: function (key) Set.has(this.pass, key) || Set.has(this.commandHive.stack.mappings, key),
 
                 get pass() (this.flush(), this.pass),
 
@@ -1611,7 +1783,7 @@ var Events = Module("events", {
                         filter.keys = events.fromString(vals[0]).map(events.closure.toString);
 
                         filter.commandKeys = vals.slice(1).map(events.closure.canonicalKeys);
-                        filter.inputKeys = filter.commandKeys.filter(/^<[ACM]-/);
+                        filter.inputKeys = filter.commandKeys.filter(bind("test", /^<[ACM]-/));
                     });
                     this.flush();
                     return values;
@@ -1620,7 +1792,14 @@ var Events = Module("events", {
 
         options.add(["strictfocus", "sf"],
             "Prevent scripts from focusing input elements without user intervention",
-            "boolean", true);
+            "sitemap", "'chrome:*':laissez-faire,*:moderate",
+            {
+                values: {
+                    despotic: "Only allow focus changes when explicitly requested by the user",
+                    moderate: "Allow focus changes after user-initiated focus change",
+                    "laissez-faire": "Always allow focus changes"
+                }
+            });
 
         options.add(["timeout", "tmo"],
             "Whether to execute a shorter key command after a timeout when a longer command exists",
index e70875bcaa805c4d254078f38b23edcf3a56be5c..a93f9a7a8f6fe96c6a299e3466f3a56acc4e6799 100644 (file)
 
         <xsl:if test="//dactyl:toc[1 and self::*]">
             <div dactyl:highlight="HelpTOC">
-                <h2>Contents</h2>
+                <h2><!--L-->Contents</h2>
                 <xsl:if test="@start">
                     <xsl:call-template name="toc">
                         <xsl:with-param name="level" select="number(@start)"/>
                 </xsl:when>
                 <xsl:when test="contains($type, 'list') or contains($type, 'map')">
                     <span dactyl:highlight="HelpString" delim=""><xsl:apply-templates mode="help-1"/></span>
-                    <xsl:if test=". = ''">(empty)</xsl:if>
+                    <xsl:if test=". = ''"><!--L-->(empty)</xsl:if>
                 </xsl:when>
                 <xsl:otherwise>
                     <span>
         <a>
             <xsl:choose>
                 <xsl:when test="not(@topic)"/>
-                <xsl:when test="regexp:match(@topic, '^([a-zA-Z]*:|[^/]*#|/)', '')">
+                <xsl:when test="regexp:match(@topic, '^([a-zA-Z]+:|[^/]*#|/)', '')">
                     <xsl:attribute name="href"><xsl:value-of select="@topic"/></xsl:attribute>
                 </xsl:when>
                 <xsl:otherwise>
                     <xsl:attribute name="href"><xsl:value-of select="concat('dactyl://help-tag/', @topic)"/></xsl:attribute>
                 </xsl:otherwise>
             </xsl:choose>
-            <xsl:if test="regexp:match(@topic, '^[a-zA-Z]*:', '')
+            <xsl:if test="regexp:match(@topic, '^[a-zA-Z]+:', '')
                     and not(starts-with(@topic, 'mailto:') or
                             starts-with(@topic, 'chrome:') or
                             starts-with(@topic, 'resource:') or
     <xsl:template match="dactyl:deprecated" mode="help-2">
         <p style="clear: both;">
             <xsl:apply-templates select="@*" mode="help-1"/>
-            <span dactyl:highlight="HelpWarning">Deprecated:</span>
+            <span dactyl:highlight="HelpWarning"><!--L-->Deprecated:</span>
             <xsl:text> </xsl:text>
             <xsl:apply-templates select="node()" mode="help-1"/>
         </p>
         <p style="clear: both;">
             <xsl:apply-templates select="@*" mode="help-1"/>
             <div style="clear: both;"/>
-            <span dactyl:highlight="HelpNote">Note:</span>
+            <span dactyl:highlight="HelpNote"><!--L-->Note:</span>
             <xsl:text> </xsl:text>
             <xsl:apply-templates select="node()" mode="help-1"/>
         </p>
         <p style="clear: both;">
             <xsl:apply-templates select="@*" mode="help-1"/>
             <div style="clear: both;"/>
-            <span dactyl:highlight="HelpWarning">Warning:</span>
+            <span dactyl:highlight="HelpWarning"><!--L-->Warning:</span>
             <xsl:text> </xsl:text>
             <xsl:apply-templates select="node()" mode="help-1"/>
         </p>
             <xsl:apply-templates select="@*|node()" mode="help-1"/>
         </div>
     </xsl:template>
-    <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:type"><xsl:attribute name="dactyl:highlight">HelpType</xsl:attribute></xsl:if>
+    <xsl:template match="dactyl:type" mode="help-2">
+        <a dactyl:highlight="HelpType">
+            <xsl:choose>
+                <xsl:when test="contains(ancestor::*/@document-tags, concat(' ', ., ' '))">
+                    <xsl:attribute name="href">#<xsl:value-of select="."/></xsl:attribute>
+                </xsl:when>
+                <xsl:when test="not(. = 'boolean' or . = 'number' or . = 'string')">
+                    <xsl:attribute name="href">dactyl://help-tag/<xsl:value-of select="."/></xsl:attribute>
+                </xsl:when>
+            </xsl:choose>
+            <xsl:apply-templates select="@*|node()" mode="help-1"/>
+        </a>
+    </xsl:template>
+    <xsl:template match="dactyl:str" mode="help-2">
+        <span dactyl:highlight="HelpString">
             <xsl:apply-templates select="@*|node()" mode="help-1"/>
         </span>
     </xsl:template>
index bcf431ce06175ffa7664bae2304ad5f26d819f64..a30ef295057f33655020e429bf58aacb61b918f0 100644 (file)
@@ -38,12 +38,13 @@ var HintSession = Class("HintSession", CommandMode, {
         this.open();
 
         this.top = opts.window || content;
-        this.top.addEventListener("resize", hints.resizeTimer.closure.tell, true);
-        this.top.addEventListener("dactyl-commandupdate", hints.resizeTimer.closure.tell, false, true);
+        this.top.addEventListener("resize", this.closure._onResize, true);
+        this.top.addEventListener("dactyl-commandupdate", this.closure._onResize, false, true);
 
         this.generate();
 
         this.show();
+        this.magic = true;
 
         if (this.validHints.length == 0) {
             dactyl.beep();
@@ -51,7 +52,7 @@ var HintSession = Class("HintSession", CommandMode, {
         }
         else if (this.validHints.length == 1 && !this.continue)
             this.process(false);
-        else // Ticket #185
+        else
             this.checkUnique();
     },
 
@@ -69,6 +70,15 @@ var HintSession = Class("HintSession", CommandMode, {
                 hints.setClass(this.imgSpan, this.valid ? val : null);
         },
 
+        get ambiguous() this.span.hasAttribute("ambiguous"),
+        set ambiguous(val) {
+            let meth = val ? "setAttribute" : "removeAttribute";
+            this.elem[meth]("ambiguous", "true");
+            this.span[meth]("ambiguous", "true");
+            if (this.imgSpan)
+                this.imgSpan[meth]("ambiguous", "true");
+        },
+
         get valid() this._valid,
         set valid(val) {
             this._valid = val,
@@ -76,7 +86,6 @@ var HintSession = Class("HintSession", CommandMode, {
             this.span.style.display = (val ? "" : "none");
             if (this.imgSpan)
                 this.imgSpan.style.display = (val ? "" : "none");
-
             this.active = this.active;
         }
     },
@@ -92,8 +101,8 @@ var HintSession = Class("HintSession", CommandMode, {
             if (hints.hintSession == this)
                 hints.hintSession = null;
             if (this.top) {
-                this.top.removeEventListener("resize", hints.resizeTimer.closure.tell, true);
-                this.top.removeEventListener("dactyl-commandupdate", hints.resizeTimer.closure.tell, true);
+                this.top.removeEventListener("resize", this.closure._onResize, true);
+                this.top.removeEventListener("dactyl-commandupdate", this.closure._onResize, true);
             }
 
             this.removeHints(0);
@@ -155,6 +164,21 @@ var HintSession = Class("HintSession", CommandMode, {
         return res.reverse().join("");
     },
 
+    /**
+     * The reverse of {@link #getHintString}. Given a hint string,
+     * returns its index.
+     *
+     * @param {string} str The hint's string.
+     * @returns {number} The hint's index.
+     */
+    getHintNumber: function getHintNumber(str) {
+        let base = this.hintKeys.length;
+        let res = 0;
+        for (let char in values(str))
+            res = res * base + this.hintKeys.indexOf(char);
+        return res;
+    },
+
     /**
      * Returns true if the given key string represents a
      * pseudo-hint-number.
@@ -255,6 +279,11 @@ var HintSession = Class("HintSession", CommandMode, {
 
         let doc = win.document;
 
+        memoize(doc, "dactylLabels", function ()
+            iter([l.getAttribute("for"), l]
+                 for (l in array.iterValues(doc.querySelectorAll("label[for]"))))
+             .toObject());
+
         let [offsetX, offsetY] = this.getContainerOffsets(doc);
 
         offsets = offsets || { left: 0, right: 0, top: 0, bottom: 0 };
@@ -263,11 +292,15 @@ var HintSession = Class("HintSession", CommandMode, {
 
         function isVisible(elem) {
             let rect = elem.getBoundingClientRect();
-            if (!rect || !rect.width || !rect.height ||
+            if (!rect ||
                 rect.top > offsets.bottom || rect.bottom < offsets.top ||
                 rect.left > offsets.right || rect.right < offsets.left)
                 return false;
 
+            if (!rect.width || !rect.height)
+                if (!Array.some(elem.childNodes, function (elem) elem instanceof Element && util.computedStyle(elem).float != "none" && isVisible(elem)))
+                    return false;
+
             let computedStyle = doc.defaultView.getComputedStyle(elem, null);
             if (computedStyle.visibility != "visible" || computedStyle.display == "none")
                 return false;
@@ -281,17 +314,24 @@ var HintSession = Class("HintSession", CommandMode, {
             util.computedStyle(fragment).height; // Force application of binding.
             let container = doc.getAnonymousElementByAttribute(fragment, "anonid", "hints") || fragment;
 
-            let baseNodeAbsolute = util.xmlToDom(<span highlight="Hint" style="display: none"/>, doc);
+            let baseNodeAbsolute = util.xmlToDom(<span highlight="Hint" style="display: none;"/>, doc);
 
             let mode = this.hintMode;
             let res = mode.matcher(doc);
 
             let start = this.pageHints.length;
-            for (let elem in res) {
-                let hint = { elem: elem, showText: false, __proto__: this.Hint };
-
-                if (!isVisible(elem) || mode.filter && !mode.filter(elem))
-                    continue;
+            let _hints = [];
+            for (let elem in res)
+                if (isVisible(elem) && (!mode.filter || mode.filter(elem)))
+                    _hints.push({
+                        elem: elem,
+                        rect: elem.getClientRects()[0] || elem.getBoundingClientRect(),
+                        showText: false,
+                        __proto__: this.Hint
+                    });
+
+            for (let hint in values(_hints)) {
+                let { elem, rect } = hint;
 
                 if (elem.hasAttributeNS(NS, "hint"))
                     [hint.text, hint.showText] = [elem.getAttributeNS(NS, "hint"), true];
@@ -302,17 +342,15 @@ var HintSession = Class("HintSession", CommandMode, {
                 else
                     hint.text = elem.textContent.toLowerCase();
 
-                hint.span = baseNodeAbsolute.cloneNode(true);
+                hint.span = baseNodeAbsolute.cloneNode(false);
 
-                let rect = elem.getClientRects()[0] || elem.getBoundingClientRect();
                 let leftPos = Math.max((rect.left + offsetX), offsetX);
                 let topPos  = Math.max((rect.top + offsetY), offsetY);
 
                 if (elem instanceof HTMLAreaElement)
                     [leftPos, topPos] = this.getAreaOffset(elem, leftPos, topPos);
 
-                hint.span.style.left = leftPos + "px";
-                hint.span.style.top =  topPos + "px";
+                hint.span.setAttribute("style", ["display: none; left:", leftPos, "px; top:", topPos, "px"].join(""));
                 container.appendChild(hint.span);
 
                 this.pageHints.push(hint);
@@ -358,7 +396,7 @@ var HintSession = Class("HintSession", CommandMode, {
     },
 
     /**
-     * Handle a hint mode event.
+     * Handle a hints mode event.
      *
      * @param {Event} event The event to handle.
      */
@@ -399,12 +437,17 @@ var HintSession = Class("HintSession", CommandMode, {
         return PASS;
     },
 
-    onResize: function () {
+    onResize: function onResize() {
         this.removeHints(0);
         this.generate(this.top);
         this.show();
     },
 
+    _onResize: function _onResize() {
+        if (this.magic)
+            hints.resizeTimer.tell();
+    },
+
     /**
      * Finish hinting.
      *
@@ -420,14 +463,8 @@ var HintSession = Class("HintSession", CommandMode, {
 
         // This "followhints" option is *too* confusing. For me, and
         // presumably for users, too. --Kris
-        if (options["followhints"] > 0) {
-            if (!followFirst)
-                return; // no return hit; don't examine uniqueness
-
-            // OK. return hit. But there's more than one hint, and
-            // there's no tab-selected current link. Do not follow in mode 2
-            dactyl.assert(options["followhints"] != 2 || this.validHints.length == 1 || this.hintNumber);
-        }
+        if (options["followhints"] > 0 && !followFirst)
+            return; // no return hit; don't examine uniqueness
 
         if (!followFirst) {
             let firstHref = this.validHints[0].elem.getAttribute("href") || null;
@@ -457,7 +494,7 @@ var HintSession = Class("HintSession", CommandMode, {
             else if (n)
                 hints.setClass(elem, n % 2);
             else
-                hints.setClass(elem, this.validHints[Math.max(0, this.hintNumber-1)].elem === elem);
+                hints.setClass(elem, this.validHints[Math.max(0, this.hintNumber - 1)].elem === elem);
 
             if (n--)
                 this.timeout(next, 50);
@@ -469,13 +506,14 @@ var HintSession = Class("HintSession", CommandMode, {
                 modes.push(modes.IGNORE, modes.HINTS);
         }
 
+        dactyl.trapErrors("action", this.hintMode,
+                          elem, elem.href || elem.src || "",
+                          this.extendedhintCount, top);
+
         this.timeout(function () {
-            if ((modes.extended & modes.HINTS) && !this.continue)
+            if (modes.main == modes.IGNORE && !this.continue)
                 modes.pop();
             commandline.lastEcho = null; // Hack.
-            dactyl.trapErrors("action", this.hintMode,
-                              elem, elem.href || elem.src || "",
-                              this.extendedhintCount, top);
             if (this.continue && this.top)
                 this.show();
         }, timeout);
@@ -491,10 +529,15 @@ var HintSession = Class("HintSession", CommandMode, {
      */
     removeHints: function _removeHints(timeout) {
         for (let { doc, start, end } in values(this.docs)) {
+            // Goddamn stupid fucking Gecko 1.x security manager bullshit.
+            try { delete doc.dactylLabels; } catch (e) { doc.dactylLabels = undefined; }
+
             for (let elem in util.evaluateXPath("//*[@dactyl:highlight='hints']", doc))
                 elem.parentNode.removeChild(elem);
-            for (let i in util.range(start, end + 1))
+            for (let i in util.range(start, end + 1)) {
+                this.pageHints[i].ambiguous = false;
                 this.pageHints[i].valid = false;
+            }
         }
         styles.system.remove("hint-positions");
 
@@ -561,19 +604,25 @@ var HintSession = Class("HintSession", CommandMode, {
                         text.push(UTF8(hint.elem.checked ? "⊙" : "○"));
                     else if (hint.elem.type === "checkbox")
                         text.push(UTF8(hint.elem.checked ? "☑" : "☐"));
-                if (hint.showText)
+                if (hint.showText && !/^\s*$/.test(hint.text))
                     text.push(hint.text.substr(0, 50));
 
                 hint.span.setAttribute("text", str + (text.length ? ": " + text.join(" ") : ""));
                 hint.span.setAttribute("number", str);
                 if (hint.imgSpan)
                     hint.imgSpan.setAttribute("number", str);
+
                 hint.active = activeHint == hintnum;
+
                 this.validHints.push(hint);
                 hintnum++;
             }
         }
 
+        let base = this.hintKeys.length;
+        for (let [i, hint] in Iterator(this.validHints))
+            hint.ambiguous = (i + 1) * base <= this.validHints.length;
+
         if (options["usermode"]) {
             let css = [];
             for (let hint in values(this.pageHints)) {
@@ -685,16 +734,15 @@ var Hints = Module("hints", {
             events.listen(appContent, "scroll", this.resizeTimer.closure.tell, false);
 
         const Mode = Hints.Mode;
-        Mode.defaultValue("tags", function () function () options.get("hinttags").matcher);
         Mode.prototype.__defineGetter__("matcher", function ()
-            options.get("extendedhinttags").getKey(this.name, this.tags()));
+            options.get("extendedhinttags").getKey(this.name, options.get("hinttags").matcher));
 
         this.modes = {};
         this.addMode(";", "Focus hint",                           buffer.closure.focusElement);
         this.addMode("?", "Show information for hint",            function (elem) buffer.showElementInfo(elem));
         this.addMode("s", "Save hint",                            function (elem) buffer.saveLink(elem, false));
         this.addMode("f", "Focus frame",                          function (elem) dactyl.focus(elem.ownerDocument.defaultView));
-        this.addMode("F", "Focus frame or pseudo-frame",          buffer.closure.focusElement, null, isScrollable);
+        this.addMode("F", "Focus frame or pseudo-frame",          buffer.closure.focusElement, isScrollable);
         this.addMode("o", "Follow hint",                          function (elem) buffer.followLink(elem, dactyl.CURRENT_TAB));
         this.addMode("t", "Follow hint in a new tab",             function (elem) buffer.followLink(elem, dactyl.NEW_TAB));
         this.addMode("b", "Follow hint in a background tab",      function (elem) buffer.followLink(elem, dactyl.NEW_BACKGROUND_TAB));
@@ -719,21 +767,35 @@ var Hints = Module("hints", {
     hintSession: Modes.boundProperty(),
 
     /**
-     * Creates a new hint mode.
+     * Creates a new hints mode.
      *
      * @param {string} mode The letter that identifies this mode.
      * @param {string} prompt The description to display to the user
      *     about this mode.
      * @param {function(Node)} action The function to be called with the
      *     element that matches.
-     * @param {function():string} tags The function that returns an
-     *     XPath expression to decide which elements can be hinted (the
-     *     default returns options["hinttags"]).
-     * @optional
+     * @param {function(Node):boolean} filter A function used to filter
+     *     the returned node set.
+     * @param {[string]} tags A value to add to the default
+     *     'extendedhinttags' value for this mode.
+     *     @optional
      */
-    addMode: function (mode, prompt, action, tags) {
-        arguments[1] = UTF8(prompt);
-        this.modes[mode] = Hints.Mode.apply(Hints.Mode, arguments);
+    addMode: function (mode, prompt, action, filter, tags) {
+        function toString(regexp) RegExp.prototype.toString.call(regexp);
+
+        if (tags != null) {
+            let eht = options.get("extendedhinttags");
+            let update = eht.isDefault;
+
+            let value = eht.parse(Option.quote(util.regexp.escape(mode)) + ":" + tags.map(Option.quote))[0];
+            eht.defaultValue = eht.defaultValue.filter(function (re) toString(re) != toString(value))
+                                  .concat(value);
+
+            if (update)
+                eht.reset();
+        }
+
+        this.modes[mode] = Hints.Mode(mode, UTF8(prompt), action, filter);
     },
 
     /**
@@ -762,7 +824,7 @@ var Hints = Module("hints", {
 
         let type = elem.type;
 
-        if (elem instanceof HTMLInputElement && set.has(util.editableInputs, elem.type))
+        if (elem instanceof HTMLInputElement && Set.has(util.editableInputs, elem.type))
             return [elem.value, false];
         else {
             for (let [, option] in Iterator(options["hintinputs"])) {
@@ -776,15 +838,14 @@ var Hints = Module("hints", {
                             return [elem.alt.toLowerCase(), true];
                     }
                     else if (elem.value && type != "password") {
-                        // radio's and checkboxes often use internal ids as values - maybe make this an option too...
+                        // radios and checkboxes often use internal ids as values - maybe make this an option too...
                         if (! ((type == "radio" || type == "checkbox") && !isNaN(elem.value)))
                             return [elem.value.toLowerCase(), (type == "radio" || type == "checkbox")];
                     }
                 }
                 else if (option == "label") {
                     if (elem.id) {
-                        // TODO: (possibly) do some guess work for label-like objects
-                        let label = util.evaluateXPath(["label[@for=" + elem.id.quote() + "]"], doc).snapshotItem(0);
+                        let label = elem.ownerDocument.dactylLabels[elem.id];
                         if (label)
                             return [label.textContent.toLowerCase(), true];
                     }
@@ -820,7 +881,7 @@ var Hints = Module("hints", {
          * returns true if each set of characters typed can be found, in any
          * order, in the link.
          *
-         * @param {string} hintString  The string typed by the user.
+         * @param {string} hintString The string typed by the user.
          * @returns {function(String):boolean} A function that takes the text
          *     of a hint and returns true if all the (space-delimited) sets of
          *     characters typed by the user can be found in it.
@@ -971,6 +1032,7 @@ var Hints = Module("hints", {
     open: function open(mode, opts) {
         this._extendedhintCount = opts.count;
         commandline.input(["Normal", mode], "", {
+            autocomplete: false,
             completer: function (context) {
                 context.compare = function () 0;
                 context.completions = [[k, v.prompt] for ([k, v] in Iterator(hints.modes))];
@@ -979,10 +1041,13 @@ var Hints = Module("hints", {
                 if (arg)
                     hints.show(arg, opts);
             },
-            onChange: function () {
+            onChange: function (arg) {
+                if (Object.keys(hints.modes).some(function (m) m != arg && m.indexOf(arg) == 0))
+                    return;
+
                 this.accepted = true;
                 modes.pop();
-            },
+            }
         });
     },
 
@@ -1112,14 +1177,14 @@ var Hints = Module("hints", {
         return -1;
     },
 
-    Mode: Struct("HintMode", "name", "prompt", "action", "tags", "filter")
+    Mode: Struct("HintMode", "name", "prompt", "action", "filter")
             .localize("prompt")
 }, {
     modes: function initModes() {
         initModes.require("commandline");
         modes.addMode("HINTS", {
             extended: true,
-            description: "Active when selecting elements in QuickHint or ExtendedHint mode",
+            description: "Active when selecting elements with hints",
             bases: [modes.COMMAND_LINE],
             input: true,
             ownsBuffer: true
@@ -1128,20 +1193,20 @@ var Hints = Module("hints", {
     mappings: function () {
         var myModes = config.browserModes.concat(modes.OUTPUT_MULTILINE);
         mappings.add(myModes, ["f"],
-            "Start QuickHint mode",
+            "Start Hints mode",
             function () { hints.show("o"); });
 
         mappings.add(myModes, ["F"],
-            "Start QuickHint mode, but open link in a new tab",
+            "Start Hints mode, but open link in a new tab",
             function () { hints.show(options.get("activate").has("links") ? "t" : "b"); });
 
         mappings.add(myModes, [";"],
-            "Start an extended hint mode",
+            "Start an extended hints mode",
             function ({ count }) { hints.open(";", { count: count }); },
             { count: true });
 
         mappings.add(myModes, ["g;"],
-            "Start an extended hint mode and stay there until <Esc> is pressed",
+            "Start an extended hints mode and stay there until <Esc> is pressed",
             function ({ count }) { hints.open("g;", { continue: true, count: count }); },
             { count: true });
 
@@ -1172,7 +1237,7 @@ var Hints = Module("hints", {
             "XPath or CSS selector strings of hintable elements for extended hint modes",
             "regexpmap", {
                 "[iI]": "img",
-                "[asOTivVWy]": ["a[href]", "area[href]", "img[src]", "iframe[src]"],
+                "[asOTvVWy]": ["a[href]", "area[href]", "img[src]", "iframe[src]"],
                 "[f]": "body",
                 "[F]": ["body", "code", "div", "html", "p", "pre", "span"],
                 "[S]": ["input:not([type=hidden])", "textarea", "button", "select"]
@@ -1180,7 +1245,7 @@ var Hints = Module("hints", {
             {
                 keepQuotes: true,
                 getKey: function (val, default_)
-                    let (res = array.nth(this.value, function (re) re.test(val), 0))
+                    let (res = array.nth(this.value, function (re) let (match = re.exec(val)) match && match[0] == val, 0))
                         res ? res.matcher : default_,
                 setter: function (vals) {
                     for (let value in values(vals))
@@ -1191,10 +1256,10 @@ var Hints = Module("hints", {
             });
 
         options.add(["hinttags", "ht"],
-            "XPath string of hintable elements activated by 'f' and 'F'",
-            "stringlist", "input:not([type=hidden]),a,area,iframe,textarea,button,select," +
+            "XPath or CSS selector strings of hintable elements for Hints mode",
+            "stringlist", "input:not([type=hidden]),a[href],area,iframe,textarea,button,select," +
                           "[onclick],[onmouseover],[onmousedown],[onmouseup],[oncommand]," +
-                          "[tabindex],[role=link],[role=button]",
+                          "[tabindex],[role=link],[role=button],[contenteditable=true]",
             {
                 setter: function (values) {
                     this.matcher = util.compileMatcher(values);
@@ -1213,8 +1278,8 @@ var Hints = Module("hints", {
                 },
                 validator: function (value) {
                     let values = events.fromString(value).map(events.closure.toString);
-                    return Option.validIf(array.uniq(values).length === values.length,
-                                            "Duplicate keys not allowed");
+                    return Option.validIf(array.uniq(values).length === values.length && values.length > 1,
+                                          _("option.hintkeys.duplicate"));
                 }
             });
 
@@ -1224,15 +1289,12 @@ var Hints = Module("hints", {
             { validator: function (value) value >= 0 });
 
         options.add(["followhints", "fh"],
-            // FIXME: this description isn't very clear but I can't think of a
-            // better one right now.
-            "Change the behavior of <Return> in hint mode",
+            "Define the conditions under which selected hints are followed",
             "number", 0,
             {
                 values: {
                     "0": "Follow the first hint as soon as typed text uniquely identifies it. Follow the selected hint on <Return>.",
                     "1": "Follow the selected hint on <Return>.",
-                    "2": "Follow the selected hint on <Return> only it's been <Tab>-selected."
                 }
             });
 
index cf985d99ade2d605ae481627e00a68909539ed5b..a4bb2838a2102317a65682a4a562f8df7404f146 100644 (file)
@@ -41,7 +41,7 @@ var History = Module("history", {
             return {
                 url: node.uri,
                 title: node.title,
-                icon: node.icon ? node.icon.spec : DEFAULT_FAVICON
+                icon: node.icon ? node.icon : DEFAULT_FAVICON
             };
         }).toArray();
         root.containerOpen = false; // close a container after using it!
@@ -230,8 +230,8 @@ var History = Module("history", {
                                 "uri",
                                 "visitcount"
                             ].map(function (order) [
-                                  ["+" + order.replace(" ", ""), "Sort by " + order + " ascending"],
-                                  ["-" + order.replace(" ", ""), "Sort by " + order + " descending"]
+                                  ["+" + order.replace(" ", ""), /*L*/"Sort by " + order + " ascending"],
+                                  ["-" + order.replace(" ", ""), /*L*/"Sort by " + order + " descending"]
                             ]));
                         }
                     }
index 668d8ce106a02fffd666ae93fb797ee425a6621b..155849e46fa73a6fb848beef87b9be44f2172129 100644 (file)
@@ -12,8 +12,8 @@
  * A class representing key mappings. Instances are created by the
  * {@link Mappings} class.
  *
- * @param {number[]} modes The modes in which this mapping is active.
- * @param {string[]} keys The key sequences which are bound to
+ * @param {[Modes.Mode]} modes The modes in which this mapping is active.
+ * @param {[string]} keys The key sequences which are bound to
  *     *action*.
  * @param {string} description A short one line description of the key mapping.
  * @param {function} action The action invoked by each key sequence.
  */
 var Map = Class("Map", {
     init: function (modes, keys, description, action, extraInfo) {
-        modes = Array.concat(modes);
-        if (!modes.every(util.identity))
-            throw TypeError("Invalid modes: " + modes);
-
         this.id = ++Map.id;
         this.modes = modes;
         this._keys = keys;
         this.action = action;
         this.description = description;
 
-        if (Object.freeze)
-            Object.freeze(this.modes);
+        Object.freeze(this.modes);
 
         if (extraInfo)
-            update(this, extraInfo);
+            this.update(extraInfo);
     },
 
     name: Class.memoize(function () this.names[0]),
 
-    /** @property {string[]} All of this mapping's names (key sequences). */
+    /** @property {[string]} All of this mapping's names (key sequences). */
     names: Class.memoize(function () this._keys.map(function (k) events.canonicalKeys(k))),
 
     get toStringParams() [this.modes.map(function (m) m.name), this.names.map(String.quote)],
@@ -58,7 +53,7 @@ var Map = Class("Map", {
 
     /** @property {number} A unique ID for this mapping. */
     id: null,
-    /** @property {number[]} All of the modes for which this mapping applies. */
+    /** @property {[Modes.Mode]} All of the modes for which this mapping applies. */
     modes: null,
     /** @property {function (number)} The function called to execute this mapping. */
     action: null,
@@ -96,7 +91,7 @@ var Map = Class("Map", {
      */
     hasName: function (name) this.keys.indexOf(name) >= 0,
 
-    get keys() this.names.map(mappings.expandLeader),
+    get keys() array.flatten(this.names.map(mappings.closure.expand)),
 
     /**
      * Execute the action for this mapping.
@@ -119,7 +114,7 @@ var Map = Class("Map", {
             mappings.repeat = repeat;
 
         if (this.executing)
-            util.dumpStack("Attempt to execute mapping recursively: " + args.command);
+            util.dumpStack(_("map.recursive", args.command));
         dactyl.assert(!this.executing, _("map.recursive", args.command));
 
         try {
@@ -159,10 +154,10 @@ var MapHive = Class("MapHive", Contexts.Hive, {
     },
 
     /**
-     * Adds a new default key mapping.
+     * Adds a new key mapping.
      *
-     * @param {number[]} modes The modes that this mapping applies to.
-     * @param {string[]} keys The key sequences which are bound to *action*.
+     * @param {[Modes.Mode]} modes The modes that this mapping applies to.
+     * @param {[string]} keys The key sequences which are bound to *action*.
      * @param {string} description A description of the key mapping.
      * @param {function} action The action invoked by each key sequence.
      * @param {Object} extra An optional extra configuration hash.
@@ -171,6 +166,10 @@ var MapHive = Class("MapHive", Contexts.Hive, {
     add: function (modes, keys, description, action, extra) {
         extra = extra || {};
 
+        modes = Array.concat(modes);
+        if (!modes.every(util.identity))
+            throw TypeError(/*L*/"Invalid modes: " + modes);
+
         let map = Map(modes, keys, description, action, extra);
         map.definedAt = contexts.getCaller(Components.stack.caller);
         map.hive = this;
@@ -307,13 +306,32 @@ var Mappings = Module("mappings", {
 
     get userHives() this.allHives.filter(function (h) h !== this.builtin, this),
 
-    expandLeader: function (keyString) keyString.replace(/<Leader>/i, options["mapleader"]),
+    expandLeader: function expandLeader(keyString) keyString.replace(/<Leader>/i, function () options["mapleader"]),
+
+    prefixes: Class.memoize(function () {
+        let list = Array.map("CASM", function (s) s + "-");
+
+        return iter(util.range(0, 1 << list.length)).map(function (mask)
+            list.filter(function (p, i) mask & (1 << i)).join("")).toArray().concat("*-");
+    }),
+
+    expand: function expand(keys) {
+        keys = keys.replace(/<leader>/i, options["mapleader"]);
+        if (!/<\*-/.test(keys))
+            return keys;
+
+        return util.debrace(events.iterKeys(keys).map(function (key) {
+            if (/^<\*-/.test(key))
+                return ["<", this.prefixes, key.slice(3)];
+            return key;
+        }, this).flatten().array).map(function (k) events.canonicalKeys(k));
+    },
 
     iterate: function (mode) {
         let seen = {};
         for (let hive in this.hives.iterValues())
             for (let map in array(hive.getStack(mode)).iterValues())
-                if (!set.add(seen, map.name))
+                if (!Set.add(seen, map.name))
                     yield map;
     },
 
@@ -330,8 +348,8 @@ var Mappings = Module("mappings", {
     /**
      * Adds a new default key mapping.
      *
-     * @param {number[]} modes The modes that this mapping applies to.
-     * @param {string[]} keys The key sequences which are bound to *action*.
+     * @param {[Modes.Mode]} modes The modes that this mapping applies to.
+     * @param {[string]} keys The key sequences which are bound to *action*.
      * @param {string} description A description of the key mapping.
      * @param {function} action The action invoked by each key sequence.
      * @param {Object} extra An optional extra configuration hash.
@@ -352,8 +370,8 @@ var Mappings = Module("mappings", {
     /**
      * Adds a new user-defined key mapping.
      *
-     * @param {number[]} modes The modes that this mapping applies to.
-     * @param {string[]} keys The key sequences which are bound to *action*.
+     * @param {[Modes.Mode]} modes The modes that this mapping applies to.
+     * @param {[string]} keys The key sequences which are bound to *action*.
      * @param {string} description A description of the key mapping.
      * @param {function} action The action invoked by each key sequence.
      * @param {Object} extra An optional extra configuration hash (see
@@ -369,7 +387,7 @@ var Mappings = Module("mappings", {
     /**
      * Returns the map from *mode* named *cmd*.
      *
-     * @param {number} mode The mode to search.
+     * @param {Modes.Mode} mode The mode to search.
      * @param {string} cmd The map name to match.
      * @returns {Map}
      */
@@ -379,9 +397,9 @@ var Mappings = Module("mappings", {
      * Returns an array of maps with names starting with but not equal to
      * *prefix*.
      *
-     * @param {number} mode The mode to search.
+     * @param {Modes.Mode} mode The mode to search.
      * @param {string} prefix The map prefix string to match.
-     * @returns {Map[]}
+     * @returns {[Map]}
      */
     getCandidates: function (mode, prefix)
         this.hives.map(function (h) h.getCandidates(mode, prefix))
@@ -389,15 +407,15 @@ var Mappings = Module("mappings", {
 
     /**
      * Lists all user-defined mappings matching *filter* for the specified
-     * *modes*.
+     * *modes* in the specified *hives*.
      *
-     * @param {number[]} modes An array of modes to search.
-     * @param {string} filter The filter string to match.
+     * @param {[Modes.Mode]} modes An array of modes to search.
+     * @param {string} filter The filter string to match. @optional
+     * @param {[MapHive]} hives The map hives to list. @optional
      */
     list: function (modes, filter, hives) {
-        let modeSign = "";
-        modes.filter(function (m)  m.char).forEach(function (m) { modeSign += m.char; });
-        modes.filter(function (m) !m.char).forEach(function (m) { modeSign += " " + m.name; });
+        let modeSign = modes.map(function (m) m.char || "").join("")
+                     + modes.map(function (m) !m.char ? " " + m.name : "").join("");
         modeSign = modeSign.replace(/^ /, "");
 
         hives = (hives || mappings.userHives).map(function (h) [h, maps(h)])
@@ -413,9 +431,9 @@ var Mappings = Module("mappings", {
         let list = <table>
                 <tr highlight="Title">
                     <td/>
-                    <td style="padding-right: 1em;">Mode</td>
-                    <td style="padding-right: 1em;">Command</td>
-                    <td style="padding-right: 1em;">Action</td>
+                    <td style="padding-right: 1em;">{_("title.Mode")}</td>
+                    <td style="padding-right: 1em;">{_("title.Command")}</td>
+                    <td style="padding-right: 1em;">{_("title.Action")}</td>
                 </tr>
                 <col style="min-width: 6em; padding-right: 1em;"/>
                 {
@@ -459,6 +477,11 @@ var Mappings = Module("mappings", {
                     return;
                 }
 
+                if (args[1] && !/^<nop>$/i.test(args[1])
+                    && !args["-count"] && !args["-ex"] && !args["-javascript"]
+                    && mapmodes.every(function (m) m.count))
+                    args[1] = "<count>" + args[1];
+
                 let [lhs, rhs] = args;
                 if (noremap)
                     args["-builtin"] = true;
@@ -474,7 +497,7 @@ var Mappings = Module("mappings", {
                         contexts.bindMacro(args, "-keys", function (params) params),
                         {
                             arg: args["-arg"],
-                            count: args["-count"],
+                            count: args["-count"] || !(args["-ex"] || args["-javascript"]),
                             noremap: args["-builtin"],
                             persist: !args["-nopersist"],
                             get rhs() String(this.action),
@@ -484,92 +507,93 @@ var Mappings = Module("mappings", {
             }
 
             const opts = {
-                    completer: function (context, args) {
-                        let mapmodes = array.uniq(args["-modes"].map(findMode));
-                        if (args.length == 1)
-                            return completion.userMapping(context, mapmodes, args["-group"]);
-                        if (args.length == 2) {
-                            if (args["-javascript"])
-                                return completion.javascript(context);
-                            if (args["-ex"])
-                                return completion.ex(context);
-                        }
+                identifier: "map",
+                completer: function (context, args) {
+                    let mapmodes = array.uniq(args["-modes"].map(findMode));
+                    if (args.length == 1)
+                        return completion.userMapping(context, mapmodes, args["-group"]);
+                    if (args.length == 2) {
+                        if (args["-javascript"])
+                            return completion.javascript(context);
+                        if (args["-ex"])
+                            return completion.ex(context);
+                    }
+                },
+                hereDoc: true,
+                literal: 1,
+                options: [
+                    {
+                        names: ["-arg", "-a"],
+                        description: "Accept an argument after the requisite key press",
                     },
-                    hereDoc: true,
-                    literal: 1,
-                    options: [
-                        {
-                            names: ["-arg", "-a"],
-                            description: "Accept an argument after the requisite key press",
-                        },
-                        {
-                            names: ["-builtin", "-b"],
-                            description: "Execute this mapping as if there were no user-defined mappings"
-                        },
-                        {
-                            names: ["-count", "-c"],
-                            description: "Accept a count before the requisite key press"
-                        },
-                        {
-                            names: ["-description", "-desc", "-d"],
-                            description: "A description of this mapping",
-                            default: "User-defined mapping",
-                            type: CommandOption.STRING
-                        },
-                        {
-                            names: ["-ex", "-e"],
-                            description: "Execute this mapping as an Ex command rather than keys"
-                        },
-                        contexts.GroupFlag("mappings"),
-                        {
-                            names: ["-javascript", "-js", "-j"],
-                            description: "Execute this mapping as JavaScript rather than keys"
-                        },
-                        update({}, modeFlag, {
-                            names: ["-modes", "-mode", "-m"],
-                            type: CommandOption.LIST,
-                            description: "Create this mapping in the given modes",
-                            default: mapmodes || ["n", "v"]
-                        }),
-                        {
-                            names: ["-nopersist", "-n"],
-                            description: "Do not save this mapping to an auto-generated RC file"
-                        },
-                        {
-                            names: ["-silent", "-s", "<silent>", "<Silent>"],
-                            description: "Do not echo any generated keys to the command line"
-                        }
-                    ],
-                    serialize: function () {
-                        return this.name != "map" ? [] :
-                            array(mappings.userHives)
-                                .filter(function (h) h.persist)
-                                .map(function (hive) [
-                                    {
-                                        command: "map",
-                                        options: array([
-                                            hive.name !== "user" && ["-group", hive.name],
-                                            ["-modes", uniqueModes(map.modes)],
-                                            ["-description", map.description],
-                                            map.silent && ["-silent"]])
-
-                                            .filter(util.identity)
-                                            .toObject(),
-                                        arguments: [map.names[0]],
-                                        literalArg: map.rhs,
-                                        ignoreDefaults: true
-                                    }
-                                    for (map in userMappings(hive))
-                                    if (map.persist)
-                                ])
-                                .flatten().array;
+                    {
+                        names: ["-builtin", "-b"],
+                        description: "Execute this mapping as if there were no user-defined mappings"
+                    },
+                    {
+                        names: ["-count", "-c"],
+                        description: "Accept a count before the requisite key press"
+                    },
+                    {
+                        names: ["-description", "-desc", "-d"],
+                        description: "A description of this mapping",
+                        default: /*L*/"User-defined mapping",
+                        type: CommandOption.STRING
+                    },
+                    {
+                        names: ["-ex", "-e"],
+                        description: "Execute this mapping as an Ex command rather than keys"
+                    },
+                    contexts.GroupFlag("mappings"),
+                    {
+                        names: ["-javascript", "-js", "-j"],
+                        description: "Execute this mapping as JavaScript rather than keys"
+                    },
+                    update({}, modeFlag, {
+                        names: ["-modes", "-mode", "-m"],
+                        type: CommandOption.LIST,
+                        description: "Create this mapping in the given modes",
+                        default: mapmodes || ["n", "v"]
+                    }),
+                    {
+                        names: ["-nopersist", "-n"],
+                        description: "Do not save this mapping to an auto-generated RC file"
+                    },
+                    {
+                        names: ["-silent", "-s", "<silent>", "<Silent>"],
+                        description: "Do not echo any generated keys to the command line"
                     }
+                ],
+                serialize: function () {
+                    return this.name != "map" ? [] :
+                        array(mappings.userHives)
+                            .filter(function (h) h.persist)
+                            .map(function (hive) [
+                                {
+                                    command: "map",
+                                    options: array([
+                                        hive.name !== "user" && ["-group", hive.name],
+                                        ["-modes", uniqueModes(map.modes)],
+                                        ["-description", map.description],
+                                        map.silent && ["-silent"]])
+
+                                        .filter(util.identity)
+                                        .toObject(),
+                                    arguments: [map.names[0]],
+                                    literalArg: map.rhs,
+                                    ignoreDefaults: true
+                                }
+                                for (map in userMappings(hive))
+                                if (map.persist)
+                            ])
+                            .flatten().array;
+                }
             };
             function userMappings(hive) {
                 let seen = {};
                 for (let stack in values(hive.stacks))
                     for (let map in array.iterValues(stack))
-                        if (!set.add(seen, map.id))
+                        if (!Set.add(seen, map.id))
                             yield map;
             }
 
@@ -606,6 +630,7 @@ var Mappings = Module("mappings", {
                         dactyl.echoerr(_("map.noSuch", args[0]));
                 },
                 {
+                    identifier: "unmap",
                     argCount: "?",
                     bang: true,
                     completer: opts.completer,
@@ -627,7 +652,7 @@ var Mappings = Module("mappings", {
             validator: function (value) Array.concat(value).every(findMode),
             completer: function () [[array.compact([mode.name.toLowerCase().replace(/_/g, "-"), mode.char]), mode.description]
                                     for (mode in values(modes.all))
-                                    if (!mode.hidden)],
+                                    if (!mode.hidden)]
         };
 
         function findMode(name) {
@@ -641,7 +666,9 @@ var Mappings = Module("mappings", {
         function uniqueModes(modes) {
             let chars = [k for ([k, v] in Iterator(modules.modes.modeChars))
                          if (v.every(function (mode) modes.indexOf(mode) >= 0))];
-            return array.uniq(modes.filter(function (m) chars.indexOf(m.char) < 0).concat(chars));
+            return array.uniq(modes.filter(function (m) chars.indexOf(m.char) < 0)
+                                   .map(function (m) m.name.toLowerCase())
+                                   .concat(chars));
         }
 
         commands.add(["feedkeys", "fk"],
@@ -664,14 +691,14 @@ var Mappings = Module("mappings", {
             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.name.toLowerCase()]);
+                               mode.displayName);
 
         let args = {
             getMode: function (args) findMode(args["-mode"]),
             iterate: function (args, mainOnly) {
                 let modes = [this.getMode(args)];
                 if (!mainOnly)
-                    modes = modes.concat(modes[0].bases);
+                    modes = modes[0].allBases;
 
                 let seen = {};
                 // Bloody hell. --Kris
@@ -679,7 +706,7 @@ var Mappings = Module("mappings", {
                     for (let hive in mappings.hives.iterValues())
                         for (let map in array.iterValues(hive.getStack(mode)))
                             for (let name in values(map.names))
-                                if (!set.add(seen, name)) {
+                                if (!Set.add(seen, name)) {
                                     yield {
                                         name: name,
                                         columns: [
@@ -725,8 +752,8 @@ var Mappings = Module("mappings", {
                                  tags = services["dactyl:"].HELP_TAGS)
                                     ({ helpTag: prefix + map.name, __proto__: map }
                                      for (map in self.iterate(args, true))
-                                     if (map.hive === mappings.builtin || set.has(tags, prefix + map.name))),
-                    description: "List all " + mode.name + " mode mappings along with their short descriptions",
+                                     if (map.hive === mappings.builtin || Set.has(tags, prefix + map.name))),
+                    description: "List all " + mode.displayName + " mode mappings along with their short descriptions",
                     index: mode.char + "-map",
                     getMode: function (args) mode,
                     options: []
index aedbfc06b88737379737083f47ec5fe11900f5c6..8e5632838a6270d2fbb675090b6e2c186f454424 100644 (file)
@@ -53,14 +53,14 @@ var Marks = Module("marks", {
         if (Marks.isURLMark(mark)) {
             let res = this._urlMarks.set(mark, { location: doc.documentURI, position: position, tab: Cu.getWeakReference(tabs.getTab()), timestamp: Date.now()*1000 });
             if (!silent)
-                dactyl.log("Adding URL mark: " + Marks.markToString(mark, res), 5);
+                dactyl.log(_("mark.addURL", Marks.markToString(mark, res)), 5);
         }
         else if (Marks.isLocalMark(mark)) {
             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);
+                dactyl.log(_("mark.addLocal", Marks.markToString(mark, marks[mark])), 5);
         }
     },
 
@@ -115,7 +115,7 @@ var Marks = Module("marks", {
                 tabs.select(tab);
                 let doc = tab.linkedBrowser.contentDocument;
                 if (doc.documentURI == mark.location) {
-                    dactyl.log("Jumping to URL mark: " + Marks.markToString(char, mark), 5);
+                    dactyl.log(_("mark.jumpingToURL", Marks.markToString(char, mark)), 5);
                     buffer.scrollToPercent(mark.position.x * 100, mark.position.y * 100);
                 }
                 else {
@@ -146,7 +146,7 @@ var Marks = Module("marks", {
             let mark = (this._localMarks.get(this.localURI) || {})[char];
             dactyl.assert(mark, _("mark.unset", char));
 
-            dactyl.log("Jumping to local mark: " + Marks.markToString(char, mark), 5);
+            dactyl.log(_("mark.jumpingToLocal", Marks.markToString(char, mark)), 5);
             buffer.scrollToPercent(mark.position.x * 100, mark.position.y * 100);
         }
         else
@@ -249,7 +249,7 @@ var Marks = Module("marks", {
             "Mark current location within the web page",
             function (args) {
                 let mark = args[0] || "";
-                dactyl.assert(mark.length <= 1, _("error.trailing"));
+                dactyl.assert(mark.length <= 1, _("error.trailingCharacters"));
                 dactyl.assert(/[a-zA-Z]/.test(mark), _("mark.invalid"));
 
                 marks.add(mark);
@@ -270,7 +270,6 @@ var Marks = Module("marks", {
         completion.mark = function mark(context) {
             function percent(i) Math.round(i * 100);
 
-            // FIXME: Line/Column doesn't make sense with %
             context.title = ["Mark", "HPos VPos File"];
             context.keys.description = function ([, m]) percent(m.position.x) + "% " + percent(m.position.y) + "% " + m.location;
             context.completions = marks.all;
index 55fa19fe6965ccb3e19200e68f7dfda52a9b0087..c498830903670defc855de8d4e1723fe4acc806b 100644 (file)
@@ -62,8 +62,7 @@ var Modes = Module("modes", {
             description: "Active when text is selected",
             display: function () "VISUAL" + (this._extended & modes.LINE ? " LINE" : ""),
             bases: [this.COMMAND],
-            ownsFocus: true,
-            passUnknown: false
+            ownsFocus: true
         }, {
             leave: function (stack, newMode) {
                 if (newMode.main == modes.CARET) {
@@ -77,7 +76,7 @@ var Modes = Module("modes", {
         });
         this.addMode("CARET", {
             description: "Active when the caret is visible in the web content",
-            bases: [this.COMMAND]
+            bases: [this.NORMAL]
         }, {
 
             get pref()    prefs.get("accessibility.browsewithcaret"),
@@ -99,13 +98,22 @@ var Modes = Module("modes", {
             char: "t",
             description: "Vim-like editing of input elements",
             bases: [this.COMMAND],
-            input: true,
-            ownsFocus: true,
-            passUnknown: false
+            ownsFocus: true
+        }, {
+            onKeyPress: function (eventList) {
+                const KILL = false, PASS = true;
+
+                // Hack, really.
+                if (eventList[0].charCode || /^<(?:.-)*(?:BS|Del|C-h|C-w|C-u|C-k)>$/.test(events.toString(eventList[0]))) {
+                    dactyl.beep();
+                    return KILL;
+                }
+                return PASS;
+            }
         });
         this.addMode("OUTPUT_MULTILINE", {
             description: "Active when the multi-line output buffer is open",
-            bases: [this.COMMAND],
+            bases: [this.NORMAL]
         });
 
         this.addMode("INPUT", {
@@ -199,6 +207,52 @@ var Modes = Module("modes", {
 
             }
         });
+
+        function makeTree() {
+            let list = modes.all.filter(function (m) m.name !== m.description);
+
+            let tree = {};
+
+            for (let mode in values(list))
+                tree[mode.name] = {};
+
+            for (let mode in values(list))
+                for (let base in values(mode.bases))
+                    tree[base.name][mode.name] = tree[mode.name];
+
+            let roots = iter([m.name, tree[m.name]] for (m in values(list)) if (!m.bases.length)).toObject();
+
+            default xml namespace = NS;
+            function rec(obj) {
+                XML.ignoreWhitespace = XML.prettyPrinting = false;
+
+                let res = <ul dactyl:highlight="Dense" xmlns:dactyl={NS}/>;
+                Object.keys(obj).sort().forEach(function (name) {
+                    let mode = modes.getMode(name);
+                    res.* += <li><em>{mode.displayName}</em>: {mode.description}{
+                        rec(obj[name])
+                    }</li>;
+                });
+
+                if (res.*.length())
+                    return res;
+                return <></>;
+            }
+
+            return rec(roots);
+        }
+
+        util.timeout(function () {
+            // Waits for the add-on to become available, if necessary.
+            config.addon;
+            config.version;
+
+            services["dactyl:"].pages["modes.dtd"] = services["dactyl:"].pages["modes.dtd"]();
+        });
+
+        services["dactyl:"].pages["modes.dtd"] = function () [null,
+            util.makeDTD(iter({ "modes.tree": makeTree() },
+                              config.dtd))];
     },
     cleanup: function cleanup() {
         modes.reset();
@@ -214,7 +268,7 @@ var Modes = Module("modes", {
 
         let val = this._modeMap[this._main].display();
         if (val)
-            return "-- " + val + " --" + macromode;;
+            return "-- " + val + " --" + macromode;
         return macromode;
     },
 
@@ -245,7 +299,7 @@ var Modes = Module("modes", {
         if (!mode.extended)
             this._mainModes.push(mode);
 
-        dactyl.triggerObserver("mode-add", mode);
+        dactyl.triggerObserver("modes.add", mode);
     },
 
     dumpStack: function dumpStack() {
@@ -270,9 +324,13 @@ var Modes = Module("modes", {
 
     // show the current mode string in the command line
     show: function show() {
+        if (!loaded.modes)
+            return;
+
         let msg = null;
-        if (options.get("showmode").getKey(this.main.name, true))
+        if (options.get("showmode").getKey(this.main.allBases, false))
             msg = this._getModeMessage();
+
         if (msg || loaded.commandline)
             commandline.widgets.mode = msg || null;
     },
@@ -354,23 +412,23 @@ var Modes = Module("modes", {
             prev = stack && stack.pop || this.topOfStack;
             if (push)
                 this._modeStack.push(push);
-
-            if (stack && stack.pop)
-                for (let { obj, prop, value, test } in values(this.topOfStack.saved))
-                    if (!test || !test(stack, prev))
-                        dactyl.trapErrors(function () { obj[prop] = value });
-
-            this.show();
         });
 
-        delayed.forEach(function ([fn, self]) dactyl.trapErrors(fn, self));
+        if (stack && stack.pop)
+            for (let { obj, prop, value, test } in values(this.topOfStack.saved))
+                if (!test || !test(stack, prev))
+                    dactyl.trapErrors(function () { obj[prop] = value });
+
+        this.show();
 
         if (this.topOfStack.params.enter && prev)
             dactyl.trapErrors("enter", this.topOfStack.params,
                               push ? { push: push } : stack || {},
                               prev);
 
-        dactyl.triggerObserver("modeChange", [oldMain, oldExtended], [this._main, this._extended], stack);
+        delayed.forEach(function ([fn, self]) dactyl.trapErrors(fn, self));
+
+        dactyl.triggerObserver("modes.change", [oldMain, oldExtended], [this._main, this._extended], stack);
         this.show();
     },
 
@@ -389,19 +447,22 @@ var Modes = Module("modes", {
         while (this._modeStack.length > 1 && this.main != mode) {
             let a = this._modeStack.pop();
             this.set(this.topOfStack.main, this.topOfStack.extended, this.topOfStack.params,
-                     update({ pop: a }, args || {}));
+                     update({ pop: a },
+                            args || {}));
 
             if (mode == null)
                 return;
         }
     },
 
-    replace: function replace(mode, oldMode) {
+    replace: function replace(mode, oldMode, args) {
         while (oldMode && this._modeStack.length > 1 && this.main != oldMode)
             this.pop();
 
         if (this._modeStack.length > 1)
-            this.set(mode, null, null, { push: this.topOfStack, pop: this._modeStack.pop() });
+            this.set(mode, null, null,
+                     update({ push: this.topOfStack, pop: this._modeStack.pop() },
+                            args || {}));
         this.push(mode);
     },
 
@@ -428,22 +489,27 @@ var Modes = Module("modes", {
         init: function init(name, options, params) {
             if (options.bases)
                 util.assert(options.bases.every(function (m) m instanceof this, this.constructor),
-                            "Invalid bases", true);
+                           _("mode.invalidBases"), true);
 
-            update(this, {
+            this.update({
                 id: 1 << Modes.Mode._id++,
+                description: name,
                 name: name,
                 params: params || {}
             }, options);
         },
 
+        description: Messages.Localized(""),
+
+        displayName: Class.memoize(function () this.name.split("_").map(util.capitalize).join(" ")),
+
         isinstance: function isinstance(obj)
-            this === obj || this.allBases.indexOf(obj) >= 0 || callable(obj) && this instanceof obj,
+            this.allBases.indexOf(obj) >= 0 || callable(obj) && this instanceof obj,
 
         allBases: Class.memoize(function () {
-            let seen = {}, res = [], queue = this.bases;
+            let seen = {}, res = [], queue = [this].concat(this.bases);
             for (let mode in array.iterValues(queue))
-                if (!set.add(seen, mode)) {
+                if (!Set.add(seen, mode)) {
                     res.push(mode);
                     queue.push.apply(queue, mode.bases);
                 }
@@ -454,8 +520,6 @@ var Modes = Module("modes", {
 
         get count() !this.insert,
 
-        get description() this._display,
-
         _display: Class.memoize(function _display() this.name.replace("_", " ", "g")),
 
         display: function display() this._display,
@@ -470,7 +534,9 @@ var Modes = Module("modes", {
 
         ownsFocus: Class.memoize(function ownsFocus() this.bases.length && this.bases.some(function (b) b.ownsFocus)),
 
-        get passUnknown() this.input,
+        passEvent: function passEvent(event) this.input && event.charCode && !(event.ctrlKey || event.altKey || event.metaKey),
+
+        passUnknown: Class.memoize(function () options.get("passunknown").getKey(this.name)),
 
         get mask() this,
 
@@ -521,7 +587,7 @@ var Modes = Module("modes", {
     mappings: function initMappings() {
         mappings.add([modes.BASE, modes.NORMAL],
             ["<Esc>", "<C-[>"],
-            "Return to NORMAL mode",
+            "Return to Normal mode",
             function () { modes.reset(); });
 
         mappings.add([modes.INPUT, modes.COMMAND, modes.PASS_THROUGH, modes.QUOTE],
@@ -529,22 +595,60 @@ var Modes = Module("modes", {
             "Return to the previous mode",
             function () { modes.pop(); });
 
+        mappings.add([modes.MENU], ["<C-c>"],
+            "Leave Menu mode",
+            function () { modes.pop(); });
+
         mappings.add([modes.MENU], ["<Esc>"],
             "Close the current popup",
-            function () {
-                modes.pop();
-                return Events.PASS_THROUGH;
-            });
+            function () { return Events.PASS_THROUGH; });
 
         mappings.add([modes.MENU], ["<C-[>"],
             "Close the current popup",
             function () { events.feedkeys("<Esc>"); });
     },
     options: function initOptions() {
+        let opts = {
+            completer: function completer(context, extra) {
+                if (extra.value && context.filter[0] == "!")
+                    context.advance(1);
+                return completer.superapply(this, arguments);
+            },
+
+            getKey: function getKey(val, default_) {
+                if (isArray(val))
+                    return (array.nth(this.value, function (v) val.some(function (m) m.name === v.mode), 0)
+                                || { result: default_ }).result;
+
+                return Set.has(this.valueMap, val) ? this.valueMap[val] : default_;
+            },
+
+            setter: function (vals) {
+                modes.all.forEach(function (m) { delete m.passUnknown });
+
+                vals = vals.map(function (v) update(new String(v.toLowerCase()), {
+                    mode: v.replace(/^!/, "").toUpperCase(),
+                    result: v[0] !== "!"
+                }));
+
+                this.valueMap = values(vals).map(function (v) [v.mode, v.result]).toObject();
+                return vals;
+            },
+
+            validator: function validator(vals) vals.map(function (v) v.replace(/^!/, "")).every(Set.has(this.values)),
+
+            get values() array.toObject([[m.name.toLowerCase(), m.description] for (m in values(modes._modes)) if (!m.hidden)])
+        };
+
+        options.add(["passunknown", "pu"],
+            "Pass through unknown keys in these modes",
+            "stringlist", "!text_edit,base",
+            opts);
+
         options.add(["showmode", "smd"],
             "Show the current mode in the command line when it matches this expression",
-            "regexplist", "!^normal$",
-            { regexpFlags: "i" });
+            "stringlist", "caret,output_multiline,!normal,base",
+            opts);
     },
     prefs: function initPrefs() {
         prefs.watch("accessibility.browsewithcaret", function () modes.onCaretChange.apply(modes, arguments));
index 9ace65a37bb3d99560e6768c9f65cb7b77cedd55..5c7a28c698e9c1a09cc859f194261cde1382f06d 100644 (file)
@@ -53,16 +53,16 @@ var MOW = Module("mow", {
                     <popupset>
                         <menupopup id="dactyl-contextmenu" highlight="Events" events="contextEvents">
                             <menuitem id="dactyl-context-copylink"
-                                      label="Copy Link Location" dactyl:group="link"
+                                      label={_("mow.contextMenu.copyLink")} dactyl:group="link"
                                       oncommand="goDoCommand('cmd_copyLink');"/>
                             <menuitem id="dactyl-context-copypath"
-                                      label="Copy File Path" dactyl:group="link path"
+                                      label={_("mow.contextMenu.copyPath")} dactyl:group="link path"
                                       oncommand="dactyl.clipboardWrite(document.popupNode.getAttribute('path'));"/>
                             <menuitem id="dactyl-context-copy"
-                                      label="Copy" dactyl:group="selection"
+                                      label={_("mow.contextMenu.copy")} dactyl:group="selection"
                                       command="cmd_copy"/>
                             <menuitem id="dactyl-context-selectall"
-                                      label="Select All"
+                                      label={_("mow.contextMenu.selectAll")}
                                       command="cmd_selectAll"/>
                         </menupopup>
                     </popupset>
@@ -131,8 +131,8 @@ var MOW = Module("mow", {
             catch (e) {
                 util.reportError(e);
                 util.dump(data);
-                this.messages.push(data);
             }
+            this.messages.push(data);
         }
         else {
             let style = isString(data) ? "pre" : "nowrap";
@@ -278,11 +278,11 @@ var MOW = Module("mow", {
         let elem = this.widget.contentDocument.documentElement;
 
         if (showHelp)
-            this.widgets.message = ["MoreMsg", "-- More -- SPACE/<C-f>/j: screen/page/line down, <C-b>/<C-u>/k: up, q: quit"];
+            this.widgets.message = ["MoreMsg", _("mow.moreHelp")];
         else if (force || (options["more"] && Buffer.isScrollable(elem, 1)))
-            this.widgets.message = ["MoreMsg", "-- More --"];
+            this.widgets.message = ["MoreMsg", _("mow.more")];
         else
-            this.widgets.message = ["Question", "Press ENTER or type command to continue"];
+            this.widgets.message = ["Question", _("mow.continue")];
     },
 
     visible: Modes.boundProperty({
@@ -311,7 +311,7 @@ var MOW = Module("mow", {
         mappings.add([modes.COMMAND],
             ["g<lt>"], "Redisplay the last command output",
             function () {
-                dactyl.assert(commandline.lastOutput, _("mow.noPreviousOutput"));
+                dactyl.assert(mow.lastOutput, _("mow.noPreviousOutput"));
                 mow.echo(mow.lastOutput, "Normal");
             });
 
@@ -393,3 +393,5 @@ var MOW = Module("mow", {
             "boolean", true);
     }
 });
+
+// vim: set fdm=marker sw=4 ts=4 et:
index c12e768109c98042b6795e1aaa7b77d18e4e7f95..74a34c7286f5c942a06f4a19e194cfcb80e4f8db 100644 (file)
@@ -35,7 +35,7 @@ var QuickMarks = Module("quickmarks", {
      * Returns a list of QuickMarks associates with the given URL.
      *
      * @param {string} url The url to find QuickMarks for.
-     * @return {[string]}
+     * @returns {[string]}
      */
     find: function find(url) {
         let res = [];
@@ -157,7 +157,7 @@ var QuickMarks = Module("quickmarks", {
                         context.fork("current", 0, this, function (context) {
                             context.title = ["Extra Completions"];
                             context.completions = [
-                                [quickmarks.get(args[0]), "Current Value"]
+                                [quickmarks.get(args[0]), _("option.currentValue")]
                             ].filter(function ([k, v]) k);
                         });
                         context.fork("url", 0, completion, "url");
index 54943425f6a650e99a916fd708911faba1dc896c..b5dbee70d417e97426443a35d5017ac4f563a8cb 100644 (file)
@@ -26,25 +26,28 @@ var StatusLine = Module("statusline", {
             util.overlayWindow(window, { append: <><statusbar id="status-bar" ordinal="0"/></> });
 
             highlight.loadCSS(util.compileMacro(<![CDATA[
-                !AddonBar;#addon-bar  {
+                !AddonBar;#addon-bar {
+                    /* The Add-on Bar */
                     padding-left: 0 !important;
                     min-height: 18px !important;
                     -moz-appearance: none !important;
                     <padding>
                 }
-                !AddonButton;#addon-bar xul|toolbarbutton  {
+                !AddonButton;#addon-bar xul|toolbarbutton {
+                    /* An Add-on Bar button */
                     -moz-appearance: none !important;
                     padding: 0 !important;
                     border-width: 0px !important;
                     min-width: 0 !important;
                     color: inherit !important;
                 }
-                AddonButton:not(:hover)  background: transparent !important;
+                AddonButton:not(:hover)  background: transparent;
             ]]>)({ padding: util.OS.isMacOSX ? "padding-right: 10px !important;" : "" }));
 
             if (document.getElementById("appmenu-button"))
                 highlight.loadCSS(<![CDATA[
-                    AppmenuButton       min-width: 0 !important; padding: 0 .5em !important;
+                    AppmenuButton       /* The app-menu button */ \
+                                        min-width: 0 !important; padding: 0 .5em !important;
                 ]]>);
         }
 
@@ -60,7 +63,7 @@ var StatusLine = Module("statusline", {
                         <hbox                                                 highlight="CmdLine StatusCmdLine" class="dactyl-container">
                             <label key="mode"          crop="end"                                               class="plain" collapsed="true"/>
                             <stack  id="dactyl-statusline-stack"     flex="1" highlight="CmdLine StatusCmdLine" class="dactyl-container">
-                                <textbox key="url"     crop="end"    flex="1"                                   class="plain dactyl-status-field-url" readonly="true"/>
+                                <textbox key="url"     crop="end"    flex="1" style="background: transparent;"  class="plain dactyl-status-field-url" readonly="true"/>
                                 <textbox key="message" crop="end"    flex="1" highlight="Normal StatusNormal"   class="plain"                         readonly="true"/>
                             </stack>
                         </hbox>
@@ -96,7 +99,7 @@ var StatusLine = Module("statusline", {
     signals: {
         "browser.locationChange": function (webProgress, request, uri) {
             let win = webProgress.DOMWindow;
-            this.status = buffer.uri;
+            this.status = uri;
             this.progress = uri && win && win.dactylProgress || "";
 
             // if this is not delayed we get the position of the old buffer
@@ -142,12 +145,14 @@ var StatusLine = Module("statusline", {
                 this.progress = 0;
             if (flags & Ci.nsIWebProgressListener.STATE_STOP) {
                 this.progress = "";
-                this.status = buffer.uri;
+                this.updateStatus();
             }
         },
         "browser.statusChange": function onStatusChange(webProgress, request, status, message) {
-            this.status = message || buffer.uri;
-        },
+            this.timeout(function () {
+                this.status = message || buffer.uri;
+            });
+        }
     },
 
     /**
@@ -175,7 +180,7 @@ var StatusLine = Module("statusline", {
 
     // update all fields of the statusline
     update: function update() {
-        this.status = buffer.uri;
+        this.updateStatus();
         this.inputBuffer = "";
         this.progress = "";
         this.updateTabCount();
@@ -183,34 +188,8 @@ var StatusLine = Module("statusline", {
         this.updateZoomLevel();
     },
 
-    // ripped from Firefox; modified
-    unsafeURI: util.regexp(String.replace(<![CDATA[
-            [
-                \s
-                // Invisible characters (bug 452979)
-                U001C U001D U001E U001F // file/group/record/unit separator
-                U00AD // Soft hyphen
-                UFEFF // BOM
-                U2060 // Word joiner
-                U2062 U2063  // Invisible times/separator
-                U200B UFFFC // Zero-width space/no-break space
-
-                // Bidi formatting characters. (RFC 3987 sections 3.2 and 4.1 paragraph 6)
-                U200E U200F U202A U202B U202C U202D U202E
-            ]
-        ]]>, /U/g, "\\u"),
-        "gx"),
-    losslessDecodeURI: function losslessDecodeURI(url) {
-        return url.split("%25").map(function (url) {
-                // Non-UTF-8 compliant URLs cause "malformed URI sequence" errors.
-                try {
-                    return decodeURI(url).replace(this.unsafeURI, encodeURIComponent);
-                }
-                catch (e) {
-                    return url;
-                }
-            }, this).join("%25");
-    },
+    unsafeURI: deprecated("util.unsafeURI", { get: function unsafeURI() util.unsafeURI }),
+    losslessDecodeURI: deprecated("util.losslessDecodeURI", function losslessDecodeURI() util.losslessDecodeURI.apply(util, arguments)),
 
     /**
      * Update the URL displayed in the status line. Also displays status
@@ -230,29 +209,26 @@ var StatusLine = Module("statusline", {
             if (uri.equals(buffer.uri) && window.getWebNavigation) {
                 let sh = window.getWebNavigation().sessionHistory;
                 if (sh && sh.index > 0)
-                    modified += "+";
-                if (sh && sh.index < sh.count - 1)
                     modified += "-";
-            }
-
-            if (modules.bookmarkcache) {
-                if (bookmarkcache.isBookmarked(uri))
+                if (sh && sh.index < sh.count - 1)
+                    modified += "+";
+                if (this.bookmarked)
                     modified += UTF8("❤");
             }
 
             if (modules.quickmarks)
                 modified += quickmarks.find(uri.spec.replace(/#.*/, "")).join("");
 
-            url = this.losslessDecodeURI(uri.spec);
+            url = util.losslessDecodeURI(uri.spec);
         }
 
         if (url == "about:blank") {
             if (!buffer.title)
-                url = "[No Name]";
+                url = _("buffer.noName");
         }
         else {
-            url = url.replace(RegExp("^dactyl://help/(\\S+)#(.*)"), function (m, n1, n2) n1 + " " + decodeURIComponent(n2) + " [Help]")
-                     .replace(RegExp("^dactyl://help/(\\S+)"), "$1 [Help]");
+            url = url.replace(RegExp("^dactyl://help/(\\S+)#(.*)"), function (m, n1, n2) n1 + " " + decodeURIComponent(n2) + " " + _("buffer.help"))
+                     .replace(RegExp("^dactyl://help/(\\S+)"), "$1 " + _("buffer.help"));
         }
 
         if (modified)
@@ -260,10 +236,20 @@ var StatusLine = Module("statusline", {
 
         this.widgets.url.value = url;
         this._status = uri;
+    },
 
+    get bookmarked() this._bookmarked,
+    set bookmarked(val) {
+        this._bookmarked = val;
+        if (this.status)
+            this.status = this.status;
     },
 
-    updateStatus: function updateStatus() { this.status = buffer.uri; },
+    updateStatus: function updateStatus() {
+        this.timeout(function () {
+            this.status = buffer.uri;
+        });
+    },
 
     updateUrl: deprecated("statusline.status", function updateUrl(url) { this.status = url || buffer.uri }),
 
@@ -291,15 +277,15 @@ var StatusLine = Module("statusline", {
      */
     progress: Modes.boundProperty({
         get: function progress() this._progress,
-        set: function  progress(progress) {
+        set: function progress(progress) {
             this._progress = progress || "";
 
-            if (typeof progress == "string")
+            if (isinstance(progress, ["String", _]))
                 this.widgets.progress.value = this._progress;
             else if (typeof progress == "number") {
                 let progressStr = "";
                 if (this._progress <= 0)
-                    progressStr = "[ Loading...         ]";
+                    progressStr = /*L*/"[ Loading...         ]";
                 else if (this._progress < 1) {
                     let progress = Math.round(this._progress * 20);
                     progressStr = "["
index f8584da2c4c3ef08aa3eebb852670c1975df0490..c6210807247f555928f78738202d1896178639cb 100644 (file)
@@ -19,6 +19,8 @@ var Tabs = Module("tabs", {
         this._lastBufferSwitchArgs = "";
         this._lastBufferSwitchSpecial = true;
 
+        this.xulTabs = document.getElementById("tabbrowser-tabs");
+
         // hide tabs initially to prevent flickering when 'stal' would hide them
         // on startup
         if (config.hasTabbrowser)
@@ -42,7 +44,17 @@ var Tabs = Module("tabs", {
             for (let { linkedBrowser: { contentDocument } } in values(this.allTabs))
                 if (contentDocument.readyState === "complete")
                     dactyl.initDocument(contentDocument);
-        });
+        }, 1000);
+
+        if (window.TabsInTitlebar)
+            window.TabsInTitlebar.allowedBy("dactyl", false);
+    },
+
+    signals: {
+        enter: function enter() {
+            if (window.TabsInTitlebar)
+                window.TabsInTitlebar.allowedBy("dactyl", true);
+        }
     },
 
     _alternates: Class.memoize(function () [config.tabbrowser.mCurrentTab, null]),
@@ -56,7 +68,7 @@ var Tabs = Module("tabs", {
         }
     },
 
-    updateTabCount: function () {
+    updateTabCount: function updateTabCount() {
         for (let [i, tab] in Iterator(this.visibleTabs)) {
             if (dactyl.has("Gecko2")) {
                 let node = function node(class_) document.getAnonymousElementByAttribute(tab, "class", class_);
@@ -80,7 +92,7 @@ var Tabs = Module("tabs", {
         statusline.updateTabCount(true);
     },
 
-    _onTabSelect: function () {
+    _onTabSelect: function _onTabSelect() {
         // TODO: is all of that necessary?
         //       I vote no. --Kris
         modes.reset();
@@ -103,7 +115,8 @@ var Tabs = Module("tabs", {
     get browsers() {
         let browsers = config.tabbrowser.browsers;
         for (let i = 0; i < browsers.length; i++)
-            yield [i, browsers[i]];
+            if (browsers[i] !== undefined) // Bug in Google's Page Speed add-on.
+                yield [i, browsers[i]];
     },
 
     /**
@@ -135,7 +148,7 @@ var Tabs = Module("tabs", {
     //        property doesn't. And the property is so oft-used that it's
     //        convenient. To the former question, because I think this is mainly
     //        useful for autocommands, and they get index arguments. --Kris
-    getLocalStore: function (tabIndex) {
+    getLocalStore: function getLocalStore(tabIndex) {
         let tab = this.getTab(tabIndex);
         if (!tab.dactylStore)
             tab.dactylStore = {};
@@ -149,10 +162,10 @@ var Tabs = Module("tabs", {
     get localStore() this.getLocalStore(),
 
     /**
-     * @property {Object[]} The array of closed tabs for the current
+     * @property {[Object]} The array of closed tabs for the current
      *     session.
      */
-    get closedTabs() services.json.decode(services.sessionStore.getClosedTabData(window)),
+    get closedTabs() JSON.parse(services.sessionStore.getClosedTabData(window)),
 
     /**
      * Clones the specified *tab* and append it to the tab list.
@@ -160,8 +173,8 @@ var Tabs = Module("tabs", {
      * @param {Object} tab The tab to clone.
      * @param {boolean} activate Whether to select the newly cloned tab.
      */
-    cloneTab: function (tab, activate) {
-        let newTab = config.tabbrowser.addTab();
+    cloneTab: function cloneTab(tab, activate) {
+        let newTab = config.tabbrowser.addTab("about:blank", { ownerTab: tab });
         Tabs.copyTab(newTab, tab);
 
         if (activate)
@@ -176,7 +189,7 @@ var Tabs = Module("tabs", {
      *
      * @param {Object} tab The tab to detach.
      */
-    detachTab: function (tab) {
+    detachTab: function detachTab(tab) {
         if (!tab)
             tab = config.tabbrowser.mTabContainer.selectedItem;
 
@@ -191,7 +204,7 @@ var Tabs = Module("tabs", {
      *     document.
      */
     // FIXME: Only called once...necessary?
-    getContentIndex: function (content) {
+    getContentIndex: function getContentIndex(content) {
         for (let [i, browser] in this.browsers) {
             if (browser.contentWindow == content || browser.contentDocument == content)
                 return i;
@@ -206,7 +219,7 @@ var Tabs = Module("tabs", {
      *
      * @returns {Window}
      */
-    getGroups: function () {
+    getGroups: function getGroups() {
         if ("_groups" in this)
             return this._groups;
 
@@ -225,13 +238,15 @@ var Tabs = Module("tabs", {
      * if *index* is not specified. This is a 0-based index.
      *
      * @param {number|Node} index The index of the tab required or the tab itself
+     * @param {boolean} visible If true, consider only visible tabs rather than
+     *      all tabs.
      * @returns {Object}
      */
-    getTab: function (index) {
+    getTab: function getTab(index, visible) {
         if (index instanceof Node)
             return index;
         if (index != null)
-            return config.tabbrowser.mTabs[index];
+            return this[visible ? "visibleTabs" : "allTabs"][index];
         return config.tabbrowser.mCurrentTab;
     },
 
@@ -243,7 +258,7 @@ var Tabs = Module("tabs", {
      * @param {boolean} visible Whether to consider only visible tabs.
      * @returns {number}
      */
-    index: function (tab, visible) {
+    index: function index(tab, visible) {
         let tabs = this[visible ? "visibleTabs" : "allTabs"];
         return tabs.indexOf(tab || config.tabbrowser.mCurrentTab);
     },
@@ -256,24 +271,25 @@ var Tabs = Module("tabs", {
      * - "-3" for the tab, which is 3 positions left of the current
      * - "$" for the last tab
      */
-    indexFromSpec: function (spec, wrap) {
+    indexFromSpec: function indexFromSpec(spec, wrap, offset) {
         if (spec instanceof Node)
             return this.allTabs.indexOf(spec);
 
         let tabs     = this.visibleTabs;
         let position = this.index(null, true);
 
-        if (spec == null || spec === "")
+        if (spec == null)
+            return -1;
+
+        if (spec === "")
             return position;
 
-        if (typeof spec === "number")
-            position = spec;
+        if (/^\d+$/.test(spec))
+            position = parseInt(spec, 10) + (offset || 0);
         else if (spec === "$")
             position = tabs.length - 1;
         else if (/^[+-]\d+$/.test(spec))
             position += parseInt(spec, 10);
-        else if (/^\d+$/.test(spec))
-            position = parseInt(spec, 10);
         else
             return -1;
 
@@ -290,7 +306,7 @@ var Tabs = Module("tabs", {
      *
      * @param {Object} tab The tab to keep.
      */
-    keepOnly: function (tab) {
+    keepOnly: function keepOnly(tab) {
         config.tabbrowser.removeAllTabsBut(tab);
     },
 
@@ -300,7 +316,7 @@ var Tabs = Module("tabs", {
      * @param {string} filter A filter matching a substring of the tab's
      *     document title or URL.
      */
-    list: function (filter) {
+    list: function list(filter) {
         completion.listCompleter("buffer", filter);
     },
 
@@ -312,8 +328,8 @@ var Tabs = Module("tabs", {
      * @param {boolean} wrap Whether an out of bounds *spec* causes the
      *     destination position to wrap around the start/end of the tab list.
      */
-    move: function (tab, spec, wrap) {
-        let index = tabs.indexFromSpec(spec, wrap);
+    move: function move(tab, spec, wrap) {
+        let index = tabs.indexFromSpec(spec, wrap, -1);
         config.tabbrowser.moveTabTo(tab, index);
     },
 
@@ -324,7 +340,7 @@ var Tabs = Module("tabs", {
      * @param {number} count How many tabs to remove.
      * @param {boolean} focusLeftTab Focus the tab to the left of the removed tab.
      */
-    remove: function (tab, count, focusLeftTab) {
+    remove: function remove(tab, count, focusLeftTab) {
         count = count || 1;
         let res = this.count > count;
 
@@ -355,7 +371,7 @@ var Tabs = Module("tabs", {
      * @param {boolean} bypassCache Whether to bypass the cache when
      *     reloading.
      */
-    reload: function (tab, bypassCache) {
+    reload: function reload(tab, bypassCache) {
         try {
             if (bypassCache) {
                 const flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
@@ -373,20 +389,15 @@ var Tabs = Module("tabs", {
      * @param {boolean} bypassCache Whether to bypass the cache when
      *     reloading.
      */
-    reloadAll: function (bypassCache) {
-        if (bypassCache) {
-            for (let i = 0; i < config.tabbrowser.mTabs.length; i++) {
-                try {
-                    this.reload(config.tabbrowser.mTabs[i], bypassCache);
-                }
-                catch (e) {
-                    // FIXME: can we do anything useful here without stopping the
-                    //        other tabs from reloading?
-                }
+    reloadAll: function reloadAll(bypassCache) {
+        this.visibleTabs.forEach(function (tab) {
+            try {
+                tabs.reload(tab, bypassCache);
             }
-        }
-        else
-            config.tabbrowser.reloadAllTabs();
+            catch (e) {
+                dactyl.reportError(e, true);
+            }
+        });
     },
 
     /**
@@ -396,7 +407,7 @@ var Tabs = Module("tabs", {
      * @param {boolean} wrap Whether an out of bounds *spec* causes the
      *     selection position to wrap around the start/end of the tab list.
      */
-    select: function (spec, wrap) {
+    select: function select(spec, wrap) {
         let index = tabs.indexFromSpec(spec, wrap);
         if (index == -1)
             dactyl.beep();
@@ -407,7 +418,7 @@ var Tabs = Module("tabs", {
     /**
      * Selects the alternate tab.
      */
-    selectAlternateTab: function () {
+    selectAlternateTab: function selectAlternateTab() {
         dactyl.assert(tabs.alternate != null && tabs.getTab() != tabs.alternate,
                       _("buffer.noAlternate"));
         tabs.select(tabs.alternate);
@@ -418,7 +429,7 @@ var Tabs = Module("tabs", {
      *
      * @param {Object} tab The tab to stop loading.
      */
-    stop: function (tab) {
+    stop: function stop(tab) {
         if (config.stop)
             config.stop(tab);
         else
@@ -428,7 +439,7 @@ var Tabs = Module("tabs", {
     /**
      * Stops loading all tabs.
      */
-    stopAll: function () {
+    stopAll: function stopAll() {
         for (let [, browser] in this.browsers)
             browser.stop();
     },
@@ -447,7 +458,7 @@ var Tabs = Module("tabs", {
      *
      */
     // FIXME: help!
-    switchTo: function (buffer, allowNonUnique, count, reverse) {
+    switchTo: function switchTo(buffer, allowNonUnique, count, reverse) {
         if (buffer != null) {
             // store this command, so it can be repeated with "B"
             this._lastBufferSwitchArgs = buffer;
@@ -502,7 +513,7 @@ var Tabs = Module("tabs", {
      * @param {Array(Object)} tabs The current and alternate tab.
      * @see tabs#alternate
      */
-    updateSelectionHistory: function (tabs) {
+    updateSelectionHistory: function updateSelectionHistory(tabs) {
         if (!tabs) {
             if (this.getTab() == this._alternates[0]
                 || this.alternate && this.allTabs.indexOf(this._alternates[0]) == -1
@@ -527,50 +538,17 @@ var Tabs = Module("tabs", {
         commands.add(["bd[elete]", "bw[ipeout]", "bun[load]", "tabc[lose]"],
             "Delete current buffer",
             function (args) {
-                let special = args.bang;
-                let count   = args.count;
-                let arg     = args[0] || "";
-
-                if (arg) {
-                    let removed = 0;
-                    let matches = arg.match(/^(\d+):?/);
-
-                    if (matches) {
-                        config.removeTab(tabs.getTab(parseInt(matches[1], 10) - 1));
-                        removed = 1;
-                    }
-                    else {
-                        let str = arg.toLowerCase();
-                        let browsers = config.tabbrowser.browsers;
-
-                        for (let i = browsers.length - 1; i >= 0; i--) {
-                            let host, title, uri = browsers[i].currentURI.spec;
-                            if (browsers[i].currentURI.schemeIs("about")) {
-                                host = "";
-                                title = "(Untitled)";
-                            }
-                            else {
-                                host = browsers[i].currentURI.host;
-                                title = browsers[i].contentTitle;
-                            }
-
-                            [host, title, uri] = [host, title, uri].map(String.toLowerCase);
-
-                            if (host.indexOf(str) >= 0 || uri == str ||
-                                (special && (title.indexOf(str) >= 0 || uri.indexOf(str) >= 0))) {
-                                config.removeTab(tabs.getTab(i));
-                                removed++;
-                            }
-                        }
-                    }
+                let removed = 0;
+                for (let tab in matchTabs(args, args.bang, true)) {
+                    config.removeTab(tab);
+                    removed++;
+                }
 
+                if (args[0])
                     if (removed > 0)
-                        dactyl.echomsg(_("buffer.fewer", removed, removed == 1 ? "" : "s"), 9);
+                        dactyl.echomsg(_("buffer.fewerTab" + (removed == 1 ? "" : "s"), removed), 9);
                     else
                         dactyl.echoerr(_("buffer.noMatching", arg));
-                }
-                else // just remove the current tab
-                    tabs.remove(tabs.getTab(), Math.max(count, 1), special);
             }, {
                 argCount: "?",
                 bang: true,
@@ -580,19 +558,87 @@ var Tabs = Module("tabs", {
                 privateData: true
             });
 
+        function matchTabs(args, substr, all) {
+            let filter = args[0];
+
+            if (!filter && args.count == null)
+                yield tabs.getTab();
+            else if (!filter)
+                yield dactyl.assert(tabs.getTab(args.count - 1));
+            else {
+                let matches = /^(\d+)(?:$|:)/.exec(filter);
+                if (matches)
+                    yield dactyl.assert(args.count == null &&
+                                        tabs.getTab(parseInt(matches[1], 10) - 1, !all));
+                else {
+                    let str = filter.toLowerCase();
+                    for (let tab in values(tabs[all ? "allTabs" : "visibleTabs"])) {
+                        let host, title;
+                        let browser = tab.linkedBrowser;
+                        let uri = browser.currentURI.spec;
+                        if (browser.currentURI.schemeIs("about")) {
+                            host = "";
+                            title = "(Untitled)";
+                        }
+                        else {
+                            host = browser.currentURI.host;
+                            title = browser.contentTitle;
+                        }
+
+                        [host, title, uri] = [host, title, uri].map(String.toLowerCase);
+
+                        if (host.indexOf(str) >= 0 || uri == str ||
+                            (substr && (title.indexOf(str) >= 0 || uri.indexOf(str) >= 0)))
+                            if (args.count == null || --args.count == 0)
+                                yield tab;
+                    }
+                }
+            }
+        }
+
+        commands.add(["pin[tab]"],
+            "Pin tab as an application tab",
+            function (args) {
+                for (let tab in matchTabs(args))
+                    config.browser[!args.bang || !tab.pinned ? "pinTab" : "unpinTab"](tab);
+            },
+            {
+                argCount: "?",
+                bang: true,
+                count: true,
+                completer: function (context, args) {
+                    if (!args.bang)
+                        context.filters.push(function ({ item }) !item.tab.pinned);
+                    completion.buffer(context);
+                }
+            });
+
+        commands.add(["unpin[tab]"],
+            "Unpin tab as an application tab",
+            function (args) {
+                for (let tab in matchTabs(args))
+                    config.browser.unpinTab(tab);
+            },
+            {
+                argCount: "?",
+                count: true,
+                completer: function (context, args) {
+                    context.filters.push(function ({ item }) item.tab.pinned);
+                    completion.buffer(context);
+                }
+            });
+
         commands.add(["keepa[lt]"],
             "Execute a command without changing the current alternate buffer",
             function (args) {
-                let alternate = tabs.alternate;
-
                 try {
-                    commands.execute(args[0] || "", null, true);
+                    dactyl.execute(args[0], null, true);
                 }
                 finally {
-                    tabs.updateSelectionHistory([tabs.getTab(), alternate]);
+                    tabs.updateSelectionHistory([tabs.getTab(), tabs.alternate]);
                 }
             }, {
-                argCount: "+",
+                argCount: "1",
                 completer: function (context) completion.ex(context),
                 literal: 0,
                 subCommand: 0
@@ -603,10 +649,10 @@ var Tabs = Module("tabs", {
             function (args) {
                 dactyl.withSavedValues(["forceNewTab"], function () {
                     this.forceNewTab = true;
-                    commands.execute(args[0] || "", null, true);
+                    dactyl.execute(args[0], null, true);
                 });
             }, {
-                argCount: "+",
+                argCount: "1",
                 completer: function (context) completion.ex(context),
                 literal: 0,
                 subCommand: 0
@@ -643,7 +689,7 @@ var Tabs = Module("tabs", {
                     if (/^\d+$/.test(arg))
                         tabs.select("-" + arg, true);
                     else
-                        dactyl.echoerr(_("error.trailing"));
+                        dactyl.echoerr(_("error.trailingCharacters"));
                 }
                 else if (count > 0)
                     tabs.select("-" + count, true);
@@ -666,7 +712,7 @@ var Tabs = Module("tabs", {
 
                     // count is ignored if an arg is specified, as per Vim
                     if (arg) {
-                        dactyl.assert(/^\d+$/.test(arg), _("error.trailing"));
+                        dactyl.assert(/^\d+$/.test(arg), _("error.trailingCharacters"));
                         index = arg - 1;
                     }
                     else
@@ -709,11 +755,9 @@ var Tabs = Module("tabs", {
                 });
 
             commands.add(["quita[ll]", "qa[ll]"],
-                "Quit " + config.appName,
-                function (args) { dactyl.quit(false, args.bang); }, {
-                    argCount: "0",
-                    bang: true
-                });
+                "Quit this " + config.appName + " window",
+                function (args) { window.close(); },
+                { argCount: "0" });
 
             commands.add(["reloada[ll]"],
                 "Reload all tab pages",
@@ -727,21 +771,24 @@ var Tabs = Module("tabs", {
                 function () { tabs.stopAll(); },
                 { argCount: "0" });
 
-            // TODO: add count support
+            // TODO: add count and bang multimatch support - unify with :buffer nonsense
             commands.add(["tabm[ove]"],
-                "Move the current tab after tab N",
+                "Move the current tab to the position of tab N",
                 function (args) {
                     let arg = args[0];
 
-                    // FIXME: tabmove! N should probably produce an error
-                    dactyl.assert(!arg || /^([+-]?\d+)$/.test(arg),
-                                  _("error.trailing"));
-
-                    // if not specified, move to after the last tab
-                    tabs.move(config.tabbrowser.mCurrentTab, arg || "$", args.bang);
+                    if (tabs.indexFromSpec(arg) == -1) {
+                        let tabs = [tab for (tab in matchTabs(args, true))];
+                        dactyl.assert(tabs.length, _("error.invalidArgument", arg));
+                        dactyl.assert(tabs.length == 1, _("buffer.multipleMatching", arg));
+                        arg = tabs[0];
+                    }
+                    tabs.move(tabs.getTab(), arg, args.bang);
                 }, {
-                    argCount: "?",
-                    bang: true
+                    argCount: "1",
+                    bang: true,
+                    completer: function (context, args) completion.buffer(context, true),
+                    literal: 0
                 });
 
             commands.add(["tabo[nly]"],
@@ -789,16 +836,23 @@ var Tabs = Module("tabs", {
             commands.add(["taba[ttach]"],
                 "Attach the current tab to another window",
                 function (args) {
-                    dactyl.assert(args.length <= 2 && !args.some(function (i) !/^\d+$/.test(i)),
-                                  _("error.trailing"));
+                    dactyl.assert(args.length <= 2 && !args.some(function (i) !/^\d+(?:$|:)/.test(i)),
+                                  _("error.trailingCharacters"));
 
-                    let [winIndex, tabIndex] = args.map(parseInt);
+                    let [winIndex, tabIndex] = args.map(function (arg) parseInt(arg));
                     let win = dactyl.windows[winIndex - 1];
 
                     dactyl.assert(win, _("window.noIndex", winIndex));
                     dactyl.assert(win != window, _("window.cantAttachSame"));
 
                     let browser = win.getBrowser();
+
+                    if (args[1]) {
+                        let tabList = browser.visibleTabs || browser.mTabs;
+                        let target  = dactyl.assert(tabList[tabIndex]);
+                        tabIndex = Array.indexOf(browser.mTabs, target) - 1;
+                    }
+
                     let dummy = browser.addTab("about:blank");
                     browser.stop();
                     // XXX: the implementation of DnD in tabbrowser.xml suggests
@@ -808,15 +862,26 @@ var Tabs = Module("tabs", {
 
                     let last = browser.mTabs.length - 1;
 
-                    browser.moveTabTo(dummy, Math.constrain(tabIndex || last, 0, last));
+                    if (args[1])
+                        browser.moveTabTo(dummy, tabIndex);
                     browser.selectedTab = dummy; // required
                     browser.swapBrowsersAndCloseOther(dummy, config.tabbrowser.mCurrentTab);
                 }, {
                     argCount: "+",
+                    literal: 1,
                     completer: function (context, args) {
-                        if (args.completeArg == 0) {
+                        switch (args.completeArg) {
+                        case 0:
                             context.filters.push(function ({ item }) item != window);
                             completion.window(context);
+                            break;
+                        case 1:
+                            let win = dactyl.windows[Number(args[0]) - 1];
+                            if (!win || !win.dactyl)
+                                context.message = _("Error", _("window.noIndex", winIndex));
+                            else
+                                win.dactyl.modules.commands.get("tabmove").completer(context);
+                            break;
                         }
                     }
                 });
@@ -977,11 +1042,16 @@ var Tabs = Module("tabs", {
                         tabs.tabStyle.enabled = true;
                     else {
                         prefs.safeSet("browser.tabs.autoHide", value === "multitab",
-                                      "See 'showtabline' option.");
+                                      _("option.showtabline.safeSet"));
                         tabs.tabStyle.enabled = false;
                     }
+
                     if (value !== "multitab" || !dactyl.has("Gecko2"))
-                        config.tabStrip.collapsed = false;
+                        if (tabs.xulTabs)
+                            tabs.xulTabs.visible = value !== "never";
+                        else
+                            config.tabStrip.collapsed = false;
+
                     if (config.tabbrowser.tabContainer._positionPinnedTabs)
                         config.tabbrowser.tabContainer._positionPinnedTabs();
                     return value;
@@ -1015,12 +1085,12 @@ var Tabs = Module("tabs", {
                     values: activateGroups,
                     has: Option.has.toggleAll,
                     setter: function (newValues) {
-                        let valueSet = set(newValues);
+                        let valueSet = Set(newValues);
                         for (let group in values(activateGroups))
                             if (group[2])
                                 prefs.safeSet("browser.tabs." + group[2],
                                               !(valueSet["all"] ^ valueSet[group[0]]),
-                                              "See the 'activate' option");
+                                              _("option.activate.safeSet"));
                         return newValues;
                     }
                 });
@@ -1031,11 +1101,8 @@ var Tabs = Module("tabs", {
                 {
                     values: {
                         "all": "All commands",
-                        "addons": ":addo[ns] command",
-                        "downloads": ":downl[oads] command",
                         "extoptions": ":exto[ptions] command",
                         "help": ":h[elp] command",
-                        "javascript": ":javascript! or :js! command",
                         "prefs": ":pref[erences]! or :prefs! command"
                     },
                     has: Option.has.toggleAll
@@ -1058,9 +1125,9 @@ var Tabs = Module("tabs", {
                         }
 
                         prefs.safeSet("browser.link.open_newwindow", open,
-                                      "See 'popups' option.");
+                                      _("option.popups.safeSet"));
                         prefs.safeSet("browser.link.open_newwindow.restriction", restriction,
-                                      "See 'popups' option.");
+                                      _("option.popups.safeSet"));
                         return values;
                     },
                     values: {
diff --git a/common/javascript.vim b/common/javascript.vim
new file mode 100644 (file)
index 0000000..c1bfe25
--- /dev/null
@@ -0,0 +1,343 @@
+" Vim syntax file
+" Language:     JavaScript
+" Maintainer:   Yi Zhao (ZHAOYI) <zzlinux AT hotmail DOT com>
+" Last Change:  May 17, 2007
+" Version:      0.7.5
+" Changes:      1, Get the vimdiff problem fixed finally.
+"                Matthew Gallant reported the problem and test the fix. ;)
+"               2, Follow the suggestioin from Ingo Karkat.
+"                The 'foldtext' and 'foldlevel' settings should only be
+"                changed if the file being edited is pure JavaScript,
+"                not if JavaScript syntax is embedded inside other syntaxes.
+"               3, Remove function FT_JavaScriptDoc().
+"                Since VIM do the better than me.
+"
+" TODO:
+"  - Add the HTML syntax inside the JSDoc
+
+if !exists("main_syntax")
+  if version < 600
+    syntax clear
+  elseif exists("b:current_syntax")
+    finish
+  endif
+  let main_syntax = 'javascript'
+endif
+
+"" Drop fold if it set but VIM doesn't support it.
+let b:javascript_fold='true'
+if version < 600    " Don't support the old version
+  unlet! b:javascript_fold
+endif
+
+syn include @xmlTop syntax/xml.vim
+unlet b:current_syntax
+
+syn include @cssTop syntax/css.vim
+unlet b:current_syntax
+
+"" dollar sigh is permittd anywhere in an identifier
+setlocal iskeyword+=$
+
+syntax sync fromstart
+syntax sync maxlines=200
+
+"" JavaScript comments
+syntax keyword javaScriptCommentTodo    TODO FIXME XXX TBD contained
+syntax region  javaScriptLineComment    start=+\/\/+ end="\v$|(\</?(css|e4x)\>)@=" keepend contains=javaScriptCommentTodo,@Spell
+syntax region  javaScriptLineComment    start=+^\s*\/\/+ skip=+\n\s*\/\/+ end="\v$|(\</?(css|e4x)\>)@=" keepend contains=javaScriptCommentTodo,@Spell fold
+syntax region  javaScriptCvsTag         start="\$\cid:" end="\$" oneline contained
+syntax region  javaScriptComment        start="/\*"  end="\v\*/|(\</?(css|e4x)\>)@=" contains=javaScriptCommentTodo,javaScriptCvsTag,@Spell fold
+
+"" JSDoc support start
+if !exists("javascript_ignore_javaScriptdoc")
+  syntax case ignore
+
+  "" syntax coloring for javadoc comments (HTML)
+  "syntax include @javaHtml <sfile>:p:h/html.vim
+  "unlet b:current_syntax
+
+  syntax region javaScriptDocComment    matchgroup=javaScriptComment start="/\*\*\s*$"  end="\*/" contains=javaScriptDocTags,javaScriptCommentTodo,javaScriptCvsTag,@javaScriptHtml,@Spell fold
+  syntax match  javaScriptDocTags       contained "@\(param\|argument\|requires\|exception\|throws\|type\|class\|extends\|see\|link\|member\|module\|method\|title\|namespace\|optional\|default\|base\|file\)\>" nextgroup=javaScriptDocParam,javaScriptDocSeeTag skipwhite
+  syntax match  javaScriptDocTags       contained "@\(beta\|deprecated\|description\|fileoverview\|author\|license\|version\|returns\=\|constructor\|private\|protected\|final\|ignore\|addon\|exec\)\>"
+  syntax match  javaScriptDocParam      contained "\%(#\|\w\|\.\|:\|\/\)\+"
+  syntax region javaScriptDocSeeTag     contained matchgroup=javaScriptDocSeeTag start="{" end="}" contains=javaScriptDocTags
+
+  syntax case match
+endif   "" JSDoc end
+
+syntax case match
+
+"" Syntax in the JavaScript code
+syntax match   javaScriptSpecial        "\\\d\d\d\|\\x\x\{2\}\|\\u\x\{4\}\|\\."
+syntax region  javaScriptStringD        start=+"+  skip=+\\\\\|\\$"+  end=+"+  contains=javaScriptSpecial,@htmlPreproc
+syntax region  javaScriptStringS        start=+'+  skip=+\\\\\|\\$'+  end=+'+  contains=javaScriptSpecial,@htmlPreproc
+syntax region  javaScriptRegexpString   start=+/\(\*\|/\)\@!+ skip=+\\\\\|\\/+ end=+/[gim]\{-,3}+ contains=javaScriptSpecial,@htmlPreproc oneline
+syntax match   javaScriptNumber         /\<-\=\d\+L\=\>\|\<0[xX]\x\+\>/
+syntax match   javaScriptFloat          /\<-\=\%(\d\+\.\d\+\|\d\+\.\|\.\d\+\)\%([eE][+-]\=\d\+\)\=\>/
+syntax match   javaScriptLabel          /\(?\s*\)\@<!\<\w\+\(\s*:\)\@=/
+
+"" JavaScript Prototype
+syntax keyword javaScriptPrototype      prototype
+
+"" Programm Keywords
+syntax keyword javaScriptSource         import export
+syntax keyword javaScriptType           const this var void yield arguments
+syntax keyword javaScriptOperator       delete new in instanceof let typeof
+syntax keyword javaScriptBoolean        true false
+syntax keyword javaScriptNull           null
+
+"" Statement Keywords
+syntax keyword javaScriptConditional    if else
+syntax keyword javaScriptRepeat         do while for
+syntax keyword javaScriptBranch         break continue switch case default return
+syntax keyword javaScriptStatement      try catch throw with finally
+
+syntax keyword javaScriptGlobalObjects  Array Boolean Date Function Infinity JavaArray JavaClass JavaObject JavaPackage Math Number NaN Object Packages RegExp String Undefined java netscape sun
+
+syntax keyword javaScriptExceptions     Error EvalError RangeError ReferenceError SyntaxError TypeError URIError
+
+syntax keyword javaScriptFutureKeys     abstract enum int short boolean export interface static byte extends long super char final native synchronized class float package throws goto private transient debugger implements protected volatile double import public
+
+"" DOM/HTML/CSS specified things
+
+  " DOM2 Objects
+  syntax keyword javaScriptGlobalObjects  DOMImplementation DocumentFragment Document Node NodeList NamedNodeMap CharacterData Attr Element Text Comment CDATASection DocumentType Notation Entity EntityReference ProcessingInstruction
+  syntax keyword javaScriptExceptions     DOMException
+
+  " DOM2 CONSTANT
+  syntax keyword javaScriptDomErrNo       INDEX_SIZE_ERR DOMSTRING_SIZE_ERR HIERARCHY_REQUEST_ERR WRONG_DOCUMENT_ERR INVALID_CHARACTER_ERR NO_DATA_ALLOWED_ERR NO_MODIFICATION_ALLOWED_ERR NOT_FOUND_ERR NOT_SUPPORTED_ERR INUSE_ATTRIBUTE_ERR INVALID_STATE_ERR SYNTAX_ERR INVALID_MODIFICATION_ERR NAMESPACE_ERR INVALID_ACCESS_ERR
+  syntax keyword javaScriptDomNodeConsts  ELEMENT_NODE ATTRIBUTE_NODE TEXT_NODE CDATA_SECTION_NODE ENTITY_REFERENCE_NODE ENTITY_NODE PROCESSING_INSTRUCTION_NODE COMMENT_NODE DOCUMENT_NODE DOCUMENT_TYPE_NODE DOCUMENT_FRAGMENT_NODE NOTATION_NODE
+
+  " HTML events and internal variables
+  syntax case ignore
+  syntax keyword javaScriptHtmlEvents     onblur onclick oncontextmenu ondblclick onfocus onkeydown onkeypress onkeyup onmousedown onmousemove onmouseout onmouseover onmouseup onresize
+  syntax case match
+
+" Follow stuff should be highligh within a special context
+" While it can't be handled with context depended with Regex based highlight
+" So, turn it off by default
+if exists("javascript_enable_domhtmlcss")
+
+    " DOM2 things
+    syntax match javaScriptDomElemAttrs     contained /\%(nodeName\|nodeValue\|nodeType\|parentNode\|childNodes\|firstChild\|lastChild\|previousSibling\|nextSibling\|attributes\|ownerDocument\|namespaceURI\|prefix\|localName\|tagName\)\>/
+    syntax match javaScriptDomElemFuncs     contained /\%(insertBefore\|replaceChild\|removeChild\|appendChild\|hasChildNodes\|cloneNode\|normalize\|isSupported\|hasAttributes\|getAttribute\|setAttribute\|removeAttribute\|getAttributeNode\|setAttributeNode\|removeAttributeNode\|getElementsByTagName\|getAttributeNS\|setAttributeNS\|removeAttributeNS\|getAttributeNodeNS\|setAttributeNodeNS\|getElementsByTagNameNS\|hasAttribute\|hasAttributeNS\)\>/ nextgroup=javaScriptParen skipwhite
+    " HTML things
+    syntax match javaScriptHtmlElemAttrs    contained /\%(className\|clientHeight\|clientLeft\|clientTop\|clientWidth\|dir\|id\|innerHTML\|lang\|length\|offsetHeight\|offsetLeft\|offsetParent\|offsetTop\|offsetWidth\|scrollHeight\|scrollLeft\|scrollTop\|scrollWidth\|style\|tabIndex\|title\)\>/
+    syntax match javaScriptHtmlElemFuncs    contained /\%(blur\|click\|focus\|scrollIntoView\|addEventListener\|dispatchEvent\|removeEventListener\|item\)\>/ nextgroup=javaScriptParen skipwhite
+
+    " CSS Styles in JavaScript
+    syntax keyword javaScriptCssStyles      contained color font fontFamily fontSize fontSizeAdjust fontStretch fontStyle fontVariant fontWeight letterSpacing lineBreak lineHeight quotes rubyAlign rubyOverhang rubyPosition
+    syntax keyword javaScriptCssStyles      contained textAlign textAlignLast textAutospace textDecoration textIndent textJustify textJustifyTrim textKashidaSpace textOverflowW6 textShadow textTransform textUnderlinePosition
+    syntax keyword javaScriptCssStyles      contained unicodeBidi whiteSpace wordBreak wordSpacing wordWrap writingMode
+    syntax keyword javaScriptCssStyles      contained bottom height left position right top width zIndex
+    syntax keyword javaScriptCssStyles      contained border borderBottom borderLeft borderRight borderTop borderBottomColor borderLeftColor borderTopColor borderBottomStyle borderLeftStyle borderRightStyle borderTopStyle borderBottomWidth borderLeftWidth borderRightWidth borderTopWidth borderColor borderStyle borderWidth borderCollapse borderSpacing captionSide emptyCells tableLayout
+    syntax keyword javaScriptCssStyles      contained margin marginBottom marginLeft marginRight marginTop outline outlineColor outlineStyle outlineWidth padding paddingBottom paddingLeft paddingRight paddingTop
+    syntax keyword javaScriptCssStyles      contained listStyle listStyleImage listStylePosition listStyleType
+    syntax keyword javaScriptCssStyles      contained background backgroundAttachment backgroundColor backgroundImage gackgroundPosition backgroundPositionX backgroundPositionY backgroundRepeat
+    syntax keyword javaScriptCssStyles      contained clear clip clipBottom clipLeft clipRight clipTop content counterIncrement counterReset cssFloat cursor direction display filter layoutGrid layoutGridChar layoutGridLine layoutGridMode layoutGridType
+    syntax keyword javaScriptCssStyles      contained marks maxHeight maxWidth minHeight minWidth opacity MozOpacity overflow overflowX overflowY verticalAlign visibility zoom cssText
+    syntax keyword javaScriptCssStyles      contained scrollbar3dLightColor scrollbarArrowColor scrollbarBaseColor scrollbarDarkShadowColor scrollbarFaceColor scrollbarHighlightColor scrollbarShadowColor scrollbarTrackColor
+
+    " Highlight ways
+    syntax match javaScriptDotNotation      "\." nextgroup=javaScriptPrototype,javaScriptDomElemAttrs,javaScriptDomElemFuncs,javaScriptHtmlElemAttrs,javaScriptHtmlElemFuncs
+    syntax match javaScriptDotNotation      "\.style\." nextgroup=javaScriptCssStyles
+
+endif "DOM/HTML/CSS
+
+"" end DOM/HTML/CSS specified things
+
+
+"" Code blocks
+syntax cluster javaScriptAll       contains=javaScriptComment,javaScriptLineComment,javaScriptDocComment,javaScriptStringD,javaScriptStringS,javaScriptRegexpString,javaScriptNumber,javaScriptFloat,javaScriptLabel,javaScriptSource,javaScriptType,javaScriptOperator,javaScriptBoolean,javaScriptNull,javaScriptFunction,javaScriptConditional,javaScriptRepeat,javaScriptBranch,javaScriptStatement,javaScriptGlobalObjects,javaScriptExceptions,javaScriptFutureKeys,javaScriptDomErrNo,javaScriptDomNodeConsts,javaScriptHtmlEvents,javaScriptDotNotation,javascriptE4X,javascriptCSS,javascriptCDATA
+syntax region  javaScriptBracket   matchgroup=javaScriptBracket transparent start="\[" end="\]" contains=@javaScriptAll,javaScriptParensErrB,javaScriptParensErrC,javaScriptBracket,javaScriptParen,javaScriptBlock,@htmlPreproc
+syntax region  javaScriptParen     matchgroup=javaScriptParen   transparent start="("  end=")"  contains=@javaScriptAll,javaScriptParensErrA,javaScriptParensErrC,javaScriptParen,javaScriptBracket,javaScriptBlock,@htmlPreproc
+syntax region  javaScriptBlock     matchgroup=javaScriptBlock   transparent start="{"  end="}"  contains=@javaScriptAll,javaScriptParensErrA,javaScriptParensErrB,javaScriptParen,javaScriptBracket,javaScriptBlock,@htmlPreproc
+
+syntax region  javascriptCDATA matchgroup=javascriptCDATA start="<\!\[CDATA\[" end="\]\]>" keepend contains=javascriptCSS
+syntax region  javascriptCSS   matchgroup=javascriptCSSDelimiter start="<css>" end="</css>" contains=@cssTop
+syntax region  javascriptE4X   matchgroup=javascriptE4XDelimiter start="<e4x>" end="</e4x>" contains=@xmlTop
+syntax region  javascriptE4X   matchgroup=javascriptE4XDelimiter start="<>" end="</>" contains=@xmlTop oneline
+
+"" catch errors caused by wrong parenthesis
+syntax match   javaScriptParensError    ")\|}\|\]"
+syntax match   javaScriptParensErrA     contained "\]"
+syntax match   javaScriptParensErrB     contained ")"
+syntax match   javaScriptParensErrC     contained "}"
+
+if main_syntax == "javascript"
+  syntax sync ccomment javaScriptComment
+endif
+
+"" Fold control
+if exists("b:javascript_fold")
+    syntax match   javaScriptFunction       /\<function\>/ nextgroup=javaScriptFuncName skipwhite
+    syntax match   javaScriptOpAssign       /=\@<!=/ nextgroup=javaScriptFuncBlock skipwhite skipempty
+    syntax region  javaScriptFuncName       contained matchgroup=javaScriptFuncName start=/\%(\$\|\w\)*\s*(/ end=/)/ contains=javaScriptLineComment,javaScriptComment nextgroup=javaScriptFuncBlock skipwhite skipempty
+    syntax region  javaScriptFuncBlock      contained matchgroup=javaScriptFuncBlock start="{" end="}" contains=@javaScriptAll,javaScriptParensErrA,javaScriptParensErrB,javaScriptParen,javaScriptBracket,javaScriptBlock fold
+
+    if &l:filetype=='javascript' && !&diff
+      " Fold setting
+      " Redefine the foldtext (to show a JS function outline) and foldlevel
+      " only if the entire buffer is JavaScript, but not if JavaScript syntax
+      " is embedded in another syntax (e.g. HTML).
+      setlocal foldmethod=syntax
+      setlocal foldlevel=4
+    endif
+else
+    syntax keyword javaScriptFunction       function
+    setlocal foldmethod<
+    setlocal foldlevel<
+endif
+
+" Define the default highlighting.
+" For version 5.7 and earlier: only when not done already
+" For version 5.8 and later: only when an item doesn't have highlighting yet
+if version >= 508 || !exists("did_javascript_syn_inits")
+  if version < 508
+    let did_javascript_syn_inits = 1
+    command -nargs=+ HiLink hi link <args>
+  else
+    command -nargs=+ HiLink hi def link <args>
+  endif
+  HiLink javascriptCDATA                String
+  HiLink javaScriptComment              Comment
+  HiLink javaScriptLineComment          Comment
+  HiLink javaScriptDocComment           Comment
+  HiLink javaScriptCommentTodo          Todo
+  HiLink javaScriptCvsTag               Function
+  HiLink javaScriptDocTags              Special
+  HiLink javaScriptDocSeeTag            Function
+  HiLink javaScriptDocParam             Function
+  HiLink javaScriptStringS              String
+  HiLink javaScriptStringD              String
+  HiLink javaScriptRegexpString         String
+  HiLink javaScriptCharacter            Character
+  HiLink javaScriptPrototype            Type
+  HiLink javaScriptConditional          Conditional
+  HiLink javaScriptBranch               Conditional
+  HiLink javaScriptRepeat               Repeat
+  HiLink javaScriptStatement            Statement
+  HiLink javaScriptFunction             Function
+  HiLink javaScriptError                Error
+  HiLink javaScriptParensError          Error
+  HiLink javaScriptParensErrA           Error
+  HiLink javaScriptParensErrB           Error
+  HiLink javaScriptParensErrC           Error
+  HiLink javaScriptOperator             Operator
+  HiLink javaScriptType                 Type
+  HiLink javaScriptNull                 Type
+  HiLink javaScriptNumber               Number
+  HiLink javaScriptFloat                Number
+  HiLink javaScriptBoolean              Boolean
+  HiLink javaScriptLabel                Label
+  HiLink javaScriptSpecial              Special
+  HiLink javaScriptSource               Special
+  HiLink javaScriptGlobalObjects        Special
+  HiLink javaScriptExceptions           Special
+
+  HiLink javaScriptDomErrNo             Constant
+  HiLink javaScriptDomNodeConsts        Constant
+  HiLink javaScriptDomElemAttrs         Label
+  HiLink javaScriptDomElemFuncs         PreProc
+
+  HiLink javaScriptHtmlEvents           Special
+  HiLink javaScriptHtmlElemAttrs        Label
+  HiLink javaScriptHtmlElemFuncs        PreProc
+
+  HiLink javaScriptCssStyles            Label
+
+  delcommand HiLink
+endif
+
+" Define the htmlJavaScript for HTML syntax html.vim
+"syntax clear htmlJavaScript
+"syntax clear javaScriptExpression
+syntax cluster  htmlJavaScript contains=@javaScriptAll,javaScriptBracket,javaScriptParen,javaScriptBlock,javaScriptParenError
+syntax cluster  javaScriptExpression contains=@javaScriptAll,javaScriptBracket,javaScriptParen,javaScriptBlock,javaScriptParenError,@htmlPreproc
+
+let b:current_syntax = "javascript"
+if main_syntax == 'javascript'
+  unlet main_syntax
+endif
+
+
+if exists('b:did_indent')
+  finish
+endif
+let b:did_indent = 1
+
+setlocal indentexpr=GetJsIndent()
+setlocal indentkeys=0{,0},0),:,!^F,O,e,=*/
+" Clean CR when the file is in Unix format
+if &fileformat == "unix"
+    silent! %s/\r$//g
+endif
+" Only define the functions once per Vim session.
+"if exists("*GetJsIndent")
+"    finish
+"endif
+"function! GetJsIndent()
+"    let pnum = prevnonblank(v:lnum - 1)
+"    if pnum == 0
+"       return 0
+"    endif
+"    let line = getline(v:lnum)
+"    let pline = getline(pnum)
+"    let ind = indent(pnum)
+"
+"    if pline =~ '{\s*$\|[\s*$\|(\s*$'
+"      let ind = ind + &sw
+"    endif
+"
+"    if pline =~ ';\s*$' && line =~ '^\s*}'
+"        let ind = ind - &sw
+"    endif
+"
+"    if pline =~ '\s*]\s*$' && line =~ '^\s*),\s*$'
+"      let ind = ind - &sw
+"    endif
+"
+"    if pline =~ '\s*]\s*$' && line =~ '^\s*}\s*$'
+"      let ind = ind - &sw
+"    endif
+"
+"    if line =~ '^\s*});\s*$\|^\s*);\s*$' && pline !~ ';\s*$'
+"      let ind = ind - &sw
+"    endif
+"
+"    if line =~ '^\s*})' && pline =~ '\s*,\s*$'
+"      let ind = ind - &sw
+"    endif
+"
+"    if line =~ '^\s*}();\s*$' && pline =~ '^\s*}\s*$'
+"      let ind = ind - &sw
+"    endif
+"
+"    if line =~ '^\s*}),\s*$'
+"      let ind = ind - &sw
+"    endif
+"
+"    if pline =~ '^\s*}\s*$' && line =~ '),\s*$'
+"       let ind = ind - &sw
+"    endif
+"
+"    if pline =~ '^\s*for\s*' && line =~ ')\s*$'
+"       let ind = ind + &sw
+"    endif
+"
+"    if line =~ '^\s*}\s*$\|^\s*]\s*$\|\s*},\|\s*]);\s*\|\s*}]\s*$\|\s*};\s*$\|\s*})$\|\s*}).el$' && pline !~ '\s*;\s*$\|\s*]\s*$' && line !~ '^\s*{' && line !~ '\s*{\s*}\s*'
+"          let ind = ind - &sw
+"    endif
+"
+"    if pline =~ '^\s*/\*'
+"      let ind = ind + 1
+"    endif
+"
+"    if pline =~ '\*/$'
+"      let ind = ind - 1
+"    endif
+"    return ind
+"endfunction
+
+" vim: ts=4
index 260eb074b852ca775432a6fc63b62d09933f1862..b2719c05452d795559fb925fbc40d9d7de4994f2 100644 (file)
@@ -42,7 +42,8 @@
         <p>
             If the <em>-group</em>=<a>group</a> flag is given, add this autocmd
             to the named <t>group</t>. Any filters for <a>group</a> apply in
-            addition to <oa>filter</oa>.
+            addition to <oa>filter</oa>. When listing commands this limits the
+            output to the specified group.
         </p>
 
         <p>Available <oa>events</oa>:</p>
 </item>
 <h2 tag="autocmd-examples">Examples</h2>
 
-<p>Enable <em>passthrough</em> mode on all Google sites:</p>
+<p>Enable <em>Pass Through</em> mode on all Google sites:</p>
 
 <code><ex>:autocmd LocationChange</ex> <str delim="">google.com</str> <ex>:normal!</ex> <k name="C-z"/></code>
 
-<p>Enable <em>passthrough</em> mode on <em>some</em> Google sites:</p>
+<p>Enable <em>Pass Through</em> mode on <em>some</em> Google sites:</p>
 
 <code><ex>:autocmd LocationChange</ex> <str delim="'">^https?://(www|mail)\.google\.com/</str> <ex>:normal!</ex> <k name="C-z"/></code>
 
index cbb31f100b4dc8ab7d1d409f954cf725f373fbd5..0e0c4075e05244a8ea4e32a137cc3504273cd61a 100644 (file)
 &dactyl.appName; overrides nearly all &dactyl.host; keys in order to
 make browsing more pleasant for Vim users. On the occasions when you
 want to bypass &dactyl.appName;'s key handling and pass keys directly to
-&dactyl.host; or to a web page, you have two options:
+&dactyl.host; or to a web page, you have several options:
 
 <item>
-    <tags><![CDATA[<A-b>]]></tags>
+    <tags><![CDATA[<pass-next-key-builtin> <A-b>]]></tags>
     <spec><![CDATA[<A-b>]]></spec>
     <description>
         <p>
@@ -53,6 +53,11 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
     </description>
 </item>
 
+<p>
+    See also <o>passkeys</o> and <o>passunknown</o> for ways to permanently pass
+    all or particular keys under certain conditions.
+</p>
+
 <h2 tag="opening">Opening web pages</h2>
 
 <item>
@@ -101,7 +106,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
                 <example><ex>:open Linus Torvalds</ex></example>
            </li>
            <li>
-               Any other value is passed directly &dactyl.host; and
+               Any other value is passed directly to &dactyl.host; and
                must be a valid URL or hostname.
            </li>
         </ol>
@@ -223,7 +228,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
 </item>
 
 <item>
-    <tags><![CDATA[<C-x>]]></tags>
+    <tags><![CDATA[<decrement-url-path> <C-x>]]></tags>
     <strut/>
     <spec><oa>count</oa>&lt;C-x></spec>
     <description>
@@ -237,7 +242,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
 </item>
 
 <item>
-    <tags><![CDATA[<C-a>]]></tags>
+    <tags><![CDATA[<increment-url-path> <C-a>]]></tags>
     <strut/>
     <spec><oa>count</oa>&lt;C-a></spec>
     <description>
@@ -249,7 +254,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
 </item>
 
 <item>
-    <tags>~</tags>
+    <tags><![CDATA[<open-home-directory> ~]]></tags>
     <spec>~</spec>
     <description short="true">
         <p>Open home directory. Equivalent to <ex>:open ~/</ex></p>
@@ -296,14 +301,14 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
 
         <p>
             Current history position is marked with <em>></em>.
-            Jump numbers may be used as counts for with
+            Jump numbers may be used as counts for
             <ex>:back</ex> or <ex>:forward</ex>.
         </p>
     </description>
 </item>
 
 <item>
-    <tags>gh</tags>
+    <tags><![CDATA[<open-homepage> gh]]></tags>
     <spec>gh</spec>
     <description short="true">
         <p>Go home. Opens the homepage in the current tab.</p>
@@ -311,7 +316,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
 </item>
 
 <item>
-    <tags>gH</tags>
+    <tags><![CDATA[<tab-open-homepage> gH]]></tags>
     <strut/>
     <spec>gH</spec>
     <description>
@@ -324,7 +329,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
 </item>
 
 <item>
-    <tags>gu</tags>
+    <tags><![CDATA[<open-parent-path> gu]]></tags>
     <spec><oa>count</oa>gu</spec>
     <description short="true">
         <p>Go to <oa>count</oa>th parent directory.</p>
@@ -338,7 +343,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
 </item>
 
 <item>
-    <tags>gU</tags>
+    <tags><![CDATA[<open-root-path> gU]]></tags>
     <spec>gU</spec>
     <description short="true">
         <p>Go to the root of the web site.</p>
@@ -369,28 +374,6 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
     </description>
 </item>
 
-<item>
-    <tags>:reh :rehash</tags>
-    <spec>:reh<oa>ash</oa> <oa>arg</oa> …</spec>
-    <description>
-        <p>
-            Reload the &dactyl.appName; add-on, including all code, plugins,
-            and configuration. For users running directly from the development
-            repository, this is a good way to update to the latest version or
-            to test your changes.
-        </p>
-        <p>
-            Any arguments supplied are parsed as command-line arguments as
-            specified in <t>startup-options</t>.
-        </p>
-        <warning>
-            Not all plugins are designed to cleanly un-apply during a rehash.
-            While official plugins are safe, beware of possible instability
-            if you rehash while running third-party plugins.
-        </warning>
-    </description>
-</item>
-
 <item>
     <tags>:re :reload</tags>
     <spec>:re<oa>load</oa><oa>!</oa></spec>
@@ -416,7 +399,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
 <h2 tag="stopping">Stopping</h2>
 
 <item>
-    <tags><![CDATA[<C-c> :st :stop]]></tags>
+    <tags><![CDATA[<stop-load> <C-c> :st :stop]]></tags>
     <spec>&lt;C-c></spec>
     <strut/>
     <spec>:st<oa>op</oa></spec>
@@ -471,6 +454,18 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
 
 <h2 tag="quitting save-session">Quitting</h2>
 
+<item>
+    <tags>ZQ :x :exit</tags>
+    <strut/>
+    <spec>:exit<oa>!</oa></spec>
+    <description>
+        <p>
+            Quit &dactyl.appName;, no matter how many tabs/windows are open.
+            The session is not stored. Use <oa>!</oa> to forcibly quit.
+        </p>
+    </description>
+</item>
+
 <item>
     <tags>:q :quit</tags>
     <strut/>
@@ -489,8 +484,8 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
     <spec>:quita<oa>ll</oa></spec>
     <description>
         <p>
-            Quit &dactyl.appName;, no matter how many tabs/windows
-            are open. The session is not stored.
+            Close the current &dactyl.appName; window, no matter how
+            many tabs are open.
         </p>
     </description>
 </item>
@@ -512,7 +507,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
 </item>
 
 <item>
-    <tags>:xa :xall :wq :wqa :wqall</tags>
+    <tags>ZZ :xa :xall :wq :wqa :wqall</tags>
     <spec>:wqa<oa>ll</oa></spec>
     <strut/>
     <spec>:xa<oa>ll</oa></spec>
@@ -525,25 +520,6 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
     </description>
 </item>
 
-<item>
-    <tags>ZQ</tags>
-    <spec>ZQ</spec>
-    <description short="true">
-        <p>Quit and don't save the session. Works like <ex>:qall</ex>.</p>
-    </description>
-</item>
-
-<item>
-    <tags>ZZ</tags>
-    <spec>ZZ</spec>
-    <description short="true">
-        <p>
-            Quit &dactyl.appName; and save the session. Works like
-            <ex>:xall</ex>.
-        </p>
-    </description>
-</item>
-
 <h2 tag="current-directory">The current directory</h2>
 
 <item>
index 66d0d978486f2db0d5cd046560cfff6625e7a3b3..7f514a91f46d58d8dc2fa02f932347bce07e8f1c 100644 (file)
@@ -55,7 +55,7 @@
 </item>
 
 <item>
-    <tags>gf</tags>
+    <tags><![CDATA[<view-source> gf]]></tags>
     <strut/>
     <spec>gf</spec>
     <description>
@@ -67,7 +67,7 @@
 </item>
 
 <item>
-    <tags>gF</tags>
+    <tags><![CDATA[<view-source-externally> gF]]></tags>
     <strut/>
     <spec>gF</spec>
     <description>
 </item>
 
 <item>
-    <tags><![CDATA[<Home> gg]]></tags>
+    <tags><![CDATA[<scroll-top> <Home> gg]]></tags>
     <strut/>
     <spec><oa>count</oa>gg</spec>
     <description>
 </item>
 
 <item>
-    <tags><![CDATA[<End> G]]></tags>
+    <tags><![CDATA[<scroll-bottom> <End> G]]></tags>
     <strut/>
     <spec><oa>count</oa>G</spec>
     <description>
 </item>
 
 <item>
-    <tags><![CDATA[<scroll-column-left> <Left> h]]></tags>
+    <tags><![CDATA[<scroll-left-column> <Left> h]]></tags>
     <strut/>
     <spec><oa>count</oa>h</spec>
     <description>
 </item>
 
 <item>
-    <tags><![CDATA[<scroll-line-down> <C-e> <Down> j]]></tags>
+    <tags><![CDATA[<scroll-down-line> <C-e> <Down> j]]></tags>
     <strut/>
     <spec><oa>count</oa>j</spec>
     <description>
 </item>
 
 <item>
-    <tags><![CDATA[<scroll-line-up> <C-y> <Up> k]]></tags>
+    <tags><![CDATA[<scroll-up-line> <C-y> <Up> k]]></tags>
     <strut/>
     <spec><oa>count</oa>k</spec>
     <description>
 </item>
 
 <item>
-    <tags><![CDATA[<scroll-column-right> <Right> l]]></tags>
+    <tags><![CDATA[<scroll-right-column> <Right> l]]></tags>
     <strut/>
     <spec><oa>count</oa>l</spec>
     <description>
 </item>
 
 <item>
-    <tags><![CDATA[<scroll-page-up> <S-Space> <PageUp> <C-b>]]></tags>
+    <tags><![CDATA[<scroll-up-page> <S-Space> <PageUp> <C-b>]]></tags>
     <strut/>
     <spec><oa>count</oa>&lt;C-b></spec>
     <description>
 </item>
 
 <item>
-    <tags><![CDATA[<scroll-page-down> <Space> <PageDown> <C-f>]]></tags>
+    <tags><![CDATA[<scroll-down-page> <Space> <PageDown> <C-f>]]></tags>
     <strut/>
     <spec><oa>count</oa>&lt;C-f></spec>
     <description>
     </description>
 </item>
 
+<item>
+    <tags>[h [p [</tags>
+    <spec><oa>count</oa>[<a>arg</a></spec>
+    <description short="true">
+        <p>Jump to the previous element as defined by <o>jumptags</o>.</p>
+    </description>
+</item>
+
+<item>
+    <tags>]h ]p ]</tags>
+    <spec><oa>count</oa>]<a>arg</a></spec>
+    <description short="true">
+        <p>Jump to the next element as defined by <o>jumptags</o>.</p>
+    </description>
+</item>
+
+<item>
+    <tags>{</tags>
+    <spec><oa>count</oa>{</spec>
+    <description short="true">
+        <p>Jump to the previous paragraph. Identical to <k>[p</k>.</p>
+    </description>
+</item>
+
+<item>
+    <tags>}</tags>
+    <spec><oa>count</oa>}</spec>
+    <description short="true">
+        <p>Jump to the next paragraph. Identical to <k>]p</k>.</p>
+    </description>
+</item>
+
+
 <h2 tag="zooming zoom">Zooming</h2>
 
 <p>
index 0ad333296d8fdd1aab1e97254da5aabd1c43bba0..b44c8bb3c904a79685c43a2893ba53f397212a6f 100644 (file)
@@ -9,11 +9,11 @@
     xmlns="&xmlns.dactyl;"
     xmlns:html="&xmlns.html;">
 
-<h1 tag="command-line-mode command-line mode-cmdline">Command-line mode</h1>
+<h1 tag="command-line-mode command-line mode-cmdline">Command Line mode</h1>
 <toc start="2"/>
 
 <p>
-    &dactyl.appName;'s command-line mode is perhaps its most
+    &dactyl.appName;'s Command Line mode is perhaps its most
     powerful interface. In this mode, the command input bar at the
     bottom of the window is given the keyboard focus for any of a
     variety of required inputs. In addition to access to almost
@@ -27,9 +27,9 @@
 
 <p>
     Included among the several command-line modes are Ex command
-    mode (the standard mode for entering commands), find mode (for
-    searching the current page), prompt mode (for selecting files,
-    confirming actions), and hint mode (for selecting links and
+    mode (the standard mode for entering commands), Find mode (for
+    searching the current page), Prompt mode (for selecting files,
+    confirming actions), and Hints mode (for selecting links and
     other items on a page).
 </p>
 
@@ -60,7 +60,7 @@
     <tags><![CDATA[c_<C-c>]]></tags>
     <spec>&lt;C-c></spec>
     <description short="true">
-        <p>Quit Command-line mode without executing.</p>
+        <p>Quit Command Line mode without executing.</p>
     </description>
 </item>
 
index ecc3becd3d8ec5ebdb2b2b71ec518772c8a66a3b..2c78e9c6b68711468bd73e1c830f404b60a4fe4a 100644 (file)
@@ -65,8 +65,8 @@
             <h3 tag="faq-statussymbols">What do the "[-+♥]" symbols in the status bar mean?</h3>
             <p>
                 These indicate that you can move backward through history,
-                that you can move forward through history, and that the page
-                is bookmarked, respectively. See also <ex>:help</ex>
+                that you can move forward through history, and that the page is
+                bookmarked, respectively. See also <ex>:help</ex>
                 <t>status-line</t>.
             </p>
 
                 <ex>:help</ex> <t>pattern</t>.
             </p>
 
-            <h3 tag="faq-autocomplete">How can I prevent the command line completion list showing until I press <k name="Tab"/>?</h3>
+            <h3 tag="faq-autocomplete"><strut/>How can I prevent the command line completion list showing until I press <k name="Tab"/>?</h3>
             <p>
-                You can disable it entirely with <ex>:set autocomplete=</ex>
-                or for specific types of command completion by
-                choosing more restrictive values. See <ex>:help</ex>
-                <o>autocomplete</o> and <o>wildmode</o>.
+                You can disable it entirely with <se opt="autocomplete"/> or for
+                specific types of command completion by choosing more
+                restrictive values. See <ex>:help</ex> <o>autocomplete</o> and
+                <o>wildmode</o>.
             </p>
 
             <h3 tag="faq-editor-fork">Why doesn't external input field editing work with my <o>editor</o> setting?</h3>
             <p>
-                Unfortunately, external editors which return immediately,
-                before editing is complete, are not supported. This means that
-                gvim, for instance, must be run with the <em>-f</em> flag, and
-                editors run from a terminal must not connect to a remote
-                process. In the case of Rxvt-unicode, this means that the
-                urxvtc program is not an option, and Gnome Terminal is very
-                likely not useable under any circumstances.
+                Unfortunately, external editors which return immediately, before
+                editing is complete, are not supported. This means that
+                <tt>gvim</tt>, for instance, must be run with the <em>-f</em>
+                flag, and editors run from a terminal must not connect to a
+                remote process. In the case of Rxvt-unicode, this means that the
+                <tt>urxvtc</tt> program is not an option, and Gnome Terminal is
+                very likely not useable under any circumstances.
             </p>
             <note>
-                If you are using a version of Firefox newer than 4.0
-                beta 7 and a version of Pentadactyl less than 1.0
-                βeta 4, you'll need to upgrade the latter.
+                If you are using a version of Firefox newer than 4.0 beta 7 and
+                a version of Pentadactyl less than 1.0 βeta 4, you'll need to
+                upgrade the latter.
             </note>
 
             <h3 tag="faq-symlinks">Why can't I build/install from the Mercurial repository on Windows®?</h3>
             <p>
                 We use symbolic links in our repository to deal with certain
-                files which are common across projects. Mercurial for
-                Windows®, unfortunately, doesn't deal with these very well.
-                However, adding the following lines to the <tt>.hg\hgrc</tt>
-                file in your repository should make things work:
+                files which are common across projects. Mercurial for Windows®,
+                unfortunately, doesn't deal with these very well. However,
+                adding the following lines to the <tt>.hg\hgrc</tt> file in your
+                repository should make things work:
             </p>
             <code><hl key="Key">[hooks]</hl>
 update = <str delim="">python:common/contrib/fix_symlinks.py:fix_symlinks</str>
@@ -140,7 +140,7 @@ precommit = <str delim="">python:common/contrib/fix_symlinks.py:fix_symlinks</st
                 engines are hidden after an update. This can be remedied by
                 invoking
 
-                <code style="position: relative"><ex>:js</ex> services.get(<str>browserSearch</str>).getEngines().forEach(<em>function</em> (e) e.hidden = <hl key="Boolean">false</hl>)</code>
+                <code style="position: relative"><ex>:js</ex> services.browserSearch.getEngines().forEach(<em>function</em> (e) e.hidden = <hl key="Boolean">false</hl>)</code>
             </p>
 
             <h2 tag="faq-keys">Key bindings</h2>
@@ -148,7 +148,7 @@ precommit = <str delim="">python:common/contrib/fix_symlinks.py:fix_symlinks</st
             <p>
                 See the <o>passkeys</o> option to automatically pass specific
                 keys on sites of your choosing, or <t>autocmd-examples</t> to
-                automatically enter <em>PASS THROUGH</em> mode for certain websites.
+                automatically enter <em>Pass Through</em> mode for certain websites.
             </p>
 
             <h3 tag="faq-passkeys-autocmd">Why doesn't my modes.passAllKeys autocmd work anymore?</h3>
@@ -158,7 +158,7 @@ precommit = <str delim="">python:common/contrib/fix_symlinks.py:fix_symlinks</st
             <h3 tag="faq-hintkeys">How can I use keys other than numbers for hinting?</h3>
             <p>Use the <o>hintkeys</o> option.</p>
 
-            <h3 tag="faq-hintkeys-uppercase">How can I display my hints in upper case but type them in lower case?</h3>
+            <h3 tag="faq-hintkeys-uppercase"><strut/>How can I display my hints in upper case but type them in lower case?</h3>
             <p>
                 If you use alphabetic characters for your <o>hintkeys</o> and
                 would like to be able to type them in lower case but still have
index 9bb1151cb92dfcba7c9169f745f65c640f2d5077..4c3d200142e4e5c512540e82fdd3fa14902dcbba 100644 (file)
             Show progress of current downloads. Here, downloads can
             be paused, resumed, and canceled.
         </p>
+
+        <p>Available options include:</p>
+
+        <dl>
+            <dt>-sort</dt> <dd>Sort order (see <o>downloadsort</o>) (short name: <em>-s</em>)</dd>
+        </dl>
+    </description>
+</item>
+
+<item>
+    <tags>:dlc :dlclear</tags>
+    <strut/>
+    <spec>:dlc<oa>lear</oa></spec>
+    <description>
+        <p>Clear completed downloads.</p>
     </description>
 </item>
 
 <h2 tag="extensions add-ons">Add-ons</h2>
 
+<p>
+    The following commands manipulate the currently installed
+    add-ons. With the exception of <ex>:extadd</ex>, they all except
+    the following arguments:
+</p>
+
+<dl>
+    <dt>-types</dt> <dd>The types of add-ons to operate on, the most
+        common types being <tt>extension</tt>, <tt>theme</tt>, and <tt>plugin</tt> (short names <em>-type</em>,
+        <em>-t</em>)</dd>
+</dl>
+
 <item>
     <tags>:exta :extadd</tags>
     <spec>:exta<oa>dd</oa> <a>file|url</a></spec>
     </li>
     <li>
         <em>History and bookmark status</em> (<tt>[+-❤⋯]</tt>): The position
-        of the current page in the tab's session history; <tt>+</tt> and
-        <tt>-</tt> indicate that it is possible to move backwards and forwards
+        of the current page in the tab's session history; <tt>-</tt> and
+        <tt>+</tt> indicate that it is possible to move backwards and forwards
         through the history respectively. ❤ indicates that the current page is
         bookmarked. Any other character indicates a QuickMark matching the
         current page.
index 68ce2f3a38c2b7292d9182a4219caf2ad4631a9c..e44a12a99c3349ff1517b884d4364613ab4dbb71 100644 (file)
@@ -9,12 +9,12 @@
     xmlns="&xmlns.dactyl;"
     xmlns:html="&xmlns.html;">
 
-<h1 tag="quick-hints hints">Hints</h1>
+<h1 tag="hints">Hints</h1>
 <toc start="2"/>
 
 <p>
     Hints are an easy way to interact with web pages without using
-    your mouse. In hint mode, &dactyl.appName; highlights and
+    your mouse. In Hints mode, &dactyl.appName; highlights and
     numbers all clickable elements. The elements can be selected
     either by typing their numbers, or typing parts of their text to
     narrow them down. While the default action is to click the
 </p>
 
 <item>
-    <tags>quick-hints</tags>
-    <tags>f QuickHint</tags>
+    <tags>quick-hints hint-mode</tags>
+    <tags>f</tags>
     <strut/>
     <spec>f<a>hint</a></spec>
     <description>
         <p>
-            Start <t>QuickHint</t> mode. In this mode, every clickable
+            Start <t>hint-mode</t>. In this mode, every clickable
             element (as defined by the <o>hinttags</o> option) is
             highlighted and numbered.  Elements can be selected
             either by typing their number, or by typing part of
             their text to narrow down the result. When an element
             has been selected, it is automatically clicked and hint
             mode ends. Additionally, the following keys have
-            special meanings in QuickHint mode:
+            special meanings in Hints mode:
         </p>
         <dl>
             <dt><k name="CR"/></dt>
@@ -53,7 +53,7 @@
                 value of <o>hintkeys</o>) as ordinary text</dd>
 
             <dt><k name="Esc"/></dt>
-            <dd>Exits hint mode without selecting an element</dd>
+            <dd>Exits Hints mode without selecting an element</dd>
         </dl>
     </description>
 </item>
@@ -64,7 +64,7 @@
     <spec>F<a>hint</a></spec>
     <description>
         <p>
-            Start <t>QuickHint</t> mode, but the selected elements
+            Start <t>hint-mode</t>, but the selected elements
             are clicked with the <k name="Shift" link="false"/> key pressed,
             which has the normal effect of opening it in a new tab
             (depending on the value of the
 
 <item>
     <tags>extended-hints</tags>
-    <tags>; ExtendedHint</tags>
+    <tags>;</tags>
     <strut/>
     <spec>;<a>mode</a><a>hint</a></spec>
     <description>
         <p>
-            Start an extended hint mode. <t>ExtendedHint</t> mode is exactly
-            like <t>QuickHint</t> mode, except that each sub-mode highlights a
+            Start an extended hints mode. Extended hints are exactly like
+            <t>quick-hints</t>, except that each sub-mode highlights a
             more specialized set of elements, and performs a unique action on
             the selected link. Because of the panoply of extended hint modes
             available, after pressing <k>;</k>, pressing <k name="Tab"/> brings
-            up the completion list with each hint mode and its description.
+            up the completion list with each hints mode and its description.
         </p>
 
         <p><a>mode</a> may be one of:</p>
     <spec>g;<a>mode</a><a>hint</a></spec>
     <description>
         <p>
-            Start an extended hint mode and stay there until
+            Start an extended hints mode and stay there until
             <k name="Esc"/> is pressed. Like <k>;</k>, except that
             after a hint is selected, hints remain visible so that
             another one can be selected with the same action as the
index 640b321ea42acea58691bf0adf973c518c7d3b99..8921e811dd9f41db269f98b0bb378e2748f45438 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>
 
-<!DOCTYPE document SYSTEM "dactyl://content/dtd">
+<!DOCTYPE document SYSTEM "dactyl://content/modes.dtd">
 
 <document
     name="map"
@@ -26,9 +26,9 @@
 <p>
     Key mappings are the most basic means &dactyl.appName; provides
     for altering the actions of key presses. Each key mapping is
-    associated with a mode, such as <link topic="insert-mode">insert</link>,
-    <link>normal</link>, or
-    <link topic="command-line-mode">command-line</link>, and only
+    associated with a mode, such as <link topic="insert-mode">Insert</link>,
+    <link>Normal</link>, or
+    <link topic="command-line-mode">Command Line</link>, and only
     has effect when that mode is active. Although each mode has a
     full suite of internal mappings, they may be easily augmented,
     altered, or removed with the <ex>:map</ex> command and its
     <dt>n</dt> <dd>Normal mode: When browsing normally</dd>
     <dt>v</dt> <dd>Visual mode: When selecting text with the cursor keys</dd>
     <dt>i</dt> <dd>Insert mode: When interacting with text fields on a website</dd>
-    <dt>t</dt> <dd>TextEdit mode: When editing text fields in Vim-like NORMAL mode</dd>
-    <dt>c</dt> <dd>Command-line mode: When typing into the &dactyl.appName; command line</dd>
+    <dt>t</dt> <dd>Text Edit mode: When editing text fields in Vim-like Normal mode</dd>
+    <dt>c</dt> <dd>Command Line mode: When typing into the &dactyl.appName; command line</dd>
 </dl>
 
 <p>
     The ordinary <ex>:map</ex> and <ex>:noremap</ex> commands
-    add mappings for normal and visual mode. In order to map key
+    add mappings for Normal and Visual mode. In order to map key
     bindings in a different mode, any of the mapping commands may be
     prefixed with one of the above letters. For instance,
-    <ex>:imap</ex> creates a new key mapping in insert mode, while
-    <ex>:cunmap</ex> removes a key mapping from command-line mode.
+    <ex>:imap</ex> creates a new key mapping in Insert mode, while
+    <ex>:cunmap</ex> removes a key mapping from Command Line mode.
     Other modes can be specified using the -modes option described below.
 </p>
 
     saved via the <ex>:mk&dactyl.name;rc</ex> command.
 </warning>
 
+<p tag="modes">
+    The following tree represents all of the modes understood by
+    dactyl. Mappings for a mode also apply to its children and
+    descendants. So a mapping in the Base mode, for instance, is
+    also active in Normal and Ex mode.
+</p>
+
+&modes.tree;
+
 <h3 tag=":map-commands">Map commands</h3>
 
 <item>
     </description>
 </item>
 
+<p tag=":map-overview">
+    Below is an overview of which modes each map command applies to:
+</p>
+
+<dl dt="width: 20em;">
+    <dt>:map   :noremap   :unmap</dt>  <dd>Normal and Visual modes</dd>
+    <dt>:nmap  :nnoremap  :nunmap</dt> <dd>Normal mode</dd>
+    <dt>:vmap  :vnoremap  :vunmap</dt> <dd>Visual mode</dd>
+    <dt>:imap  :inoremap  :iunmap</dt> <dd>Insert mode</dd>
+    <dt>:tmap  :tnoremap  :tunmap</dt> <dd>Text Edit mode</dd>
+    <dt>:cmap  :cnoremap  :cunmap</dt> <dd>Command Line mode</dd>
+</dl>
+
+<note>
+    The <em>-modes</em> option, described below, provides a more flexible way
+    to specify the applicable modes.
+</note>
+
 <h3 tag=":map-options">Map options</h3>
 <p>
     Any of the map commands may be given the following options:
     <dt>-count</dt> <dd>Accept a count before the requisite key press. Sets the <tt>count</tt> parameter to the result. (short name <em>-c</em>)</dd>
     <dt>-description</dt> <dd>A description of this mapping (short name <em>-d</em>)</dd>
     <dt>-ex</dt> <dd>Execute <a>rhs</a> as an Ex command rather than keys (short name <em>-e</em>)</dd>
-    <dt>-group=<a>group</a></dt> <dd>Add this command to the given <t>group</t> (short name <em>-g</em>)</dd>
+    <dt>-group=<a>group</a></dt> <dd>Add this command to the given <t>group</t> (short name <em>-g</em>). When listing commands this limits the output to the specified group.</dd>
     <dt>-javascript</dt> <dd>Execute <a>rhs</a> as JavaScript rather than keys (short names <em>-js</em>, <em>-j</em>)</dd>
     <dt>-literal=<a>n</a></dt> <dd>Parse the <a>n</a>th argument without specially processing any quote or meta characters. (short name <em>-l</em>)</dd>
-    <dt>-modes</dt> <dd>Create this mapping in the given modes (short names <em>-mode</em>, <em>-m</em>)</dd>
+    <dt>-modes=<a>modes</a></dt> <dd>Create this mapping in the given modes (short names <em>-mode</em>, <em>-m</em>)</dd>
     <dt>-nopersist</dt> <dd>Do not save this mapping to an auto-generated rc file (short name <em>-n</em>)</dd>
     <dt>-silent</dt> <dd>Do not echo any generated keys to the command line (short name <em>-s</em>, also <em>&lt;silent></em> for Vim compatibility)</dd>
 </dl>
 <item>
     <tags>:no :noremap</tags>
     <spec>:no<oa>remap</oa> <a>lhs</a> <a>rhs</a></spec>
-    <tags>:nn :nnoremap</tags>
-    <spec>:nn<oa>oremap</oa> <a>lhs</a> <a>rhs</a></spec>
-    <tags>:vn :vnoremap</tags>
-    <spec>:vn<oa>oremap</oa> <a>lhs</a> <a>rhs</a></spec>
+    <tags>:nno :nnoremap</tags>
+    <spec>:nno<oa>remap</oa> <a>lhs</a> <a>rhs</a></spec>
+    <tags>:vno :vnoremap</tags>
+    <spec>:vno<oa>remap</oa> <a>lhs</a> <a>rhs</a></spec>
     <tags>:ino :inoremap</tags>
     <spec>:ino<oa>remap</oa> <a>lhs</a> <a>rhs</a></spec>
     <tags>:tno :tnoremap</tags>
     documentation of those options for more information.
 </p>
 
-<h3 tag=":map-arguments">Special arguments</h3>
-
-<p>
-    Below is an overview of which modes each map command applies to:
-</p>
-
-<!-- TODO: table format -->
-
-<code>
-:map   :noremap   :unmap   :mapclear  – both Normal and Visual modes
-:nmap  :nnoremap  :nunmap  :nmapclear – Normal mode
-:vmap  :vnoremap  :vunmap  :vmapclear – Visual mode
-:imap  :inoremap  :iunmap  :imapclear – Insert mode
-:tmap  :tnoremap  :tunmap  :tmapclear – Text Edit mode
-:cmap  :cnoremap  :cunmap  :cmapclear – Command-line mode
-</code>
-
 <h3 tag="key-notation key-sequence">Key sequences</h3>
 
 <p>
     <li><k name="C-␣" link="false"/>: The control or ctrl key.</li>
     <li><k name="A-␣" link="false"/>: The alt key.</li>
     <li><k name="M-␣" link="false"/>: The meta key, windows key, or command key.</li>
+    <li><k name="⌘-␣" link="false"/>: Same as <k name="M-␣" link="false"/>.</li>
     <li><k name="S-␣" link="false"/>: The shift key.</li>
 </ol>
 
 
 <dl dt="width: 10em;">
     <dt><k link="false">xc</k></dt>
-    <dd>Type the ‘X’ key followed by the ‘C’ key.</dd>
+    <dd>Press the ‘X’ key followed by the ‘C’ key.</dd>
 
     <dt><k name="C-x" link="false">c</k></dt>
     <dd>
-        Type the ‘X’ key while holding the ‘Control’ key, followed
+        Press the ‘X’ key while holding the ‘Control’ key, followed
         by the ‘C’ key.
     </dd>
 
     <dt><k name="C-2" link="false"/></dt>
-    <dd>Type the ‘2’ while holding the ‘Control’ key.</dd>
+    <dd>Type ‘2’ while holding the ‘Control’ key.</dd>
 
     <dt><k name="C-@" link="false"/></dt>
-    <dd>Type the ‘@’ key while holding the ‘Control’ key.</dd>
+    <dd>Press the ‘@’ key while holding the ‘Control’ key.</dd>
 
     <dt><k name="S-Space" link="false"/></dt>
     <dd>Press the space bar while holding the ‘Shift’ key.</dd>
 
     <dt><k name="C-A-j" link="false"/></dt>
-    <dd>Type the ‘J’ key while while holding both the ‘Control’ and ‘Alt’ keys.</dd>
+    <dd>Press the ‘J’ key while holding both the ‘Control’ and ‘Alt’ keys.</dd>
 
     <dt><k name="C-A-J" link="false"/></dt>
     <dd>Exactly the same as above.</dd>
 
     <dt><k name="C-A-S-j" link="false"/></dt>
-    <dd>Type the ‘J’ key while while holding both the ‘Control’, ‘Alt’, and ‘Shift’ keys.</dd>
+    <dd>Press the ‘J’ key while holding all of ‘Control’, ‘Alt’, and ‘Shift’ keys.</dd>
 </dl>
 
 <h3 tag=":map-special-chars">Special characters</h3>
 
 <item>
     <tags>:ab :abbreviate</tags>
-    <spec>:ab<oa>breviate</oa> <oa>-group=<a>group</a></oa> <a>lhs</a> <a>rhs</a></spec>
+    <spec>:ab<oa>breviate</oa> <oa>-group=<a>group</a></oa> <oa>-js</oa> <a>lhs</a> <a>rhs</a></spec>
     <spec>:ab<oa>breviate</oa> <oa>-group=<a>group</a></oa> <a>lhs</a></spec>
     <spec>:ab<oa>breviate</oa> <oa>-group=<a>group</a></oa></spec>
     <description>
     <spec>:ca<oa>bbreviate</oa></spec>
     <description>
         <p>
-            Abbreviate a key sequence for Command-line mode. Same as
-            <ex>:ab<oa>breviate</oa></ex>, but for
-            <t>command-line</t> mode only.
+            Abbreviate a key sequence for Command Line mode. Same as
+            <ex>:ab<oa>breviate</oa></ex>, but for &mode.command-line; mode
+            only.
         </p>
     </description>
 </item>
     <description>
         <p>
             Abbreviate a key sequence for Insert mode. Same as
-            <ex>:ab<oa>breviate</oa></ex>, but for insert mode only.
+            <ex>:ab<oa>breviate</oa></ex>, but for Insert mode only.
         </p>
     </description>
 </item>
     <spec>:cuna<oa>bbreviate</oa>!</spec>
     <description>
         <p>
-            Remove abbreviation(s) for Command-line mode. Same as
-            <ex>:una<oa>bbreviate</oa></ex>, but for
-            <t>command-line</t> mode only.
+            Remove abbreviation(s) for Command Line mode. Same as
+            <ex>:una<oa>bbreviate</oa></ex>, but for &mode.command-line; mode
+            only.
         </p>
     </description>
 </item>
 
         <p>
             The <em>-group</em> flag (short name: <em>-g</em>) can be used to
-            assign this command to a specific <t>group</t>.
+            assign this command to a specific <t>group</t>.  When listing
+            commands this limits the output to the specified group.
         </p>
 
         <h3 tag="E175 E176 :command-nargs">Argument handling</h3>
 
         <p>
             <em>completions</em> is a two-dimensional array of the form:
-            <tt>[[arg1, description1], [arg2, description2], …]</tt>
+            <tt>[[val1, description1], [val2, description2], …]</tt>
         </p>
 
         <p>
         <p>
             Example:
             <code><ex>:command foo -nargs=? -complete custom,<str delim="'">
-    \ function (context) context.completions = [["arg1", "description1"], ["arg2", "description2"]]</str>
+    \ function (context) context.completions = [["val1", "description1"], ["val2", "description2"]]</str>
     \ <ex>:echo</ex> <str>Useless </str> + <em>&lt;q-args></em></ex>
 
 <ex>:command foo -nargs=?
-    \ -complete custom,<str delim="'">[["arg1", "description1"], ["arg2, "description2"]]</str>
+    \ -complete custom,<str delim="'">[["val1", "description1"], ["val2", "description2"]]</str>
     \ <ex>:echo</ex> <str>Same as above but simpler </str> + <em>&lt;q-args></em></ex></code>
         </p>
 
index 662286b6cf2d6be60f6ed58df50b54aa4d20e941..e9c29911b5c5fcabd98100440d48fd403b076bda 100644 (file)
@@ -1,12 +1,27 @@
 # TODO: normalize this debacle of Vim legacy messages
-#     : are we losing the error code prefixes? --djk
 
-abbrev.noSuch = No such abbreviation
-abbrev.none = No abbreviations found
+abbreviation.noSuch = No such abbreviation
+abbreviation.none = No abbreviations found
 
 addon.check-1 = Checking updates for addons: %S
 addon.cantInstallDir-1 = Cannot install a directory: %S
 addon.unavailable = Don't have add-on yet
+addon.unknownCommand = Unknown command
+addon.commandNotAllowed = Command not allowed
+addon.installingUpdates-1 = Installing updates for addons: %S
+addon.noUpdates = No addon updates found
+addon.error-3 = Add-on %S %S: %S
+
+addon.action.On  = On 
+addon.action.Off = Off
+addon.action.Delete = Del
+addon.action.Update = Upd
+addon.action.Options = Opt
+
+AddonManager.ERROR_NETWORK_FAILURE = A network error occurred
+AddonManager.ERROR_INCORRECT_HASH = The downloaded file did not match the expected hash
+AddonManager.ERROR_CORRUPT_FILE = The file appears to be corrupt
+AddonManager.ERROR_FILE_ACCESS = There was an error accessing the filesystem
 
 autocmd.executing-2 = Executing %S Auto commands for %S
 autocmd.autocommand-1 = autocommand %S
@@ -19,12 +34,15 @@ bookmark.noMatchingTags-1 = No bookmarks matching tags %S
 bookmark.noMatchingString-1 = No bookmarks matching string %S
 bookmark.none = No bookmarks set
 bookmark.cantAdd-1 = Could not add bookmark %S
-bookmark.allGone = All bookmarks deleted
+bookmark.allDeleted = All bookmarks deleted
 bookmark.removed-1 = Removed bookmark: %S
 bookmark.added-1 = Added bookmark: %S
 bookmark.deleted-1 = %S bookmark(s) deleted
+bookmark.prompt.deleteAll = This will delete all bookmarks. Would you like to continue? (yes/[no]):
 
-buffer.fewer-2 = %S fewer tab%S
+# TODO: add plurals support
+buffer.fewerTab-1 = %S fewer tab
+buffer.fewerTabs-1 = %S fewer tabs
 buffer.cantDetatchLast = Can't detach the last tab
 buffer.noMatching-1 = No matching buffer for %S
 buffer.multipleMatching-1 = More than one match for %S
@@ -32,6 +50,18 @@ buffer.noClosed = No matching closed tab
 buffer.noAlternate = No alternate page
 buffer.backgroundLoaded = Background tab loaded: %S
 
+# TODO: categorise these
+buffer.noTitle = [No Title]
+buffer.noName = [No Name]
+buffer.help = [Help]
+
+buffer.bookmarked = bookmarked
+
+buffer.prompt.uploadFile = Upload file:
+buffer.prompt.saveLink = Save link:
+
+context.scriptGroup-1 = Script group for %S
+
 command.commands = commands
 
 command.cantDelete = Cannot delete non-user commands
@@ -48,21 +78,38 @@ command.invalidOptArg-2 = Invalid argument for option %S: %S
 command.invalidOptTypeArg-3 = Invalid argument for %S option %S: %S
 command.parsing-1 = Error parsing arguments: %S
 command.none = No user-defined commands found
-command.unknownCompleter-1 = E117: Unknown function: %S
 command.exists = E174: Command already exists: add ! to replace it
 command.noPrevious = E30: No previous command line
-command.noRange = E481: No range allowed
+command.noCount = E481: No count allowed
 command.noBang = E477: No ! allowed
 
 command.colorscheme.notFound-1 = E185: Cannot find color scheme %S
 command.conditional.illegal = Invalid use of conditional
 command.finish.illegal = E168: :finish used outside of a sourced file
-command.let.noSuch-1 = E108: No such variable: %S
-command.let.unexpectedChar = E18: Unexpected characters in :let
 command.let.illegalVar-1 = E461: Illegal variable name: %S
-command.let.undefinedVar-1 = E121: Undefined variable: %S
 command.let.invalidExpression-1 = E15: Invalid expression: %S
-
+command.let.noSuch-1 = E108: No such variable: %S
+command.let.undefinedVar-1 = E121: Undefined variable: %S
+command.let.unexpectedChar = E18: Unexpected characters in :let
+command.run.noPrevious = E34: No previous command
+command.sanitize.privateMode = Cannot sanitize items in private mode
+command.sanitize.allDeleted = All items sanitized
+command.sanitize.noneDeleted = No items sanitized
+command.scriptnames.none = No sourced scripts found
+command.set.errorParsing-1 = Error parsing :set command: %S
+command.set.numberRequired-2 = E521: Number required after =: %S=%S
+command.set.unknownOption-1 = E518: Unknown option: %S
+command.yank.yankedLine-1 = Yanked %S line
+command.yank.yankedLines-1 = Yanked %S lines
+
+completion.additional = (additional)
+completion.generating = Generating results...
+completion.noCompletions = No Completions
+completion.waitingFor-1 = Waiting for %S
+completion.waitingForKeyPress = Waiting for key press
+completion.matchIndex-2 = match %S of %S
+
+dactyl.created-1 = "(created %S)"
 dactyl.parsingCommandLine-1 = Parsing command line options: %S
 dactyl.notCommand-2 = E492: Not a %S command: %S
 dactyl.sourcingPlugins-1 = Sourcing plugin directory: %S...
@@ -71,17 +118,32 @@ dactyl.modulesLoaded = All modules loaded
 dactyl.commandlineOpts-1 = Command-line options: %S
 dactyl.noRCFile = No user RC file found
 dactyl.initialized-1 = %S fully initialized
+dactyl.sourced-1 = Sourced: %S
+dactyl.prompt.openMany-1 = This will open %S new tabs. Would you like to continue? (yes/[no]):
+dactyl.yank-1 = Yank %S
+
+deprecated.for.theOptionsSystem = the options system
 
 dialog.notAvailable-1 = Dialog %S not available
 
-group.cantChangeBuiltin-1 = Cannot change %S in the builtin group
-group.cantModifyBuiltin = Cannot modify builtin group
-group.cantRemoveBuiltin = Cannot remove builtin group
-group.noSuch-1 = No such group: %S
-group.invalidName-1 = Invalid group name: %S
-group.noCurrent = No current group
+# TODO: merge with addon.*?
+download.unknownCommand = Unknown command
+download.commandNotAllowed = Command not allowed
+download.prompt.launchExecutable = This will launch an executable download. Would you like to continue? (yes/[no]/always):
 
-editor.noEditor = No editor specified
+download.nActive-1 = %S active
+download.almostDone = ~1 second
+download.unknown = Unknown
+
+download.action.Cancel = Cancel
+download.action.Clear = Clear
+download.action.Delete = Delete
+download.action.Pause = Pause
+download.action.Remove = Remove
+download.action.Resume = Resume
+download.action.Retry = Retry
+
+editor.prompt.editPassword = Editing a password field externally will reveal the password. Would you like to continue? (yes/[no]):
 
 emenu.notFound-1 = Menu not found: %S
 
@@ -89,38 +151,55 @@ event.error-2 = Processing %S event: %S
 event.nothingToPass = No events to pass
 
 finder.notFound-1 = E486: Pattern not found: %S
-finder.atTop = find hit TOP, continuing at BOTTOM
-finder.atBottom = find hit BOTTOM, continuing at TOP
+finder.atTop = Find hit TOP, continuing at BOTTOM
+finder.atBottom = Find hit BOTTOM, continuing at TOP
+
+group.cantChangeBuiltin-1 = Cannot change %S in the builtin group
+group.cantModifyBuiltin = Cannot modify builtin group
+group.cantRemoveBuiltin = Cannot remove builtin group
+group.noSuch-1 = No such group: %S
+group.invalidName-1 = Invalid group name: %S
+group.noCurrent = No current group
 
 help.dontPanic = E478: Don't panic!
+help.Example = Example
 help.noFile-1 = Sorry, help file %S not found
 help.noTopic-1 = Sorry, no help for %S
+help.title.Using Plugins = Using Plugins
 
-hints.noMatcher-1 = Invalid hintmatching type: %S
+hints.noMatcher-1 = Invalid 'hintmatching' type: %S
 
 history.noMatching-1 = No history matching %S
 history.none = No history set
 history.noURL = URL not found in history
 
-io.noSuchDir-1 = E344: Can't find directory %S
-io.noPrevDir = E186: No previous directory
-io.notReadable-1 = Can't open file %S
-io.notWriteable-1 = Can't open file %S for writing
+io.callingShell-1 = Calling shell to execute: %S
+io.cantCreateTempFile = Couldn't create temporary file
+io.cantEncode = Input contains characters not valid in the current file encoding
+io.commandFailed = E472: Command failed
+io.definedAt = Defined at
+io.downloadFinished-2 = Download of %S to %S finished
+io.eNotDir = Not a directory
 io.exists = File exists (add ! to override)
 io.exists-1 = File %S exists (add ! to override)
 io.noCommand-1 = Command not found: %S
-io.commandFailed = E472: Command failed
-io.oneFileAllowed = E172: Only one file name allowed
-io.callingShell-1 = Calling shell to execute: %S
-io.sourcing-1 = sourcing %S
-io.sourcingEnd-1 = finished sourcing %S
+io.noPrevDir = E186: No previous directory
+io.noSuchDir-1 = E344: Can't find directory %S
+io.noSuchFile = File does not exist
 io.notInRTP-1 = not found in 'runtimepath': %S
+io.notReadable-1 = Can't open file %S
+io.notWriteable-1 = Can't open file %S for writing
+io.oneFileAllowed = E172: Only one file name allowed
 io.searchingFor-1 = Searching for %S
 io.searchingFor-2 = Searching for %S in %S
-io.downloadFinished-2 = Download of %S to %S finished
+io.shellReturn-1 = shell returned %S
+io.sourcing-1 = sourcing %S
+io.sourcingEnd-1 = finished sourcing %S
+io.sourcingError-1 = Sourcing file: %S
 
 macro.canceled-1 = Canceled playback of macro '%S'
 macro.recorded-1 = Recorded macro '%S'
+macro.recorded-2 = Recorded macro %S: %S
 macro.loadFailed-1 = Page did not load completely in %S seconds
 macro.loadWaiting = Waiting for page to load...
 macro.noSuch-1 = Macro '%S' not set
@@ -136,19 +215,56 @@ mark.none = No marks set
 mark.invalid = Invalid mark
 mark.unset-1 = Mark not set: %S
 mark.noMatching-1 = E283: No marks matching %S
+# TODO: relies on mark formatter
+mark.addURL-1 = Adding URL mark: %S
+mark.addLocal-1 = Adding local mark: %S
+mark.jumpingToURL-1 = Jumping to URL mark: %S
+mark.jumpingToLocal-1 = Jumping to local mark: %S
 
 mode.recursiveSet = Not executing modes.set recursively
+mode.invalidBases = Invalid bases
 
 mow.noPreviousOutput = No previous command output
+mow.continue = Press ENTER or type command to continue
+mow.more = -- More --
+mow.moreHelp = -- More -- SPACE/<C-f>/j: screen/page/line down, <C-b>/<C-u>/k: up, q: quit
+
+mow.contextMenu.copyLink = Copy Link Location
+mow.contextMenu.copyPath = Copy File Path
+mow.contextMenu.copy = Copy
+mow.contextMenu.selectAll = Select All
 
 option.noSuch = No such option
 option.noSuch-1 = No such option: %S
 option.replaceExisting-1 = Warning: %S already exists: replacing existing option
+option.intRequired = Integer value required
+option.operatorNotSupported-2 = Operator %S not supported for option type %S
+option.notSet-1 = E764: Option '%S' is not set
+
+option.currentValue = Current Value
+option.defaultValue = Default Value
+
+option.bufferLocal = buffer local
+
+option.activate.safeSet = See the 'activate' option.
+option.guioptions.safeSet = See 'guioptions' scrollbar flags.
+option.hintkeys.duplicate = Duplicate keys not allowed
+# The string 'passkeys' must appear exactly, including U0027 apostrophes
+option.passkeys.passedBy = passed by 'passkeys'
+option.popups.safeSet = See the 'activate' option.
+option.showtabline.safeSet = See 'showtabline' option.
+option.visualbell.safeSet = See 'visualbell' option.
+
+pageinfo.s.ownerUnverified = %S (unverified)
 
 plugin.searchingFor-1 = Searching for %S
 plugin.searchingForIn-2 = Searching for %S in %S
 plugin.notReplacingContext-1 = Not replacing plugin context for %S
 
+pref.hostPreferences-1 = %S Preferences
+pref.prompt.resetAll-1 = Warning: Resetting all preferences may make %S unusable. Would you like to continue (yes/[no]):
+pref.safeSet.warnChanged-1 = Warning: Setting preference %S, but it's changed from its default value.
+
 print.toFile-1 = Printing to file: %S
 print.sending = Sending to printer...
 print.sent = Print job sent
@@ -160,14 +276,49 @@ quickmark.notSet = QuickMark not set
 quickmark.invalid = Argument must be an ASCII letter or digit
 quickmark.added-2 = Added Quick Mark '%S': %S
 
+sanitize.prompt.deleteAll = This will sanitize all items. Would you like to continue? (yes/[no]):
+
 save.invalidDestination-1 = Invalid destination: %S
 
+sort.ascending  = ascending
+sort.descending = descending
+
 status.link-1 = Link: %S
 
 style.none = No style found
+style.styles = styles
+style.inline = inline
 
 time.total-1 = Total time: %S
 
+title.Abbrev = Abbrev
+title.Action = Action
+title.Args = Args
+title.Average time = Average time
+title.CSS = CSS
+title.Code execution summary = Code execution summary
+title.Command = Command
+title.Complete = Complete
+title.Definition = Definition
+title.Description = Description
+title.Executed = Executed
+title.Filter = Filter
+title.Jump = Jump
+title.Mode = Mode
+title.Name = Name
+title.Progress = Progress
+title.Range = Range
+title.Replacement = Replacement
+title.Source = Source
+title.Speed = Speed
+title.Status = Status
+title.Time remaining = Time remaining
+title.Title = Title
+title.Total time = Total time
+title.Totals = Totals
+title.URI = URI
+title.Version = Version
+
 variable.none = No variables found
 
 window.cantAttachSame = Can't reattach to the same window
@@ -179,10 +330,14 @@ zoom.illegal = Illegal zoom value
 error.clipboardEmpty = No clipboard data
 error.countRequired-1 = Count required for %S
 error.cantOpen-2 = Error opening %S: %S
+error.error-1 = Error: %S
+error.disabled = Item is disabled
+error.disabled-1 = '%S' is disabled
 error.interrupted = Interrupted
 error.invalidSort-1 = Invalid sort order: %S
-error.trailing = Trailing characters
-error.trailing-1 = Trailing characters: %S
+error.missingQuote-1 = E114: Missing quote: %S
+error.trailingCharacters = Trailing characters
+error.trailingCharacters-1 = Trailing characters: %S
 error.invalid-1 = Invalid %S
 error.invalidArgument = Invalid argument
 error.invalidArgument-1 = Invalid argument: %S
@@ -192,7 +347,12 @@ error.argumentOrBang = Argument or ! required
 error.invalidOperation = Invalid operation
 error.monkeyPatchOverlay-1 = Not replacing property with eval-generated overlay by %S
 error.nullComputedStyle-1 = Computed style is null: %S
+error.syntaxError = Syntax error
+error.charactersOutsideRange-1 = Character list outside the range %S
+error.invalidCharacterRange = Invalid character range: %S
+error.notWriteable-2 = Could not write to %S: %S
 
+warn.deprecated-2 = %S is deprecated: Please use %S instead.
 warn.notDefaultBranch-2 = You are running %S from a testing branch: %S. Please do not report errors which do not also occur in the default branch.
 
 # vim:se ft=jproperties tw=0:
index 9f911abf1077a0a0f499d6ecfea6dcec80b69e94..3aa6f5a94d17df1ecf748bf13e08a0e1592d3412 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>
 
-<!DOCTYPE document SYSTEM "dactyl://content/dtd">
+<!DOCTYPE document SYSTEM "dactyl://content/options.dtd">
 
 <document
     name="options"
     the form of <tt>&lt;e-<a>name</a>></tt>, its value is never shown but may be
     used to test whether the given parameter is empty.
 </p>
+<p>
+    Array elements, such as in the <tt>&lt;args></tt> parameter
+    of <ex>:command</ex> macros, may be accessed by appending
+    <tt>[<a>n</a>]</tt>, where <a>n</a> is the one-based array
+    index, to the macro name. The first argument of a command is
+    therefore accessed with <tt>&lt;args[1]&gt;</tt>.
+</p>
 <p>
     Any substring enclosed by <em><tt>&lt;{</tt></em> and <em><tt>}></tt></em>
     is automatically elided if any of the contained macros aren't currently
 </item>
 
 <item>
+    <tags>:set!=</tags>
     <spec>:se<oa>t</oa> inv<a>option</a>=<a>value</a> <oa>...</oa></spec>
     <spec>:se<oa>t</oa> <a>option</a>!=<a>value</a> <oa>...</oa></spec>
     <description>
 </item>
 
 <item>
+    <tags>:set-default-all</tags>
     <spec>:se<oa>t</oa> all&amp;</spec>
     <description>
         <p>Set all options to their default value.</p>
 
 <item>
     <tags>:set! :set-!</tags>
-    <spec>:se<oa>t</oa>! <a>preference</a>=<a>value</a></spec>
-    <spec>:se<oa>t</oa>! <a>preference</a>&amp;</spec>
+    <spec>:se<oa>t</oa>! …</spec>
     <description>
         <p>
-            Change any &dactyl.host; <a>preference</a> (those on the about:config
-            page). You can also reset/delete these preferences with
-            <ex>:set! <a>preference</a>&amp;</ex>.
+            The same as <ex>:set</ex> but operates on &dactyl.host; preferences
+            (those on the about:config page). See <ex>:set</ex> for operator
+            details.
         </p>
+        <note>
+            The <em>no</em> and <em>inv</em> prefix operators are not available
+            for setting preferences.
+        </note>
     </description>
 </item>
 
     <tags>'act' 'activate'</tags>
     <strut/>
     <spec>'activate' 'act'</spec>
-    <type>stringlist</type>
+    <type>&option.activate.type;</type>
     <default>addons,bookmarks,diverted,downloads,extoptions,
           help,homepage,quickmark,tabopen,paste</default>
     <description>
 <item>
     <tags>'awim' 'altwildmode'</tags>
     <spec>'altwildmode' 'awim'</spec>
-    <type>stringlist</type>
-    <default>list:full</default>
+    <type>&option.altwildmode.type;</type>
+    <default>&option.altwildmode.default;</default>
     <description>
         <p>
             Like <o>wildmode</o>, but when the <k name="A-Tab" mode="c"/> key
 <item>
     <tags>'au' 'autocomplete'</tags>
     <spec>'autocomplete' 'au'</spec>
-    <type>regexplist</type>
-    <default>.*</default>
+    <type>&option.autocomplete.type;</type>
+    <default>&option.autocomplete.default;</default>
     <description>
         <p>
             Enables automatic completion for completion contexts (see
 <item>
     <tags>'bh' 'banghist'</tags>
     <spec>'banghist' 'bh'</spec>
-    <type>boolean</type>
-    <default>on</default>
+    <type>&option.banghist.type;</type>
+    <default>&option.banghist.default;</default>
     <description>
         <p>
             Replace occurrences of ! with the previous command when
     <tags>$CDPATH</tags>
     <tags>'cd' 'cdpath'</tags>
     <spec>'cdpath' 'cd'</spec>
-    <type>stringlist</type>
+    <type>&option.cdpath.type;</type>
     <default type="plain">equivalent to <str>.</str> or <str>.,$CDPATH</str></default>
     <description>
         <p>
 <item>
     <tags>'ca' 'cookieaccept'</tags>
     <spec>'cookieaccept' 'ca'</spec>
-    <type>string</type>
-    <default>all</default>
+    <type>&option.cookieaccept.type;</type>
+    <default>&option.cookieaccept.default;</default>
     <description>
         <p>When to accept cookies.</p>
 
 <item>
     <tags>'cl' 'cookielifetime'</tags>
     <spec>'cookielifetime'</spec>
-    <type>string</type>
-    <default>default</default>
+    <type>&option.cookielifetime.type;</type>
+    <default>&option.cookielifetime.default;</default>
     <description>
         <p>
             The lifetime for which to accept cookies. The available
 <item>
     <tags>'ck' 'cookies'</tags>
     <spec>'cookies' 'ck'</spec>
-    <type>stringlist</type> <default>session</default>
+    <type>&option.cookies.type;</type>
+    <default>&option.cookies.default;</default>
     <description>
         <p>The default action for the <ex>:cookies</ex> command.</p>
     </description>
 <item>
     <tags>'cpt' 'complete'</tags>
     <spec>'complete' 'cpt'</spec>
-    <type>charlist</type>
-    <default>slf</default>
+    <type>&option.complete.type;</type>
+    <default>&option.complete.default;</default>
     <description>
         <p>Items which are completed at the <ex>:open</ex> prompts. Available items:</p>
 
     </description>
 </item>
 
+<item>
+    <tags>'dls' 'dlsort' 'downloadsort'</tags>
+    <spec>'downloadsort'</spec>
+    <type>stringlist</type>
+    <default>-active,+filename</default>
+    <description>
+        <p>
+            <ex>:downloads</ex> sort order, in order of precedence.
+            Each element must be preceded by a <tt>+</tt> or
+            <tt>-</tt>, indicating ascending or descending sorting,
+            respectively. Valid sort orders are:
+        </p>
+
+        <dl>
+            <dt>active</dt>   <dd>Whether download is active</dd>
+            <dt>complete</dt> <dd>Percent complete</dd>
+            <dt>date</dt>     <dd>Date and time the download began</dd>
+            <dt>filename</dt> <dd>Target filename</dd>
+            <dt>size</dt>     <dd>File size</dd>
+            <dt>speed</dt>    <dd>Download speed</dd>
+            <dt>time</dt>     <dd>Time remaining</dd>
+            <dt>url</dt>      <dd>Source URL</dd>
+        </dl>
+    </description>
+</item>
+
+
 <item>
     <tags>'ds' 'defsearch'</tags>
     <spec>'defsearch' 'ds'</spec>
-    <type>string</type>
-    <default>google</default>
+    <type>&option.defsearch.type;</type>
+    <default>&option.defsearch.default;</default>
     <description>
         <p>
             Sets the default search engine. The default search engine is
 <item>
     <tags>'editor'</tags>
     <spec>'editor'</spec>
-    <type>string</type>
-    <default><![CDATA[gvim -f +<line> <file>]]></default>
+    <type>&option.editor.type;</type>
+    <default>&option.editor.default;</default>
     <description>
         <p>
             Set the external text editor.
 <item>
     <tags>'enc' 'encoding'</tags>
     <spec>'encoding' 'enc'</spec>
-    <type>string</type>
-    <default>UTF-8</default>
+    <type>&option.encoding.type;</type>
+    <default>&option.encoding.default;</default>
     <description>
         <p>
             Changes the character encoding of the current buffer. Valid only
     <tags>'noeb' 'noerrorbells'</tags>
     <tags>'eb' 'errorbells'</tags>
     <spec>'errorbells' 'eb'</spec>
-    <type>boolean</type>
-    <default>off</default>
+    <type>&option.errorbells.type;</type>
+    <default>&option.errorbells.default;</default>
     <description>
         <p>
             Ring the bell when an error message is displayed. See also
 <item>
     <tags>'ei' 'eventignore'</tags>
     <spec>'eventignore' 'ei'</spec>
-    <type>stringlist</type>
-    <default></default>
+    <type>&option.eventignore.type;</type>
+    <default>&option.eventignore.default;</default>
     <description>
         <p>
             A list of autocommand event names which should be ignored. If the
     <tags>'noex' 'noexrc'</tags>
     <tags>'ex' 'exrc'</tags>
     <spec>'exrc' 'ex'</spec>
-    <type>boolean</type>
-    <default>off</default>
+    <type>&option.exrc.type;</type>
+    <default>&option.exrc.default;</default>
     <description>
         <p>
             Allow reading of an RC file in the current directory. This file is
     <tags>'eht' 'extendedhinttags'</tags>
     <spec>'extendedhinttags' 'eht'</spec>
     <strut/>
-    <type>regexpmap</type>
-    <default>[asOTivVWy]:a[href],area[href],img[src],iframe[src],
+    <type>&option.extendedhinttags.type;</type>
+    <default>[asOTvVWy]:a[href],area[href],img[src],iframe[src],
           [f]:body,
           [F]:body,code,div,html,p,pre,span,
           [iI]:img,
 <item>
     <tags>'fenc' 'fileencoding'</tags>
     <spec>'fileencoding' 'fenc'</spec>
-    <type>string</type>
-    <default>UTF-8</default>
+    <type>&option.fileencoding.type;</type>
+    <default>&option.fileencoding.default;</default>
     <description>
         <p>
             Changes the character encoding that &dactyl.appName; uses to read
 <item>
     <tags>'fc' 'findcase'</tags>
     <spec>'findcase' 'fc'</spec>
-    <type>string</type>
-    <default>smart</default>
+    <type>&option.findcase.type;</type>
+    <default>&option.findcase.default;</default>
     <description>
         <p>Find case matching mode.</p>
 
 <item>
     <tags>'fh' 'followhints'</tags>
     <spec>'followhints' 'fh'</spec>
-    <type>number</type>
-    <default>0</default>
+    <type>&option.followhints.type;</type>
+    <default>&option.followhints.default;</default>
     <description>
-        <p>Changes how soon matching hints are followed in Hints mode.</p>
+        <p>
+            Define the conditions under which hints selected by typing the link
+            substring are followed. Hints selected by typing their label (as
+            specified by <o>hintkeys</o>) are always followed immediately.
+        </p>
 
         <p>Possible values:</p>
 
         <dl dt="width: 6em;">
             <dt>0</dt>      <dd>Follow the first hint as soon as typed text uniquely identifies it.</dd>
             <dt>1</dt>      <dd>Follow the selected hint on <k name="CR"/>.</dd>
-            <dt>2</dt>      <dd>Follow the selected hint on <k name="CR"/> only if it's been <k name="Tab" mode="c"/>-selected.</dd>
         </dl>
     </description>
 </item>
     <tags>'nofs' 'nofullscreen'</tags>
     <tags>'fs' 'fullscreen'</tags>
     <spec>'fullscreen' 'fs'</spec>
-    <type>boolean</type>
-    <default>off</default>
+    <type>&option.fullscreen.type;</type>
+    <default>&option.fullscreen.default;</default>
     <description>
         <p>
             Show the current window full-screen. Also hide certain GUI elements, such as
 <item>
     <tags>'go' 'guioptions'</tags>
     <spec>'guioptions' 'go'</spec>
-    <type>charlist</type>
-    <default>bCrs</default> <!-- TODO: make this config specific -->
+    <type>&option.guioptions.type;</type>
+    <default>&option.guioptions.default;</default>
     <description>
         <p>Show or hide certain GUI elements.</p>
 
 <item>
     <tags>'hf' 'helpfile'</tags>
     <spec>'helpfile' 'hf'</spec>
-    <type>string</type>
-    <default>intro</default>
+    <type>&option.helpfile.type;</type>
+    <default>&option.helpfile.default;</default>
     <description>
         <p>
             Name of the main help file. This is that page shown if the
 <item>
     <tags>'hin' 'hintinputs'</tags>
     <spec>'hintinputs' 'hin'</spec>
-    <type>stringlist</type>
-    <default>label,value</default>
+    <type>&option.hintinputs.type;</type>
+    <default>&option.hintinputs.default;</default>
     <description>
         <p>
             When generating hints for input elements that do not have an
 <item>
     <tags>'hk' 'hintkeys'</tags>
     <spec>'hintkeys' 'hk'</spec>
-    <type>string</type>
-    <default>0123456789</default>
+    <type>&option.hintkeys.type;</type>
+    <default>&option.hintkeys.default;</default>
     <description>
         <p>
             The keys used to label and select hints. With its default value,
 <item>
     <tags>'hm' 'hintmatching'</tags>
     <spec>'hintmatching' 'hm'</spec>
-    <type>stringlist</type>
-    <default>contains</default>
+    <type>&option.hintmatching.type;</type>
+    <default>&option.hintmatching.default;</default>
     <description>
         <p>Change the hint matching algorithm used in Hints mode.</p>
 
     <tags>'ht' 'hinttags'</tags>
     <strut/>
     <spec>'hinttags' 'ht'</spec>
-    <type>stringlist</type>
+    <type>&option.hinttags.type;</type>
     <default>a,area,button,iframe,input:not([type=hidden]),select,textarea,
           [onclick],[onmouseover],[onmousedown],[onmouseup],[oncommand],
           [tabindex],[role=link],[role=button]</default>
 <item>
     <tags>'hto' 'hinttimeout'</tags>
     <spec>'hinttimeout' 'hto'</spec>
-    <type>number</type>
-    <default>0</default>
+    <type>&option.hinttimeout.type;</type>
+    <default>&option.hinttimeout.default;</default>
     <description>
         <p>
             Timeout in milliseconds before automatically following a non-unique
 <item>
     <tags>'hi' 'history'</tags>
     <spec>'history' 'hi'</spec>
-    <type>number</type>
-    <default>500</default>
+    <type>&option.history.type;</type>
+    <default>&option.history.default;</default>
     <description>
         <p>
             Maximum number of Ex commands and find patterns to store in the
     <tags>'nohlf' 'nohlfind'</tags>
     <tags>'hlf' 'hlfind'</tags>
     <spec>'hlfind' 'hlf'</spec>
-    <type>boolean</type>
-    <default>off</default>
+    <type>&option.hlfind.type;</type>
+    <default>&option.hlfind.default;</default>
     <description>
         <p>Highlight previous find pattern matches.</p>
     </description>
     <tags>'noif' 'noincfind'</tags>
     <tags>'if' 'incfind'</tags>
     <spec>'incfind' 'if'</spec>
-    <type>boolean</type>
-    <default>on</default>
+    <type>&option.incfind.type;</type>
+    <default>&option.incfind.default;</default>
     <description>
         <p>Show the first match for a find pattern as it is typed.</p>
     </description>
     <tags>'noim' 'noinsertmode'</tags>
     <tags>'im' 'insertmode'</tags>
     <spec>'insertmode' 'im'</spec>
-    <type>boolean</type>
-    <default>on</default>
+    <type>&option.insertmode.type;</type>
+    <default>&option.insertmode.default;</default>
     <description>
         <p>
             Use Insert mode as the default for text areas. This is useful if you
         </p>
 
         <p>
-            TextEdit mode can be entered with <k name="C-t" mode="I"/> from Insert mode.
+            Text Edit mode can be entered with <k name="C-t" mode="I"/> from Insert mode.
         </p>
     </description>
 </item>
     <tags>'nojsd' 'nojsdebugger'</tags>
     <tags>'jsd' 'jsdebugger'</tags>
     <spec>'jsdebugger' 'jsd'</spec>
-    <type>boolean</type>
-    <default>off</default>
+    <type>&option.jsdebugger.type;</type>
+    <default>&option.jsdebugger.default;</default>
     <description>
         <p>
             Use the JavaScript debugger service for JavaScript completion.
 </item>
 
 <item>
-    <tags>'nolpl' 'noloadplugins'</tags>
+    <tags>'jt' 'jumptags'</tags>
+    <spec>'jumptags'</spec>
+    <type>&option.jumptags.type;</type>
+    <default>&option.jumptags.default;</default>
+    <description>
+        <p>XPath or CSS selector strings of jumpable elements for extended hint modes.</p>
+    </description>
+</item>
+
+<item>
     <tags>'lpl' 'loadplugins'</tags>
     <spec>'loadplugins' 'lpl'</spec>
-    <type>regexplist</type>
-    <default>'\.(js|&dactyl.fileExt;)$'</default>
+    <type>&option.loadplugins.type;</type>
+    <default>&option.loadplugins.default;</default>
     <description>
         <p>
             A regular expression list that defines which plugins are loaded at
 <item>
     <tags>'ml' 'mapleader'</tags>
     <spec>'mapleader' 'ml'</spec>
-    <type>string</type>
-    <default>\</default>
+    <type>&option.mapleader.type;</type>
+    <default>&option.mapleader.default;</default>
     <description>
         <p>Defines the replacement keys for the <k name="Leader"/> pseudo-key.</p>
     </description>
 <item>
     <tags>'maxitems'</tags>
     <spec>'maxitems'</spec>
-    <type>number</type>
-    <default>20</default>
+    <type>&option.maxitems.type;</type>
+    <default>&option.maxitems.default;</default>
     <description>
         <p>Maximum number of items to display at once in a listing.</p>
     </description>
 <item>
     <tags>'msgs' 'messages'</tags>
     <spec>'messages' 'msgs'</spec>
-    <type>number</type>
-    <default>100</default>
+    <type>&option.messages.type;</type>
+    <default>&option.messages.default;</default>
     <description>
         <p>Maximum number of messages to store in the message history.</p>
     </description>
 <item>
     <tags>'nomore' 'more'</tags>
     <spec>'more'</spec>
-    <type>boolean</type>
-    <default>on</default>
+    <type>&option.more.type;</type>
+    <default>&option.more.default;</default>
     <description>
         <p>
             Pause the message list window when more than one screen of
 <item>
     <tags>'newtab'</tags>
     <spec>'newtab'</spec>
-    <type>stringlist</type>
-    <default></default>
+    <type>&option.newtab.type;</type>
+    <default>&option.newtab.default;</default>
     <description>
         <p>
             Defines which Ex commands open pages in new tabs rather than the
     <tags>'nextpattern'</tags>
     <strut/>
     <spec>'nextpattern'</spec>
-    <type>stringlist</type>
-    <default>'\bnext',^>$,'^(>>|»)$','^(>|»)','(>|»)$','\bmore\b'</default>
+    <type>&option.nextpattern.type;</type>
+    <default>&option.nextpattern.default;</default>
     <description>
         <p>
             Patterns to use when guessing the next page in a document
 <item>
     <tags>'noonline' 'online'</tags>
     <spec>'online'</spec>
-    <type>boolean</type>
-    <default>on</default>
+    <type>&option.online.type;</type>
+    <default>&option.online.default;</default>
     <description>
         <p>
             Enables or disables ‘offline’ mode, where network access is
 <item>
     <tags>'pa' 'pageinfo'</tags>
     <spec>'pageinfo' 'pa'</spec>
-    <type>charlist</type>
-    <default>gfm</default>
+    <type>&option.pageinfo.type;</type>
+    <default>&option.pageinfo.default;</default>
     <description>
         <p>Info shown in the <ex>:pageinfo</ex> output.</p>
 
 
         <dl dt="width: 6em;">
             <dt>g</dt>      <dd>General info</dd>
+            <dt>e</dt>      <dd>Search Engines</dd>
             <dt>f</dt>      <dd>Feeds</dd>
             <dt>m</dt>      <dd>Meta tags</dd>
+            <dt>s</dt>      <dd>Security information</dd>
         </dl>
 
         <p>
 <item>
     <tags>'pk' 'passkeys'</tags>
     <spec>'passkeys' 'pk'</spec>
-    <type>sitemap</type>
-    <default/>
+    <type>&option.passkeys.type;</type>
+    <default>&option.passkeys.default;</default>
     <description>
         <p>
             Pass certain keys through directly for the given URLs.
     </description>
 </item>
 
+<item>
+    <tags>'pu' 'passunknown'</tags>
+    <spec>'passunknown' 'pu'</spec>
+    <type>&option.showmode.type;</type>
+    <default>&option.showmode.default;</default>
+    <description>
+        <p>
+            Pass unknown keys through to &dactyl.host; in these
+            <t>modes</t>. The first element matching a currently
+            active mode is the one that takes effect. Modes may be
+            negated by prefixing them with a <tt>!</tt>.
+        </p>
+    </description>
+</item>
+
 <item>
     <tags>'pps' 'popups'</tags>
     <spec>'popups' 'pps'</spec>
-    <type>stringlist</type>
-    <default>tab</default>
+    <type>&option.popups.type;</type>
+    <default>&option.popups.default;</default>
     <description>
         <p>
             Defines where to show requested pop-up windows. Applies only to
     <tags>'previouspattern'</tags>
     <strut/>
     <spec>'previouspattern'</spec>
-    <type>stringlist</type>
-    <default><![CDATA['\bprev|previous\b',^<$,'^(<<|«)$','^(<|«)','(<|«)$']]></default>
+    <type>&option.previouspattern.type;</type>
+    <default>&option.previouspattern.default;</default>
     <description>
         <p>
             Patterns to use when guessing the previous page in a document
 <item>
     <tags>'noprivate' 'private'</tags>
     <spec>'private'</spec>
-    <type>boolean</type>
-    <default>off</default>
+    <type>&option.private.type;</type>
+    <default>&option.private.default;</default>
     <description>
         <p>
             Set the <str>private browsing</str> option. In private browsing mode
     <tags>$&dactyl.idName;_RUNTIME</tags>
     <tags>'rtp' 'runtimepath'</tags>
     <spec>'runtimepath' 'rtp'</spec>
-    <type>stringlist</type>
+    <type>&option.runtimepath.type;</type>
     <default type="plain"><str>$&dactyl.idName;_RUNTIME</str> or
        Unix, Mac: <str>~/.&dactyl.name;</str>
          Windows: <str>~/&dactyl.name;</str></default>
     <tags>'si' 'sanitizeitems'</tags>
     <spec>'sanitizeitems' 'si'</spec>
     <strut/>
-    <type>stringlist</type>
-    <default>all</default>
+    <type>&option.sanitizeitems.type;</type>
+    <default>&option.sanitizeitems.default;</default>
     <description>
         <p>
             The default list of private items to sanitize. See
 <item>
     <tags>'ss' 'sanitizeshutdown'</tags>
     <spec>'sanitizeshutdown' 'ss'</spec>
-    <type>stringlist</type>
-    <default/>
+    <type>&option.sanitizeshutdown.type;</type>
+    <default>&option.sanitizeshutdown.default;</default>
     <description>
         <p>The items to sanitize automatically at shutdown.</p>
     </description>
     <tags>'sts' 'sanitizetimespan'</tags>
     <spec>'sanitizetimespan' 'sts'</spec>
     <strut/>
-    <type>number</type>
-    <default>all</default>
+    <type>&option.sanitizetimespan.type;</type>
+    <default>&option.sanitizetimespan.default;</default>
     <description>
         <p>
             The default sanitizer time span. Only items created within this timespan are
 <item>
     <tags>'scr' 'scroll'</tags>
     <spec>'scroll' 'scr'</spec>
-    <type>number</type>
-    <default>0</default>
+    <type>&option.scroll.type;</type>
+    <default>&option.scroll.default;</default>
     <description>
         <p>
             Number of lines to scroll with <k name="C-u"/> and <k name="C-d"/>
 <item>
     <tags>'sh' 'shell'</tags>
     <spec>'shell' 'sh'</spec>
-    <type>string</type>
+    <type>&option.shell.type;</type>
     <default type="plain"><em>$SHELL</em> or <str>sh</str>, Windows: <str>cmd.exe</str></default>
     <description>
         <p>Shell to use for executing <ex>:!</ex> and <ex>:run</ex> commands.</p>
     <tags>'shcf' 'shellcmdflag'</tags>
     <spec>'shellcmdflag' 'shcf'</spec>
     <strut/>
-    <type>string</type>
+    <type>&option.shellcmdflag.type;</type>
     <default type="plain"><str>-c</str>, Windows: <str>/c</str></default>
     <description>
         <p>Flag passed to shell when executing <ex>:!</ex> and <ex>:run</ex> commands.</p>
 </item>
 
 <item>
-    <tags>'nosmd' 'noshowmode'</tags>
     <tags>'smd' 'showmode'</tags>
     <spec>'showmode' 'smd'</spec>
-    <type>regexplist</type>
-    <default>!^normal$</default>
+    <type>&option.showmode.type;</type>
+    <default>&option.showmode.default;</default>
     <description>
-        <p>Show the current mode in the command line if it matches this expression.</p>
+        <p>
+            Show the current mode in the command line if it or any
+            of its parent <t>modes</t> is included in the list.
+            Modes may be negated by prefixing them with a
+            <tt>!</tt>.
+        </p>
     </description>
 </item>
 
     <tags>'ssli' 'showstatuslinks'</tags>
     <spec>'showstatuslinks' 'ssli'</spec>
     <strut/>
-    <type>string</type>
-    <default>status</default>
+    <type>&option.showstatuslinks.type;</type>
+    <default>&option.showstatuslinks.default;</default>
     <description>
         <p>
             When the mouse hovers over a link, or a link is otherwise focused,
 <item>
     <tags>'stal' 'showtabline'</tags>
     <spec>'showtabline' 'stal'</spec>
-    <type>string</type>
-    <default>always</default>
+    <type>&option.showtabline.type;</type>
+    <default>&option.showtabline.default;</default>
     <description>
         <p>Define when the tab bar is visible.</p>
 
 </item>
 
 <item>
-    <tags>'nosf' 'nostrictfocus'</tags>
     <tags>'sf' 'strictfocus'</tags>
     <spec>'strictfocus' 'sf'</spec>
-    <type>boolean</type>
-    <default>on</default>
+    <type>&option.strictfocus.type;</type>
+    <default>&option.strictfocus.default;</default>
     <description>
         <p>
             Prevent scripts from focusing input elements without user intervention.
         </p>
+
+        <p>Possible values:</p>
+
+        <dl>
+            <dt>despotic</dt>      <dd>Only allow focus changes when explicitly requested by the user</dd>
+            <dt>laissez-faire</dt> <dd>Always allow focus changes</dd>
+            <dt>moderate</dt>      <dd>Allow focus changes after user-initiated focus change</dd>
+        </dl>
     </description>
 </item>
 
 <item>
     <tags>'suggestengines'</tags>
     <spec>'suggestengines'</spec>
-    <type>stringlist</type>
-    <default>google</default>
+    <type>&option.suggestengines.type;</type>
+    <default>&option.suggestengines.default;</default>
     <description>
         <p>
             Set the search engines which can be used for completion
     <tags>'notmo' 'notimeout'</tags>
     <tags>'tmo' 'timeout'</tags>
     <spec>'timeout' 'tmo'</spec>
-    <type>boolean</type>
-    <default>true</default>
+    <type>&option.timeout.type;</type>
+    <default>&option.timeout.default;</default>
     <description>
         <p>
             When this option is set and a key sequence interpretable both as a
 <item>
     <tags>'tmol' 'timeoutlen'</tags>
     <spec>'timeoutlen' 'tmol'</spec>
-    <type>number</type>
-    <default>1000</default>
+    <type>&option.timeoutlen.type;</type>
+    <default>&option.timeoutlen.default;</default>
     <description>
         <p>
             Maximum number of milliseconds to wait for a longer key command
 <item>
     <tags>'titlestring'</tags>
     <spec>'titlestring'</spec>
-    <type>string</type>
-    <default>&dactyl.appName;</default>
+    <type>&option.titlestring.type;</type>
+    <default>&option.titlestring.default;</default>
     <description>
         <p>
             Set the application name shown after the current page title in
 <item>
     <tags>'us' 'urlsep' 'urlseparator'</tags>
     <spec>'urlseparator' 'urlsep' 'us'</spec>
-    <type>string</type>
-    <default>\|</default>
+    <type>&option.urlseparator.type;</type>
+    <default>&option.urlseparator.default;</default>
     <description>
         <p>
             The regular expression used to split URL lists in commands
     <tags>'noum' 'nousermode'</tags>
     <tags>'um' 'usermode'</tags>
     <spec>'usermode' 'um'</spec>
-    <type>boolean</type>
-    <default>off</default>
+    <type>&option.usermode.type;</type>
+    <default>&option.usermode.default;</default>
     <description>
         <p>Show current website with minimal styling.</p>
     </description>
 <item>
     <tags>'vbs' 'verbose'</tags>
     <spec>'verbose' 'vbs'</spec>
-    <type>number</type>
-    <default>1</default>
+    <type>&option.verbose.type;</type>
+    <default>&option.verbose.default;</default>
     <description>
         <p>
             Define which info messages are displayed. As the value increases,
     <tags>'novb' 'novisualbell'</tags>
     <tags>'vb' 'visualbell'</tags>
     <spec>'visualbell' 'vb'</spec>
-    <type>boolean</type>
-    <default>off</default>
+    <type>&option.visualbell.type;</type>
+    <default>&option.visualbell.default;</default>
     <description>
         <p>
             Use visual bell instead of beeping on errors. The visual bell
     <tags>'wia' 'wildanchor'</tags>
     <strut/>
     <spec>'wildanchor' 'wia'</spec>
-    <type>regexplist</type>
-    <default>!'/ex/(back|buffer|ext|forward|help|undo)'</default>
+    <type>&option.wildanchor.type;</type>
+    <default>&option.wildanchor.default;</default>
     <description>
         <p>
             Regular expression list defining which completion groups show only
 <item>
     <tags>'wic' 'wildcase'</tags>
     <spec>'wildcase' 'wic'</spec>
-    <type>regexpmap</type>
-    <default>.?:smart</default>
+    <type>&option.wildcase.type;</type>
+    <default>&option.wildcase.default;</default>
     <description>
         <p>
             Defines how completions are matched with regard to character case.
 <item>
     <tags>'wig' 'wildignore'</tags>
     <spec>'wildignore' 'wig'</spec>
-    <type>regexplist</type>
-    <default></default>
+    <type>&option.wildignore.type;</type>
+    <default>&option.wildignore.default;</default>
     <description>
         <p>
-            List of file patterns to ignore when completing files. For example,
-            the following will ignore object files and Vim swap files:
+            List of path name patterns to ignore when completing files and
+            directories. For example, the following will ignore object files
+            and Vim swap files:
         </p>
 
         <set opt="wildignore"><str delim="'">\.o$</str>,<str delim="'">^\..*\.s[a-z]{2}$</str></set>
 <item>
     <tags>'wim' 'wildmode'</tags>
     <spec>'wildmode' 'wim'</spec>
-    <type>stringlist</type>
-    <default>list:full</default>
+    <type>&option.wildmode.type;</type>
+    <default>&option.wildmode.default;</default>
     <description>
         <p>
             Defines how command-line completion works. It is a comma-separated
 <item>
     <tags>'wis' 'wildsort'</tags>
     <spec>'wildsort' 'wis'</spec>
-    <type>regexplist</type>
-    <default>.*</default>
+    <type>&option.wildsort.type;</type>
+    <default>&option.wildsort.default;</default>
     <description>
         <p>
             A list of regular expressions defining which completion contexts
     <tags>'wsp' 'wordseparators'</tags>
     <spec>'wordseparators' 'wsp'</spec>
     <strut/>
-    <type>string</type>
-    <default><![CDATA[[.,!?:;\\/"^$%&?()[\]{}<>#*+|=~ _-]]]></default>
+    <type>&option.wordseparators.type;</type>
+    <default>&option.wordseparators.default;</default>
     <description>
         <p>
             A regular expression which defines how words are split for
index e874701268d66c8fff6c3ead0ee41e22fb99b488..6ecac3a04a31d9ea0ed37fcff5e710d5e50119dc 100644 (file)
@@ -45,8 +45,8 @@
 </p>
 
 <item>
-    <tags>/</tags>
-    <spec>/<a>pattern</a><oa>/</oa><k name="CR"/></spec>
+    <tags><![CDATA[<find-forward> /]]></tags>
+    <spec>/<a>pattern</a><k name="CR"/></spec>
     <description>
         <p>Find <a>pattern</a> starting at the current caret position.</p>
 
@@ -75,8 +75,8 @@
 </item>
 
 <item>
-    <tags>?</tags>
-    <spec>?<a>pattern</a><oa>?</oa><k name="CR"/></spec>
+    <tags><![CDATA[<find-forward> ?]]></tags>
+    <spec>?<a>pattern</a><k name="CR"/></spec>
     <description>
         <p>
             Find a pattern backward of the current caret position in exactly the
@@ -86,7 +86,7 @@
 </item>
 
 <item>
-    <tags>n</tags>
+    <tags><![CDATA[<find-next> n]]></tags>
     <spec>n</spec>
     <description short="true">
         <p>Find next. Repeat the last find.</p>
@@ -94,7 +94,7 @@
 </item>
 
 <item>
-    <tags>N</tags>
+    <tags><![CDATA[<find-previous> N]]></tags>
     <spec>N</spec>
     <description short="true">
         <p>Find previous. Repeat the last find in the opposite direction.</p>
 </item>
 
 <item>
-    <tags>*</tags>
+    <tags><![CDATA[<find-word-next> *]]></tags>
     <spec>*</spec>
     <description short="true">
         <p>Search forward for the next occurrence of the word under cursor.</p>
 </item>
 
 <item>
-    <tags>#</tags>
+    <tags><![CDATA[<find-word-previous> #]]></tags>
     <spec>#</spec>
     <description short="true">
         <p>Search backward for the previous occurrence of the word under cursor.</p>
index 84dafa9860f66323784f1b773f65d6d333cf52bb..7782ff99cc0cabef9afed00b5a9fcf1b2718f8c7 100644 (file)
@@ -21,7 +21,7 @@
     sensitive data.
 </p>
 
-<h2 tag="private-mode porn-mode">Private mode browsing</h2>
+<h2 tag="private-mode porn-mode">Private browsing</h2>
 
 <p>
     &dactyl.appName; fully supports &dactyl.host;'s private browsing mode.
@@ -92,7 +92,7 @@
 
         <note>
             The following items are always cleared entirely, regardless of
-            <a>timeframe</a>: <em>cache</em>, <em>host</em>, <em>offlineapps</em>,
+            <a>timespan</a>: <em>cache</em>, <em>host</em>, <em>offlineapps</em>,
             <em>passwords</em>, <em>sessions</em>, <em>sitesettings</em>.
             Conversely, <em>host</em> and <em>options</em> are never cleared
             unless a host is specified.
index b14b8a8a3c4b743eec7f19cbba748864ee7fc1d5..c8696839a72da79e1fe9cb1a59f52dc2dac3641c 100644 (file)
     </description>
 </item>
 
+<item>
+    <tags>:delgr :delgroup</tags>
+    <spec>:delgr<oa>oup</oa> <a>group</a></spec>
+    <spec>:delgr<oa>oup</oa>!</spec>
+    <description>
+        <p>
+            Delete the specified <a>group</a>. With <oa>!</oa> delete all
+            user groups.
+        </p>
+    </description>
+</item>
 
 <h2 tag="site-filter site-filters">Site Filters</h2>
 
 </item>
 
 <item>
-    <tags>:ru :runtime</tags>
+    <tags>:runt :runtime</tags>
     <spec>:runt<oa>ime</oa><oa>!</oa> <a>file</a> …</spec>
     <description>
         <p>
 
 <p tag="comments">
     Lines may be commented out by prefixing them with a <em>"</em>
-    character.
+    character. Comments and commands cannot both occur in a single command
+    line.
 </p>
 
 <code>            <hl style="color: #444">" This is a comment</hl>
-    foo bar <hl style="color: #444">" This is a comment</hl>
+    foo bar " This is a syntax error
             <str> This is not a comment</str>
     foo bar <str> This is not a comment</str>
 </code>
index 2a6d33d19f094f1b322a8fb3c05d827085b14964..389355045afa80f5ed170b4fa01ec75090d79d2e 100644 (file)
     </li>
     <li>
         <p>
-            If <o>exrc</o> is set and the +u command-line option was not
+            If <o>exrc</o> is set and the <t>+u</t> command-line option was not
             specified, then any RC file in the current directory is also
             sourced.
         </p>
         </p>
 
         <ul>
-            <li><o>noloadplugins</o> is set,</li>
+            <li><o>loadplugins</o> is unset,</li>
             <li>the <t>++noplugin</t> command-line option was specified, or</li>
             <li>the <tt><t>+u</t>=NONE</tt> command-line option was specified.</li>
         </ul>
 
 <h2 tag="restarting">Restarting</h2>
 
+<item>
+    <tags>:reh :rehash</tags>
+    <spec>:reh<oa>ash</oa> <oa>arg</oa> …</spec>
+    <description>
+        <p>
+            Reload the &dactyl.appName; add-on, including all code, plugins,
+            and configuration. For users running directly from the development
+            repository, this is a good way to update to the latest version or
+            to test your changes.
+        </p>
+        <p>
+            Any arguments supplied are parsed as command-line arguments as
+            specified in <t>startup-options</t>.
+        </p>
+        <warning>
+            Not all plugins are designed to cleanly un-apply during a rehash.
+            While official plugins are safe, beware of possible instability
+            if you rehash while running third-party plugins.
+        </warning>
+    </description>
+</item>
+
 <item>
     <tags>:res :restart</tags>
     <spec>:res<oa>tart</oa></spec>
index eda39bbff62166614e9e51d2e06b51b93af97f16..0ff26336e4ce6b98cdd07747a6647f6bfc568b40 100644 (file)
@@ -27,6 +27,8 @@
         <p>
             Load a color scheme. <a>name</a> is found by searching the <o>runtimepath</o> for the
             first file matching <tt>colors/<a>name</a>.&dactyl.fileExt;</tt>.
+            The special scheme <em>default</em> can be used to reload the
+            default highlight settings.
         </p>
 
         <p>
 
         <p>Valid groups include:</p>
 
-        <dl>
-            <dt>Bell</dt>              <dd>&dactyl.appName;'s visual bell</dd>
-            <dt>Boolean</dt>           <dd>A JavaScript Boolean object</dd>
-            <dt>CmdLine</dt>           <dd>The command line</dd>
-            <dt>CmdOutput</dt>         <dd>The output of commands executed by <ex>:run</ex></dd>
-            <dt>CompDesc</dt>          <dd>The description column of the completion list</dd>
-            <dt>CompGroup</dt>         <dd>The top-level container for a group of completion results</dd>
-            <dt>CompIcon</dt>          <dd>The favicon of a completion row</dd>
-            <dt>CompItem</dt>          <dd>A row of completion list</dd>
-            <dt>CompItem[selected]</dt><dd>A selected row of completion list</dd>
-            <dt>CompLess::after</dt>   <dd>The character of indicator shown when completions may be scrolled up</dd>
-            <dt>CompLess</dt>          <dd>The indicator shown when completions may be scrolled up</dd>
-            <dt>CompMore::after</dt>   <dd>The character of indicator shown when completions may be scrolled down</dd>
-            <dt>CompMore</dt>          <dd>The indicator shown when completions may be scrolled down</dd>
-            <dt>CompMsg</dt>           <dd>The message which may appear at the top of a group of completion results</dd>
-            <dt>CompResult</dt>        <dd>The result column of the completion list</dd>
-            <dt>CompTitle</dt>         <dd>Completion row titles</dd>
-            <dt>CompTitleSep</dt>      <dd>The element which separates the completion title from its results</dd>
-            <dt>Disabled</dt>          <dd>Text indicating disabled status, such as of an extension or style group</dd>
-            <dt>Enabled</dt>           <dd>Text indicating enabled status, such as of an extension or style group</dd>
-            <dt>ErrorMsg</dt>          <dd>Error messages</dd>
-            <dt>Filter</dt>            <dd>The matching text in a completion list</dd>
-            <dt>FrameIndicator</dt>    <dd>The indicator shown when a new frame is selected</dd>
-            <dt>Function</dt>          <dd>A JavaScript Function object</dd>
-            <dt>Hint</dt>              <dd>A hint indicator. See <ex>:help hints</ex></dd>
-            <dt>HintActive</dt>        <dd>The hint element of link which will be followed by <k name="CR"/></dd>
-            <dt>HintElem</dt>          <dd>The hintable element</dd>
-            <dt>HintImage</dt>         <dd>The indicator which floats above hinted images</dd>
-            <dt>Indicator</dt>         <dd>The <em>#</em> and  <em>%</em> in the <ex>:buffers</ex> list</dd>
-            <dt>InfoMsg</dt>           <dd>Information messages</dd>
-            <dt>Key</dt>               <dd>Generally a keyword used in syntax highlighting.</dd>
-            <dt>Keyword</dt>           <dd>A bookmark keyword for a URL</dd>
-            <dt>LineNr</dt>            <dd>The line number of an error</dd>
-            <dt>Message</dt>           <dd>A message as displayed in <ex>:messages</ex></dd>
-            <dt>ModeMsg</dt>           <dd>The mode indicator in the command line</dd>
-            <dt>MoreMsg</dt>           <dd>The indicator that there is more text to view</dd>
-            <dt>NonText</dt>           <dd>The <em>~</em> indicators which mark blank lines in the completion list</dd>
-            <dt>Normal</dt>            <dd>Normal text in the command line</dd>
-            <dt>Null</dt>              <dd>A JavaScript Null object</dd>
-            <dt>Number</dt>            <dd>A JavaScript Number object</dd>
-            <dt>Object</dt>            <dd>A JavaScript Object</dd>
-            <dt>Preview</dt>           <dd>The completion preview displayed in the &tag.command-line;</dd>
-            <dt>Question</dt>          <dd>A prompt for a decision</dd>
-            <dt>StatusLine</dt>        <dd>The status bar</dd>
-            <dt>StatusLineNormal</dt>  <dd>The status bar for an ordinary web page</dd>
-            <dt>StatusLineBroken</dt>  <dd>The status bar for a broken web page</dd>
-            <dt>StatusLineExtended</dt><dd>The status bar for a secure web page with an Extended Validation (EV) certificate</dd>
-            <dt>StatusLineSecure</dt>  <dd>The status bar for a secure web page</dd>
-            <dt>String</dt>            <dd>A JavaScript String object</dd>
-            <dt>TabClose</dt>          <dd>The close button of a browser tab</dd>
-            <dt>TabIcon</dt>           <dd>The icon of a browser tab</dd>
-            <dt>TabIconNumber</dt>     <dd>The number of a browser tab, over its icon</dd>
-            <dt>TabNumber</dt>         <dd>The number of a browser tab, next to its icon</dd>
-            <dt>TabText</dt>           <dd>The text of a browser tab</dd>
-            <dt>Tag</dt>               <dd>A bookmark tag for a URL</dd>
-            <dt>Title</dt>             <dd>The title of a listing, including <ex>:pageinfo</ex>, <ex>:jumps</ex></dd>
-            <dt>URL</dt>               <dd>A URL</dd>
-            <dt>WarningMsg</dt>        <dd>A warning message</dd>
+        <dl dt="width: 12em;">
+            <dt>Addon</dt>                      <dd>An add-on in the <ex>:addons</ex> manager</dd>
+            <dt>AddonBar</dt>                   <dd></dd>
+            <dt>AddonButton</dt>                <dd></dd>
+            <dt>AddonButtons</dt>               <dd></dd>
+            <dt>AddonCell</dt>                  <dd>A cell in tell <ex>:addons</ex> manager</dd>
+            <dt>AddonDescription</dt>           <dd></dd>
+            <dt>AddonHead</dt>                  <dd>A heading in the <ex>:addons</ex> manager</dd>
+            <dt>AddonName</dt>                  <dd></dd>
+            <dt>AddonStatus</dt>                <dd></dd>
+            <dt>AddonVersion</dt>               <dd></dd>
+            <dt>Addons</dt>                     <dd>The <ex>:addons</ex> manager</dd>
+            <dt>AppmenuButton</dt>              <dd>The app-menu button</dd>
+            <dt>Bell</dt>                       <dd>&dactyl.appName;'s visual bell</dd>
+            <dt>Boolean</dt>                    <dd>JavaScript booleans</dd>
+            <dt>Button</dt>                     <dd>A button widget</dd>
+            <dt>Buttons</dt>                    <dd>A group of buttons</dd>
+            <dt>CmdCmdLine</dt>                 <dd></dd>
+            <dt>CmdErrorMsg</dt>                <dd></dd>
+            <dt>CmdInfoMsg</dt>                 <dd></dd>
+            <dt>CmdInput</dt>                   <dd></dd>
+            <dt>CmdLine</dt>                    <dd>The command line</dd>
+            <dt>CmdModeMsg</dt>                 <dd></dd>
+            <dt>CmdMoreMsg</dt>                 <dd></dd>
+            <dt>CmdNormal</dt>                  <dd></dd>
+            <dt>CmdOutput</dt>                  <dd>The output of commands executed by <ex>:run</ex></dd>
+            <dt>CmdPrompt</dt>                  <dd></dd>
+            <dt>CmdQuestion</dt>                <dd></dd>
+            <dt>CmdWarningMsg</dt>              <dd></dd>
+            <dt>Comment</dt>                    <dd>JavaScriptor CSS comments</dd>
+            <dt>CompDesc</dt>                   <dd>The description column of the completion list</dd>
+            <dt>CompGroup</dt>                  <dd>Item group in completion output</dd>
+            <dt>CompIcon</dt>                   <dd>The favicon of a completion row</dd>
+            <dt>CompIcon>img</dt>               <dd></dd>
+            <dt>CompItem</dt>                   <dd>A single row of output in the completion list</dd>
+            <dt>CompItem[selected]</dt>         <dd>A selected row of completion list</dd>
+            <dt>CompLess::after</dt>            <dd>The character of indicator shown when completions may be scrolled up</dd>
+            <dt>CompLess</dt>                   <dd>The indicator shown when completions may be scrolled up</dd>
+            <dt>CompMore::after</dt>            <dd>The character of indicator shown when completions may be scrolled down</dd>
+            <dt>CompMore</dt>                   <dd>The indicator shown when completions may be scrolled down</dd>
+            <dt>CompMsg</dt>                    <dd>The message which may appear at the top of a group of completion results</dd>
+            <dt>CompResult</dt>                 <dd>The result column of the completion list</dd>
+            <dt>CompTitle</dt>                  <dd>Completion row titles</dd>
+            <dt>CompTitleSep</dt>               <dd>The element which separates the completion title from its results</dd>
+            <dt>Dense</dt>                      <dd>Arbitrary elements which should be packed densely together</dd>
+            <dt>Disabled</dt>                   <dd>Disabled item indicator text</dd>
+            <dt>Download[active]</dt>           <dd>A currently active download</dd>
+            <dt>Download</dt>                   <dd>A download in the <ex>:downloads</ex> manager</dd>
+            <dt>DownloadButtons</dt>            <dd>A button group in the <ex>:downloads</ex> manager</dd>
+            <dt>DownloadCell</dt>               <dd>A table cell in the <ex>:downloads</ex> manager</dd>
+            <dt>DownloadHead</dt>               <dd>A heading in the <ex>:downloads</ex> manager</dd>
+            <dt>DownloadPercent</dt>            <dd>The percentage column for a download</dd>
+            <dt>DownloadProgress</dt>           <dd>The progress column for a download</dd>
+            <dt>DownloadProgressHave</dt>       <dd>The completed portion of the progress column</dd>
+            <dt>DownloadProgressTotal</dt>      <dd>The remaining portion of the progress column</dd>
+            <dt>DownloadSource</dt>             <dd>The download source column for a download</dd>
+            <dt>DownloadState</dt>              <dd>The download state column for a download</dd>
+            <dt>DownloadTime</dt>               <dd>The time remaining column for a download</dd>
+            <dt>DownloadTitle</dt>              <dd>The title column for a download</dd>
+            <dt>Downloads</dt>                  <dd>The <ex>:downloads</ex> manager</dd>
+            <dt>EditorBlink1</dt>               <dd>Text fields briefly after successfully running the external editor, alternated with EditorBlink2</dd>
+            <dt>EditorBlink2</dt>               <dd>Text fields briefly after successfully running the external editor, alternated with EditorBlink1</dd>
+            <dt>EditorEditing</dt>              <dd>Text fields for which an external editor is open</dd>
+            <dt>EditorError</dt>                <dd>Text fields briefly after an error has occurred running the external editor</dd>
+            <dt>Enabled</dt>                    <dd>Enabled item indicator text</dd>
+            <dt>ErrorMsg</dt>                   <dd>Error messages</dd>
+            <dt>Filter</dt>                     <dd>The matching text in a completion list</dd>
+            <dt>FontCode</dt>                   <dd>The font used for code listings</dd>
+            <dt>FontFixed</dt>                  <dd>The font used for fixed-width text</dd>
+            <dt>FontProportional</dt>           <dd>The font used for proportionally spaced text</dd>
+            <dt>Find</dt>                       <dd>Text find highlighting. Only background and foreground colors apply.</dd>
+            <dt>FrameIndicator</dt>             <dd>The styling applied to briefly indicate the active frame</dd>
+            <dt>Function</dt>                   <dd>JavaScript functions</dd>
+            <dt>Help</dt>                       <dd>A help page</dd>
+            <dt>HelpArg</dt>                    <dd>A required command argument indicator</dd>
+            <dt>HelpBody</dt>                   <dd>The body of a help page</dd>
+            <dt>HelpBorder</dt>                 <dd>The styling of bordered elements</dd>
+            <dt>HelpCode</dt>                   <dd>Code listings</dd>
+            <dt>HelpDefault</dt>                <dd>The default value of a help item</dd>
+            <dt>HelpDescription</dt>            <dd>The description of a help item</dd>
+            <dt>HelpDescription[short]</dt>     <dd></dd>
+            <dt>HelpEm</dt>                     <dd>Emphasized text</dd>
+            <dt>HelpEx</dt>                     <dd>An Ex command</dd>
+            <dt>HelpExample</dt>                <dd>An example</dd>
+            <dt>HelpHead1</dt>                  <dd>Any help heading</dd>
+            <dt>HelpHead2</dt>                  <dd>A first-level help heading</dd>
+            <dt>HelpHead3</dt>                  <dd>A second-level help heading</dd>
+            <dt>HelpHead4</dt>                  <dd>A third-level help heading</dd>
+            <dt>HelpHead</dt>                   <dd>A fourth-level help heading</dd>
+            <dt>HelpInclude</dt>                <dd>A help page included in the consolidated help listing</dd>
+            <dt>HelpInfo</dt>                   <dd>Arbitrary information about a help item</dd>
+            <dt>HelpInfoLabel</dt>              <dd>The label for a HelpInfo item</dd>
+            <dt>HelpInfoValue</dt>              <dd>The details for a HelpInfo item</dd>
+            <dt>HelpItem</dt>                   <dd>A help item</dd>
+            <dt>HelpKey</dt>                    <dd>A keyboard key specification</dd>
+            <dt>HelpKeyword</dt>                <dd>A keyword</dd>
+            <dt>HelpLink</dt>                   <dd>A hyperlink</dd>
+            <dt>HelpLink[rel=external]</dt>     <dd>A hyperlink to an external resource</dd>
+            <dt>HelpList</dt>                   <dd>An unordered list</dd>
+            <dt>HelpListItem</dt>               <dd>A list item, ordered or unordered</dd>
+            <dt>HelpNews</dt>                   <dd>A news item</dd>
+            <dt>HelpNewsNew</dt>                <dd>A new news item</dd>
+            <dt>HelpNewsOld</dt>                <dd>An old news item</dd>
+            <dt>HelpNewsTag</dt>                <dd>The version tag for a news item</dd>
+            <dt>HelpNote</dt>                   <dd>The indicator for a note</dd>
+            <dt>HelpOpt</dt>                    <dd>An option name</dd>
+            <dt>HelpOptInfo</dt>                <dd>Information about the type and default values for an option entry</dd>
+            <dt>HelpOptionalArg</dt>            <dd>An optional command argument indicator</dd>
+            <dt>HelpOrderedList1</dt>           <dd>A first-level ordered list</dd>
+            <dt>HelpOrderedList2</dt>           <dd>A second-level ordered list</dd>
+            <dt>HelpOrderedList3</dt>           <dd>A third-level ordered list</dd>
+            <dt>HelpOrderedList4</dt>           <dd>A fourth-level ordered list</dd>
+            <dt>HelpOrderedList</dt>            <dd>Any ordered list</dd>
+            <dt>HelpParagraph</dt>              <dd>An ordinary paragraph</dd>
+            <dt>HelpSpec</dt>                   <dd>The specification for a help entry</dd>
+            <dt>HelpString</dt>                 <dd>A quoted string</dd>
+            <dt>HelpTOC</dt>                    <dd>The Table of Contents for a help page</dd>
+            <dt>HelpTOC>ol</dt>                 <dd></dd>
+            <dt>HelpTT</dt>                     <dd>Teletype text</dd>
+            <dt>HelpTab</dt>                    <dd></dd>
+            <dt>HelpTabColumn</dt>              <dd></dd>
+            <dt>HelpTabDescription</dt>         <dd>The description column of description tables</dd>
+            <dt>HelpTabRow</dt>                 <dd>Entire rows in description tables</dd>
+            <dt>HelpTabTitle</dt>               <dd>The title column of description tables</dd>
+            <dt>HelpTag</dt>                    <dd>A help tag</dd>
+            <dt>HelpTags</dt>                   <dd>A group of help tags</dd>
+            <dt>HelpTopic</dt>                  <dd>A link to a help topic</dd>
+            <dt>HelpType</dt>                   <dd>An option type</dd>
+            <dt>HelpWarning</dt>                <dd>The indicator for a warning</dd>
+            <dt>HelpXML</dt>                    <dd>Highlighted XML</dd>
+            <dt>HelpXMLAttribute</dt>           <dd></dd>
+            <dt>HelpXMLBlock</dt>               <dd></dd>
+            <dt>HelpXMLComment</dt>             <dd></dd>
+            <dt>HelpXMLNamespace</dt>           <dd></dd>
+            <dt>HelpXMLProcessing</dt>          <dd></dd>
+            <dt>HelpXMLString</dt>              <dd></dd>
+            <dt>HelpXMLTagEnd</dt>              <dd></dd>
+            <dt>HelpXMLTagStart</dt>            <dd></dd>
+            <dt>HelpXMLText</dt>                <dd></dd>
+            <dt>Hint</dt>                       <dd></dd>
+            <dt>HintActive</dt>                 <dd>The hint element of link which will be followed by <k name="CR"/></dd>
+            <dt>HintElem</dt>                   <dd>The hintable element</dd>
+            <dt>HintImage</dt>                  <dd>The indicator which floats above hinted images</dd>
+            <dt>Hint[active]</dt>               <dd></dd>
+            <dt>Indicator</dt>                  <dd>The <em>#</em> and  <em>%</em> in the <ex>:buffers</ex> list</dd>
+            <dt>InfoMsg</dt>                    <dd>Information messages</dd>
+            <dt>InlineHelpLink</dt>             <dd>A help link shown in the command line or multi-line output area</dd>
+            <dt>Key</dt>                        <dd>Keywords</dd>
+            <dt>Keyword</dt>                    <dd>A bookmark keyword for a URL</dd>
+            <dt>LineNr</dt>                     <dd>The line number of an error</dd>
+            <dt>Link</dt>                       <dd>A link with additional information shown on hover</dd>
+            <dt>LinkInfo</dt>                   <dd>Information shown when hovering over a link</dd>
+            <dt>Message</dt>                    <dd></dd>
+            <dt>Message</dt>                    <dd>A message as displayed in <ex>:messages</ex></dd>
+            <dt>ModeMsg</dt>                    <dd>The mode indicator</dd>
+            <dt>MoreMsg</dt>                    <dd>The indicator that there is more text to view</dd>
+            <dt>NonText</dt>                    <dd>The <em>~</em> indicators which mark blank lines in the completion list</dd>
+            <dt>Normal</dt>                     <dd>Normal text</dd>
+            <dt>Null</dt>                       <dd>JavaScript null values</dd>
+            <dt>Number</dt>                     <dd>JavaScript numbers</dd>
+            <dt>Object</dt>                     <dd>JavaScript objects</dd>
+            <dt>Preview</dt>                    <dd>The completion preview displayed in the &tag.command-line;</dd>
+            <dt>Question</dt>                   <dd>A prompt for a decision</dd>
+            <dt>REPL-E</dt>                     <dd>Evaled input in REPL mode</dd>
+            <dt>REPL-P</dt>                     <dd>Evaled output in REPL mode</dd>
+            <dt>REPL-R</dt>                     <dd>Prompts in REPL mode</dd>
+            <dt>REPL</dt>                       <dd>Read-Eval-Print-Loop output</dd>
+            <dt>StatusInfoMsg</dt>              <dd>Information messages in the status line</dd>
+            <dt>StatusLine</dt>                 <dd>The status bar</dd>
+            <dt>StatusLineBroken</dt>           <dd>The status bar for a broken web page</dd>
+            <dt>StatusLineExtended</dt>         <dd>The status bar for a secure web page with an Extended Validation (EV) certificate</dd>
+            <dt>StatusLineNormal</dt>           <dd>The status bar for an ordinary web page</dd>
+            <dt>StatusLineSecure</dt>           <dd>The status bar for a secure web page</dd>
+            <dt>StatusModeMsg</dt>              <dd>The mode indicator in the status line</dd>
+            <dt>StatusMoreMsg</dt>              <dd></dd>
+            <dt>StatusNormal</dt>               <dd>Normal text in the status line</dd>
+            <dt>StatusQuestion</dt>             <dd>A prompt for a decision in the status line</dd>
+            <dt>StatusWarningMsg</dt>           <dd>A warning message in the status line</dd>
+            <dt>String</dt>                     <dd>String values</dd>
+            <dt>TabClose</dt>                   <dd>The close button of a browser tab</dd>
+            <dt>TabIcon</dt>                    <dd>The icon of a browser tab</dd>
+            <dt>TabIconNumber</dt>              <dd>The number of a browser tab, over its icon</dd>
+            <dt>TabNumber</dt>                  <dd>The number of a browser tab, next to its icon</dd>
+            <dt>TabText</dt>                    <dd>The text of a browser tab</dd>
+            <dt>Tag</dt>                        <dd>A bookmark tag for a URL</dd>
+            <dt>Title</dt>                      <dd>The title of a listing, including <ex>:pageinfo</ex>, <ex>:jumps</ex></dd>
+            <dt>URL:hover</dt>                  <dd></dd>
+            <dt>URL</dt>                        <dd>A URL</dd>
+            <dt>URLExtra</dt>                   <dd>Extra information about a URL</dd>
+            <dt>Usage</dt>                      <dd>Output from the :*usage commands</dd>
+            <dt>UsageBody</dt>                  <dd>The body of listings in output from the :*usage commands</dd>
+            <dt>UsageHead</dt>                  <dd>Headings in output from the :*usage commands</dd>
+            <dt>UsageItem</dt>                  <dd>Individual items in output from the :*usage commands</dd>
+            <dt>WarningMsg</dt>                 <dd>A warning message</dd>
         </dl>
 
         <p>
 
 <item>
     <tags>:sty :style</tags>
-    <spec>:sty<oa>le</oa> <oa>-name=<a>name</a></oa> <oa>-append</oa> <a>filter</a> <oa>css</oa></spec>
+    <strut/>
+    <spec>:sty<oa>le</oa> <oa>-name=<a>name</a></oa> <oa>-group=<a>name</a></oa> <oa>-agent</oa> <a>filter</a> <a>css</a></spec>
+    <spec>:sty<oa>le</oa> -name=<a>name</a> -append <a>filter</a> <a>css</a></spec>
+    <spec>:sty<oa>le</oa> <oa>-name=<a>name</a></oa> <oa>-group=<a>name</a></oa> <oa>filter</oa></spec>
+    <strut/>
     <description>
         <p>
             Add CSS styles to the browser or to web pages. <a>filter</a> is a
             comma-separated list of <t>site-filters</t> for which the style will
-            apply. Regular expression filters may not be used and the <tt>!</tt>
-            character may not be used to invert the sense of the match.
-            <oa>css</oa> is a full CSS rule set (e.g., <tt>body { color: blue; }</tt>).
+            apply. The <tt>!</tt> character may not be used to invert the sense
+            of the match. <oa>css</oa> is a full CSS rule set (e.g., <tt>body {
+            color: blue; }</tt>).
         </p>
 
         <p>The following options are available:</p>
         <dl>
             <dt>-append</dt>
-            <dd>If provided along with <em>-name</em>, <oa>css</oa> and
-                <a>filter</a> are appended to its current value. (short name <em>-a</em>)</dd>
+            <dd>If provided along with <em>-name</em>, <a>css</a> and
+                <a>filter</a> are appended to its current value. (short name
+                <em>-a</em>)</dd>
 
             <dt>-agent</dt>
             <dd>If provided, the style is installed as an Agent sheet, which
index d894020ca4c29a2abbd4599d5d27dc50372348d2..d275bfe301e7c88e5759837ca0bbc04a68c089c7 100644 (file)
     </description>
 </item>
 
-<item>
-    <tags>:tabdu :tabduplicate</tags>
-    <spec>:<oa>count</oa>tabdu<oa>plicate</oa></spec>
-    <description>
-        <p>
-            Duplicate the current tab and focus the duplicate. If
-            <oa>count</oa> is given, duplicate the tab <oa>count</oa> times.
-        </p>
-    </description>
-</item>
-
 <!-- TODO: should the tab commands be moved back here? -->
 <p>
     See <t>opening</t> for other ways to open new tabs.
 <item>
     <tags>b :b :buffer</tags>
     <spec>:<oa>count</oa>b<oa>uffer</oa><oa>!</oa> <oa>url|index</oa></spec>
+    <spec>:<oa>count</oa>b<oa>uffer</oa><oa>!</oa> <a>match</a></spec>
     <spec><oa>count</oa>b</spec>
     <description>
         <p>
 
         <p>
             If argument is neither a full URL nor an index but uniquely identifies a
-            buffer, it is selected. With <oa>!</oa> the next buffer matching the argument is
-            selected, even if it cannot be identified uniquely. Use <k>b</k> as a
+            buffer, by a partial match with the URL or title, it is selected.
+            With <oa>!</oa> the next buffer matching the argument is selected,
+            even if it cannot be identified uniquely. Use <k>b</k> as a
             shortcut to open this prompt.
         </p>
 
 
 <item>
     <tags>:tabm :tabmove</tags>
-    <spec>:tabm<oa>ove</oa> <oa>N</oa></spec>
-    <spec>:tabm<oa>ove</oa><oa>!</oa> <oa>+N</oa> | <oa>-N</oa></spec>
+    <spec>:tabm<oa>ove</oa> <a>N</a></spec>
+    <spec>:tabm<oa>ove</oa> <a>match</a></spec>
+    <spec>:tabm<oa>ove</oa><oa>!</oa> <a>+N|-N</a></spec>
     <description>
         <p>
-            Move the current tab to a position after tab <oa>N</oa>. When <oa>N</oa> is 0, the
-            current tab is made the first one. Without <oa>N</oa> the current tab is made the
-            last one. <oa>N</oa> can also be prefixed with ‘+’ or ‘-’ to indicate a relative
-            movement. If <oa>!</oa> is specified the movement wraps around the start or end of the
-            tab list.
+            Move the current tab to the position of tab <a>N</a>. When <a>N</a>
+            is <em>$</em>, the current tab is made the last one. <a>N</a> can
+            also be prefixed with <em>+</em> or <em>-</em> to indicate a
+            relative movement. If <oa>!</oa> is specified the movement wraps
+            around the start or end of the tab list.
+        </p>
+        <p>
+            The tab index may also be selected by a general <a>match</a> string
+            like <ex>:buffer</ex>.
         </p>
     </description>
 </item>
     </description>
 </item>
 
+<h2 tag="app-tabs application-tabs pinned-tabs">Application Tabs</h2>
+
+<item>
+    <tags>:pin :pintab</tags>
+    <spec>:<oa>count</oa>pin<oa>tab</oa><oa>!</oa> <oa>arg</oa></spec>
+    <description>
+        <p>
+            Pin tab as an application tab. If <oa>!</oa> is given,
+            the tab's pinned state is toggled. Arguments and count
+            are the same as for <ex>:bdelete</ex> and <ex>:buffer</ex>.
+        </p>
+    </description>
+</item>
+
+<item>
+    <tags>:unpin :unpintab</tags>
+    <spec>:<oa>count</oa>unpin<oa>tab</oa> <oa>arg</oa></spec>
+    <description>
+        <p>
+            Unpin tab as an application tab. Arguments and count
+            are the same as for <ex>:pintab</ex>.
+        </p>
+    </description>
+</item>
+
+
 </document>
 
 <!-- vim:se sts=4 sw=4 et: -->
index 78ade2e5f846a4938b76b153b5cc0cc768ce5b46..39d80dc018ac0f802cd880e840f6babd3404eaa9 100644 (file)
@@ -87,7 +87,7 @@
 </item>
 
 <item>
-    <tags><![CDATA[<C-l> CTRL-L :redr :redraw]]></tags>
+    <tags><![CDATA[<redraw-screen> <C-l> CTRL-L :redr :redraw]]></tags>
     <strut/>
     <spec>:redr<oa>aw</oa></spec>
     <description>
 <h2 tag="online-help">Online help</h2>
 
 <item>
-    <tags><![CDATA[<F1> :help :h help]]></tags>
+    <tags><![CDATA[<open-help> <F1> :help :h help]]></tags>
     <spec>:h<oa>elp</oa> <oa>subject</oa></spec>
     <spec>&lt;F1></spec>
     <description>
 </item>
 
 <item>
-    <tags><![CDATA[<A-F1> :helpall :helpa help-all]]></tags>
+    <tags><![CDATA[<open-single-help> <A-F1> :helpall :helpa help-all]]></tags>
     <spec>:helpa<oa>ll</oa> <oa>subject</oa></spec>
     <spec>&lt;A-F1></spec>
     <description>
         <p>
             Start Caret mode. This mode resembles the Vim's Normal mode where
             the text cursor is visible on the web page. The <k link="false">v</k> key
-            enters visual mode, where text is selected as the cursor moves.
+            enters Visual mode, where text is selected as the cursor moves.
         </p>
     </description>
 </item>
index 03c4e0fb9bf18ff2a4920dac0cdd5706b69fd975..6f43950c46b44649bcb836869c4ecddb1ce5335b 100644 (file)
@@ -22,8 +22,8 @@ var callResult = function callResult(method) {
 var listener = function listener(action, event)
     function addonListener(install) {
         this.dactyl[install.error ? "echoerr" : "echomsg"](
-            "Add-on " + action + " " + event + ": " + (install.name || install.sourceURI.spec) +
-            (install.error ? ": " + addonErrors[install.error] : ""));
+            _("addon.error", action, event, (install.name || install.sourceURI.spec) +
+                (install.error ? ": " + addons.errors[install.error] : "")));
     }
 
 var AddonListener = Class("AddonListener", {
@@ -59,17 +59,18 @@ var updateAddons = Class("UpgradeListener", AddonListener, {
 
     },
     onUpdateAvailable: function (addon, install) {
+        util.dump("onUpdateAvailable");
         this.upgrade.push(addon);
         install.addListener(this);
         install.install();
     },
     onUpdateFinished: function (addon, error) {
-        this.remaining = this.remaining.filter(function (a) a != addon);
+        this.remaining = this.remaining.filter(function (a) a.type != addon.type || a.id != addon.id);
         if (!this.remaining.length)
             this.dactyl.echomsg(
                 this.upgrade.length
-                    ? "Installing updates for addons: " + this.upgrade.map(function (i) i.name).join(", ")
-                    : "No addon updates found");
+                    ? _("addon.installingUpdates", this.upgrade.map(function (i) i.name).join(", "))
+                    : _("addon.noUpdates"));
     }
 });
 
@@ -110,15 +111,15 @@ var actions = {
         name: "extr[ehash]",
         description: "Reload an extension",
         action: function (addon) {
-            util.assert(util.haveGecko("2b"), _("error.notUseful", config.host));
+            util.assert(util.haveGecko("2b"), _("command.notUseful", config.host));
             util.timeout(function () {
                 addon.userDisabled = true;
                 addon.userDisabled = false;
             });
         },
         get filter() {
-            let ids = set(keys(JSON.parse(prefs.get("extensions.bootstrappedAddons", "{}"))));
-            return function ({ item }) !item.userDisabled && set.has(ids, item.id);
+            let ids = Set(keys(JSON.parse(prefs.get("extensions.bootstrappedAddons", "{}"))));
+            return function ({ item }) !item.userDisabled && Set.has(ids, item.id);
         },
         perm: "disable"
     },
@@ -151,11 +152,11 @@ var Addon = Class("Addon", {
                 <td highlight="AddonVersion" key="version"/>
                 <td highlight="AddonStatus" key="status"/>
                 <td highlight="AddonButtons Buttons">
-                    <a highlight="Button" key="enable">On&#xa0;</a>
-                    <a highlight="Button" key="disable">Off</a>
-                    <a highlight="Button" key="delete">Del</a>
-                    <a highlight="Button" key="update">Upd</a>
-                    <a highlight="Button" key="options">Opt</a>
+                    <a highlight="Button" href="javascript:0" key="enable">{_("addon.action.On")}</a>
+                    <a highlight="Button" href="javascript:0" key="disable">{_("addon.action.Off")}</a>
+                    <a highlight="Button" href="javascript:0" key="delete">{_("addon.action.Delete")}</a>
+                    <a highlight="Button" href="javascript:0" key="update">{_("addon.action.Update")}</a>
+                    <a highlight="Button" href="javascript:0" key="options">{_("addon.action.Options")}</a>
                 </td>
                 <td highlight="AddonDescription" key="description"/>
             </tr>,
@@ -165,7 +166,7 @@ var Addon = Class("Addon", {
     },
 
     commandAllowed: function commandAllowed(cmd) {
-        util.assert(set.has(actions, cmd), "Unknown command");
+        util.assert(Set.has(actions, cmd), _("addon.unknownCommand"));
 
         let action = actions[cmd];
         if ("perm" in action && !(this.permissions & AddonManager["PERM_CAN_" + action.perm.toUpperCase()]))
@@ -176,7 +177,7 @@ var Addon = Class("Addon", {
     },
 
     command: function command(cmd) {
-        util.assert(this.commandAllowed(cmd), "Command not allowed");
+        util.assert(this.commandAllowed(cmd), _("addon.commandNotAllowed"));
 
         let action = actions[cmd];
         if (action.action)
@@ -282,11 +283,11 @@ var AddonList = Class("AddonList", {
         XML.ignoreWhitespace = true;
         util.xmlToDom(<table highlight="Addons" key="list" xmlns={XHTML}>
                         <tr highlight="AddonHead">
-                            <td>Name</td>
-                            <td>Version</td>
-                            <td>Status</td>
+                            <td>{_("title.Name")}</td>
+                            <td>{_("title.Version")}</td>
+                            <td>{_("title.Status")}</td>
                             <td/>
-                            <td>Description</td>
+                            <td>{_("title.Description")}</td>
                         </tr>
                       </table>, this.document, this.nodes);
 
@@ -347,6 +348,11 @@ var AddonList = Class("AddonList", {
 });
 
 var Addons = Module("addons", {
+    errors: Class.memoize(function ()
+            array(["ERROR_NETWORK_FAILURE", "ERROR_INCORRECT_HASH",
+                   "ERROR_CORRUPT_FILE", "ERROR_FILE_ACCESS"])
+                .map(function (e) [AddonManager[e], _("AddonManager." + e)])
+                .toObject())
 }, {
 }, {
     commands: function (dactyl, modules, window) {
@@ -393,7 +399,7 @@ var Addons = Module("addons", {
                 else if (file.isDirectory())
                     dactyl.echoerr(_("addon.cantInstallDir", file.path.quote()));
                 else
-                    dactyl.echoerr(_("io.notReadable-1", file.path));
+                    dactyl.echoerr(_("io.notReadable", file.path));
             }, {
                 argCount: "1",
                 completer: function (context) {
@@ -413,11 +419,11 @@ var Addons = Module("addons", {
                 function (args) {
                     let name = args[0];
                     if (args.bang && !command.bang)
-                        dactyl.assert(!name, _("error.trailing"));
+                        dactyl.assert(!name, _("error.trailingCharacters"));
                     else
                         dactyl.assert(name, _("error.argumentRequired"));
 
-                    AddonManager.getAddonsByTypes(["extension"], dactyl.wrapCallback(function (list) {
+                    AddonManager.getAddonsByTypes(args["-types"], dactyl.wrapCallback(function (list) {
                         if (!args.bang || command.bang) {
                             list = list.filter(function (extension) extension.name == name);
                             if (list.length == 0)
@@ -433,13 +439,22 @@ var Addons = Module("addons", {
                 }, {
                     argCount: "?", // FIXME: should be "1"
                     bang: true,
-                    completer: function (context) {
-                        completion.extension(context);
+                    completer: function (context, args) {
+                        completion.extension(context, args["-types"]);
                         context.filters.push(function ({ item }) ok(item));
                         if (command.filter)
                             context.filters.push(command.filter);
                     },
-                    literal: 0
+                    literal: 0,
+                    options: [
+                        {
+                            names: ["-types", "-type", "-t"],
+                            description: "The add-on types to operate on",
+                            default: ["extension"],
+                            completer: function (context, args) completion.addonType(context),
+                            type: CommandOption.LIST
+                        }
+                    ]
                 });
         });
     },
@@ -460,8 +475,8 @@ var Addons = Module("addons", {
                                           true));
                     });
                 }
-            }
-        }
+            };
+        };
 
         completion.extension = function extension(context, types) {
             context.title = ["Extension"];
@@ -494,27 +509,14 @@ else
                 addon = this.wrapAddon(addon);
             return callback(addon);
         },
+
         wrapAddon: function wrapAddon(addon) {
             addon = Object.create(addon.QueryInterface(Ci.nsIUpdateItem));
 
-            function getRdfProperty(item, property) {
-                let resource = services.rdf.GetResource("urn:mozilla:item:" + item.id);
-                let value = "";
-
-                if (resource) {
-                    let target = services.extensionManager.datasource.GetTarget(resource,
-                        services.rdf.GetResource("http://www.mozilla.org/2004/em-rdf#" + property), true);
-                    if (target && target instanceof Ci.nsIRDFLiteral)
-                        value = target.Value;
-                }
-
-                return value;
-            }
-
             ["aboutURL", "creator", "description", "developers",
              "homepageURL", "installDate", "optionsURL",
              "releaseNotesURI", "updateDate"].forEach(function (item) {
-                memoize(addon, item, function (item) getRdfProperty(this, item));
+                memoize(addon, item, function (item) this.getProperty(item));
             });
 
             update(addon, {
@@ -523,19 +525,33 @@ else
 
                 appDisabled: false,
 
+                getProperty: function getProperty(property) {
+                    let resource = services.rdf.GetResource("urn:mozilla:item:" + this.id);
+
+                    if (resource) {
+                        let target = services.extensionManager.datasource.GetTarget(resource,
+                            services.rdf.GetResource("http://www.mozilla.org/2004/em-rdf#" + property), true);
+
+                        if (target && target instanceof Ci.nsIRDFLiteral)
+                            return target.Value;
+                    }
+
+                    return "";
+                },
+
                 installLocation: Class.memoize(function () services.extensionManager.getInstallLocation(this.id)),
                 getResourceURI: function getResourceURI(path) {
                     let file = this.installLocation.getItemFile(this.id, path);
                     return services.io.newFileURI(file);
                 },
 
-                isActive: getRdfProperty(addon, "isDisabled") != "true",
+                get isActive() this.getProperty("isDisabled") != "true",
 
                 uninstall: function uninstall() {
                     services.extensionManager.uninstallItem(this.id);
                 },
 
-                get userDisabled() getRdfProperty(addon, "userDisabled") === "true",
+                get userDisabled() this.getProperty("userDisabled") === "true",
                 set userDisabled(val) {
                     services.extensionManager[val ? "disableItem" : "enableItem"](this.id);
                 }
@@ -543,6 +559,7 @@ else
 
             return addon;
         },
+
         getAddonsByTypes: function (types, callback) {
             let res = [];
             for (let [, type] in Iterator(types))
@@ -554,6 +571,7 @@ else
                 util.timeout(function () { callback(res); });
             return res;
         },
+
         getInstallForFile: function (file, callback, mimetype) {
             callback({
                 addListener: function () {},
@@ -562,9 +580,11 @@ else
                 }
             });
         },
+
         getInstallForURL: function (url, callback, mimetype) {
             util.assert(false, _("error.unavailable", config.host, services.runtime.version));
         },
+
         observers: [],
         addAddonListener: function (listener) {
             observer.listener = listener;
@@ -595,12 +615,6 @@ else
         }
     };
 
-var addonErrors = array.toObject([
-    [AddonManager.ERROR_NETWORK_FAILURE, "A network error occurred"],
-    [AddonManager.ERROR_INCORRECT_HASH,  "The downloaded file did not match the expected hash"],
-    [AddonManager.ERROR_CORRUPT_FILE,    "The file appears to be corrupt"],
-    [AddonManager.ERROR_FILE_ACCESS,     "There was an error accessing the filesystem"]]);
-
 endModule();
 
 } catch(e){ if (isString(e)) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
index 13bd9a03e93fcd3b88a5400ba38061e5b25bf08a..d07ba1230732b78e0f69eb944b84e8017c91f3fc 100644 (file)
@@ -37,65 +37,83 @@ if (!Object.create)
     };
 if (!Object.defineProperty)
     Object.defineProperty = function defineProperty(obj, prop, desc) {
-        let value = desc.value;
-        if ("value" in desc)
-            if (desc.writable && !__lookupGetter__.call(obj, prop)
-                              && !__lookupSetter__.call(obj, prop))
-                try {
-                    obj[prop] = value;
+        try {
+            let value = desc.value;
+            if ("value" in desc)
+                if (desc.writable && !__lookupGetter__.call(obj, prop)
+                                  && !__lookupSetter__.call(obj, prop))
+                    try {
+                        obj[prop] = value;
+                    }
+                    catch (e if e instanceof TypeError) {}
+                else {
+                    objproto.__defineGetter__.call(obj, prop, function () value);
+                    if (desc.writable)
+                        objproto.__defineSetter__.call(obj, prop, function (val) { value = val; });
                 }
-                catch (e if e instanceof TypeError) {}
-            else {
-                objproto.__defineGetter__.call(obj, prop, function () value);
-                if (desc.writable)
-                    objproto.__defineSetter__.call(obj, prop, function (val) { value = val; });
-            }
 
-        if ("get" in desc)
-            objproto.__defineGetter__.call(obj, prop, desc.get);
-        if ("set" in desc)
-            objproto.__defineSetter__.call(obj, prop, desc.set);
+            if ("get" in desc)
+                objproto.__defineGetter__.call(obj, prop, desc.get);
+            if ("set" in desc)
+                objproto.__defineSetter__.call(obj, prop, desc.set);
+        }
+        catch (e) {
+            throw e.stack ? e : Error(e);
+        }
     };
 if (!Object.defineProperties)
     Object.defineProperties = function defineProperties(obj, props) {
         for (let [k, v] in Iterator(props))
             Object.defineProperty(obj, k, v);
-    }
+    };
 if (!Object.freeze)
     Object.freeze = function freeze(obj) {};
+if (!Object.getPropertyDescriptor)
+    Object.getPropertyDescriptor = function getPropertyDescriptor(obj, prop) {
+        try {
+            let desc = {
+                configurable: true,
+                enumerable: propertyIsEnumerable.call(obj, prop)
+            };
+            var get = __lookupGetter__.call(obj, prop),
+                set = __lookupSetter__.call(obj, prop);
+            if (!get && !set) {
+                desc.value = obj[prop];
+                desc.writable = true;
+            }
+            if (get)
+                desc.get = get;
+            if (set)
+                desc.set = set;
+            return desc;
+        }
+        catch (e) {
+            throw e.stack ? e : Error(e);
+        }
+    };
 if (!Object.getOwnPropertyDescriptor)
     Object.getOwnPropertyDescriptor = function getOwnPropertyDescriptor(obj, prop) {
-        if (!hasOwnProperty.call(obj, prop))
-            return undefined;
-        let desc = {
-            configurable: true,
-            enumerable: propertyIsEnumerable.call(obj, prop)
-        };
-        var get = __lookupGetter__.call(obj, prop),
-            set = __lookupSetter__.call(obj, prop);
-        if (!get && !set) {
-            desc.value = obj[prop];
-            desc.writable = true;
-        }
-        if (get)
-            desc.get = get;
-        if (set)
-            desc.set = set;
-        return desc;
+        if (hasOwnProperty.call(obj, prop))
+            return Object.getPropertyDescriptor(obj, prop);
     };
 if (!Object.getOwnPropertyNames)
     Object.getOwnPropertyNames = function getOwnPropertyNames(obj, _debugger) {
-        // This is an ugly and unfortunately necessary hack.
-        if (hasOwnProperty.call(obj, "__iterator__")) {
-            var oldIter = obj.__iterator__;
-            delete obj.__iterator__;
+        try {
+            // This is an ugly and unfortunately necessary hack.
+            if (hasOwnProperty.call(obj, "__iterator__")) {
+                var oldIter = obj.__iterator__;
+                delete obj.__iterator__;
+            }
+            let res = [k for (k in obj) if (hasOwnProperty.call(obj, k))];
+            if (oldIter !== undefined) {
+                obj.__iterator__ = oldIter;
+                res.push("__iterator__");
+            }
+            return res;
         }
-        let res = [k for (k in obj) if (hasOwnProperty.call(obj, k))];
-        if (oldIter !== undefined) {
-            obj.__iterator__ = oldIter;
-            res.push("__iterator__");
+        catch (e) {
+            throw e.stack ? e : Error(e);
         }
-        return res;
     };
 if (!Object.getPrototypeOf)
     Object.getPrototypeOf = function getPrototypeOf(obj) obj.__proto__;
@@ -127,6 +145,7 @@ function defineModule(name, params, module) {
             use[mod].push(module);
         }
     currentModule = module;
+    module.startTime = Date.now();
 }
 
 defineModule.loadLog = [];
@@ -148,7 +167,6 @@ defineModule.dump = function dump_() {
                .replace(/^./gm, name + ": $&"));
 }
 defineModule.modules = [];
-defineModule.times = { all: 0 };
 defineModule.time = function time(major, minor, func, self) {
     let time = Date.now();
     if (typeof func !== "function")
@@ -161,13 +179,7 @@ defineModule.time = function time(major, minor, func, self) {
         loaded.util && util.reportError(e);
     }
 
-    let delta = Date.now() - time;
-    defineModule.times.all += delta;
-    defineModule.times[major] = (defineModule.times[major] || 0) + delta;
-    if (minor) {
-        defineModule.times[":" + minor] = (defineModule.times[":" + minor] || 0) + delta;
-        defineModule.times[major + ":" + minor] = (defineModule.times[major + ":" + minor] || 0) + delta;
-    }
+    JSMLoader.times.add(major, minor, Date.now() - time);
     return res;
 }
 
@@ -185,8 +197,10 @@ function require(obj, name, from) {
         if (arguments.length === 1)
             [obj, name] = [{}, obj];
 
+        let caller = Components.stack.caller;
+
         if (!loaded[name])
-            defineModule.loadLog.push((from || "require") + ": loading " + name + (obj.NAME ? " into " + obj.NAME : ""));
+            defineModule.loadLog.push((from || "require") + ": loading " + name + " into " + (obj.NAME || caller.filename + ":" + caller.lineNumber));
 
         JSMLoader.load(name + ".jsm", obj);
         return obj;
@@ -196,7 +210,7 @@ function require(obj, name, from) {
         if (loaded.util)
             util.reportError(e);
         else
-            defineModule.dump("    " + (e.filename || e.fileName) + ":" + e.lineNumber + ": " + e +"\n");
+            defineModule.dump("    " + (e.filename || e.fileName) + ":" + e.lineNumber + ": " + e + "\n");
     }
 }
 
@@ -204,10 +218,10 @@ defineModule("base", {
     // sed -n 's/^(const|function) ([a-zA-Z0-9_]+).*/  "\2",/p' base.jsm | sort | fmt
     exports: [
         "ErrorBase", "Cc", "Ci", "Class", "Cr", "Cu", "Module", "JSMLoader", "Object", "Runnable",
-        "Struct", "StructBase", "Timer", "UTF8", "XPCOM", "XPCOMUtils", "XPCSafeJSObjectWrapper",
+        "Set", "Struct", "StructBase", "Timer", "UTF8", "XPCOM", "XPCOMUtils", "XPCSafeJSObjectWrapper",
         "array", "bind", "call", "callable", "ctypes", "curry", "debuggerProperties", "defineModule",
         "deprecated", "endModule", "forEach", "isArray", "isGenerator", "isinstance", "isObject",
-        "isString", "isSubclass", "iter", "iterAll", "iterOwnProperties","keys", "memoize", "octal",
+        "isString", "isSubclass", "iter", "iterAll", "iterOwnProperties", "keys", "memoize", "octal",
         "properties", "require", "set", "update", "values", "withCallerGlobal"
     ],
     use: ["config", "services", "util"]
@@ -259,7 +273,7 @@ function properties(obj, prototypes, debugger_) {
     try {
         if ("dactylPropertyNames" in obj && !prototypes)
             for (let key in values(obj.dactylPropertyNames))
-                if (key in obj && !set.add(seen, key))
+                if (key in obj && !Set.add(seen, key))
                     yield key;
     }
     catch (e) {}
@@ -274,7 +288,7 @@ function properties(obj, prototypes, debugger_) {
             iter = (prop.name.stringValue for (prop in values(debuggerProperties(obj))));
 
         for (let key in iter)
-            if (!prototypes || !set.add(seen, key) && obj != orig)
+            if (!prototypes || !Set.add(seen, key) && obj != orig)
                 yield key;
     }
 }
@@ -292,7 +306,8 @@ function deprecated(alternative, fn) {
     let name, func = callable(fn) ? fn : function () this[fn].apply(this, arguments);
 
     function deprecatedMethod() {
-        let obj = this.className             ? this.className + "#" :
+        let obj = !this                      ? "" :
+                  this.className             ? this.className + "#" :
                   this.constructor.className ? this.constructor.className + "#" :
                       "";
 
@@ -307,17 +322,16 @@ function deprecated(alternative, fn) {
 }
 deprecated.warn = function warn(func, name, alternative, frame) {
     if (!func.seenCaller)
-        func.seenCaller = set([
+        func.seenCaller = Set([
             "resource://dactyl" + JSMLoader.suffix + "/javascript.jsm",
             "resource://dactyl" + JSMLoader.suffix + "/util.jsm"
         ]);
 
     frame = frame || Components.stack.caller.caller;
     let filename = util.fixURI(frame.filename || "unknown");
-    if (!set.add(func.seenCaller, filename))
-        util.dactyl(func).warn(
-            util.urlPath(filename) + ":" + frame.lineNumber + ": " +
-            name + " is deprecated: Please use " + alternative + " instead");
+    if (!Set.add(func.seenCaller, filename))
+        util.dactyl(func).warn([util.urlPath(filename), frame.lineNumber, " "].join(":")
+                                   + require("messages")._("warn.deprecated", name, alternative));
 }
 
 /**
@@ -359,7 +373,7 @@ var iterAll = deprecated("iter", function iterAll() iter.apply(null, arguments))
  * @param {[string]} ary @optional
  * @returns {object}
  */
-function set(ary) {
+function Set(ary) {
     let obj = {};
     if (ary)
         for (let val in values(ary))
@@ -374,7 +388,7 @@ function set(ary) {
  * @param {string} key The key to add.
  * @returns boolean
  */
-set.add = curry(function set_add(set, key) {
+Set.add = curry(function set_add(set, key) {
     let res = this.has(set, key);
     set[key] = true;
     return res;
@@ -386,7 +400,7 @@ set.add = curry(function set_add(set, key) {
  * @param {string} key The key to check.
  * @returns {boolean}
  */
-set.has = curry(function set_has(set, key) hasOwnProperty.call(set, key) &&
+Set.has = curry(function set_has(set, key) hasOwnProperty.call(set, key) &&
                                            propertyIsEnumerable.call(set, key));
 /**
  * Returns a new set containing the members of the first argument which
@@ -395,7 +409,7 @@ set.has = curry(function set_has(set, key) hasOwnProperty.call(set, key) &&
  * @param {object} set The set.
  * @returns {object}
  */
-set.subtract = function set_subtract(set) {
+Set.subtract = function set_subtract(set) {
     set = update({}, set);
     for (let i = 1; i < arguments.length; i++)
         for (let k in keys(arguments[i]))
@@ -410,12 +424,23 @@ set.subtract = function set_subtract(set) {
  * @param {string} key The key to remove.
  * @returns boolean
  */
-set.remove = curry(function set_remove(set, key) {
+Set.remove = curry(function set_remove(set, key) {
     let res = set.has(set, key);
     delete set[key];
     return res;
 });
 
+function set() {
+    deprecated.warn(set, "set", "Set");
+    return Set.apply(this, arguments);
+}
+Object.keys(Set).forEach(function (meth) {
+    set[meth] = function proxy() {
+        deprecated.warn(proxy, "set." + meth, "Set." + meth);
+        return Set[meth].apply(Set, arguments);
+    };
+});
+
 /**
  * Curries a function to the given number of arguments. Each
  * call of the resulting function returns a new function. When
@@ -464,9 +489,13 @@ function curry(fn, length, self, acc) {
 }
 
 if (curry.bind)
-    var bind = function bind(func) func.bind.apply(func, Array.slice(arguments, bind.length));
+    var bind = function bind(meth, self) let (func = callable(meth) ? meth : self[meth])
+        func.bind.apply(func, Array.slice(arguments, 1));
 else
     var bind = function bind(func, self) {
+        if (!callable(func))
+            func = self[func];
+
         let args = Array.slice(arguments, bind.length);
         return function bound() func.apply(self, args.concat(Array.slice(arguments)));
     };
@@ -582,7 +611,7 @@ function call(fn) {
  */
 function memoize(obj, key, getter) {
     if (arguments.length == 1) {
-        obj = update({}, obj);
+        obj = update({ __proto__: obj.__proto__ }, obj);
         for (let prop in Object.getOwnPropertyNames(obj)) {
             let get = __lookupGetter__.call(obj, prop);
             if (get)
@@ -591,17 +620,22 @@ function memoize(obj, key, getter) {
         return obj;
     }
 
-    Object.defineProperty(obj, key, {
-        configurable: true,
-        enumerable: true,
+    try {
+        Object.defineProperty(obj, key, {
+            configurable: true,
+            enumerable: true,
 
-        get: function g_replaceProperty() (
-            Class.replaceProperty(this.instance || this, key, null),
-            Class.replaceProperty(this.instance || this, key, getter.call(this, key))),
+            get: function g_replaceProperty() (
+                Class.replaceProperty(this.instance || this, key, null),
+                Class.replaceProperty(this.instance || this, key, getter.call(this, key))),
 
-        set: function s_replaceProperty(val)
-            Class.replaceProperty(this.instance || this, key, val)
-    });
+            set: function s_replaceProperty(val)
+                Class.replaceProperty(this.instance || this, key, val)
+        });
+    }
+    catch (e) {
+        obj[key] = getter.call(obj, key);
+    }
 }
 
 let sandbox = Cu.Sandbox(this);
@@ -648,12 +682,14 @@ function update(target) {
 
             if (typeof desc.value === "function" && target.__proto__) {
                 let func = desc.value.wrapped || desc.value;
-                func.__defineGetter__("super", function () Object.getPrototypeOf(target)[k]);
-                func.superapply = function superapply(self, args)
-                    let (meth = Object.getPrototypeOf(target)[k])
-                        meth && meth.apply(self, args);
-                func.supercall = function supercall(self)
-                    func.superapply(self, Array.slice(arguments, 1));
+                if (!func.superapply) {
+                    func.__defineGetter__("super", function () Object.getPrototypeOf(target)[k]);
+                    func.superapply = function superapply(self, args)
+                        let (meth = Object.getPrototypeOf(target)[k])
+                            meth && meth.apply(self, args);
+                    func.supercall = function supercall(self)
+                        func.superapply(self, Array.slice(arguments, 1));
+                }
             }
             try {
                 Object.defineProperty(target, k, desc);
@@ -696,18 +732,28 @@ function Class() {
     if (callable(args[0]))
         superclass = args.shift();
 
-    var Constructor = eval(String.replace(<![CDATA[
-        (function constructor(PARAMS) {
+    if (loaded.util && util.haveGecko("6.0a1")) // Bug 657418.
+        var Constructor = function Constructor() {
             var self = Object.create(Constructor.prototype, {
                 constructor: { value: Constructor },
             });
             self.instance = self;
             var res = self.init.apply(self, arguments);
             return res !== undefined ? res : self;
-        })]]>,
-        "constructor", (name || superclass.className).replace(/\W/g, "_"))
-            .replace("PARAMS", /^function .*?\((.*?)\)/.exec(args[0] && args[0].init || Class.prototype.init)[1]
-                                                       .replace(/\b(self|res|Constructor)\b/g, "$1_")));
+        };
+    else
+        var Constructor = eval(String.replace(<![CDATA[
+            (function constructor(PARAMS) {
+                var self = Object.create(Constructor.prototype, {
+                    constructor: { value: Constructor },
+                });
+                self.instance = self;
+                var res = self.init.apply(self, arguments);
+                return res !== undefined ? res : self;
+            })]]>,
+            "constructor", (name || superclass.className).replace(/\W/g, "_"))
+                .replace("PARAMS", /^function .*?\((.*?)\)/.exec(args[0] && args[0].init || Class.prototype.init)[1]
+                                                           .replace(/\b(self|res|Constructor)\b/g, "$1_")));
 
     Constructor.className = name || superclass.className || superclass.name;
 
@@ -735,19 +781,19 @@ function Class() {
 }
 
 if (Cu.getGlobalForObject)
-    Class.objectGlobal = function (caller) {
+    Class.objectGlobal = function (object) {
         try {
-            return Cu.getGlobalForObject(caller);
+            return Cu.getGlobalForObject(object);
         }
         catch (e) {
             return null;
         }
     };
 else
-    Class.objectGlobal = function (caller) {
-        while (caller.__parent__)
-            caller = caller.__parent__;
-        return caller;
+    Class.objectGlobal = function (object) {
+        while (object.__parent__)
+            object = object.__parent__;
+        return object;
     };
 
 /**
@@ -791,7 +837,7 @@ Class.extend = function extend(subclass, superclass, overrides) {
  *
  * @param {function(string)} getter The function which returns the
  *      property's value.
- * @return {Class.Property}
+ * @returns {Class.Property}
  */
 Class.memoize = function memoize(getter, wait)
     Class.Property({
@@ -883,7 +929,48 @@ Class.prototype = {
             util.trapErrors(callback, self);
         }
         return services.Timer(timeout_notify, timeout || 0, services.Timer.TYPE_ONE_SHOT);
-    }
+    },
+
+    /**
+     * Updates this instance with the properties of the given objects.
+     * Like the update function, but with special semantics for
+     * localized properties.
+     */
+    update: function update() {
+        let self = this;
+        // XXX: Duplication.
+
+        for (let i = 0; i < arguments.length; i++) {
+            let src = arguments[i];
+            Object.getOwnPropertyNames(src || {}).forEach(function (k) {
+                let desc = Object.getOwnPropertyDescriptor(src, k);
+                if (desc.value instanceof Class.Property)
+                    desc = desc.value.init(k, this) || desc.value;
+
+                if (typeof desc.value === "function") {
+                    let func = desc.value.wrapped || desc.value;
+                    if (!func.superapply) {
+                        func.__defineGetter__("super", function () Object.getPrototypeOf(self)[k]);
+                        func.superapply = function superapply(self, args)
+                            let (meth = Object.getPrototypeOf(self)[k])
+                                meth && meth.apply(self, args);
+                        func.supercall = function supercall(self)
+                            func.superapply(self, Array.slice(arguments, 1));
+                    }
+                }
+
+                try {
+                    if ("value" in desc && (k in this.localizedProperties || k in this.magicalProperties))
+                        this[k] = desc.value;
+                    else
+                        Object.defineProperty(this, k, desc);
+                }
+                catch (e) {}
+            }, this);
+        }
+    },
+
+    magicalProperties: {}
 };
 Class.makeClosure = function makeClosure() {
     const self = this;
@@ -946,7 +1033,9 @@ var ErrorBase = Class("ErrorBase", Error, {
     level: 2,
     init: function EB_init(message, level) {
         level = level || 0;
-        update(this, Error(message))
+        let error = Error(message);
+        update(this, error)
+        this.stack = error.stack;
         this.message = message;
 
         let frame = Components.stack;
@@ -956,7 +1045,8 @@ var ErrorBase = Class("ErrorBase", Error, {
         }
         this.fileName = frame.filename;
         this.lineNumber = frame.lineNumber;
-    }
+    },
+    toString: function () String(this.message)
 });
 
 /**
@@ -1368,7 +1458,7 @@ update(iter, {
     uniq: function uniq(iter) {
         let seen = {};
         for (let item in iter)
-            if (!set.add(seen, item))
+            if (!Set.add(seen, item))
                 yield item;
     },
 
@@ -1423,7 +1513,7 @@ var array = Class("array", Array, {
      * as such:
      *    [["a", "b"], ["c", "d"]] -> { a: "b", c: "d" }
      *
-     * @param {Array[]} assoc
+     * @param {[Array]} assoc
      * @... {string} 0 - Key
      * @...          1 - Value
      */
index afaa56b91bcf1674296bc014db288acd9ad721c1..e85a7c6c978b4c415bd847f788eaf6365692b37f 100644 (file)
@@ -30,6 +30,8 @@ update(Bookmark.prototype, {
 })
 Bookmark.setter = function (key, func) this.prototype.__defineSetter__(key, func);
 Bookmark.setter("url", function (val) {
+    if (isString(val))
+        val = util.newURI(val);
     let tags = this.tags;
     this.tags = null;
     services.bookmarks.changeBookmarkURI(this.id, val);
@@ -149,7 +151,6 @@ var BookmarkCache = Module("BookmarkCache", XPCOM(Ci.nsINavBookmarkObserver), {
         return this.rootFolders.indexOf(root) >= 0;
     },
 
-    // Should be made thread safe.
     load: function load() {
         let bookmarks = {};
 
index 005ba1624c5ef86dc2a3177863f76aef47b27452..d51f8696b1f7192c1c6a47577f01a8c8a7e312dc 100644 (file)
@@ -6,6 +6,8 @@
 
 try {
 
+let { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
 var EXPORTED_SYMBOLS = ["JSMLoader"];
 
 var BOOTSTRAP_CONTRACT = "@dactyl.googlecode.com/base/bootstrap";
@@ -17,21 +19,53 @@ if (!JSMLoader && "@mozilla.org/fuel/application;1" in Components.classes)
                           .getService(Components.interfaces.extIApplication)
                           .storage.get("dactyl.JSMLoader", null);
 
-if (JSMLoader && JSMLoader.bump === 4)
+if (JSMLoader && JSMLoader.bump === 5)
     JSMLoader.global = this;
 else
     JSMLoader = {
-        bump: 4,
-        builtin: Components.utils.Sandbox(this),
+        bump: 5,
+
+        builtin: Cu.Sandbox(this),
+
         canonical: {},
+
         factories: [],
+
         global: this,
+
         globals: JSMLoader ? JSMLoader.globals : {},
-        io: Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService),
-        loader: Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader),
-        manager: Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar),
+
+        io: Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService),
+
+        loader: Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader),
+
+        manager: Components.manager.QueryInterface(Ci.nsIComponentRegistrar),
+
+        modules: JSMLoader && JSMLoader.modules || {},
+
         stale: JSMLoader ? JSMLoader.stale : {},
+
         suffix: "",
+
+        times: {
+            all: 0,
+            add: function add(major, minor, delta) {
+                this.all += delta;
+
+                this[major] = (this[major] || 0) + delta;
+                if (minor) {
+                    minor = ":" + minor;
+                    this[minor] = (this[minor] || 0) + delta;
+                    this[major + minor] = (this[major + minor] || 0) + delta;
+                }
+            },
+            clear: function clear() {
+                for (let key in this)
+                    if (typeof this[key] !== "number")
+                        delete this[key];
+            }
+        },
+
         init: function init(suffix) {
             this.initialized = true;
             this.suffix = suffix || "";
@@ -41,14 +75,16 @@ else
             this.global.JSMLoader = this;
             base.JSMLoader = this;
         },
+
         getTarget: function getTarget(url) {
             if (url.indexOf(":") === -1)
                 url = "resource://dactyl" + this.suffix + "/" + url;
 
             let chan = this.io.newChannel(url, null, null);
-            chan.cancel(Components.results.NS_BINDING_ABORTED);
+            chan.cancel(Cr.NS_BINDING_ABORTED);
             return chan.name;
         },
+
         load: function load(name, target) {
             let url = name;
             if (url.indexOf(":") === -1)
@@ -68,7 +104,13 @@ else
             }
 
             try {
-                let global = Components.utils.import(url, target);
+                let now = Date.now();
+                this.modules[url] = true;
+                let global = Cu.import(url, target);
+
+                if (!(name in this.globals))
+                    this.times.add("require", name, Date.now() - now);
+
                 return this.globals[name] = global;
             }
             catch (e) {
@@ -76,33 +118,52 @@ else
                 throw e;
             }
         },
-        loadSubScript: function loadSubScript() this.loader.loadSubScript.apply(this.loader, arguments),
+
+        loadSubScript: function loadSubScript(script) {
+            let now = Date.now();
+            this.loader.loadSubScript.apply(this.loader, arguments);
+            this.times.add("loadSubScript", script, Date.now() - now);
+        },
+
         cleanup: function unregister() {
             for each (let factory in this.factories.splice(0))
                 this.manager.unregisterFactory(factory.classID, factory);
         },
+
         purge: function purge() {
             dump("dactyl: JSMLoader: purge\n");
 
-            for (let [url, global] in Iterator(this.globals)) {
-                if (url === "bootstrap.jsm" || url === "resource://dactyl/bootstrap.jsm")
-                    continue;
-
-                let target = this.getTarget(url);
-                this.stale[url] = target;
-                this.stale[target] = target;
-
-                for each (let prop in Object.getOwnPropertyNames(global))
+            if (Cu.unload) {
+                Object.keys(this.modules).reverse().forEach(function (url) {
                     try {
-                        if (!(prop in this.builtin) &&
-                            ["JSMLoader", "set", "EXPORTED_SYMBOLS"].indexOf(prop) < 0 &&
-                            !global.__lookupGetter__(prop))
-                            global[prop] = undefined;
+                        Cu.unload(url);
                     }
                     catch (e) {
-                        dump("Deleting property " + prop + " on " + url + ":\n    " + e + "\n");
-                        Components.utils.reportError(e);
+                        Cu.reportError(e);
                     }
+                });
+            }
+            else {
+                for (let [url, global] in Iterator(this.globals)) {
+                    if (url === "bootstrap.jsm" || url === "resource://dactyl/bootstrap.jsm")
+                        continue;
+
+                    let target = this.getTarget(url);
+                    this.stale[url] = target;
+                    this.stale[target] = target;
+
+                    for each (let prop in Object.getOwnPropertyNames(global))
+                        try {
+                            if (!(prop in this.builtin) &&
+                                ["JSMLoader", "Set", "set", "EXPORTED_SYMBOLS"].indexOf(prop) < 0 &&
+                                !global.__lookupGetter__(prop))
+                                global[prop] = undefined;
+                        }
+                        catch (e) {
+                            dump("Deleting property " + prop + " on " + url + ":\n    " + e + "\n");
+                            Cu.reportError(e);
+                        }
+                }
             }
         },
 
index 1036f20cf09b7bf2263bf27bdaa9dfe1441daf90..7d7093e99a41befb806ccd157b0a3dd2b3cb6c86 100644 (file)
@@ -94,7 +94,7 @@ update(CommandOption, {
  * A class representing Ex commands. Instances are created by
  * the {@link Commands} class.
  *
- * @param {string[]} specs The names by which this command can be invoked.
+ * @param {[string]} specs The names by which this command can be invoked.
  *     These are specified in the form "com[mand]" where "com" is a unique
  *     command name prefix.
  * @param {string} description A short one line description of the command.
@@ -129,7 +129,7 @@ var Command = Class("Command", {
         this.action = action;
 
         if (extraInfo)
-            update(this, extraInfo);
+            this.update(extraInfo);
         if (this.options)
             this.options = this.options.map(CommandOption.fromArray, CommandOption);
         for each (let option in this.options)
@@ -142,7 +142,7 @@ var Command = Class("Command", {
 
     get helpTag() ":" + this.name,
 
-    get lastCommand() this._lastCommand || commandline.command,
+    get lastCommand() this._lastCommand || this.modules.commandline.command,
     set lastCommand(val) { this._lastCommand = val; },
 
     /**
@@ -155,16 +155,13 @@ var Command = Class("Command", {
         const { dactyl } = this.modules;
 
         let context = args.context;
-        if (this.deprecated && !set.add(this.complained, context ? context.file : "[Command Line]")) {
-            let loc = contexts.context ? context.file + ":" + context.line + ": " : "";
-            dactyl.echoerr(loc + ":" + this.name + " is deprecated" +
-                           (isString(this.deprecated) ? ": " + this.deprecated : ""));
-        }
+        if (this.deprecated)
+            this.warn(context, "deprecated", _("warn.deprecated", ":" + this.name, this.deprecated));
 
         modifiers = modifiers || {};
 
         if (args.count != null && !this.count)
-            throw FailedAssertion(_("command.noRange"));
+            throw FailedAssertion(_("command.noCount"));
         if (args.bang && !this.bang)
             throw FailedAssertion(_("command.noBang"));
 
@@ -212,23 +209,27 @@ var Command = Class("Command", {
     complained: Class.memoize(function () ({})),
 
     /**
-     * @property {string[]} All of this command's name specs. e.g., "com[mand]"
+     * @property {[string]} All of this command's name specs. e.g., "com[mand]"
      */
     specs: null,
-    /** @property {string[]} All of this command's short names, e.g., "com" */
+    /** @property {[string]} All of this command's short names, e.g., "com" */
     shortNames: null,
     /**
-     * @property {string[]} All of this command's long names, e.g., "command"
+     * @property {[string]} All of this command's long names, e.g., "command"
      */
     longNames: null,
 
     /** @property {string} The command's canonical name. */
     name: null,
-    /** @property {string[]} All of this command's long and short names. */
+    /** @property {[string]} All of this command's long and short names. */
     names: null,
 
     /** @property {string} This command's description, as shown in :listcommands */
     description: Messages.Localized(""),
+
+    /** @property {string|null} If set, the deprecation message for this command. */
+    deprecated: Messages.Localized(null),
+
     /**
      * @property {function (Args)} The function called to execute this command.
      */
@@ -295,7 +296,7 @@ var Command = Class("Command", {
 
                 explicitOpts: Class.memoize(function () ({})),
 
-                has: function AP_has(opt) set.has(this.explicitOpts, opt) || typeof opt === "number" && set.has(this, opt),
+                has: function AP_has(opt) Set.has(this.explicitOpts, opt) || typeof opt === "number" && Set.has(this, opt),
 
                 get literalArg() this.command.literal != null && this[this.command.literal] || "",
 
@@ -309,7 +310,7 @@ var Command = Class("Command", {
 
                         util.assert((this.length == 0 || this.command.argCount !== "0") &&
                                     (this.length <= 1 || !/^[01?]$/.test(this.command.argCount)),
-                                    _("error.trailing"));
+                                    _("error.trailingCharacters"));
                     }
                 }
         });
@@ -356,7 +357,21 @@ var Command = Class("Command", {
      * @property {string} For commands defined via :command, contains the Ex
      *     command line to be executed upon invocation.
      */
-    replacementText: null
+    replacementText: null,
+
+    /**
+     * Warns of a misuse of this command once per warning type per file.
+     *
+     * @param {object} context The calling context.
+     * @param {string} type The type of warning.
+     * @param {string} warning The warning message.
+     */
+    warn: function warn(context, type, message) {
+        let loc = !context ? "" : [context.file, context.line, " "].join(":");
+
+        if (!Set.add(this.complained, type + ":" + (context ? context.file : "[Command Line]")))
+            this.modules.dactyl.warn(loc + message);
+    }
 }, {
     // TODO: do we really need more than longNames as a convenience anyway?
     /**
@@ -395,10 +410,12 @@ var Ex = Module("Ex", {
                 else {
                     let opt = cmd.optionMap["-" + k];
                     let val = opt.type && opt.type.parse(v);
+
                     util.assert(val != null && (typeof val !== "number" || !isNaN(val)),
                                 _("option.noSuch", k));
-                    Class.replaceProperty(args, opt.names[0], val);
-                    args.explicitOpts[opt.names[0]] = val;
+
+                    Class.replaceProperty(res, opt.names[0], val);
+                    res.explicitOpts[opt.names[0]] = val;
                 }
         for (let [i, val] in array.iterItems(args))
             res[i] = String(val);
@@ -447,7 +464,7 @@ var CommandHive = Class("CommandHive", Contexts.Hive, {
      * Adds a new command to the builtin hive. Accessible only to core
      * dactyl code. Plugins should use group.commands.add instead.
      *
-     * @param {string[]} names The names by which this command can be
+     * @param {[string]} specs The names by which this command can be
      *     invoked. The first name specified is the command's canonical
      *     name.
      * @param {string} description A description of the command.
@@ -455,7 +472,7 @@ var CommandHive = Class("CommandHive", Contexts.Hive, {
      * @param {Object} extra An optional extra configuration hash.
      * @optional
      */
-    add: function add(names, description, action, extra, replace) {
+    add: function add(specs, description, action, extra, replace) {
         const { commands, contexts } = this.modules;
 
         extra = extra || {};
@@ -463,7 +480,7 @@ var CommandHive = Class("CommandHive", Contexts.Hive, {
             extra.definedAt = contexts.getCaller(Components.stack.caller);
 
         extra.hive = this;
-        extra.parsedSpecs = Command.parseSpecs(names);
+        extra.parsedSpecs = Command.parseSpecs(specs);
 
         let names = array.flatten(extra.parsedSpecs);
         let name = names[0];
@@ -483,7 +500,7 @@ var CommandHive = Class("CommandHive", Contexts.Hive, {
         let self = this;
         let closure = function () self._map[name];
 
-        memoize(this._map, name, function () commands.Command(names, description, action, extra));
+        memoize(this._map, name, function () commands.Command(specs, description, action, extra));
         memoize(this._list, this._list.length, closure);
         for (let alias in values(names.slice(1)))
             memoize(this._map, alias, closure);
@@ -624,48 +641,58 @@ var Commands = Module("commands", {
         },
 
         /**
-         * Displays a list of user-defined commands.
+         * Lists all user-defined commands matching *filter* and optionally
+         * *hives*.
+         *
+         * @param {string} filter Limits the list to those commands with a name
+         *     matching this anchored substring.
+         * @param {[Hive]} hives List of hives.
+         * @optional
          */
-        list: function list() {
+        list: function list(filter, hives) {
             const { commandline, completion } = this.modules;
             function completerToString(completer) {
                 if (completer)
                     return [k for ([k, v] in Iterator(config.completers)) if (completer == completion.closure[v])][0] || "custom";
                 return "";
             }
+            // TODO: allow matching of aliases?
+            function cmds(hive) hive._list.filter(function (cmd) cmd.name.indexOf(filter || "") == 0)
+
+            let hives = (hives || this.userHives).map(function (h) [h, cmds(h)]).filter(function ([h, c]) c.length);
+
+            let list = <table>
+                <tr highlight="Title">
+                    <td/>
+                    <td style="padding-right: 1em;"></td>
+                    <td style="padding-right: 1ex;">{_("title.Name")}</td>
+                    <td style="padding-right: 1ex;">{_("title.Args")}</td>
+                    <td style="padding-right: 1ex;">{_("title.Range")}</td>
+                    <td style="padding-right: 1ex;">{_("title.Complete")}</td>
+                    <td style="padding-right: 1ex;">{_("title.Definition")}</td>
+                </tr>
+                <col style="min-width: 6em; padding-right: 1em;"/>
+                {
+                    template.map(hives, function ([hive, cmds]) let (i = 0)
+                        <tr style="height: .5ex;"/> +
+                        template.map(cmds, function (cmd)
+                            <tr>
+                                <td highlight="Title">{!i++ ? hive.name : ""}</td>
+                                <td>{cmd.bang ? "!" : " "}</td>
+                                <td>{cmd.name}</td>
+                                <td>{cmd.argCount}</td>
+                                <td>{cmd.count ? "0c" : ""}</td>
+                                <td>{completerToString(cmd.completer)}</td>
+                                <td>{cmd.replacementText || "function () { ... }"}</td>
+                            </tr>) +
+                        <tr style="height: .5ex;"/>)
+                }
+            </table>;
 
-            if (!this.userHives.some(function (h) h._list.length))
+            if (list.*.length() === list.text().length() + 2)
                 dactyl.echomsg(_("command.none"));
             else
-                commandline.commandOutput(
-                    <table>
-                        <tr highlight="Title">
-                            <td/>
-                            <td style="padding-right: 1em;"></td>
-                            <td style="padding-right: 1ex;">Name</td>
-                            <td style="padding-right: 1ex;">Args</td>
-                            <td style="padding-right: 1ex;">Range</td>
-                            <td style="padding-right: 1ex;">Complete</td>
-                            <td style="padding-right: 1ex;">Definition</td>
-                        </tr>
-                        <col style="min-width: 6em; padding-right: 1em;"/>
-                        {
-                            template.map(this.userHives, function (hive) let (i = 0)
-                                <tr style="height: .5ex;"/> +
-                                template.map(hive, function (cmd)
-                                    template.map(cmd.names, function (name)
-                                    <tr>
-                                        <td highlight="Title">{!i++ ? hive.name : ""}</td>
-                                        <td>{cmd.bang ? "!" : " "}</td>
-                                        <td>{cmd.name}</td>
-                                        <td>{cmd.argCount}</td>
-                                        <td>{cmd.count ? "0c" : ""}</td>
-                                        <td>{completerToString(cmd.completer)}</td>
-                                        <td>{cmd.replacementText || "function () { ... }"}</td>
-                                    </tr>)) +
-                                <tr style="height: .5ex;"/>)
-                        }
-                    </table>);
+                commandline.commandOutput(list);
         }
     }),
 
@@ -848,9 +875,9 @@ var Commands = Module("commands", {
 
             let [count, arg, quote] = Commands.parseArg(str, null, _keepQuotes);
             if (quote == "\\" && !complete)
-                return [, , , "Trailing \\"];
+                return [, , , _("error.trailingCharacters", "\\")];
             if (quote && !complete)
-                return [, , , "E114: Missing quote: " + quote];
+                return [, , , _("error.missingQuote", quote)];
             return [count, arg, quote];
         }
 
@@ -881,7 +908,7 @@ var Commands = Module("commands", {
             let matchOpts = function matchOpts(arg) {
                 // Push possible option matches into completions
                 if (complete && !onlyArgumentsRemaining)
-                    completeOpts = options.filter(function (opt) opt.multiple || !set.has(args, opt.names[0]));
+                    completeOpts = options.filter(function (opt) opt.multiple || !Set.has(args, opt.names[0]));
             };
             let resetCompletions = function resetCompletions() {
                 completeOpts = null;
@@ -946,7 +973,7 @@ var Commands = Module("commands", {
                                     util.assert(!error, error);
 
                                     // if we add the argument to an option after a space, it MUST not be empty
-                                    if (sep != "=" && !quote && arg.length == 0)
+                                    if (sep != "=" && !quote && arg.length == 0 && !complete)
                                         arg = null;
 
                                     count++; // to compensate the "=" character
@@ -1336,7 +1363,7 @@ var Commands = Module("commands", {
             // dynamically get completions as specified with the command's completer function
             context.highlight();
             if (!command) {
-                context.message = "No such command: " + match.cmd;
+                context.message = _("command.noSuch", match.cmd);
                 context.highlight(0, match.cmd.length, "SPELLCHECK");
                 return;
             }
@@ -1354,6 +1381,7 @@ var Commands = Module("commands", {
             }
             catch (e) {
                 util.reportError(e);
+                cmdContext.message = _("error.error", e);
             }
         };
 
@@ -1367,8 +1395,6 @@ var Commands = Module("commands", {
     commands: function initCommands(dactyl, modules, window) {
         const { commands, contexts } = modules;
 
-        // TODO: Vim allows commands to be defined without {rep} if there are {attr}s
-        // specified - useful?
         commands.add(["com[mand]"],
             "List or define commands",
             function (args) {
@@ -1377,33 +1403,26 @@ var Commands = Module("commands", {
                 util.assert(!cmd || cmd.split(",").every(commands.validName.closure.test),
                             _("command.invalidName", cmd));
 
-                if (!args.literalArg)
-                    commands.list();
+                if (args.length <= 1)
+                    commands.list(cmd, args.explicitOpts["-group"] ? [args["-group"]] : null);
                 else {
                     util.assert(args["-group"].modifiable,
                                 _("group.cantChangeBuiltin", _("command.commands")));
 
-                    let completer  = args["-complete"];
+                    let completer = args["-complete"];
                     let completerFunc = null; // default to no completion for user commands
 
                     if (completer) {
                         if (/^custom,/.test(completer)) {
                             completer = completer.substr(7);
 
-                            let context = update({}, contexts.context || {});
+                            if (contexts.context)
+                                var ctxt = update({}, contexts.context || {});
                             completerFunc = function (context) {
-                                try {
-                                    var result = contextswithSavedValues(["context"], function () {
-                                        contexts.context = context;
-                                        return dactyl.userEval(completer);
-                                    });
-                                }
-                                catch (e) {
-                                    dactyl.echo(":" + this.name + " ...");
-                                    dactyl.echoerr(_("command.unknownCompleter", completer));
-                                    dactyl.log(e);
-                                    return undefined;
-                                }
+                                var result = contexts.withSavedValues(["context"], function () {
+                                    contexts.context = ctxt;
+                                    return dactyl.userEval(completer);
+                                });
                                 if (callable(result))
                                     return result.apply(this, Array.slice(arguments));
                                 else
@@ -1473,7 +1492,7 @@ var Commands = Module("commands", {
                     },
                     {
                         names: ["-literal", "-l"],
-                        description: "Process the nth ignoring any quoting or meta characters",
+                        description: "Process the specified argument ignoring any quoting or meta characters",
                         type: CommandOption.INT
                     },
                     {
@@ -1558,7 +1577,7 @@ var Commands = Module("commands", {
                 ]
             })),
             iterateIndex: function (args) let (tags = services["dactyl:"].HELP_TAGS)
-                this.iterate(args).filter(function (cmd) cmd.hive === commands.builtin || set.has(cmd.helpTag)),
+                this.iterate(args).filter(function (cmd) cmd.hive === commands.builtin || Set.has(tags, cmd.helpTag)),
             format: {
                 headings: ["Command", "Group", "Description"],
                 description: function (cmd) template.linkifyHelp(cmd.description + (cmd.replacementText ? ": " + cmd.action : "")),
@@ -1576,9 +1595,10 @@ var Commands = Module("commands", {
                 dactyl.clipboardWrite(res);
 
                 let lines = res.split("\n").length;
-                dactyl.echomsg("Yanked " + lines + " line" + (lines == 1 ? "" : "s"));
+                dactyl.echomsg(_("command.yank.yankedLine" + (lines == 1 ? "" : "s"), lines));
             },
             {
+                argCount: "1",
                 completer: function (context) modules.completion[/^:/.test(context.filter) ? "ex" : "javascript"](context),
                 literal: 0
             });
index b7a34e54938363c25e27ba0a03db622fe190736a..9671e95aad543f8eadf3a5f6acaaf8498bef26c0 100644 (file)
@@ -11,7 +11,7 @@ try {
 Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("completion", {
     exports: ["CompletionContext", "Completion", "completion"],
-    use: ["config", "template", "util"]
+    use: ["config", "messages", "template", "util"]
 }, this);
 
 /**
@@ -331,16 +331,16 @@ var CompletionContext = Class("CompletionContext", {
      * The message displayed at the head of the completions for the
      * current context.
      */
-    get message() this._message || (this.waitingForTab && this.hasItems !== false ? "Waiting for <Tab>" : null),
+    get message() this._message || (this.waitingForTab && this.hasItems !== false ? _("completion.waitingFor", "<Tab>") : null),
     set message(val) this._message = val,
 
     /**
      * The prototype object for items returned by {@link items}.
      */
     get itemPrototype() {
-        let res = {};
+        let res = { highlight: "" };
         function result(quote) {
-            yield ["result", quote ? function () quote[0] + quote[1](this.text) + quote[2]
+            yield ["result", quote ? function () quote[0] + util.trapErrors(1, quote, this.text) + quote[2]
                                    : function () this.text];
         };
         for (let i in iter(this.keys, result(this.quote))) {
@@ -396,7 +396,7 @@ var CompletionContext = Class("CompletionContext", {
             }
             catch (e) {
                 util.reportError(e);
-                this.message = "Error: " + e;
+                this.message = _("error.error", e);
             }
         }
         // XXX
@@ -495,7 +495,7 @@ var CompletionContext = Class("CompletionContext", {
             return this.cache.filtered = filtered;
         }
         catch (e) {
-            this.message = "Error: " + e;
+            this.message = _("error.error", e);
             util.reportError(e);
             return [];
         }
@@ -903,6 +903,13 @@ var Completion = Module("completion", {
     // depending on the 'complete' option
     // if the 'complete' argument is passed like "h", it temporarily overrides the complete option
     url: function url(context, complete) {
+        if (/^jar:[^!]*$/.test(context.filter)) {
+            context.advance(4);
+
+            context.quote = context.quote || ["", util.identity, ""];
+            let quote = context.quote[1];
+            context.quote[1] = function (str) quote(str.replace(/!/g, escape));
+        }
 
         if (this.options["urlseparator"])
             var skip = util.regexp("^.*" + this.options["urlseparator"] + "\\s*")
@@ -953,7 +960,7 @@ var Completion = Module("completion", {
             context.title = ["URL", "Title"];
 
         context.fork("additional", 0, this, function (context) {
-            context.title[0] += " (additional)";
+            context.title[0] += " " + _("completion.additional");
             context.filter = context.parent.filter; // FIXME
             context.completions = context.parent.completions;
             // For items whose URL doesn't exactly match the filter,
@@ -1037,7 +1044,7 @@ var Completion = Module("completion", {
         };
 
         options.add(["altwildmode", "awim"],
-            "Define the behavior of the <A-Tab> key in command-line completion",
+            "Define the behavior of the c_<A-Tab> key in command-line completion",
             "stringlist", "list:full",
             wildmode);
 
@@ -1066,7 +1073,7 @@ var Completion = Module("completion", {
             });
 
         options.add(["wildmode", "wim"],
-            "Define the behavior of the <Tab> key in command-line completion",
+            "Define the behavior of the c_<Tab> key in command-line completion",
             "stringlist", "list:full",
             wildmode);
 
index 9fb101a97746fb4a4b91d4bfdc318f215a6eba48..4da5a88d6f50249a4fdba6cfaa07d3945cf0186c 100644 (file)
@@ -13,7 +13,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("config", {
     exports: ["ConfigBase", "Config", "config"],
     require: ["services", "storage", "util", "template"],
-    use: ["io", "prefs"]
+    use: ["io", "messages", "prefs", "styles"]
 }, this);
 
 var ConfigBase = Class("ConfigBase", {
@@ -22,25 +22,24 @@ var ConfigBase = Class("ConfigBase", {
      * initialization code. Must call superclass's init function.
      */
     init: function init() {
-        this.features.push = deprecated("set.add", function push(feature) set.add(this, feature));
+        this.features.push = deprecated("Set.add", function push(feature) Set.add(this, feature));
         if (util.haveGecko("2b"))
-            set.add(this.features, "Gecko2");
+            Set.add(this.features, "Gecko2");
 
         this.timeout(function () {
-            services["dactyl:"].pages.dtd = function () [null,
-                iter(config.dtdExtra,
-                     (["dactyl." + k, v] for ([k, v] in iter(config.dtd))),
-                     (["dactyl." + s, config[s]] for each (s in config.dtdStrings)))
-                  .map(function ([k, v]) ["<!ENTITY ", k, " '", String.replace(v || "null", /'/g, "&apos;"), "'>"].join(""))
-                  .join("\n")]
+            services["dactyl:"].pages.dtd = function () [null, util.makeDTD(config.dtd)];
         });
     },
 
-    loadStyles: function loadStyles() {
+    loadStyles: function loadStyles(force) {
         const { highlight } = require("highlight");
+        const { _ } = require("messages");
+
         highlight.styleableChrome = this.styleableChrome;
-        highlight.loadCSS(this.CSS);
-        highlight.loadCSS(this.helpCSS);
+
+        highlight.loadCSS(this.CSS.replace(/__MSG_(.*?)__/g, function (m0, m1) _(m1)));
+        highlight.loadCSS(this.helpCSS.replace(/__MSG_(.*?)__/g, function (m0, m1) _(m1)));
+
         if (!util.haveGecko("2b"))
             highlight.loadCSS(<![CDATA[
                 !TabNumber               font-weight: bold; margin: 0px; padding-right: .8ex;
@@ -51,6 +50,25 @@ var ConfigBase = Class("ConfigBase", {
                     text-shadow: black -1px 0 1px, black 0 1px 1px, black 1px 0 1px, black 0 -1px 1px;
                 }
             ]]>);
+
+        let hl = highlight.set("Find", "");
+        hl.onChange = function () {
+            function hex(val) ("#" + util.regexp.iterate(/\d+/g, val)
+                                         .map(function (num) ("0" + Number(num).toString(16)).slice(-2))
+                                         .join("")
+                              ).slice(0, 7);
+
+            let elem = services.appShell.hiddenDOMWindow.document.createElement("div");
+            elem.style.cssText = this.cssText;
+            let style = util.computedStyle(elem);
+
+            let keys = iter(Styles.propertyIter(this.cssText)).map(function (p) p.name).toArray();
+            let bg = keys.some(function (k) /^background/.test(k));
+            let fg = keys.indexOf("color") >= 0;
+
+            prefs[bg ? "safeSet" : "safeReset"]("ui.textHighlightBackground", hex(style.backgroundColor));
+            prefs[fg ? "safeSet" : "safeReset"]("ui.textHighlightForeground", hex(style.color));
+        };
     },
 
     get addonID() this.name + "@dactyl.googlecode.com",
@@ -124,50 +142,59 @@ var ConfigBase = Class("ConfigBase", {
      * @returns {string}
      */
     bestLocale: function (list) {
-        let langs = set(list);
+        let langs = Set(list);
         return values([this.appLocale, this.appLocale.replace(/-.*/, ""),
                        "en", "en-US", iter(langs).next()])
-            .nth(function (l) set.has(langs, l), 0);
+            .nth(function (l) Set.has(langs, l), 0);
     },
 
-    haveHg: Class.memoize(function () {
+    /**
+     * @property {string} The pathname of the VCS repository clone's root
+     *     directory if the application is running from one via an extension
+     *     proxy file.
+     */
+    VCSPath: Class.memoize(function () {
         if (/pre$/.test(this.addon.version)) {
-            let uri = this.addon.getResourceURI("../.hg");
+            let uri = util.newURI(this.addon.getResourceURI("").spec + "../.hg");
             if (uri instanceof Ci.nsIFileURL &&
-                    uri.QueryInterface(Ci.nsIFileURL).file.exists() &&
+                    uri.file.exists() &&
                     io.pathSearch("hg"))
-                return ["hg", "-R", uri.file.parent.path];
+                return uri.file.parent.path;
         }
         return null;
     }),
 
+    /**
+     * @property {string} The name of the VCS branch that the application is
+     *     running from if using an extension proxy file or was built from if
+     *     installed as an XPI.
+     */
     branch: Class.memoize(function () {
-        if (this.haveHg)
-            return io.system(this.haveHg.concat(["branch"])).output;
+        if (this.VCSPath)
+            return io.system(["hg", "-R", this.VCSPath, "branch"]).output;
         return (/pre-hg\d+-(\S*)/.exec(this.version) || [])[1];
     }),
 
     /** @property {string} The Dactyl version string. */
     version: Class.memoize(function () {
-        if (/pre$/.test(this.addon.version)) {
-            let uri = this.addon.getResourceURI("../.hg");
-            if (uri instanceof Ci.nsIFileURL &&
-                    uri.QueryInterface(Ci.nsIFileURL).file.exists() &&
-                    io.pathSearch("hg")) {
-                return io.system(["hg", "-R", uri.file.parent.path,
-                                  "log", "-r.",
-                                  "--template=hg{rev}-" + this.branch + " ({date|isodate})"]).output;
-            }
-        }
+        if (this.VCSPath)
+            return io.system(["hg", "-R", this.VCSPath, "log", "-r.",
+                              "--template=hg{rev}-" + this.branch + " ({date|isodate})"]).output;
         let version = this.addon.version;
         if ("@DATE@" !== "@" + "DATE@")
-            version += " (created: @DATE@)";
+            version += " " + _("dactyl.created", "@DATE@");
         return version;
     }),
 
-    get fileExt() this.name.slice(0, -5),
+    get fileExt() this.name.slice(0, -6),
 
-    dtd: memoize({
+    dtd: Class.memoize(function ()
+        iter(this.dtdExtra,
+             (["dactyl." + k, v] for ([k, v] in iter(config.dtdDactyl))),
+             (["dactyl." + s, config[s]] for each (s in config.dtdStrings)))
+            .toObject()),
+
+    dtdDactyl: memoize({
         get name() config.name,
         get home() "http://dactyl.sourceforge.net/",
         get apphome() this.home + this.name,
@@ -188,8 +215,9 @@ var ConfigBase = Class("ConfigBase", {
         "xmlns.html":   "http://www.w3.org/1999/xhtml",
         "xmlns.xul":    "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
 
-        "tag.command-line": '<link topic="command-line">command line</link>',
-        "tag.status-line":  '<link topic="status-line">status line</link>',
+        "tag.command-line": <link topic="command-line">command line</link>,
+        "tag.status-line":  <link topic="status-line">status line</link>,
+        "mode.command-line": <link topic="command-line-mode">Command Line</link>,
     },
 
     dtdStrings: [
@@ -202,7 +230,7 @@ var ConfigBase = Class("ConfigBase", {
         "version"
     ],
 
-    helpStyles: /^(Help|StatusLine|REPL)|^(Boolean|Indicator|MoreMsg|Number|Object|Logo|Key(word)?|String)$/,
+    helpStyles: /^(Help|StatusLine|REPL)|^(Boolean|Dense|Indicator|MoreMsg|Number|Object|Logo|Key(word)?|String)$/,
     styleHelp: function styleHelp() {
         if (!this.helpStyled) {
             const { highlight } = require("highlight");
@@ -222,12 +250,12 @@ var ConfigBase = Class("ConfigBase", {
             </e4x>;
             for each (let [id, [name, key, uri]] in Iterator(this.sidebars)) {
                 append.XUL::menupopup[0].* +=
-                        <menuitem observes={"pentadactyl-" + id + "Sidebar"} label={name} accesskey={key} xmlns={XUL}/>
+                        <menuitem observes={"pentadactyl-" + id + "Sidebar"} label={name} accesskey={key} xmlns={XUL}/>;
                 append.XUL::broadcasterset[0].* +=
                         <broadcaster id={"pentadactyl-" + id + "Sidebar"}
                             autoCheck="false" type="checkbox" group="sidebar"
                             sidebartitle={name} sidebarurl={uri}
-                            oncommand="toggleSidebar(this.id || this.observes);" xmlns={XUL}/>
+                            oncommand="toggleSidebar(this.id || this.observes);" xmlns={XUL}/>;
             }
 
             util.overlayWindow(window, { append: append.elements() });
@@ -307,10 +335,12 @@ var ConfigBase = Class("ConfigBase", {
     /**
      * @property {Object} A map of dialogs available via the
      *      :dialog command. Property names map dialog names to an array
-     *      as follows:
+     *      with the following elements:
      *  [0] description - A description of the dialog, used in
      *                    command completion results for :dialog.
      *  [1] action - The function executed by :dialog.
+     *  [2] test - Function which returns true if the dialog is available in
+     *      the current window. @optional
      */
     dialogs: {},
 
@@ -373,21 +403,25 @@ var ConfigBase = Class("ConfigBase", {
      */
     CSS: UTF8(String.replace(<><![CDATA[
         // <css>
-        Boolean      color: red;
-        Function     color: navy;
-        Null         color: blue;
-        Number       color: blue;
-        Object       color: maroon;
-        String       color: green; white-space: pre;
-
-        Key          font-weight: bold;
-
-        Enabled      color: blue;
-        Disabled     color: red;
-
-        FontFixed                            font-family: monospace !important;
-        FontCode            font-size: 9pt;  font-family: -mox-fixed, monospace !important;
-        FontProportional    font-size: 10pt; font-family: "Droid Sans", "Helvetica LT Std", Helvetica, "DejaVu Sans", Verdana, sans-serif !important;
+        Boolean      /* JavaScript booleans */       color: red;
+        Function     /* JavaScript functions */      color: navy;
+        Null         /* JavaScript null values */    color: blue;
+        Number       /* JavaScript numbers */        color: blue;
+        Object       /* JavaScript objects */        color: maroon;
+        String       /* String values */             color: green; white-space: pre;
+        Comment      /* JavaScriptor CSS comments */ color: gray;
+
+        Key          /* Keywords */                  font-weight: bold;
+
+        Enabled      /* Enabled item indicator text */  color: blue;
+        Disabled     /* Disabled item indicator text */ color: red;
+
+        FontFixed           /* The font used for fixed-width text */ \
+                                             font-family: monospace !important;
+        FontCode            /* The font used for code listings */ \
+                            font-size: 9pt;  font-family: monospace !important;
+        FontProportional    /* The font used for proportionally spaced text */ \
+                            font-size: 10pt; font-family: "Droid Sans", "Helvetica LT Std", Helvetica, "DejaVu Sans", Verdana, sans-serif !important;
 
         // Hack to give these groups slightly higher precedence
         // than their unadorned variants.
@@ -400,84 +434,130 @@ var ConfigBase = Class("ConfigBase", {
         CmdQuestion;[dactyl|highlight]   &#x0d; StatusQuestion;[dactyl|highlight]
         CmdWarningMsg;[dactyl|highlight] &#x0d; StatusWarningMsg;[dactyl|highlight]
 
-        Normal            color: black   !important; background: white       !important; font-weight: normal !important;
-        StatusNormal      color: inherit !important; background: transparent !important;
-        ErrorMsg          color: white   !important; background: red         !important; font-weight: bold !important;
-        InfoMsg           color: black   !important; background: white       !important;
-        StatusInfoMsg     color: inherit !important; background: transparent !important;
-        LineNr            color: orange  !important; background: white       !important;
-        ModeMsg           color: black   !important; background: white       !important;
-        StatusModeMsg     color: inherit !important; background: transparent !important; padding-right: 1em;
-        MoreMsg           color: green   !important; background: white       !important;
+        Normal            /* Normal text */ \
+                          color: black   !important; background: white       !important; font-weight: normal !important;
+        StatusNormal      /* Normal text in the status line */ \
+                          color: inherit !important; background: transparent !important;
+        ErrorMsg          /* Error messages */ \
+                          color: white   !important; background: red         !important; font-weight: bold !important;
+        InfoMsg           /* Information messages */ \
+                          color: black   !important; background: white       !important;
+        StatusInfoMsg     /* Information messages in the status line */ \
+                          color: inherit !important; background: transparent !important;
+        LineNr            /* The line number of an error */ \
+                          color: orange  !important; background: white       !important;
+        ModeMsg           /* The mode indicator */ \
+                          color: black   !important; background: white       !important;
+        StatusModeMsg     /* The mode indicator in the status line */ \
+                          color: inherit !important; background: transparent !important; padding-right: 1em;
+        MoreMsg           /* The indicator that there is more text to view */ \
+                          color: green   !important; background: white       !important;
         StatusMoreMsg                                background: transparent !important;
-        Message           white-space: pre-wrap !important; min-width: 100%; width: 100%; padding-left: 4em; text-indent: -4em; display: block;
-        Message String    white-space: pre-wrap;
-        NonText           color: blue; background: transparent !important;
-        *Preview          color: gray;
-        Question          color: green   !important; background: white       !important; font-weight: bold !important;
-        StatusQuestion    color: green   !important; background: transparent !important;
-        WarningMsg        color: red     !important; background: white       !important;
-        StatusWarningMsg  color: red     !important; background: transparent !important;
-
-        CmdLine;>*;;FontFixed   padding: 1px !important;
-        CmdPrompt;.dactyl-commandline-prompt
+        Message           /* A message as displayed in <ex>:messages</ex> */ \
+                          white-space: pre-wrap !important; min-width: 100%; width: 100%; padding-left: 4em; text-indent: -4em; display: block;
+        Message String    /* A message as displayed in <ex>:messages</ex> */ \
+                          white-space: pre-wrap;
+        NonText           /* The <em>~</em> indicators which mark blank lines in the completion list */ \
+                          color: blue; background: transparent !important;
+        *Preview          /* The completion preview displayed in the &tag.command-line; */ \
+                          color: gray;
+        Question          /* A prompt for a decision */ \
+                          color: green   !important; background: white       !important; font-weight: bold !important;
+        StatusQuestion    /* A prompt for a decision in the status line */ \
+                          color: green   !important; background: transparent !important;
+        WarningMsg        /* A warning message */ \
+                          color: red     !important; background: white       !important;
+        StatusWarningMsg  /* A warning message in the status line */ \
+                          color: red     !important; background: transparent !important;
+        Disabled          /* Disabled items */ \
+                          color: gray    !important;
+
+        CmdLine;>*;;FontFixed   /* The command line */ \
+                                padding: 1px !important;
+        CmdPrompt;.dactyl-commandline-prompt  /* The default styling form the command prompt */
         CmdInput;.dactyl-commandline-command
-        CmdOutput         white-space: pre;
-
+        CmdOutput         /* The output of commands executed by <ex>:run</ex> */ \
+                          white-space: pre;
 
-        CompGroup
+        CompGroup                      /* Item group in completion output */
         CompGroup:not(:first-of-type)  margin-top: .5em;
         CompGroup:last-of-type         padding-bottom: 1.5ex;
 
-        CompTitle            color: magenta; background: white; font-weight: bold;
+        CompTitle            /* Completion row titles */ \
+                             color: magenta; background: white; font-weight: bold;
         CompTitle>*          padding: 0 .5ex;
-        CompTitleSep         height: 1px; background: magenta; background: -moz-linear-gradient(60deg, magenta, white);
+        CompTitleSep         /* The element which separates the completion title from its results */ \
+                             height: 1px; background: magenta; background: -moz-linear-gradient(60deg, magenta, white);
 
-        CompMsg              font-style: italic; margin-left: 16px;
+        CompMsg              /* The message which may appear at the top of a group of completion results */ \
+                             font-style: italic; margin-left: 16px;
 
-        CompItem
+        CompItem             /* A single row of output in the completion list */
         CompItem:nth-child(2n+1)    background: rgba(0, 0, 0, .04);
-        CompItem[selected]   background: yellow;
+        CompItem[selected]   /* A selected row of completion list */ \
+                             background: yellow;
         CompItem>*           padding: 0 .5ex;
 
-        CompIcon             width: 16px; min-width: 16px; display: inline-block; margin-right: .5ex;
+        CompIcon             /* The favicon of a completion row */ \
+                             width: 16px; min-width: 16px; display: inline-block; margin-right: .5ex;
         CompIcon>img         max-width: 16px; max-height: 16px; vertical-align: middle;
 
-        CompResult           width: 36%; padding-right: 1%; overflow: hidden;
-        CompDesc             color: gray; width: 62%; padding-left: 1em;
-
-        CompLess             text-align: center; height: 0;    line-height: .5ex; padding-top: 1ex;
-        CompLess::after      content: "⌃";
-
-        CompMore             text-align: center; height: .5ex; line-height: .5ex; margin-bottom: -.5ex;
-        CompMore::after      content: "⌄";
-
-
-        EditorEditing;;*   background: #bbb !important; -moz-user-input: none !important; -moz-user-modify: read-only !important;
-        EditorError;;*     background: red !important;
-        EditorBlink1;;*    background: yellow !important;
-        EditorBlink2;;*
-
-        REPL                overflow: auto; max-height: 40em;
-        REPL-R;;;Question
-        REPL-E              white-space: pre-wrap;
-        REPL-P              white-space: pre-wrap; margin-bottom: 1em;
-
-        Usage               width: 100%;
-        UsageBody
-        UsageHead
-        UsageItem
+        CompResult           /* The result column of the completion list */ \
+                             width: 36%; padding-right: 1%; overflow: hidden;
+        CompDesc             /* The description column of the completion list */ \
+                             color: gray; width: 62%; padding-left: 1em;
+
+        CompLess             /* The indicator shown when completions may be scrolled up */ \
+                             text-align: center; height: 0;    line-height: .5ex; padding-top: 1ex;
+        CompLess::after      /* The character of indicator shown when completions may be scrolled up */ \
+                             content: "⌃";
+
+        CompMore             /* The indicator shown when completions may be scrolled down */ \
+                             text-align: center; height: .5ex; line-height: .5ex; margin-bottom: -.5ex;
+        CompMore::after      /* The character of indicator shown when completions may be scrolled down */ \
+                             content: "⌄";
+
+        Dense              /* Arbitrary elements which should be packed densely together */\
+                           margin-top: 0; margin-bottom: 0;
+
+        EditorEditing;;*   /* Text fields for which an external editor is open */ \
+                           background-color: #bbb !important; -moz-user-input: none !important; -moz-user-modify: read-only !important;
+        EditorError;;*     /* Text fields briefly after an error has occurred running the external editor */ \
+                           background: red !important;
+        EditorBlink1;;*    /* Text fields briefly after successfully running the external editor, alternated with EditorBlink2 */ \
+                           background: yellow !important;
+        EditorBlink2;;*    /* Text fields briefly after successfully running the external editor, alternated with EditorBlink1 */
+
+        REPL                /* Read-Eval-Print-Loop output */ \
+                            overflow: auto; max-height: 40em;
+        REPL-R;;;Question   /* Prompts in REPL mode */
+        REPL-E              /* Evaled input in REPL mode */ \
+                            white-space: pre-wrap;
+        REPL-P              /* Evaled output in REPL mode */ \
+                            white-space: pre-wrap; margin-bottom: 1em;
+
+        Usage               /* Output from the :*usage commands */ \
+                            width: 100%;
+        UsageHead           /* Headings in output from the :*usage commands */
+        UsageBody           /* The body of listings in output from the :*usage commands */
+        UsageItem           /* Individual items in output from the :*usage commands */
         UsageItem:nth-of-type(2n)    background: rgba(0, 0, 0, .04);
 
-        Indicator   color: blue; width: 1.5em; text-align: center;
-        Filter      font-weight: bold;
+        Indicator   /* The <em>#</em> and  <em>%</em> in the <ex>:buffers</ex> list */ \
+                    color: blue; width: 1.5em; text-align: center;
+        Filter      /* The matching text in a completion list */ \
+                    font-weight: bold;
 
-        Keyword     color: red;
-        Tag         color: blue;
+        Keyword     /* A bookmark keyword for a URL */ \
+                    color: red;
+        Tag         /* A bookmark tag for a URL */ \
+                    color: blue;
 
-        Link                        position: relative; padding-right: 2em;
+        Link                        /* A link with additional information shown on hover */ \
+                                    position: relative; padding-right: 2em;
         Link:not(:hover)>LinkInfo   opacity: 0; left: 0; width: 1px; height: 1px; overflow: hidden;
         LinkInfo                    {
+            /* Information shown when hovering over a link */
             color: black;
             position: absolute;
             left: 100%;
@@ -488,6 +568,7 @@ var ConfigBase = Class("ConfigBase", {
         }
 
         StatusLine;;;FontFixed  {
+            /* The status bar */
             -moz-appearance: none !important;
             font-weight: bold;
             background: transparent !important;
@@ -496,16 +577,24 @@ var ConfigBase = Class("ConfigBase", {
             min-height: 18px !important;
             text-shadow: none !important;
         }
-        StatusLineNormal;[dactyl|highlight]    color: white !important; background: black   !important;
-        StatusLineBroken;[dactyl|highlight]    color: black !important; background: #FFa0a0 !important; /* light-red */
-        StatusLineSecure;[dactyl|highlight]    color: black !important; background: #a0a0FF !important; /* light-blue */
-        StatusLineExtended;[dactyl|highlight]  color: black !important; background: #a0FFa0 !important; /* light-green */
-
-        TabClose;.tab-close-button
-        TabIcon;.tab-icon       min-width: 16px;
-        TabText;.tab-text
-        TabNumber               font-weight: bold; margin: 0px; padding-right: .8ex; cursor: default;
-        TabIconNumber {
+        StatusLineNormal;[dactyl|highlight]    /* The status bar for an ordinary web page */ \
+                                               color: white !important; background: black   !important;
+        StatusLineBroken;[dactyl|highlight]    /* The status bar for a broken web page */ \
+                                               color: black !important; background: #FFa0a0 !important; /* light-red */
+        StatusLineSecure;[dactyl|highlight]    /* The status bar for a secure web page */ \
+                                               color: black !important; background: #a0a0FF !important; /* light-blue */
+        StatusLineExtended;[dactyl|highlight]  /* The status bar for a secure web page with an Extended Validation (EV) certificate */ \
+                                               color: black !important; background: #a0FFa0 !important; /* light-green */
+
+        !TabClose;.tab-close-button            /* The close button of a browser tab */ \
+                                               /* The close button of a browser tab */
+        !TabIcon;.tab-icon,.tab-icon-image     /* The icon of a browser tab */ \
+                                               /* The icon of a browser tab */
+        !TabText;.tab-text                     /* The text of a browser tab */
+        TabNumber                              /* The number of a browser tab, next to its icon */ \
+                                               font-weight: bold; margin: 0px; padding-right: .8ex; cursor: default;
+        TabIconNumber  {
+            /* The number of a browser tab, over its icon */
             cursor: default;
             width: 16px;
             margin: 0 2px 0 -18px !important;
@@ -515,12 +604,16 @@ var ConfigBase = Class("ConfigBase", {
             text-shadow: black -1px 0 1px, black 0 1px 1px, black 1px 0 1px, black 0 -1px 1px;
         }
 
-        Title       color: magenta; font-weight: bold;
-        URL         text-decoration: none; color: green; background: inherit;
+        Title       /* The title of a listing, including <ex>:pageinfo</ex>, <ex>:jumps</ex> */ \
+                    color: magenta; font-weight: bold;
+        URL         /* A URL */ \
+                    text-decoration: none; color: green; background: inherit;
         URL:hover   text-decoration: underline; cursor: pointer;
-        URLExtra    color: gray;
+        URLExtra    /* Extra information about a URL */ \
+                    color: gray;
 
         FrameIndicator;;* {
+            /* The styling applied to briefly indicate the active frame */
             background-color: red;
             opacity: 0.5;
             z-index: 999999;
@@ -531,9 +624,11 @@ var ConfigBase = Class("ConfigBase", {
             right:    0;
         }
 
-        Bell          background-color: black !important;
+        Bell          /* &dactyl.appName;’s visual bell */ \
+                      background-color: black !important;
 
         Hint;;* {
+            /* A hint indicator. See <ex>:help hints</ex> */
             font:        bold 10px "Droid Sans Mono", monospace !important;
             margin:      -.2ex;
             padding:     0 0 0 1px;
@@ -543,47 +638,61 @@ var ConfigBase = Class("ConfigBase", {
         }
         Hint[active];;*  background: rgba(255, 253, 208, .8);
         Hint::after;;*   content: attr(text) !important;
-        HintElem;;*      background-color: yellow  !important; color: black !important;
-        HintActive;;*    background-color: #88FF00 !important; color: black !important;
-        HintImage;;*     opacity: .5 !important;
-
-        Button                  display: inline-block; font-weight: bold; cursor: pointer; color: black; text-decoration: none;
+        HintElem;;*      /* The hintable element */ \
+                         background-color: yellow  !important; color: black !important;
+        HintActive;;*    /* The hint element of link which will be followed by <k name="CR"/> */ \
+                         background-color: #88FF00 !important; color: black !important;
+        HintImage;;*     /* The indicator which floats above hinted images */ \
+                         opacity: .5 !important;
+
+        Button                  /* A button widget */ \
+                                display: inline-block; font-weight: bold; cursor: pointer; color: black; text-decoration: none;
         Button:hover            text-decoration: underline;
         Button[collapsed]       visibility: collapse; width: 0;
         Button::before          content: "["; color: gray; text-decoration: none !important;
         Button::after           content: "]"; color: gray; text-decoration: none !important;
         Button:not([collapsed]) ~ Button:not([collapsed])::before  content: "/[";
 
-        Buttons
+        Buttons                 /* A group of buttons */
 
-        DownloadCell                    display: table-cell; padding: 0 1ex;
+        DownloadCell                    /* A table cell in the :downloads manager */ \
+                                        display: table-cell; padding: 0 1ex;
 
-        Downloads                       display: table; margin: 0; padding: 0;
-        DownloadHead;;;CompTitle        display: table-row;
+        Downloads                       /* The :downloads manager */ \
+                                        display: table; margin: 0; padding: 0;
+        DownloadHead;;;CompTitle        /* A heading in the :downloads manager */ \
+                                        display: table-row;
         DownloadHead>*;;;DownloadCell
 
-        Download                        display: table-row;
+        Download                        /* A download in the :downloads manager */ \
+                                        display: table-row;
         Download:not([active])          color: gray;
+        Download:nth-child(2n+1)        background: rgba(0, 0, 0, .04);
 
         Download>*;;;DownloadCell
-        DownloadButtons
-        DownloadPercent
-        DownloadProgress
-        DownloadProgressHave
-        DownloadProgressTotal
-        DownloadSource
-        DownloadState
-        DownloadTime
-        DownloadTitle
+        DownloadButtons                 /* A button group in the :downloads manager */
+        DownloadPercent                 /* The percentage column for a download */
+        DownloadProgress                /* The progress column for a download */
+        DownloadProgressHave            /* The completed portion of the progress column */
+        DownloadProgressTotal           /* The remaining portion of the progress column */
+        DownloadSource                  /* The download source column for a download */
+        DownloadState                   /* The download state column for a download */
+        DownloadTime                    /* The time remaining column for a download */
+        DownloadTitle                   /* The title column for a download */
         DownloadTitle>Link>a         max-width: 48ex; overflow: hidden; display: inline-block;
 
-        AddonCell                    display: table-cell; padding: 0 1ex;
+        AddonCell                    /* A cell in tell :addons manager */ \
+                                     display: table-cell; padding: 0 1ex;
 
-        Addons                       display: table; margin: 0; padding: 0;
-        AddonHead;;;CompTitle        display: table-row;
+        Addons                       /* The :addons manager */ \
+                                     display: table; margin: 0; padding: 0;
+        AddonHead;;;CompTitle        /* A heading in the :addons manager */ \
+                                     display: table-row;
         AddonHead>*;;;AddonCell
 
-        Addon                        display: table-row;
+        Addon                        /* An add-on in the :addons manager */ \
+                                     display: table-row;
+        Addon:nth-child(2n+1)        background: rgba(0, 0, 0, .04);
 
         Addon>*;;;AddonCell
         AddonButtons
@@ -597,111 +706,154 @@ var ConfigBase = Class("ConfigBase", {
 
     helpCSS: UTF8(<><![CDATA[
         // <css>
-        InlineHelpLink                              font-size: inherit !important; font-family: inherit !important;
+        InlineHelpLink                              /* A help link shown in the command line or multi-line output area */ \
+                                                    font-size: inherit !important; font-family: inherit !important;
 
-        Help;;;FontProportional                     line-height: 1.4em;
+        Help;;;FontProportional                     /* A help page */ \
+                                                    line-height: 1.4em;
 
-        HelpInclude                                 margin: 2em 0;
+        HelpInclude                                 /* A help page included in the consolidated help listing */ \
+                                                    margin: 2em 0;
 
-        HelpArg;;;FontCode                          color: #6A97D4;
-        HelpOptionalArg;;;FontCode                  color: #6A97D4;
+        HelpArg;;;FontCode                          /* A required command argument indicator */ \
+                                                    color: #6A97D4;
+        HelpOptionalArg;;;FontCode                  /* An optional command argument indicator */ \
+                                                    color: #6A97D4;
 
-        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;;;FontCode                         display: block; white-space: pre; margin-left: 2em;
-        HelpTT;html|tt;dactyl://help/*;FontCode
+        HelpBody                                    /* The body of a help page */ \
+                                                    display: block; margin: 1em auto; max-width: 100ex; padding-bottom: 1em; margin-bottom: 4em; border-bottom-width: 1px;
+        HelpBorder;*;dactyl://help/*                /* The styling of bordered elements */ \
+                                                    border-color: silver; border-width: 0px; border-style: solid;
+        HelpCode;;;FontCode                         /* Code listings */ \
+                                                    display: block; white-space: pre; margin-left: 2em;
+        HelpTT;html|tt;dactyl://help/*;FontCode     /* Teletype text */
 
-        HelpDefault;;;FontCode                      display: inline-block; margin: -1px 1ex 0 0; white-space: pre; vertical-align: text-top;
+        HelpDefault;;;FontCode                      /* The default value of a help item */ \
+                                                    display: inline-block; margin: -1px 1ex 0 0; white-space: pre; vertical-align: text-top;
 
-        HelpDescription                             display: block; clear: right;
+        HelpDescription                             /* The description of a help item */ \
+                                                    display: block; clear: right;
         HelpDescription[short]                      clear: none;
-        HelpEm;html|em;dactyl://help/*              font-weight: bold; font-style: normal;
+        HelpEm;html|em;dactyl://help/*              /* Emphasized text */ \
+                                                    font-weight: bold; font-style: normal;
 
-        HelpEx;;;FontCode                           display: inline-block; color: #527BBD;
+        HelpEx;;;FontCode                           /* An Ex command */ \
+                                                    display: inline-block; color: #527BBD;
 
-        HelpExample                                 display: block; margin: 1em 0;
-        HelpExample::before                         content: "Example: "; font-weight: bold;
+        HelpExample                                 /* An example */ \
+                                                    display: block; margin: 1em 0;
+        HelpExample::before                         content: "__MSG_help.Example__: "; font-weight: bold;
 
-        HelpInfo                                    display: block; width: 20em; margin-left: auto;
-        HelpInfoLabel                               display: inline-block; width: 6em;  color: magenta; font-weight: bold; vertical-align: text-top;
-        HelpInfoValue                               display: inline-block; width: 14em; text-decoration: none;             vertical-align: text-top;
+        HelpInfo                                    /* Arbitrary information about a help item */ \
+                                                    display: block; width: 20em; margin-left: auto;
+        HelpInfoLabel                               /* The label for a HelpInfo item */ \
+                                                    display: inline-block; width: 6em;  color: magenta; font-weight: bold; vertical-align: text-top;
+        HelpInfoValue                               /* The details for a HelpInfo item */ \
+                                                    display: inline-block; width: 14em; text-decoration: none;             vertical-align: text-top;
 
-        HelpItem                                    display: block; margin: 1em 1em 1em 10em; clear: both;
+        HelpItem                                    /* A help item */ \
+                                                    display: block; margin: 1em 1em 1em 10em; clear: both;
 
-        HelpKey;;;FontCode                          color: #102663;
-        HelpKeyword                                 font-weight: bold; color: navy;
+        HelpKey;;;FontCode                          /* A keyboard key specification */ \
+                                                    color: #102663;
+        HelpKeyword                                 /* A keyword */ \
+                                                    font-weight: bold; color: navy;
 
-        HelpLink;html|a;dactyl://help/*             text-decoration: none !important;
+        HelpLink;html|a;dactyl://help/*             /* A hyperlink */ \
+                                                    text-decoration: none !important;
         HelpLink[href]:hover                        text-decoration: underline !important;
         HelpLink[href^="mailto:"]::after            content: "✉"; padding-left: .2em;
         HelpLink[rel=external] {
+            /* A hyperlink to an external resource */
             /* Thanks, Wikipedia */
             background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAMAAAC67D+PAAAAFVBMVEVmmcwzmcyZzP8AZswAZv////////9E6giVAAAAB3RSTlP///////8AGksDRgAAADhJREFUGFcly0ESAEAEA0Ei6/9P3sEcVB8kmrwFyni0bOeyyDpy9JTLEaOhQq7Ongf5FeMhHS/4AVnsAZubxDVmAAAAAElFTkSuQmCC) no-repeat scroll right center;
             padding-right: 13px;
         }
 
-
-        HelpTOC
-        HelpTOC>ol ol                               margin-left: -1em;
-
-        HelpOrderedList;ol;dactyl://help/*                          margin: 1em 0;
-        HelpOrderedList1;ol[level="1"],ol;dactyl://help/*           list-style: outside decimal; display: block;
-        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;
-        HelpOrderedList4;ol[level="4"],ol ol ol ol;dactyl://help/*  list-style: outside decimal;
-
-        HelpList;html|ul;dactyl://help/*      display: block; list-style-position: outside; margin: 1em 0;
-        HelpListItem;html|li;dactyl://help/*  display: list-item;
-
-
-        HelpNote                                    color: red; font-weight: bold;
-
-        HelpOpt;;;FontCode                          color: #106326;
-        HelpOptInfo;;;FontCode                      display: block; margin-bottom: 1ex; padding-left: 4em;
-
-        HelpParagraph;html|p;dactyl://help/*        display: block; margin: 1em 0em;
+        ErrorMsg HelpEx       color: inherit; background: inherit; text-decoration: underline;
+        ErrorMsg HelpKey      color: inherit; background: inherit; text-decoration: underline;
+        ErrorMsg HelpOption   color: inherit; background: inherit; text-decoration: underline;
+        ErrorMsg HelpTopic    color: inherit; background: inherit; text-decoration: underline;
+
+        HelpTOC               /* The Table of Contents for a help page */
+        HelpTOC>ol ol         margin-left: -1em;
+
+        HelpOrderedList;ol;dactyl://help/*                          /* Any ordered list */ \
+                                                                    margin: 1em 0;
+        HelpOrderedList1;ol[level="1"],ol;dactyl://help/*           /* A first-level ordered list */ \
+                                                                    list-style: outside decimal; display: block;
+        HelpOrderedList2;ol[level="2"],ol ol;dactyl://help/*        /* A second-level ordered list */ \
+                                                                    list-style: outside upper-alpha;
+        HelpOrderedList3;ol[level="3"],ol ol ol;dactyl://help/*     /* A third-level ordered list */ \
+                                                                    list-style: outside lower-roman;
+        HelpOrderedList4;ol[level="4"],ol ol ol ol;dactyl://help/*  /* A fourth-level ordered list */ \
+                                                                    list-style: outside decimal;
+
+        HelpList;html|ul;dactyl://help/*      /* An unordered list */ \
+                                              display: block; list-style-position: outside; margin: 1em 0;
+        HelpListItem;html|li;dactyl://help/*  /* A list item, ordered or unordered */ \
+                                              display: list-item;
+
+        HelpNote                                    /* The indicator for a note */ \
+                                                    color: red; font-weight: bold;
+
+        HelpOpt;;;FontCode                          /* An option name */ \
+                                                    color: #106326;
+        HelpOptInfo;;;FontCode                      /* Information about the type and default values for an option entry */ \
+                                                    display: block; margin-bottom: 1ex; padding-left: 4em;
+
+        HelpParagraph;html|p;dactyl://help/*        /* An ordinary paragraph */ \
+                                                    display: block; margin: 1em 0em;
         HelpParagraph:first-child                   margin-top: 0;
         HelpParagraph:last-child                    margin-bottom: 0;
-        HelpSpec;;;FontCode                         display: block; margin-left: -10em; float: left; clear: left; color: #527BBD; margin-right: 1em;
+        HelpSpec;;;FontCode                         /* The specification for a help entry */ \
+                                                    display: block; margin-left: -10em; float: left; clear: left; color: #527BBD; margin-right: 1em;
 
-        HelpString;;;FontCode                       color: green; font-weight: normal;
+        HelpString;;;FontCode                       /* A quoted string */ \
+                                                    color: green; font-weight: normal;
         HelpString::before                          content: '"';
         HelpString::after                           content: '"';
         HelpString[delim]::before                   content: attr(delim);
         HelpString[delim]::after                    content: attr(delim);
 
-        HelpNews        position: relative;
-        HelpNewsOld     opacity: .7;
-        HelpNewsNew     font-style: italic;
-        HelpNewsTag     font-style: normal; position: absolute; left: 100%; padding-left: 1em; color: #527BBD; opacity: .6; white-space: pre;
+        HelpNews        /* A news item */           position: relative;
+        HelpNewsOld     /* An old news item */      opacity: .7;
+        HelpNewsNew     /* A new news item */       font-style: italic;
+        HelpNewsTag     /* The version tag for a news item */ \
+                        font-style: normal; position: absolute; left: 100%; padding-left: 1em; color: #527BBD; opacity: .6; white-space: pre;
 
         HelpHead;html|h1,html|h2,html|h3,html|h4;dactyl://help/* {
+            /* Any help heading */
             font-weight: bold;
             color: #527BBD;
             clear: both;
         }
         HelpHead1;html|h1;dactyl://help/* {
+            /* A first-level help heading */
             margin: 2em 0 1em;
             padding-bottom: .2ex;
             border-bottom-width: 1px;
             font-size: 2em;
         }
         HelpHead2;html|h2;dactyl://help/* {
+            /* A second-level help heading */
             margin: 2em 0 1em;
             padding-bottom: .2ex;
             border-bottom-width: 1px;
             font-size: 1.2em;
         }
         HelpHead3;html|h3;dactyl://help/* {
+            /* A third-level help heading */
             margin: 1em 0;
             padding-bottom: .2ex;
             font-size: 1.1em;
         }
         HelpHead4;html|h4;dactyl://help/* {
+            /* A fourth-level help heading */
         }
 
-
         HelpTab;html|dl;dactyl://help/* {
+            /* A description table */
             display: table;
             width: 100%;
             margin: 1em 0;
@@ -712,19 +864,28 @@ var ConfigBase = Class("ConfigBase", {
         }
         HelpTabColumn;html|column;dactyl://help/*   display: table-column;
         HelpTabColumn:first-child                   width: 25%;
-        HelpTabTitle;html|dt;dactyl://help/*;FontCode  display: table-cell; padding: .1ex 1ex; font-weight: bold;
-        HelpTabDescription;html|dd;dactyl://help/*  display: table-cell; padding: .3ex 1em; text-indent: -1em; border-width: 0px;
+        HelpTabTitle;html|dt;dactyl://help/*;FontCode  /* The title column of description tables */ \
+                                                    display: table-cell; padding: .1ex 1ex; font-weight: bold;
+        HelpTabDescription;html|dd;dactyl://help/*  /* The description column of description tables */ \
+                                                    display: table-cell; padding: .3ex 1em; text-indent: -1em; border-width: 0px;
         HelpTabDescription>*;;dactyl://help/*       text-indent: 0;
-        HelpTabRow;html|dl>html|tr;dactyl://help/*  display: table-row;
-
-        HelpTag;;;FontCode                          display: inline-block; color: #527BBD; margin-left: 1ex; font-weight: normal;
-        HelpTags                                    display: block; float: right; clear: right;
-        HelpTopic;;;FontCode                        color: #102663;
-        HelpType;;;FontCode                         margin-right: 2ex;
-
-        HelpWarning                                 color: red; font-weight: bold;
-
-        HelpXML;;;FontCode                          color: #C5F779; background-color: #444444; font-family: Terminus, Fixed, monospace;
+        HelpTabRow;html|dl>html|tr;dactyl://help/*  /* Entire rows in description tables */ \
+                                                    display: table-row;
+
+        HelpTag;;;FontCode                          /* A help tag */ \
+                                                    display: inline-block; color: #527BBD; margin-left: 1ex; font-weight: normal;
+        HelpTags                                    /* A group of help tags */ \
+                                                    display: block; float: right; clear: right;
+        HelpTopic;;;FontCode                        /* A link to a help topic */ \
+                                                    color: #102663;
+        HelpType;;;FontCode                         /* An option type */ \
+                                                    color: #102663 !important; margin-right: 2ex;
+
+        HelpWarning                                 /* The indicator for a warning */ \
+                                                    color: red; font-weight: bold;
+
+        HelpXML;;;FontCode                          /* Highlighted XML */ \
+                                                    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;
index 15549e37cede5a2d10de6f1bb818571bd77bed00..f5d976cae6dc030069d379599edd318ee66abf63 100644 (file)
@@ -109,6 +109,9 @@ var Contexts = Module("contexts", {
 
                 completer: function (context) modules.completion.group(context)
             });
+
+            memoize(modules, "userContext",  function () contexts.Context(modules.io.getRCFile("~", true), contexts.user, [modules, true]));
+            memoize(modules, "_userContext", function () contexts.Context(modules.io.getRCFile("~", true), contexts.user, [modules.userContext]));
         },
 
         cleanup: function () {
@@ -121,7 +124,7 @@ var Contexts = Module("contexts", {
                 util.trapErrors("destroy", hive);
 
             for (let [name, plugin] in iter(this.modules.plugins.contexts))
-                if (plugin && "onUnload" in plugin)
+                if (plugin && "onUnload" in plugin && callable(plugin.onUnload))
                     util.trapErrors("onUnload", plugin);
         },
 
@@ -159,7 +162,7 @@ var Contexts = Module("contexts", {
                                                                 { _hive: { value: name } })));
 
                 memoize(contexts.groupsProto, name,
-                        function () [group[name] for (group in values(this.groups)) if (set.has(group, name))]);
+                        function () [group[name] for (group in values(this.groups)) if (Set.has(group, name))]);
             },
 
             get toStringParams() [this.name, this.Hive]
@@ -177,17 +180,18 @@ var Contexts = Module("contexts", {
                                   0);
 
         let contextPath = file.path;
-        let self = set.has(plugins, contextPath) && plugins.contexts[contextPath];
+        let self = Set.has(plugins, contextPath) && plugins.contexts[contextPath];
 
         if (self) {
-            if (set.has(self, "onUnload"))
+            if (Set.has(self, "onUnload"))
                 self.onUnload();
         }
         else {
             let name = isPlugin ? file.getRelativeDescriptor(isPlugin).replace(File.PATH_SEP, "-")
                                 : file.leafName;
 
-            self = update(newContext.apply(null, args || [userContext]), {
+            self = args && !isArray(args) ? args : newContext.apply(null, args || [userContext]);
+            update(self, {
                 NAME: Const(name.replace(/\.[^.]*$/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase())),
 
                 PATH: Const(file.path),
@@ -212,10 +216,12 @@ var Contexts = Module("contexts", {
                         contexts.removeGroup(this.GROUP);
                 })
             });
-            Class.replaceProperty(plugins, file.path, self);
+
+            if (group !== this.user)
+                Class.replaceProperty(plugins, file.path, self);
 
             // This belongs elsewhere
-            if (isPlugin && args)
+            if (isPlugin)
                 Object.defineProperty(plugins, self.NAME, {
                     configurable: true,
                     enumerable: true,
@@ -233,7 +239,7 @@ var Contexts = Module("contexts", {
             group = this.addGroup(commands.nameRegexp
                                           .iterate(name.replace(/\.[^.]*$/, ""))
                                           .join("-").replace(/--+/g, "-"),
-                                  "Script group for " + file.path,
+                                  _("context.scriptGroup", file.path),
                                   null, false);
 
         Class.replaceProperty(self, "GROUP", group);
@@ -272,7 +278,7 @@ var Contexts = Module("contexts", {
     })),
 
     matchingGroups: function (uri) Object.create(this.groupsProto, {
-        groups: { value: this.activeGroups(uri) },
+        groups: { value: this.activeGroups(uri) }
     }),
 
     activeGroups: function (uri, doc) {
@@ -288,7 +294,7 @@ var Contexts = Module("contexts", {
 
     initializedGroups: function (hive)
         let (need = hive ? [hive] : Object.keys(this.hives))
-            this.groupList.filter(function (group) need.some(set.has(group))),
+            this.groupList.filter(function (group) need.some(Set.has(group))),
 
     addGroup: function addGroup(name, description, filter, persist, replace) {
         let group = this.getGroup(name);
@@ -307,7 +313,7 @@ var Contexts = Module("contexts", {
             if (description)
                 group.description = description;
             if (filter)
-                group.filter = filter
+                group.filter = filter;
             group.persist = persist;
         }
 
@@ -344,7 +350,7 @@ var Contexts = Module("contexts", {
     getGroup: function getGroup(name, hive) {
         if (name === "default")
             var group = this.context && this.context.context && this.context.context.GROUP;
-        else if (set.has(this.groupMap, name))
+        else if (Set.has(this.groupMap, name))
             group = this.groupMap[name];
 
         if (group && hive)
@@ -352,15 +358,35 @@ var Contexts = Module("contexts", {
         return group;
     },
 
+    getDocs: function getDocs(context) {
+        try {
+            if (isinstance(context, ["Sandbox"])) {
+                let info = "INFO" in context && Cu.evalInSandbox("this.INFO instanceof XML && INFO.toXMLString()", context);
+                return info && XML(info);
+            }
+            if (typeof context.INFO == "xml")
+                return context.INFO;
+        }
+        catch (e) {}
+        return null;
+    },
+
     bindMacro: function (args, default_, params) {
         const { dactyl, events, modules } = this.modules;
 
+        function Proxy(obj, key) Class.Property({
+            configurable: true,
+            enumerable: true,
+            get: function Proxy_get() process(obj[key]),
+            set: function Proxy_set(val) obj[key] = val
+        })
+
         let process = util.identity;
 
         if (callable(params))
             var makeParams = function makeParams(self, args)
-                iter.toObject([k, process(v)]
-                               for ([k, v] in iter(params.apply(self, args))));
+                let (obj = params.apply(self, args))
+                    iter.toObject([k, Proxy(obj, k)] for (k in properties(obj)));
         else if (params)
             makeParams = function makeParams(self, args)
                 iter.toObject([name, process(args[i])]
@@ -368,6 +394,7 @@ var Contexts = Module("contexts", {
 
         let rhs = args.literalArg;
         let type = ["-builtin", "-ex", "-javascript", "-keys"].reduce(function (a, b) args[b] ? b : a, default_);
+
         switch (type) {
         case "-builtin":
             let noremap = true;
@@ -378,9 +405,10 @@ var Contexts = Module("contexts", {
             var action = function action() {
                 events.feedkeys(action.macro(makeParams(this, arguments)),
                                 noremap, silent);
-            }
+            };
             action.macro = util.compileMacro(rhs, true);
             break;
+
         case "-ex":
             action = function action() modules.commands
                                               .execute(action.macro, makeParams(this, arguments),
@@ -388,6 +416,7 @@ var Contexts = Module("contexts", {
             action.macro = util.compileMacro(rhs, true);
             action.context = this.context && update({}, this.context);
             break;
+
         case "-javascript":
             if (callable(params))
                 action = dactyl.userEval("(function action() { with (action.makeParams(this, arguments)) {" + args.literalArg + "} })");
@@ -398,6 +427,7 @@ var Contexts = Module("contexts", {
             action.makeParams = makeParams;
             break;
         }
+
         action.toString = function toString() (type === default_ ? "" : type + " ") + rhs;
         args = null;
         return action;
@@ -466,9 +496,9 @@ var Contexts = Module("contexts", {
                     if (args.has("-locations"))
                         group.filter = filter;
                     if (args.has("-description"))
-                        group.description = args["-description"]
+                        group.description = args["-description"];
                     if (args.has("-nopersist"))
-                        group.persist = !args["-nopersist"]
+                        group.persist = !args["-nopersist"];
                 }
 
                 if (!group.builtin && args.has("-args")) {
@@ -482,7 +512,7 @@ var Contexts = Module("contexts", {
 
                 util.assert(!group.builtin ||
                                 !["-description", "-locations", "-nopersist"]
-                                    .some(set.has(args.explicitOpts)),
+                                    .some(Set.has(args.explicitOpts)),
                             _("group.cantModifyBuiltin"));
             },
             {
@@ -540,14 +570,23 @@ var Contexts = Module("contexts", {
         commands.add(["delg[roup]"],
             "Delete a group",
             function (args) {
-                util.assert(contexts.getGroup(args[0]), _("group.noSuch", args[0]));
-                contexts.removeGroup(args[0]);
+                util.assert(args.bang ^ !!args[0], _("error.argumentOrBang"));
+
+                if (args.bang)
+                    contexts.groupList = contexts.groupList.filter(function (g) g.builtin);
+                else {
+                    util.assert(contexts.getGroup(args[0]), _("group.noSuch", args[0]));
+                    contexts.removeGroup(args[0]);
+                }
             },
             {
-                argCount: "1",
+                argCount: "?",
+                bang: true,
                 completer: function (context, args) {
-                    modules.completion.group(context);
+                    if (args.bang)
+                        return;
                     context.filters.push(function ({ item }) !item.builtin);
+                    modules.completion.group(context);
                 }
             });
 
index 403c1e418a4642ec6867d1a6b47babb0b8557549..faee6e22a7a1ce5a28bcda6019c2e99dc9c05a68 100644 (file)
@@ -7,7 +7,7 @@
 Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("downloads", {
     exports: ["Download", "Downloads", "downloads"],
-    use: ["io", "prefs", "services", "util"]
+    use: ["io", "messages", "prefs", "services", "util"]
 }, this);
 
 Cu.import("resource://gre/modules/DownloadUtils.jsm", this);
@@ -32,30 +32,38 @@ var Download = Class("Download", {
             <tr highlight="Download" key="row" xmlns:dactyl={NS} xmlns={XHTML}>
                 <td highlight="DownloadTitle">
                     <span highlight="Link">
-                        <a key="launch" dactyl:command="download.command"
+                        <a key="launch"
                            href={self.target.spec} path={self.targetFile.path}>{self.displayName}</a>
                         <span highlight="LinkInfo">{self.targetFile.path}</span>
                     </span>
                 </td>
                 <td highlight="DownloadState" key="state"/>
                 <td highlight="DownloadButtons Buttons">
-                    <a highlight="Button" key="pause">Pause</a>
-                    <a highlight="Button" key="remove">Remove</a>
-                    <a highlight="Button" key="resume">Resume</a>
-                    <a highlight="Button" key="retry">Retry</a>
-                    <a highlight="Button" key="cancel">Cancel</a>
-                    <a highlight="Button" key="delete">Delete</a>
+                    <a highlight="Button" href="javascript:0" key="pause">{_("download.action.Pause")}</a>
+                    <a highlight="Button" href="javascript:0" key="remove">{_("download.action.Remove")}</a>
+                    <a highlight="Button" href="javascript:0" key="resume">{_("download.action.Resume")}</a>
+                    <a highlight="Button" href="javascript:0" key="retry">{_("download.action.Retry")}</a>
+                    <a highlight="Button" href="javascript:0" key="cancel">{_("download.action.Cancel")}</a>
+                    <a highlight="Button" href="javascript:0" key="delete">{_("download.action.Delete")}</a>
                 </td>
                 <td highlight="DownloadProgress" key="progress">
                     <span highlight="DownloadProgressHave" key="progressHave"
                     />/<span highlight="DownloadProgressTotal" key="progressTotal"/>
                 </td>
                 <td highlight="DownloadPercent" key="percent"/>
+                <td highlight="DownloadSpeed" key="speed"/>
                 <td highlight="DownloadTime" key="time"/>
                 <td><a highlight="DownloadSource" key="source" href={self.source.spec}>{self.source.spec}</a></td>
             </tr>,
             this.list.document, this.nodes);
 
+        this.nodes.launch.addEventListener("click", function (event) {
+            if (event.button == 0) {
+                event.preventDefault();
+                self.command("launch");
+            }
+        }, false);
+
         self.updateStatus();
         return self;
     },
@@ -78,10 +86,13 @@ var Download = Class("Download", {
     })),
 
     command: function command(name) {
-        util.assert(set.has(this.allowedCommands, name), "Unknown command");
-        util.assert(this.allowedCommands[name], "Command not allowed");
+        util.assert(Set.has(this.allowedCommands, name), _("download.unknownCommand"));
+        util.assert(this.allowedCommands[name], _("download.commandNotAllowed"));
 
-        services.downloadManager[name + "Download"](this.id);
+        if (Set.has(this.commands, name))
+            this.commands[name].call(this);
+        else
+            services.downloadManager[name + "Download"](this.id);
     },
 
     commands: {
@@ -95,7 +106,7 @@ var Download = Class("Download", {
             function action() {
                 try {
                     if (this.MIMEInfo && this.MIMEInfo.preferredAction == this.MIMEInfo.useHelperApp)
-                        this.MIMEInfo.launchWithFile(file)
+                        this.MIMEInfo.launchWithFile(file);
                     else
                         file.launch();
                 }
@@ -106,7 +117,7 @@ var Download = Class("Download", {
 
             let file = io.File(this.targetFile);
             if (file.isExecutable() && prefs.get("browser.download.manager.alertOnEXEOpen", true))
-                this.list.modules.commandline.input("This will launch an executable download. Continue? (yes/[no]/always) ",
+                this.list.modules.commandline.input(_("download.prompt.launchExecutable") + " ",
                     function (resp) {
                         if (/^a(lways)$/i.test(resp)) {
                             prefs.set("browser.download.manager.alertOnEXEOpen", false);
@@ -120,26 +131,48 @@ var Download = Class("Download", {
         }
     },
 
-    compare: function compare(other) String.localeCompare(this.displayName, other.displayName),
+    _compare: {
+        active: function (a, b) a.alive - b.alive,
+        complete: function (a, b) a.percentComplete - b.percentComplete,
+        date: function (a, b) a.startTime - b.startTime,
+        filename: function (a, b) String.localeCompare(a.targetFile.leafName, b.targetFile.leafName),
+        size: function (a, b) a.size - b.size,
+        speed: function (a, b) a.speed - b.speed,
+        time: function (a, b) a.timeRemaining - b.timeRemaining,
+        url: function (a, b) String.localeCompare(a.source.spec, b.source.spec)
+    },
+
+    compare: function compare(other) values(this.list.sortOrder).map(function (order) {
+        let val = this._compare[order.substr(1)](this, other);
+
+        return (order[0] == "-") ? -val : val;
+    }, this).nth(util.identity, 0) || 0,
 
     timeRemaining: Infinity,
 
     updateProgress: function updateProgress() {
         let self = this.__proto__;
 
-        if (this.amountTransferred === this.size)
+        if (this.amountTransferred === this.size) {
+            this.nodes.speed.textContent = "";
             this.nodes.time.textContent = "";
-        else if (this.speed == 0 || this.size == 0)
-            this.nodes.time.textContent = "Unknown";
+        }
         else {
-            let seconds = (this.size - this.amountTransferred) / this.speed;
-            [, self.timeRemaining] = DownloadUtils.getTimeLeft(seconds, this.timeRemaining);
-            if (this.timeRemaining)
-                this.nodes.time.textContent = util.formatSeconds(this.timeRemaining);
-            else
-                this.nodes.time.textContent = "~1 second";
+            this.nodes.speed.textContent = util.formatBytes(this.speed, 1, true) + "/s";
+
+            if (this.speed == 0 || this.size == 0)
+                this.nodes.time.textContent = _("download.unknown");
+            else {
+                let seconds = (this.size - this.amountTransferred) / this.speed;
+                [, self.timeRemaining] = DownloadUtils.getTimeLeft(seconds, this.timeRemaining);
+                if (this.timeRemaining)
+                    this.nodes.time.textContent = util.formatSeconds(this.timeRemaining);
+                else
+                    this.nodes.time.textContent = _("download.almostDone");
+            }
         }
-        let total = this.nodes.progressTotal.textContent = this.size ? util.formatBytes(this.size, 1, true) : "Unknown";
+
+        let total = this.nodes.progressTotal.textContent = this.size ? util.formatBytes(this.size, 1, true) : _("download.unknown");
         let suffix = RegExp(/( [a-z]+)?$/i.exec(total)[0] + "$");
         this.nodes.progressHave.textContent = util.formatBytes(this.amountTransferred, 1, true).replace(suffix, "");
 
@@ -165,7 +198,8 @@ var DownloadList = Class("DownloadList",
                          XPCOM([Ci.nsIDownloadProgressListener,
                                 Ci.nsIObserver,
                                 Ci.nsISupportsWeakReference]), {
-    init: function init(modules, filter) {
+    init: function init(modules, filter, sort) {
+        this.sortOrder = sort;
         this.modules = modules;
         this.filter = filter && filter.toLowerCase();
         this.nodes = {
@@ -173,6 +207,7 @@ var DownloadList = Class("DownloadList",
         };
         this.downloads = {};
     },
+
     cleanup: function cleanup() {
         this.observe.unregister();
         services.downloadManager.removeListener(this);
@@ -182,26 +217,28 @@ var DownloadList = Class("DownloadList",
 
         util.xmlToDom(<table highlight="Downloads" key="list" xmlns={XHTML}>
                         <tr highlight="DownloadHead">
-                            <span>Title</span>
-                            <span>Status</span>
+                            <span>{_("title.Title")}</span>
+                            <span>{_("title.Status")}</span>
                             <span/>
-                            <span>Progress</span>
+                            <span>{_("title.Progress")}</span>
                             <span/>
-                            <span>Time remaining</span>
-                            <span>Source</span>
+                            <span>{_("title.Speed")}</span>
+                            <span>{_("title.Time remaining")}</span>
+                            <span>{_("title.Source")}</span>
                         </tr>
                         <tr highlight="Download"><span><div style="min-height: 1ex; /* FIXME */"/></span></tr>
                         <tr highlight="Download" key="totals" active="true">
-                            <td><span highlight="Title">Totals:</span>&#xa0;<span key="total"/></td>
+                            <td><span highlight="Title">{_("title.Totals")}:</span>&#xa0;<span key="total"/></td>
                             <td/>
                             <td highlight="DownloadButtons">
-                                <a highlight="Button" key="clear">Clear</a>
+                                <a highlight="Button" href="javascript:0" key="clear">{_("download.action.Clear")}</a>
                             </td>
                             <td highlight="DownloadProgress" key="progress">
                                 <span highlight="DownloadProgressHave" key="progressHave"
                                 />/<span highlight="DownloadProgressTotal" key="progressTotal"/>
                             </td>
                             <td highlight="DownloadPercent" key="percent"/>
+                            <td highlight="DownloadSpeed" key="speed"/>
                             <td highlight="DownloadTime" key="time"/>
                             <td/>
                         </tr>
@@ -253,6 +290,17 @@ var DownloadList = Class("DownloadList",
         }
     },
 
+    sort: function sort() {
+        let list = values(this.downloads).sort(function (a, b) a.compare(b));
+
+        for (let [i, download] in iter(list))
+            if (this.nodes.list.childNodes[i + 1] != download.nodes.row)
+                this.nodes.list.insertBefore(download.nodes.row,
+                                             this.nodes.list.childNodes[i + 1]);
+    },
+
+    shouldSort: function shouldSort() Array.some(arguments, function (val) this.sortOrder.some(function (v) v.substr(1) == val), this),
+
     update: function update() {
         for (let node in values(this.nodes))
             if (node.update && node.update != update)
@@ -277,9 +325,12 @@ var DownloadList = Class("DownloadList",
 
         let active = downloads.filter(function (dl) dl.alive).length;
         if (active)
-            this.nodes.total.textContent = active + " active";
-        else for (let key in values(["total", "percent", "time"]))
+            this.nodes.total.textContent = _("download.nActive", active);
+        else for (let key in values(["total", "percent", "speed", "time"]))
             this.nodes[key].textContent = "";
+
+        if (this.shouldSort("complete", "size", "speed", "time"))
+            this.sort();
     },
 
     observers: {
@@ -305,11 +356,15 @@ var DownloadList = Class("DownloadList",
                 this.nodes.list.scrollIntoView(false);
             }
             this.update();
+
+            if (this.shouldSort("active"))
+                this.sort();
         }
         catch (e) {
             util.reportError(e);
         }
     },
+
     onProgressChange: function (webProgress, request,
                                 curProgress, maxProgress,
                                 curTotalProgress, maxTotalProgress,
@@ -328,17 +383,85 @@ var DownloadList = Class("DownloadList",
 var Downloads = Module("downloads", {
 }, {
 }, {
-    commands: function (dactyl, modules, window) {
-        const { commands } = modules;
+    commands: function initCommands(dactyl, modules, window) {
+        const { commands, CommandOption } = modules;
 
         commands.add(["downl[oads]", "dl"],
             "Display the downloads list",
             function (args) {
-                let downloads = DownloadList(modules, args[0]);
+                let downloads = DownloadList(modules, args[0], args["-sort"]);
                 modules.commandline.echo(downloads);
             },
             {
-                argCount: "?"
+                argCount: "?",
+                options: [
+                    {
+                        names: ["-sort", "-s"],
+                        description: "Sort order (see 'downloadsort')",
+                        type: CommandOption.LIST,
+                        get default() modules.options["downloadsort"],
+                        completer: function (context, args) modules.options.get("downloadsort").completer(context, { values: args["-sort"] }),
+                        validator: function (value) modules.options.get("downloadsort").validator(value)
+                    }
+                ]
+            });
+
+        commands.add(["dlc[lear]"],
+            "Clear completed downloads",
+            function (args) { services.downloadManager.cleanUp(); });
+    },
+    options: function initOptions(dactyl, modules, window) {
+        const { options } = modules;
+
+        if (false)
+        options.add(["downloadcolumns", "dlc"],
+            "The columns to show in the download manager",
+            "stringlist", "filename,state,buttons,progress,percent,time,url",
+            {
+                values: {
+                    buttons:    "Control buttons",
+                    filename:   "Target filename",
+                    percent:    "Percent complete",
+                    size:       "File size",
+                    speed:      "Download speed",
+                    state:      "The download's state",
+                    time:       "Time remaining",
+                    url:        "Source URL"
+                }
+            });
+
+        options.add(["downloadsort", "dlsort", "dls"],
+            ":downloads sort order",
+            "stringlist", "-active,+filename",
+            {
+                values: {
+                    active:     "Whether download is active",
+                    complete:   "Percent complete",
+                    date:       "Date and time the download began",
+                    filename:   "Target filename",
+                    size:       "File size",
+                    speed:      "Download speed",
+                    time:       "Time remaining",
+                    url:        "Source URL"
+                },
+
+                completer: function (context, extra) {
+                    let seen = Set.has(Set(extra.values.map(function (val) val.substr(1))));
+
+                    context.completions = iter(this.values).filter(function ([k, v]) !seen(k))
+                                                           .map(function ([k, v]) [["+" + k, [v, " (", _("sort.ascending"), ")"].join("")],
+                                                                                   ["-" + k, [v, " (", _("sort.descending"), ")"].join("")]])
+                                                           .flatten().array;
+                },
+
+                has: function () Array.some(arguments, function (val) this.value.some(function (v) v.substr(1) == val)),
+
+                validator: function (value) {
+                    let seen = {};
+                    return value.every(function (val) /^[+-]/.test(val) && Set.has(this.values, val.substr(1))
+                                                                        && !Set.add(seen, val.substr(1)),
+                                       this) && value.length;
+                }
             });
     }
 });
index 5edd5904f4b0d657ec3cd754461338cca5ff703d..e2d24bb020495fa74ee45d713d963ac31957f679 100644 (file)
@@ -7,11 +7,14 @@
 Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("finder", {
     exports: ["RangeFind", "RangeFinder", "rangefinder"],
+    require: ["prefs"],
     use: ["messages", "services", "util"]
 }, this);
 
 function equals(a, b) XPCNativeWrapper(a) == XPCNativeWrapper(b);
 
+try {
+
 /** @instance rangefinder */
 var RangeFinder = Module("rangefinder", {
     Local: function (dactyl, modules, window) ({
@@ -31,6 +34,12 @@ var RangeFinder = Module("rangefinder", {
         set rangeFind(val) modules.buffer.localStore.rangeFind = val
     }),
 
+    init: function init() {
+        prefs.safeSet("accessibility.typeaheadfind.autostart", false);
+        // The above should be sufficient, but: http://dactyl.sf.net/bmo/348187
+        prefs.safeSet("accessibility.typeaheadfind", false);
+    },
+
     get commandline() this.modules.commandline,
     get modes() this.modules.modes,
     get options() this.modules.options,
@@ -41,10 +50,13 @@ var RangeFinder = Module("rangefinder", {
 
         if (this.rangeFind && equals(this.rangeFind.window.get(), this.window))
             this.rangeFind.reset();
-        this.find("", mode === this.modes.FIND_BACKWARD);
+        this.find("", mode == this.modes.FIND_BACKWARD);
     },
 
     bootstrap: function (str, backward) {
+        if (arguments.length < 2 && this.rangeFind)
+            backward = this.rangeFind.reverse;
+
         let highlighted = this.rangeFind && this.rangeFind.highlighted;
         let selections = this.rangeFind && this.rangeFind.selections;
         let linksOnly = false;
@@ -132,6 +144,10 @@ var RangeFinder = Module("rangefinder", {
         }
     },
 
+    onHistory: function () {
+        this.rangeFind.found = false;
+    },
+
     onSubmit: function (command) {
         if (!this.options["incfind"] || !this.rangeFind || !this.rangeFind.found) {
             this.clear();
@@ -168,7 +184,7 @@ var RangeFinder = Module("rangefinder", {
 
         modes.addMode("FIND", {
             description: "Find mode, active when typing search input",
-            bases: [modes.COMMAND_LINE],
+            bases: [modes.COMMAND_LINE]
         });
         modes.addMode("FIND_FORWARD", {
             description: "Forward Find mode, active when typing search input",
@@ -200,40 +216,41 @@ var RangeFinder = Module("rangefinder", {
 
             get onCancel() modules.rangefinder.closure.onCancel,
             get onChange() modules.rangefinder.closure.onChange,
+            get onHistory() modules.rangefinder.closure.onHistory,
             get onSubmit() modules.rangefinder.closure.onSubmit
         });
     },
     mappings: function (dactyl, modules, window) {
-        const { buffer, config, mappings, modes, rangefinder } = modules;
+        const { Buffer, buffer, config, mappings, modes, rangefinder } = modules;
         var myModes = config.browserModes.concat([modes.CARET]);
 
         mappings.add(myModes,
-            ["/"], "Find a pattern starting at the current caret position",
+            ["/", "<find-forward>"], "Find a pattern starting at the current caret position",
             function () { rangefinder.openPrompt(modes.FIND_FORWARD); });
 
         mappings.add(myModes,
-            ["?"], "Find a pattern backward of the current caret position",
+            ["?", "<find-backward>"], "Find a pattern backward of the current caret position",
             function () { rangefinder.openPrompt(modes.FIND_BACKWARD); });
 
         mappings.add(myModes,
-            ["n"], "Find next",
+            ["n", "<find-next>"], "Find next",
             function () { rangefinder.findAgain(false); });
 
         mappings.add(myModes,
-            ["N"], "Find previous",
+            ["N", "<find-previous>"], "Find previous",
             function () { rangefinder.findAgain(true); });
 
-        mappings.add(myModes.concat([modes.CARET, modes.TEXT_EDIT]), ["*"],
+        mappings.add(myModes.concat([modes.CARET, modes.TEXT_EDIT]), ["*", "<find-word-forward>"],
             "Find word under cursor",
             function () {
-                rangefinder.find(buffer.getCurrentWord(), false);
+                rangefinder.find(Buffer.currentWord(buffer.focusedFrame, true), false);
                 rangefinder.findAgain();
             });
 
-        mappings.add(myModes.concat([modes.CARET, modes.TEXT_EDIT]), ["#"],
+        mappings.add(myModes.concat([modes.CARET, modes.TEXT_EDIT]), ["#", "<find-word-backward>"],
             "Find word under cursor backwards",
             function () {
-                rangefinder.find(buffer.getCurrentWord(), true);
+                rangefinder.find(Buffer.currentWord(buffer.focusedFrame, true), true);
                 rangefinder.findAgain();
             });
 
@@ -242,10 +259,6 @@ var RangeFinder = Module("rangefinder", {
         const { options, rangefinder } = modules;
         const { prefs } = require("prefs");
 
-        // prefs.safeSet("accessibility.typeaheadfind.autostart", false);
-        // The above should be sufficient, but: https://bugzilla.mozilla.org/show_bug.cgi?id=348187
-        prefs.safeSet("accessibility.typeaheadfind", false);
-
         options.add(["hlfind", "hlf"],
             "Highlight all /find pattern matches on the current page after submission",
             "boolean", false, {
@@ -267,7 +280,7 @@ var RangeFinder = Module("rangefinder", {
             });
 
         options.add(["incfind", "if"],
-            "Find a pattern incrementally as it is typed rather than awaiting <Return>",
+            "Find a pattern incrementally as it is typed rather than awaiting c_<Return>",
             "boolean", true);
     }
 });
@@ -369,7 +382,7 @@ var RangeFind = Class("RangeFind", {
         let doc = range.startContainer.ownerDocument;
         let win = doc.defaultView;
         let ranges = this.ranges.filter(function (r)
-            r.window === win && RangeFind.contains(r.range, range));
+            r.window === win && RangeFind.sameDocument(r.range, range) && RangeFind.contains(r.range, range));
 
         if (this.backward)
             return ranges[ranges.length - 1];
@@ -390,7 +403,7 @@ var RangeFind = Class("RangeFind", {
             var node = util.evaluateXPath(RangeFind.selectNodePath,
                                           this.lastRange.commonAncestorContainer).snapshotItem(0);
         if (node) {
-            node.focus()
+            node.focus();
             // Re-highlight collapsed selection
             this.selectedRange = this.lastRange;
         }
@@ -480,7 +493,9 @@ var RangeFind = Class("RangeFind", {
                     frames.push(r);
             }
 
-            let range = start.startContainer.ownerDocument.createRange();
+            let doc = start.startContainer.ownerDocument;
+
+            let range = doc.createRange();
             range.setStart(start.startContainer, start.startOffset);
             range.setEnd(end.startContainer, end.startOffset);
 
@@ -492,7 +507,7 @@ var RangeFind = Class("RangeFind", {
         }
         function rec(win) {
             let doc = win.document;
-            let pageRange = RangeFind.nodeRange(doc.body || doc.documentElement.lastChild);
+            let pageRange = RangeFind[doc.body ? "nodeRange" : "nodeContents"](doc.body || doc.documentElement);
             backup = backup || pageRange;
             let pageStart = RangeFind.endpoint(pageRange, true);
             let pageEnd = RangeFind.endpoint(pageRange, false);
@@ -507,6 +522,14 @@ var RangeFind = Class("RangeFind", {
                 }
             }
             pushRange(pageStart, pageEnd);
+
+            let anonNodes = doc.getAnonymousNodes(doc.documentElement);
+            if (anonNodes) {
+                for (let [, elem] in iter(anonNodes)) {
+                    let range = RangeFind.nodeContents(elem);
+                    pushRange(RangeFind.endpoint(range, true), RangeFind.endpoint(range, false));
+                }
+            }
         }
         rec(win);
         if (frames.length == 0)
@@ -724,6 +747,14 @@ var RangeFind = Class("RangeFind", {
             return false;
         }
     },
+    nodeContents: function (node) {
+        let range = node.ownerDocument.createRange();
+        try {
+            range.selectNodeContents(node);
+        }
+        catch (e) {}
+        return range;
+    },
     nodeRange: function (node) {
         let range = node.ownerDocument.createRange();
         try {
@@ -732,10 +763,23 @@ var RangeFind = Class("RangeFind", {
         catch (e) {}
         return range;
     },
-    sameDocument: function (r1, r2) r1 && r2 && r1.endContainer.ownerDocument == r2.endContainer.ownerDocument,
+    sameDocument: function (r1, r2) {
+        if (!(r1 && r2 && r1.endContainer.ownerDocument == r2.endContainer.ownerDocument))
+            return false;
+        try {
+            r1.compareBoundaryPoints(r1.START_TO_START, r2);
+        }
+        catch (e if e.result == 0x80530004 /* NS_ERROR_DOM_WRONG_DOCUMENT_ERR */) {
+            return false;
+        }
+        return true;
+    },
     selectNodePath: ["a", "xhtml:a", "*[@onclick]"].map(function (p) "ancestor-or-self::" + p).join(" | ")
 });
 
+} catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
+
 endModule();
 
+
 // vim: set fdm=marker sw=4 ts=4 et ft=javascript:
index 35288942f54c24afb5b97cfa1e8b09a65fee63da..73b6b06ae7cf35ed166aa9b003e442d3285538bd 100644 (file)
@@ -33,6 +33,8 @@ Highlight.liveProperty = function (name, prop) {
                     h.style.css = h.css;
 
         this.style[prop || name] = this[prop || name];
+        if (this.onChange)
+            this.onChange();
     });
 }
 Highlight.liveProperty("agent");
@@ -100,7 +102,7 @@ var Highlights = Module("Highlight", {
     __iterator__: function () values(this.highlight).sort(function (a, b) String.localeCompare(a.class, b.class))
                                                     .iterValues(),
 
-    _create: function (agent, args) {
+    _create: function _create(agent, args) {
         let obj = Highlight.apply(Highlight, args);
 
         if (!isArray(obj.sites))
@@ -143,9 +145,9 @@ var Highlights = Module("Highlight", {
         return obj;
     },
 
-    get: function (k) this.highlight[k],
+    get: function get(k) this.highlight[k],
 
-    set: function (key, newStyle, force, append, extend) {
+    set: function set(key, newStyle, force, append, extend) {
         let [, class_, selectors] = key.match(/^([a-zA-Z_-]+)(.*)/);
 
         let highlight = this.highlight[key] || this._create(false, [key]);
@@ -181,7 +183,7 @@ var Highlights = Module("Highlight", {
      * Clears all highlighting rules. Rules with default values are
      * reset.
      */
-    clear: function () {
+    clear: function clear() {
         for (let [k, v] in Iterator(this.highlight))
             this.set(k, null, true);
     },
@@ -193,7 +195,7 @@ var Highlights = Module("Highlight", {
      * @param {Node} node
      * @param {string} group
      */
-    highlightNode: function (node, group, applyBindings) {
+    highlightNode: function highlightNode(node, group, applyBindings) {
         node.setAttributeNS(NS.uri, "highlight", group);
 
         let groups = group.split(" ");
@@ -214,7 +216,7 @@ var Highlights = Module("Highlight", {
      *
      * @param {string} class
      */
-    selector: function (class_)
+    selector: function selector(class_)
         let (self = this)
            class_.replace(/(^|[>\s])([A-Z][\w-]+)\b/g,
             function (m, n1, hl) n1 +
@@ -275,8 +277,9 @@ var Highlights = Module("Highlight", {
      * @param {string} css The rules to load. See {@link Highlights#css}.
      * @param {boolean} eager When true, load all provided rules immediately.
      */
-    loadCSS: function (css, eager) {
-        String.replace(css, this.groupRegexp, function (m, m1, m2) m1 + " " + m2.replace(/\n\s*/g, " "))
+    loadCSS: function loadCSS(css, eager) {
+        String.replace(css, /\\\n/g, "")
+              .replace(this.groupRegexp, function (m, m1, m2) m1 + " " + m2.replace(/\n\s*/g, " "))
               .split("\n").filter(function (s) /\S/.test(s) && !/^\s*\/\//.test(s))
               .forEach(function (highlight) {
 
@@ -291,7 +294,7 @@ var Highlights = Module("Highlight", {
     }
 }, {
 }, {
-    commands: function (dactyl, modules) {
+    commands: function initCommands(dactyl, modules) {
         const { autocommands, commands, completion, CommandOption, config, io } = modules;
 
         let lastScheme;
@@ -337,7 +340,7 @@ var Highlights = Module("Highlight", {
                 if (!modify && /&$/.test(key))
                     [clear, modify, key] = [true, true, key.replace(/&$/, "")];
 
-                dactyl.assert(!(clear && css), _("error.trailing"));
+                dactyl.assert(!(clear && css), _("error.trailingCharacters"));
 
                 if (!modify)
                     modules.commandline.commandOutput(
@@ -347,9 +350,11 @@ var Highlights = Module("Highlight", {
                             ([h.class,
                               <span style={"text-align: center; line-height: 1em;" + h.value + style}>XXX</span>,
                               template.map(h.extends, template.highlight),
-                              template.highlightRegexp(h.value, /\b[-\w]+(?=:)/g)]
-                                for (h in highlight)
-                                if (!key || h.class.indexOf(key) > -1))));
+                              template.highlightRegexp(h.value, /\b[-\w]+(?=:)|\/\*.*?\*\//g,
+                                                       function (match) <span highlight={match[0] == "/" ? "Comment" : "Key"}>{match}</span>)
+                             ]
+                             for (h in highlight)
+                             if (!key || h.class.indexOf(key) > -1))));
                 else if (!key && clear)
                     highlight.clear();
                 else if (key)
@@ -369,7 +374,10 @@ var Highlights = Module("Highlight", {
                     else if (args.completeArg == 1) {
                         let hl = highlight.get(args[0]);
                         if (hl)
-                            context.completions = [[hl.value, "Current Value"], [hl.defaultValue || "", "Default Value"]];
+                            context.completions = [
+                                [hl.value, _("option.currentValue")],
+                                [hl.defaultValue || "", _("option.defaultValue")]
+                            ];
                         context.fork("css", 0, completion, "css");
                     }
                 },
@@ -385,8 +393,8 @@ var Highlights = Module("Highlight", {
                             let group = args[0] && highlight.get(args[0]);
                             if (group)
                                 context.fork("extra", 0, this, function (context) [
-                                     [String(group.extends), "Current Value"],
-                                     [String(group.defaultExtends) || "", "Default Value"]
+                                     [String(group.extends), _("option.currentValue")],
+                                     [String(group.defaultExtends) || "", _("option.defaultValue")]
                                 ]);
                             context.fork("groups", 0, completion, "highlightGroup");
                         }
@@ -403,17 +411,22 @@ var Highlights = Module("Highlight", {
                 ]
             });
     },
-    completion: function (dactyl, modules) {
+    completion: function initCompletion(dactyl, modules) {
         const { completion, config, io } = modules;
+
         completion.colorScheme = function colorScheme(context) {
             let extRe = RegExp("\\." + config.fileExtension + "$");
 
             context.title = ["Color Scheme", "Runtime Path"];
             context.keys = { text: function (f) f.leafName.replace(extRe, ""), description: ".parent.path" };
-            context.completions = array.flatten(
-                io.getRuntimeDirectories("colors").map(
-                    function (dir) dir.readDirectory().filter(
-                        function (file) extRe.test(file.leafName))));
+            context.completions =
+                array.flatten(
+                        io.getRuntimeDirectories("colors").map(
+                            function (dir) dir.readDirectory().filter(
+                                function (file) extRe.test(file.leafName))))
+                     .concat([
+                        { leafName: "default", parent: { path: /*L*/"Revert to builtin colorscheme" } }
+                     ]);
 
         };
 
@@ -422,7 +435,7 @@ var Highlights = Module("Highlight", {
             context.completions = [[v.class, v.value] for (v in highlight)];
         };
     },
-    javascript: function (dactyl, modules, window) {
+    javascript: function initJavascript(dactyl, modules, window) {
         modules.JavaScript.setCompleter(["get", "set"].map(function (m) highlight[m]),
             [ function (context, obj, args) Iterator(highlight.highlight) ]);
         modules.JavaScript.setCompleter(["highlightNode"].map(function (m) highlight[m]),
index a6487a99ea99d18ecd88928349a378180cafc0db..63ac6061921a35d2d88c12a13d1f65c7712c458b 100644 (file)
@@ -105,7 +105,7 @@ var IO = Module("io", {
          * found it is sourced. Only the first file found (per specified path) is
          * sourced unless *all* is specified, then all found files are sourced.
          *
-         * @param {string[]} paths An array of relative paths to source.
+         * @param {[string]} paths An array of relative paths to source.
          * @param {boolean} all Whether all found files should be sourced.
          */
         sourceFromRuntimePath: function sourceFromRuntimePath(paths, all) {
@@ -116,7 +116,7 @@ var IO = Module("io", {
 
         outer:
             for (let dir in values(dirs)) {
-                for (let [,path] in Iterator(paths)) {
+                for (let [, path] in Iterator(paths)) {
                     let file = dir.child(path);
 
                     dactyl.echomsg(_("io.searchingFor", file.path.quote()), 3);
@@ -202,13 +202,13 @@ var IO = Module("io", {
                         this._scriptNames.push(file.path);
 
                     dactyl.echomsg(_("io.sourcingEnd", filename.quote()), 2);
+                    dactyl.log(_("dactyl.sourced", filename), 3);
 
-                    dactyl.log("Sourced: " + filename, 3);
                     return context;
                 }
                 catch (e) {
                     dactyl.reportError(e);
-                    let message = "Sourcing file: " + (e.echoerr || file.path + ": " + e);
+                    let message = _("io.sourcingError", e.echoerr || (file ? file.path : filename) + ": " + e);
                     if (!params.silent)
                         dactyl.echoerr(message);
                 }
@@ -216,7 +216,7 @@ var IO = Module("io", {
                     defineModule.loadLog.push("done sourcing " + filename + ": " + (Date.now() - time) + "ms");
                 }
             }, this);
-        },
+        }
     }),
 
     // TODO: there seems to be no way, short of a new component, to change
@@ -354,7 +354,7 @@ var IO = Module("io", {
      */
     listJar: function listJar(file, path) {
         file = util.getFile(file);
-        if (file) {
+        if (file && file.exists() && file.isFile() && file.isReadable()) {
             // let jar = services.zipReader.getZip(file); Crashes.
             let jar = services.ZipReader(file);
             try {
@@ -366,7 +366,8 @@ var IO = Module("io", {
                         yield entry;
             }
             finally {
-                jar.close();
+                if (jar)
+                    jar.close();
             }
         }
     },
@@ -422,7 +423,7 @@ var IO = Module("io", {
      * Runs an external program.
      *
      * @param {File|string} program The program to run.
-     * @param {string[]} args An array of arguments to pass to *program*.
+     * @param {[string]} args An array of arguments to pass to *program*.
      */
     run: function (program, args, blocking) {
         args = args || [];
@@ -569,7 +570,6 @@ var IO = Module("io", {
 
                 // go directly to an absolute path or look for a relative path
                 // match in 'cdpath'
-                // TODO: handle ../ and ./ paths
                 if (File.isAbsolutePath(arg)) {
                     io.cwd = arg;
                     dactyl.echomsg(io.cwd.path);
@@ -610,7 +610,7 @@ var IO = Module("io", {
                 dactyl.assert(!file.exists() || args.bang, _("io.exists", file.path.quote()));
 
                 // TODO: Use a set/specifiable list here:
-                let lines = [cmd.serialize().map(commands.commandToString, cmd) for (cmd in commands.iterator(true)) if (cmd.serialize)];
+                let lines = [cmd.serialize().map(commands.commandToString, cmd) for (cmd in commands.iterator()) if (cmd.serialize)];
                 lines = array.flatten(lines);
 
                 lines.unshift('"' + config.version + "\n");
@@ -620,8 +620,8 @@ var IO = Module("io", {
                     file.write(lines.join("\n"));
                 }
                 catch (e) {
-                    dactyl.echoerr(_("io.notWriteable"), file.path.quote());
-                    dactyl.log("Could not write to " + file.path + ": " + e.message); // XXX
+                    dactyl.echoerr(_("io.notWriteable", file.path.quote()));
+                    dactyl.log(_("error.notWriteable", file.path, e.message)); // XXX
                 }
             }, {
                 argCount: "*", // FIXME: should be "?" but kludged for proper error message
@@ -639,7 +639,7 @@ var IO = Module("io", {
 
                 if (file.exists() && file.isDirectory() || args[0] && /\/$/.test(args[0]))
                     file.append(config.name + ".vim");
-                dactyl.assert(!file.exists() || args.bang, "File exists");
+                dactyl.assert(!file.exists() || args.bang, _("io.exists"));
 
                 let template = util.compileMacro(<![CDATA[
 " Vim syntax file
@@ -699,22 +699,20 @@ execute 'syn region <name>Css matchgroup=<name>CssDelimiter'
 
 syn match <name>Notation "<[0-9A-Za-z-]\+>"
 
-syn match   <name>Comment +".*$+ contains=<name>Todo,@Spell
 syn keyword <name>Todo FIXME NOTE TODO XXX contained
 
 syn region <name>String start="\z(["']\)" end="\z1" skip="\\\\\|\\\z1" oneline
 
-syn match <name>LineComment +^\s*".*$+ contains=<name>Todo,@Spell
+syn match <name>Comment +^\s*".*$+ contains=<name>Todo,@Spell
 
 " NOTE: match vim.vim highlighting group names
 hi def link <name>AutoCmd               <name>Command
 hi def link <name>AutoEvent             Type
 hi def link <name>Command               Statement
-hi def link <name>Comment               Comment
 hi def link <name>JavaScriptDelimiter   Delimiter
 hi def link <name>CssDelimiter          Delimiter
 hi def link <name>Notation              Special
-hi def link <name>LineComment           Comment
+hi def link <name>Comment               Comment
 hi def link <name>Option                PreProc
 hi def link <name>SetMod                <name>Option
 hi def link <name>String                String
@@ -782,14 +780,18 @@ unlet s:cpo_save
         commands.add(["scrip[tnames]"],
             "List all sourced script names",
             function () {
-                modules.commandline.commandOutput(
-                    template.tabular(["<SNR>", "Filename"], ["text-align: right; padding-right: 1em;"],
-                        ([i + 1, file] for ([i, file] in Iterator(io._scriptNames)))));  // TODO: add colon and remove column titles for pedantic Vim compatibility?
+                if (!io._scriptNames.length)
+                    dactyl.echomsg(_("command.scriptnames.none"));
+                else
+                    modules.commandline.commandOutput(
+                        template.tabular(["<SNR>", "Filename"], ["text-align: right; padding-right: 1em;"],
+                            ([i + 1, file] for ([i, file] in Iterator(io._scriptNames)))));
+
             },
             { argCount: "0" });
 
         commands.add(["so[urce]"],
-            "Read Ex commands from a file",
+            "Read Ex commands, JavaScript or CSS from a file",
             function (args) {
                 if (args.length > 1)
                     dactyl.echoerr(_("io.oneFileAllowed"));
@@ -819,7 +821,7 @@ unlet s:cpo_save
                 if (modules.options["banghist"]) {
                     // replaceable bang and no previous command?
                     dactyl.assert(!/((^|[^\\])(\\\\)*)!/.test(arg) || io._lastRunCommand,
-                        "E34: No previous command");
+                        _("command.run.noPrevious"));
 
                     arg = arg.replace(/(\\)*!/g,
                         function (m) /^\\(\\\\)*!$/.test(m) ? m.replace("\\!", "!") : m.replace("!", io._lastRunCommand)
@@ -830,14 +832,14 @@ unlet s:cpo_save
 
                 let result = io.system(arg);
                 if (result.returnValue != 0)
-                    result.output += "\nshell returned " + result.returnValue;
+                    result.output += "\n" + _("io.shellReturn", result.returnValue);
 
-                modules.commandline.command = "!" + arg;
+                modules.commandline.command = args.commandName.replace("run", "$& ") + arg;
                 modules.commandline.commandOutput(<span highlight="CmdOutput">{result.output}</span>);
 
                 modules.autocommands.trigger("ShellCmdPost", {});
             }, {
-                argCount: "?", // TODO: "1" - probably not worth supporting weird Vim edge cases. The dream is dead. --djk
+                argCount: "?",
                 bang: true,
                 // This is abominably slow.
                 // completer: function (context) completion.shellCommand(context),
@@ -878,6 +880,9 @@ unlet s:cpo_save
         };
 
         completion.file = function file(context, full, dir) {
+            if (/^jar:[^!]*$/.test(context.filter))
+                context.advance(4);
+
             // dir == "" is expanded inside readDirectory to the current dir
             function getDir(str) str.match(/^(?:.*[\/\\])?/)[0];
             dir = getDir(dir || context.filter);
@@ -900,10 +905,8 @@ unlet s:cpo_save
             };
             context.compare = function (a, b) b.isdir - a.isdir || String.localeCompare(a.text, b.text);
 
-            if (modules.options["wildignore"]) {
-                let wig = modules.options.get("wildignore");
-                context.filters.push(function (item) item.isdir || !wig.getKey(this.name));
-            }
+            if (modules.options["wildignore"])
+                context.filters.push(function (item) !modules.options.get("wildignore").getKey(item.path));
 
             // context.background = true;
             context.key = dir;
@@ -977,14 +980,17 @@ unlet s:cpo_save
                     context.key = match.prefix;
                     context.advance(match.prefix.length + 1);
                     context.generate = function () iter({
-                        content: "Chrome content",
-                        locale: "Locale-specific content",
-                        skin: "Theme-specific content"
+                        content: /*L*/"Chrome content",
+                        locale: /*L*/"Locale-specific content",
+                        skin: /*L*/"Theme-specific content"
                     });
                 }
             }
             if (!match || match.scheme === "resource" && match.path)
-                if (/^(\.{0,2}|~)\/|^file:/.test(context.filter) || util.getFile(context.filter) || io.isJarURL(context.filter))
+                if (/^(\.{0,2}|~)\/|^file:/.test(context.filter)
+                    || util.OS.isWindows && /^[a-z]:/i.test(context.filter)
+                    || util.getFile(context.filter)
+                    || io.isJarURL(context.filter))
                     completion.file(context, full);
         });
     },
@@ -1067,7 +1073,7 @@ unlet s:cpo_save
         options["shellcmdflag"];
 
         options.add(["wildignore", "wig"],
-            "List of file patterns to ignore when completing file names",
+            "List of path name patterns to ignore when completing files and directories",
             "regexplist", "");
     }
 });
index 0f78615cc5b204b924716042538f1bae4c41101b..e85b8ca41f3002ae3151226b519139c254ab5a64 100644 (file)
@@ -11,7 +11,7 @@ try {
 Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("javascript", {
     exports: ["JavaScript", "javascript"],
-    use: ["services", "template", "util"]
+    use: ["messages", "services", "template", "util"]
 }, this);
 
 let isPrototypeOf = Object.prototype.isPrototypeOf;
@@ -41,11 +41,11 @@ var JavaScript = Module("javascript", {
             this.window = window;
 
             init.supercall(this);
-        },
+        }
     }),
 
     globals: Class.memoize(function () [
-       [this.modules.userContext, "Global Variables"],
+       [this.modules.userContext, /*L*/"Global Variables"],
        [this.modules, "modules"],
        [this.window, "window"]
     ]),
@@ -54,7 +54,7 @@ var JavaScript = Module("javascript", {
 
     lazyInit: true,
 
-    newContext: function () this.modules.newContext(this.modules.userContext),
+    newContext: function () this.modules.newContext(this.modules.userContext, true),
 
     get completers() JavaScript.completers, // For backward compatibility
 
@@ -71,22 +71,22 @@ var JavaScript = Module("javascript", {
         if (obj == null)
             return;
 
-        let seen = isinstance(obj, ["Sandbox"]) ? set(JavaScript.magicalNames) : {};
+        let seen = isinstance(obj, ["Sandbox"]) ? Set(JavaScript.magicalNames) : {};
         let globals = values(toplevel && this.window === obj ? this.globalNames : []);
 
         if (toplevel && isObject(obj) && "wrappedJSObject" in obj)
-            if (!set.add(seen, "wrappedJSObject"))
+            if (!Set.add(seen, "wrappedJSObject"))
                 yield "wrappedJSObject";
 
         for (let key in iter(globals, properties(obj, !toplevel, true)))
-            if (!set.add(seen, key))
+            if (!Set.add(seen, key))
                 yield key;
 
         // Properties aren't visible in an XPCNativeWrapper until
         // they're accessed.
         for (let key in properties(this.getKey(obj, "wrappedJSObject"), !toplevel, true))
             try {
-                if (key in obj && !set.has(seen, key))
+                if (key in obj && !Set.has(seen, key))
                     yield key;
             }
             catch (e) {}
@@ -117,18 +117,14 @@ var JavaScript = Module("javascript", {
             return cache[key];
 
         context[JavaScript.EVAL_TMP] = tmp;
-        context[JavaScript.EVAL_EXPORT] = function export_(obj) cache[key] = obj;
         try {
-            if (tmp != null) // Temporary hack until bug 609949 is fixed.
-                this.modules.dactyl.userEval(JavaScript.EVAL_EXPORT + "(" + arg + ")", context, "[Command Line Completion]", 1);
-            else
-                cache[key] = this.modules.dactyl.userEval(arg, context, "[Command Line Completion]", 1);
+            cache[key] = this.modules.dactyl.userEval(arg, context, /*L*/"[Command Line Completion]", 1);
 
             return cache[key];
         }
         catch (e) {
             util.reportError(e);
-            this.context.message = "Error: " + e;
+            this.context.message = _("error.error", e);
             return null;
         }
         finally {
@@ -170,7 +166,7 @@ var JavaScript = Module("javascript", {
 
         if (this._top.char != arg) {
             this.context.highlight(this._top.offset, this._i - this._top.offset, "SPELLCHECK");
-            throw Error("Invalid JS");
+            throw Error(/*L*/"Invalid JS");
         }
 
         // The closing character of this stack frame will have pushed a new
@@ -308,7 +304,7 @@ var JavaScript = Module("javascript", {
             if (this._checkFunction(prev, dot, cacheKey))
                 return [];
             if (prev != statement && obj == null) {
-                this.context.message = "Error: " + cacheKey.quote() + " is " + String(obj);
+                this.context.message = /*L*/"Error: " + cacheKey.quote() + " is " + String(obj);
                 return [];
             }
 
@@ -324,7 +320,7 @@ var JavaScript = Module("javascript", {
         let end = (frame == -1 ? this._lastIdx : this._get(frame + 1).offset);
 
         this._cacheKey = null;
-        let obj = [[this.cache.evalContext, "Local Variables"]].concat(this.globals);
+        let obj = [[this.cache.evalContext, /*L*/"Local Variables"]].concat(this.globals);
         // Is this an object dereference?
         if (dot < statement) // No.
             dot = statement - 1;
@@ -339,7 +335,7 @@ var JavaScript = Module("javascript", {
         const self = this;
 
         if (!getOwnPropertyNames && !services.debugger.isOn && !this.context.message)
-            this.context.message = "For better completion data, please enable the JavaScript debugger (:set jsdebugger)";
+            this.context.message = /*L*/"For better completion data, please enable the JavaScript debugger (:set jsdebugger)";
 
         let base = this.context.fork("js", this._top.offset);
         base.forceAnchored = true;
@@ -419,14 +415,14 @@ var JavaScript = Module("javascript", {
         objects.forEach(function (obj) {
             obj.ctxt_p.split(obj[1] + "/anchored", this, function (context) {
                 context.anchored = true;
-                context.title[0] += " (prototypes)";
+                context.title[0] += /*L*/" (prototypes)";
             });
         });
 
         objects.forEach(function (obj) {
             obj.ctxt_t.split(obj[1] + "/unanchored", this, function (context) {
                 context.anchored = false;
-                context.title[0] += " (substrings)";
+                context.title[0] += /*L*/" (substrings)";
                 context.filters.push(unanchored);
             });
         });
@@ -434,7 +430,7 @@ var JavaScript = Module("javascript", {
         objects.forEach(function (obj) {
             obj.ctxt_p.split(obj[1] + "/unanchored", this, function (context) {
                 context.anchored = false;
-                context.title[0] += " (prototype substrings)";
+                context.title[0] += /*L*/" (prototype substrings)";
                 context.filters.push(unanchored);
             });
         });
@@ -598,7 +594,7 @@ var JavaScript = Module("javascript", {
         // Wait for a keypress before completing when there's no key
         if (!this.context.tabPressed && key == "" && obj.length > 1) {
             this.context.waitingForTab = true;
-            this.context.message = "Waiting for key press";
+            this.context.message = _("completion.waitingForKeyPress");
             return null;
         }
 
@@ -633,12 +629,11 @@ var JavaScript = Module("javascript", {
         "Math", "NaN", "Namespace", "Number", "Object", "Proxy", "QName",
         "ROCSSPrimitiveValue", "RangeError", "ReferenceError", "RegExp",
         "StopIteration", "String", "SyntaxError", "TypeError", "URIError",
-        "Uint16Array", "Uint32Array", "Uint8Array", "XML",
-        "XMLHttpProgressEvent", "XMLList", "XMLSerializer", "XPCNativeWrapper",
-        "XPCSafeJSWrapper", "XULControllers", "constructor", "decodeURI",
-        "decodeURIComponent", "encodeURI", "encodeURIComponent", "escape",
-        "eval", "isFinite", "isNaN", "isXMLName", "parseFloat", "parseInt",
-        "undefined", "unescape", "uneval"
+        "Uint16Array", "Uint32Array", "Uint8Array", "XML", "XMLHttpProgressEvent",
+        "XMLList", "XMLSerializer", "XPCNativeWrapper", "XPCSafeJSWrapper",
+        "XULControllers", "constructor", "decodeURI", "decodeURIComponent",
+        "encodeURI", "encodeURIComponent", "escape", "eval", "isFinite", "isNaN",
+        "isXMLName", "parseFloat", "parseInt", "undefined", "unescape", "uneval"
     ].concat([k.substr(6) for (k in keys(Ci)) if (/^nsIDOM/.test(k))])
      .concat([k.substr(3) for (k in keys(Ci)) if (/^nsI/.test(k))])
      .concat(this.magicalNames)
@@ -646,7 +641,6 @@ var JavaScript = Module("javascript", {
 
 }, {
     EVAL_TMP: "__dactyl_eval_tmp",
-    EVAL_EXPORT: "__dactyl_eval_export",
 
     /**
      * A map of argument completion functions for named methods. The
@@ -670,9 +664,9 @@ var JavaScript = Module("javascript", {
      * time they are accessed, so they should be accessed
      * judiciously.
      *
-     * @param {function|function[]} funcs The functions for which to
+     * @param {function|[function]} funcs The functions for which to
      *      install the completers.
-     * @param {function[]} completers An array of completer
+     * @param {[function]} completers An array of completer
      *      functions.
      */
     setCompleter: function (funcs, completers) {
@@ -705,7 +699,8 @@ var JavaScript = Module("javascript", {
 
         modes.addMode("REPL", {
             description: "JavaScript Read Eval Print Loop",
-            bases: [modes.COMMAND_LINE]
+            bases: [modes.COMMAND_LINE],
+            displayName: Class.memoize(function () this.name)
         });
     },
     commandline: function initCommandLine(dactyl, modules, window) {
@@ -775,8 +770,8 @@ var JavaScript = Module("javascript", {
                 this.js.newContext = function newContext() modules.newContext(self.context, !sandbox);
 
                 this.js.globals = [
-                   [this.context, "REPL Variables"],
-                   [context, "REPL Global"]
+                   [this.context, /*L*/"REPL Variables"],
+                   [context, /*L*/"REPL Global"]
                 ].concat(this.js.globals.filter(function ([global]) isPrototypeOf.call(global, context)));
 
                 if (!isPrototypeOf.call(modules.jsmodules, context))
@@ -790,13 +785,14 @@ var JavaScript = Module("javascript", {
 
                 this.repl = REPL(this.context);
             },
+
             open: function open(context) {
-                this.updatePrompt();
 
                 modules.mow.echo(this.repl);
                 this.widgets.message = null;
 
                 open.superapply(this, arguments);
+                this.updatePrompt();
             },
 
             complete: function complete(context) {
@@ -807,8 +803,10 @@ var JavaScript = Module("javascript", {
 
             mode: modes.REPL,
 
+            get completionList() this.widgets.statusbar.commandline.id,
+
             accept: function accept() {
-                dactyl.trapErrors(function () { this.repl.addOutput(this.command) }, this);
+                dactyl.trapErrors(function () { this.repl.addOutput(this.command); }, this);
 
                 this.completions.cleanup();
                 this.history.save();
@@ -836,8 +834,6 @@ var JavaScript = Module("javascript", {
         commands.add(["javas[cript]", "js"],
             "Evaluate a JavaScript string",
             function (args) {
-                modules.commandline;
-
                 if (args[0] && !args.bang)
                     dactyl.userEval(args[0]);
                 else {
@@ -846,6 +842,7 @@ var JavaScript = Module("javascript", {
                            .open();
                 }
             }, {
+                argCount: "?",
                 bang: true,
                 completer: function (context) modules.completion.javascript(context),
                 hereDoc: true,
index 4b6e47888eb7de3aaebee4423ccfda73dc05a473..f1f4de1324a3d02103e7313294775671135ef629 100644 (file)
@@ -45,7 +45,7 @@ var Messages = Module("messages", {
 
         let seen = {};
         for (let { key } in this.iterate()) {
-            if (!set.add(seen, key))
+            if (!Set.add(seen, key))
                 this._[key] = this[key] = {
                     __noSuchMethod__: function __(prop, args) self._.apply(self, [prop].concat(args))
                 };
@@ -67,7 +67,7 @@ var Messages = Module("messages", {
             catch (e) {}
 
         // Report error so tests fail, but don't throw
-        if (arguments.length < 2)
+        if (arguments.length < 2) // Do *not* localize these strings
             util.reportError(Error("Invalid locale string: " + value));
         return arguments.length > 1 ? default_ : value;
     },
@@ -80,7 +80,7 @@ var Messages = Module("messages", {
             catch (e) {}
 
         // Report error so tests fail, but don't throw
-        if (arguments.length < 3)
+        if (arguments.length < 3) // Do *not* localize these strings
             util.reportError(Error("Invalid locale string: " + value));
         return arguments.length > 2 ? default_ : value;
     }
@@ -88,13 +88,17 @@ var Messages = Module("messages", {
 }, {
     Localized: Class("Localized", Class.Property, {
         init: function init(prop, obj) {
-            let _prop = "localized_" + prop;
+            let _prop = "unlocalized_" + prop;
             if (this.initialized) {
                 /*
                 if (config.locale === "en-US")
-                    return { configurable: true, enumerable: true, value: null, writable: true };
+                    return { configurable: true, enumerable: true, value: this.default, writable: true };
                 */
 
+                if (!Set.has(obj, "localizedProperties"))
+                    obj.localizedProperties = { __proto__: obj.localizedProperties };
+                obj.localizedProperties[prop] = true;
+
                 obj[_prop] = this.default;
                 return {
                     get: function get() {
@@ -103,26 +107,29 @@ var Messages = Module("messages", {
 
                         function getter(key, default_) function getter() messages.get([name, key].join("."), default_);
 
-                        let name = [this.constructor.className.toLowerCase(), this.identifier || this.name, prop].join(".");
-                        if (!isObject(value))
-                            value = messages.get(name, value)
-                        else if (isArray(value))
-                            // Deprecated
-                            iter(value).forEach(function ([k, v]) {
-                                if (isArray(v))
-                                    memoize(v, 1, getter(v[0], v[1]));
-                                else
-                                    memoize(value, k, getter(k, v));
-                            });
-                        else
-                            iter(value).forEach(function ([k, v]) {
-                                memoize(value, k, function () messages.get([name, k].join("."), v));
-                            });
+                        if (value != null) {
+                            var name = [this.constructor.className.toLowerCase(), this.identifier || this.name, prop].join(".");
+                            if (!isObject(value))
+                                value = messages.get(name, value);
+                            else if (isArray(value))
+                                // Deprecated
+                                iter(value).forEach(function ([k, v]) {
+                                    if (isArray(v))
+                                        memoize(v, 1, getter(v[0], v[1]));
+                                    else
+                                        memoize(value, k, getter(k, v));
+                                });
+                            else
+                                iter(value).forEach(function ([k, v]) {
+                                    memoize(value, k, function () messages.get([name, k].join("."), v));
+                                });
+                        }
 
                         return Class.replaceProperty(this, prop, value);
                     },
+
                     set: function set(val) this[_prop] = val
-                }
+                };
             }
             this.default = prop;
             this.initialized = true;
index 1c61a40af6bd7bbd239bd79f7f60c3ae86cd11a0..a09fef257b3dc149ab1b8a53c1b20b054e2e0984 100644 (file)
@@ -12,7 +12,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("options", {
     exports: ["Option", "Options", "ValueError", "options"],
     require: ["messages", "storage"],
-    use: ["commands", "completion", "prefs", "services", "styles", "template", "util"]
+    use: ["commands", "completion", "config", "prefs", "services", "styles", "template", "util"]
 }, this);
 
 /** @scope modules */
@@ -25,7 +25,7 @@ let ValueError = Class("ValueError", ErrorBase);
  * A class representing configuration options. Instances are created by the
  * {@link Options} class.
  *
- * @param {string[]} names The names by which this option is identified.
+ * @param {[string]} names The names by which this option is identified.
  * @param {string} description A short one line description of the option.
  * @param {string} type The option's value data type (see {@link Option#type}).
  * @param {string} defaultValue The default value for this option.
@@ -44,44 +44,21 @@ let ValueError = Class("ValueError", ErrorBase);
  * @private
  */
 var Option = Class("Option", {
-    init: function init(names, description, type, defaultValue, extraInfo) {
+    init: function init(modules, names, description, defaultValue, extraInfo) {
+        this.modules = modules;
         this.name = names[0];
         this.names = names;
         this.realNames = names;
-        this.type = type;
         this.description = description;
 
-        if (this.type in Option.getKey)
-            this.getKey = Option.getKey[this.type];
-
-        if (this.type in Option.parse)
-            this.parse = Option.parse[this.type];
-
-        if (this.type in Option.stringify)
-            this.stringify = Option.stringify[this.type];
-
-        if (this.type in Option.domains)
-            this.domains = Option.domains[this.type];
-
-        if (this.type in Option.testValues)
-            this.testValues = Option.testValues[this.type];
-
-        this._op = Option.ops[this.type];
-
-        // Need to trigger setter
-        if (extraInfo && "values" in extraInfo && !extraInfo.__lookupGetter__("values")) {
-            this.values = extraInfo.values;
-            delete extraInfo.values;
-        }
-
         if (extraInfo)
-            update(this, extraInfo);
+            this.update(extraInfo);
 
-        if (set.has(this.modules.config.defaults, this.name))
+        if (Set.has(this.modules.config.defaults, this.name))
             defaultValue = this.modules.config.defaults[this.name];
 
         if (defaultValue !== undefined) {
-            if (this.type == "string")
+            if (this.type === "string")
                 defaultValue = Commands.quote(defaultValue);
 
             if (isObject(defaultValue))
@@ -101,6 +78,8 @@ var Option = Class("Option", {
             this.globalValue = this.defaultValue;
     },
 
+    magicalProperties: Set(["cleanupValue"]),
+
     /**
      * @property {string} This option's description, as shown in :listoptions.
      */
@@ -114,6 +93,13 @@ var Option = Class("Option", {
 
     get isDefault() this.stringValue === this.stringDefaultValue,
 
+    /** @property {value} The value to reset this option to at cleanup time. */
+    get cleanupValue() options.cleanupPrefs.get(this.name),
+    set cleanupValue(value) {
+        if (options.cleanupPrefs.get(this.name) == null)
+            options.cleanupPrefs.set(this.name, value);
+    },
+
     /** @property {value} The option's global value. @see #scope */
     get globalValue() { try { return options.store.get(this.name, {}).value; } catch (e) { util.reportError(e); throw e; } },
     set globalValue(val) { options.store.set(this.name, { value: val, time: Date.now() }); },
@@ -123,14 +109,14 @@ var Option = Class("Option", {
      * "charlist" or "stringlist" or else unchanged.
      *
      * @param {value} value The option value.
-     * @returns {value|string[]}
+     * @returns {value|[string]}
      */
     parse: function parse(value) Option.dequote(value),
 
     /**
      * Returns *values* packed in the appropriate format for the option type.
      *
-     * @param {value|string[]} values The option value.
+     * @param {value|[string]} values The option value.
      * @returns {value}
      */
     stringify: function stringify(vals) Commands.quote(vals),
@@ -141,7 +127,7 @@ var Option = Class("Option", {
      *
      * @param {number} scope The scope to return these values from (see
      *     {@link Option#scope}).
-     * @returns {value|string[]}
+     * @returns {value|[string]}
      */
     get: function get(scope) {
         if (scope) {
@@ -213,6 +199,7 @@ var Option = Class("Option", {
     set stringValue(value) this.value = this.parse(value),
 
     get stringDefaultValue() this.stringify(this.defaultValue),
+    set stringDefaultValue(val) this.defaultValue = this.parse(val),
 
     getKey: function getKey(key) undefined,
 
@@ -252,7 +239,7 @@ var Option = Class("Option", {
      * Sets the option's value using the specified set *operator*.
      *
      * @param {string} operator The set operator.
-     * @param {value|string[]} values The value (or values) to apply.
+     * @param {value|[string]} values The value (or values) to apply.
      * @param {number} scope The scope to apply this value to (see
      *     {@link #scope}).
      * @param {boolean} invert Whether this is an invert boolean operation.
@@ -262,7 +249,7 @@ var Option = Class("Option", {
         try {
             var newValues = this._op(operator, values, scope, invert);
             if (newValues == null)
-                return "Operator " + operator + " not supported for option type " + this.type;
+                return _("option.operatorNotSupported", operator, this.type);
 
             if (!this.isValidValue(newValues))
                 return this.invalidArgument(str || this.stringify(values), operator);
@@ -281,7 +268,7 @@ var Option = Class("Option", {
 
     /** @property {string} The option's canonical name. */
     name: null,
-    /** @property {string[]} All names by which this option is identified. */
+    /** @property {[string]} All names by which this option is identified. */
     names: null,
 
     /**
@@ -305,13 +292,14 @@ var Option = Class("Option", {
      */
     scope: 1, // Option.SCOPE_GLOBAL // XXX set to BOTH by default someday? - kstep
 
-    cleanupValue: null,
-
     /**
      * @property {function(CompletionContext, Args)} This option's completer.
      * @see CompletionContext
      */
-    completer: function completer(context) {
+    completer: function completer(context, extra) {
+        if (/map$/.test(this.type) && extra.value == null)
+            return;
+
         if (this.values)
             context.completions = this.values;
     },
@@ -451,12 +439,14 @@ var Option = Class("Option", {
         let [, bang, filter] = /^(!?)(.*)/.exec(pattern);
         filter = Option.dequote(filter);
 
+        let quote = this.keepQuotes ? util.identity : Option.quote;
+
         return update(Styles.matchFilter(filter), {
             bang: bang,
             filter: filter,
             result: result !== undefined ? result : !bang,
-            toString: function toString() this.bang + Option.quote(this.filter) +
-                (typeof this.result === "boolean" ? "" : ":" + Option.quote(this.result)),
+            toString: function toString() this.bang + Option.quote(this.filter, /:/) +
+                (typeof this.result === "boolean" ? "" : ":" + quote(this.result)),
         });
     },
 
@@ -466,7 +456,7 @@ var Option = Class("Option", {
 
         regexplist: function regexplist(k, default_) {
             for (let re in values(this.value))
-                if (re(k))
+                if ((re.test || re).call(re, k))
                     return re.result;
             return arguments.length > 1 ? default_ : null;
         },
@@ -495,7 +485,7 @@ var Option = Class("Option", {
 
     parse: {
         number:     function (value) let (val = Option.dequote(value))
-                            Option.validIf(Number(val) % 1 == 0, "Integer value required") && parseInt(val),
+                            Option.validIf(Number(val) % 1 == 0, _("option.intRequired")) && parseInt(val),
 
         boolean:    function boolean(value) Option.dequote(value) == "true" || value == true ? true : false,
 
@@ -512,7 +502,7 @@ var Option = Class("Option", {
                 return [];
             if (!isArray(value))
                 value = Option.splitList(value, true);
-            return value.map(Option.parseSite);
+            return value.map(Option.parseSite, this);
         },
 
         stringmap: function stringmap(value) array.toObject(
@@ -536,7 +526,7 @@ var Option = Class("Option", {
                 if (v.length > count)
                     return prev = parse.call(this, filter, val);
                 else {
-                    util.assert(prev, "Syntax error", false);
+                    util.assert(prev, _("error.syntaxError"), false);
                     prev.result += "," + v;
                 }
             }, this))
@@ -592,7 +582,7 @@ var Option = Class("Option", {
 
             let value = parseInt(values);
             util.assert(Number(values) % 1 == 0,
-                        "E521: Number required after =: " + this.name + "=" + values);
+                        _("command.set.numberRequired", this.name, values));
 
             switch (operator) {
             case "+":
@@ -637,18 +627,23 @@ var Option = Class("Option", {
         stringlist: function stringlist(operator, values, scope, invert) {
             values = Array.concat(values);
 
+            function uniq(ary) {
+                let seen = {};
+                return ary.filter(function (elem) !Set.add(seen, elem));
+            }
+
             switch (operator) {
             case "+":
-                return array.uniq(Array.concat(this.value, values), true);
+                return uniq(Array.concat(this.value, values), true);
             case "^":
                 // NOTE: Vim doesn't prepend if there's a match in the current value
-                return array.uniq(Array.concat(values, this.value), true);
+                return uniq(Array.concat(values, this.value), true);
             case "-":
-                return this.value.filter(function (item) values.indexOf(item) == -1);
+                return this.value.filter(function (item) !Set.has(this, item), Set(values));
             case "=":
                 if (invert) {
-                    let keepValues = this.value.filter(function (item) values.indexOf(item) == -1);
-                    let addValues  = values.filter(function (item) this.value.indexOf(item) == -1, this);
+                    let keepValues = this.value.filter(function (item) !Set.has(this, item), Set(values));
+                    let addValues  = values.filter(function (item) !Set.has(this, item), Set(this.value));
                     return addValues.concat(keepValues);
                 }
                 return values;
@@ -688,7 +683,7 @@ var Option = Class("Option", {
      * Validates the specified *values* against values generated by the
      * option's completer function.
      *
-     * @param {value|string[]} values The value or array of values to validate.
+     * @param {value|[string]} values The value or array of values to validate.
      * @returns {boolean}
      */
     validateCompleter: function validateCompleter(values) {
@@ -702,15 +697,54 @@ var Option = Class("Option", {
         }
 
         if (isArray(acceptable))
-            acceptable = set(acceptable.map(function ([k]) k));
+            acceptable = Set(acceptable.map(function ([k]) k));
 
         if (this.type === "regexpmap" || this.type === "sitemap")
-            return Array.concat(values).every(function (re) set.has(acceptable, re.result));
+            return Array.concat(values).every(function (re) Set.has(acceptable, re.result));
 
-        return Array.concat(values).every(set.has(acceptable));
-    }
+        return Array.concat(values).every(Set.has(acceptable));
+    },
+
+    types: {}
 });
 
+["Boolean",
+ "Charlist",
+ "Number",
+ "RegexpList",
+ "RegexpMap",
+ "SiteList",
+ "SiteMap",
+ "String",
+ "StringList",
+ "StringMap"].forEach(function (name) {
+     let type = name.toLowerCase();
+     let class_ = Class(name + "Option", Option, {
+         type: type,
+
+         _op: Option.ops[type]
+     });
+
+    if (type in Option.getKey)
+        class_.prototype.getKey = Option.getKey[type];
+
+    if (type in Option.parse)
+        class_.prototype.parse = Option.parse[type];
+
+    if (type in Option.stringify)
+        class_.prototype.stringify = Option.stringify[type];
+
+    if (type in Option.domains)
+        class_.prototype.domains = Option.domains[type];
+
+    if (type in Option.testValues)
+        class_.prototype.testValues = Option.testValues[type];
+
+    Option.types[type] = class_;
+    this[class_.className] = class_;
+    EXPORTED_SYMBOLS.push(class_.className);
+}, this);
+
 /**
  * @instance options
  */
@@ -721,7 +755,6 @@ var Options = Module("options", {
             this.needInit = [];
             this._options = [];
             this._optionMap = {};
-            this.Option = Class("Option", Option, { modules: modules });
 
             storage.newMap("options", { store: false });
             storage.addObserver("options", function optionObserver(key, event, option) {
@@ -730,6 +763,18 @@ var Options = Module("options", {
                 if (event == "change" && opt)
                     opt.set(opt.globalValue, Option.SCOPE_GLOBAL, true);
             }, window);
+
+            services["dactyl:"].pages["options.dtd"] = function () [null,
+                util.makeDTD(
+                    iter(([["option", o.name, "default"].join("."),
+                           o.type === "string" ? o.defaultValue.replace(/'/g, "''") :
+                           o.value === true    ? "on"  :
+                           o.value === false   ? "off" : o.stringDefaultValue]
+                          for (o in self)),
+
+                         ([["option", o.name, "type"].join("."), o.type] for (o in self)),
+
+                         config.dtd))];
         },
 
         dactyl: dactyl,
@@ -742,7 +787,7 @@ var Options = Module("options", {
          * @param {number} scope Only list options in this scope (see
          *     {@link Option#scope}).
          */
-        list: function (filter, scope) {
+        list: function list(filter, scope) {
             if (!scope)
                 scope = Option.SCOPE_BOTH;
 
@@ -780,13 +825,13 @@ var Options = Module("options", {
         cleanup: function cleanup() {
             for (let opt in this)
                 if (opt.cleanupValue != null)
-                    opt.value = opt.parse(opt.cleanupValue);
+                    opt.stringValue = opt.cleanupValue;
         },
 
         /**
          * Adds a new option.
          *
-         * @param {string[]} names All names for the option.
+         * @param {[string]} names All names for the option.
          * @param {string} description A description of the option.
          * @param {string} type The option type (see {@link Option#type}).
          * @param {value} defaultValue The option's default value.
@@ -794,7 +839,7 @@ var Options = Module("options", {
          *     {@link Map#extraInfo}).
          * @optional
          */
-        add: function (names, description, type, defaultValue, extraInfo) {
+        add: function add(names, description, type, defaultValue, extraInfo) {
             const self = this;
 
             if (!extraInfo)
@@ -810,7 +855,7 @@ var Options = Module("options", {
 
             let closure = function () self._optionMap[name];
 
-            memoize(this._optionMap, name, function () self.Option(names, description, type, defaultValue, extraInfo));
+            memoize(this._optionMap, name, function () Option.types[type](modules, names, description, defaultValue, extraInfo));
             for (let alias in values(names.slice(1)))
                 memoize(this._optionMap, alias, closure);
 
@@ -836,7 +881,7 @@ var Options = Module("options", {
     allPrefs: deprecated("prefs.getNames", function allPrefs() prefs.getNames.apply(prefs, arguments)),
     getPref: deprecated("prefs.get", function getPref() prefs.get.apply(prefs, arguments)),
     invertPref: deprecated("prefs.invert", function invertPref() prefs.invert.apply(prefs, arguments)),
-    listPrefs: deprecated("prefs.list", function listPrefs() { commandline.commandOutput(prefs.list.apply(prefs, arguments)); }),
+    listPrefs: deprecated("prefs.list", function listPrefs() { this.modules.commandline.commandOutput(prefs.list.apply(prefs, arguments)); }),
     observePref: deprecated("prefs.observe", function observePref() prefs.observe.apply(prefs, arguments)),
     popContext: deprecated("prefs.popContext", function popContext() prefs.popContext.apply(prefs, arguments)),
     pushContext: deprecated("prefs.pushContext", function pushContext() prefs.pushContext.apply(prefs, arguments)),
@@ -846,6 +891,13 @@ var Options = Module("options", {
     setPref: deprecated("prefs.set", function setPref() prefs.set.apply(prefs, arguments)),
     withContext: deprecated("prefs.withContext", function withContext() prefs.withContext.apply(prefs, arguments)),
 
+    cleanupPrefs: Class.memoize(function () localPrefs.Branch("cleanup.option.")),
+
+    cleanup: function cleanup(reason) {
+        if (~["disable", "uninstall"].indexOf(reason))
+            this.cleanupPrefs.resetBranch();
+    },
+
     /**
      * Returns the option with *name* in the specified *scope*.
      *
@@ -887,8 +939,11 @@ var Options = Module("options", {
         }
 
         if (matches) {
-            res.option = this.get(res.name, res.scope);
-            if (!res.option && (res.option = this.get(prefix + res.name, res.scope))) {
+            if (res.option = this.get(res.name, res.scope)) {
+                if (prefix === "no" && res.option.type !== "boolean")
+                    res.option = null;
+            }
+            else if (res.option = this.get(prefix + res.name, res.scope)) {
                 res.name = prefix + res.name;
                 prefix = "";
             }
@@ -953,7 +1008,9 @@ var Options = Module("options", {
             format: {
                 description: function (map) (XML.ignoreWhitespace = false, XML.prettyPrinting = false, <>
                         {options.get("passkeys").has(map.name)
-                            ? <span highlight="URLExtra">(passed by {template.helpLink("'passkeys'")})</span>
+                            ? <span highlight="URLExtra">({
+                                tempate.linkifyHelp(_("option.passkeys.passedBy"))
+                              })</span>
                             : <></>}
                         {template.linkifyHelp(map.description)}
                 </>)
@@ -968,7 +1025,7 @@ var Options = Module("options", {
             format: {
                 description: function (opt) (XML.ignoreWhitespace = false, XML.prettyPrinting = false, <>
                         {opt.scope == Option.SCOPE_LOCAL
-                            ? <span highlight="URLExtra">(buffer local)</span> : ""}
+                            ? <span highlight="URLExtra">({_("option.bufferLocal")})</span> : ""}
                         {template.linkifyHelp(opt.description)}
                 </>),
                 help: function (opt) "'" + opt.name + "'"
@@ -982,12 +1039,12 @@ var Options = Module("options", {
 
             let list = [];
             function flushList() {
-                let names = set(list.map(function (opt) opt.option ? opt.option.name : ""));
+                let names = Set(list.map(function (opt) opt.option ? opt.option.name : ""));
                 if (list.length)
                     if (list.some(function (opt) opt.all))
-                        options.list(function (opt) !(list[0].onlyNonDefault && opt.isDefault) , list[0].scope);
+                        options.list(function (opt) !(list[0].onlyNonDefault && opt.isDefault), list[0].scope);
                     else
-                        options.list(function (opt) set.has(names, opt.name), list[0].scope);
+                        options.list(function (opt) Set.has(names, opt.name), list[0].scope);
                 list = [];
             }
 
@@ -1009,7 +1066,7 @@ var Options = Module("options", {
                     }
 
                     if (name == "all" && reset)
-                        modules.commandline.input("Warning: Resetting all preferences may make " + config.host + " unusable. Continue (yes/[no]): ",
+                        modules.commandline.input(_("pref.prompt.resetAll", config.host) + " ",
                             function (resp) {
                                 if (resp == "yes")
                                     for (let pref in values(prefs.getNames()))
@@ -1017,7 +1074,7 @@ var Options = Module("options", {
                             },
                             { promptHighlight: "WarningMsg" });
                     else if (name == "all")
-                        commandline.commandOutput(prefs.list(onlyNonDefault, ""));
+                        modules.commandline.commandOutput(prefs.list(onlyNonDefault, ""));
                     else if (reset)
                         prefs.reset(name);
                     else if (invertBoolean)
@@ -1044,10 +1101,11 @@ var Options = Module("options", {
                 }
 
                 let opt = modules.options.parseOpt(arg, modifiers);
-                util.assert(opt, "Error parsing :set command: " + arg);
+                util.assert(opt, _("command.set.errorParsing", arg));
+                util.assert(!opt.error, _("command.set.errorParsing", opt.error));
 
                 let option = opt.option;
-                util.assert(option != null || opt.all, "E518: Unknown option: " + opt.name);
+                util.assert(option != null || opt.all, _("command.set.unknownOption", opt.name));
 
                 // reset a variable to its default value
                 if (opt.reset) {
@@ -1099,8 +1157,8 @@ var Options = Module("options", {
 
                     context.pushProcessor(0, function (item, text, next) next(item, text.substr(0, 100)));
                     context.completions = [
-                            [prefs.get(filter), "Current Value"],
-                            [prefs.defaults.get(filter), "Default Value"]
+                            [prefs.get(filter), _("option.currentValue")],
+                            [prefs.defaults.get(filter), _("option.defaultValue")]
                     ].filter(function (k) k[0] != null);
                     return null;
                 }
@@ -1115,7 +1173,7 @@ var Options = Module("options", {
             if (context.filter.indexOf("=") == -1) {
                 if (false && prefix)
                     context.filters.push(function ({ item }) item.type == "boolean" || prefix == "inv" && isArray(item.values));
-                return completion.option(context, opt.scope, prefix);
+                return completion.option(context, opt.scope, opt.name == "inv" ? opt.name : prefix);
             }
 
             function error(length, message) {
@@ -1125,11 +1183,11 @@ var Options = Module("options", {
 
             let option = opt.option;
             if (!option)
-                return error(opt.name.length, "No such option: " + opt.name);
+                return error(opt.name.length, _("option.noSuch", opt.name));
 
             context.advance(context.filter.indexOf("="));
             if (option.type == "boolean")
-                return error(context.filter.length, "Trailing characters");
+                return error(context.filter.length, _("error.trailingCharacters"));
 
             context.advance(1);
             if (opt.error)
@@ -1143,8 +1201,8 @@ var Options = Module("options", {
                     context.title = ["Extra Completions"];
                     context.pushProcessor(0, function (item, text, next) next(item, text.substr(0, 100)));
                     context.completions = [
-                            [option.stringValue, "Current value"],
-                            [option.stringDefaultValue, "Default value"]
+                            [option.stringValue, _("option.currentValue")],
+                            [option.stringDefaultValue, _("option.defaultValue")]
                     ].filter(function (f) f[0] !== "");
                     context.quote = ["", util.identity, ""];
                 });
@@ -1155,12 +1213,12 @@ var Options = Module("options", {
 
             // Fill in the current values if we're removing
             if (opt.operator == "-" && isArray(opt.values)) {
-                let have = set([i.text for (i in values(context.allItems.items))]);
+                let have = Set([i.text for (i in values(context.allItems.items))]);
                 context = context.fork("current-values", 0);
                 context.anchored = optcontext.anchored;
                 context.maxItems = optcontext.maxItems;
 
-                context.filters.push(function (i) !set.has(have, i.text));
+                context.filters.push(function (i) !Set.has(have, i.text));
                 modules.completion.optionValue(context, opt.name, opt.operator, null,
                                        function (context) {
                                            context.generate = function () option.value.map(function (o) [o, ""]);
@@ -1195,7 +1253,7 @@ var Options = Module("options", {
                     if (str.text().length() == str.*.length())
                         dactyl.echomsg(_("variable.none"));
                     else
-                        dactyl.echo(str, commandline.FORCE_MULTILINE);
+                        dactyl.echo(str, modules.commandline.FORCE_MULTILINE);
                     return;
                 }
 
@@ -1206,7 +1264,7 @@ var Options = Module("options", {
 
                     util.assert(scope == "g:" || scope == null,
                                 _("command.let.illegalVar", scope + name));
-                    util.assert(set.has(globalVariables, name) || (expr && !op),
+                    util.assert(Set.has(globalVariables, name) || (expr && !op),
                                 _("command.let.undefinedVar", fullName));
 
                     if (!expr)
@@ -1304,7 +1362,7 @@ var Options = Module("options", {
             function (args) {
                 for (let [, name] in args) {
                     name = name.replace(/^g:/, ""); // throw away the scope prefix
-                    if (!set.has(dactyl._globalVariables, name)) {
+                    if (!Set.has(dactyl._globalVariables, name)) {
                         if (!args.bang)
                             dactyl.echoerr(_("command.let.noSuch", name));
                         return;
@@ -1346,7 +1404,7 @@ var Options = Module("options", {
                 var newValues = opt.parse(context.filter);
             }
             catch (e) {
-                context.message = "Error: " + e;
+                context.message = _("error.error", e);
                 context.completions = [];
                 return;
             }
@@ -1394,6 +1452,14 @@ var Options = Module("options", {
                     context.filters.push(function (i) curValues.indexOf(i.text) == -1);
                 if (op == "-")
                     context.filters.push(function (i) curValues.indexOf(i.text) > -1);
+
+                memoize(extra, "values", function () {
+                    if (op == "+")
+                        return curValues.concat(newValues);
+                    if (op == "-")
+                        return curValues.filter(function (v) newValues.indexOf(val) == -1);
+                    return newValues;
+                });
             }
 
             let res = completer.call(opt, context, extra);
index ea626b4c5c08c85cc4a4090ffef0770664904820..08c2829d774415219aefdf05a627d3b61199bed5 100644 (file)
@@ -9,7 +9,7 @@ try {
 Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("overlay", {
     exports: ["ModuleBase"],
-    require: ["config", "services", "util"]
+    require: ["config", "io", "services", "util"]
 }, this);
 
 /**
@@ -206,8 +206,8 @@ var Overlay = Module("Overlay", {
 
                 const start = Date.now();
                 const deferredInit = { load: {} };
-                const seen = set();
-                const loaded = set();
+                const seen = Set();
+                const loaded = Set();
                 modules.loaded = loaded;
 
                 function load(module, prereq, frame) {
@@ -222,7 +222,7 @@ var Overlay = Module("Overlay", {
                             return;
                         if (module.className in seen)
                             throw Error("Module dependency loop.");
-                        set.add(seen, module.className);
+                        Set.add(seen, module.className);
 
                         for (let dep in values(module.requires))
                             load(Module.constructors[dep], module.className);
@@ -275,9 +275,9 @@ var Overlay = Module("Overlay", {
                     });
                 }
                 defineModule.modules.forEach(function defModule(mod) {
-                    let names = set(Object.keys(mod.INIT));
+                    let names = Set(Object.keys(mod.INIT));
                     if ("init" in mod.INIT)
-                        set.add(names, "init");
+                        Set.add(names, "init");
 
                     keys(names).forEach(function (name) { deferInit(name, mod.INIT, mod); });
                 });
index 981a6beb0f609ed1c751e6d284879b836f54c280..13500685dbb10318a2925b33be7ced0a96db680f 100644 (file)
@@ -16,36 +16,71 @@ defineModule("prefs", {
 }, this);
 
 var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), {
-    SAVED: "extensions.dactyl.saved.",
+    ORIGINAL: "extensions.dactyl.original.",
     RESTORE: "extensions.dactyl.restore.",
+    SAVED: "extensions.dactyl.saved.",
     INIT: {},
 
-    init: function (branch, defaults) {
+    init: function init(branch, defaults) {
         this._prefContexts = [];
 
         this.branch = services.pref[defaults ? "getDefaultBranch" : "getBranch"](branch || "");
-        if (this.branch instanceof Ci.nsIPrefBranch2)
-            this.branch.QueryInterface(Ci.nsIPrefBranch2);
+        this.branch instanceof Ci.nsIPrefBranch2;
 
         this.defaults = defaults ? this : this.constructor(branch, true);
 
+        this.branches = memoize({
+            __proto__: this,
+            get original() this.constructor(this.ORIGINAL + this.root),
+            get restore() this.constructor(this.RESTORE + this.root),
+            get saved() this.constructor(this.SAVED + this.root),
+        });
+
         if (!defaults)
             this.restore();
 
         this._observers = {};
     },
 
-    cleanup: function cleanup() {
+    cleanup: function cleanup(reason) {
         if (this.defaults != this)
             this.defaults.cleanup();
+
         this._observers = {};
         if (this.observe) {
             this.branch.removeObserver("", this);
             this.observe.unregister();
             delete this.observe;
         }
+
+        if (this == prefs) {
+            if (~["uninstall", "disable"].indexOf(reason)) {
+                for (let name in values(this.branches.saved.getNames()))
+                    this.safeReset(name, null, true);
+
+                this.branches.original.resetBranch();
+                this.branches.saved.resetBranch();
+            }
+
+            if (reason == "uninstall" && this == prefs)
+                localPrefs.resetBranch();
+        }
     },
 
+    /**
+     * Returns the full name of this object's preference branch.
+     */
+    get root() this.branch.root,
+
+    /**
+     * Returns a new Prefs instance for the sub-branch *branch* of this
+     * branch.
+     *
+     * @param {string} branch The branch to branch to.
+     * @returns {Prefs}
+     */
+    Branch: function Branch(branch) Prefs(this.root + branch),
+
     observe: null,
     observers: {
         "nsPref:changed": function (subject, data) {
@@ -69,7 +104,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference])
      * @param {function(object)} callback The callback, called with the
      *    new value of the preference whenever it changes.
      */
-    watch: function (pref, callback, strong) {
+    watch: function watch(pref, callback, strong) {
         if (!this.observe) {
             util.addObserver(this);
             this.branch.addObserver("", this, false);
@@ -116,7 +151,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference])
             }
         };
 
-        return template.options(config.host + " Preferences", prefs.call(this));
+        return template.options(_("pref.hostPreferences", config.host), prefs.call(this));
     },
 
     /**
@@ -161,17 +196,22 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference])
      * @param {string} branch The branch in which to search preferences.
      *     @default ""
      */
-    getNames: function (branch) this.branch.getChildList(branch || "", { value: 0 }),
+    getNames: function getNames(branch) this.branch.getChildList(branch || "", { value: 0 }),
 
-    _checkSafe: function (name, message, value) {
+    _checkSafe: function _checkSafe(name, message, value) {
         let curval = this.get(name, null);
+
+        if (this.branches.original.get(name) == null)
+            this.branches.original.set(name, curval, true);
+
         if (arguments.length > 2 && curval === value)
             return;
+
         let defval = this.defaults.get(name, null);
-        let saved  = this.get(this.SAVED + name);
+        let saved  = this.branches.saved.get(name);
 
         if (saved == null && curval != defval || saved != null && curval != saved) {
-            let msg = "Warning: setting preference " + name + ", but it's changed from its default value.";
+            let msg = _("pref.safeSet.warnChanged", name);
             if (message)
                 msg = template.linkifyHelp(msg + " " + message);
             util.dactyl.warn(msg);
@@ -184,11 +224,13 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference])
      *
      * @param {string} name The preference name.
      * @param {value} value The new preference value.
+     * @param {boolean} silent Ignore errors.
      */
-    safeReset: function (name, message) {
+    safeReset: function safeReset(name, message, silent) {
         this._checkSafe(name, message);
-        this.reset(name);
-        this.reset(this.SAVED + name);
+        this.set(name, this.branches.original.get(name), silent);
+        this.branches.original.reset(name);
+        this.branches.saved.reset(name);
     },
 
     /**
@@ -198,10 +240,10 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference])
      * @param {string} name The preference name.
      * @param {value} value The new preference value.
      */
-    safeSet: function (name, value, message, skipSave) {
+    safeSet: function safeSet(name, value, message, skipSave) {
         this._checkSafe(name, message, value);
         this.set(name, value);
-        this[skipSave ? "reset" : "set"](this.SAVED + name, value);
+        this.branches.saved[skipSave ? "reset" : "set"](name, value);
     },
 
     /**
@@ -209,40 +251,45 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference])
      *
      * @param {string} name The preference name.
      * @param {value} value The new preference value.
+     * @param {boolean} silent Ignore errors.
      */
-    set: function (name, value) {
-        if (this._prefContexts.length) {
-            let val = this.get(name, null);
-            if (val != null)
-                this._prefContexts[this._prefContexts.length - 1][name] = val;
-        }
+    set: function set(name, value, silent) {
+        if (this._prefContexts.length)
+            this._prefContexts[this._prefContexts.length - 1][name] = this.get(name, null);
 
         function assertType(needType)
             util.assert(type === Ci.nsIPrefBranch.PREF_INVALID || type === needType,
                 type === Ci.nsIPrefBranch.PREF_INT
-                                ? "E521: Number required after =: " + name + "=" + value
-                                : "E474: Invalid argument: " + name + "=" + value);
+                                ? /*L*/"E521: Number required after =: " + name + "=" + value
+                                : /*L*/"E474: Invalid argument: " + name + "=" + value);
 
         let type = this.branch.getPrefType(name);
-        switch (typeof value) {
-        case "string":
-            assertType(Ci.nsIPrefBranch.PREF_STRING);
-
-            this.branch.setComplexValue(name, Ci.nsISupportsString, services.String(value));
-            break;
-        case "number":
-            assertType(Ci.nsIPrefBranch.PREF_INT);
-
-            this.branch.setIntPref(name, value);
-            break;
-        case "boolean":
-            assertType(Ci.nsIPrefBranch.PREF_BOOL);
-
-            this.branch.setBoolPref(name, value);
-            break;
-        default:
-            throw FailedAssertion("Unknown preference type: " + typeof value + " (" + name + "=" + value + ")");
+        try {
+            switch (typeof value) {
+            case "string":
+                assertType(Ci.nsIPrefBranch.PREF_STRING);
+
+                this.branch.setComplexValue(name, Ci.nsISupportsString, services.String(value));
+                break;
+            case "number":
+                assertType(Ci.nsIPrefBranch.PREF_INT);
+
+                this.branch.setIntPref(name, value);
+                break;
+            case "boolean":
+                assertType(Ci.nsIPrefBranch.PREF_BOOL);
+
+                this.branch.setBoolPref(name, value);
+                break;
+            default:
+                if (value == null && this != this.defaults)
+                    this.reset(name);
+                else
+                    throw FailedAssertion("Unknown preference type: " + typeof value + " (" + name + "=" + value + ")");
+            }
         }
+        catch (e if silent) {}
+        return value;
     },
 
     /**
@@ -251,7 +298,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference])
      *
      * @param {string} name The preference to save.
      */
-    save: function (name) {
+    save: function save(name) {
         let val = this.get(name);
         this.set(this.RESTORE + name, val);
         this.safeSet(name, val);
@@ -263,7 +310,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference])
      * @param {string} branch The branch from which to restore
      *      preferences. @optional
      */
-    restore: function (branch) {
+    restore: function restore(branch) {
         this.getNames(this.RESTORE + (branch || "")).forEach(function (pref) {
             this.safeSet(pref.substr(this.RESTORE.length), this.get(pref), null, true);
             this.reset(pref);
@@ -275,21 +322,30 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference])
      *
      * @param {string} name The preference name.
      */
-    reset: function (name) {
+    reset: function reset(name) {
         try {
             this.branch.clearUserPref(name);
         }
         catch (e) {} // ignore - thrown if not a user set value
     },
 
+    /**
+     * Resets the preference branch *branch* to its default value.
+     *
+     * @param {string} branch The preference name. @optional
+     */
+    resetBranch: function resetBranch(branch) {
+        this.getNames(branch).forEach(this.closure.reset);
+    },
+
     /**
      * Toggles the value of the boolean preference *name*.
      *
      * @param {string} name The preference name.
      */
-    toggle: function (name) {
+    toggle: function toggle(name) {
         util.assert(this.branch.getPrefType(name) === Ci.nsIPrefBranch.PREF_BOOL,
-                    _("error.trailing", name + "!"));
+                    _("error.trailingCharacters", name + "!"));
         this.set(name, !this.get(name));
     },
 
@@ -298,7 +354,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference])
      *
      * @see #withContext
      */
-    pushContext: function () {
+    pushContext: function pushContext() {
         this._prefContexts.push({});
     },
 
@@ -307,7 +363,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference])
      *
      * @see #withContext
      */
-    popContext: function () {
+    popContext: function popContext() {
         for (let [k, v] in Iterator(this._prefContexts.pop()))
             this.set(k, v);
     },
@@ -322,7 +378,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference])
      * @see #pushContext
      * @see #popContext
      */
-    withContext: function (func, self) {
+    withContext: function withContext(func, self) {
         try {
             this.pushContext();
             return func.call(self);
@@ -333,7 +389,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference])
     }
 }, {
 }, {
-    completion: function (dactyl, modules) {
+    completion: function init_completion(dactyl, modules) {
         modules.completion.preference = function preference(context) {
             context.anchored = false;
             context.title = [config.host + " Preference", "Value"];
@@ -341,7 +397,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference])
             context.completions = prefs.getNames();
         };
     },
-    javascript: function (dactyl, modules) {
+    javascript: function init_javascript(dactyl, modules) {
         modules.JavaScript.setCompleter([this.get, this.safeSet, this.set, this.reset, this.toggle],
                 [function (context) (context.anchored=false, this.getNames().map(function (pref) [pref, ""]))]);
     }
index c7f3ac9ebe5472aa1654ed9a0e6466b42914652f..e240424dba36f7af3ee308008d01adbbc65670c8 100644 (file)
@@ -18,7 +18,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("sanitizer", {
     exports: ["Range", "Sanitizer", "sanitizer"],
     use: ["config"],
-    require: ["messages", "prefs", "services", "storage", "template", "util"],
+    require: ["messages", "prefs", "services", "storage", "template", "util"]
 }, this);
 
 let tmp = {};
@@ -33,7 +33,7 @@ update(Range.prototype, {
     get isEternity() this.max == null && this.min == null,
     get isSession() this.max == null && this.min == sanitizer.sessionStart,
 
-    get native() this.isEternity ? null : [range.min || 0, range.max == null ? Number.MAX_VALUE : range.max]
+    get native() this.isEternity ? null : [this.min || 0, this.max == null ? Number.MAX_VALUE : this.max]
 });
 
 var Item = Class("SanitizeItem", {
@@ -69,10 +69,10 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
 
         util.addObserver(this);
 
-        services.add("contentprefs", "@mozilla.org/content-pref/service;1", Ci.nsIContentPrefService);
+        services.add("contentPrefs", "@mozilla.org/content-pref/service;1", Ci.nsIContentPrefService);
         services.add("cookies",      "@mozilla.org/cookiemanager;1",        [Ci.nsICookieManager, Ci.nsICookieManager2,
                                                                              Ci.nsICookieService]);
-        services.add("loginmanager", "@mozilla.org/login-manager;1",        Ci.nsILoginManager);
+        services.add("loginManager", "@mozilla.org/login-manager;1",        Ci.nsILoginManager);
         services.add("permissions",  "@mozilla.org/permissionmanager;1",    Ci.nsIPermissionManager);
 
         this.itemMap = {};
@@ -107,11 +107,13 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
             action: function (range, host) {
                 if (host)
                     services.history.removePagesFromHost(host, true);
-                else
-                    services.history.removeVisitsByTimeframe(this.range.min, this.range.max);
-
-                if (!host)
+                else {
+                    if (range.isEternity)
+                        services.history.removeAllPages();
+                    else
+                        services.history.removeVisitsByTimeframe(range.native[0], Math.min(Date.now() * 1000, range.native[1])); // XXX
                     services.observer.notifyObservers(null, "browser:purge-session-history", "");
+                }
 
                 if (!host || util.isDomainURL(prefs.get("general.open_location.last_url"), host))
                     prefs.reset("general.open_location.last_url");
@@ -138,20 +140,20 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
                         services.permissions.remove(util.createURI(p.host), p.type);
                         services.permissions.add(util.createURI(p.host), p.type, 0);
                     }
-                    for (let p in iter(services.contentprefs.getPrefs(util.createURI(host))))
-                        services.contentprefs.removePref(util.createURI(host), p.QueryInterface(Ci.nsIProperty).name);
+                    for (let p in iter(services.contentPrefs.getPrefs(util.createURI(host))))
+                        services.contentPrefs.removePref(util.createURI(host), p.QueryInterface(Ci.nsIProperty).name);
                 }
                 else {
                     // "Allow this site to open popups" ...
                     services.permissions.removeAll();
                     // Zoom level, ...
-                    services.contentprefs.removeGroupedPrefs();
+                    services.contentPrefs.removeGroupedPrefs();
                 }
 
                 // "Never remember passwords" ...
-                for each (let domain in services.loginmanager.getAllDisabledHosts())
+                for each (let domain in services.loginManager.getAllDisabledHosts())
                     if (!host || util.isSubdomain(domain, host))
-                        services.loginmanager.setLoginSavingEnabled(host, true);
+                        services.loginManager.setLoginSavingEnabled(host, true);
             },
             override: true
         });
@@ -183,7 +185,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
                 append: {
                     SanitizeDialogPane:
                         <groupbox orient="horizontal" xmlns={XUL}>
-                          <caption label={config.appName + " (see :help privacy)"}/>
+                          <caption label={config.appName + /*L*/" (see :help privacy)"}/>
                           <grid flex="1">
                             <columns><column flex="1"/><column flex="1"/></columns>
                             <rows>{
@@ -204,7 +206,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
                                function (win) prefOverlay(branch, false, {
                 append: {
                     itemList: <>
-                        <listitem xmlns={XUL} label="See :help privacy for the following:" disabled="true" style="font-style: italic; font-weight: bold;"/>
+                        <listitem xmlns={XUL} label={/*L*/"See :help privacy for the following:"} disabled="true" style="font-style: italic; font-weight: bold;"/>
                         {
                           template.map(ourItems(), function ([item, desc])
                             <listitem xmlns={XUL} type="checkbox"
@@ -233,6 +235,8 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
         }
     },
 
+    firstRun: 0,
+
     addItem: function addItem(name, params) {
         let item = this.itemMap[name] || Item(name, params);
         this.itemMap[name] = item;
@@ -241,7 +245,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
             if (!("value" in prop) || !callable(prop.value) && !(k in item))
                 Object.defineProperty(item, k, prop);
 
-        let names = set([name].concat(params.contains || []).map(function (e) "clear-" + e));
+        let names = Set([name].concat(params.contains || []).map(function (e) "clear-" + e));
         if (params.action)
             storage.addObserver("sanitizer",
                 function (key, event, arg) {
@@ -343,16 +347,18 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
         deny:    2,
         session: 8
     },
+
     UNPERMS: Class.memoize(function () iter(this.PERMS).map(Array.reverse).toObject()),
+
     COMMANDS: {
-        unset:   "Unset",
-        allow:   "Allowed",
-        deny:    "Denied",
-        session: "Allowed for the current session",
-        list:    "List all cookies for domain",
-        clear:   "Clear all cookies for domain",
-        "clear-persistent": "Clear all persistent cookies for domain",
-        "clear-session":    "Clear all session cookies for domain"
+        unset:   /*L*/"Unset",
+        allow:   /*L*/"Allowed",
+        deny:    /*L*/"Denied",
+        session: /*L*/"Allowed for the current session",
+        list:    /*L*/"List all cookies for domain",
+        clear:   /*L*/"Clear all cookies for domain",
+        "clear-persistent": /*L*/"Clear all persistent cookies for domain",
+        "clear-session":    /*L*/"Clear all session cookies for domain"
     },
 
     argPrefMap: {
@@ -377,7 +383,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
     }
 }, {
     load: function (dactyl, modules, window) {
-        if (sanitizer.runAtShutdown && !sanitizer.ranAtShutdown)
+        if (!sanitizer.firstRun++ && sanitizer.runAtShutdown && !sanitizer.ranAtShutdown)
             sanitizer.sanitizeItems(null, Range(), null, "shutdown");
         sanitizer.ranAtShutdown = false;
     },
@@ -399,7 +405,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
         commands.add(["sa[nitize]"],
             "Clear private data",
             function (args) {
-                dactyl.assert(!modules.options['private'], "Cannot sanitize items in private mode");
+                dactyl.assert(!modules.options['private'], _("command.sanitize.privateMode"));
 
                 let timespan = args["-timespan"] || modules.options["sanitizetimespan"];
 
@@ -414,27 +420,41 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
                     args[0] = "all";
 
                 if (args.bang) {
-                    dactyl.assert(args.length == 0, _("error.trailing"));
+                    dactyl.assert(args.length == 0, _("error.trailingCharacters"));
                     items = Object.keys(sanitizer.itemMap).filter(
                         function (k) modules.options.get("sanitizeitems").has(k));
                 }
                 else
-                    dactyl.assert(modules.options.get("sanitizeitems").validator(items), "Valid items required");
+                    dactyl.assert(modules.options.get("sanitizeitems").validator(items), _("error.invalidArgument"));
+
+                function sanitize(items) {
+                    sanitizer.range = range.native;
+                    sanitizer.ignoreTimespan = range.min == null;
+                    sanitizer.sanitizing = true;
+                    if (args["-host"]) {
+                        args["-host"].forEach(function (host) {
+                            sanitizer.sanitizing = true;
+                            sanitizer.sanitizeItems(items, range, host);
+                        });
+                    }
+                    else
+                        sanitizer.sanitize(items, range);
+                }
 
                 if (items.indexOf("all") >= 0)
-                    items = Object.keys(sanitizer.itemMap).filter(function (k) items.indexOf(k) === -1);
-
-                sanitizer.range = range.native;
-                sanitizer.ignoreTimespan = range.min == null;
-                sanitizer.sanitizing = true;
-                if (args["-host"]) {
-                    args["-host"].forEach(function (host) {
-                        sanitizer.sanitizing = true;
-                        sanitizer.sanitizeItems(items, range, host)
-                    });
-                }
+                    modules.commandline.input(_("sanitize.prompt.deleteAll") + " ",
+                        function (resp) {
+                            if (resp.match(/^y(es)?$/i)) {
+                                items = Object.keys(sanitizer.itemMap).filter(function (k) items.indexOf(k) === -1);
+                                sanitize(items);
+                                dactyl.echo(_("command.sanitize.allDeleted"));
+                            }
+                            else
+                                dactyl.echo(_("command.sanitize.noneDeleted"));
+                        });
                 else
-                    sanitizer.sanitize(items, range);
+                    sanitize(items);
+
             },
             {
                 argCount: "*", // FIXME: should be + and 0
@@ -453,7 +473,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
                                 !args["-host"].some(function (host) util.isSubdomain(item.text, host)));
                             modules.completion.domain(context);
                         },
-                        type: modules.CommandOption.LIST,
+                        type: modules.CommandOption.LIST
                     }, {
                         names: ["-older", "-o"],
                         description: "Sanitize items older than timespan",
@@ -575,7 +595,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
                 get values() values(sanitizer.itemMap).toArray(),
                 has: modules.Option.has.toggleAll,
                 validator: function (values) values.length &&
-                    values.every(function (val) val === "all" || set.has(sanitizer.itemMap, val))
+                    values.every(function (val) val === "all" || Set.has(sanitizer.itemMap, val))
             });
 
         options.add(["sanitizeshutdown", "ss"],
@@ -593,10 +613,10 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
                         sanitizer.runAtShutdown = false;
                     else {
                         sanitizer.runAtShutdown = true;
-                        let have = set(value);
+                        let have = Set(value);
                         for (let item in values(sanitizer.itemMap))
                             prefs.set(item.shutdownPref,
-                                      Boolean(set.has(have, item.name) ^ set.has(have, "all")));
+                                      Boolean(Set.has(have, item.name) ^ Set.has(have, "all")));
                     }
                     return value;
                 }
@@ -667,7 +687,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
                 },
                 initialValue: true,
                 persist: false,
-                validator: function (val) parseInt(val) == val || modules.Option.validateCompleter.call(this, val)
+                validator: function validator(val) parseInt(val) == val || validator.superapply(this, arguments)
             });
     }
 });
index bceb265a1ce97816c997592b39c5878083de2dbf..b3372ddf5377db31298b81679c7590c226fe8442 100644 (file)
@@ -25,6 +25,7 @@ var Services = Module("Services", {
         this.add("appStartup",          "@mozilla.org/toolkit/app-startup;1",               "nsIAppStartup");
         this.add("autoCompleteSearch",  "@mozilla.org/autocomplete/search;1?name=history",  "nsIAutoCompleteSearch");
         this.add("bookmarks",           "@mozilla.org/browser/nav-bookmarks-service;1",     "nsINavBookmarksService");
+        this.add("bootstrap",           "@dactyl.googlecode.com/base/bootstrap");
         this.add("browserSearch",       "@mozilla.org/browser/search-service;1",            "nsIBrowserSearchService");
         this.add("cache",               "@mozilla.org/network/cache-service;1",             "nsICacheService");
         this.add("charset",             "@mozilla.org/charset-converter-manager;1",         "nsICharsetConverterManager");
@@ -39,11 +40,13 @@ var Services = Module("Services", {
         this.add("extensionManager",    "@mozilla.org/extensions/manager;1",                "nsIExtensionManager");
         this.add("externalProtocol",    "@mozilla.org/uriloader/external-protocol-service;1", "nsIExternalProtocolService");
         this.add("favicon",             "@mozilla.org/browser/favicon-service;1",           "nsIFaviconService");
+        this.add("file:",               "@mozilla.org/network/protocol;1?name=file",        "nsIFileProtocolHandler");
         this.add("focus",               "@mozilla.org/focus-manager;1",                     "nsIFocusManager");
         this.add("history",             "@mozilla.org/browser/global-history;2",
                  ["nsIBrowserHistory", "nsIGlobalHistory3", "nsINavHistoryService", "nsPIPlacesDatabase"]);
         this.add("io",                  "@mozilla.org/network/io-service;1",                "nsIIOService");
         this.add("json",                "@mozilla.org/dom/json;1",                          "nsIJSON", "createInstance");
+        this.add("listeners",           "@mozilla.org/eventlistenerservice;1",              "nsIEventListenerService");
         this.add("livemark",            "@mozilla.org/browser/livemark-service;2",          "nsILivemarkService");
         this.add("mime",                "@mozilla.org/mime;1",                              "nsIMIMEService");
         this.add("observer",            "@mozilla.org/observer-service;1",                  "nsIObserverService");
@@ -58,6 +61,7 @@ var Services = Module("Services", {
         this.add("stylesheet",          "@mozilla.org/content/style-sheet-service;1",       "nsIStyleSheetService");
         this.add("subscriptLoader",     "@mozilla.org/moz/jssubscript-loader;1",            "mozIJSSubScriptLoader");
         this.add("tagging",             "@mozilla.org/browser/tagging-service;1",           "nsITaggingService");
+        this.add("tld",                 "@mozilla.org/network/effective-tld-service;1",     "nsIEffectiveTLDService");
         this.add("threading",           "@mozilla.org/thread-manager;1",                    "nsIThreadManager");
         this.add("urifixup",            "@mozilla.org/docshell/urifixup;1",                 "nsIURIFixup");
         this.add("versionCompare",      "@mozilla.org/xpcom/version-comparator;1",          "nsIVersionComparator");
@@ -67,7 +71,6 @@ var Services = Module("Services", {
 
         this.addClass("CharsetConv",  "@mozilla.org/intl/scriptableunicodeconverter", "nsIScriptableUnicodeConverter", "charset");
         this.addClass("File",         "@mozilla.org/file/local;1",                 "nsILocalFile");
-        this.addClass("file:",        "@mozilla.org/network/protocol;1?name=file", "nsIFileProtocolHandler");
         this.addClass("Find",         "@mozilla.org/embedcomp/rangefind;1",        "nsIFind");
         this.addClass("HtmlConverter","@mozilla.org/widget/htmlformatconverter;1", "nsIFormatConverter");
         this.addClass("HtmlEncoder",  "@mozilla.org/layout/htmlCopyEncoder;1",     "nsIDocumentEncoder");
@@ -84,6 +87,7 @@ var Services = Module("Services", {
         this.addClass("Timer",        "@mozilla.org/timer;1",                      "nsITimer", "initWithCallback");
         this.addClass("Xmlhttp",      "@mozilla.org/xmlextras/xmlhttprequest;1",   "nsIXMLHttpRequest");
         this.addClass("XPathEvaluator", "@mozilla.org/dom/xpath-evaluator;1",      "nsIDOMXPathEvaluator");
+        this.addClass("XMLDocument",  "@mozilla.org/xml/xml-document;1",           ["nsIDOMXMLDocument", "nsIDOMNodeSelector"]);
         this.addClass("ZipReader",    "@mozilla.org/libjar/zip-reader;1",          "nsIZipReader", "open");
         this.addClass("ZipWriter",    "@mozilla.org/zipwriter;1",                  "nsIZipWriter");
     },
@@ -122,7 +126,7 @@ var Services = Module("Services", {
      *
      * @param {string} name The service's cache key.
      * @param {string} class The class's contract ID.
-     * @param {string|string[]} ifaces The interface or array of
+     * @param {string|[string]} ifaces The interface or array of
      *     interfaces implemented by this service.
      * @param {string} meth The name of the function used to instantiate
      *     the service.
@@ -140,7 +144,7 @@ var Services = Module("Services", {
      *
      * @param {string} name The class's cache key.
      * @param {string} class_ The class's contract ID.
-     * @param {nsISupports|nsISupports[]} ifaces The interface or array of
+     * @param {nsISupports|[nsISupports]} ifaces The interface or array of
      *     interfaces implemented by this class.
      * @param {string} init Name of a property or method used to initialise the
      *     class. See {@link #_create}.
@@ -171,7 +175,7 @@ var Services = Module("Services", {
      *
      * @param {string} name The service's cache key.
      */
-    has: function (name) set.has(this.services, name) && this.services[name].class in Cc &&
+    has: function (name) Set.has(this.services, name) && this.services[name].class in Cc &&
         this.services[name].interfaces.every(function (iface) iface in Ci)
 }, {
 }, {
index 28ad0e8dd0715d0b7b3ad2946c907806d8f7050f..896daf4461189867811231b5c916dba25887f93b 100644 (file)
@@ -150,6 +150,10 @@ var Storage = Module("Storage", {
 
     init: function () {
         this.cleanup();
+
+        if (services.bootstrap && !services.bootstrap.session)
+            services.bootstrap.session = {};
+        this.session = services.bootstrap ? services.bootstrap.session : {};
     },
 
     cleanup: function () {
@@ -294,9 +298,9 @@ var File = Class("File", {
         let file = services.File();
 
         if (path instanceof Ci.nsIFile)
-            file = path.QueryInterface(Ci.nsIFile).clone();
+            file = path.clone();
         else if (/file:\/\//.test(path))
-            file = services["file:"]().getFileFromURLSpec(path);
+            file = services["file:"].getFileFromURLSpec(path);
         else {
             try {
                 let expandedPath = File.expandPath(path);
@@ -316,14 +320,19 @@ var File = Class("File", {
         return self;
     },
 
+    /**
+     * @property {nsIFileURL} Returns the nsIFileURL object for this file.
+     */
+    get URI() services.io.newFileURI(this),
+
     /**
      * Iterates over the objects in this directory.
      */
     iterDirectory: function () {
         if (!this.exists())
-            throw Error("File does not exist");
+            throw Error(_("io.noSuchFile"));
         if (!this.isDirectory())
-            throw Error("Not a directory");
+            throw Error(_("io.eNotDir"));
         for (let file in iter(this.directoryEntries))
             yield File(file);
     },
@@ -358,11 +367,11 @@ var File = Class("File", {
      *
      * @param {boolean} sort Whether to sort the returned directory
      *     entries.
-     * @returns {nsIFile[]}
+     * @returns {[nsIFile]}
      */
     readDirectory: function (sort) {
         if (!this.isDirectory())
-            throw Error("Not a directory");
+            throw Error(_("io.eNotDir"));
 
         let array = [e for (e in this.iterDirectory())];
         if (sort)
@@ -426,14 +435,11 @@ var File = Class("File", {
 
         ofstream.init(this, mode, perms, 0);
         try {
-            if (callable(buf))
-                buf(ofstream.QueryInterface(Ci.nsIOutputStream));
-            else {
-                var ocstream = getStream(0);
-                ocstream.writeString(buf);
-            }
+            var ocstream = getStream(0);
+            ocstream.writeString(buf);
         }
-        catch (e if callable(buf) && e.result == Cr.NS_ERROR_LOSS_OF_SIGNIFICANT_DATA) {
+        catch (e if e.result == Cr.NS_ERROR_LOSS_OF_SIGNIFICANT_DATA) {
+            ocstream.close();
             ocstream = getStream("?".charCodeAt(0));
             ocstream.writeString(buf);
             return false;
index adfc725c8269780495c182289689e672b9c1af67..70608b9fb170eca5cddec2992b490b093028bb25 100644 (file)
@@ -68,17 +68,20 @@ update(Sheet.prototype, {
     get fullCSS() {
         let filter = this.sites;
         let css = this.css;
+
+        let preamble = "/* " + this.uri + (this.agent ? " (agent)" : "") + " */\n\n" + namespace + "\n";
         if (filter[0] == "*")
-            return namespace + css;
+            return preamble + css;
 
         let selectors = filter.map(function (part)
-                                    (/[*]$/.test(part)   ? "url-prefix" :
-                                     /[\/:]/.test(part)  ? "url"
-                                                         : "domain")
-                                    + '("' + part.replace(/"/g, "%22").replace(/\*$/, "") + '")')
+                                    !/^(?:[a-z-]+[:*]|[a-z-.]+$)/i.test(part) ? "regexp(" + (".*(?:" + part + ").*").quote() + ")" :
+                                       (/[*]$/.test(part)   ? "url-prefix" :
+                                        /[\/:]/.test(part)  ? "url"
+                                                            : "domain")
+                                       + '("' + part.replace(/"/g, "%22").replace(/\*$/, "") + '")')
                               .join(",\n               ");
-        return "/* " + this.uri + (this.agent ? " (agent)" : "") + " */\n\n"
-             + namespace + "\n@-moz-document " + selectors + " {\n\n" + css + "\n\n}\n";
+
+        return preamble + "@-moz-document " + selectors + " {\n\n" + css + "\n\n}\n";
     }
 });
 
@@ -253,8 +256,8 @@ var Styles = Module("Styles", {
 
         services["dactyl:"].providers["style"] = function styleProvider(uri) {
             let id = /^\/(\d*)/.exec(uri.path)[1];
-            if (set.has(styles.allSheets, id))
-                return ["text/css", unescape(encodeURI(styles.allSheets[id].fullCSS))];
+            if (Set.has(styles.allSheets, id))
+                return ["text/css", styles.allSheets[id].fullCSS];
             return null;
         };
     },
@@ -296,13 +299,15 @@ var Styles = Module("Styles", {
     systemNames: Class.Property({ get: deprecated("Styles#system.names", function systemNames() this.system.names) }),
     sites: Class.Property({ get: deprecated("Styles#user.sites", function sites() this.user.sites) }),
 
-    list: function list(content, filter, name, hives) {
+    list: function list(content, sites, name, hives) {
         const { commandline, dactyl } = this.modules;
 
         hives = hives || styles.hives.filter(function (h) h.modifiable && h.sheets.length);
 
         function sheets(group)
             group.sheets.slice()
+                 .filter(function (sheet) (!name || sheet.name === name) &&
+                                          (!sites || sites.every(function (s) sheet.sites.indexOf(s) >= 0)))
                  .sort(function (a, b) a.name && b.name ? String.localeCompare(a.name, b.name)
                                                         : !!b.name - !!a.name || a.id - b.id);
 
@@ -312,9 +317,9 @@ var Styles = Module("Styles", {
                 <tr highlight="Title">
                     <td/>
                     <td/>
-                    <td style="padding-right: 1em;">Name</td>
-                    <td style="padding-right: 1em;">Filter</td>
-                    <td style="padding-right: 1em;">CSS</td>
+                    <td style="padding-right: 1em;">{_("title.Name")}</td>
+                    <td style="padding-right: 1em;">{_("title.Filter")}</td>
+                    <td style="padding-right: 1em;">{_("title.CSS")}</td>
                 </tr>
                 <col style="min-width: 4em; padding-right: 1em;"/>
                 <col style="min-width: 1em; text-align: center; color: red; font-weight: bold;"/>
@@ -365,9 +370,13 @@ var Styles = Module("Styles", {
             for (let prop in Styles.propertyIter(str))
                 props[prop.name] = prop.value;
 
-        return Object.keys(props)[sort ? "sort" : "slice"]()
-                     .map(function (prop) prop + ": " + props[prop] + ";")
-                     .join(" ");
+        let val = Object.keys(props)[sort ? "sort" : "slice"]()
+                        .map(function (prop) prop + ": " + props[prop] + ";")
+                        .join(" ");
+
+        if (/^\s*(\/\*.*?\*\/)/.exec(src))
+            val = RegExp.$1 + " " + val;
+        return val;
     },
 
     completeSite: function (context, content, group) {
@@ -377,8 +386,8 @@ var Styles = Module("Styles", {
             context.fork("current", 0, this, function (context) {
                 context.title = ["Current Site"];
                 context.completions = [
-                    [content.location.host, "Current Host"],
-                    [content.location.href, "Current URL"]
+                    [content.location.host, /*L*/"Current Host"],
+                    [content.location.href, /*L*/"Current URL"]
                 ];
             });
         }
@@ -389,7 +398,7 @@ var Styles = Module("Styles", {
         context.generate = function () values(group.sites);
 
         context.keys.text = util.identity;
-        context.keys.description = function (site) this.sheets.length + " sheet" + (this.sheets.length == 1 ? "" : "s") + ": " +
+        context.keys.description = function (site) this.sheets.length + /*L*/" sheet" + (this.sheets.length == 1 ? "" : "s") + ": " +
             array.compact(this.sheets.map(function (s) s.name)).join(", ");
         context.keys.sheets = function (site) group.sheets.filter(function (s) s.sites.indexOf(site) >= 0);
         context.keys.active = function (site) uris.some(Styles.matchFilter(site));
@@ -434,7 +443,7 @@ var Styles = Module("Styles", {
         for (let item in Iterator({ Active: true, Inactive: false })) {
             let [name, active] = item;
             context.split(name, null, function (context) {
-                context.title[0] = name + " " + (title || "Sheets");
+                context.title[0] = /*L*/name + " " + (title || "Sheets");
                 context.filters.push(function (item) !!item.active == active);
             });
         }
@@ -553,17 +562,16 @@ var Styles = Module("Styles", {
                 let [filter, css] = args;
 
                 if (!css)
-                    styles.list(window.content, filter, args["-name"], args.explicitOpts["-group"] ? [args["-group"]] : null);
+                    styles.list(window.content, filter ? filter.split(",") : null, args["-name"], args.explicitOpts["-group"] ? [args["-group"]] : null);
                 else {
                     util.assert(args["-group"].modifiable && args["-group"].hive.modifiable,
-                                "Cannot modify styles in the builtin group");
+                                _("group.cantChangeBuiltin", _("style.styles")));
 
                     if (args["-append"]) {
                         let sheet = args["-group"].get(args["-name"]);
                         if (sheet) {
-                            filter = sheet.sites.concat(filter).join(",");
+                            filter = array(sheet.sites).concat(filter).uniq().join(",");
                             css = sheet.css + " " + css;
-
                         }
                     }
                     let style = args["-group"].add(args["-name"], filter, css, args["-agent"]);
@@ -573,7 +581,6 @@ var Styles = Module("Styles", {
                 }
             },
             {
-                bang: true,
                 completer: function (context, args) {
                     let compl = [];
                     let sheet = args["-group"].get(args["-name"]);
@@ -584,7 +591,9 @@ var Styles = Module("Styles", {
                     }
                     else if (args.completeArg == 1) {
                         if (sheet)
-                            context.completions = [[sheet.css, "Current Value"]];
+                            context.completions = [
+                                [sheet.css, _("option.currentValue")]
+                            ];
                         context.fork("css", 0, modules.completion, "css");
                     }
                 },
@@ -607,9 +616,8 @@ var Styles = Module("Styles", {
                                     command: "style",
                                     arguments: [style.sites.join(",")],
                                     literalArg: style.css,
-                                    options: update({
-                                            "-group": hive.name,
-                                        },
+                                    options: update(
+                                        hive.name == "user" ? {} : { "-group": hive.name },
                                         style.name ? { "-name": style.name } : {})
                                 })))
                         .flatten().array
@@ -642,7 +650,7 @@ var Styles = Module("Styles", {
             commands.add(cmd.name, cmd.desc,
                 function (args) {
                     dactyl.assert(args.bang ^ !!(args[0] || args[1] || args["-name"] || args["-index"]),
-                                  "Argument or ! required");
+                                  _("error.argumentOrBang"));
 
                     args["-group"].find(args["-name"], args[0], args.literalArg, args["-index"])
                                   .forEach(cmd.action);
index 8a398c2e148c1fab6e153a2a2eaf0320c316443b..6e18dc506c676e085153eebcfb9f338d1dfe68be 100644 (file)
@@ -119,7 +119,7 @@ var Template = Module("Template", {
                 "click": function onClick(event) {
                     event.preventDefault();
                     if (this.commandAllowed) {
-                        if (set.has(this.target.commands || {}, this.command))
+                        if (Set.has(this.target.commands || {}, this.command))
                             this.target.commands[this.command].call(this.target);
                         else
                             this.target.command(this.command);
@@ -128,7 +128,7 @@ var Template = Module("Template", {
             },
 
             get commandAllowed() {
-                if (set.has(this.target.allowedCommands || {}, this.command))
+                if (Set.has(this.target.allowedCommands || {}, this.command))
                     return this.target.allowedCommands[this.command];
                 if ("commandAllowed" in this.target)
                     return this.target.commandAllowed(this.command);
@@ -197,7 +197,7 @@ var Template = Module("Template", {
                       - from pushing the baseline down and enlarging
                       - the row.
                       -->
-                   <li highlight="CompResult">{text}&#xa0;</li>
+                   <li highlight={"CompResult " + item.highlight}>{text}&#xa0;</li>
                    <li highlight="CompDesc">{desc}&#xa0;</li>
                </div>;
         // </e4x>
@@ -213,7 +213,7 @@ var Template = Module("Template", {
         else if (/^n_/.test(topic))
             topic = topic.slice(2);
 
-        if (services["dactyl:"].initialized && !set.has(services["dactyl:"].HELP_TAGS, topic))
+        if (services["dactyl:"].initialized && !Set.has(services["dactyl:"].HELP_TAGS, topic))
             return <span highlight={type || ""}>{text || token}</span>;
 
         XML.ignoreWhitespace = false; XML.prettyPrinting = false;
@@ -233,7 +233,7 @@ var Template = Module("Template", {
         else if (/^n_/.test(topic))
             topic = topic.slice(2);
 
-        if (services["dactyl:"].initialized && !set.has(services["dactyl:"].HELP_TAGS, topic))
+        if (services["dactyl:"].initialized && !Set.has(services["dactyl:"].HELP_TAGS, topic))
             return <>{token}</>;
 
         XML.ignoreWhitespace = false; XML.prettyPrinting = false;
@@ -247,7 +247,7 @@ var Template = Module("Template", {
     linkifyHelp: function linkifyHelp(str, help) {
         let re = util.regexp(<![CDATA[
             (?P<pre> [/\s]|^)
-            (?P<tag> '[\w-]+' | :(?:[\w-]+!?|!) | (?:._)?<[\w-]+>\w* | [a-zA-Z]_\w+ | \[[\w-]+\] | E\d{3} )
+            (?P<tag> '[\w-]+' | :(?:[\w-]+!?|!) | (?:._)?<[\w-]+>\w* | \b[a-zA-Z]_(?:\w+|.) | \[[\w-]+\] | E\d{3} )
             (?=      [[\)!,:;./\s]|$)
         ]]>, "gx");
         return this.highlightSubstrings(str, (function () {
@@ -349,7 +349,7 @@ var Template = Module("Template", {
 
     highlightURL: function highlightURL(str, force) {
         if (force || /^[a-zA-Z]+:\/\//.test(str))
-            return <a highlight="URL" href={str}>{str}</a>;
+            return <a highlight="URL" href={str}>{util.losslessDecodeURI(str)}</a>;
         else
             return str;
     },
@@ -363,7 +363,9 @@ var Template = Module("Template", {
         // <e4x>
         return <table>
                 <tr style="text-align: left;" highlight="Title">
-                    <th colspan="2">jump</th><th>title</th><th>URI</th>
+                    <th colspan="2">{_("title.Jump")}</th>
+                    <th>{_("title.Title")}</th>
+                    <th>{_("title.URI")}</th>
                 </tr>
                 {
                     this.map(Iterator(elems), function ([idx, val])
@@ -371,7 +373,7 @@ var Template = Module("Template", {
                         <td class="indicator">{idx == index ? ">" : ""}</td>
                         <td>{Math.abs(idx - index)}</td>
                         <td style="width: 250px; max-width: 500px; overflow: hidden;">{val.title}</td>
-                        <td><a href={val.URI.spec} highlight="URL jump-list">{val.URI.spec}</a></td>
+                        <td><a href={val.URI.spec} highlight="URL jump-list">{util.losslessDecodeURI(val.URI.spec)}</a></td>
                     </tr>)
                 }
             </table>;
@@ -494,7 +496,7 @@ var Template = Module("Template", {
                             let (name = item.name || item.names[0], frame = item.definedAt)
                                 !frame ? name :
                                     template.helpLink(help(item), name, "Title") +
-                                    <span highlight="LinkInfo" xmlns:dactyl={NS}>Defined at {sourceLink(frame)}</span>
+                                    <span highlight="LinkInfo" xmlns:dactyl={NS}>{_("io.definedAt")} {sourceLink(frame)}</span>
                         }</span>
                     </td>
                     { item.columns ? template.map(item.columns, function (c) <td>{c}</td>) : "" }
index 673366a099188f42f964583bc1a7f44a78d6f0b5..7941ebf587249f94d4a90979432023c773cbe67a 100644 (file)
@@ -13,7 +13,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("util", {
     exports: ["frag", "FailedAssertion", "Math", "NS", "Point", "Util", "XBL", "XHTML", "XUL", "util"],
     require: ["services"],
-    use: ["commands", "config", "highlight", "storage", "template"]
+    use: ["commands", "config", "highlight", "messages", "storage", "template"]
 }, this);
 
 var XBL = Namespace("xbl", "http://www.mozilla.org/xbl");
@@ -115,12 +115,12 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      *
      * @param {object} obj
      */
-    addObserver: function (obj) {
+    addObserver: update(function addObserver(obj) {
         if (!obj.observers)
             obj.observers = obj.observe;
 
         function register(meth) {
-            for (let target in set(["dactyl-cleanup-modules", "quit-application"].concat(Object.keys(obj.observers))))
+            for (let target in Set(["dactyl-cleanup-modules", "quit-application"].concat(Object.keys(obj.observers))))
                 try {
                     services.observer[meth](obj, target, true);
                 }
@@ -137,7 +137,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                 }
                 catch (e) {
                     if (typeof util === "undefined")
-                        dump("dactyl: error: " + e + "\n" + (e.stack || Error().stack).replace(/^/gm, "dactyl:    "));
+                        addObserver.dump("dactyl: error: " + e + "\n" + (e.stack || addObserver.Error().stack).replace(/^/gm, "dactyl:    "));
                     else
                         util.reportError(e);
                 }
@@ -145,7 +145,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
 
         obj.observe.unregister = function () register("removeObserver");
         register("addObserver");
-    },
+    }, { dump: dump, Error: Error }),
 
     /*
      * Tests a condition and throws a FailedAssertion error on
@@ -158,6 +158,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
     assert: function (condition, message, quiet) {
         if (!condition)
             throw FailedAssertion(message, 1, quiet === undefined ? true : quiet);
+        return condition;
     },
 
     /**
@@ -165,7 +166,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * @param {string} str The string to capitalize
      * @returns {string}
      */
-    capitalize: function capitalize(str) str && str[0].toUpperCase() + str.slice(1),
+    capitalize: function capitalize(str) str && str[0].toUpperCase() + str.slice(1).toLowerCase(),
 
     /**
      * Returns a RegExp object that matches characters specified in the range
@@ -181,12 +182,12 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
 
         // check for chars not in the accepted range
         this.assert(RegExp("^[" + accepted + "-]+$").test(list),
-                    "Character list outside the range " + accepted.quote());
+                    _("error.charactersOutsideRange", accepted.quote()));
 
         // check for illegal ranges
         for (let [match] in this.regexp.iterate(/.-./g, list))
             this.assert(match.charCodeAt(0) <= match.charCodeAt(2),
-                        "Invalid character range: " + list.slice(list.indexOf(match)))
+                        _("error.invalidCharacterRange", list.slice(list.indexOf(match))));
 
         return RegExp("[" + util.regexp.escape(list) + "]");
     },
@@ -308,7 +309,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             }
             else if (char === "]") {
                 stack.pop();
-                util.assert(stack.length, "Unmatched %] in format");
+                util.assert(stack.length, /*L*/"Unmatched %] in format");
             }
             else {
                 let quote = function quote(obj, char) obj[char];
@@ -327,10 +328,35 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         if (end < format.length)
             stack.top.elements.push(format.substr(end));
 
-        util.assert(stack.length === 1, "Unmatched %[ in format");
+        util.assert(stack.length === 1, /*L*/"Unmatched %[ in format");
         return stack.top;
     },
 
+    /**
+     * Compiles a macro string into a function which generates a string
+     * result based on the input *macro* and its parameters. The
+     * definitive documentation for macro strings resides in :help
+     * macro-string.
+     *
+     * Macro parameters may have any of the following flags:
+     *     e: The parameter is only tested for existence. Its
+     *        interpolation is always empty.
+     *     q: The result is quoted such that it is parsed as a single
+     *        argument by the Ex argument parser.
+     *
+     * The returned function has the following additional properties:
+     *
+     *     seen {set}: The set of parameters used in this macro.
+     *
+     *     valid {function(object)}: Returns true if every parameter of
+     *          this macro is provided by the passed object.
+     *
+     * @param {string} macro The macro string to compile.
+     * @param {boolean} keepUnknown If true, unknown macro parameters
+     *      are left untouched. Otherwise, they are replaced with the null
+     *      string.
+     * @returns {function}
+     */
     compileMacro: function compileMacro(macro, keepUnknown) {
         let stack = [frame()];
         stack.__defineGetter__("top", function () this[this.length - 1]);
@@ -355,14 +381,14 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             ([^]*?) // 1
             (?:
                 (<\{) | // 2
-                (< ((?:[a-z]-)?[a-z-]+?) >) | // 3 4
-                (\}>) // 5
+                (< ((?:[a-z]-)?[a-z-]+?) (?:\[([0-9]+)\])? >) | // 3 4 5
+                (\}>) // 6
             )
         ]]>, "gixy");
         macro = String(macro);
         let end = 0;
         for (let match in re.iterate(macro)) {
-            let [, prefix, open, full, macro, close] = match;
+            let [, prefix, open, full, macro, idx, close] = match;
             end += match[0].length;
 
             if (prefix)
@@ -374,11 +400,11 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             }
             else if (close) {
                 stack.pop();
-                util.assert(stack.length, "Unmatched %] in macro");
+                util.assert(stack.length, /*L*/"Unmatched %] in macro");
             }
             else {
                 let [, flags, name] = /^((?:[a-z]-)*)(.*)/.exec(macro);
-                flags = set(flags);
+                flags = Set(flags);
 
                 let quote = util.identity;
                 if (flags.q)
@@ -386,12 +412,20 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                 if (flags.e)
                     quote = function quote(obj) "";
 
-                if (set.has(defaults, name))
+                if (Set.has(defaults, name))
                     stack.top.elements.push(quote(defaults[name]));
                 else {
-                    stack.top.elements.push(update(
-                        function (obj) obj[name] != null ? quote(obj[name]) : set.has(obj, name) ? "" : unknown(full),
-                        { test: function (obj) obj[name] != null && obj[name] !== false && (!flags.e || obj[name] != "") }));
+                    if (idx) {
+                        idx = Number(idx) - 1;
+                        stack.top.elements.push(update(
+                            function (obj) obj[name] != null && idx in obj[name] ? quote(obj[name][idx]) : Set.has(obj, name) ? "" : unknown(full),
+                            { test: function (obj) obj[name] != null && idx in obj[name] && obj[name][idx] !== false && (!flags.e || obj[name][idx] != "") }));
+                    }
+                    else {
+                        stack.top.elements.push(update(
+                            function (obj) obj[name] != null ? quote(obj[name]) : Set.has(obj, name) ? "" : unknown(full),
+                            { test: function (obj) obj[name] != null && obj[name] !== false && (!flags.e || obj[name] != "") }));
+                    }
 
                     for (let elem in array.iterValues(stack))
                         elem.seen[name] = true;
@@ -401,10 +435,20 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         if (end < macro.length)
             stack.top.elements.push(macro.substr(end));
 
-        util.assert(stack.length === 1, "Unmatched <{ in macro");
+        util.assert(stack.length === 1, /*L*/"Unmatched <{ in macro");
         return stack.top;
     },
 
+    /**
+     * Compiles a CSS spec and XPath pattern matcher based on the given
+     * list. List elements prefixed with "xpath:" are parsed as XPath
+     * patterns, while other elements are parsed as CSS specs. The
+     * returned function will, given a node, return an iterator of all
+     * descendants of that node which match the given specs.
+     *
+     * @param {[string]} list The list of patterns to match.
+     * @returns {function(Node)}
+     */
     compileMatcher: function compileMatcher(list) {
         let xpath = [], css = [];
         for (let elem in values(list))
@@ -428,10 +472,18 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             });
     },
 
-    validateMatcher: function validateMatcher(values) {
+    /**
+     * Validates a list as input for {@link #compileMatcher}. Returns
+     * true if and only if every element of the list is a valid XPath or
+     * CSS selector.
+     *
+     * @param {[string]} list The list of patterns to test
+     * @returns {boolean} True when the patterns are all valid.
+     */
+    validateMatcher: function validateMatcher(list) {
         let evaluator = services.XPathEvaluator();
-        let node = util.xmlToDom(<div/>, document);
-        return this.testValues(values, function (value) {
+        let node = services.XMLDocument();
+        return this.testValues(list, function (value) {
             if (/^xpath:/.test(value))
                 evaluator.createExpression(value.substr(6), util.evaluateXPath.resolver);
             else
@@ -483,10 +535,28 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * Example:
      *     "a{b,c}d" => ["abd", "acd"]
      *
-     * @param {string} pattern The pattern to deglob.
+     * @param {string|[string|Array]} pattern The pattern to deglob.
      * @returns [string] The resulting strings.
      */
     debrace: function debrace(pattern) {
+        if (isArray(pattern)) {
+            let res = [];
+            let rec = function rec(acc) {
+                let vals;
+
+                while (isString(vals = pattern[acc.length]))
+                    acc.push(vals);
+
+                if (acc.length == pattern.length)
+                    res.push(acc.join(""))
+                else
+                    for (let val in values(vals))
+                        rec(acc.concat(val));
+            }
+            rec([]);
+            return res;
+        }
+
         if (pattern.indexOf("{") == -1)
             return [pattern];
 
@@ -501,12 +571,14 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             res.push(pattern.substr(end));
             return res.map(function (s) util.dequote(s, dequote));
         }
-        let patterns = [], res = [];
+        let patterns = [];
         let substrings = split(pattern, /((?:[^\\{]|\\.)*)\{((?:[^\\}]|\\.)*)\}/gy,
             function (match) {
                 patterns.push(split(match[2], /((?:[^\\,]|\\.)*),/gy,
                     null, ",{}"));
             }, "{}");
+
+        let res = [];
         function rec(acc) {
             if (acc.length == patterns.length)
                 res.push(array(substrings).zip(acc).flatten().join(""));
@@ -529,6 +601,16 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
     dequote: function dequote(pattern, chars)
         pattern.replace(/\\(.)/, function (m0, m1) chars.indexOf(m1) >= 0 ? m1 : m0),
 
+    /**
+     * Converts a given DOM Node, Range, or Selection to a string. If
+     * *html* is true, the output is HTML, otherwise it is presentation
+     * text.
+     *
+     * @param {nsIDOMNode | nsIDOMRange | nsISelection} node The node to
+     *      stringify.
+     * @param {boolean} html Whether the output should be HTML rather
+     *      than presentation text.
+     */
     domToString: function (node, html) {
         if (node instanceof Ci.nsISelection && node.isCollapsed)
             return "";
@@ -538,7 +620,8 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             range.selectNode(node);
             node = range;
         }
-        let doc = (node.getRangeAt ? node.getRangeAt(0) : node).startContainer.ownerDocument;
+        let doc = (node.getRangeAt ? node.getRangeAt(0) : node).startContainer;
+        doc = doc.ownerDocument || doc;
 
         let encoder = services.HtmlEncoder();
         encoder.init(doc, "text/unicode", encoder.OutputRaw|encoder.OutputPreformatted);
@@ -564,6 +647,13 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      */
     dump: defineModule.dump,
 
+    /**
+     * Returns a list of reformatted stack frames from
+     * {@see Error#stack}.
+     *
+     * @param {string} stack The stack trace from an Error.
+     * @returns {[string]} The stack frames.
+     */
     stackLines: function (stack) {
         let lines = [];
         let match, re = /([^]*?)@([^@\n]*)(?:\n|$)/g;
@@ -589,7 +679,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * The set of input element type attribute values that mark the element as
      * an editable field.
      */
-    editableInputs: set(["date", "datetime", "datetime-local", "email", "file",
+    editableInputs: Set(["date", "datetime", "datetime-local", "email", "file",
                          "month", "number", "password", "range", "search",
                          "tel", "text", "time", "url", "week"]),
 
@@ -738,12 +828,12 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         [hours, minutes]   = div(minutes, 60);
         [days, hours]      = div(hours,   24);
         if (days)
-            return days + " days " + hours + " hours"
+            return /*L*/days + " days " + hours + " hours"
         if (hours)
-            return hours + "h " + minutes + "m";
+            return /*L*/hours + "h " + minutes + "m";
         if (minutes)
-            return minutes + ":" + pad(2, seconds);
-        return seconds + "s";
+            return /*L*/minutes + ":" + pad(2, seconds);
+        return /*L*/seconds + "s";
     },
 
     /**
@@ -758,12 +848,12 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                 uri = util.newURI(util.fixURI(uri));
 
             if (uri instanceof Ci.nsIFileURL)
-                return File(uri.QueryInterface(Ci.nsIFileURL).file);
+                return File(uri.file);
 
             let channel = services.io.newChannelFromURI(uri);
             channel.cancel(Cr.NS_BINDING_ABORTED);
             if (channel instanceof Ci.nsIFileChannel)
-                return File(channel.QueryInterface(Ci.nsIFileChannel).file);
+                return File(channel.file);
         }
         catch (e) {}
         return null;
@@ -826,7 +916,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             return xmlhttp;
         }
         catch (e) {
-            util.dactyl.log("Error opening " + String.quote(url) + ": " + e, 1);
+            util.dactyl.log(_("error.cantOpen", String.quote(url), e), 1);
             return null;
         }
     },
@@ -939,6 +1029,35 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         }
     },
 
+    // ripped from Firefox; modified
+    unsafeURI: Class.memoize(function () util.regexp(String.replace(<![CDATA[
+            [
+                \s
+                // Invisible characters (bug 452979)
+                U001C U001D U001E U001F // file/group/record/unit separator
+                U00AD // Soft hyphen
+                UFEFF // BOM
+                U2060 // Word joiner
+                U2062 U2063 // Invisible times/separator
+                U200B UFFFC // Zero-width space/no-break space
+
+                // Bidi formatting characters. (RFC 3987 sections 3.2 and 4.1 paragraph 6)
+                U200E U200F U202A U202B U202C U202D U202E
+            ]
+        ]]>, /U/g, "\\u"),
+        "gx")),
+    losslessDecodeURI: function losslessDecodeURI(url) {
+        return url.split("%25").map(function (url) {
+                // Non-UTF-8 compliant URLs cause "malformed URI sequence" errors.
+                try {
+                    return decodeURI(url).replace(this.unsafeURI, encodeURIComponent);
+                }
+                catch (e) {
+                    return url;
+                }
+            }, this).join("%25");
+    },
+
     /**
      * Returns an XPath union expression constructed from the specified node
      * tests. An expression is built with node tests for both the null and
@@ -949,10 +1068,27 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      */
     makeXPath: function makeXPath(nodes) {
         return array(nodes).map(util.debrace).flatten()
-                           .map(function (node) [node, "xhtml:" + node]).flatten()
+                           .map(function (node) /^[a-z]+:/.test(node) ? node : [node, "xhtml:" + node]).flatten()
                            .map(function (node) "//" + node).join(" | ");
     },
 
+    /**
+     * Creates a DTD fragment from the given object. Each property of
+     * the object is converted to an ENTITY declaration. SGML special
+     * characters other than ' and % are left intact.
+     *
+     * @param {object} obj The object to convert.
+     * @returns {string} The DTD fragment containing entity declaration
+     *      for *obj*.
+     */
+    makeDTD: let (map = { "'": "&apos;", '"': "&quot;", "%": "&#x25;", "&": "&amp;", "<": "&lt;", ">": "&gt;" })
+        function makeDTD(obj) iter(obj)
+          .map(function ([k, v]) ["<!ENTITY ", k, " '", String.replace(v == null ? "null" : typeof v == "xml" ? v.toXMLString() : v,
+                                                                       typeof v == "xml" ? /['%]/g : /['"%&<>]/g,
+                                                                       function (m) map[m]),
+                                  "'>"].join(""))
+          .join("\n"),
+
     map: deprecated("iter.map", function map(obj, fn, self) iter(obj).map(fn, self).toArray()),
     writeToClipboard: deprecated("dactyl.clipboardWrite", function writeToClipboard(str, verbose) util.dactyl.clipboardWrite(str, verbose)),
     readFromClipboard: deprecated("dactyl.clipboardRead", function readFromClipboard() util.dactyl.clipboardRead(false)),
@@ -964,7 +1100,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      * @returns {nsIURI}
      */
     // FIXME: createURI needed too?
-    newURI: function (uri, charset, base) services.io.newURI(uri, charset, base),
+    newURI: function newURI(uri, charset, base) this.withProperErrors("newURI", services.io, uri, charset, base),
 
     /**
      * Removes leading garbage prepended to URIs by the subscript
@@ -1029,7 +1165,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                     }</span>;
 
                 let tag = "<" + [namespaced(elem)].concat(
-                    [namespaced(a) + "=" +  template.highlight(a.value, true)
+                    [namespaced(a) + "=" + template.highlight(a.value, true)
                      for ([i, a] in array.iterItems(elem.attributes))]).join(" ");
                 return tag + (!hasChildren ? "/>" : ">...</" + namespaced(elem) + ">");
             }
@@ -1090,13 +1226,13 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
     },
 
     observers: {
-        "dactyl-cleanup-modules": function () {
-            defineModule.loadLog.push("dactyl: util: observe: dactyl-cleanup-modules");
+        "dactyl-cleanup-modules": function (subject, reason) {
+            defineModule.loadLog.push("dactyl: util: observe: dactyl-cleanup-modules " + reason);
 
             for (let module in values(defineModule.modules))
                 if (module.cleanup) {
                     util.dump("cleanup: " + module.constructor.className);
-                    util.trapErrors(module.cleanup, module);
+                    util.trapErrors(module.cleanup, module, reason);
                 }
 
             JSMLoader.cleanup();
@@ -1120,6 +1256,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         "dactyl-purge": function () {
             this.rehashing = 1;
         },
+
         "toplevel-window-ready": function (window, data) {
             window.addEventListener("DOMContentLoaded", wrapCallback(function listener(event) {
                 if (event.originalTarget === window.document) {
@@ -1199,6 +1336,20 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                 }), true);
     },
 
+    /**
+     * Overlays an object with the given property overrides. Each
+     * property in *overrides* is added to *object*, replacing any
+     * original value. Functions in *overrides* are augmented with the
+     * new properties *super*, *supercall*, and *superapply*, in the
+     * same manner as class methods, so that they man call their
+     * overridden counterparts.
+     *
+     * @param {object} object The object to overlay.
+     * @param {object} overrides An object containing properties to
+     *      override.
+     * @returns {function} A function which, when called, will remove
+     *      the overlay.
+     */
     overlayObject: function (object, overrides) {
         let original = Object.create(object);
         overrides = update(Object.create(original), overrides);
@@ -1208,24 +1359,26 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             if (desc.value instanceof Class.Property)
                 desc = desc.value.init(k) || desc.value;
 
-            for (let obj = object; obj && !orig; obj = Object.getPrototypeOf(obj))
-                if (orig = Object.getOwnPropertyDescriptor(obj, k))
-                    Object.defineProperty(original, k, orig);
+            if (k in object) {
+                for (let obj = object; obj && !orig; obj = Object.getPrototypeOf(obj))
+                    if (orig = Object.getOwnPropertyDescriptor(obj, k))
+                        Object.defineProperty(original, k, orig);
+
+                if (!orig)
+                    if (orig = Object.getPropertyDescriptor(object, k))
+                        Object.defineProperty(original, k, orig);
+            }
 
             // Guard against horrible add-ons that use eval-based monkey
             // patching.
+            let value = desc.value;
             if (callable(desc.value)) {
-                let value = desc.value;
-
-                let sentinel = "(function DactylOverlay() {}())"
-                value.toString = function toString() toString.toString.call(this).replace(/\}?$/, sentinel + "; $&");
-                value.toSource = function toSource() toString.toSource.call(this).replace(/\}?$/, sentinel + "; $&");
 
                 delete desc.value;
                 delete desc.writable;
                 desc.get = function get() value;
                 desc.set = function set(val) {
-                    if (String(val).indexOf(sentinel) < 0)
+                    if (!callable(val) || Function.prototype.toString(val).indexOf(sentinel) < 0)
                         Class.replaceProperty(this, k, val);
                     else {
                         let package_ = util.newURI(util.fixURI(Components.stack.caller.filename)).host;
@@ -1235,12 +1388,37 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
                 };
             }
 
-            Object.defineProperty(object, k, desc);
+            try {
+                Object.defineProperty(object, k, desc);
+
+                if (callable(value)) {
+                    let sentinel = "(function DactylOverlay() {}())"
+                    value.toString = function toString() toString.toString.call(this).replace(/\}?$/, sentinel + "; $&");
+                    value.toSource = function toSource() toSource.toSource.call(this).replace(/\}?$/, sentinel + "; $&");
+                }
+            }
+            catch (e) {
+                try {
+                    if (value) {
+                        object[k] = value;
+                        return;
+                    }
+                }
+                catch (f) {}
+                util.reportError(e);
+            }
         }, this);
 
         return function unwrap() {
             for each (let k in Object.getOwnPropertyNames(original))
-                Object.defineProperty(object, k, Object.getOwnPropertyDescriptor(original, k));
+                if (Object.getOwnPropertyDescriptor(object, k).configurable)
+                    Object.defineProperty(object, k, Object.getOwnPropertyDescriptor(original, k));
+                else {
+                    try {
+                        object[k] = original[k];
+                    }
+                    catch (e) {}
+                }
         };
     },
 
@@ -1305,17 +1483,20 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         if (field instanceof Ci.nsIDOMHTMLInputElement && field.type == "submit")
             elems.push(encode(field.name, field.value));
 
-        for (let [, elem] in iter(form.elements)) {
-            if (set.has(util.editableInputs, elem.type)
-                    || /^(?:hidden|textarea)$/.test(elem.type)
-                    || elem.checked && /^(?:checkbox|radio)$/.test(elem.type))
-                elems.push(encode(elem.name, elem.value, elem === field));
-            else if (elem instanceof Ci.nsIDOMHTMLSelectElement) {
-                for (let [, opt] in Iterator(elem.options))
-                    if (opt.selected)
-                        elems.push(encode(elem.name, opt.value));
+        for (let [, elem] in iter(form.elements))
+            if (elem.name && !elem.disabled) {
+                if (Set.has(util.editableInputs, elem.type)
+                        || /^(?:hidden|textarea)$/.test(elem.type)
+                        || elem.type == "submit" && elem == field
+                        || elem.checked && /^(?:checkbox|radio)$/.test(elem.type))
+                    elems.push(encode(elem.name, elem.value, elem === field));
+                else if (elem instanceof Ci.nsIDOMHTMLSelectElement) {
+                    for (let [, opt] in Iterator(elem.options))
+                        if (opt.selected)
+                            elems.push(encode(elem.name, opt.value));
+                }
             }
-        }
+
         if (post)
             return [url, elems.join('&'), charset, elems];
         return [url + "?" + elems.join('&'), null, charset, elems];
@@ -1400,7 +1581,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
 
         // Replace replacement <tokens>.
         if (tokens)
-            expr = String.replace(expr, /(\(?P)?<(\w+)>/g, function (m, n1, n2) !n1 && set.has(tokens, n2) ? tokens[n2].dactylSource || tokens[n2].source || tokens[n2] : m);
+            expr = String.replace(expr, /(\(?P)?<(\w+)>/g, function (m, n1, n2) !n1 && Set.has(tokens, n2) ? tokens[n2].dactylSource || tokens[n2].source || tokens[n2] : m);
 
         // Strip comments and white space.
         if (/x/.test(flags))
@@ -1471,9 +1652,14 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         }())
     }),
 
+    /**
+     * Reloads dactyl in entirety by disabling the add-on and
+     * re-enabling it.
+     */
     rehash: function (args) {
-        JSMLoader.commandlineArgs = args;
+        storage.session.commandlineArgs = args;
         this.timeout(function () {
+            services.observer.notifyObservers(null, "startupcache-invalidate", "");
             this.rehashing = true;
             let addon = config.addon;
             addon.userDisabled = true;
@@ -1484,6 +1670,11 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
     errorCount: 0,
     errors: Class.memoize(function () []),
     maxErrors: 15,
+    /**
+     * Reports an error to the Error Console and the standard output,
+     * along with a stack trace and other relevant information. The
+     * error is appended to {@see #errors}.
+     */
     reportError: function (error) {
         if (error.noTrace)
             return;
@@ -1541,7 +1732,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         catch (e) {}
 
         let ary = host.split(".");
-        ary = [ary.slice(i).join(".") for (i in util.range(ary.length - 1, 0, -1))];
+        ary = [ary.slice(i).join(".") for (i in util.range(ary.length, 0, -1))];
         return ary.filter(function (h) h.length >= base.length);
     },
 
@@ -1586,20 +1777,6 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         return true;
     },
 
-    highlightFilter: function highlightFilter(str, filter, highlight) {
-        return this.highlightSubstrings(str, (function () {
-            if (filter.length == 0)
-                return;
-            let lcstr = String.toLowerCase(str);
-            let lcfilter = filter.toLowerCase();
-            let start = 0;
-            while ((start = lcstr.indexOf(lcfilter, start)) > -1) {
-                yield [start, filter.length];
-                start += filter.length;
-            }
-        })(), highlight || template.filter);
-    },
-
     /**
      * Behaves like String.split, except that when *limit* is reached,
      * the trailing element contains the entire trailing portion of the
@@ -1658,6 +1835,20 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
     },
 
     yielders: 0,
+    /**
+     * Yields execution to the next event in the current thread's event
+     * queue. This is a potentially dangerous operation, since any
+     * yielders higher in the event stack will prevent execution from
+     * returning to the caller until they have finished their wait. The
+     * potential for deadlock is high.
+     *
+     * @param {boolean} flush If true, flush all events in the event
+     *      queue before returning. Otherwise, wait for an event to
+     *      process before proceeding.
+     * @param {boolean} interruptable If true, this yield may be
+     *      interrupted by pressing <C-c>, in which case,
+     *      Error("Interrupted") will be thrown.
+     */
     threadYield: function (flush, interruptable) {
         this.yielders++;
         try {
@@ -1667,7 +1858,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             do {
                 mainThread.processNextEvent(!flush);
                 if (util.interrupted)
-                    throw new Error("Interrupted");
+                    throw Error("Interrupted");
             }
             while (flush === true && mainThread.hasPendingEvents());
         }
@@ -1676,6 +1867,19 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         }
     },
 
+    /**
+     * Waits for the function *test* to return true, or *timeout*
+     * milliseconds to expire.
+     *
+     * @param {function} test The predicate on which to wait.
+     * @param {object} self The 'this' object for *test*.
+     * @param {Number} timeout The maximum number of milliseconds to
+     *      wait.
+     *      @optional
+     * @param {boolean} interruptable If true, may be interrupted by
+     *      pressing <C-c>, in which case, Error("Interrupted") will be
+     *      thrown.
+     */
     waitFor: function waitFor(test, self, timeout, interruptable) {
         let end = timeout && Date.now() + timeout, result;
 
@@ -1690,6 +1894,23 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         return result;
     },
 
+    /**
+     * Makes the passed function yieldable. Each time the function calls
+     * yield, execution is suspended for the yielded number of
+     * milliseconds.
+     *
+     * Example:
+     *      let func = yieldable(function () {
+     *          util.dump(Date.now()); // 0
+     *          yield 1500;
+     *          util.dump(Date.now()); // 1500
+     *      });
+     *      func();
+     *
+     * @param {function} func The function to mangle.
+     * @returns {function} A new function which may not execute
+     *      synchronously.
+     */
     yieldable: function yieldable(func)
         function magic() {
             let gen = func.apply(this, arguments);
@@ -1701,6 +1922,16 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             })();
         },
 
+    /**
+     * Wraps a callback function such that its errors are not lost. This
+     * is useful for DOM event listeners, which ordinarily eat errors.
+     * The passed function has the property *wrapper* set to the new
+     * wrapper function, while the wrapper has the property *wrapped*
+     * set to the original callback.
+     *
+     * @param {function} callback The callback to wrap.
+     * @returns {function}
+     */
     wrapCallback: wrapCallback,
 
     /**
@@ -1722,7 +1953,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      */
     trapErrors: function trapErrors(func, self) {
         try {
-            if (isString(func))
+            if (!callable(func))
                 func = self[func];
             return func.apply(self || this, Array.slice(arguments, 2));
         }
@@ -1732,6 +1963,15 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         }
     },
 
+    /**
+     * Returns the file path of a given *url*, for debugging purposes.
+     * If *url* points to a file (even if indirectly), the native
+     * filesystem path is returned. Otherwise, the URL itself is
+     * returned.
+     *
+     * @param {string} url The URL to mangle.
+     * @returns {string} The path to the file.
+     */
     urlPath: function urlPath(url) {
         try {
             return util.getFile(url).path;
@@ -1741,18 +1981,33 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         }
     },
 
+    /**
+     * Returns a list of all domains and subdomains of documents in the
+     * given window and all of its descendant frames.
+     *
+     * @param {nsIDOMWindow} win The window for which to find domains.
+     * @returns {[string]} The visible domains.
+     */
     visibleHosts: function (win) {
         let res = [], seen = {};
         (function rec(frame) {
             try {
-                res = res.concat(util.subdomains(frame.location.host));
+                if (frame.location.hostname)
+                    res = res.concat(util.subdomains(frame.location.hostname));
             }
             catch (e) {}
             Array.forEach(frame.frames, rec);
         })(win);
-        return res.filter(function (h) !set.add(seen, h));
+        return res.filter(function (h) !Set.add(seen, h));
     },
 
+    /**
+     * Returns a list of URIs of documents in the given window and all
+     * of its descendant frames.
+     *
+     * @param {nsIDOMWindow} win The window for which to find URIs.
+     * @returns {[nsIURI]} The visible URIs.
+     */
     visibleURIs: function (win) {
         let res = [], seen = {};
         (function rec(frame) {
@@ -1762,7 +2017,24 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             catch (e) {}
             Array.forEach(frame.frames, rec);
         })(win);
-        return res.filter(function (h) !set.add(seen, h.spec));
+        return res.filter(function (h) !Set.add(seen, h.spec));
+    },
+
+    /**
+     * Wraps native exceptions thrown by the called function so that a
+     * proper stack trace may be retrieved from them.
+     *
+     * @param {function|string} meth The method to call.
+     * @param {object} self The 'this' object of the method.
+     * @param ... Arguments to pass to *meth*.
+     */
+    withProperErrors: function withProperErrors(meth, self) {
+        try {
+            return (callable(meth) ? meth : self[meth]).apply(self, Array.slice(arguments, withProperErrors.length));
+        }
+        catch (e) {
+            throw e.stack ? e : Error(e);
+        }
     },
 
     /**
index 57b01d3cee4b789776b6641f7e7b9cf7562cfa49..f4db095c309284542d3473cd0a1553300854e196 100644 (file)
@@ -7,8 +7,13 @@
     -moz-binding: url(resource://dactyl-content/bindings.xml#frame) !important;
 }
 
+[dactyl|modifiable][dactyl|modifiable] {
+    -moz-user-input: enabled !important;
+}
+
 [dactyl|highlight~=hints] {
     -moz-binding: url(resource://dactyl-content/bindings.xml#hints) !important;
+    position: static !important;
 }
 
 [dactyl|highlight~=HintImage],
index 9b2a20e74fc4f8de5000cb77b703fd91142e95f5..5f7bda5a4b50d4462a2399f9615c09a1860ddf1c 100644 (file)
@@ -301,7 +301,7 @@ Controller.prototype = {
     }),
 
     /**
-     * Returns dactyl to normal mode.
+     * Returns dactyl to Normal mode.
      */
     setNormalMode: wrapAssertNoErrors(function () {
         // XXX: Normal mode test
@@ -313,9 +313,9 @@ Controller.prototype = {
         utils.assert("dactyl.setNormalMode", this.modules.modes.stack.length == 1,
                      "Failed to return to Normal mode");
 
-        this.assertMessageWindowOpen(false, "Returning to normal mode: Multi-line output not closed");
-        this.assertMessageLine(function (msg) !msg, "Returning to normal mode: Message not cleared");
-    }, "Returning to normal mode"),
+        this.assertMessageWindowOpen(false, "Returning to Normal mode: Multi-line output not closed");
+        this.assertMessageLine(function (msg) !msg, "Returning to Normal mode: Message not cleared");
+    }, "Returning to Normal mode"),
 
     /**
      * Returns dactyl to Ex mode.
index 59724b559a9c54c898fea7c9d76281e34b6bfebd..c65365acf0da90d6b110ef618283d886b01e6af1 100644 (file)
@@ -47,12 +47,22 @@ var tests = {
     "!": {
         multiOutput: ["echo foo"]
     },
+    get Clistkeys() this.listcommands,
+    Cmap: {},
+    Cnoremap: {},
+    Cunmap: {},
+    get Ilistkeys() this.listcommands,
+    Imap: {},
+    Inoremap: {},
+    Iunmap: {},
     abbreviate: {
+        error: ["!"],
         someOutput: ["", "abc"],
         noOutput: ["abc def", "-js abc def"],
         completions: ["", "abc ", "-js abc "]
     },
     addons: {
+        error: ["!"],
         multiOutput: ["", "dactyl", "-type=extension", "-type=extension dactyl"],
         completions: [
             "",
@@ -75,6 +85,8 @@ var tests = {
         anyOutput: ["about:pentadactyl"],
         completions: [["", hasItems]]
     },
+    get blistkeys() this.listcommands,
+    bmap: {},
     bmark: {
         singleOutput: ["", "-tags=foo -title=bar -keyword=baz -charset=UTF-8 -post=quux about:pentadactyl"],
         error: ["-tags=foo -title=bar -keyword=baz -charset=nonExistentCharset -post=quux about:pentadactyl"],
@@ -95,6 +107,7 @@ var tests = {
             "-max=1 -keyword=foo -tags=bar -title=baz about:"
         ]
     },
+    bnoremap: {},
     buffer: {
         anyOutput: ["", "1"],
         noOutput: ["!", "! 1"],
@@ -104,15 +117,21 @@ var tests = {
         ]
     },
     buffers: {
+        error: ["!"],
         multiOutput: ["", "1"],
         completions: ["", "1"]
     },
+    bunmap: {},
     cd: {
+        error: ["!"],
         singleOutput: ["", "~/"],
         completions: ["", "~/"]
     },
+    get clistkeys() this.listcommands,
+    cmap: {},
+    cnoremap: {},
     colorscheme: {
-        error: ["", "some-nonexistent-scheme"]
+        error: ["!", "", "some-nonexistent-scheme"]
     },
     command: {
         init: ["delc!"],
@@ -131,15 +150,22 @@ var tests = {
             ["-group=user ", hasItems]
         ]
     },
-    contexts: {}, // Not testable in this manner
+    completions: {
+        error: ["!", ""]
+    },
+    contexts: { // Not testable in this manner
+        error: ["!"]
+    },
     cookies: {
         anyOutput: ["dactyl.sf.net", "dactyl.sf.net list"],
-        error: [""],
+        error: ["!", ""],
         completions: [
             "",
             ["dactyl.sf.net ", hasItems]
         ]
     },
+    cunabbreviate: {},
+    cunmap: {},
     delbmarks: { anyOutput: ["", "about:pentadactyl"] },
     delcommand: [
         {
@@ -157,8 +183,12 @@ var tests = {
             error: ["foo"]
         }
     ],
+    delgroup: {
+        error: ["", "! foo", "builtin"],
+        completions: [""]
+    },
     delmacros: {
-        error: [""],
+        error: ["", "! foo"],
         noOutput: ["x"],
         completions: ["", "x"]
     },
@@ -168,17 +198,27 @@ var tests = {
         completions: ["", "-name=", "-name=foo ", "-index=", "-index="]
     },
     dialog: {
+        error: ["!", ""],
         // Skip implementation for now
         completions: [
             ["", hasntNullItems]
         ]
     },
-    doautoall: {}, // Skip for now
-    doautocmd: {}, // Skip for now
+    dlclear: {
+        error: ["!"]
+    },
+    doautoall: {
+        error: ["!"]
+    },
+    doautocmd: {
+        error: ["!"]
+    },
     downloads: {
+        error: ["!"],
         multiOutput: ["", "dactyl", "dactyl"]
     },
     echo: {
+        error: ["!"],
         singleOutput: [
             ["' - '", " - "]
         ],
@@ -199,25 +239,35 @@ var tests = {
         __proto__: this.echo,
     }),
     get echomsg() this.echo,
-    else: {}, // Skip for now
-    elseif: {}, // Skip for now
+    else: {
+        error: ["!", "foo"]
+    },
+    elseif: {
+        error: ["!", ""]
+    },
     emenu: {
         noOutput: ["View.Zoom.Zoom In", "View.Zoom.Zoom Out"],
-        error: [""],
+        error: ["!", ""],
         completions: [
             ["", hasItems],
             ["View.", hasItems]
         ]
     },
-    endif: {}, // Skip for now
+    endif: {
+        error: ["!", "foo"]
+    },
     execute: {
+        error: ["!"],
         noOutput: ["", "'js " + "".quote() + "'"],
         someOutput: ["'ls'"],
         completions: [["", hasItems]]
     },
+    exit: {
+        error: ["foo"]
+    },
     extadd: {
         completions: [["", hasItems]],
-        error: [""]
+        error: ["!", ""]
     },
     extdelete: {
         completions: [["", hasItems]],
@@ -239,12 +289,14 @@ var tests = {
         noOutput: ["<Esc>"],
         error: [""]
     },
-    finish: { noOutput: [""] },
+    finish: {
+        error: ["!", "foo"],
+        noOutput: [""]
+    },
     forward: { noOutput: [""] },
-    frameonly: { noOutput: [""] },
-    delgroup: {
-        error: ["builtin"],
-        completions: [""]
+    frameonly: {
+        error: ["!", "foo"],
+        noOutput: [""]
     },
     group: {
         multiOutput: [""],
@@ -263,6 +315,7 @@ var tests = {
     },
     hardcopy: {}, // Skip for now
     help: {
+        error: ["!"],
         noOutput: ["", "intro"],
         cleanup: ["tabdelete", "tabdelete"],
         completions: [
@@ -272,6 +325,7 @@ var tests = {
     },
     get helpall() this.help,
     highlight: {
+        error: ["!"],
         multiOutput: ["", "Help"],
         noOutput: [
             "Help foo: bar;",
@@ -299,9 +353,17 @@ var tests = {
             "-sort=+date about:"
         ]
     },
-    if: {}, // Skip for now
+    if: {
+        error: ["!", ""],
+    },
+    iabbreviate: {},
+    get ilistkeys() this.listcommands,
+    imap: {},
+    inoremap: {},
+    iunabbreviate: {},
+    iunmap: {},
     javascript: {
-        noOutput: ["''", "'\\n'", "<pre>foo bar</pre>", "window"],
+        noOutput: ["''", "'\\n'", "<pre>foo bar</pre>", "window", "<<EOF\n''\nEOF"],
         completions: [
             ["", hasItems],
             ["window", hasItems],
@@ -325,15 +387,20 @@ var tests = {
         ]
     },
     jumps: {
+        error: ["!", "foo"],
         multiOutput: [""]
     },
     keepalt: {
-        error: [""],
+        error: ["!", "", "some-nonexistent-command"],
         noOutput: ["js ''"],
-        anyOutput: ["echo 'foo'"]
+        anyOutput: ["echo 'foo'"],
+        completions: [["", hasItems]]
+    },
+    let: {
+        error: ["!"]
     },
-    let: {}, // Deprecated. Fuck it.
     listcommands: {
+        error: ["!"],
         anyOutput: ["", "in"],
         completions: [
             ["", hasItems],
@@ -344,10 +411,12 @@ var tests = {
     get listoptions() this.listcommands,
     loadplugins: {},
     macros: {
+        error: ["!"],
         multiOutput: [""],
         completions: [""]
     },
     map: {
+        error: ["!"],
         init: ["unmap!"],
         anyOutput: [""],
         singleOutput: ["i"],
@@ -378,21 +447,22 @@ var tests = {
         ]
     },
     mark: {
-        error: ["", "#", "xy"],
+        error: ["!", "", "#", "xy"],
         noOutput: ["y"],
         completions: [""]
     },
     marks: {
         init: ["delmarks q"],
         multiOutput: ["", "y"],
-        error: ["q", "#"],
+        error: ["!", "q", "#"],
         completions: [""]
     },
     messages: {
+        error: ["!", "foo"],
         anyOutput: ["messages"]
     },
     messclear: {
-        error: ["q"],
+        error: ["!", "foo"],
         noOutput: [""]
     },
     mkpentadactylrc: {
@@ -420,12 +490,26 @@ var tests = {
         ],
         cleanup: ["silent !rm -r some-nonexistent-pentadactyl-dir/"]
     },
+    get mlistkeys() this.listcommands,
+    mmap: {},
+    mnoremap: {},
+    munmap: {},
+    get nlistkeys() this.listcommands,
+    nmap: {},
+    nnoremap: {},
+    nohlfind: {
+        error: ["!", "foo"]
+    },
+    noremap: {},
     normal: {
+        error: [""],
         noOutput: ["<Nop>"],
         singleOutput: ["<C-g>"],
         multiOutput: ["g<C-g>"]
     },
+    nunmap: {},
     open: {
+        error: ["!"],
         noOutput: ["about:blank | about:home"],
         completions: [
             ["", hasItems],
@@ -442,13 +526,18 @@ var tests = {
     pageinfo: {
         multiOutput: ["", "fgm"],
         completions: [["", hasItems]],
-        error: ["abcdefghijklmnopqrstuvwxyz", "f g m"]
+        error: ["!", "abcdefghijklmnopqrstuvwxyz", "f g m"]
     },
     pagestyle: {
+        error: ["!"],
         completions: [""]
     },
-    preferences: {}, // Skip for now
+    pintab: {},
+    preferences: {
+        error: ["foo"]
+    },
     pwd: {
+        error: ["!", "foo"],
         singleOutput: [""]
     },
     qmark: {
@@ -456,13 +545,16 @@ var tests = {
             "m",
             "m foo bar"
         ],
-        error: ["", "#"],
+        error: ["!", "", "#"],
         completions: [
             ["", hasItems],
             ["m ", hasItems]
         ]
     },
     qmarks: [
+        {
+            error: ["!"]
+        },
         {
             init: ["delqmarks a-zA-Z0-9"],
             error: ["", "x"],
@@ -473,19 +565,30 @@ var tests = {
             completions: [["", hasItems]]
         }
     ],
-    quit: {}, // Skip for now
-    quitall: {}, // Skip for now
+    quit: {
+        error: ["foo"]
+    },
+    quitall: {
+        error: ["!", "foo"]
+    },
     redraw: {
+        error: ["!", "foo"],
         noOutput: [""]
     },
-    rehash: {}, // Skip for now
+    rehash: {
+        error: ["!"]
+    },
     reload: {
+        error: ["foo"],
         noOutput: [""]
     },
     reloadall: {
+        error: ["foo"],
         noOutput: [""]
     },
-    restart: {}, // Skip
+    restart: {
+        error: ["!", "foo"]
+    },
     runtime: {
         init: [
             "js File('~/.pentadactyl/some-nonexistent/good.css').write('')",
@@ -501,6 +604,7 @@ var tests = {
             "some-nonexistent/good.penta"
         ],
         error: [
+            "",
             "some-nonexistent/bad.js",
             "some-nonexistent/bad.penta"
         ],
@@ -539,13 +643,16 @@ var tests = {
     },
     saveas: {},
     sbclose: {
+        error: ["!", "foo"],
         noOutput: [""]
     },
-    scriptnames: {},
+    scriptnames: {
+        error: ["!", "foo"]
+    },
     set: {
         multiOutput: [
             "vb?", "cpt?", "messages?", "titlestring?", "au?", "eht?",
-            "cpt", "messages", "titlestring", "au", "eht"
+            "cpt", "messages", "titlestring", "au", "eht", "! "
         ],
         noOutput: ["vb", "novb"],
         completions: [
@@ -595,6 +702,7 @@ var tests = {
         ]
     },
     silent: {
+        error: ["!"],
         noOutput: [
             "echo 'foo'",
             "echo " + "foo\nbar".quote(),
@@ -613,6 +721,7 @@ var tests = {
             ".pentadactyl/some-nonexistent/good.penta"
         ],
         error: [
+            "",
             ".pentadactyl/some-nonexistent/really-nonexistent.js",
             "~/.pentadactyl/some-nonexistent/bad.js",
             "~/.pentadactyl/some-nonexistent/bad.penta",
@@ -629,9 +738,16 @@ var tests = {
             ["resource://dactyl/", hasItems]
         ]
     }),
-    stop: { noOutput: [""] },
-    stopall: { noOutput: [""] },
+    stop: {
+        error: ["!", "foo"],
+        noOutput: [""]
+    },
+    stopall: {
+        error: ["!", "foo"],
+        noOutput: [""]
+    },
     style: {
+        error: ["!"],
         cleanup: ["delstyle -n foo"],
         noOutput: [
             "-name=foo http://does.not.exist/* div { display: inline; }",
@@ -671,19 +787,59 @@ var tests = {
             ["-index=", hasNItems(2)]
         ]
     },
-    tab: {},
-    tabattach: {},
-    tabdetach: {},
-    tabdo: {},
-    tabduplicate: {},
-    tablast: {},
-    tabmove: {},
-    tabnext: {},
-    tabonly: {},
+    tab: {
+        error: ["!", "", "some-nonexistent-command"],
+        noOutput: ["js ''"],
+        anyOutput: ["echo 'foo'"],
+        completions: [["", hasItems]]
+    },
+    tabattach: {
+        error: ["!", ""]
+    },
+    tabdetach: {
+        error: ["!", "foo"]
+    },
+    tabdo: {
+        error: ["!", "", "some-nonexistent-command"],
+        noOutput: ["js ''"],
+        anyOutput: ["echo 'foo'"],
+        completions: [["", hasItems]]
+    },
+    tabduplicate: {
+        error: ["foo"]
+    },
+    tablast: {
+        error: ["!", "foo"]
+    },
+    tabmove: {
+        error: [""],
+        noOutput: ["1", "$", "999", "-1", "+1", "! +1", "! -1", "-999", "+999", "! +999", "! -999"],
+        completions: [
+            ["", hasItems],
+            ["1", hasItems]
+        ]
+    },
+    tabnext: {
+        error: ["!", "foo"]
+    },
+    tabonly: {
+        error: ["!", "foo"]
+    },
     tabopen: {},
-    tabprevious: {},
-    tabrewind: {},
-    time: {},
+    tabprevious: {
+        error: ["!", "foo"]
+    },
+    tabrewind: {
+        error: ["!", "foo"]
+    },
+    time: {
+        error: ["", ":some-nonexistent-command"/*, "some_nonexistent_reference"*/], // FIXME
+        singleOutput: [":js null", "null"]
+
+    },
+    get tlistkeys() this.listcommands,
+    tmap: {},
+    tnoremap: {},
     toolbarhide: {
         init: [
             ["tbs Navigation Toolbar", toolbarState("#nav-bar", true)],
@@ -694,7 +850,7 @@ var tests = {
             ["Navigation Toolbar", toolbarState("#nav-bar", false)],
             ["Bookmarks Toolbar", toolbarState("#PersonalToolbar", false)]
         ],
-        error: ["", "foo"]
+        error: ["!", "", "foo"]
     },
     toolbarshow: {
         completions: [["", hasItems]],
@@ -702,7 +858,7 @@ var tests = {
             ["Navigation Toolbar", toolbarState("#nav-bar", true)],
             ["Bookmarks Toolbar", toolbarState("#PersonalToolbar", true)]
         ],
-        error: ["", "foo"]
+        error: ["!", "", "foo"]
     },
     toolbartoggle: {
         completions: [["", hasItems]],
@@ -714,15 +870,25 @@ var tests = {
             ["Navigation Toolbar", toolbarState("#nav-bar", false)],
             ["Bookmarks Toolbar", toolbarState("#PersonalToolbar", false)]
         ],
-        error: ["", "foo"]
+        error: ["!", "", "foo"]
     },
+    tunmap: {},
     unabbreviate: {
         noOutput: ["abc", "! "],
         error: [""]
     },
-    undo: {},
-    undoall: {},
-    unlet: {},
+    undo: {
+        error: ["!"]
+    },
+    undoall: {
+        error: ["!", "foo"]
+    },
+    unpintab: {
+        error: ["!"]
+    },
+    unlet: {
+        error: [""],
+    },
     unmap: {
         noOutput: [
             "i",
@@ -738,8 +904,11 @@ var tests = {
             "-group="
         ]
     },
-    verbose: {},
+    verbose: {
+        error: ["!", ""]
+    },
     version: {
+        error: ["foo"],
         multiOutput: [
             ["", function (msg) {
                 var res = /(\w+dactyl) (\S+) \(([\^)]+)\) running on:\nMozilla/;
@@ -748,11 +917,25 @@ var tests = {
         ]
     },
     viewsource: {},
-    winclose: {},
-    window: {},
-    winonly: {},
-    winopen: {},
-    wqall: {},
+    get vlistkeys() this.listcommands,
+    vmap: {},
+    vnoremap: {},
+    vunmap: {},
+    winclose: {
+        error: ["!", "foo"]
+    },
+    window: {
+        error: ["!", ""]
+    },
+    winonly: {
+        error: ["!", "foo"]
+    },
+    winopen: {
+        error: ["!"]
+    },
+    wqall: {
+        error: ["!", "foo"]
+    },
     yank: {
         multiOutput: [
             ["foo".quote(), /foo/],
@@ -760,6 +943,7 @@ var tests = {
             [":addons", /Pentadactyl/]
         ],
         error: [
+            "!", "",
             ":echoerr " + "foo".quote()
         ],
         completions: [
index c268743be3082072e55fcdb128b6773d570d045e..89301320f766f619c95b91b18da0295391df5aaf 100644 (file)
@@ -31,7 +31,7 @@ var options = {};
 
 function testCompleters() {
     for (var option in dactyl.modules.options)
-        for (var [,value] in Iterator([""].concat(options[option.name] || []))) {
+        for (var [, value] in Iterator([""].concat(options[option.name] || []))) {
             dump("OPT COMP " + option.name + " " + value + "\n");
             dactyl.testCompleter(option, "completer", value, "Option '" + option.name + "' completer failed");
         }
index 02702037706bcfbe0f35429d5aaf192652a31006..0213a574dfb222c1025ff61fa143dbd39e8b0e41 100644 (file)
@@ -133,7 +133,7 @@ const Config = Module("config", ConfigBase, {
     },
 
     /*** optional options, there are checked for existence and a fallback provided  ***/
-    features: set(["bookmarks", "hints", "marks", "history", "quickmarks", "session", "tabs", "player"]),
+    features: Set(["bookmarks", "hints", "marks", "history", "quickmarks", "session", "tabs", "player"]),
 
     defaults: {
         guioptions: "bCmprs",
index be884741d19e5637202d34eb7953fc2b3ef5d572..95f165921e2f6cc454aa94c8031c5e35d54f2726 100644 (file)
@@ -22,7 +22,7 @@ const Library = Module("library", {
     /**
      * Returns an array of all the artist names in the main library.
      *
-     * @returns {string[]}
+     * @returns {[string]}
      */
     getArtists: function getArtists() this._toJSArray(this.MAIN_LIBRARY.getDistinctValuesForProperty(SBProperties.artistName)),
 
@@ -32,7 +32,7 @@ const Library = Module("library", {
      * library.
      *
      * @param {string} artist The artist's name.
-     * @returns {string[]}
+     * @returns {[string]}
      */
     getAlbums: function getAlbums(artist) {
         let albums = this._toJSArray(this.MAIN_LIBRARY.getItemsByProperty(SBProperties.artistName, artist))
@@ -46,7 +46,7 @@ const Library = Module("library", {
      *
      * @param {string} artist The artist's name.
      * @param {string} album The album's name.
-     * @returns {string[]}
+     * @returns {[string]}
      */
     getTracks: function getTracks(artist, album) {
         let properties = services.MutablePropertyArray();
index 8eb8f1171092fa8326f05e1b06b6e6dcb3f45b9c..f3b943ac4653147fc2596a1df4c102505cb1aa04 100644 (file)
@@ -44,34 +44,35 @@ const Player = Module("player", {
         onMediacoreEvent: function (event) {
             switch (event.type) {
                 case Ci.sbIMediacoreEvent.BEFORE_TRACK_CHANGE:
-                    dactyl.log("Before track changed: " + event.data);
+                    dactyl.log(_("player.preTrackChange", event.data));
                     autocommands.trigger("TrackChangePre", { track: event.data });
                     break;
                 case Ci.sbIMediacoreEvent.TRACK_CHANGE:
+                    dactyl.log(_("player.trackChanged", event.data));
                     autocommands.trigger("TrackChange", { track: event.data });
                     break;
                 case Ci.sbIMediacoreEvent.BEFORE_VIEW_CHANGE:
-                    dactyl.log("Before view changed: " + event.data);
+                    dactyl.log(_("player.preViewChange", event.data));
                     autocommands.trigger("ViewChangePre", { view: event.data });
                     break;
                 case Ci.sbIMediacoreEvent.VIEW_CHANGE:
-                    dactyl.log("View changed: " + event.data);
+                    dactyl.log(_("player.viewChange", event.data));
                     autocommands.trigger("ViewChange", { view: event.data });
                     break;
                 case Ci.sbIMediacoreEvent.STREAM_START:
-                    dactyl.log("Track started: " + gMM.sequencer.currentItem);
+                    dactyl.log(_("player.trackStart", gMM.sequencer.currentItem));
                     autocommands.trigger("StreamStart", { track: gMM.sequencer.currentItem });
                     break;
                 case Ci.sbIMediacoreEvent.STREAM_PAUSE:
-                    dactyl.log("Track paused: " + gMM.sequencer.currentItem);
+                    dactyl.log(_("player.trackPause", gMM.sequencer.currentItem));
                     autocommands.trigger("StreamPause", { track: gMM.sequencer.currentItem });
                     break;
                 case Ci.sbIMediacoreEvent.STREAM_END:
-                    dactyl.log("Track ended: " + gMM.sequencer.currentItem);
+                    dactyl.log(_("player.trackEnd", gMM.sequencer.currentItem));
                     autocommands.trigger("StreamEnd", { track: gMM.sequencer.currentItem });
                     break;
                 case Ci.sbIMediacoreEvent.STREAM_STOP:
-                    dactyl.log("Track stopped: " + gMM.sequencer.currentItem);
+                    dactyl.log(_("player.trackStop", gMM.sequencer.currentItem));
                     autocommands.trigger("StreamStop", { track: gMM.sequencer.currentItem });
                     break;
             }
@@ -332,7 +333,7 @@ const Player = Module("player", {
     /**
      * Returns an array of all available playlists.
      *
-     * @returns {sbIMediaList[]}
+     * @returns {[sbIMediaList]}
      */
     getPlaylists: function getPlaylists() {
         let mainLibrary = LibraryUtils.mainLibrary;
@@ -366,7 +367,7 @@ const Player = Module("player", {
     /**
      * Returns an array of all available media pages.
      *
-     * @returns {sbIMediaPageInfo[]}
+     * @returns {[sbIMediaPageInfo]}
      */
     getMediaPages: function getMediaPages() {
         let list = SBGetBrowser().currentMediaPage.mediaListView.mediaList;
@@ -375,7 +376,7 @@ const Player = Module("player", {
     },
 
     /**
-     * Loads the the specified media page into *view* with the given *list* of
+     * Loads the specified media page into *view* with the given *list* of
      * media items.
      *
      * @param {sbIMediaPage} page
@@ -660,7 +661,7 @@ const Player = Module("player", {
                 let arg = args[0];
 
                 dactyl.assert(arg, _("error.argumentRequired"));
-                dactyl.assert(/^[+-]?\d+$/.test(arg), _("error.trailing"));
+                dactyl.assert(/^[+-]?\d+$/.test(arg), _("error.trailingCharacters"));
 
                 let level = parseInt(arg, 10) / 100;
 
index fd788b60b360b9aa6bad210b81b4ee2988f896d8..c4d308c8572a53fb170c76e9a8e3a30891c90674 100644 (file)
@@ -7,30 +7,6 @@
     xmlns="&xmlns.dactyl;"
     xmlns:html="&xmlns.html;">
 
-<dl tag="dialog-list" replace="dialog-list">
-    <dt>about</dt>           <dd>About &dactyl.host;</dd>
-    <dt>addons</dt>          <dd>Manage Add-ons</dd>
-    <dt>checkupdates</dt>    <dd>Check for updates</dd>
-    <dt>cleardata</dt>       <dd>Clear private data</dd>
-    <dt>cookies</dt>         <dd>List your cookies</dd>
-    <dt>console</dt>         <dd>JavaScript console</dd>
-    <dt>dominspector</dt>    <dd>DOM Inspector</dd>
-    <dt>downloads</dt>       <dd>Manage Downloads</dd>
-    <dt>jumpto</dt>          <dd>Jump to a media item</dd>
-    <dt>newsmartplaylist</dt><dd>Create a new smart playlist</dd>
-    <dt>openfile</dt>        <dd>Open the file selector dialog</dd>
-    <dt>pagesource</dt>      <dd>View page source</dd>
-    <dt>places</dt>          <dd>Places Organizer: Manage your bookmarks and history</dd>
-    <dt>preferences</dt>     <dd>Show &dactyl.host; preferences dialog</dd>
-    <dt>printsetup</dt>      <dd>Setup the page size and orientation before printing</dd>
-    <dt>print</dt>           <dd>Show print dialog</dd>
-    <dt>saveframe</dt>       <dd>Save frame to disk</dd>
-    <dt>savepage</dt>        <dd>Save page to disk</dd>
-    <dt>searchengines</dt>   <dd>Manage installed search engines</dd>
-    <dt>selectionsource</dt> <dd>View selection source</dd>
-    <dt>subscribe</dt>       <dd>Add a new subscription</dd>
-</dl>
-
 <item replace=":sbcl">
     <tags>:dpcl :dpclose</tags>
     <spec>:dpcl<oa>ose</oa> <a>pane</a></spec>
index 7e49891410c186eca1167a5f05aa62fac0fc018d..e2d3c3611ac5c0004adb24a8239e987c38f32ab2 100644 (file)
@@ -41,7 +41,7 @@
         etc.)
     </li>
     <li>
-        <link topic="cmdline">Command-line mode</link>:
+        <link topic="cmdline">Command Line mode</link>:
         Command-line editing.
     </li>
     <li>
diff --git a/melodactyl/locale/en-US/messages.properties b/melodactyl/locale/en-US/messages.properties
new file mode 100644 (file)
index 0000000..f5ec217
--- /dev/null
@@ -0,0 +1,10 @@
+player.preTrackChange-1 = Before track changed: %S
+player.postTrackChange-1 = Track changed: %S
+player.preViewChange-1 = Before view changed: %S
+player.postViewChange-1 = View changed: %S
+player.trackStart-1 = Track started: %S
+player.trackPause-1 = Track paused: %S
+player.trackEnd-1 = Track ended: %S
+player.trackStop-1 = Track stopped: %S
+
+# vim:se ft=jproperties tw=0:
index 6ebc13d1c08523fbeb9a8eff900d03483cb84738..da3e1e892ee2395d74537f45af3def53f2990843 100644 (file)
@@ -1,8 +1,8 @@
 Developers:
   * Kris Maglione
   * Doug Kearns <dougkearns@gmail.com>
-  * anekos <anekos@snca.net>
-  * teramako <teramako@gmail.com>
+
+Contributers:
   * Štěpán Němec
 
 Inactive/former developers:
@@ -15,6 +15,8 @@ Inactive/former developers:
   * janus_wel <janus.wel.3@gmail.com>
   * Martin Stubenschrott <stubenschrott@vimperator.org>
   * Conrad Irwin
+  * anekos <anekos@snca.net>
+  * teramako <teramako@gmail.com>
 
 Patches (in no special order):
   * Ruud Grosmann ('followhints' option)
index 74afcb9688628817bf6e212c174c08e470d16472..daf282e56f20e80d530ab301328247347d17c61d 100644 (file)
@@ -5,8 +5,8 @@ NAME          = pentadactyl
 FIREFOX      ?= firefox
 HOSTAPP      ?= $(FIREFOX)
 PROFILEPATHS ?= "$$HOME/.mozilla/firefox" \
-               "$$HOME/Library/Mozilla/Firefox" \
-               "$$APPDATA/Mozilla/Firefox" \
-               "$$AppData/Mozilla/Firefox"
+               "$$HOME/Library/Application Support/Firefox" \
+               "$$APPDATA/Mozilla/Firefox/Profiles" \
+               "$$AppData/Mozilla/Firefox/Profiles"
 
 include ../common/Makefile
index aece9125f214da211c233143c7b1942a18d312ac..af99705228447e8fdfec3def18571027bd407176 100644 (file)
@@ -1,4 +1,4 @@
-1.0b6:
+1.0b7pre:
     • Extensive Firefox 4 support, including:
       - Fully restartless. Can now be installed, uninstalled,
         enabled, disabled, and upgraded without restarting Firefox.
@@ -48,8 +48,8 @@
         quotes, i.e., 'fo\o''bar' ⇒ fo\o'bar [b1]
     • The command line is now hidden by default. Added c, C, and M to
       'guioptions'. [b4]
-    • Hint mode improvements, including:
-      - Added g; continued extended hint mode, which allows
+    • Hints mode improvements, including:
+      - Added g; continued extended hints mode, which allows
         selecting multiple hints. Removed ;F. [b1]
       - Hints are now updated after scrolling and window resizing. [b3]
       - ;s now prompts for a filename on the command-line rather
     • The external editor can now be configured to open to a given
       line number and column, used for opening source links and
       editing input fields with i_<C-i>. See :h 'editor'. [b4]
+    • Improved [macro-string] support, including automatic elision
+      of optional elements, and array subscripts. [b4][b7]
     • Mapping changes:
       - It's now possible to map keys in many more modes, including
         Hint, Multi-line Output, and Menu. [b4]
       - Added site-specific mapping groups and related command
         changes. [b6]
       - Added 'timeout' and 'timeoutlen' options. [b6]
+      - Added n_{, n_}, n_[, and n_] mappings. [b7]
       - Added <A-b> to execute a builtin mapping. [b6]
       - Added <A-m>l and <A-m>s to aid in the construction of
         macros. [b6]
         listing keys for modes other than Normal, filtering the output,
         and linking to source code locations). [b4]
       - :downloads now opens a download list in the multi-line output
-        buffer. [b6]
+        buffer. Added -sort flag. [b6][b7]
+      - :style now supports regexp site-filters on Firefox 6+. [b7]
+      - :qa closes only the current window, per Vim. [b7]
+      - Added :exit command. [b7]
+      - Added :dlclear command. [b7]
       - :extensions has been replaced with a more powerful :addons. [b6]
       - :javascript! now opens a Read Eval Print Loop. [b6]
       - Added -arg flag to :map. [b6]
       - Added several new options, including -javascript, to :abbreviate and
         :map. [b2]
       - Added :mksyntax command to auto-generate Vim syntax files. [b4]
+      - Added 's' and 'e' flags to :pageinfo command. [b7]
+      - Added :pintab and :unpintab commands. [b7]
+      - Extension manipulation commands, including :extenable, :extdisable,
+        and :exttoggle, now accept a -types argument. [b7]
+      - :tabmove now moves to the position of tab N, not after it.
+        Without arguments it no longer moves the tab to the end of
+        the list. [b7]
       - :open now only opens files beginning with /, ./, ../, or ~/ [b1]
       - :saveas now provides completions for default file names, and
         automatically chooses a filename when the save target is a
       - CSS property name completion is now available. [b4]
       - :delstyle, :styleenable, :styledisable and :styletoggle accept a !
         to operate on all styles. [b6]
+      - Added Find highlighting group. [b7]
     • IMPORTANT option changes:
       - Option value quoting has changed. List options will
         no longer be split at quoted commas and the option name,
         operators, and = sign may no longer be quoted. This will
         break certain automatically-generated configuration files.
-        See :help
-        [stringlist]. [b2]
+        See :help [stringlist]. [b2]
       - Boolean options no longer accept an argument. [b4]
       - 'cdpath' and 'runtimepath' no longer treat ",,"
         specially. Use "." instead. [b2]
       - 'extendedhinttags' is now a [regexpmap] rather than a
         string. [b2]
-      - 'guioptions' default value has changed. [b4]
+      - 'guioptions' default value has changed. [b4][b7]
       - 'hinttags' and 'extendedhinttags' now treat their values as
         CSS selectors by default. [b6]
       - 'incsearch', 'hlsearch', 'ignorecase', and 'smartcase' have
         'guioptions'. [b4]
       - 'linksearch' has been removed.  The \l search modifier can
         still be used for this purpose. [b4]
-      - 'loadplugins' is now a regexplist option rather than
+      - 'loadplugins' is now a [regexplist] option rather than
         a boolean. [b2]
       - 'mapleader' is now an option rather than a :let
         variable. [b4]
-      - 'passkeys' is now a sitemap with key chain support rather
-        than a regexpmap. [b6]
-      - 'showmode' is now a regexplist. [b6]
-      - 'showstatuslinks' and 'showtabline' are now string options. [b4]
+      - 'passkeys' is now a [sitemap] with key chain support rather
+        than a [regexpmap]. [b6]
+      - 'showmode' is now a [regexplist]. [b6]
+      - 'showstatuslinks' and 'showtabline' are now [string] options. [b4]
     • IMPORTANT: Command script files now use the *.penta file extension. [b2]
     • IMPORTANT: Plugins are now loaded from the 'plugins/'
       directory in 'runtimepath' rather than 'plugin/'. [b1]
       - Added 'autocomplete' option for specifying which completion
         groups should be auto-completed. [b2]
       - Added 'banghist' option. [b1]
+      - Added 'cookies', 'cookieaccept', and 'cookielifetime' options. [b3]
+      - Added 'downloadsort' option. [b7]
       - Replaced 'focuscontent' with 'strictfocus'. [b1]
+      - Made 'strictfocus' a [sitemap]. [b7]
       - 'complete' now defaults to "slf" but file completion only
         triggers when the URL begins as above. [b1]
+      - Added 'jumptags' option. [b7]
+      - Added 's' flag to 'pageinfo' and changed default value. [b7]
       - Added 'passkeys' option. [b3]
+      - Added 'passunknown' option. [b7]
       - Changed 'urlseparator' default value to "|". [b3]
       - Added "passwords" and "venkman" dialogs to :dialog. [b2]
+      - Make 'showmode' a [stringlist] option. [b7]
       - Added 'wildanchor' option. [b2]
-      - Added 'cookies', 'cookieaccept', and 'cookielifetime' options. [b3]
     • Added BookmarkChange, BookmarkRemove autocommands. [b2]
     • Removed the :source line at the end of files generated by
       :mkpentadactylrc. [b2]
index 9e11e7253f9aff8ce01c79b16f562832be61e936..809af63aa9e647252150bbab645d7deb85124828 100644 (file)
@@ -2,69 +2,53 @@ Priority list:
 1-9 as in Vim (9 = required for next release, 5 = would be nice, 1 = probably not)
 
 ARCHITECTURE:
-- decide on how to document Arrays in our jsdoc(ish) documentation. Host docs
-  on the website - is there even a documentation tool that can parse our source
-  sensibly?
+- Host docs on the website - is there even a documentation tool that
+  can parse our source sensibly?
 
 - unify the disgusting hodge podge of contract specification styles
 
-REFACTORING:
-- remove unnecessary usage of "self"
-
 BUGS:
-- insert abbreviations broken on <space>
-- :sidebar improvements (:sidebar! Downloads while downloads is open should refocus the sidebar)
 - ;s saves the page rather than the image
 - RC file is sourced once per window
 
 FEATURES:
+9 <C-o>/<C-i> should work as in Vim (i.e., save page positions as well as
+  locations in the history list).
+9 clean up error message codes and document
+9 option groups, including buffer-local and site-specific
+9 proper motion maps
+8 wherever possible: get rid of dialogs and ask console-like dialog questions
+  or write error prompts directly on the webpage or with :echo()
+8 add search capability to MOW
 8 registers
 8 Document Caret and Visual modes.
 8 replace global variables with plugin scoped user options
-8 fix local options
 8 adaptive timeout for auto-completions, :set completions can be updated more often than
   :open foo
-8 use the storage module for autocommands
 8 add support for filename special characters such as %
 8 :redir and 'verbosefile'
 8 middleclick in content == p, and if command line is open, paste there the clipboard buffer
 8 all search commands should start searching from the top of the visible viewport
-8 <C-o>/<C-i> should work as in Vim (i.e., save page positions as well as
-  locations in the history list).
-8 jump to the next heading with ]h, next image ]i, previous textbox [t and so on
-7 clean up error message codes and document
-7 add search capability to MOW
+8 Add information to dactyl/HACKING file about testing and optimization
 7 describe-key command (prompt for a key and display its binding with documentation)
 7 Specify all INSERT mode mappings rather than falling through to the host apps
   mystery meat mappings.
 7 use ctrl-n/p in insert mode for word completion
 7 implement QuickFix window based on ItemList
-7 wherever possible: get rid of dialogs and ask console-like dialog questions
-  or write error prompts directly on the webpage or with :echo()
 7 [d could go to the last domain in the history stack. So if I browse from
   google to another page and click 10 links there, [d would take me back to the google page
   opera's fast forward does something like this
 7 make an option to disable session saving by default when you close Firefox
-7 The output of the pageinfo-command should contain the security-information of ssl-encrypted sites
 7 :grep support (needs location list)
-7 map <A-n/p> in command-line mode to something useful (such as Down/Up) and
-  possibly <C-n/p> to Vim-like behaviour
 6 :mksession
 6 add [count] support to :b* and :tab* commands where missing
 6 check/correct spellings in insert mode with some mappings
 6 add more autocommands (TabClose, TabOpen, TabChanged etc)
 6 Use ctrl-w+j/k/w to switch between sidebar, content, preview window
-6 Command :tags for getting a list of used tags
 6 ;?<hint> should show more information
-6 Add information to dactyl/HACKING file about testing and optimization
 5 when looking at a zoomed out image (because it's large), zi should zoom in
   maybe with this? : http://mxr.mozilla.org/seamonkey/source/content/html/document/public/nsIImageDocument.idl
-5 make a command to search within google search results
-  (http://gadelkareem.com/2007/01/28/using-google-ajax-api-as-an-array/)
-  maybe impossible, needs a per-site key from google
-4 } { should jump to the next paragraph of the page
 3 A format for 'guitablabel' and 'statusline'
 3 add a command-line window (:help cmdline-window in Vim).
 3 Splitting Windows with [:sp :vsp ctrl-w,s ctrl-w,v] and closing with [ctrl-w,q], moving with [ctrl-w,w or tab]
   have a look into the split browser extension
-1 Reformat dactyl/HACKING so that git diff can find sections and report changes @ somewhere
index 2dd3b532110a60ad0474c07156935af6d036a7da..6c52b2ca60db7b95f7b387b9f5824ed7553def43 100644 (file)
@@ -117,7 +117,7 @@ var Config = Module("config", ConfigBase, {
         LocationChange: "Triggered when changing tabs or when navigation to a new location",
         PageLoadPre: "Triggered after a page load is initiated",
         PageLoad: "Triggered when a page gets (re)loaded/opened",
-        PrivateMode: "Triggered when private mode is activated or deactivated",
+        PrivateMode: "Triggered when private browsing mode is activated or deactivated",
         Sanitize: "Triggered when a sanitizeable item is cleared",
         ShellCmdPost: "Triggered after executing a shell command with :!cmd",
         Enter: "Triggered after Firefox starts",
@@ -132,7 +132,7 @@ var Config = Module("config", ConfigBase, {
         titlestring: "Pentadactyl"
     },
 
-    features: set([
+    features: Set([
         "bookmarks", "hints", "history", "marks", "quickmarks", "sanitizer",
         "session", "tabs", "tabs_undo", "windows"
     ]),
@@ -205,7 +205,7 @@ var Config = Module("config", ConfigBase, {
                 let title = document.getElementById("sidebar-title");
 
                 dactyl.assert(args.length || title.value || args.bang && config.lastSidebar,
-                              "Argument required");
+                              _("error.argumentRequired"));
 
                 if (!args.length)
                     return window.toggleSidebar(title.value ? null : config.lastSidebar);
@@ -227,7 +227,7 @@ var Config = Module("config", ConfigBase, {
                         return;
                     }
 
-                return dactyl.echoerr("No sidebar " + args[0] + " found");
+                return dactyl.echoerr(_("error.invalidArgument", args[0]));
             },
             {
                 argCount: "?",
@@ -248,7 +248,7 @@ var Config = Module("config", ConfigBase, {
                 });
             },
             {
-                argCount: "+",
+                argCount: "1",
                 completer: function (context) completion.ex(context),
                 literal: 0,
                 subCommand: 0
index 8b811cdb294a88bfd3de8ec7aed6d0b87449c89a..45de1fa19cb82d5df112f3eb1bd83a8cd18b240d 100644 (file)
@@ -5,7 +5,7 @@
         em:id="pentadactyl@dactyl.googlecode.com"
         em:type="2"
         em:name="Pentadactyl"
-        em:version="1.0b6"
+        em:version="1.0b7.1"
         em:description="Firefox for Vim and Links addicts"
         em:homepageURL="http://dactyl.sourceforge.net/pentadactyl"
         em:iconURL="resource://dactyl-local-skin/icon.png"
@@ -14,7 +14,6 @@
         <em:creator>Kris Maglione, Doug Kearns</em:creator>
         <em:developer>Kris Maglione</em:developer>
         <em:developer>Doug Kearns</em:developer>
-        <em:developer>Štěpán Němec</em:developer>
 
         <em:contributor>anekos</em:contributor>
         <em:contributor>teramako</em:contributor>
         <em:contributor>janus_wel</em:contributor>
         <em:contributor>Martin Stubenschrott</em:contributor>
         <em:contributor>Conrad Irwin</em:contributor>
+        <em:contributor>Štěpán Němec</em:contributor>
 
         <em:targetApplication>
             <Description
                 em:id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"
-                em:minVersion="3.5"
-                em:maxVersion="4.0.*"/>
+                em:minVersion="3.6"
+                em:maxVersion="8.*"/>
         </em:targetApplication>
     </Description>
 </RDF>
index 3726d8b509e154e1ee00d8766c3fae8d517ea350..d2aa0ce44edb65771faf529b87157ae64ed49698 100644 (file)
@@ -18,7 +18,7 @@
     <dt>LocationChange</dt>    <dd>Triggered when changing tabs or when navigating to a new location</dd>
     <dt>PageLoadPre</dt>       <dd>Triggered after a page load is initiated</dd>
     <dt>PageLoad</dt>          <dd>Triggered when a page gets (re)loaded/opened</dd>
-    <dt>PrivateMode</dt>       <dd>Triggered when private mode is activated or deactivated</dd>
+    <dt>PrivateMode</dt>       <dd>Triggered when private browsing mode is activated or deactivated</dd>
     <dt>Sanitize</dt>          <dd>Triggered when private data are sanitized</dd>
     <dt>ShellCmdPost</dt>      <dd>Triggered after executing a shell command with <ex>:!</ex><a>cmd</a></dd>
     <dt>Enter</dt>             <dd>Triggered after &dactyl.host; starts</dd>
@@ -40,6 +40,7 @@
     <dt>&lt;tags></dt>      <dd>The tags applied to <em>&lt;url></em>. Only for <em>Bookmark*</em>.</dd>
     <dt>&lt;title></dt>     <dd>The page, bookmark or download title.</dd>
     <dt>&lt;url></dt>       <dd>The URL against which the event was selected.</dd>
+    <dt>&lt;win></dt>       <dd>The window for which the event occurred. Only for <em>DOMLoad</em>, <em>PageLoad</em> and <em>PageLoadPre</em>.</dd>
 </dl>
 
 </overlay>
diff --git a/pentadactyl/locale/en-US/gui.xml b/pentadactyl/locale/en-US/gui.xml
deleted file mode 100644 (file)
index 1c4e9fb..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>
-
-<!DOCTYPE overlay SYSTEM "dactyl://content/dtd">
-
-<overlay
-    xmlns="&xmlns.dactyl;"
-    xmlns:html="&xmlns.html;">
-
-<dl tag="dialog-list" replace="dialog-list">
-    <dt>about</dt>           <dd>About Mozilla &dactyl.host;</dd>
-    <dt>addbookmark</dt>     <dd>Add bookmark for the current page</dd>
-    <dt>addons</dt>          <dd>Manage Add-ons</dd>
-    <dt>bookmarks</dt>       <dd>List your bookmarks</dd>
-    <dt>checkupdates</dt>    <dd>Check for updates</dd>
-    <dt>cookies</dt>         <dd>List your cookies</dd>
-    <dt>console</dt>         <dd>JavaScript console</dd>
-    <dt>customizetoolbar</dt><dd>Customize the Toolbar</dd>
-    <dt>dominspector</dt>    <dd>DOM Inspector</dd>
-    <dt>downloads</dt>       <dd>Manage Downloads</dd>
-    <dt>history</dt>         <dd>List your history</dd>
-    <dt>import</dt>          <dd>Import Preferences, Bookmarks, History, etc. from other browsers</dd>
-    <dt>openfile</dt>        <dd>Open the file selector dialog</dd>
-    <dt>pageinfo</dt>        <dd>Show information about the current page</dd>
-    <dt>pagesource</dt>      <dd>View page source</dd>
-    <dt>passwords</dt>       <dd>Passwords dialog</dd>
-    <dt>places</dt>          <dd>Places Organizer: Manage your bookmarks and history</dd>
-    <dt>preferences</dt>     <dd>Show &dactyl.host; preferences dialog</dd>
-    <dt>printpreview</dt>    <dd>Preview the page before printing</dd>
-    <dt>printsetup</dt>      <dd>Setup the page size and orientation before printing</dd>
-    <dt>print</dt>           <dd>Show print dialog</dd>
-    <dt>saveframe</dt>       <dd>Save frame to disk</dd>
-    <dt>savepage</dt>        <dd>Save page to disk</dd>
-    <dt>searchengines</dt>   <dd>Manage installed search engines</dd>
-    <dt>selectionsource</dt> <dd>View selection source</dd>
-    <dt>venkman</dt>         <dd>The JavaScript debugger</dd>
-</dl>
-
-</overlay>
-
-<!-- vim:se sts=4 sw=4 et: -->
index bc8bd7fd3c36973f4fce8073a6c11ddcfefd2f28..2f3b1d9cffced7e5dce86b529307f00a028ca3ff 100644 (file)
@@ -35,7 +35,7 @@
         etc.)
     </li>
     <li>
-        <link topic="cmdline">Command-line mode</link>:
+        <link topic="cmdline">Command Line mode</link>:
         Command-line editing.
     </li>
     <li>
     <li>Quick and powerful <link topic="hints">keyboard navigation</link> for links, input fields, etc.</li>
     <li>Vim-like <link topic="status-line">status line</link></li>
     <li>Keyboard <t>macros</t>, along with custom key mappings and commands</li>
-    <li>Minimal GUI, along with commands to <link href=":toolbarhide">hide</link> and <link href=":toolbartoggle">toggle</link> toolbars and menus</li>
+    <li>Minimal GUI, along with commands to <link topic=":toolbarhide">hide</link> and <link topic=":toolbartoggle">toggle</link> toolbars and menus</li>
     <li>Ability to <ex>:source</ex> JavaScript, CSS, and &dactyl.appName; command files</li>
     <li>Ability to <link topic="marks">mark</link> the current page position and return to it</li>
     <li>Count support for many commands (<tt>3</tt><k name="C-o"/> will go back <tt>3</tt> pages)</li>
diff --git a/pentadactyl/locale/en-US/messages.properties b/pentadactyl/locale/en-US/messages.properties
new file mode 100644 (file)
index 0000000..e3d5b3b
--- /dev/null
@@ -0,0 +1,2 @@
+
+# vim:se ft=jproperties tw=0:
index 4ab27d647d0fe022c2bbf38b870755811f7bf8ff..1a88ee5581d57d9a17dc63c30593b0c014aee213 100644 (file)
@@ -47,7 +47,7 @@
     &dactyl.appName;'s power, like Vim's, comes from its modal interface. Keys have
     different meanings depending on which mode the browser is in. &dactyl.appName; has
     several modes, but the 2 most important are <em>Normal</em> mode and
-    <em>Command-line</em> mode.
+    <em>Command Line</em> mode.
 </p>
 
 <p>
 </p>
 
 <p>
-    The other core mode of &dactyl.appName;, Command-line mode, can be entered from
+    The other core mode of &dactyl.appName;, Command Line mode, can be entered from
     Normal mode by typing a <k>:</k> (colon). You will frequently see &dactyl.appName;
     commands start with a <k>:</k>, indicating that what follows is a command.
 </p>
 
 <p>
-    To return to Normal mode from Command-line mode, type <k name="Esc"/>. Pressing
+    To return to Normal mode from Command Line mode, type <k name="Esc"/>. Pressing
     <k name="Esc"/> will also return you to Normal mode from most other modes in
     &dactyl.appName;.
 </p>
 </p>
 
 <p>
-    The most common hint mode is called <t>quick-hints</t>.
-    To activate QuickHint mode, press either <k>f</k> or <k>F</k>. The lower-case
-    <k>f</k> will open the resulting link in the current tab, while the upper-case
-    <k>F</k> will open it in a new tab.
+    To activate Hints mode, press either <k>f</k> or <k>F</k>. The lower-case
+    <k>f</k> will open the resulting link in the current tab, while the
+    upper-case <k>F</k> will open it in a new tab.
 </p>
 
 <p>
     To test it, try this link: <link topic="&dactyl.apphome;">&dactyl.appName; Homepage</link>.
-    Activate QuickHint mode with <k>f</k> or <k>F</k> to highlight all currently
+    Activate Hints mode with <k>f</k> or <k>F</k> to highlight all currently
     visible links. Then start typing the text of the link. The link should be
-    uniquely identified soon, and &dactyl.appName; will open it. Once you're done,
-    remember to use <k name="C-o"/> (<em>History Back</em>) or <k>d</k> (<em>Delete Buffer</em>)
-    to return here, depending on which key you used to activate QuickHint mode.
+    uniquely identified soon, and &dactyl.appName; will open it. Once you're
+    done, remember to use <k name="C-o"/> (<em>History Back</em>) or <k>d</k>
+    (<em>Delete Buffer</em>) to return here, depending on which key you used to
+    activate Hints mode.
 </p>
 
 <h2 tag="common-issues">Common issues</h2>
diff --git a/teledactyl/bootstrap.js b/teledactyl/bootstrap.js
new file mode 120000 (symlink)
index 0000000..ff1024d
--- /dev/null
@@ -0,0 +1 @@
+../common/bootstrap.js
\ No newline at end of file
index 2a7dd1e5b9cdfa00279362834f7a931849cb0d4e..ecc5524ce410f713084d7cac81be3b6e2d4883b6 100644 (file)
@@ -4,7 +4,7 @@
 // given in the LICENSE.txt file included with this file.
 "use strict";
 
-const Addressbook = Module("addressbook", {
+var Addressbook = Module("addressbook", {
     init: function () {
     },
 
@@ -63,9 +63,9 @@ const Addressbook = Module("addressbook", {
 
         if (addresses.length < 1) {
             if (!filter)
-                dactyl.echoerr("Exxx: No contacts", commandline.FORCE_SINGLELINE);
+                dactyl.echoerr(_("addressbook.noContacts"), commandline.FORCE_SINGLELINE);
             else
-                dactyl.echoerr("Exxx: No contacts matching string '" + filter + "'", commandline.FORCE_SINGLELINE);
+                dactyl.echoerr(_("addressbook.noMatchingContacts"), filter, commandline.FORCE_SINGLELINE);
             return false;
         }
 
@@ -100,9 +100,9 @@ const Addressbook = Module("addressbook", {
                     displayName = this.generateDisplayName(firstName, lastName);
 
                 if (addressbook.add(mailAddr, firstName, lastName, displayName))
-                    dactyl.echomsg("Added address: " + displayName + " <" + mailAddr + ">", 1, commandline.FORCE_SINGLELINE);
+                    dactyl.echomsg(_("addressbook.added", displayName, mailAddr), 1, commandline.FORCE_SINGLELINE);
                 else
-                    dactyl.echoerr("Exxx: Could not add contact `" + mailAddr + "'", commandline.FORCE_SINGLELINE);
+                    dactyl.echoerr(_("addressbook.cantAdd", mailAddr), commandline.FORCE_SINGLELINE);
 
             },
             {
index 67586e81f1ab3783e5874a2e404146bf02b9117f..3a883ae9f7f0d8b879b39fa89d98e6ab09db7424 100644 (file)
@@ -4,7 +4,7 @@
 // given in the LICENSE.txt file included with this file.
 "use strict";
 
-const Config = Module("config", ConfigBase, {
+var Config = Module("config", ConfigBase, {
     name: "teledactyl",
     appName: "Teledactyl",
     idName: "TELEDACTYL",
@@ -16,12 +16,16 @@ const Config = Module("config", ConfigBase, {
         init: function init() {
             init.superapply(this, arguments);
 
-            modules.__defineGetter__("content", function () window.content);
+            if (!("content" in modules))
+                modules.__defineGetter__("content", function () window.content);
 
             util.overlayWindow(window, { append: <><hbox id="statusTextBox" flex=""/></> });
         },
 
-        get browser() window.getBrowser(),
+        get browser()
+            let (tabmail = document.getElementById('tabmail'))
+                tabmail && tabmail.tabInfo.length ? tabmail.getBrowserForSelectedTab()
+                                                  : document.getElementById("messagepane"),
 
         get commandContainer() document.documentElement.id,
 
@@ -33,6 +37,8 @@ const Config = Module("config", ConfigBase, {
             get mStrip() this.tabStrip,
             get browsers() [browser for (browser in Iterator(this.mTabs))],
 
+            removeTab: function removeTab(tab) this.closeTab(tab),
+
             loadOneTab: function loadOneTab(uri) {
                 return this.openTab("contentTab", { contentPage: uri });
             },
@@ -137,7 +143,7 @@ const Config = Module("config", ConfigBase, {
     },
 
     /*** optional options, there are checked for existence and a fallback provided  ***/
-    features: Class.memoize(function () set(
+    features: Class.memoize(function () Set(
         this.isComposeWindow ? ["addressbook"]
                              : ["hints", "mail", "marks", "addressbook", "tabs"])),
 
index ff01820c6eb57555861fae186f3cf972644c8612..3b67b4500441d7e808e0ecd6935922e6fbdcb09f 100644 (file)
@@ -4,7 +4,7 @@
 // given in the LICENSE.txt file included with this file.
 "use strict";
 
-const Mail = Module("mail", {
+var Mail = Module("mail", {
     init: function init() {
         // used for asynchronously selecting messages after wrapping folders
         this._selectMessageKeys = [];
@@ -70,9 +70,9 @@ const Mail = Module("mail", {
     _moveOrCopy: function (copy, destinationFolder, operateOnThread) {
         let folders = mail.getFolders(destinationFolder);
         if (folders.length == 0)
-            return void dactyl.echoerr("Exxx: No matching folder for " + destinationFolder);
+            return void dactyl.echoerr(_("addressbook.noMatchingFolder", destinationFolder));
         else if (folders.length > 1)
-            return dactyl.echoerr("Exxx: More than one match for " + destinationFolder);
+            return dactyl.echoerr(_("addressbook.multipleFolderMatches", destinationFolder));
 
         let count = gDBView.selection.count;
         if (!count)
@@ -129,7 +129,7 @@ const Mail = Module("mail", {
 
     get currentFolder() gFolderTreeView.getSelectedFolders()[0],
 
-    /** @property {nsISmtpServer[]} The list of configured SMTP servers. */
+    /** @property {[nsISmtpServer]} The list of configured SMTP servers. */
     get smtpServers() {
         let servers = services.smtp.smtpServers;
         let res = [];
@@ -168,7 +168,7 @@ const Mail = Module("mail", {
                     let url = args.attachments.pop();
                     let file = io.getFile(url);
                     if (!file.exists())
-                        return void dactyl.echoerr("Exxx: Could not attach file `" + url + "'", commandline.FORCE_SINGLELINE);
+                        return void dactyl.echoerr(_("mail.cantAttachFile", url), commandline.FORCE_SINGLELINE);
 
                     attachment = Cc["@mozilla.org/messengercompose/attachment;1"].createInstance(Ci.nsIMsgAttachment);
                     attachment.url = "file://" + file.path;
@@ -402,7 +402,7 @@ const Mail = Module("mail", {
 
                 let folder = mail.getFolders(arg, true, true)[count];
                 if (!folder)
-                    dactyl.echoerr("Exxx: Folder \"" + arg + "\" does not exist");
+                    dactyl.echoerr(_("command.goto.folderNotExist", arg));
                 else if (dactyl.forceNewTab)
                     MsgOpenNewTabForFolder(folder.URI);
                 else
@@ -434,7 +434,7 @@ const Mail = Module("mail", {
 
                 // TODO: is there a better way to check for validity?
                 if (addresses.some(function (recipient) !(/\S@\S+\.\S/.test(recipient))))
-                    return void dactyl.echoerr("Exxx: Invalid e-mail address");
+                    return void dactyl.echoerr(_("command.mail.invalidEmailAddress"));
 
                 mail.composeNewMail(mailargs);
             },
@@ -453,7 +453,7 @@ const Mail = Module("mail", {
             "Copy selected messages",
             function (args) { mail._moveOrCopy(true, args.literalArg); },
             {
-                argCount: 1,
+                argCount: "1",
                 completer: function (context) completion.mailFolder(context),
                 literal: 0
             });
@@ -462,7 +462,7 @@ const Mail = Module("mail", {
             "Move selected messages",
             function (args) { mail._moveOrCopy(false, args.literalArg); },
             {
-                argCount: 1,
+                argCount: "1",
                 completer: function (context) completion.mailFolder(context),
                 literal: 0
             });
index d68eb62efcde5e6a75ef6fc3edb458c2487d1519..ae822069471331b417ffa509f8f20150e462473c 100644 (file)
@@ -5,17 +5,17 @@
         em:id="teledactyl@dactyl.googlecode.com"
         em:type="2"
         em:name="Teledactyl"
-        em:version="0.5b1pre"
+        em:version="0.6"
         em:description="Thunderbird for Mutt and Vim addicts"
         em:creator="Kris Maglione"
         em:homepageURL="http://dactyl.sf.net/Teledactyl"
         em:iconURL="chrome://teledactyl/skin/icon.png"
-        em:optionsURL="chrome://dactyl/content/preferences.xul">
+        em:bootstrap="true">
         <em:targetApplication>
             <Description>
                 <em:id>{3550f703-e582-4d05-9a08-453d09bdfdc6}</em:id>
-                <em:minVersion>3.0b3</em:minVersion>
-                <em:maxVersion>3.2</em:maxVersion>
+                <em:minVersion>5.0</em:minVersion>
+                <em:maxVersion>8.*</em:maxVersion>
             </Description>
         </em:targetApplication>
     </Description>
diff --git a/teledactyl/locale/en-US/gui.xml b/teledactyl/locale/en-US/gui.xml
deleted file mode 100644 (file)
index 4f8b449..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>
-
-<!DOCTYPE overlay SYSTEM "dactyl://content/dtd">
-
-<overlay
-    xmlns="&xmlns.dactyl;"
-    xmlns:html="&xmlns.html;">
-
-<dl tag="dialog-list" replace="dialog-list">
-    <dt>about</dt>           <dd>About &dactyl.host;</dd>
-    <dt>addons</dt>          <dd>Manage Add-ons</dd>
-    <dt>addressbook</dt>     <dd>Address book</dd>
-    <dt>checkupdates</dt>    <dd>Check for updates</dd>
-    <dt>console</dt>         <dd>JavaScript console</dd>
-    <dt>dominspector</dt>    <dd>DOM Inspector</dd>
-    <dt>downloads</dt>       <dd>Manage Downloads</dd>
-    <dt>openfile</dt>        <dd>Open the file selector dialog</dd>
-    <dt>pageinfo</dt>        <dd>Show information about the current page</dd>
-    <dt>pagesource</dt>      <dd>View page source</dd>
-    <dt>preferences</dt>     <dd>Show &dactyl.host; preferences dialog</dd>
-    <dt>printsetup</dt>      <dd>Setup the page size and orientation before printing</dd>
-    <dt>print</dt>           <dd>Show print dialog</dd>
-    <dt>saveframe</dt>       <dd>Save frame to disk</dd>
-    <dt>savepage</dt>        <dd>Save page to disk</dd>
-</dl>
-
-</overlay>
-
-<!-- vim:se sts=4 sw=4 et: -->
index 4f111d51aa4de2e67c686630a9642d7b14c944e6..328fb1cdc123d99549cb406afdbc5a927048b298 100644 (file)
@@ -35,7 +35,7 @@
         etc.)
     </li>
     <li>
-        <link topic="cmdline">Command-line mode</link>:
+        <link topic="cmdline">Command Line mode</link>:
         Command-line editing.
     </li>
     <li>
diff --git a/teledactyl/locale/en-US/messages.properties b/teledactyl/locale/en-US/messages.properties
new file mode 100644 (file)
index 0000000..ddc929b
--- /dev/null
@@ -0,0 +1,14 @@
+addressbook.noContacts = No contacts
+addressbook.noMatchingContacts-1 = No contacts matching '%S'
+addressbook.added-2 = Added address: %S <%S>
+addressbook.cantAdd-1 = Could not add contact '%S'
+
+command.goto.folderNotExist-1 = Folder '%S' does not exist
+command.mail.invalidEmailAddress = Invalid e-mail address
+
+mail.noMatchingFolder-1 = No matching folder for %S
+mail.multipleFolderMatches-1 = More than one match for %S
+mail.cantAttachFile = Could not attach file '%S'
+
+# vim:se ft=jproperties tw=0:
+