]> git.donarmstrong.com Git - dactyl.git/commitdiff
Import 1.0b7.1 supporting Firefox up to 8.* upstream/1.0b7.1
authorMichael Schutte <michi@uiae.at>
Sat, 8 Oct 2011 16:33:04 +0000 (18:33 +0200)
committerMichael Schutte <michi@uiae.at>
Sat, 8 Oct 2011 16:33:04 +0000 (18:33 +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,
   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
 
 * 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.
 
 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.,
 
 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
 
 #### 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
 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))
 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)
 
 
 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
 .SILENT:
 
 #### rules
@@ -121,7 +122,7 @@ install:
        done;                                                                   \
                                                                                \
        profile=$$(sed 's/^$$/\#/' "$$dir/profiles.ini" |                       \
        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 }   \
                        '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)"
 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"
        $(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                     \
        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"
        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)"
        $(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 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;
 
 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);
 
                           .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);
 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];
     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+/);
 
     for each (let line in manifest.split("\n")) {
         let fields = line.split(/\s+/);
@@ -173,12 +173,22 @@ function init() {
             break;
 
         case "resource":
             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]));
         }
     }
 
             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);
     }
     try {
         module("resource://dactyl-content/disable-acr.jsm").init(addon.id);
     }
@@ -186,16 +196,23 @@ function init() {
         reportError(e);
     }
 
         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.bootstrap = this;
 
-    JSMLoader.load("resource://dactyl/bootstrap.jsm", global);
+    JSMLoader.load(BOOTSTRAP_JSM, global);
 
     JSMLoader.init(suffix);
     JSMLoader.load("base.jsm", global);
 
     JSMLoader.init(suffix);
     JSMLoader.load("base.jsm", global);
@@ -211,9 +228,9 @@ function init() {
                 wrappedJSObject: {}
             },
             createInstance: function () this.instance
                 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();
 
     for each (let component in components)
         component.register();
@@ -233,11 +250,11 @@ function shutdown(data, reason) {
             reportError(e);
         }
 
             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-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)
 
         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);
 
 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";
     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 {
     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]");
         }
         catch (e) {}
         if (!open)
             this.writes.push("\n]");
-        this.writes.push(post)
+        this.writes.push(post);
     }
     this.writes.push(channelStream);
 
     }
     this.writes.push(channelStream);
 
index 9a76fa372df310d733ca85f6326a66ec501a7730..71e6c93b969391810617c96bdfa3256eaf8d49ee 100644 (file)
@@ -8,6 +8,23 @@
 
 /** @scope modules */
 
 
 /** @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();
 var Abbreviation = Class("Abbreviation", {
     init: function (modes, lhs, rhs) {
         this.modes = modes.sort();
@@ -15,20 +32,61 @@ var Abbreviation = Class("Abbreviation", {
         this.rhs = rhs;
     },
 
         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,
 
     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),
 
     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),
 
     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),
 
     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),
 
     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();
     },
 
     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) {
     get modeChar() Abbreviation.modeChar(this.modes)
 }, {
     modeChar: function (_modes) {
@@ -45,6 +103,7 @@ var AbbrevHive = Class("AbbrevHive", Contexts.Hive, {
         this._store = {};
     },
 
         this._store = {};
     },
 
+    /** @property {boolean} True if there are no abbreviations. */
     get empty() !values(this._store).nth(util.identity, 0),
 
     /**
     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.
      *
      * @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];
      */
     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() {
      *     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 {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));
 
         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/>
         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;"/>
                 {
                 </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)
 
         // 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);
     }
         else
             commandline.commandOutput(list);
     }
@@ -245,7 +307,6 @@ var Abbreviations = Module("abbreviations", {
             context.completions = group.merged.filter(fn);
         };
     },
             context.completions = group.merged.filter(fn);
         };
     },
-
     commands: function () {
         function addAbbreviationCommands(modes, ch, modeDescription) {
             modes.sort();
     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"));
 
                     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);
                     }
                 }, {
                     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"]);
                     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]))
                     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,
                 }, {
                     argCount: "?",
                     bang: true,
@@ -312,8 +376,9 @@ var Abbreviations = Module("abbreviations", {
         }
 
         addAbbreviationCommands([modes.INSERT, modes.COMMAND_LINE], "", "");
         }
 
         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.
      *
      * @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));
      */
     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 }),
 
     /**
     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 {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 = {};
 
         function cmds(hive) {
             let cmds = {};
@@ -108,7 +113,7 @@ var AutoCommands = Module("autocommands", {
                     <td colspan="3">----- Auto Commands -----</td>
                 </tr>
                 {
                     <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> +
                         <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)
         let lastPattern = null;
         var { url, doc } = args;
         if (url)
-            uri = util.newURI(url);
+            uri = util.createURI(url);
         else
             var { uri, doc } = buffer;
 
         else
             var { uri, doc } = buffer;
 
@@ -200,7 +205,7 @@ var AutoCommands = Module("autocommands", {
                             args["-group"].remove(event, regexp); // remove all
                     }
                     else
                             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,
                 }
             }, {
                 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 () {
 // 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),
         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));
                              valueOf: function () arg
                          }
                      }, arg));
-            statusline.updateStatus();
+            bookmarks.timer.tell();
         }, window);
     },
 
         }, window);
     },
 
+    signals: {
+        "browser.locationChange": function (webProgress, request, uri) {
+            statusline.bookmarked = false;
+            this.checkBookmarked(uri);
+        }
+    },
+
     get format() ({
         anchored: false,
         title: ["URL", "Info"],
     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.
      */
      *
      * @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
         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 ");
     },
 
             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.
     /**
      * 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 (!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;
                 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";
 
     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)
         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);
         }
 
             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;
         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)
             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";
                         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 = [
                         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;
                     }
                             for ([, win] in Iterator(frames))];
                         return;
                     }
@@ -485,11 +507,11 @@ var Bookmarks = Module("bookmarks", {
             "Delete a bookmark",
             function (args) {
                 if (args.bang)
             "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));
                         function (resp) {
                             if (resp && resp.match(/^y(es)?$/i)) {
                                 bookmarks.remove(Object.keys(bookmarkcache.bookmarks));
-                                dactyl.echomsg(_("bookmark.allGone"));
+                                dactyl.echomsg(_("bookmark.allDeleted"));
                             }
                         });
                 else {
                             }
                         });
                 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"] });
                         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) });
                     }
 
                     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;
             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 () {
                     // 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);
 
                     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;
                 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);
 
         "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
         };
 
             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.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);
         }
 
         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) {
     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);
                 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
                 // 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();
 
                 }
                     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();
                 }
             }
                     // 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);
         }),
         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 "); });
 
             ["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",
         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",
 
         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",
 
         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",
 
         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",
 
         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("~"); });
 
             "Open home directory",
             function () { dactyl.open("~"); });
 
-        mappings.add([modes.NORMAL], ["gh"],
+        mappings.add([modes.NORMAL], ["<open-homepage>", "gh"],
             "Open homepage",
             function () { BrowserHome(); });
 
             "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 });
             });
 
             "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(); });
     }
             "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.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",
         this.addPageInfoSection("f", "Feeds", function (verbose) {
             const feedTypes = {
                 "application/rss+xml": "RSS",
@@ -75,7 +93,7 @@ var Buffer = Module("buffer", {
             }
 
             if (!verbose && nFeed)
             }
 
             if (!verbose && nFeed)
-                yield nFeed + " feed" + (nFeed > 1 ? "s" : "");
+                yield nFeed + /*L*/" feed" + (nFeed > 1 ? "s" : "");
         });
 
         this.addPageInfoSection("g", "General Info", function (verbose) {
         });
 
         this.addPageInfoSection("g", "General Info", function (verbose) {
@@ -110,7 +128,7 @@ var Buffer = Module("buffer", {
 
             if (!verbose) {
                 if (pageSize[0])
 
             if (!verbose) {
                 if (pageSize[0])
-                    yield (pageSize[1] || pageSize[0]) + " bytes";
+                    yield (pageSize[1] || pageSize[0]) + /*L*/" bytes";
                 yield lastMod;
                 return;
             }
                 yield lastMod;
                 return;
             }
@@ -134,6 +152,9 @@ var Buffer = Module("buffer", {
         });
 
         this.addPageInfoSection("m", "Meta Tags", function (verbose) {
         });
 
         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");
 
             // 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]));
         });
 
                         .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;
         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) {
     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);
                 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),
      * @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
 
     /**
      * 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;
     focusAllowed: function focusAllowed(elem) {
         if (elem instanceof Window && !Editor.getEditor(elem))
             return true;
+
         let doc = elem.ownerDocument || elem.document || elem;
         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;
      */
     focusElement: function focusElement(elem) {
         let win = elem.ownerDocument && elem.ownerDocument.defaultView || elem;
+        elem.dactylFocusAllowed = true;
         win.document.dactylFocusAllowed = true;
 
         if (isinstance(elem, [HTMLFrameElement, HTMLIFrameElement]))
         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
      * 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;
      */
     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]))
         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
                 }));
             });
                     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.
      */
      * @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*.
 
     /**
      * Opens the appropriate context menu for *elem*.
@@ -561,7 +630,7 @@ var Buffer = Module("buffer", {
         try {
             window.urlSecurityCheck(uri.spec, doc.nodePrincipal);
 
         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())
                 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,
                              | 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), {
                                   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);
 
                 return onStateChange.superapply(this, arguments);
-            }
+            })
         });
 
         persist.saveURI(uri, null, null, null, null, file);
         });
 
         persist.saveURI(uri, null, null, null, null, file);
@@ -672,7 +741,7 @@ var Buffer = Module("buffer", {
      */
     findScrollable: function findScrollable(dir, horizontal) {
         function find(elem) {
      */
     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))
                 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;
                         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;
     },
 
         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.
      *
     // 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) {
      *     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) {
      * @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) {
     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))
                 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);
 
             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.
      *
      * 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.
      */
      * @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;
 
         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",
             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)
         }
         else {
             if (useExternalEditor)
-                this.viewSourceExternally(url || doc);
+                this.viewSourceExternally(loc || doc);
             else {
             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);
                 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.
      * 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) {
      */
     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;
                 };
                                               function () { temp && file.remove(false); });
                     return true;
                 };
@@ -928,8 +1042,8 @@ var Buffer = Module("buffer", {
             return null;
         },
 
             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);
                 }
                 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.
      *
      * 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,
      * @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)
      */
     bumpZoomLevel: function bumpZoomLevel(steps, fullZoom) {
         if (fullZoom === undefined)
@@ -1019,7 +1133,7 @@ var Buffer = Module("buffer", {
         this.setZoom(Math.round(values[i] * 100), fullZoom);
     },
 
         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)),
     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)),
     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"),
 }, {
     PageInfo: Struct("PageInfo", "name", "title", "action")
                         .localize("title"),
@@ -1044,17 +1158,21 @@ var Buffer = Module("buffer", {
      *
      * @returns {string}
      */
      *
      * @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();
         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);
         }
             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);
     },
 
         return util.domToString(range);
     },
 
@@ -1079,13 +1197,13 @@ var Buffer = Module("buffer", {
 
         var names = [];
         if (node.title)
 
         var names = [];
         if (node.title)
-            names.push([node.title, "Page Name"]);
+            names.push([node.title, /*L*/"Page Name"]);
 
         if (node.alt)
 
         if (node.alt)
-            names.push([node.alt, "Alternate Text"]);
+            names.push([node.alt, /*L*/"Alternate Text"]);
 
         if (!isinstance(node, Document) && node.textContent)
 
         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"]);
 
 
         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;
             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();
 
         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;
 
         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);
     },
 
         Buffer.scrollTo(elem, left + number * increment, null);
     },
 
@@ -1184,10 +1308,11 @@ var Buffer = Module("buffer", {
         else
             throw Error();
 
         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;
 
         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);
     },
 
         Buffer.scrollTo(elem, null, top + number * increment);
     },
 
@@ -1212,7 +1337,7 @@ var Buffer = Module("buffer", {
     },
 
     openUploadPrompt: function openUploadPrompt(elem) {
     },
 
     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());
             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,
 
                 // 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.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
                         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
                     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);
             },
 
                 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) {
             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))];
         };
 
             });
 
             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 filter = context.filter.toLowerCase();
+
             let defItem = { parent: { getTitle: function () "" } };
             let defItem = { parent: { getTitle: function () "" } };
+
             let tabGroups = {};
             tabs.getGroups();
             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;
                 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(), []];
                     tabGroups[group.id] = [group.getTitle(), []];
+
                 group = tabGroups[group.id];
                 group[1].push([i, tab.linkedBrowser]);
             });
                 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;
                 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]) {
 
             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 = "#";
 
                             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 {
                             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,
                                 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],
             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],
             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 });
 
             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 });
 
             "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); });
 
             "Go to the root of the website",
             function () { buffer.climbUrlPath(-1); });
 
@@ -1542,11 +1679,11 @@ var Buffer = Module("buffer", {
             },
             { count: true });
 
             },
             { 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); });
 
             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(); });
 
             "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); });
 
             "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 });
 
             "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 });
             "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 });
 
             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 });
 
             "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 });
 
             "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 });
 
             "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 });
 
             "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 });
 
             "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 });
 
             "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); });
 
             "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); });
 
             "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)) {
             "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);
 
 
                     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 (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" &&
                             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);
                     });
 
                     dactyl.assert(elements.length > 0);
@@ -1681,36 +1848,40 @@ var Buffer = Module("buffer", {
             },
             { count: true });
 
             },
             { 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 () {
             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 () {
             "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 () {
             "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
             });
 
         // reloading
-        mappings.add([modes.COMMAND], ["r", "<reload>"],
+        mappings.add([modes.NORMAL], ["r", "<reload>"],
             "Reload the current web page",
             function () { tabs.reload(tabs.getTab(), false); });
 
             "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); });
 
             "Reload while skipping the cache",
             function () { tabs.reload(tabs.getTab(), true); });
 
@@ -1724,62 +1895,62 @@ var Buffer = Module("buffer", {
             });
 
         // zooming
             });
 
         // 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 });
 
             "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 });
 
             "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 });
 
             "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 });
 
             "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 });
 
             "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 });
 
             "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 });
 
             "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 });
 
             "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 });
 
             "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
             "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); });
 
             "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); });
     },
             "Print file information",
             function () { buffer.showPageInfo(true); });
     },
@@ -1817,6 +1988,23 @@ var Buffer = Module("buffer", {
                 validator: function (value) RegExp(value)
             });
 
                 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'"),
         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",
 
         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"],
             { 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;
             }
         });
                 return this.commandbar;
             }
         });
+        this.updateVisibility();
     },
     addElement: function addElement(obj) {
         const self = this;
     },
     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]
                     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;
                 },
 
                     return null;
                 },
 
@@ -300,19 +301,21 @@ var CommandWidgets = Class("CommandWidgets", {
 });
 
 var CommandMode = Class("CommandMode", {
 });
 
 var CommandMode = Class("CommandMode", {
-    init: function init() {
+    init: function CM_init() {
         this.keepCommand = userContext.hidden_option_command_afterimage;
     },
 
         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,
 
     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),
         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);
 
         this.messageCount = commandline.messageCount;
         modes.push(this.mode, this.extendedMode, this.closure);
@@ -340,7 +343,7 @@ var CommandMode = Class("CommandMode", {
 
     get widgets() commandline.widgets,
 
 
     get widgets() commandline.widgets,
 
-    enter: function (stack) {
+    enter: function CM_enter(stack) {
         commandline.commandSession = this;
         if (stack.pop && commandline.command) {
             this.onChange(commandline.command);
         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;
         if (!stack.push) {
             commandline.commandSession = null;
             this.input.dactylKeyPress = undefined;
@@ -374,7 +377,7 @@ var CommandMode = Class("CommandMode", {
     },
 
     events: {
     },
 
     events: {
-        input: function onInput(event) {
+        input: function CM_onInput(event) {
             if (this.completions) {
                 this.resetCompletions();
 
             if (this.completions) {
                 this.resetCompletions();
 
@@ -382,7 +385,7 @@ var CommandMode = Class("CommandMode", {
             }
             this.onChange(commandline.command);
         },
             }
             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();
             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,
 
 
     keepCommand: false,
 
-    onKeyPress: function onKeyPress(events) {
+    onKeyPress: function CM_onKeyPress(events) {
         if (this.completions)
             this.completions.previewClear();
 
         return true; /* Pass event */
     },
 
         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;
         if (this.completions) {
             this.completions.context.cancelAll();
             this.completions.wildIndex = -1;
@@ -426,12 +428,12 @@ var CommandExMode = Class("CommandExMode", CommandMode, {
 
     prompt: ["Normal", ":"],
 
 
     prompt: ["Normal", ":"],
 
-    complete: function complete(context) {
+    complete: function CEM_complete(context) {
         context.fork("ex", 0, completion, "ex");
     },
 
         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;
                              function _onSubmit() {
             io.withSavedValues(["readHeredoc"], function _onSubmit() {
                 this.readHeredoc = commandline.readHeredoc;
@@ -449,7 +451,7 @@ var CommandPromptMode = Class("CommandPromptMode", CommandMode, {
         init.supercall(this);
     },
 
         init.supercall(this);
     },
 
-    complete: function (context) {
+    complete: function CPM_complete(context) {
         if (this.completer)
             context.forkapply("prompt", 0, this, "completer", Array.slice(arguments, 1));
     },
         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;
 
     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));
         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) {
      * @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)
         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;
     },
 
         this.command = null;
     },
 
@@ -722,6 +727,9 @@ var CommandLine = Module("commandline", {
 
         if (flags & this.APPEND_TO_MESSAGES) {
             let message = isObject(data) ? data : { message: data };
 
         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;
         }
             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;
 
         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)
             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;
     // 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, {
         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);
 
         if (cmd != false)
             this._echoLine(cmd, this.HL_NORMAL);
 
@@ -838,7 +854,7 @@ var CommandLine = Module("commandline", {
                     event.target.blur();
                     dactyl.beep();
                 }
                     event.target.blur();
                     dactyl.beep();
                 }
-            },
+            }
         }
     ),
 
         }
     ),
 
@@ -940,6 +956,7 @@ var CommandLine = Module("commandline", {
             if (this.completions)
                 this.completions.previewClear();
             this.input.value = val;
             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) {
             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++;
                     this.ignoredCount++;
-                if (options["autocomplete"].length) {
+                else if (this.session.autocomplete) {
                     this.itemList.visible = true;
                     this.complete(true, false);
                 }
                     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) {
         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);
             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);
 
                     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;
 
                         if (done())
                             break;
 
@@ -1259,6 +1277,7 @@ var CommandLine = Module("commandline", {
 
         tab: function tab(reverse, wildmode) {
             this.autocompleteTimer.flush();
 
         tab: function tab(reverse, wildmode) {
             this.autocompleteTimer.flush();
+            this.ignoredCount = 0;
 
             if (this._caret != this.caret)
                 this.reset();
 
             if (this._caret != this.caret)
                 this.reset();
@@ -1301,7 +1320,7 @@ var CommandLine = Module("commandline", {
                 if (this.selected == null)
                     statusline.progress = "";
                 else
                 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)
             }
 
             if (this.items.length == 0)
@@ -1415,7 +1434,7 @@ var CommandLine = Module("commandline", {
     mappings: function init_mappings() {
 
         mappings.add([modes.COMMAND],
     mappings: function init_mappings() {
 
         mappings.add([modes.COMMAND],
-            [":"], "Enter command-line mode",
+            [":"], "Enter Command Line mode",
             function () { CommandExMode().open(""); });
 
         mappings.add([modes.INPUT_MULTILINE],
             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) {
 
                 let index = text.indexOf(self.end);
                 if (index >= 0) {
+                    self.done = true;
                     text = text.substring(1, index);
                     modes.pop();
 
                     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">
     _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">
                 {
                 <div key="completions"/>
                 <div highlight="Completions">
                 {
@@ -1812,7 +1832,7 @@ var ItemList = Class("ItemList", {
 
     onKeyPress: function onKeyPress(event) false
 }, {
 
     onKeyPress: function onKeyPress(event) false
 }, {
-    WAITING_MESSAGE: "Generating results..."
+    WAITING_MESSAGE: _("completion.generating")
 });
 
 // vim: set fdm=marker sw=4 ts=4 et:
 });
 
 // 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;
 
 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";
 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");
         };
 
         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 () {
     },
 
     cleanup: function () {
+        for (let cleanup in values(this.cleanups))
+            cleanup.call(this);
+
         delete window.dactyl;
         delete window.liberator;
 
         delete window.dactyl;
         delete window.liberator;
 
@@ -57,21 +70,36 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         autocommands.trigger("Leave", {});
     },
 
         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: {
     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)
             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)
                 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)
             }
 
             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 {
 
             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", {
      * @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
     }),
 
         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",
 
     // Global constants
     CURRENT_TAB: "here",
@@ -184,14 +242,16 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
     },
 
     addUsageCommand: function (params) {
     },
 
     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 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)
                 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));
 
                 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;
                 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 = {};
                     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) {
                                                 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);
                 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;
                         res[1].@tag = obj.helpTag;
 
                     yield res;
@@ -311,7 +371,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         clipboardHelper.copyString(str);
 
         if (verbose) {
         clipboardHelper.copyString(str);
 
         if (verbose) {
-            let message = { message: "Yanked " + str };
+            let message = { message: _("dactyl.yank", str) };
             try {
                 message.domains = [util.newURI(str).host];
             }
             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;
 
     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;
             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] !== "[")
                 ({ 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);
 
         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}
      */
      * @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.
 
     /**
      * 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) {
      * 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);
         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))
 
             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' +
                 }
 
             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"}>
                 <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}
                     <toc start="2"/>
 
                     {body}
-                </document>.toXMLString()));
+                </document>.toXMLString();
             fileMap["plugins"] = function () ['text/xml;charset=UTF-8', help];
 
             fileMap["versions"] = function () {
             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) {
 
                 default xml namespace = NS;
                 function rec(text, level, li) {
+                    XML.ignoreWhitespace = XML.prettyPrinting = false;
+
                     let res = <></>;
                     let list, space, i = 0;
 
                     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/>;
                             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) {
                             list.* += li;
                         }
                         else if (match.par) {
@@ -751,8 +821,10 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                             }
 
                             list = null;
                             }
 
                             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"}>{
                             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;
                 }
 
                     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");
                 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' +
                 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 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);
                 ];
             }
             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' +
 
             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)
                 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);
 
             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;
         }
     },
             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));
         }
 
                 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) {
                             .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)) {
             }
         }
 
         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'
                 '<?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(""));
         }
 
             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 + "}")
             .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 || ""));
         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}</>;
         }
         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) {
             };
         }
         else if (obj instanceof Option) {
+            tag = spec = function (name) <>'{name}'</>;
             link = function (opt, name) <o>{name}</o>;
             link = function (opt, name) <o>{name}</o>;
+            args = { value: "", values: [] };
         }
 
         XML.prettyPrinting = false;
         }
 
         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 : "" }{
                 <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></>;
 
                 </description>
             </item></>;
 
@@ -1014,7 +1108,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         }
 
         if (obj.completer)
         }
 
         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))
                           .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: {},
      * 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
     }),
 
         get: function globalVariables() this._globalVariables
     }),
 
@@ -1076,13 +1170,14 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
             let loadplugins = options.get("loadplugins");
             if (args)
 
             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) {
 
             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);
                     try {
                         io.source(file.path);
-                        dactyl.pluginFiles[file.path] = true;
+                        dactyl.pluginFiles[file.path] = file.lastModifiedTime;
                     }
                     catch (e) {
                         dactyl.reportError(e);
                     }
                     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,
     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)
             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);
                 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
      * ['www.google.com/search?q=bla', 'www.osnews.com']
      *
      * @param {string} str
-     * @returns {string[]}
+     * @returns {[string]}
      */
     parseURLs: function parseURLs(str) {
         let urls;
      */
     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();
 
         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);
                 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)
             // 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);
 
             // 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.
      */
      *     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);
 }, {
     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) {
                     M: ["Always show messages outside of the status line"]
                 },
                 setter: function (opts) {
-                    if (loaded.commandline)
+                    if (loaded.commandline || ~opts.indexOf("c"))
                         commandline.widgets.updateVisibility();
                 }
             },
                         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,
                                       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"))
                 },
                 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))
                 // 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(),
 
 
                 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",
 
         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"],
             { 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,
             {
                 setter: function (value) {
                     prefs.safeSet("accessibility.typeaheadfind.enablesound", !value,
-                                  "See 'visualbell' option");
+                                  _("option.visualbell.safeSet"));
                     return value;
                 }
             });
     },
 
     mappings: function () {
                     return value;
                 }
             });
     },
 
     mappings: function () {
-        mappings.add([modes.MAIN], ["<F1>"],
+        mappings.add([modes.MAIN], ["<open-help>", "<F1>"],
             "Open the introductory help page",
             function () { dactyl.help(); });
 
             "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(); });
 
             "Open the single, consolidated help page",
             function () { ex.helpall(); });
 
@@ -1727,7 +1787,6 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 }
             }, {
                 argCount: "1",
                 }
             }, {
                 argCount: "1",
-                bang: true,
                 completer: function (context) {
                     context.ignoreCase = true;
                     completion.dialog(context);
                 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] || "";
             "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)) {
                               _("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();
                         item.doCommand();
+                    }
                 }
             }, {
                 argCount: "1",
                 }
             }, {
                 argCount: "1",
@@ -1793,7 +1854,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         });
 
         commands.add(["loadplugins", "lpl"],
         });
 
         commands.add(["loadplugins", "lpl"],
-            "Load all plugins immediately",
+            "Load all or matching plugins",
             function (args) {
                 dactyl.loadPlugins(args.length ? args : null, args.bang);
             },
             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,
                 bang: true,
                 keepQuotes: true,
                 serialGroup: 10,
-                serialize: function ()  [
+                serialize: function () [
                     {
                         command: this.name,
                         literalArg: options["loadplugins"].join(" ")
                     {
                         command: this.name,
                         literalArg: options["loadplugins"].join(" ")
@@ -1819,6 +1880,15 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 literal: 0
             });
 
                 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) {
         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)
             "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);
             },
             {
                 args.break = true;
                 util.rehash(args);
             },
             {
-                argCount: "0",
+                argCount: "0", // FIXME
                 options: [
                     {
                         names: ["+u"],
                 options: [
                     {
                         names: ["+u"],
@@ -1870,16 +1940,16 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
         commands.add(["res[tart]"],
             "Force " + config.appName + " to restart",
 
         commands.add(["res[tart]"],
             "Force " + config.appName + " to restart",
-            function () { dactyl.restart(); });
+            function () { dactyl.restart(); },
+            { argCount: "0" });
 
         function findToolbar(name) util.evaluateXPath(
 
         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) {
             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) {
             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();
                     }, {
                         action(toolbar);
                         events.checkFocus();
                     }, {
-                        argcount: "1",
+                        argCount: "1",
                         completer: function (context) {
                             completion.toolbar(context);
                             if (filter)
                         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),
 
             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),
             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",
             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"],
         }
 
         commands.add(["time"],
@@ -1916,9 +1986,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 args = args[0] || "";
 
                 if (args[0] == ":")
                 args = args[0] || "";
 
                 if (args[0] == ":")
-                    var method = function () commands.execute(args, null, true);
+                    var func = function () commands.execute(args, null, false);
                 else
                 else
-                    method = dactyl.userFunc(args);
+                    func = dactyl.userFunc(args);
 
                 try {
                     if (count > 1) {
 
                 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();
 
                         for (let i in util.interruptibleRange(0, count, 500)) {
                             let now = Date.now();
-                            method();
+                            func();
                             total += Date.now() - now;
                         }
 
                             total += Date.now() - now;
                         }
 
@@ -1953,16 +2023,16 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                         commandline.commandOutput(
                                 <table>
                                     <tr highlight="Title" align="left">
                         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>
-                                    <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();
                                 </table>);
                     }
                     else {
                         let beforeTime = Date.now();
-                        method();
+                        func();
 
                         if (special)
                             return;
 
                         if (special)
                             return;
@@ -1979,7 +2049,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                     dactyl.echoerr(e);
                 }
             }, {
                     dactyl.echoerr(e);
                 }
             }, {
-                argCount: "+",
+                argCount: "1",
                 bang: true,
                 completer: function (context) {
                     if (/^:/.test(context.filter))
                 bang: true,
                 completer: function (context) {
                     if (/^:/.test(context.filter))
@@ -2010,7 +2080,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                     vbs.setFrom = setFrom;
                 }
             }, {
                     vbs.setFrom = setFrom;
                 }
             }, {
-                argCount: "+",
+                argCount: "1",
                 completer: function (context) completion.ex(context),
                 count: true,
                 literal: 0,
                 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;
         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");
         };
 
         var toolbox = document.getElementById("navigator-toolbox");
@@ -2076,7 +2150,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
         dactyl.timeout(function () {
             try {
 
         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);
 
                 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
             }
 
             // 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);
 
             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);
                 });
 
                     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);
 
             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");
 
 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) {
 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;
 
         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); }
         checkDocument(doc, !id);
 }
 function cleanup() { init(null); }
@@ -55,7 +57,7 @@ function checkDocument(doc, disable, force) {
 }
 
 function chromeDocuments() {
 }
 
 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"]) {
     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
 // 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) {
     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;
         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;
 
             return -1;
 
-        let text = Editor.getEditor().value;
         // XXX
         if (count == null)
             count = 1;
 
         // 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.
      *
     },
 
     /**
      * 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
      * @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)
      */
     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);
 
 
         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);
     },
 
         io.run(args.shift(), args, blocking);
     },
@@ -266,7 +252,7 @@ var Editor = Module("editor", {
         let line, column;
 
         if (!forceEditing && textBox && textBox.type == "password") {
         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);
                 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 {
             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") || "";
         }
 
         let origGroup = textBox && textBox.getAttributeNS(NS, "highlight") || "";
@@ -318,19 +304,25 @@ var Editor = Module("editor", {
             lastUpdate = Date.now();
 
             let val = tmpfile.read();
             lastUpdate = Date.now();
 
             let val = tmpfile.read();
-            if (textBox)
+            if (textBox) {
                 textBox.value = val;
                 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 {
             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)
             }
         }
 
         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");
 
             if (textBox) {
                 highlight.highlightNode(textBox, origGroup + " EditorEditing");
@@ -338,8 +330,7 @@ var Editor = Module("editor", {
             }
 
             if (!tmpfile.write(text))
             }
 
             if (!tmpfile.write(text))
-                throw Error("Input contains characters not valid in the current " +
-                            "file encoding");
+                throw Error(_("io.cantEncode"));
 
             var lastUpdate = Date.now();
 
 
             var lastUpdate = Date.now();
 
@@ -414,9 +405,9 @@ var Editor = Module("editor", {
             elem = dactyl.focusedElement || document.commandDispatcher.focusedWindow;
         dactyl.assert(elem);
 
             elem = dactyl.focusedElement || document.commandDispatcher.focusedWindow;
         dactyl.assert(elem);
 
-        if (elem instanceof Element)
-            return elem.QueryInterface(Ci.nsIDOMNSEditableElement).editor;
         try {
         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);
             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 () {
         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);
             });
 
                 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],
         mappings.add([modes.INSERT],
-            ["<Space>", "<Return>"], "Expand insert mode abbreviation",
+            ["<Space>", "<Return>"], "Expand Insert mode abbreviation",
             function () {
                 editor.expandAbbreviation(modes.INSERT);
             function () {
                 editor.expandAbbreviation(modes.INSERT);
-                return Events.PASS;
+                return Events.PASS_THROUGH;
             });
 
         mappings.add([modes.INSERT],
             });
 
         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
             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],
 
         // 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],
             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],
             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);
             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 }) {
         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);
             },
                 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 }) {
         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);
             },
                 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 }) {
         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);
             },
                 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 }) {
         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);
             },
                 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)))
 
         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);
 
              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",
              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",
     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))
                 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;
                 },
                         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);
                 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 = [];
 
         this.buffer = "";
         this.events = [];
 
+        events.dbg("STACK " + mode);
+
         let main = { __proto__: mode.main, params: mode.params };
         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");
 
 
         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;
         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.preExecute)
                 input.preExecute = params.preExecute;
+
             if (params.postExecute)
                 input.postExecute = params.postExecute;
             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;
             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"];
             }
 
         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));
     },
 
             this.processors.unshift(KeyProcessor(modes.BASE, hive));
     },
 
+    passUnknown: Class.memoize(function () options.get("passunknown").getKey(this.modes)),
+
     notify: function () {
     notify: function () {
+        events.dbg("NOTIFY()");
         events.keyEvents = [];
         events.processor = null;
         events.keyEvents = [];
         events.processor = null;
-        if (!this.execute(Events.KILL, true)) {
+        if (!this.execute(undefined, true)) {
             events.processor = this;
             events.keyEvents = this.keyEvents;
         }
             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) {
                                 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.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)) {
 
             for (var action in values(this.actions)) {
                 while (callable(action)) {
+                    length = action.eventLength;
                     action = dactyl.trapErrors(action);
                     action = dactyl.trapErrors(action);
-                    events.dbg("ACTION RES: " + this._result(action));
+                    events.dbg("ACTION RES: " + length + " " + this._result(action));
                 }
                 if (action !== Events.PASS)
                     break;
             }
 
                 }
                 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) {
             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;
             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.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)
             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;
 
             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)
 
         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 });
 
         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);
             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;
         }
 
         return this.processors.length === 0;
@@ -137,7 +171,7 @@ var ProcessorStack = Class("ProcessorStack", {
         let actions = [];
         let processors = [];
 
         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);
 
         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;
 
             if (res === Events.KILL)
                 break;
 
-            buffer = buffer || input.inputBuffer;
-
             if (callable(res))
                 actions.push(res);
 
             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("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);
 
 
         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)
         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);
             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;
                 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);
 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"]];
             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,
         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 () {
     dbg: function () {},
 
     init: function () {
-        const self = this;
         this.keyEvents = [];
 
         update(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}>
         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"
                     <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"]
         };
 
             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._key_key = {};
         this._code_key = {};
         this._key_code = {};
+        this._code_nativeKey = {};
 
         for (let list in values(this._keyTable))
             for (let v in values(list)) {
 
         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)) {
             }
 
         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")];
             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._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();
     },
 
     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 }),
 
     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()
             });
 
                 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;
             dactyl.echomsg(_("macro.recorded", this.recording));
         }
         this._recording = macro || null;
@@ -640,10 +674,15 @@ var Events = Module("events", {
             if (quiet)
                 commandline.quiet = quiet;
 
             if (quiet)
                 commandline.quiet = quiet;
 
+            keys = mappings.expandLeader(keys);
+
             for (let [, evt_obj] in Iterator(events.fromString(keys))) {
                 let now = Date.now();
             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 });
                     let evt = update({}, evt_obj, { type: type });
+                    if (type !== "keypress" && !evt.keyCode)
+                        evt.keyCode = evt._keyCode || 0;
 
                     if (isObject(noremap))
                         update(evt, noremap);
 
                     if (isObject(noremap))
                         update(evt, noremap);
@@ -654,9 +693,18 @@ var Events = Module("events", {
                     evt.dactylSavedEvents = savedEvents;
                     this.feedingEvent = evt;
 
                     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);
                 }
                     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) {
      * @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
             },
             HTML: {
                 type: type, bubbles: true, cancelable: false
             },
@@ -713,22 +760,31 @@ var Events = Module("events", {
                 relatedTarget: null
             }
         };
                 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"];
         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;
     },
 
         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.
     /**
      * 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("");
     },
 
         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];
         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.
 
     /**
      * 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 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" };
 
             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) ||
                 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 (keyname.length == 1) { // normal characters
-                        if (evt_obj.shiftKey) {
+                        if (evt_obj.shiftKey)
                             keyname = keyname.toUpperCase();
                             keyname = keyname.toUpperCase();
-                            if (keyname == keyname.toLowerCase())
-                                evt_obj.dactylShift = true;
-                        }
 
                         evt_obj.charCode = keyname.charCodeAt(0);
 
                         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
                         evt_obj.dactylString = "<" + this._key_key[keyname] + ">";
                     }
                     else if (/mouse$/.test(keyname)) { // mouse events
@@ -875,8 +937,6 @@ var Events = Module("events", {
                     continue;
                 }
             }
                     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)
 
             // 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>
 
             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.shiftKey && Ci.nsIDOMNSEvent.SHIFT_MASK)
-                              | (evt_obj.metaKey && Ci.nsIDOMNSEvent.META_MASK);
+                              | (evt_obj.metaKey  && Ci.nsIDOMNSEvent.META_MASK);
 
             out.push(evt_obj);
         }
 
             out.push(evt_obj);
         }
@@ -911,6 +971,8 @@ var Events = Module("events", {
         let key = null;
         let modifier = "";
 
         let key = null;
         let modifier = "";
 
+        if (event.globKey)
+            modifier += "*-";
         if (event.ctrlKey)
             modifier += "C-";
         if (event.altKey)
         if (event.ctrlKey)
             modifier += "C-";
         if (event.altKey)
@@ -931,6 +993,7 @@ var Events = Module("events", {
                             key = key.toUpperCase();
                         else
                             key = key.toLowerCase();
                             key = key.toUpperCase();
                         else
                             key = key.toLowerCase();
+
                     if (!modifier && /^[a-z0-9]$/i.test(key))
                         return key;
                 }
                     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
                 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;
                         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;
                 }
             }
                         return key;
                 }
             }
-            if (key == null)
+            if (key == null) {
+                if (event.shiftKey)
+                    modifier += "S-";
                 key = this._key_key[event.dactylKeyname] || event.dactylKeyname;
                 key = this._key_key[event.dactylKeyname] || event.dactylKeyname;
+            }
             if (key == null)
                 return null;
         }
             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}
      *
      * @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>",
 
     /**
     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>",
 
      *
      * @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;
     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;
 
         if (buffer.loaded)
             return true;
 
-        dactyl.echo(_("macro.loadWaiting"), commandline.DISALLOW_MULTILINE);
+        dactyl.echo(_("macro.loadWaiting"), commandline.FORCE_SINGLELINE);
 
         const maxWaitTime = (time || 25);
 
         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));
 
         if (!buffer.loaded)
             dactyl.echoerr(_("macro.loadFailed", maxWaitTime));
 
@@ -1115,14 +1252,19 @@ var Events = Module("events", {
 
             let win = (elem.ownerDocument || elem).defaultView || elem;
 
 
             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])) {
                 && 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.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)
                     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;
                     });
                 }
                         elem.dactylKeyPress = undefined;
                     });
                 }
@@ -1214,20 +1356,19 @@ var Events = Module("events", {
 
                     let ignore = false;
 
 
                     let ignore = false;
 
-                    if (modes.main == modes.PASS_THROUGH)
+                    if (mode.main == modes.PASS_THROUGH)
                         ignore = !Events.isEscape(key) && key != "<C-v>";
                         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) {
                         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))
                             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;
 
                         else
                             ignore = true;
 
-                        if (ignore && !Events.isEscape(key))
-                            modes.pop();
+                        modes.pop();
                     }
                     else if (!event.isMacro && !event.noremap && events.shouldPass(event))
                         ignore = true;
                     }
                     else if (!event.isMacro && !event.noremap && events.shouldPass(event))
                         ignore = true;
@@ -1277,25 +1418,40 @@ var Events = Module("events", {
         },
 
         keyup: function onKeyUp(event) {
         },
 
         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) ||
                     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.
 
             // 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) {
                 event.stopPropagation();
         },
         keydown: function onKeyDown(event) {
+            if (!event.isMacro)
+                this.passing = false;
             this.events.keyup.call(this, event);
         },
 
             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;
 
             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;
                 win.document.dactylFocusAllowed = true;
+            }
         },
 
         popupshown: function onPopupShown(event) {
         },
 
         popupshown: function onPopupShown(event) {
@@ -1322,8 +1481,7 @@ var Events = Module("events", {
                     modes.push(modes.MENU);
         },
 
                     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);
             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
 
             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;
                  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) {
             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 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,
                                  HTMLObjectElement, HTMLSelectElement,
                                  HTMLTextAreaElement,
-                                 Ci.nsIDOMXULTreeElement, Ci.nsIDOMXULTextBoxElement]) ||
+                                 Ci.nsIDOMXULTextBoxElement]) ||
                elem instanceof Window && Editor.getEditor(elem);
     },
 
                elem instanceof Window && Editor.getEditor(elem);
     },
 
@@ -1480,12 +1647,13 @@ var Events = Module("events", {
                 else
                     dactyl.echoerr(_("error.argumentRequired"));
             }, {
                 else
                     dactyl.echoerr(_("error.argumentRequired"));
             }, {
+                argCount: "?",
                 bang: true,
                 completer: function (context) completion.macro(context),
                 literal: 0
             });
 
                 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: "?",
             "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],
     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;
             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); });
 
             ["<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)
             ["<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);
             });
 
                 modes.push(modes.QUOTE);
             });
 
+        mappings.add([modes.BASE],
+            ["<CapsLock>"], "Do Nothing",
+            function () {});
+
         mappings.add([modes.BASE],
             ["<Nop>"], "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)));
             "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"));
                 },
 
                     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),
 
 
                 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.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;
                     });
                     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",
 
         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",
 
         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">
 
         <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: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: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>
                 </xsl:when>
                 <xsl:otherwise>
                     <span>
         <a>
             <xsl:choose>
                 <xsl:when test="not(@topic)"/>
         <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: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
                     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"/>
     <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>
             <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;"/>
         <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>
             <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;"/>
         <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:text> </xsl:text>
             <xsl:apply-templates select="node()" mode="help-1"/>
         </p>
             <xsl:apply-templates select="@*|node()" mode="help-1"/>
         </div>
     </xsl:template>
             <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>
             <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.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.generate();
 
         this.show();
+        this.magic = true;
 
         if (this.validHints.length == 0) {
             dactyl.beep();
 
         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 if (this.validHints.length == 1 && !this.continue)
             this.process(false);
-        else // Ticket #185
+        else
             this.checkUnique();
     },
 
             this.checkUnique();
     },
 
@@ -69,6 +70,15 @@ var HintSession = Class("HintSession", CommandMode, {
                 hints.setClass(this.imgSpan, this.valid ? val : null);
         },
 
                 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,
         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.span.style.display = (val ? "" : "none");
             if (this.imgSpan)
                 this.imgSpan.style.display = (val ? "" : "none");
-
             this.active = this.active;
         }
     },
             this.active = this.active;
         }
     },
@@ -92,8 +101,8 @@ var HintSession = Class("HintSession", CommandMode, {
             if (hints.hintSession == this)
                 hints.hintSession = null;
             if (this.top) {
             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);
             }
 
             this.removeHints(0);
@@ -155,6 +164,21 @@ var HintSession = Class("HintSession", CommandMode, {
         return res.reverse().join("");
     },
 
         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.
     /**
      * 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;
 
 
         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 };
         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();
 
         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;
 
                 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;
             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;
 
             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;
 
             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];
 
                 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();
 
                 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);
 
                 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);
                 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.
      */
      *
      * @param {Event} event The event to handle.
      */
@@ -399,12 +437,17 @@ var HintSession = Class("HintSession", CommandMode, {
         return PASS;
     },
 
         return PASS;
     },
 
-    onResize: function () {
+    onResize: function onResize() {
         this.removeHints(0);
         this.generate(this.top);
         this.show();
     },
 
         this.removeHints(0);
         this.generate(this.top);
         this.show();
     },
 
+    _onResize: function _onResize() {
+        if (this.magic)
+            hints.resizeTimer.tell();
+    },
+
     /**
      * Finish hinting.
      *
     /**
      * 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
 
         // 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;
 
         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
             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);
 
             if (n--)
                 this.timeout(next, 50);
@@ -469,13 +506,14 @@ var HintSession = Class("HintSession", CommandMode, {
                 modes.push(modes.IGNORE, modes.HINTS);
         }
 
                 modes.push(modes.IGNORE, modes.HINTS);
         }
 
+        dactyl.trapErrors("action", this.hintMode,
+                          elem, elem.href || elem.src || "",
+                          this.extendedhintCount, top);
+
         this.timeout(function () {
         this.timeout(function () {
-            if ((modes.extended & modes.HINTS) && !this.continue)
+            if (modes.main == modes.IGNORE && !this.continue)
                 modes.pop();
             commandline.lastEcho = null; // Hack.
                 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);
             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)) {
      */
     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 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;
                 this.pageHints[i].valid = false;
+            }
         }
         styles.system.remove("hint-positions");
 
         }
         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 ? "☑" : "☐"));
                         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);
                     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;
                 hint.active = activeHint == hintnum;
+
                 this.validHints.push(hint);
                 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)) {
         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;
             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 ()
         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.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));
         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(),
 
     /**
     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 {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;
 
 
         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"])) {
             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") {
                             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) {
                         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];
                     }
                         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.
          *
          * 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.
          * @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], "", {
     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))];
             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);
             },
                 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();
                 this.accepted = true;
                 modes.pop();
-            },
+            }
         });
     },
 
         });
     },
 
@@ -1112,14 +1177,14 @@ var Hints = Module("hints", {
         return -1;
     },
 
         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,
             .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
             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"],
     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"],
             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, [";"],
             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;"],
             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 });
 
             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",
             "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"]
                 "[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_)
             {
                 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))
                         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"],
             });
 
         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]," +
                           "[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);
             {
                 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);
                 },
                 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"],
             { 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>.",
             "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,
             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!
             };
         }).toArray();
         root.containerOpen = false; // close a container after using it!
@@ -230,8 +230,8 @@ var History = Module("history", {
                                 "uri",
                                 "visitcount"
                             ].map(function (order) [
                                 "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.
  *
  * 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.
  *     *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) {
  */
 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;
 
         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)
 
         if (extraInfo)
-            update(this, extraInfo);
+            this.update(extraInfo);
     },
 
     name: Class.memoize(function () this.names[0]),
 
     },
 
     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)],
     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} 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,
     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,
 
      */
     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.
 
     /**
      * Execute the action for this mapping.
@@ -119,7 +114,7 @@ var Map = Class("Map", {
             mappings.repeat = repeat;
 
         if (this.executing)
             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 {
         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.
      * @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 || {};
 
     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;
         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),
 
 
     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())
 
     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;
     },
 
                     yield map;
     },
 
@@ -330,8 +348,8 @@ var Mappings = Module("mappings", {
     /**
      * Adds a new default key mapping.
      *
     /**
      * 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.
      * @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.
      *
     /**
      * 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
      * @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*.
      *
     /**
      * 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}
      */
      * @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*.
      *
      * 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.
      * @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))
      */
     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
 
     /**
      * 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) {
      */
     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)])
         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/>
         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;"/>
                 {
                 </tr>
                 <col style="min-width: 6em; padding-right: 1em;"/>
                 {
@@ -459,6 +477,11 @@ var Mappings = Module("mappings", {
                     return;
                 }
 
                     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;
                 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"],
                         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),
                             noremap: args["-builtin"],
                             persist: !args["-nopersist"],
                             get rhs() String(this.action),
@@ -484,92 +507,93 @@ var Mappings = Module("mappings", {
             }
 
             const opts = {
             }
 
             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))
             };
             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;
             }
 
                             yield map;
             }
 
@@ -606,6 +630,7 @@ var Mappings = Module("mappings", {
                         dactyl.echoerr(_("map.noSuch", args[0]));
                 },
                 {
                         dactyl.echoerr(_("map.noSuch", args[0]));
                 },
                 {
+                    identifier: "unmap",
                     argCount: "?",
                     bang: true,
                     completer: opts.completer,
                     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))
             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) {
         };
 
         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))];
         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"],
         }
 
         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)],
             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)
 
         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
 
                 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))
                     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: [
                                     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))
                                  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: 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)
         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)
         }
         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) {
                 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 {
                     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));
 
             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
             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] || "";
             "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);
                 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);
 
         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;
             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],
             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) {
         }, {
             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",
         });
         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"),
         }, {
 
             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],
             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",
         });
         this.addMode("OUTPUT_MULTILINE", {
             description: "Active when the multi-line output buffer is open",
-            bases: [this.COMMAND],
+            bases: [this.NORMAL]
         });
 
         this.addMode("INPUT", {
         });
 
         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();
     },
     cleanup: function cleanup() {
         modes.reset();
@@ -214,7 +268,7 @@ var Modes = Module("modes", {
 
         let val = this._modeMap[this._main].display();
         if (val)
 
         let val = this._modeMap[this._main].display();
         if (val)
-            return "-- " + val + " --" + macromode;;
+            return "-- " + val + " --" + macromode;
         return macromode;
     },
 
         return macromode;
     },
 
@@ -245,7 +299,7 @@ var Modes = Module("modes", {
         if (!mode.extended)
             this._mainModes.push(mode);
 
         if (!mode.extended)
             this._mainModes.push(mode);
 
-        dactyl.triggerObserver("mode-add", mode);
+        dactyl.triggerObserver("modes.add", mode);
     },
 
     dumpStack: function dumpStack() {
     },
 
     dumpStack: function dumpStack() {
@@ -270,9 +324,13 @@ var Modes = Module("modes", {
 
     // show the current mode string in the command line
     show: function show() {
 
     // show the current mode string in the command line
     show: function show() {
+        if (!loaded.modes)
+            return;
+
         let msg = null;
         let msg = null;
-        if (options.get("showmode").getKey(this.main.name, true))
+        if (options.get("showmode").getKey(this.main.allBases, false))
             msg = this._getModeMessage();
             msg = this._getModeMessage();
+
         if (msg || loaded.commandline)
             commandline.widgets.mode = msg || null;
     },
         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);
             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);
 
 
         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();
     },
 
         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,
         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;
         }
     },
 
 
             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)
         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);
     },
 
         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),
         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++,
                 id: 1 << Modes.Mode._id++,
+                description: name,
                 name: name,
                 params: params || {}
             }, options);
         },
 
                 name: name,
                 params: params || {}
             }, options);
         },
 
+        description: Messages.Localized(""),
+
+        displayName: Class.memoize(function () this.name.split("_").map(util.capitalize).join(" ")),
+
         isinstance: function isinstance(obj)
         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 () {
 
         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))
             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);
                 }
                     res.push(mode);
                     queue.push.apply(queue, mode.bases);
                 }
@@ -454,8 +520,6 @@ var Modes = Module("modes", {
 
         get count() !this.insert,
 
 
         get count() !this.insert,
 
-        get description() this._display,
-
         _display: Class.memoize(function _display() this.name.replace("_", " ", "g")),
 
         display: function display() 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)),
 
 
         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,
 
 
         get mask() this,
 
@@ -521,7 +587,7 @@ var Modes = Module("modes", {
     mappings: function initMappings() {
         mappings.add([modes.BASE, modes.NORMAL],
             ["<Esc>", "<C-[>"],
     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],
             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(); });
 
             "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",
         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() {
 
         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",
         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));
     },
     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"
                     <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"
                                       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"
                                       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"
                                       command="cmd_copy"/>
                             <menuitem id="dactyl-context-selectall"
-                                      label="Select All"
+                                      label={_("mow.contextMenu.selectAll")}
                                       command="cmd_selectAll"/>
                         </menupopup>
                     </popupset>
                                       command="cmd_selectAll"/>
                         </menupopup>
                     </popupset>
@@ -131,8 +131,8 @@ var MOW = Module("mow", {
             catch (e) {
                 util.reportError(e);
                 util.dump(data);
             catch (e) {
                 util.reportError(e);
                 util.dump(data);
-                this.messages.push(data);
             }
             }
+            this.messages.push(data);
         }
         else {
             let style = isString(data) ? "pre" : "nowrap";
         }
         else {
             let style = isString(data) ? "pre" : "nowrap";
@@ -278,11 +278,11 @@ var MOW = Module("mow", {
         let elem = this.widget.contentDocument.documentElement;
 
         if (showHelp)
         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)))
         else if (force || (options["more"] && Buffer.isScrollable(elem, 1)))
-            this.widgets.message = ["MoreMsg", "-- More --"];
+            this.widgets.message = ["MoreMsg", _("mow.more")];
         else
         else
-            this.widgets.message = ["Question", "Press ENTER or type command to continue"];
+            this.widgets.message = ["Question", _("mow.continue")];
     },
 
     visible: Modes.boundProperty({
     },
 
     visible: Modes.boundProperty({
@@ -311,7 +311,7 @@ var MOW = Module("mow", {
         mappings.add([modes.COMMAND],
             ["g<lt>"], "Redisplay the last command output",
             function () {
         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");
             });
 
                 mow.echo(mow.lastOutput, "Normal");
             });
 
@@ -393,3 +393,5 @@ var MOW = Module("mow", {
             "boolean", true);
     }
 });
             "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.
      * 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 = [];
      */
     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 = [
                         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");
                             ].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[
             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>
                 }
                     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;
                 }
                     -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[
             ]]>)({ 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">
                         <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>
                                 <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;
     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
             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.progress = 0;
             if (flags & Ci.nsIWebProgressListener.STATE_STOP) {
                 this.progress = "";
-                this.status = buffer.uri;
+                this.updateStatus();
             }
         },
         "browser.statusChange": function onStatusChange(webProgress, request, status, message) {
             }
         },
         "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() {
 
     // update all fields of the statusline
     update: function update() {
-        this.status = buffer.uri;
+        this.updateStatus();
         this.inputBuffer = "";
         this.progress = "";
         this.updateTabCount();
         this.inputBuffer = "";
         this.progress = "";
         this.updateTabCount();
@@ -183,34 +188,8 @@ var StatusLine = Module("statusline", {
         this.updateZoomLevel();
     },
 
         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
 
     /**
      * 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)
             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 += "-";
                     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("");
 
                     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)
         }
 
         if (url == "about:blank") {
             if (!buffer.title)
-                url = "[No Name]";
+                url = _("buffer.noName");
         }
         else {
         }
         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)
         }
 
         if (modified)
@@ -260,10 +236,20 @@ var StatusLine = Module("statusline", {
 
         this.widgets.url.value = url;
         this._status = uri;
 
         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 }),
 
 
     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,
      */
     progress: Modes.boundProperty({
         get: function progress() this._progress,
-        set: function  progress(progress) {
+        set: function progress(progress) {
             this._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)
                 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 = "["
                 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._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)
         // 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);
             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]),
     },
 
     _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_);
         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);
     },
 
         statusline.updateTabCount(true);
     },
 
-    _onTabSelect: function () {
+    _onTabSelect: function _onTabSelect() {
         // TODO: is all of that necessary?
         //       I vote no. --Kris
         modes.reset();
         // 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++)
     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
     //        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 = {};
         let tab = this.getTab(tabIndex);
         if (!tab.dactylStore)
             tab.dactylStore = {};
@@ -149,10 +162,10 @@ var Tabs = Module("tabs", {
     get localStore() this.getLocalStore(),
 
     /**
     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.
      */
      *     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.
 
     /**
      * 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.
      */
      * @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)
         Tabs.copyTab(newTab, tab);
 
         if (activate)
@@ -176,7 +189,7 @@ var Tabs = Module("tabs", {
      *
      * @param {Object} tab The tab to detach.
      */
      *
      * @param {Object} tab The tab to detach.
      */
-    detachTab: function (tab) {
+    detachTab: function detachTab(tab) {
         if (!tab)
             tab = config.tabbrowser.mTabContainer.selectedItem;
 
         if (!tab)
             tab = config.tabbrowser.mTabContainer.selectedItem;
 
@@ -191,7 +204,7 @@ var Tabs = Module("tabs", {
      *     document.
      */
     // FIXME: Only called once...necessary?
      *     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;
         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}
      */
      *
      * @returns {Window}
      */
-    getGroups: function () {
+    getGroups: function getGroups() {
         if ("_groups" in this)
             return this._groups;
 
         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
      * 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}
      */
      * @returns {Object}
      */
-    getTab: function (index) {
+    getTab: function getTab(index, visible) {
         if (index instanceof Node)
             return index;
         if (index != null)
         if (index instanceof Node)
             return index;
         if (index != null)
-            return config.tabbrowser.mTabs[index];
+            return this[visible ? "visibleTabs" : "allTabs"][index];
         return config.tabbrowser.mCurrentTab;
     },
 
         return config.tabbrowser.mCurrentTab;
     },
 
@@ -243,7 +258,7 @@ var Tabs = Module("tabs", {
      * @param {boolean} visible Whether to consider only visible tabs.
      * @returns {number}
      */
      * @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);
     },
         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
      */
      * - "-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 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;
 
             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 (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;
 
         else
             return -1;
 
@@ -290,7 +306,7 @@ var Tabs = Module("tabs", {
      *
      * @param {Object} tab The tab to keep.
      */
      *
      * @param {Object} tab The tab to keep.
      */
-    keepOnly: function (tab) {
+    keepOnly: function keepOnly(tab) {
         config.tabbrowser.removeAllTabsBut(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.
      */
      * @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);
     },
 
         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.
      */
      * @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);
     },
 
         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.
      */
      * @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;
 
         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.
      */
      * @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;
         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.
      */
      * @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.
      */
      * @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();
         let index = tabs.indexFromSpec(spec, wrap);
         if (index == -1)
             dactyl.beep();
@@ -407,7 +418,7 @@ var Tabs = Module("tabs", {
     /**
      * Selects the alternate tab.
      */
     /**
      * Selects the alternate tab.
      */
-    selectAlternateTab: function () {
+    selectAlternateTab: function selectAlternateTab() {
         dactyl.assert(tabs.alternate != null && tabs.getTab() != tabs.alternate,
                       _("buffer.noAlternate"));
         tabs.select(tabs.alternate);
         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.
      */
      *
      * @param {Object} tab The tab to stop loading.
      */
-    stop: function (tab) {
+    stop: function stop(tab) {
         if (config.stop)
             config.stop(tab);
         else
         if (config.stop)
             config.stop(tab);
         else
@@ -428,7 +439,7 @@ var Tabs = Module("tabs", {
     /**
      * Stops loading all tabs.
      */
     /**
      * Stops loading all tabs.
      */
-    stopAll: function () {
+    stopAll: function stopAll() {
         for (let [, browser] in this.browsers)
             browser.stop();
     },
         for (let [, browser] in this.browsers)
             browser.stop();
     },
@@ -447,7 +458,7 @@ var Tabs = Module("tabs", {
      *
      */
     // FIXME: help!
      *
      */
     // 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;
         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
      */
      * @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
         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) {
         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)
                     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
                         dactyl.echoerr(_("buffer.noMatching", arg));
-                }
-                else // just remove the current tab
-                    tabs.remove(tabs.getTab(), Math.max(count, 1), special);
             }, {
                 argCount: "?",
                 bang: true,
             }, {
                 argCount: "?",
                 bang: true,
@@ -580,19 +558,87 @@ var Tabs = Module("tabs", {
                 privateData: true
             });
 
                 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) {
         commands.add(["keepa[lt]"],
             "Execute a command without changing the current alternate buffer",
             function (args) {
-                let alternate = tabs.alternate;
-
                 try {
                 try {
-                    commands.execute(args[0] || "", null, true);
+                    dactyl.execute(args[0], null, true);
                 }
                 finally {
                 }
                 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
                 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;
             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
                 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
                     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);
                 }
                 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) {
 
                     // 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
                         index = arg - 1;
                     }
                     else
@@ -709,11 +755,9 @@ var Tabs = Module("tabs", {
                 });
 
             commands.add(["quita[ll]", "qa[ll]"],
                 });
 
             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",
 
             commands.add(["reloada[ll]"],
                 "Reload all tab pages",
@@ -727,21 +771,24 @@ var Tabs = Module("tabs", {
                 function () { tabs.stopAll(); },
                 { argCount: "0" });
 
                 function () { tabs.stopAll(); },
                 { argCount: "0" });
 
-            // TODO: add count support
+            // TODO: add count and bang multimatch support - unify with :buffer nonsense
             commands.add(["tabm[ove]"],
             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];
 
                 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]"],
                 });
 
             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) {
             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();
                     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
                     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;
 
 
                     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: "+",
                     browser.selectedTab = dummy; // required
                     browser.swapBrowsersAndCloseOther(dummy, config.tabbrowser.mCurrentTab);
                 }, {
                     argCount: "+",
+                    literal: 1,
                     completer: function (context, args) {
                     completer: function (context, args) {
-                        if (args.completeArg == 0) {
+                        switch (args.completeArg) {
+                        case 0:
                             context.filters.push(function ({ item }) item != window);
                             completion.window(context);
                             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",
                         tabs.tabStyle.enabled = true;
                     else {
                         prefs.safeSet("browser.tabs.autoHide", value === "multitab",
-                                      "See 'showtabline' option.");
+                                      _("option.showtabline.safeSet"));
                         tabs.tabStyle.enabled = false;
                     }
                         tabs.tabStyle.enabled = false;
                     }
+
                     if (value !== "multitab" || !dactyl.has("Gecko2"))
                     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;
                     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) {
                     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]]),
                         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;
                     }
                 });
                         return newValues;
                     }
                 });
@@ -1031,11 +1101,8 @@ var Tabs = Module("tabs", {
                 {
                     values: {
                         "all": "All commands",
                 {
                     values: {
                         "all": "All commands",
-                        "addons": ":addo[ns] command",
-                        "downloads": ":downl[oads] command",
                         "extoptions": ":exto[ptions] command",
                         "help": ":h[elp] command",
                         "extoptions": ":exto[ptions] command",
                         "help": ":h[elp] command",
-                        "javascript": ":javascript! or :js! command",
                         "prefs": ":pref[erences]! or :prefs! command"
                     },
                     has: Option.has.toggleAll
                         "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,
                         }
 
                         prefs.safeSet("browser.link.open_newwindow", open,
-                                      "See 'popups' option.");
+                                      _("option.popups.safeSet"));
                         prefs.safeSet("browser.link.open_newwindow.restriction", restriction,
                         prefs.safeSet("browser.link.open_newwindow.restriction", restriction,
-                                      "See 'popups' option.");
+                                      _("option.popups.safeSet"));
                         return values;
                     },
                     values: {
                         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
         <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>
         </p>
 
         <p>Available <oa>events</oa>:</p>
 </item>
 <h2 tag="autocmd-examples">Examples</h2>
 
 </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>
 
 
 <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>
 
 
 <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.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>
 
 <item>
-    <tags><![CDATA[<A-b>]]></tags>
+    <tags><![CDATA[<pass-next-key-builtin> <A-b>]]></tags>
     <spec><![CDATA[<A-b>]]></spec>
     <description>
         <p>
     <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>
 
     </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>
 <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>
                 <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>
                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>
 </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>
     <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>
 </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>
     <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>
 </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>
     <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>.
 
         <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>
             <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>
     <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>
 </item>
 
 <item>
-    <tags>gH</tags>
+    <tags><![CDATA[<tab-open-homepage> gH]]></tags>
     <strut/>
     <spec>gH</spec>
     <description>
     <strut/>
     <spec>gH</spec>
     <description>
@@ -324,7 +329,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
 </item>
 
 <item>
 </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>
     <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>
 </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>
     <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>
 
     </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>
 <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>
 <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>
     <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>
 
 
 <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/>
 <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>
     <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>
         </p>
     </description>
 </item>
@@ -512,7 +507,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
 </item>
 
 <item>
 </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>
     <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>
 
     </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>
 <h2 tag="current-directory">The current directory</h2>
 
 <item>
index 66d0d978486f2db0d5cd046560cfff6625e7a3b3..7f514a91f46d58d8dc2fa02f932347bce07e8f1c 100644 (file)
@@ -55,7 +55,7 @@
 </item>
 
 <item>
 </item>
 
 <item>
-    <tags>gf</tags>
+    <tags><![CDATA[<view-source> gf]]></tags>
     <strut/>
     <spec>gf</spec>
     <description>
     <strut/>
     <spec>gf</spec>
     <description>
@@ -67,7 +67,7 @@
 </item>
 
 <item>
 </item>
 
 <item>
-    <tags>gF</tags>
+    <tags><![CDATA[<view-source-externally> gF]]></tags>
     <strut/>
     <spec>gF</spec>
     <description>
     <strut/>
     <spec>gF</spec>
     <description>
 </item>
 
 <item>
 </item>
 
 <item>
-    <tags><![CDATA[<Home> gg]]></tags>
+    <tags><![CDATA[<scroll-top> <Home> gg]]></tags>
     <strut/>
     <spec><oa>count</oa>gg</spec>
     <description>
     <strut/>
     <spec><oa>count</oa>gg</spec>
     <description>
 </item>
 
 <item>
 </item>
 
 <item>
-    <tags><![CDATA[<End> G]]></tags>
+    <tags><![CDATA[<scroll-bottom> <End> G]]></tags>
     <strut/>
     <spec><oa>count</oa>G</spec>
     <description>
     <strut/>
     <spec><oa>count</oa>G</spec>
     <description>
 </item>
 
 <item>
 </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>
     <strut/>
     <spec><oa>count</oa>h</spec>
     <description>
 </item>
 
 <item>
 </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>
     <strut/>
     <spec><oa>count</oa>j</spec>
     <description>
 </item>
 
 <item>
 </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>
     <strut/>
     <spec><oa>count</oa>k</spec>
     <description>
 </item>
 
 <item>
 </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>
     <strut/>
     <spec><oa>count</oa>l</spec>
     <description>
 </item>
 
 <item>
 </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>
     <strut/>
     <spec><oa>count</oa>&lt;C-b></spec>
     <description>
 </item>
 
 <item>
 </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>
     <strut/>
     <spec><oa>count</oa>&lt;C-f></spec>
     <description>
     </description>
 </item>
 
     </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>
 <h2 tag="zooming zoom">Zooming</h2>
 
 <p>
index 0ad333296d8fdd1aab1e97254da5aabd1c43bba0..b44c8bb3c904a79685c43a2893ba53f397212a6f 100644 (file)
@@ -9,11 +9,11 @@
     xmlns="&xmlns.dactyl;"
     xmlns:html="&xmlns.html;">
 
     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>
 <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
     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
 
 <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>
 
     other items on a page).
 </p>
 
@@ -60,7 +60,7 @@
     <tags><![CDATA[c_<C-c>]]></tags>
     <spec>&lt;C-c></spec>
     <description short="true">
     <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>
 
     </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,
             <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>
 
                 <t>status-line</t>.
             </p>
 
                 <ex>:help</ex> <t>pattern</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>
             <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>
             </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>
             </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
             </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>
             </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
 
                 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>
             </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
             <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>
             </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">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
             <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>
             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>
 
     </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>
 <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
     </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.
         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;">
 
     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
 <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
     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>
 </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>
     <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
             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>
         </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>
                 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>
         </dl>
     </description>
 </item>
@@ -64,7 +64,7 @@
     <spec>F<a>hint</a></spec>
     <description>
         <p>
     <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
             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>
 
 <item>
     <tags>extended-hints</tags>
-    <tags>; ExtendedHint</tags>
+    <tags>;</tags>
     <strut/>
     <spec>;<a>mode</a><a>hint</a></spec>
     <description>
         <p>
     <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
             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>
         </p>
 
         <p><a>mode</a> may be one of:</p>
     <spec>g;<a>mode</a><a>hint</a></spec>
     <description>
         <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
             <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"?>
 
 <?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"
 
 <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
 <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
     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>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
 </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,
     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>
 
     Other modes can be specified using the -modes option described below.
 </p>
 
     saved via the <ex>:mk&dactyl.name;rc</ex> command.
 </warning>
 
     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>
 <h3 tag=":map-commands">Map commands</h3>
 
 <item>
     </description>
 </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:
 <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>-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>-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>
     <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>
 <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>
     <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>
 
     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>
 <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="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>
 
     <li><k name="S-␣" link="false"/>: The shift key.</li>
 </ol>
 
 
 <dl dt="width: 10em;">
     <dt><k link="false">xc</k></dt>
 
 <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>
 
     <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>
         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>
 
     <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>
 
     <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>
 
     <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>
 </dl>
 
 <h3 tag=":map-special-chars">Special characters</h3>
 
 <item>
     <tags>:ab :abbreviate</tags>
 
 <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>: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>
     <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>
         </p>
     </description>
 </item>
     <description>
         <p>
             Abbreviate a key sequence for Insert mode. Same as
     <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>
         </p>
     </description>
 </item>
     <spec>:cuna<oa>bbreviate</oa>!</spec>
     <description>
         <p>
     <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>
     </description>
 </item>
 
         <p>
             The <em>-group</em> flag (short name: <em>-g</em>) can be used to
 
         <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>
 
         <h3 tag="E175 E176 :command-nargs">Argument handling</h3>
 
         <p>
             <em>completions</em> is a two-dimensional array of the form:
 
         <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>
 
         <p>
         <p>
             Example:
             <code><ex>:command foo -nargs=? -complete custom,<str delim="'">
         <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=?
     \ <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>
 
     \ <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
 # 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.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
 
 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.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.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
 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
 
 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
 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.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.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.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.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.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...
 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.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
 
 
 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
 
 
 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
 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.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.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
 
 
 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.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.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.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.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
 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
 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.recursiveSet = Not executing modes.set recursively
+mode.invalidBases = Invalid bases
 
 mow.noPreviousOutput = No previous command output
 
 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.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
 
 
 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
 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
 
 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
 
 save.invalidDestination-1 = Invalid destination: %S
 
+sort.ascending  = ascending
+sort.descending = descending
+
 status.link-1 = Link: %S
 
 style.none = No style found
 status.link-1 = Link: %S
 
 style.none = No style found
+style.styles = styles
+style.inline = inline
 
 time.total-1 = Total time: %S
 
 
 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
 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.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.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
 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.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:
 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"?>
 
 <?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"
 
 <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>
     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
 <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>
 </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>
     <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>
 </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>
     <spec>:se<oa>t</oa> all&amp;</spec>
     <description>
         <p>Set all options to their default value.</p>
 
 <item>
     <tags>:set! :set-!</tags>
 
 <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>
     <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>
         </p>
+        <note>
+            The <em>no</em> and <em>inv</em> prefix operators are not available
+            for setting preferences.
+        </note>
     </description>
 </item>
 
     </description>
 </item>
 
     <tags>'act' 'activate'</tags>
     <strut/>
     <spec>'activate' 'act'</spec>
     <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>
     <default>addons,bookmarks,diverted,downloads,extoptions,
           help,homepage,quickmark,tabopen,paste</default>
     <description>
 <item>
     <tags>'awim' 'altwildmode'</tags>
     <spec>'altwildmode' 'awim'</spec>
 <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
     <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>
 <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
     <description>
         <p>
             Enables automatic completion for completion contexts (see
 <item>
     <tags>'bh' 'banghist'</tags>
     <spec>'banghist' 'bh'</spec>
 <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
     <description>
         <p>
             Replace occurrences of ! with the previous command when
     <tags>$CDPATH</tags>
     <tags>'cd' 'cdpath'</tags>
     <spec>'cdpath' 'cd'</spec>
     <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>
     <default type="plain">equivalent to <str>.</str> or <str>.,$CDPATH</str></default>
     <description>
         <p>
 <item>
     <tags>'ca' 'cookieaccept'</tags>
     <spec>'cookieaccept' 'ca'</spec>
 <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>
 
     <description>
         <p>When to accept cookies.</p>
 
 <item>
     <tags>'cl' 'cookielifetime'</tags>
     <spec>'cookielifetime'</spec>
 <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
     <description>
         <p>
             The lifetime for which to accept cookies. The available
 <item>
     <tags>'ck' 'cookies'</tags>
     <spec>'cookies' 'ck'</spec>
 <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>
     <description>
         <p>The default action for the <ex>:cookies</ex> command.</p>
     </description>
 <item>
     <tags>'cpt' 'complete'</tags>
     <spec>'complete' 'cpt'</spec>
 <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>
         <p>Items which are completed at the <ex>:open</ex> prompts. Available items:</p>
 
     </description>
 </item>
 
     </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>
 <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
     <description>
         <p>
             Sets the default search engine. The default search engine is
 <item>
     <tags>'editor'</tags>
     <spec>'editor'</spec>
 <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.
     <description>
         <p>
             Set the external text editor.
 <item>
     <tags>'enc' 'encoding'</tags>
     <spec>'encoding' 'enc'</spec>
 <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
     <description>
         <p>
             Changes the character encoding of the current buffer. Valid only
     <tags>'noeb' 'noerrorbells'</tags>
     <tags>'eb' 'errorbells'</tags>
     <spec>'errorbells' 'eb'</spec>
     <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
     <description>
         <p>
             Ring the bell when an error message is displayed. See also
 <item>
     <tags>'ei' 'eventignore'</tags>
     <spec>'eventignore' 'ei'</spec>
 <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
     <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>
     <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
     <description>
         <p>
             Allow reading of an RC file in the current directory. This file is
     <tags>'eht' 'extendedhinttags'</tags>
     <spec>'extendedhinttags' 'eht'</spec>
     <strut/>
     <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,
           [f]:body,
           [F]:body,code,div,html,p,pre,span,
           [iI]:img,
 <item>
     <tags>'fenc' 'fileencoding'</tags>
     <spec>'fileencoding' 'fenc'</spec>
 <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
     <description>
         <p>
             Changes the character encoding that &dactyl.appName; uses to read
 <item>
     <tags>'fc' 'findcase'</tags>
     <spec>'findcase' 'fc'</spec>
 <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>
 
     <description>
         <p>Find case matching mode.</p>
 
 <item>
     <tags>'fh' 'followhints'</tags>
     <spec>'followhints' 'fh'</spec>
 <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>
     <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>
 
         <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>
         </dl>
     </description>
 </item>
     <tags>'nofs' 'nofullscreen'</tags>
     <tags>'fs' 'fullscreen'</tags>
     <spec>'fullscreen' 'fs'</spec>
     <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
     <description>
         <p>
             Show the current window full-screen. Also hide certain GUI elements, such as
 <item>
     <tags>'go' 'guioptions'</tags>
     <spec>'guioptions' 'go'</spec>
 <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>
 
     <description>
         <p>Show or hide certain GUI elements.</p>
 
 <item>
     <tags>'hf' 'helpfile'</tags>
     <spec>'helpfile' 'hf'</spec>
 <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
     <description>
         <p>
             Name of the main help file. This is that page shown if the
 <item>
     <tags>'hin' 'hintinputs'</tags>
     <spec>'hintinputs' 'hin'</spec>
 <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
     <description>
         <p>
             When generating hints for input elements that do not have an
 <item>
     <tags>'hk' 'hintkeys'</tags>
     <spec>'hintkeys' 'hk'</spec>
 <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,
     <description>
         <p>
             The keys used to label and select hints. With its default value,
 <item>
     <tags>'hm' 'hintmatching'</tags>
     <spec>'hintmatching' 'hm'</spec>
 <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>
 
     <description>
         <p>Change the hint matching algorithm used in Hints mode.</p>
 
     <tags>'ht' 'hinttags'</tags>
     <strut/>
     <spec>'hinttags' 'ht'</spec>
     <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>
     <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>
 <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
     <description>
         <p>
             Timeout in milliseconds before automatically following a non-unique
 <item>
     <tags>'hi' 'history'</tags>
     <spec>'history' 'hi'</spec>
 <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
     <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>
     <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>
     <description>
         <p>Highlight previous find pattern matches.</p>
     </description>
     <tags>'noif' 'noincfind'</tags>
     <tags>'if' 'incfind'</tags>
     <spec>'incfind' 'if'</spec>
     <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>
     <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>
     <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
     <description>
         <p>
             Use Insert mode as the default for text areas. This is useful if you
         </p>
 
         <p>
         </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>
         </p>
     </description>
 </item>
     <tags>'nojsd' 'nojsdebugger'</tags>
     <tags>'jsd' 'jsdebugger'</tags>
     <spec>'jsdebugger' 'jsd'</spec>
     <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.
     <description>
         <p>
             Use the JavaScript debugger service for JavaScript completion.
 </item>
 
 <item>
 </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>
     <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
     <description>
         <p>
             A regular expression list that defines which plugins are loaded at
 <item>
     <tags>'ml' 'mapleader'</tags>
     <spec>'mapleader' 'ml'</spec>
 <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>
     <description>
         <p>Defines the replacement keys for the <k name="Leader"/> pseudo-key.</p>
     </description>
 <item>
     <tags>'maxitems'</tags>
     <spec>'maxitems'</spec>
 <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>
     <description>
         <p>Maximum number of items to display at once in a listing.</p>
     </description>
 <item>
     <tags>'msgs' 'messages'</tags>
     <spec>'messages' 'msgs'</spec>
 <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>
     <description>
         <p>Maximum number of messages to store in the message history.</p>
     </description>
 <item>
     <tags>'nomore' 'more'</tags>
     <spec>'more'</spec>
 <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
     <description>
         <p>
             Pause the message list window when more than one screen of
 <item>
     <tags>'newtab'</tags>
     <spec>'newtab'</spec>
 <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
     <description>
         <p>
             Defines which Ex commands open pages in new tabs rather than the
     <tags>'nextpattern'</tags>
     <strut/>
     <spec>'nextpattern'</spec>
     <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
     <description>
         <p>
             Patterns to use when guessing the next page in a document
 <item>
     <tags>'noonline' 'online'</tags>
     <spec>'online'</spec>
 <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
     <description>
         <p>
             Enables or disables ‘offline’ mode, where network access is
 <item>
     <tags>'pa' 'pageinfo'</tags>
     <spec>'pageinfo' 'pa'</spec>
 <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>
 
     <description>
         <p>Info shown in the <ex>:pageinfo</ex> output.</p>
 
 
         <dl dt="width: 6em;">
             <dt>g</dt>      <dd>General info</dd>
 
         <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>f</dt>      <dd>Feeds</dd>
             <dt>m</dt>      <dd>Meta tags</dd>
+            <dt>s</dt>      <dd>Security information</dd>
         </dl>
 
         <p>
         </dl>
 
         <p>
 <item>
     <tags>'pk' 'passkeys'</tags>
     <spec>'passkeys' 'pk'</spec>
 <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>
         <p>
             Pass certain keys through directly for the given URLs.
     </description>
 </item>
 
     </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>
 <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
     <description>
         <p>
             Defines where to show requested pop-up windows. Applies only to
     <tags>'previouspattern'</tags>
     <strut/>
     <spec>'previouspattern'</spec>
     <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
     <description>
         <p>
             Patterns to use when guessing the previous page in a document
 <item>
     <tags>'noprivate' 'private'</tags>
     <spec>'private'</spec>
 <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
     <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>
     <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>
     <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/>
     <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
     <description>
         <p>
             The default list of private items to sanitize. See
 <item>
     <tags>'ss' 'sanitizeshutdown'</tags>
     <spec>'sanitizeshutdown' 'ss'</spec>
 <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>
     <description>
         <p>The items to sanitize automatically at shutdown.</p>
     </description>
     <tags>'sts' 'sanitizetimespan'</tags>
     <spec>'sanitizetimespan' 'sts'</spec>
     <strut/>
     <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
     <description>
         <p>
             The default sanitizer time span. Only items created within this timespan are
 <item>
     <tags>'scr' 'scroll'</tags>
     <spec>'scroll' 'scr'</spec>
 <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"/>
     <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>
 <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>
     <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/>
     <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>
     <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>
 </item>
 
 <item>
-    <tags>'nosmd' 'noshowmode'</tags>
     <tags>'smd' 'showmode'</tags>
     <spec>'showmode' 'smd'</spec>
     <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>
     <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>
 
     </description>
 </item>
 
     <tags>'ssli' 'showstatuslinks'</tags>
     <spec>'showstatuslinks' 'ssli'</spec>
     <strut/>
     <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,
     <description>
         <p>
             When the mouse hovers over a link, or a link is otherwise focused,
 <item>
     <tags>'stal' 'showtabline'</tags>
     <spec>'showtabline' 'stal'</spec>
 <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>
 
     <description>
         <p>Define when the tab bar is visible.</p>
 
 </item>
 
 <item>
 </item>
 
 <item>
-    <tags>'nosf' 'nostrictfocus'</tags>
     <tags>'sf' 'strictfocus'</tags>
     <spec>'strictfocus' 'sf'</spec>
     <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>
     <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>
     </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
     <description>
         <p>
             Set the search engines which can be used for completion
     <tags>'notmo' 'notimeout'</tags>
     <tags>'tmo' 'timeout'</tags>
     <spec>'timeout' 'tmo'</spec>
     <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
     <description>
         <p>
             When this option is set and a key sequence interpretable both as a
 <item>
     <tags>'tmol' 'timeoutlen'</tags>
     <spec>'timeoutlen' 'tmol'</spec>
 <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
     <description>
         <p>
             Maximum number of milliseconds to wait for a longer key command
 <item>
     <tags>'titlestring'</tags>
     <spec>'titlestring'</spec>
 <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
     <description>
         <p>
             Set the application name shown after the current page title in
 <item>
     <tags>'us' 'urlsep' 'urlseparator'</tags>
     <spec>'urlseparator' 'urlsep' 'us'</spec>
 <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
     <description>
         <p>
             The regular expression used to split URL lists in commands
     <tags>'noum' 'nousermode'</tags>
     <tags>'um' 'usermode'</tags>
     <spec>'usermode' 'um'</spec>
     <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>
     <description>
         <p>Show current website with minimal styling.</p>
     </description>
 <item>
     <tags>'vbs' 'verbose'</tags>
     <spec>'verbose' 'vbs'</spec>
 <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,
     <description>
         <p>
             Define which info messages are displayed. As the value increases,
     <tags>'novb' 'novisualbell'</tags>
     <tags>'vb' 'visualbell'</tags>
     <spec>'visualbell' 'vb'</spec>
     <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
     <description>
         <p>
             Use visual bell instead of beeping on errors. The visual bell
     <tags>'wia' 'wildanchor'</tags>
     <strut/>
     <spec>'wildanchor' 'wia'</spec>
     <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
     <description>
         <p>
             Regular expression list defining which completion groups show only
 <item>
     <tags>'wic' 'wildcase'</tags>
     <spec>'wildcase' 'wic'</spec>
 <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.
     <description>
         <p>
             Defines how completions are matched with regard to character case.
 <item>
     <tags>'wig' 'wildignore'</tags>
     <spec>'wildignore' 'wig'</spec>
 <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>
     <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>
         </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>
 <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
     <description>
         <p>
             Defines how command-line completion works. It is a comma-separated
 <item>
     <tags>'wis' 'wildsort'</tags>
     <spec>'wildsort' 'wis'</spec>
 <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
     <description>
         <p>
             A list of regular expressions defining which completion contexts
     <tags>'wsp' 'wordseparators'</tags>
     <spec>'wordseparators' 'wsp'</spec>
     <strut/>
     <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
     <description>
         <p>
             A regular expression which defines how words are split for
index e874701268d66c8fff6c3ead0ee41e22fb99b488..6ecac3a04a31d9ea0ed37fcff5e710d5e50119dc 100644 (file)
@@ -45,8 +45,8 @@
 </p>
 
 <item>
 </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>
 
     <description>
         <p>Find <a>pattern</a> starting at the current caret position.</p>
 
@@ -75,8 +75,8 @@
 </item>
 
 <item>
 </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
     <description>
         <p>
             Find a pattern backward of the current caret position in exactly the
@@ -86,7 +86,7 @@
 </item>
 
 <item>
 </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>
     <spec>n</spec>
     <description short="true">
         <p>Find next. Repeat the last find.</p>
@@ -94,7 +94,7 @@
 </item>
 
 <item>
 </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>
     <spec>N</spec>
     <description short="true">
         <p>Find previous. Repeat the last find in the opposite direction.</p>
 </item>
 
 <item>
 </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>
     <spec>*</spec>
     <description short="true">
         <p>Search forward for the next occurrence of the word under cursor.</p>
 </item>
 
 <item>
 </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>
     <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>
 
     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.
 
 <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
 
         <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.
             <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>
 
     </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>
 
 
 <h2 tag="site-filter site-filters">Site Filters</h2>
 
 </item>
 
 <item>
 </item>
 
 <item>
-    <tags>:ru :runtime</tags>
+    <tags>:runt :runtime</tags>
     <spec>:runt<oa>ime</oa><oa>!</oa> <a>file</a> …</spec>
     <description>
         <p>
     <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>
 
 <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>
 </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>
             <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>
     </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>
             specified, then any RC file in the current directory is also
             sourced.
         </p>
         </p>
 
         <ul>
         </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>
             <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>
 
 
 <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>
 <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>.
         <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>
 
         <p>
 
         <p>Valid groups include:</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>
         </dl>
 
         <p>
 
 <item>
     <tags>:sty :style</tags>
 
 <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
     <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>
         </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
 
             <dt>-agent</dt>
             <dd>If provided, the style is installed as an Agent sheet, which
index d894020ca4c29a2abbd4599d5d27dc50372348d2..d275bfe301e7c88e5759837ca0bbc04a68c089c7 100644 (file)
     </description>
 </item>
 
     </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.
 <!-- 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>
 <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>
     <spec><oa>count</oa>b</spec>
     <description>
         <p>
 
         <p>
             If argument is neither a full URL nor an index but uniquely identifies a
 
         <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>
 
             shortcut to open this prompt.
         </p>
 
 
 <item>
     <tags>:tabm :tabmove</tags>
 
 <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>
     <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>
         </p>
     </description>
 </item>
     </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: -->
 </document>
 
 <!-- vim:se sts=4 sw=4 et: -->
index 78ade2e5f846a4938b76b153b5cc0cc768ce5b46..39d80dc018ac0f802cd880e840f6babd3404eaa9 100644 (file)
@@ -87,7 +87,7 @@
 </item>
 
 <item>
 </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>
     <strut/>
     <spec>:redr<oa>aw</oa></spec>
     <description>
 <h2 tag="online-help">Online help</h2>
 
 <item>
 <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>
     <spec>:h<oa>elp</oa> <oa>subject</oa></spec>
     <spec>&lt;F1></spec>
     <description>
 </item>
 
 <item>
 </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>
     <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
         <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>
         </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"](
 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", {
     }
 
 var AddonListener = Class("AddonListener", {
@@ -59,17 +59,18 @@ var updateAddons = Class("UpgradeListener", AddonListener, {
 
     },
     onUpdateAvailable: function (addon, install) {
 
     },
     onUpdateAvailable: function (addon, install) {
+        util.dump("onUpdateAvailable");
         this.upgrade.push(addon);
         install.addListener(this);
         install.install();
     },
     onUpdateFinished: function (addon, error) {
         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
         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) {
         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() {
             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"
     },
         },
         perm: "disable"
     },
@@ -151,11 +152,11 @@ var Addon = Class("Addon", {
                 <td highlight="AddonVersion" key="version"/>
                 <td highlight="AddonStatus" key="status"/>
                 <td highlight="AddonButtons Buttons">
                 <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>,
                 </td>
                 <td highlight="AddonDescription" key="description"/>
             </tr>,
@@ -165,7 +166,7 @@ var Addon = Class("Addon", {
     },
 
     commandAllowed: function commandAllowed(cmd) {
     },
 
     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()]))
 
         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) {
     },
 
     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)
 
         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">
         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/>
-                            <td>Description</td>
+                            <td>{_("title.Description")}</td>
                         </tr>
                       </table>, this.document, this.nodes);
 
                         </tr>
                       </table>, this.document, this.nodes);
 
@@ -347,6 +348,11 @@ var AddonList = Class("AddonList", {
 });
 
 var Addons = Module("addons", {
 });
 
 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) {
 }, {
 }, {
     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
                 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) {
             }, {
                 argCount: "1",
                 completer: function (context) {
@@ -413,11 +419,11 @@ var Addons = Module("addons", {
                 function (args) {
                     let name = args[0];
                     if (args.bang && !command.bang)
                 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"));
 
                     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)
                         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,
                 }, {
                     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);
                     },
                         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));
                     });
                 }
                                           true));
                     });
                 }
-            }
-        }
+            };
+        };
 
         completion.extension = function extension(context, types) {
             context.title = ["Extension"];
 
         completion.extension = function extension(context, types) {
             context.title = ["Extension"];
@@ -494,27 +509,14 @@ else
                 addon = this.wrapAddon(addon);
             return callback(addon);
         },
                 addon = this.wrapAddon(addon);
             return callback(addon);
         },
+
         wrapAddon: function wrapAddon(addon) {
             addon = Object.create(addon.QueryInterface(Ci.nsIUpdateItem));
 
         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) {
             ["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, {
             });
 
             update(addon, {
@@ -523,19 +525,33 @@ else
 
                 appDisabled: false,
 
 
                 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);
                 },
 
                 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);
                 },
 
 
                 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);
                 }
                 set userDisabled(val) {
                     services.extensionManager[val ? "disableItem" : "enableItem"](this.id);
                 }
@@ -543,6 +559,7 @@ else
 
             return addon;
         },
 
             return addon;
         },
+
         getAddonsByTypes: function (types, callback) {
             let res = [];
             for (let [, type] in Iterator(types))
         getAddonsByTypes: function (types, callback) {
             let res = [];
             for (let [, type] in Iterator(types))
@@ -554,6 +571,7 @@ else
                 util.timeout(function () { callback(res); });
             return res;
         },
                 util.timeout(function () { callback(res); });
             return res;
         },
+
         getInstallForFile: function (file, callback, mimetype) {
             callback({
                 addListener: function () {},
         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));
         },
         getInstallForURL: function (url, callback, mimetype) {
             util.assert(false, _("error.unavailable", config.host, services.runtime.version));
         },
+
         observers: [],
         addAddonListener: function (listener) {
             observer.listener = listener;
         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); }
 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) {
     };
 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.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.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 (!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) {
     };
 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__;
     };
 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;
             use[mod].push(module);
         }
     currentModule = module;
+    module.startTime = Date.now();
 }
 
 defineModule.loadLog = [];
 }
 
 defineModule.loadLog = [];
@@ -148,7 +167,6 @@ defineModule.dump = function dump_() {
                .replace(/^./gm, name + ": $&"));
 }
 defineModule.modules = [];
                .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")
 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);
     }
 
         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;
 }
 
     return res;
 }
 
@@ -185,8 +197,10 @@ function require(obj, name, from) {
         if (arguments.length === 1)
             [obj, name] = [{}, obj];
 
         if (arguments.length === 1)
             [obj, name] = [{}, obj];
 
+        let caller = Components.stack.caller;
+
         if (!loaded[name])
         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;
 
         JSMLoader.load(name + ".jsm", obj);
         return obj;
@@ -196,7 +210,7 @@ function require(obj, name, from) {
         if (loaded.util)
             util.reportError(e);
         else
         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",
     // 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",
         "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"]
         "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))
     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) {}
                     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)
             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;
     }
 }
                 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 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 + "#" :
                       "";
 
                   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)
 }
 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");
             "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}
  */
  * @param {[string]} ary @optional
  * @returns {object}
  */
-function set(ary) {
+function Set(ary) {
     let obj = {};
     if (ary)
         for (let val in values(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
  */
  * @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;
     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}
  */
  * @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
                                            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}
  */
  * @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]))
     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
  */
  * @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;
 });
 
     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
 /**
  * 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)
 }
 
 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) {
 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)));
     };
         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) {
  */
 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)
         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;
     }
 
         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);
 }
 
 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;
 
             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);
             }
             try {
                 Object.defineProperty(target, k, desc);
@@ -696,18 +732,28 @@ function Class() {
     if (callable(args[0]))
         superclass = args.shift();
 
     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;
             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;
 
 
     Constructor.className = name || superclass.className || superclass.name;
 
@@ -735,19 +781,19 @@ function Class() {
 }
 
 if (Cu.getGlobalForObject)
 }
 
 if (Cu.getGlobalForObject)
-    Class.objectGlobal = function (caller) {
+    Class.objectGlobal = function (object) {
         try {
         try {
-            return Cu.getGlobalForObject(caller);
+            return Cu.getGlobalForObject(object);
         }
         catch (e) {
             return null;
         }
     };
 else
         }
         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.
  *
  * @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({
  */
 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);
             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;
 };
 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;
     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;
         this.message = message;
 
         let frame = Components.stack;
@@ -956,7 +1045,8 @@ var ErrorBase = Class("ErrorBase", Error, {
         }
         this.fileName = frame.filename;
         this.lineNumber = frame.lineNumber;
         }
         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)
     uniq: function uniq(iter) {
         let seen = {};
         for (let item in iter)
-            if (!set.add(seen, item))
+            if (!Set.add(seen, item))
                 yield item;
     },
 
                 yield item;
     },
 
@@ -1423,7 +1513,7 @@ var array = Class("array", Array, {
      * as such:
      *    [["a", "b"], ["c", "d"]] -> { a: "b", c: "d" }
      *
      * as such:
      *    [["a", "b"], ["c", "d"]] -> { a: "b", c: "d" }
      *
-     * @param {Array[]} assoc
+     * @param {[Array]} assoc
      * @... {string} 0 - Key
      * @...          1 - Value
      */
      * @... {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) {
 })
 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);
     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;
     },
 
         return this.rootFolders.indexOf(root) >= 0;
     },
 
-    // Should be made thread safe.
     load: function load() {
         let bookmarks = {};
 
     load: function load() {
         let bookmarks = {};
 
index 005ba1624c5ef86dc2a3177863f76aef47b27452..d51f8696b1f7192c1c6a47577f01a8c8a7e312dc 100644 (file)
@@ -6,6 +6,8 @@
 
 try {
 
 
 try {
 
+let { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
 var EXPORTED_SYMBOLS = ["JSMLoader"];
 
 var BOOTSTRAP_CONTRACT = "@dactyl.googlecode.com/base/bootstrap";
 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);
 
                           .getService(Components.interfaces.extIApplication)
                           .storage.get("dactyl.JSMLoader", null);
 
-if (JSMLoader && JSMLoader.bump === 4)
+if (JSMLoader && JSMLoader.bump === 5)
     JSMLoader.global = this;
 else
     JSMLoader = {
     JSMLoader.global = this;
 else
     JSMLoader = {
-        bump: 4,
-        builtin: Components.utils.Sandbox(this),
+        bump: 5,
+
+        builtin: Cu.Sandbox(this),
+
         canonical: {},
         canonical: {},
+
         factories: [],
         factories: [],
+
         global: this,
         global: this,
+
         globals: JSMLoader ? JSMLoader.globals : {},
         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 : {},
         stale: JSMLoader ? JSMLoader.stale : {},
+
         suffix: "",
         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 || "";
         init: function init(suffix) {
             this.initialized = true;
             this.suffix = suffix || "";
@@ -41,14 +75,16 @@ else
             this.global.JSMLoader = this;
             base.JSMLoader = this;
         },
             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);
         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;
         },
             return chan.name;
         },
+
         load: function load(name, target) {
             let url = name;
             if (url.indexOf(":") === -1)
         load: function load(name, target) {
             let url = name;
             if (url.indexOf(":") === -1)
@@ -68,7 +104,13 @@ else
             }
 
             try {
             }
 
             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) {
                 return this.globals[name] = global;
             }
             catch (e) {
@@ -76,33 +118,52 @@ else
                 throw e;
             }
         },
                 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);
         },
         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");
 
         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 {
                     try {
-                        if (!(prop in this.builtin) &&
-                            ["JSMLoader", "set", "EXPORTED_SYMBOLS"].indexOf(prop) < 0 &&
-                            !global.__lookupGetter__(prop))
-                            global[prop] = undefined;
+                        Cu.unload(url);
                     }
                     catch (e) {
                     }
                     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.
  *
  * 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.
  *     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)
         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)
         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 helpTag() ":" + this.name,
 
-    get lastCommand() this._lastCommand || commandline.command,
+    get lastCommand() this._lastCommand || this.modules.commandline.command,
     set lastCommand(val) { this._lastCommand = val; },
 
     /**
     set lastCommand(val) { this._lastCommand = val; },
 
     /**
@@ -155,16 +155,13 @@ var Command = Class("Command", {
         const { dactyl } = this.modules;
 
         let context = args.context;
         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)
 
         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"));
 
         if (args.bang && !this.bang)
             throw FailedAssertion(_("command.noBang"));
 
@@ -212,23 +209,27 @@ var Command = Class("Command", {
     complained: Class.memoize(function () ({})),
 
     /**
     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,
      */
     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,
     /**
     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,
      */
     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(""),
     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.
      */
     /**
      * @property {function (Args)} The function called to execute this command.
      */
@@ -295,7 +296,7 @@ var Command = Class("Command", {
 
                 explicitOpts: Class.memoize(function () ({})),
 
 
                 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] || "",
 
 
                 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)),
 
                         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.
      */
      * @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?
     /**
 }, {
     // 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);
                 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));
                     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);
                 }
         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.
      *
      * 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.
      *     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
      */
      * @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 || {};
         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.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];
 
         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];
 
         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);
         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 "";
             }
             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
                 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)
 
             let [count, arg, quote] = Commands.parseArg(str, null, _keepQuotes);
             if (quote == "\\" && !complete)
-                return [, , , "Trailing \\"];
+                return [, , , _("error.trailingCharacters", "\\")];
             if (quote && !complete)
             if (quote && !complete)
-                return [, , , "E114: Missing quote: " + quote];
+                return [, , , _("error.missingQuote", quote)];
             return [count, arg, 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)
             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;
             };
             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
                                     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
                                         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) {
             // 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;
             }
                 context.highlight(0, match.cmd.length, "SPELLCHECK");
                 return;
             }
@@ -1354,6 +1381,7 @@ var Commands = Module("commands", {
             }
             catch (e) {
                 util.reportError(e);
             }
             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;
 
     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) {
         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));
 
                 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")));
 
                 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 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) {
                             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
                                 if (callable(result))
                                     return result.apply(this, Array.slice(arguments));
                                 else
@@ -1473,7 +1492,7 @@ var Commands = Module("commands", {
                     },
                     {
                         names: ["-literal", "-l"],
                     },
                     {
                         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
                     },
                     {
                         type: CommandOption.INT
                     },
                     {
@@ -1558,7 +1577,7 @@ var Commands = Module("commands", {
                 ]
             })),
             iterateIndex: function (args) let (tags = services["dactyl:"].HELP_TAGS)
                 ]
             })),
             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 : "")),
             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.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
             });
                 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"],
 Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("completion", {
     exports: ["CompletionContext", "Completion", "completion"],
-    use: ["config", "template", "util"]
+    use: ["config", "messages", "template", "util"]
 }, this);
 
 /**
 }, this);
 
 /**
@@ -331,16 +331,16 @@ var CompletionContext = Class("CompletionContext", {
      * The message displayed at the head of the completions for the
      * current context.
      */
      * 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() {
     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) {
         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))) {
                                    : 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);
             }
             catch (e) {
                 util.reportError(e);
-                this.message = "Error: " + e;
+                this.message = _("error.error", e);
             }
         }
         // XXX
             }
         }
         // XXX
@@ -495,7 +495,7 @@ var CompletionContext = Class("CompletionContext", {
             return this.cache.filtered = filtered;
         }
         catch (e) {
             return this.cache.filtered = filtered;
         }
         catch (e) {
-            this.message = "Error: " + e;
+            this.message = _("error.error", e);
             util.reportError(e);
             return [];
         }
             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) {
     // 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*")
 
         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 = ["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,
             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"],
         };
 
         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);
 
             "stringlist", "list:full",
             wildmode);
 
@@ -1066,7 +1073,7 @@ var Completion = Module("completion", {
             });
 
         options.add(["wildmode", "wim"],
             });
 
         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);
 
             "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"],
 defineModule("config", {
     exports: ["ConfigBase", "Config", "config"],
     require: ["services", "storage", "util", "template"],
-    use: ["io", "prefs"]
+    use: ["io", "messages", "prefs", "styles"]
 }, this);
 
 var ConfigBase = Class("ConfigBase", {
 }, this);
 
 var ConfigBase = Class("ConfigBase", {
@@ -22,25 +22,24 @@ var ConfigBase = Class("ConfigBase", {
      * initialization code. Must call superclass's init function.
      */
     init: function init() {
      * 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"))
         if (util.haveGecko("2b"))
-            set.add(this.features, "Gecko2");
+            Set.add(this.features, "Gecko2");
 
         this.timeout(function () {
 
         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 { highlight } = require("highlight");
+        const { _ } = require("messages");
+
         highlight.styleableChrome = this.styleableChrome;
         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;
         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;
                 }
             ]]>);
                     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",
     },
 
     get addonID() this.name + "@dactyl.googlecode.com",
@@ -124,50 +142,59 @@ var ConfigBase = Class("ConfigBase", {
      * @returns {string}
      */
     bestLocale: function (list) {
      * @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()])
         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)) {
         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 &&
             if (uri instanceof Ci.nsIFileURL &&
-                    uri.QueryInterface(Ci.nsIFileURL).file.exists() &&
+                    uri.file.exists() &&
                     io.pathSearch("hg"))
                     io.pathSearch("hg"))
-                return ["hg", "-R", uri.file.parent.path];
+                return uri.file.parent.path;
         }
         return null;
     }),
 
         }
         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 () {
     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 () {
         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@")
         let version = this.addon.version;
         if ("@DATE@" !== "@" + "DATE@")
-            version += " (created: @DATE@)";
+            version += " " + _("dactyl.created", "@DATE@");
         return version;
     }),
 
         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,
         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",
 
         "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: [
     },
 
     dtdStrings: [
@@ -202,7 +230,7 @@ var ConfigBase = Class("ConfigBase", {
         "version"
     ],
 
         "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");
     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].* +=
             </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}
                 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() });
             }
 
             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
     /**
      * @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.
      *  [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: {},
 
      */
     dialogs: {},
 
@@ -373,21 +403,25 @@ var ConfigBase = Class("ConfigBase", {
      */
     CSS: UTF8(String.replace(<><![CDATA[
         // <css>
      */
     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.
 
         // 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]
 
         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;
         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
         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;
 
         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;
         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: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;
 
         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;
 
         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);
 
         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                    {
         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%;
             color: black;
             position: absolute;
             left: 100%;
@@ -488,6 +568,7 @@ var ConfigBase = Class("ConfigBase", {
         }
 
         StatusLine;;;FontFixed  {
         }
 
         StatusLine;;;FontFixed  {
+            /* The status bar */
             -moz-appearance: none !important;
             font-weight: bold;
             background: transparent !important;
             -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;
         }
             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;
             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;
         }
 
             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;
         URL:hover   text-decoration: underline; cursor: pointer;
-        URLExtra    color: gray;
+        URLExtra    /* Extra information about a URL */ \
+                    color: gray;
 
         FrameIndicator;;* {
 
         FrameIndicator;;* {
+            /* The styling applied to briefly indicate the active frame */
             background-color: red;
             opacity: 0.5;
             z-index: 999999;
             background-color: red;
             opacity: 0.5;
             z-index: 999999;
@@ -531,9 +624,11 @@ var ConfigBase = Class("ConfigBase", {
             right:    0;
         }
 
             right:    0;
         }
 
-        Bell          background-color: black !important;
+        Bell          /* &dactyl.appName;’s visual bell */ \
+                      background-color: black !important;
 
         Hint;;* {
 
         Hint;;* {
+            /* A hint indicator. See <ex>:help hints</ex> */
             font:        bold 10px "Droid Sans Mono", monospace !important;
             margin:      -.2ex;
             padding:     0 0 0 1px;
             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;
         }
         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: "/[";
 
         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
 
         DownloadHead>*;;;DownloadCell
 
-        Download                        display: table-row;
+        Download                        /* A download in the :downloads manager */ \
+                                        display: table-row;
         Download:not([active])          color: gray;
         Download:not([active])          color: gray;
+        Download:nth-child(2n+1)        background: rgba(0, 0, 0, .04);
 
         Download>*;;;DownloadCell
 
         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;
 
         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
 
         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
 
         Addon>*;;;AddonCell
         AddonButtons
@@ -597,111 +706,154 @@ var ConfigBase = Class("ConfigBase", {
 
     helpCSS: UTF8(<><![CDATA[
         // <css>
 
     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;
         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] {
         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;
         }
 
             /* 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;
         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);
 
         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/* {
 
         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/* {
             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/* {
             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/* {
             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/* {
             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/* {
         HelpTab;html|dl;dactyl://help/* {
+            /* A description table */
             display: table;
             width: 100%;
             margin: 1em 0;
             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%;
         }
         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;
         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;
         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)
             });
 
                 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 () {
         },
 
         cleanup: function () {
@@ -121,7 +124,7 @@ var Contexts = Module("contexts", {
                 util.trapErrors("destroy", hive);
 
             for (let [name, plugin] in iter(this.modules.plugins.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);
         },
 
                     util.trapErrors("onUnload", plugin);
         },
 
@@ -159,7 +162,7 @@ var Contexts = Module("contexts", {
                                                                 { _hive: { value: name } })));
 
                 memoize(contexts.groupsProto, name,
                                                                 { _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]
             },
 
             get toStringParams() [this.name, this.Hive]
@@ -177,17 +180,18 @@ var Contexts = Module("contexts", {
                                   0);
 
         let contextPath = file.path;
                                   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 (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.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),
                 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);
                 })
             });
                         contexts.removeGroup(this.GROUP);
                 })
             });
-            Class.replaceProperty(plugins, file.path, self);
+
+            if (group !== this.user)
+                Class.replaceProperty(plugins, file.path, self);
 
             // This belongs elsewhere
 
             // This belongs elsewhere
-            if (isPlugin && args)
+            if (isPlugin)
                 Object.defineProperty(plugins, self.NAME, {
                     configurable: true,
                     enumerable: true,
                 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, "-"),
             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);
                                   null, false);
 
         Class.replaceProperty(self, "GROUP", group);
@@ -272,7 +278,7 @@ var Contexts = Module("contexts", {
     })),
 
     matchingGroups: function (uri) Object.create(this.groupsProto, {
     })),
 
     matchingGroups: function (uri) Object.create(this.groupsProto, {
-        groups: { value: this.activeGroups(uri) },
+        groups: { value: this.activeGroups(uri) }
     }),
 
     activeGroups: function (uri, doc) {
     }),
 
     activeGroups: function (uri, doc) {
@@ -288,7 +294,7 @@ var Contexts = Module("contexts", {
 
     initializedGroups: function (hive)
         let (need = hive ? [hive] : Object.keys(this.hives))
 
     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);
 
     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)
             if (description)
                 group.description = description;
             if (filter)
-                group.filter = filter
+                group.filter = filter;
             group.persist = persist;
         }
 
             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;
     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)
             group = this.groupMap[name];
 
         if (group && hive)
@@ -352,15 +358,35 @@ var Contexts = Module("contexts", {
         return group;
     },
 
         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;
 
     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)
         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])]
         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_);
 
         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;
         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);
             var action = function action() {
                 events.feedkeys(action.macro(makeParams(this, arguments)),
                                 noremap, silent);
-            }
+            };
             action.macro = util.compileMacro(rhs, true);
             break;
             action.macro = util.compileMacro(rhs, true);
             break;
+
         case "-ex":
             action = function action() modules.commands
                                               .execute(action.macro, makeParams(this, arguments),
         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;
             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 + "} })");
         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.makeParams = makeParams;
             break;
         }
+
         action.toString = function toString() (type === default_ ? "" : type + " ") + rhs;
         args = null;
         return action;
         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"))
                     if (args.has("-locations"))
                         group.filter = filter;
                     if (args.has("-description"))
-                        group.description = args["-description"]
+                        group.description = args["-description"];
                     if (args.has("-nopersist"))
                     if (args.has("-nopersist"))
-                        group.persist = !args["-nopersist"]
+                        group.persist = !args["-nopersist"];
                 }
 
                 if (!group.builtin && args.has("-args")) {
                 }
 
                 if (!group.builtin && args.has("-args")) {
@@ -482,7 +512,7 @@ var Contexts = Module("contexts", {
 
                 util.assert(!group.builtin ||
                                 !["-description", "-locations", "-nopersist"]
 
                 util.assert(!group.builtin ||
                                 !["-description", "-locations", "-nopersist"]
-                                    .some(set.has(args.explicitOpts)),
+                                    .some(Set.has(args.explicitOpts)),
                             _("group.cantModifyBuiltin"));
             },
             {
                             _("group.cantModifyBuiltin"));
             },
             {
@@ -540,14 +570,23 @@ var Contexts = Module("contexts", {
         commands.add(["delg[roup]"],
             "Delete a group",
             function (args) {
         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) {
                 completer: function (context, args) {
-                    modules.completion.group(context);
+                    if (args.bang)
+                        return;
                     context.filters.push(function ({ item }) !item.builtin);
                     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"],
 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);
 }, 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">
             <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">
                            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>
                 <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);
 
                 <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;
     },
         self.updateStatus();
         return self;
     },
@@ -78,10 +86,13 @@ var Download = Class("Download", {
     })),
 
     command: function command(name) {
     })),
 
     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: {
     },
 
     commands: {
@@ -95,7 +106,7 @@ var Download = Class("Download", {
             function action() {
                 try {
                     if (this.MIMEInfo && this.MIMEInfo.preferredAction == this.MIMEInfo.useHelperApp)
             function action() {
                 try {
                     if (this.MIMEInfo && this.MIMEInfo.preferredAction == this.MIMEInfo.useHelperApp)
-                        this.MIMEInfo.launchWithFile(file)
+                        this.MIMEInfo.launchWithFile(file);
                     else
                         file.launch();
                 }
                     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))
 
             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);
                     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__;
 
 
     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 = "";
             this.nodes.time.textContent = "";
-        else if (this.speed == 0 || this.size == 0)
-            this.nodes.time.textContent = "Unknown";
+        }
         else {
         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, "");
 
         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]), {
                          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 = {
         this.modules = modules;
         this.filter = filter && filter.toLowerCase();
         this.nodes = {
@@ -173,6 +207,7 @@ var DownloadList = Class("DownloadList",
         };
         this.downloads = {};
     },
         };
         this.downloads = {};
     },
+
     cleanup: function cleanup() {
         this.observe.unregister();
         services.downloadManager.removeListener(this);
     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">
 
         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/>
-                            <span>Progress</span>
+                            <span>{_("title.Progress")}</span>
                             <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">
                         </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">
                             <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>
                             <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>
                             <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)
     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)
 
         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 = "";
             this.nodes[key].textContent = "";
+
+        if (this.shouldSort("complete", "size", "speed", "time"))
+            this.sort();
     },
 
     observers: {
     },
 
     observers: {
@@ -305,11 +356,15 @@ var DownloadList = Class("DownloadList",
                 this.nodes.list.scrollIntoView(false);
             }
             this.update();
                 this.nodes.list.scrollIntoView(false);
             }
             this.update();
+
+            if (this.shouldSort("active"))
+                this.sort();
         }
         catch (e) {
             util.reportError(e);
         }
     },
         }
         catch (e) {
             util.reportError(e);
         }
     },
+
     onProgressChange: function (webProgress, request,
                                 curProgress, maxProgress,
                                 curTotalProgress, maxTotalProgress,
     onProgressChange: function (webProgress, request,
                                 curProgress, maxProgress,
                                 curTotalProgress, maxTotalProgress,
@@ -328,17 +383,85 @@ var DownloadList = Class("DownloadList",
 var Downloads = Module("downloads", {
 }, {
 }, {
 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) {
 
         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);
             },
             {
                 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"],
 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);
 
     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) ({
 /** @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
     }),
 
         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,
     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();
 
         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) {
     },
 
     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;
         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();
     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",
 
         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",
         });
         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 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) {
             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,
         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,
             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,
             function () { rangefinder.openPrompt(modes.FIND_BACKWARD); });
 
         mappings.add(myModes,
-            ["n"], "Find next",
+            ["n", "<find-next>"], "Find next",
             function () { rangefinder.findAgain(false); });
 
         mappings.add(myModes,
             function () { rangefinder.findAgain(false); });
 
         mappings.add(myModes,
-            ["N"], "Find previous",
+            ["N", "<find-previous>"], "Find previous",
             function () { rangefinder.findAgain(true); });
 
             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 () {
             "Find word under cursor",
             function () {
-                rangefinder.find(buffer.getCurrentWord(), false);
+                rangefinder.find(Buffer.currentWord(buffer.focusedFrame, true), false);
                 rangefinder.findAgain();
             });
 
                 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 () {
             "Find word under cursor backwards",
             function () {
-                rangefinder.find(buffer.getCurrentWord(), true);
+                rangefinder.find(Buffer.currentWord(buffer.focusedFrame, true), true);
                 rangefinder.findAgain();
             });
 
                 rangefinder.findAgain();
             });
 
@@ -242,10 +259,6 @@ var RangeFinder = Module("rangefinder", {
         const { options, rangefinder } = modules;
         const { prefs } = require("prefs");
 
         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, {
         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"],
             });
 
         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);
     }
 });
             "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)
         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];
 
         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) {
             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;
         }
             // Re-highlight collapsed selection
             this.selectedRange = this.lastRange;
         }
@@ -480,7 +493,9 @@ var RangeFind = Class("RangeFind", {
                     frames.push(r);
             }
 
                     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);
 
             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;
         }
         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);
             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);
                 }
             }
             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)
         }
         rec(win);
         if (frames.length == 0)
@@ -724,6 +747,14 @@ var RangeFind = Class("RangeFind", {
             return false;
         }
     },
             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 {
     nodeRange: function (node) {
         let range = node.ownerDocument.createRange();
         try {
@@ -732,10 +763,23 @@ var RangeFind = Class("RangeFind", {
         catch (e) {}
         return range;
     },
         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(" | ")
 });
 
     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();
 
 endModule();
 
+
 // vim: set fdm=marker sw=4 ts=4 et ft=javascript:
 // 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];
                     h.style.css = h.css;
 
         this.style[prop || name] = this[prop || name];
+        if (this.onChange)
+            this.onChange();
     });
 }
 Highlight.liveProperty("agent");
     });
 }
 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(),
 
     __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))
         let obj = Highlight.apply(Highlight, args);
 
         if (!isArray(obj.sites))
@@ -143,9 +145,9 @@ var Highlights = Module("Highlight", {
         return obj;
     },
 
         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]);
         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.
      */
      * 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);
     },
         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
      */
      * @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(" ");
         node.setAttributeNS(NS.uri, "highlight", group);
 
         let groups = group.split(" ");
@@ -214,7 +216,7 @@ var Highlights = Module("Highlight", {
      *
      * @param {string} class
      */
      *
      * @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 +
         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.
      */
      * @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) {
 
               .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;
         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(/&$/, "")];
 
                 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(
 
                 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),
                             ([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)
                 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)
                     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");
                     }
                 },
                         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) [
                             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");
                         }
                                 ]);
                             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;
         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" };
         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)];
         };
     },
             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]),
         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.
          *
          * 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) {
          * @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)) {
 
         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);
                     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);
                         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);
                     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);
                 }
                     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);
                     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
     }),
 
     // 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);
      */
     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 {
             // let jar = services.zipReader.getZip(file); Crashes.
             let jar = services.ZipReader(file);
             try {
@@ -366,7 +366,8 @@ var IO = Module("io", {
                         yield entry;
             }
             finally {
                         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.
      * 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 || [];
      */
     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'
 
                 // 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);
                 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:
                 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");
                 lines = array.flatten(lines);
 
                 lines.unshift('"' + config.version + "\n");
@@ -620,8 +620,8 @@ var IO = Module("io", {
                     file.write(lines.join("\n"));
                 }
                 catch (e) {
                     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
                 }
             }, {
                 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");
 
                 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
 
                 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>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 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
 
 " 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>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
 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 () {
         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]"],
             },
             { 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"));
             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,
                 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)
 
                     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)
 
                 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", {});
             }, {
                 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),
                 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) {
         };
 
         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);
             // 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);
 
             };
             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;
 
             // 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({
                     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 (!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);
         });
     },
                     completion.file(context, full);
         });
     },
@@ -1067,7 +1073,7 @@ unlet s:cpo_save
         options["shellcmdflag"];
 
         options.add(["wildignore", "wig"],
         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", "");
     }
 });
             "regexplist", "");
     }
 });
index 0f78615cc5b204b924716042538f1bae4c41101b..e85b8ca41f3002ae3151226b519139c254ab5a64 100644 (file)
@@ -11,7 +11,7 @@ try {
 Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("javascript", {
     exports: ["JavaScript", "javascript"],
 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;
 }, this);
 
 let isPrototypeOf = Object.prototype.isPrototypeOf;
@@ -41,11 +41,11 @@ var JavaScript = Module("javascript", {
             this.window = window;
 
             init.supercall(this);
             this.window = window;
 
             init.supercall(this);
-        },
+        }
     }),
 
     globals: Class.memoize(function () [
     }),
 
     globals: Class.memoize(function () [
-       [this.modules.userContext, "Global Variables"],
+       [this.modules.userContext, /*L*/"Global Variables"],
        [this.modules, "modules"],
        [this.window, "window"]
     ]),
        [this.modules, "modules"],
        [this.window, "window"]
     ]),
@@ -54,7 +54,7 @@ var JavaScript = Module("javascript", {
 
     lazyInit: true,
 
 
     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
 
 
     get completers() JavaScript.completers, // For backward compatibility
 
@@ -71,22 +71,22 @@ var JavaScript = Module("javascript", {
         if (obj == null)
             return;
 
         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)
         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)))
                 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 {
                 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) {}
                     yield key;
             }
             catch (e) {}
@@ -117,18 +117,14 @@ var JavaScript = Module("javascript", {
             return cache[key];
 
         context[JavaScript.EVAL_TMP] = tmp;
             return cache[key];
 
         context[JavaScript.EVAL_TMP] = tmp;
-        context[JavaScript.EVAL_EXPORT] = function export_(obj) cache[key] = obj;
         try {
         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);
 
             return cache[key];
         }
         catch (e) {
             util.reportError(e);
-            this.context.message = "Error: " + e;
+            this.context.message = _("error.error", e);
             return null;
         }
         finally {
             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");
 
         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
         }
 
         // 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) {
             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 [];
             }
 
                 return [];
             }
 
@@ -324,7 +320,7 @@ var JavaScript = Module("javascript", {
         let end = (frame == -1 ? this._lastIdx : this._get(frame + 1).offset);
 
         this._cacheKey = null;
         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;
         // 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)
         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;
 
         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;
         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;
             });
         });
 
         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);
             });
         });
                 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;
         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);
             });
         });
                 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;
         // 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;
         }
 
             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",
         "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)
     ].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_TMP: "__dactyl_eval_tmp",
-    EVAL_EXPORT: "__dactyl_eval_export",
 
     /**
      * A map of argument completion functions for named methods. The
 
     /**
      * 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.
      *
      * 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.
      *      install the completers.
-     * @param {function[]} completers An array of completer
+     * @param {[function]} completers An array of completer
      *      functions.
      */
     setCompleter: function (funcs, completers) {
      *      functions.
      */
     setCompleter: function (funcs, completers) {
@@ -705,7 +699,8 @@ var JavaScript = Module("javascript", {
 
         modes.addMode("REPL", {
             description: "JavaScript Read Eval Print Loop",
 
         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) {
         });
     },
     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.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))
                 ].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);
             },
 
                 this.repl = REPL(this.context);
             },
+
             open: function open(context) {
             open: function open(context) {
-                this.updatePrompt();
 
                 modules.mow.echo(this.repl);
                 this.widgets.message = null;
 
                 open.superapply(this, arguments);
 
                 modules.mow.echo(this.repl);
                 this.widgets.message = null;
 
                 open.superapply(this, arguments);
+                this.updatePrompt();
             },
 
             complete: function complete(context) {
             },
 
             complete: function complete(context) {
@@ -807,8 +803,10 @@ var JavaScript = Module("javascript", {
 
             mode: modes.REPL,
 
 
             mode: modes.REPL,
 
+            get completionList() this.widgets.statusbar.commandline.id,
+
             accept: function accept() {
             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();
 
                 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) {
         commands.add(["javas[cript]", "js"],
             "Evaluate a JavaScript string",
             function (args) {
-                modules.commandline;
-
                 if (args[0] && !args.bang)
                     dactyl.userEval(args[0]);
                 else {
                 if (args[0] && !args.bang)
                     dactyl.userEval(args[0]);
                 else {
@@ -846,6 +842,7 @@ var JavaScript = Module("javascript", {
                            .open();
                 }
             }, {
                            .open();
                 }
             }, {
+                argCount: "?",
                 bang: true,
                 completer: function (context) modules.completion.javascript(context),
                 hereDoc: true,
                 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()) {
 
         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))
                 };
                 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
             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;
     },
             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
             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;
     }
             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) {
 }, {
     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")
             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() {
                 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_);
 
 
                         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);
                     },
 
                         return Class.replaceProperty(this, prop, value);
                     },
+
                     set: function set(val) this[_prop] = val
                     set: function set(val) this[_prop] = val
-                }
+                };
             }
             this.default = prop;
             this.initialized = true;
             }
             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"],
 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 */
 }, 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.
  *
  * 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.
  * @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", {
  * @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.name = names[0];
         this.names = names;
         this.realNames = names;
-        this.type = type;
         this.description = description;
 
         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)
         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) {
             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))
                 defaultValue = Commands.quote(defaultValue);
 
             if (isObject(defaultValue))
@@ -101,6 +78,8 @@ var Option = Class("Option", {
             this.globalValue = this.defaultValue;
     },
 
             this.globalValue = this.defaultValue;
     },
 
+    magicalProperties: Set(["cleanupValue"]),
+
     /**
      * @property {string} This option's description, as shown in :listoptions.
      */
     /**
      * @property {string} This option's description, as shown in :listoptions.
      */
@@ -114,6 +93,13 @@ var Option = Class("Option", {
 
     get isDefault() this.stringValue === this.stringDefaultValue,
 
 
     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() }); },
     /** @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.
      * "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.
      *
      */
     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),
      * @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}).
      *
      * @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) {
      */
     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 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,
 
 
     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.
      * 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.
      * @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)
         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);
 
             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} 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,
 
     /**
     names: null,
 
     /**
@@ -305,13 +292,14 @@ var Option = Class("Option", {
      */
     scope: 1, // Option.SCOPE_GLOBAL // XXX set to BOTH by default someday? - kstep
 
      */
     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
      */
     /**
      * @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;
     },
         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 [, 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,
         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))
 
         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;
         },
                     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))
 
     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,
 
 
         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 [];
             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(
         },
 
         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 {
                 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))
                     prev.result += "," + v;
                 }
             }, this))
@@ -592,7 +582,7 @@ var Option = Class("Option", {
 
             let value = parseInt(values);
             util.assert(Number(values) % 1 == 0,
 
             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 "+":
 
             switch (operator) {
             case "+":
@@ -637,18 +627,23 @@ var Option = Class("Option", {
         stringlist: function stringlist(operator, values, scope, invert) {
             values = Array.concat(values);
 
         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 "+":
             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
             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 "-":
             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) {
             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;
                     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.
      *
      * 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) {
      * @returns {boolean}
      */
     validateCompleter: function validateCompleter(values) {
@@ -702,15 +697,54 @@ var Option = Class("Option", {
         }
 
         if (isArray(acceptable))
         }
 
         if (isArray(acceptable))
-            acceptable = set(acceptable.map(function ([k]) k));
+            acceptable = Set(acceptable.map(function ([k]) k));
 
         if (this.type === "regexpmap" || this.type === "sitemap")
 
         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
  */
 /**
  * @instance options
  */
@@ -721,7 +755,6 @@ var Options = Module("options", {
             this.needInit = [];
             this._options = [];
             this._optionMap = {};
             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) {
 
             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);
                 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,
         },
 
         dactyl: dactyl,
@@ -742,7 +787,7 @@ var Options = Module("options", {
          * @param {number} scope Only list options in this scope (see
          *     {@link Option#scope}).
          */
          * @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;
 
             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)
         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.
          *
         },
 
         /**
          * 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.
          * @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
          */
          *     {@link Map#extraInfo}).
          * @optional
          */
-        add: function (names, description, type, defaultValue, extraInfo) {
+        add: function add(names, description, type, defaultValue, extraInfo) {
             const self = this;
 
             if (!extraInfo)
             const self = this;
 
             if (!extraInfo)
@@ -810,7 +855,7 @@ var Options = Module("options", {
 
             let closure = function () self._optionMap[name];
 
 
             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);
 
             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)),
     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)),
     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)),
 
     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*.
      *
     /**
      * Returns the option with *name* in the specified *scope*.
      *
@@ -887,8 +939,11 @@ var Options = Module("options", {
         }
 
         if (matches) {
         }
 
         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 = "";
             }
                 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)
             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)}
                 </>)
                             : <></>}
                         {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
             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 + "'"
                         {template.linkifyHelp(opt.description)}
                 </>),
                 help: function (opt) "'" + opt.name + "'"
@@ -982,12 +1039,12 @@ var Options = Module("options", {
 
             let list = [];
             function flushList() {
 
             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))
                 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
                     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 = [];
             }
 
                 list = [];
             }
 
@@ -1009,7 +1066,7 @@ var Options = Module("options", {
                     }
 
                     if (name == "all" && reset)
                     }
 
                     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()))
                             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")
                             },
                             { 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)
                     else if (reset)
                         prefs.reset(name);
                     else if (invertBoolean)
@@ -1044,10 +1101,11 @@ var Options = Module("options", {
                 }
 
                 let opt = modules.options.parseOpt(arg, modifiers);
                 }
 
                 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;
 
                 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) {
 
                 // 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 = [
 
                     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;
                 }
                     ].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));
             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) {
             }
 
             function error(length, message) {
@@ -1125,11 +1183,11 @@ var Options = Module("options", {
 
             let option = opt.option;
             if (!option)
 
             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")
 
             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)
 
             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 = [
                     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, ""];
                 });
                     ].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)) {
 
             // 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 = 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, ""]);
                 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
                     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;
                 }
 
                     return;
                 }
 
@@ -1206,7 +1264,7 @@ var Options = Module("options", {
 
                     util.assert(scope == "g:" || scope == null,
                                 _("command.let.illegalVar", scope + name));
 
                     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)
                                 _("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
             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;
                         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) {
                 var newValues = opt.parse(context.filter);
             }
             catch (e) {
-                context.message = "Error: " + e;
+                context.message = _("error.error", e);
                 context.completions = [];
                 return;
             }
                 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);
                     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);
             }
 
             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"],
 Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("overlay", {
     exports: ["ModuleBase"],
-    require: ["config", "services", "util"]
+    require: ["config", "io", "services", "util"]
 }, this);
 
 /**
 }, this);
 
 /**
@@ -206,8 +206,8 @@ var Overlay = Module("Overlay", {
 
                 const start = Date.now();
                 const deferredInit = { load: {} };
 
                 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) {
                 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.");
                             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);
 
                         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) {
                     });
                 }
                 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)
                     if ("init" in mod.INIT)
-                        set.add(names, "init");
+                        Set.add(names, "init");
 
                     keys(names).forEach(function (name) { deferInit(name, mod.INIT, mod); });
                 });
 
                     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]), {
 }, this);
 
 var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), {
-    SAVED: "extensions.dactyl.saved.",
+    ORIGINAL: "extensions.dactyl.original.",
     RESTORE: "extensions.dactyl.restore.",
     RESTORE: "extensions.dactyl.restore.",
+    SAVED: "extensions.dactyl.saved.",
     INIT: {},
 
     INIT: {},
 
-    init: function (branch, defaults) {
+    init: function init(branch, defaults) {
         this._prefContexts = [];
 
         this.branch = services.pref[defaults ? "getDefaultBranch" : "getBranch"](branch || "");
         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.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 = {};
     },
 
         if (!defaults)
             this.restore();
 
         this._observers = {};
     },
 
-    cleanup: function cleanup() {
+    cleanup: function cleanup(reason) {
         if (this.defaults != this)
             this.defaults.cleanup();
         if (this.defaults != this)
             this.defaults.cleanup();
+
         this._observers = {};
         if (this.observe) {
             this.branch.removeObserver("", this);
             this.observe.unregister();
             delete this.observe;
         }
         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) {
     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.
      */
      * @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);
         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 ""
      */
      * @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);
         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;
         if (arguments.length > 2 && curval === value)
             return;
+
         let defval = this.defaults.get(name, null);
         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) {
 
         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);
             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 {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._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.
      */
      * @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._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 {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
 
         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);
 
         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.
      */
      *
      * @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);
         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
      */
      * @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);
         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.
      */
      *
      * @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
     },
 
         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.
      */
     /**
      * 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,
         util.assert(this.branch.getPrefType(name) === Ci.nsIPrefBranch.PREF_BOOL,
-                    _("error.trailing", name + "!"));
+                    _("error.trailingCharacters", name + "!"));
         this.set(name, !this.get(name));
     },
 
         this.set(name, !this.get(name));
     },
 
@@ -298,7 +354,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference])
      *
      * @see #withContext
      */
      *
      * @see #withContext
      */
-    pushContext: function () {
+    pushContext: function pushContext() {
         this._prefContexts.push({});
     },
 
         this._prefContexts.push({});
     },
 
@@ -307,7 +363,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference])
      *
      * @see #withContext
      */
      *
      * @see #withContext
      */
-    popContext: function () {
+    popContext: function popContext() {
         for (let [k, v] in Iterator(this._prefContexts.pop()))
             this.set(k, v);
     },
         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
      */
      * @see #pushContext
      * @see #popContext
      */
-    withContext: function (func, self) {
+    withContext: function withContext(func, self) {
         try {
             this.pushContext();
             return func.call(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"];
         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();
         };
     },
             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, ""]))]);
     }
         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"],
 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 = {};
 }, 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 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", {
 });
 
 var Item = Class("SanitizeItem", {
@@ -69,10 +69,10 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
 
         util.addObserver(this);
 
 
         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("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 = {};
         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);
             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", "");
                     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");
 
                 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);
                     }
                         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, ...
                 }
                 else {
                     // "Allow this site to open popups" ...
                     services.permissions.removeAll();
                     // Zoom level, ...
-                    services.contentprefs.removeGroupedPrefs();
+                    services.contentPrefs.removeGroupedPrefs();
                 }
 
                 // "Never remember passwords" ...
                 }
 
                 // "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))
                     if (!host || util.isSubdomain(domain, host))
-                        services.loginmanager.setLoginSavingEnabled(host, true);
+                        services.loginManager.setLoginSavingEnabled(host, true);
             },
             override: true
         });
             },
             override: true
         });
@@ -183,7 +185,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
                 append: {
                     SanitizeDialogPane:
                         <groupbox orient="horizontal" xmlns={XUL}>
                 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>{
                           <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: <>
                                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"
                         {
                           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;
     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);
 
             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) {
         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
     },
         deny:    2,
         session: 8
     },
+
     UNPERMS: Class.memoize(function () iter(this.PERMS).map(Array.reverse).toObject()),
     UNPERMS: Class.memoize(function () iter(this.PERMS).map(Array.reverse).toObject()),
+
     COMMANDS: {
     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: {
     },
 
     argPrefMap: {
@@ -377,7 +383,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
     }
 }, {
     load: function (dactyl, modules, window) {
     }
 }, {
     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;
     },
             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) {
         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"];
 
 
                 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) {
                     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
                     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)
 
                 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
                 else
-                    sanitizer.sanitize(items, range);
+                    sanitize(items);
+
             },
             {
                 argCount: "*", // FIXME: should be + and 0
             },
             {
                 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);
                         },
                                 !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",
                     }, {
                         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 &&
                 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"],
             });
 
         options.add(["sanitizeshutdown", "ss"],
@@ -593,10 +613,10 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
                         sanitizer.runAtShutdown = false;
                     else {
                         sanitizer.runAtShutdown = true;
                         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,
                         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;
                 }
                     }
                     return value;
                 }
@@ -667,7 +687,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
                 },
                 initialValue: true,
                 persist: false,
                 },
                 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("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");
         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("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("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");
         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("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");
         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("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");
         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("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");
     },
         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} 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.
      *     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 {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}.
      *     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.
      */
      *
      * @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)
 }, {
 }, {
         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();
 
     init: function () {
         this.cleanup();
+
+        if (services.bootstrap && !services.bootstrap.session)
+            services.bootstrap.session = {};
+        this.session = services.bootstrap ? services.bootstrap.session : {};
     },
 
     cleanup: function () {
     },
 
     cleanup: function () {
@@ -294,9 +298,9 @@ var File = Class("File", {
         let file = services.File();
 
         if (path instanceof Ci.nsIFile)
         let file = services.File();
 
         if (path instanceof Ci.nsIFile)
-            file = path.QueryInterface(Ci.nsIFile).clone();
+            file = path.clone();
         else if (/file:\/\//.test(path))
         else if (/file:\/\//.test(path))
-            file = services["file:"]().getFileFromURLSpec(path);
+            file = services["file:"].getFileFromURLSpec(path);
         else {
             try {
                 let expandedPath = File.expandPath(path);
         else {
             try {
                 let expandedPath = File.expandPath(path);
@@ -316,14 +320,19 @@ var File = Class("File", {
         return self;
     },
 
         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())
     /**
      * 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())
         if (!this.isDirectory())
-            throw Error("Not a directory");
+            throw Error(_("io.eNotDir"));
         for (let file in iter(this.directoryEntries))
             yield File(file);
     },
         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.
      *
      * @param {boolean} sort Whether to sort the returned directory
      *     entries.
-     * @returns {nsIFile[]}
+     * @returns {[nsIFile]}
      */
     readDirectory: function (sort) {
         if (!this.isDirectory())
      */
     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)
 
         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 {
 
         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;
             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;
     get fullCSS() {
         let filter = this.sites;
         let css = this.css;
+
+        let preamble = "/* " + this.uri + (this.agent ? " (agent)" : "") + " */\n\n" + namespace + "\n";
         if (filter[0] == "*")
         if (filter[0] == "*")
-            return namespace + css;
+            return preamble + css;
 
         let selectors = filter.map(function (part)
 
         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               ");
                               .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];
 
         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;
         };
     },
             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) }),
 
     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()
         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);
 
                  .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/>
                 <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;"/>
                 </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;
 
             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) {
     },
 
     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 = [
             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.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));
             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) {
         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);
             });
         }
                 context.filters.push(function (item) !!item.active == active);
             });
         }
@@ -553,17 +562,16 @@ var Styles = Module("Styles", {
                 let [filter, css] = args;
 
                 if (!css)
                 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,
                 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) {
 
                     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;
                             css = sheet.css + " " + css;
-
                         }
                     }
                     let style = args["-group"].add(args["-name"], filter, css, args["-agent"]);
                         }
                     }
                     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"]);
                 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)
                     }
                     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");
                     }
                 },
                         context.fork("css", 0, modules.completion, "css");
                     }
                 },
@@ -607,9 +616,8 @@ var Styles = Module("Styles", {
                                     command: "style",
                                     arguments: [style.sites.join(",")],
                                     literalArg: style.css,
                                     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
                                         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"]),
             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);
 
                     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) {
                 "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);
                             this.target.commands[this.command].call(this.target);
                         else
                             this.target.command(this.command);
@@ -128,7 +128,7 @@ var Template = Module("Template", {
             },
 
             get commandAllowed() {
             },
 
             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);
                     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.
                       -->
                       - 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>
                    <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);
 
         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;
             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);
 
         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;
             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]|^)
     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 () {
             (?=      [[\)!,:;./\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))
 
     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;
     },
         else
             return str;
     },
@@ -363,7 +363,9 @@ var Template = Module("Template", {
         // <e4x>
         return <table>
                 <tr style="text-align: left;" highlight="Title">
         // <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])
                 </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 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>;
                     </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") +
                             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>) : "" }
                         }</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"],
 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");
 }, 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
      */
      *
      * @param {object} obj
      */
-    addObserver: function (obj) {
+    addObserver: update(function addObserver(obj) {
         if (!obj.observers)
             obj.observers = obj.observe;
 
         function register(meth) {
         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);
                 }
                 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")
                 }
                 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);
                 }
                     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");
 
         obj.observe.unregister = function () register("removeObserver");
         register("addObserver");
-    },
+    }, { dump: dump, Error: Error }),
 
     /*
      * Tests a condition and throws a FailedAssertion error on
 
     /*
      * 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);
     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}
      */
      * @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
 
     /**
      * 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),
 
         // 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),
 
         // 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) + "]");
     },
 
         return RegExp("[" + util.regexp.escape(list) + "]");
     },
@@ -308,7 +309,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             }
             else if (char === "]") {
                 stack.pop();
             }
             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];
             }
             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));
 
         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;
     },
 
         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]);
     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
             ([^]*?) // 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)) {
             )
         ]]>, "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)
             end += match[0].length;
 
             if (prefix)
@@ -374,11 +400,11 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             }
             else if (close) {
                 stack.pop();
             }
             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);
             }
             else {
                 let [, flags, name] = /^((?:[a-z]-)*)(.*)/.exec(macro);
-                flags = set(flags);
+                flags = Set(flags);
 
                 let quote = util.identity;
                 if (flags.q)
 
                 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 (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(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;
 
                     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));
 
         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;
     },
 
         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))
     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 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
             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"]
      *
      * 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) {
      * @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];
 
         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));
         }
             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 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(""));
         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),
 
     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 "";
     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;
         }
             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);
 
         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,
 
      */
     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;
     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.
      */
      * 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"]),
 
                          "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)
         [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)
         if (hours)
-            return hours + "h " + minutes + "m";
+            return /*L*/hours + "h " + minutes + "m";
         if (minutes)
         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)
                 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)
 
             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;
         }
         catch (e) {}
         return null;
@@ -826,7 +916,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
             return xmlhttp;
         }
         catch (e) {
             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;
         }
     },
             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
     /**
      * 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()
      */
     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(" | ");
     },
 
                            .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)),
     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?
      * @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
 
     /**
      * 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(
                     }</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) + ">");
             }
                      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: {
     },
 
     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);
 
             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();
                 }
 
             JSMLoader.cleanup();
@@ -1120,6 +1256,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         "dactyl-purge": function () {
             this.rehashing = 1;
         },
         "dactyl-purge": function () {
             this.rehashing = 1;
         },
+
         "toplevel-window-ready": function (window, data) {
             window.addEventListener("DOMContentLoaded", wrapCallback(function listener(event) {
                 if (event.originalTarget === window.document) {
         "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);
     },
 
                 }), 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);
     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;
 
             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.
 
             // Guard against horrible add-ons that use eval-based monkey
             // patching.
+            let value = desc.value;
             if (callable(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) {
 
                 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;
                         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))
         }, 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));
 
         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];
         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)
 
         // 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))
 
         // 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) {
     rehash: function (args) {
-        JSMLoader.commandlineArgs = args;
+        storage.session.commandlineArgs = args;
         this.timeout(function () {
         this.timeout(function () {
+            services.observer.notifyObservers(null, "startupcache-invalidate", "");
             this.rehashing = true;
             let addon = config.addon;
             addon.userDisabled = true;
             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,
     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;
     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(".");
         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);
     },
 
         return ary.filter(function (h) h.length >= base.length);
     },
 
@@ -1586,20 +1777,6 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
         return true;
     },
 
         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
     /**
      * 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,
     },
 
     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 {
     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)
             do {
                 mainThread.processNextEvent(!flush);
                 if (util.interrupted)
-                    throw new Error("Interrupted");
+                    throw Error("Interrupted");
             }
             while (flush === true && mainThread.hasPendingEvents());
         }
             }
             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;
 
     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;
     },
 
         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);
     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,
 
     /**
     wrapCallback: wrapCallback,
 
     /**
@@ -1722,7 +1953,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
      */
     trapErrors: function trapErrors(func, self) {
         try {
      */
     trapErrors: function trapErrors(func, self) {
         try {
-            if (isString(func))
+            if (!callable(func))
                 func = self[func];
             return func.apply(self || this, Array.slice(arguments, 2));
         }
                 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;
     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 {
     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);
             }
             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) {
     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);
             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;
 }
 
     -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;
 [dactyl|highlight~=hints] {
     -moz-binding: url(resource://dactyl-content/bindings.xml#hints) !important;
+    position: static !important;
 }
 
 [dactyl|highlight~=HintImage],
 }
 
 [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
      */
     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");
 
         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.
 
     /**
      * Returns dactyl to Ex mode.
index 59724b559a9c54c898fea7c9d76281e34b6bfebd..c65365acf0da90d6b110ef618283d886b01e6af1 100644 (file)
@@ -47,12 +47,22 @@ var tests = {
     "!": {
         multiOutput: ["echo foo"]
     },
     "!": {
         multiOutput: ["echo foo"]
     },
+    get Clistkeys() this.listcommands,
+    Cmap: {},
+    Cnoremap: {},
+    Cunmap: {},
+    get Ilistkeys() this.listcommands,
+    Imap: {},
+    Inoremap: {},
+    Iunmap: {},
     abbreviate: {
     abbreviate: {
+        error: ["!"],
         someOutput: ["", "abc"],
         noOutput: ["abc def", "-js abc def"],
         completions: ["", "abc ", "-js abc "]
     },
     addons: {
         someOutput: ["", "abc"],
         noOutput: ["abc def", "-js abc def"],
         completions: ["", "abc ", "-js abc "]
     },
     addons: {
+        error: ["!"],
         multiOutput: ["", "dactyl", "-type=extension", "-type=extension dactyl"],
         completions: [
             "",
         multiOutput: ["", "dactyl", "-type=extension", "-type=extension dactyl"],
         completions: [
             "",
@@ -75,6 +85,8 @@ var tests = {
         anyOutput: ["about:pentadactyl"],
         completions: [["", hasItems]]
     },
         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"],
     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:"
         ]
     },
             "-max=1 -keyword=foo -tags=bar -title=baz about:"
         ]
     },
+    bnoremap: {},
     buffer: {
         anyOutput: ["", "1"],
         noOutput: ["!", "! 1"],
     buffer: {
         anyOutput: ["", "1"],
         noOutput: ["!", "! 1"],
@@ -104,15 +117,21 @@ var tests = {
         ]
     },
     buffers: {
         ]
     },
     buffers: {
+        error: ["!"],
         multiOutput: ["", "1"],
         completions: ["", "1"]
     },
         multiOutput: ["", "1"],
         completions: ["", "1"]
     },
+    bunmap: {},
     cd: {
     cd: {
+        error: ["!"],
         singleOutput: ["", "~/"],
         completions: ["", "~/"]
     },
         singleOutput: ["", "~/"],
         completions: ["", "~/"]
     },
+    get clistkeys() this.listcommands,
+    cmap: {},
+    cnoremap: {},
     colorscheme: {
     colorscheme: {
-        error: ["", "some-nonexistent-scheme"]
+        error: ["!", "", "some-nonexistent-scheme"]
     },
     command: {
         init: ["delc!"],
     },
     command: {
         init: ["delc!"],
@@ -131,15 +150,22 @@ var tests = {
             ["-group=user ", hasItems]
         ]
     },
             ["-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"],
     cookies: {
         anyOutput: ["dactyl.sf.net", "dactyl.sf.net list"],
-        error: [""],
+        error: ["!", ""],
         completions: [
             "",
             ["dactyl.sf.net ", hasItems]
         ]
     },
         completions: [
             "",
             ["dactyl.sf.net ", hasItems]
         ]
     },
+    cunabbreviate: {},
+    cunmap: {},
     delbmarks: { anyOutput: ["", "about:pentadactyl"] },
     delcommand: [
         {
     delbmarks: { anyOutput: ["", "about:pentadactyl"] },
     delcommand: [
         {
@@ -157,8 +183,12 @@ var tests = {
             error: ["foo"]
         }
     ],
             error: ["foo"]
         }
     ],
+    delgroup: {
+        error: ["", "! foo", "builtin"],
+        completions: [""]
+    },
     delmacros: {
     delmacros: {
-        error: [""],
+        error: ["", "! foo"],
         noOutput: ["x"],
         completions: ["", "x"]
     },
         noOutput: ["x"],
         completions: ["", "x"]
     },
@@ -168,17 +198,27 @@ var tests = {
         completions: ["", "-name=", "-name=foo ", "-index=", "-index="]
     },
     dialog: {
         completions: ["", "-name=", "-name=foo ", "-index=", "-index="]
     },
     dialog: {
+        error: ["!", ""],
         // Skip implementation for now
         completions: [
             ["", hasntNullItems]
         ]
     },
         // Skip implementation for now
         completions: [
             ["", hasntNullItems]
         ]
     },
-    doautoall: {}, // Skip for now
-    doautocmd: {}, // Skip for now
+    dlclear: {
+        error: ["!"]
+    },
+    doautoall: {
+        error: ["!"]
+    },
+    doautocmd: {
+        error: ["!"]
+    },
     downloads: {
     downloads: {
+        error: ["!"],
         multiOutput: ["", "dactyl", "dactyl"]
     },
     echo: {
         multiOutput: ["", "dactyl", "dactyl"]
     },
     echo: {
+        error: ["!"],
         singleOutput: [
             ["' - '", " - "]
         ],
         singleOutput: [
             ["' - '", " - "]
         ],
@@ -199,25 +239,35 @@ var tests = {
         __proto__: this.echo,
     }),
     get echomsg() this.echo,
         __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"],
     emenu: {
         noOutput: ["View.Zoom.Zoom In", "View.Zoom.Zoom Out"],
-        error: [""],
+        error: ["!", ""],
         completions: [
             ["", hasItems],
             ["View.", hasItems]
         ]
     },
         completions: [
             ["", hasItems],
             ["View.", hasItems]
         ]
     },
-    endif: {}, // Skip for now
+    endif: {
+        error: ["!", "foo"]
+    },
     execute: {
     execute: {
+        error: ["!"],
         noOutput: ["", "'js " + "".quote() + "'"],
         someOutput: ["'ls'"],
         completions: [["", hasItems]]
     },
         noOutput: ["", "'js " + "".quote() + "'"],
         someOutput: ["'ls'"],
         completions: [["", hasItems]]
     },
+    exit: {
+        error: ["foo"]
+    },
     extadd: {
         completions: [["", hasItems]],
     extadd: {
         completions: [["", hasItems]],
-        error: [""]
+        error: ["!", ""]
     },
     extdelete: {
         completions: [["", hasItems]],
     },
     extdelete: {
         completions: [["", hasItems]],
@@ -239,12 +289,14 @@ var tests = {
         noOutput: ["<Esc>"],
         error: [""]
     },
         noOutput: ["<Esc>"],
         error: [""]
     },
-    finish: { noOutput: [""] },
+    finish: {
+        error: ["!", "foo"],
+        noOutput: [""]
+    },
     forward: { noOutput: [""] },
     forward: { noOutput: [""] },
-    frameonly: { noOutput: [""] },
-    delgroup: {
-        error: ["builtin"],
-        completions: [""]
+    frameonly: {
+        error: ["!", "foo"],
+        noOutput: [""]
     },
     group: {
         multiOutput: [""],
     },
     group: {
         multiOutput: [""],
@@ -263,6 +315,7 @@ var tests = {
     },
     hardcopy: {}, // Skip for now
     help: {
     },
     hardcopy: {}, // Skip for now
     help: {
+        error: ["!"],
         noOutput: ["", "intro"],
         cleanup: ["tabdelete", "tabdelete"],
         completions: [
         noOutput: ["", "intro"],
         cleanup: ["tabdelete", "tabdelete"],
         completions: [
@@ -272,6 +325,7 @@ var tests = {
     },
     get helpall() this.help,
     highlight: {
     },
     get helpall() this.help,
     highlight: {
+        error: ["!"],
         multiOutput: ["", "Help"],
         noOutput: [
             "Help foo: bar;",
         multiOutput: ["", "Help"],
         noOutput: [
             "Help foo: bar;",
@@ -299,9 +353,17 @@ var tests = {
             "-sort=+date about:"
         ]
     },
             "-sort=+date about:"
         ]
     },
-    if: {}, // Skip for now
+    if: {
+        error: ["!", ""],
+    },
+    iabbreviate: {},
+    get ilistkeys() this.listcommands,
+    imap: {},
+    inoremap: {},
+    iunabbreviate: {},
+    iunmap: {},
     javascript: {
     javascript: {
-        noOutput: ["''", "'\\n'", "<pre>foo bar</pre>", "window"],
+        noOutput: ["''", "'\\n'", "<pre>foo bar</pre>", "window", "<<EOF\n''\nEOF"],
         completions: [
             ["", hasItems],
             ["window", hasItems],
         completions: [
             ["", hasItems],
             ["window", hasItems],
@@ -325,15 +387,20 @@ var tests = {
         ]
     },
     jumps: {
         ]
     },
     jumps: {
+        error: ["!", "foo"],
         multiOutput: [""]
     },
     keepalt: {
         multiOutput: [""]
     },
     keepalt: {
-        error: [""],
+        error: ["!", "", "some-nonexistent-command"],
         noOutput: ["js ''"],
         noOutput: ["js ''"],
-        anyOutput: ["echo 'foo'"]
+        anyOutput: ["echo 'foo'"],
+        completions: [["", hasItems]]
+    },
+    let: {
+        error: ["!"]
     },
     },
-    let: {}, // Deprecated. Fuck it.
     listcommands: {
     listcommands: {
+        error: ["!"],
         anyOutput: ["", "in"],
         completions: [
             ["", hasItems],
         anyOutput: ["", "in"],
         completions: [
             ["", hasItems],
@@ -344,10 +411,12 @@ var tests = {
     get listoptions() this.listcommands,
     loadplugins: {},
     macros: {
     get listoptions() this.listcommands,
     loadplugins: {},
     macros: {
+        error: ["!"],
         multiOutput: [""],
         completions: [""]
     },
     map: {
         multiOutput: [""],
         completions: [""]
     },
     map: {
+        error: ["!"],
         init: ["unmap!"],
         anyOutput: [""],
         singleOutput: ["i"],
         init: ["unmap!"],
         anyOutput: [""],
         singleOutput: ["i"],
@@ -378,21 +447,22 @@ var tests = {
         ]
     },
     mark: {
         ]
     },
     mark: {
-        error: ["", "#", "xy"],
+        error: ["!", "", "#", "xy"],
         noOutput: ["y"],
         completions: [""]
     },
     marks: {
         init: ["delmarks q"],
         multiOutput: ["", "y"],
         noOutput: ["y"],
         completions: [""]
     },
     marks: {
         init: ["delmarks q"],
         multiOutput: ["", "y"],
-        error: ["q", "#"],
+        error: ["!", "q", "#"],
         completions: [""]
     },
     messages: {
         completions: [""]
     },
     messages: {
+        error: ["!", "foo"],
         anyOutput: ["messages"]
     },
     messclear: {
         anyOutput: ["messages"]
     },
     messclear: {
-        error: ["q"],
+        error: ["!", "foo"],
         noOutput: [""]
     },
     mkpentadactylrc: {
         noOutput: [""]
     },
     mkpentadactylrc: {
@@ -420,12 +490,26 @@ var tests = {
         ],
         cleanup: ["silent !rm -r some-nonexistent-pentadactyl-dir/"]
     },
         ],
         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: {
     normal: {
+        error: [""],
         noOutput: ["<Nop>"],
         singleOutput: ["<C-g>"],
         multiOutput: ["g<C-g>"]
     },
         noOutput: ["<Nop>"],
         singleOutput: ["<C-g>"],
         multiOutput: ["g<C-g>"]
     },
+    nunmap: {},
     open: {
     open: {
+        error: ["!"],
         noOutput: ["about:blank | about:home"],
         completions: [
             ["", hasItems],
         noOutput: ["about:blank | about:home"],
         completions: [
             ["", hasItems],
@@ -442,13 +526,18 @@ var tests = {
     pageinfo: {
         multiOutput: ["", "fgm"],
         completions: [["", hasItems]],
     pageinfo: {
         multiOutput: ["", "fgm"],
         completions: [["", hasItems]],
-        error: ["abcdefghijklmnopqrstuvwxyz", "f g m"]
+        error: ["!", "abcdefghijklmnopqrstuvwxyz", "f g m"]
     },
     pagestyle: {
     },
     pagestyle: {
+        error: ["!"],
         completions: [""]
     },
         completions: [""]
     },
-    preferences: {}, // Skip for now
+    pintab: {},
+    preferences: {
+        error: ["foo"]
+    },
     pwd: {
     pwd: {
+        error: ["!", "foo"],
         singleOutput: [""]
     },
     qmark: {
         singleOutput: [""]
     },
     qmark: {
@@ -456,13 +545,16 @@ var tests = {
             "m",
             "m foo bar"
         ],
             "m",
             "m foo bar"
         ],
-        error: ["", "#"],
+        error: ["!", "", "#"],
         completions: [
             ["", hasItems],
             ["m ", hasItems]
         ]
     },
     qmarks: [
         completions: [
             ["", hasItems],
             ["m ", hasItems]
         ]
     },
     qmarks: [
+        {
+            error: ["!"]
+        },
         {
             init: ["delqmarks a-zA-Z0-9"],
             error: ["", "x"],
         {
             init: ["delqmarks a-zA-Z0-9"],
             error: ["", "x"],
@@ -473,19 +565,30 @@ var tests = {
             completions: [["", hasItems]]
         }
     ],
             completions: [["", hasItems]]
         }
     ],
-    quit: {}, // Skip for now
-    quitall: {}, // Skip for now
+    quit: {
+        error: ["foo"]
+    },
+    quitall: {
+        error: ["!", "foo"]
+    },
     redraw: {
     redraw: {
+        error: ["!", "foo"],
         noOutput: [""]
     },
         noOutput: [""]
     },
-    rehash: {}, // Skip for now
+    rehash: {
+        error: ["!"]
+    },
     reload: {
     reload: {
+        error: ["foo"],
         noOutput: [""]
     },
     reloadall: {
         noOutput: [""]
     },
     reloadall: {
+        error: ["foo"],
         noOutput: [""]
     },
         noOutput: [""]
     },
-    restart: {}, // Skip
+    restart: {
+        error: ["!", "foo"]
+    },
     runtime: {
         init: [
             "js File('~/.pentadactyl/some-nonexistent/good.css').write('')",
     runtime: {
         init: [
             "js File('~/.pentadactyl/some-nonexistent/good.css').write('')",
@@ -501,6 +604,7 @@ var tests = {
             "some-nonexistent/good.penta"
         ],
         error: [
             "some-nonexistent/good.penta"
         ],
         error: [
+            "",
             "some-nonexistent/bad.js",
             "some-nonexistent/bad.penta"
         ],
             "some-nonexistent/bad.js",
             "some-nonexistent/bad.penta"
         ],
@@ -539,13 +643,16 @@ var tests = {
     },
     saveas: {},
     sbclose: {
     },
     saveas: {},
     sbclose: {
+        error: ["!", "foo"],
         noOutput: [""]
     },
         noOutput: [""]
     },
-    scriptnames: {},
+    scriptnames: {
+        error: ["!", "foo"]
+    },
     set: {
         multiOutput: [
             "vb?", "cpt?", "messages?", "titlestring?", "au?", "eht?",
     set: {
         multiOutput: [
             "vb?", "cpt?", "messages?", "titlestring?", "au?", "eht?",
-            "cpt", "messages", "titlestring", "au", "eht"
+            "cpt", "messages", "titlestring", "au", "eht", "! "
         ],
         noOutput: ["vb", "novb"],
         completions: [
         ],
         noOutput: ["vb", "novb"],
         completions: [
@@ -595,6 +702,7 @@ var tests = {
         ]
     },
     silent: {
         ]
     },
     silent: {
+        error: ["!"],
         noOutput: [
             "echo 'foo'",
             "echo " + "foo\nbar".quote(),
         noOutput: [
             "echo 'foo'",
             "echo " + "foo\nbar".quote(),
@@ -613,6 +721,7 @@ var tests = {
             ".pentadactyl/some-nonexistent/good.penta"
         ],
         error: [
             ".pentadactyl/some-nonexistent/good.penta"
         ],
         error: [
+            "",
             ".pentadactyl/some-nonexistent/really-nonexistent.js",
             "~/.pentadactyl/some-nonexistent/bad.js",
             "~/.pentadactyl/some-nonexistent/bad.penta",
             ".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]
         ]
     }),
             ["resource://dactyl/", hasItems]
         ]
     }),
-    stop: { noOutput: [""] },
-    stopall: { noOutput: [""] },
+    stop: {
+        error: ["!", "foo"],
+        noOutput: [""]
+    },
+    stopall: {
+        error: ["!", "foo"],
+        noOutput: [""]
+    },
     style: {
     style: {
+        error: ["!"],
         cleanup: ["delstyle -n foo"],
         noOutput: [
             "-name=foo http://does.not.exist/* div { display: inline; }",
         cleanup: ["delstyle -n foo"],
         noOutput: [
             "-name=foo http://does.not.exist/* div { display: inline; }",
@@ -671,19 +787,59 @@ var tests = {
             ["-index=", hasNItems(2)]
         ]
     },
             ["-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: {},
     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)],
     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)]
         ],
             ["Navigation Toolbar", toolbarState("#nav-bar", false)],
             ["Bookmarks Toolbar", toolbarState("#PersonalToolbar", false)]
         ],
-        error: ["", "foo"]
+        error: ["!", "", "foo"]
     },
     toolbarshow: {
         completions: [["", hasItems]],
     },
     toolbarshow: {
         completions: [["", hasItems]],
@@ -702,7 +858,7 @@ var tests = {
             ["Navigation Toolbar", toolbarState("#nav-bar", true)],
             ["Bookmarks Toolbar", toolbarState("#PersonalToolbar", true)]
         ],
             ["Navigation Toolbar", toolbarState("#nav-bar", true)],
             ["Bookmarks Toolbar", toolbarState("#PersonalToolbar", true)]
         ],
-        error: ["", "foo"]
+        error: ["!", "", "foo"]
     },
     toolbartoggle: {
         completions: [["", hasItems]],
     },
     toolbartoggle: {
         completions: [["", hasItems]],
@@ -714,15 +870,25 @@ var tests = {
             ["Navigation Toolbar", toolbarState("#nav-bar", false)],
             ["Bookmarks Toolbar", toolbarState("#PersonalToolbar", false)]
         ],
             ["Navigation Toolbar", toolbarState("#nav-bar", false)],
             ["Bookmarks Toolbar", toolbarState("#PersonalToolbar", false)]
         ],
-        error: ["", "foo"]
+        error: ["!", "", "foo"]
     },
     },
+    tunmap: {},
     unabbreviate: {
         noOutput: ["abc", "! "],
         error: [""]
     },
     unabbreviate: {
         noOutput: ["abc", "! "],
         error: [""]
     },
-    undo: {},
-    undoall: {},
-    unlet: {},
+    undo: {
+        error: ["!"]
+    },
+    undoall: {
+        error: ["!", "foo"]
+    },
+    unpintab: {
+        error: ["!"]
+    },
+    unlet: {
+        error: [""],
+    },
     unmap: {
         noOutput: [
             "i",
     unmap: {
         noOutput: [
             "i",
@@ -738,8 +904,11 @@ var tests = {
             "-group="
         ]
     },
             "-group="
         ]
     },
-    verbose: {},
+    verbose: {
+        error: ["!", ""]
+    },
     version: {
     version: {
+        error: ["foo"],
         multiOutput: [
             ["", function (msg) {
                 var res = /(\w+dactyl) (\S+) \(([\^)]+)\) running on:\nMozilla/;
         multiOutput: [
             ["", function (msg) {
                 var res = /(\w+dactyl) (\S+) \(([\^)]+)\) running on:\nMozilla/;
@@ -748,11 +917,25 @@ var tests = {
         ]
     },
     viewsource: {},
         ]
     },
     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/],
     yank: {
         multiOutput: [
             ["foo".quote(), /foo/],
@@ -760,6 +943,7 @@ var tests = {
             [":addons", /Pentadactyl/]
         ],
         error: [
             [":addons", /Pentadactyl/]
         ],
         error: [
+            "!", "",
             ":echoerr " + "foo".quote()
         ],
         completions: [
             ":echoerr " + "foo".quote()
         ],
         completions: [
index c268743be3082072e55fcdb128b6773d570d045e..89301320f766f619c95b91b18da0295391df5aaf 100644 (file)
@@ -31,7 +31,7 @@ var options = {};
 
 function testCompleters() {
     for (var option in dactyl.modules.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");
         }
             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  ***/
     },
 
     /*** 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",
 
     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 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)),
 
      */
     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.
      * 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))
      */
     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.
      *
      * @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();
      */
     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:
         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:
                     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:
                     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:
                     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:
                     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:
                     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:
                     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:
                     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;
             }
                     autocommands.trigger("StreamStop", { track: gMM.sequencer.currentItem });
                     break;
             }
@@ -332,7 +333,7 @@ const Player = Module("player", {
     /**
      * Returns an array of all available playlists.
      *
     /**
      * Returns an array of all available playlists.
      *
-     * @returns {sbIMediaList[]}
+     * @returns {[sbIMediaList]}
      */
     getPlaylists: function getPlaylists() {
         let mainLibrary = LibraryUtils.mainLibrary;
      */
     getPlaylists: function getPlaylists() {
         let mainLibrary = LibraryUtils.mainLibrary;
@@ -366,7 +367,7 @@ const Player = Module("player", {
     /**
      * Returns an array of all available media pages.
      *
     /**
      * Returns an array of all available media pages.
      *
-     * @returns {sbIMediaPageInfo[]}
+     * @returns {[sbIMediaPageInfo]}
      */
     getMediaPages: function getMediaPages() {
         let list = SBGetBrowser().currentMediaPage.mediaListView.mediaList;
      */
     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
      * media items.
      *
      * @param {sbIMediaPage} page
@@ -660,7 +661,7 @@ const Player = Module("player", {
                 let arg = args[0];
 
                 dactyl.assert(arg, _("error.argumentRequired"));
                 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;
 
 
                 let level = parseInt(arg, 10) / 100;
 
index fd788b60b360b9aa6bad210b81b4ee2988f896d8..c4d308c8572a53fb170c76e9a8e3a30891c90674 100644 (file)
@@ -7,30 +7,6 @@
     xmlns="&xmlns.dactyl;"
     xmlns:html="&xmlns.html;">
 
     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>
 <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>
         etc.)
     </li>
     <li>
-        <link topic="cmdline">Command-line mode</link>:
+        <link topic="cmdline">Command Line mode</link>:
         Command-line editing.
     </li>
     <li>
         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>
 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:
   * Š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
   * 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)
 
 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" \
 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
 
 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.
     • 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]
         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
         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]
     • 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]
     • 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]
       - 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
         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]
       - :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 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
       - :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]
       - 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.
     • 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]
       - 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
       - '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]
         '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]
         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]
     • 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 '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]
       - 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]
       - '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 'passkeys' option. [b3]
+      - Added 'passunknown' option. [b7]
       - Changed 'urlseparator' default value to "|". [b3]
       - Added "passwords" and "venkman" dialogs to :dialog. [b2]
       - 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 '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]
     • 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:
 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
 
 
 - unify the disgusting hodge podge of contract specification styles
 
-REFACTORING:
-- remove unnecessary usage of "self"
-
 BUGS:
 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:
 - ;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 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 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 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 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 [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 :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 :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 ;?<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 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
 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",
         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",
         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"
     },
 
         titlestring: "Pentadactyl"
     },
 
-    features: set([
+    features: Set([
         "bookmarks", "hints", "history", "marks", "quickmarks", "sanitizer",
         "session", "tabs", "tabs_undo", "windows"
     ]),
         "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,
                 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);
 
                 if (!args.length)
                     return window.toggleSidebar(title.value ? null : config.lastSidebar);
@@ -227,7 +227,7 @@ var Config = Module("config", ConfigBase, {
                         return;
                     }
 
                         return;
                     }
 
-                return dactyl.echoerr("No sidebar " + args[0] + " found");
+                return dactyl.echoerr(_("error.invalidArgument", args[0]));
             },
             {
                 argCount: "?",
             },
             {
                 argCount: "?",
@@ -248,7 +248,7 @@ var Config = Module("config", ConfigBase, {
                 });
             },
             {
                 });
             },
             {
-                argCount: "+",
+                argCount: "1",
                 completer: function (context) completion.ex(context),
                 literal: 0,
                 subCommand: 0
                 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: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"
         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: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>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>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: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>
         </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>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>
     <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;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>
 </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>
         etc.)
     </li>
     <li>
-        <link topic="cmdline">Command-line mode</link>:
+        <link topic="cmdline">Command Line mode</link>:
         Command-line editing.
     </li>
     <li>
         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>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>
     <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
     &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>
 </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>
     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>
     <k name="Esc"/> will also return you to Normal mode from most other modes in
     &dactyl.appName;.
 </p>
 </p>
 
 <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>.
 </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
     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>
 </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";
 
 // given in the LICENSE.txt file included with this file.
 "use strict";
 
-const Addressbook = Module("addressbook", {
+var Addressbook = Module("addressbook", {
     init: function () {
     },
 
     init: function () {
     },
 
@@ -63,9 +63,9 @@ const Addressbook = Module("addressbook", {
 
         if (addresses.length < 1) {
             if (!filter)
 
         if (addresses.length < 1) {
             if (!filter)
-                dactyl.echoerr("Exxx: No contacts", commandline.FORCE_SINGLELINE);
+                dactyl.echoerr(_("addressbook.noContacts"), commandline.FORCE_SINGLELINE);
             else
             else
-                dactyl.echoerr("Exxx: No contacts matching string '" + filter + "'", commandline.FORCE_SINGLELINE);
+                dactyl.echoerr(_("addressbook.noMatchingContacts"), filter, commandline.FORCE_SINGLELINE);
             return false;
         }
 
             return false;
         }
 
@@ -100,9 +100,9 @@ const Addressbook = Module("addressbook", {
                     displayName = this.generateDisplayName(firstName, lastName);
 
                 if (addressbook.add(mailAddr, firstName, lastName, displayName))
                     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
                 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";
 
 // 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",
     name: "teledactyl",
     appName: "Teledactyl",
     idName: "TELEDACTYL",
@@ -16,12 +16,16 @@ const Config = Module("config", ConfigBase, {
         init: function init() {
             init.superapply(this, arguments);
 
         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=""/></> });
         },
 
 
             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,
 
 
         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))],
 
             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 });
             },
             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  ***/
     },
 
     /*** 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"])),
 
         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";
 
 // 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 = [];
     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)
     _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)
         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)
 
         let count = gDBView.selection.count;
         if (!count)
@@ -129,7 +129,7 @@ const Mail = Module("mail", {
 
     get currentFolder() gFolderTreeView.getSelectedFolders()[0],
 
 
     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 = [];
     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())
                     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;
 
                     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)
 
                 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
                 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))))
 
                 // 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);
             },
 
                 mail.composeNewMail(mailargs);
             },
@@ -453,7 +453,7 @@ const Mail = Module("mail", {
             "Copy selected messages",
             function (args) { mail._moveOrCopy(true, args.literalArg); },
             {
             "Copy selected messages",
             function (args) { mail._moveOrCopy(true, args.literalArg); },
             {
-                argCount: 1,
+                argCount: "1",
                 completer: function (context) completion.mailFolder(context),
                 literal: 0
             });
                 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); },
             {
             "Move selected messages",
             function (args) { mail._moveOrCopy(false, args.literalArg); },
             {
-                argCount: 1,
+                argCount: "1",
                 completer: function (context) completion.mailFolder(context),
                 literal: 0
             });
                 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: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: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: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>
             </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>
         etc.)
     </li>
     <li>
-        <link topic="cmdline">Command-line mode</link>:
+        <link topic="cmdline">Command Line mode</link>:
         Command-line editing.
     </li>
     <li>
         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:
+