]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/content/mappings.js
Import r6948 from upstream hg supporting Firefox up to 24.*
[dactyl.git] / common / content / mappings.js
index 668d8ce106a02fffd666ae93fb797ee425a6621b..68bba94c6e9bed082211128e30069ddb5cf53b8e 100644 (file)
@@ -1,6 +1,6 @@
 // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
 // Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
-// Copyright (c) 2008-2011 by Kris Maglione <maglione.k at Gmail>
+// Copyright (c) 2008-2012 Kris Maglione <maglione.k at Gmail>
 //
 // This work is licensed for reuse under an MIT license. Details are
 // given in the LICENSE.txt file included with this file.
  * 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.
- * @param {Object} extraInfo An optional extra configuration hash. The
+ * @param {Object} info An optional extra configuration hash. The
  *     following properties are supported.
  *         arg     - see {@link Map#arg}
  *         count   - see {@link Map#count}
  * @private
  */
 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);
-
+    init: function (modes, keys, description, action, info) {
         this.id = ++Map.id;
         this.modes = modes;
         this._keys = keys;
         this.action = action;
         this.description = description;
 
-        if (Object.freeze)
-            Object.freeze(this.modes);
+        Object.freeze(this.modes);
 
-        if (extraInfo)
-            update(this, extraInfo);
+        if (info) {
+            if (Set.has(Map.types, info.type))
+                this.update(Map.types[info.type]);
+            this.update(info);
+        }
     },
 
-    name: Class.memoize(function () this.names[0]),
+    name: Class.Memoize(function () this.names[0]),
 
-    /** @property {string[]} All of this mapping's names (key sequences). */
-    names: Class.memoize(function () this._keys.map(function (k) events.canonicalKeys(k))),
+    /** @property {[string]} All of this mapping's names (key sequences). */
+    names: Class.Memoize(function () this._keys.map(function (k) DOM.Event.canonicalKeys(k))),
 
     get toStringParams() [this.modes.map(function (m) m.name), this.names.map(String.quote)],
 
@@ -58,7 +56,7 @@ var Map = Class("Map", {
 
     /** @property {number} A unique ID for this mapping. */
     id: null,
-    /** @property {number[]} All of the modes for which this mapping applies. */
+    /** @property {[Modes.Mode]} All of the modes for which this mapping applies. */
     modes: null,
     /** @property {function (number)} The function called to execute this mapping. */
     action: null,
@@ -74,12 +72,24 @@ var Map = Class("Map", {
      *     as an argument.
      */
     motion: false,
+
     /** @property {boolean} Whether the RHS of the mapping should expand mappings recursively. */
     noremap: false,
+
+    /** @property {function(object)} A function to be executed before this mapping. */
+    preExecute: function preExecute(args) {},
+    /** @property {function(object)} A function to be executed after this mapping. */
+    postExecute: function postExecute(args) {},
+
     /** @property {boolean} Whether any output from the mapping should be echoed on the command line. */
     silent: false,
+
     /** @property {string} The literal RHS expansion of this mapping. */
     rhs: null,
+
+    /** @property {string} The type of this mapping. */
+    type: "",
+
     /**
      * @property {boolean} Specifies whether this is a user mapping. User
      *     mappings may be created by plugins, or directly by users. Users and
@@ -96,7 +106,7 @@ var Map = Class("Map", {
      */
     hasName: function (name) this.keys.indexOf(name) >= 0,
 
-    get keys() this.names.map(mappings.expandLeader),
+    get keys() array.flatten(this.names.map(mappings.closure.expand)),
 
     /**
      * Execute the action for this mapping.
@@ -109,20 +119,23 @@ var Map = Class("Map", {
                 .map(function ([i, prop]) [prop, this[i]], arguments)
                 .toObject();
 
-        args = update({ context: contexts.context },
-                      this.hive.argsExtra(args),
-                      args);
+        args = this.hive.makeArgs(this.hive.group.lastDocument,
+                                  contexts.context,
+                                  args);
 
-        let self = this;
-        function repeat() self.action(args)
+        let repeat = () => this.action(args);
         if (this.names[0] != ".") // FIXME: Kludge.
             mappings.repeat = repeat;
 
         if (this.executing)
-            util.dumpStack("Attempt to execute mapping recursively: " + args.command);
-        dactyl.assert(!this.executing, _("map.recursive", args.command));
+            util.assert(!args.keypressEvents[0].isMacro,
+                        _("map.recursive", args.command),
+                        false);
 
         try {
+            dactyl.triggerObserver("mappings.willExecute", this, args);
+            mappings.pushCommand();
+            this.preExecute(args);
             this.executing = true;
             var res = repeat();
         }
@@ -132,12 +145,17 @@ var Map = Class("Map", {
         }
         finally {
             this.executing = false;
+            mappings.popCommand();
+            this.postExecute(args);
+            dactyl.triggerObserver("mappings.executed", this, args);
         }
         return res;
     }
 
 }, {
-    id: 0
+    id: 0,
+
+    types: {}
 });
 
 var MapHive = Class("MapHive", Contexts.Hive, {
@@ -159,10 +177,10 @@ var MapHive = Class("MapHive", Contexts.Hive, {
     },
 
     /**
-     * Adds a new default key mapping.
+     * Adds a new key mapping.
      *
-     * @param {number[]} modes The modes that this mapping applies to.
-     * @param {string[]} keys The key sequences which are bound to *action*.
+     * @param {[Modes.Mode]} modes The modes that this mapping applies to.
+     * @param {[string]} keys The key sequences which are bound to *action*.
      * @param {string} description A description of the key mapping.
      * @param {function} action The action invoked by each key sequence.
      * @param {Object} extra An optional extra configuration hash.
@@ -171,6 +189,10 @@ var MapHive = Class("MapHive", Contexts.Hive, {
     add: function (modes, keys, description, action, extra) {
         extra = extra || {};
 
+        modes = Array.concat(modes);
+        if (!modes.every(util.identity))
+            throw TypeError(/*L*/"Invalid modes: " + modes);
+
         let map = Map(modes, keys, description, action, extra);
         map.definedAt = contexts.getCaller(Components.stack.caller);
         map.hive = this;
@@ -273,7 +295,7 @@ var MapHive = Class("MapHive", Contexts.Hive, {
             delete this.states;
         },
 
-        states: Class.memoize(function () {
+        states: Class.Memoize(function () {
             var states = {
                 candidates: {},
                 mappings: {}
@@ -283,7 +305,7 @@ var MapHive = Class("MapHive", Contexts.Hive, {
                 for (let name in values(map.keys)) {
                     states.mappings[name] = map;
                     let state = "";
-                    for (let key in events.iterKeys(name)) {
+                    for (let key in DOM.Event.iterKeys(name)) {
                         state += key;
                         if (state !== name)
                             states.candidates[state] = (states.candidates[state] || 0) + 1;
@@ -299,6 +321,31 @@ var MapHive = Class("MapHive", Contexts.Hive, {
  */
 var Mappings = Module("mappings", {
     init: function () {
+        this.watches = [];
+        this._watchStack = 0;
+        this._yielders = 0;
+    },
+
+    afterCommands: function afterCommands(count, cmd, self) {
+        this.watches.push([cmd, self, Math.max(this._watchStack - 1, 0), count || 1]);
+    },
+
+    pushCommand: function pushCommand(cmd) {
+        this._watchStack++;
+        this._yielders = util.yielders;
+    },
+    popCommand: function popCommand(cmd) {
+        this._watchStack = Math.max(this._watchStack - 1, 0);
+        if (util.yielders > this._yielders)
+            this._watchStack = 0;
+
+        this.watches = this.watches.filter(function (elem) {
+            if (this._watchStack <= elem[2])
+                elem[3]--;
+            if (elem[3] <= 0)
+                elem[0].call(elem[1] || null);
+            return elem[3] > 0;
+        }, this);
     },
 
     repeat: Modes.boundProperty(),
@@ -307,13 +354,35 @@ var Mappings = Module("mappings", {
 
     get userHives() this.allHives.filter(function (h) h !== this.builtin, this),
 
-    expandLeader: function (keyString) keyString.replace(/<Leader>/i, options["mapleader"]),
+    expandLeader: deprecated("your brain", function expandLeader(keyString) keyString),
+
+    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) {
+        if (!/<\*-/.test(keys))
+            var res = keys;
+        else
+            res = util.debrace(DOM.Event.iterKeys(keys).map(function (key) {
+                if (/^<\*-/.test(key))
+                    return ["<", this.prefixes, key.slice(3)];
+                return key;
+            }, this).flatten().array).map(function (k) DOM.Event.canonicalKeys(k));
+
+        if (keys != arguments[0])
+            return [arguments[0]].concat(keys);
+        return keys;
+    },
 
     iterate: function (mode) {
         let seen = {};
         for (let hive in this.hives.iterValues())
             for (let map in array(hive.getStack(mode)).iterValues())
-                if (!set.add(seen, map.name))
+                if (!Set.add(seen, map.name))
                     yield map;
     },
 
@@ -330,8 +399,8 @@ var Mappings = Module("mappings", {
     /**
      * Adds a new default key mapping.
      *
-     * @param {number[]} modes The modes that this mapping applies to.
-     * @param {string[]} keys The key sequences which are bound to *action*.
+     * @param {[Modes.Mode]} modes The modes that this mapping applies to.
+     * @param {[string]} keys The key sequences which are bound to *action*.
      * @param {string} description A description of the key mapping.
      * @param {function} action The action invoked by each key sequence.
      * @param {Object} extra An optional extra configuration hash.
@@ -352,8 +421,8 @@ var Mappings = Module("mappings", {
     /**
      * Adds a new user-defined key mapping.
      *
-     * @param {number[]} modes The modes that this mapping applies to.
-     * @param {string[]} keys The key sequences which are bound to *action*.
+     * @param {[Modes.Mode]} modes The modes that this mapping applies to.
+     * @param {[string]} keys The key sequences which are bound to *action*.
      * @param {string} description A description of the key mapping.
      * @param {function} action The action invoked by each key sequence.
      * @param {Object} extra An optional extra configuration hash (see
@@ -369,35 +438,35 @@ var Mappings = Module("mappings", {
     /**
      * Returns the map from *mode* named *cmd*.
      *
-     * @param {number} mode The mode to search.
+     * @param {Modes.Mode} mode The mode to search.
      * @param {string} cmd The map name to match.
      * @returns {Map}
      */
     get: function get(mode, cmd) this.hives.map(function (h) h.get(mode, cmd)).compact()[0] || null,
 
     /**
-     * Returns an array of maps with names starting with but not equal to
+     * Returns a count of maps with names starting with but not equal to
      * *prefix*.
      *
-     * @param {number} mode The mode to search.
+     * @param {Modes.Mode} mode The mode to search.
      * @param {string} prefix The map prefix string to match.
-     * @returns {Map[]}
+     * @returns {[Map]}
      */
     getCandidates: function (mode, prefix)
         this.hives.map(function (h) h.getCandidates(mode, prefix))
-                  .flatten(),
+                  .reduce(function (a, b) a + b, 0),
 
     /**
      * Lists all user-defined mappings matching *filter* for the specified
-     * *modes*.
+     * *modes* in the specified *hives*.
      *
-     * @param {number[]} modes An array of modes to search.
-     * @param {string} filter The filter string to match.
+     * @param {[Modes.Mode]} modes An array of modes to search.
+     * @param {string} filter The filter string to match. @optional
+     * @param {[MapHive]} hives The map hives to list. @optional
      */
     list: function (modes, filter, hives) {
-        let modeSign = "";
-        modes.filter(function (m)  m.char).forEach(function (m) { modeSign += m.char; });
-        modes.filter(function (m) !m.char).forEach(function (m) { modeSign += " " + m.name; });
+        let modeSign = modes.map(function (m) m.char || "").join("")
+                     + modes.map(function (m) !m.char ? " " + m.name : "").join("");
         modeSign = modeSign.replace(/^ /, "");
 
         hives = (hives || mappings.userHives).map(function (h) [h, maps(h)])
@@ -410,34 +479,30 @@ var Mappings = Module("mappings", {
             return maps;
         }
 
-        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>
-                </tr>
-                <col style="min-width: 6em; padding-right: 1em;"/>
-                {
-                    template.map(hives, function ([hive, maps]) let (i = 0)
-                        <tr style="height: .5ex;"/> +
-                        template.map(maps, function (map)
-                            template.map(map.names, function (name)
-                            <tr>
-                                <td highlight="Title">{!i++ ? hive.name : ""}</td>
-                                <td>{modeSign}</td>
-                                <td>{name}</td>
-                                <td>{map.rhs || map.action.toSource()}</td>
-                            </tr>)) +
-                        <tr style="height: .5ex;"/>)
-                }
-                </table>;
-
-        // TODO: Move this to an ItemList to show this automatically
-        if (list.*.length() === list.text().length() + 2)
-            dactyl.echomsg(_("map.none"));
-        else
-            commandline.commandOutput(list);
+        let list = ["table", {},
+                ["tr", { highlight: "Title" },
+                    ["td", {}],
+                    ["td", { style: "padding-right: 1em;" }, _("title.Mode")],
+                    ["td", { style: "padding-right: 1em;" }, _("title.Command")],
+                    ["td", { style: "padding-right: 1em;" }, _("title.Action")]],
+                ["col", { style: "min-width: 6em; padding-right: 1em;" }],
+                hives.map(function ([hive, maps]) let (i = 0) [
+                    ["tr", { style: "height: .5ex;" }],
+                    maps.map(function (map)
+                        map.names.map(function (name)
+                        ["tr", {},
+                            ["td", { highlight: "Title" }, !i++ ? hive.name : ""],
+                            ["td", {}, modeSign],
+                            ["td", {}, name],
+                            ["td", {}, map.rhs || map.action.toSource()]])),
+                    ["tr", { style: "height: .5ex;" }]])];
+
+        // E4X-FIXME
+        // // TODO: Move this to an ItemList to show this automatically
+        // if (list.*.length() === list.text().length() + 2)
+        //     dactyl.echomsg(_("map.none"));
+        // else
+        commandline.commandOutput(list);
     }
 }, {
 }, {
@@ -459,12 +524,17 @@ var Mappings = Module("mappings", {
                     return;
                 }
 
+                if (args[1] && !/^<nop>$/i.test(args[1])
+                    && !args["-count"] && !args["-ex"] && !args["-javascript"]
+                    && mapmodes.every(function (m) m.count))
+                    args[1] = "<count>" + args[1];
+
                 let [lhs, rhs] = args;
                 if (noremap)
                     args["-builtin"] = true;
 
                 if (!rhs) // list the mapping
-                    mappings.list(mapmodes, mappings.expandLeader(lhs), hives);
+                    mappings.list(mapmodes, lhs, hives);
                 else {
                     util.assert(args["-group"].modifiable,
                                 _("map.builtinImmutable"));
@@ -474,7 +544,7 @@ var Mappings = Module("mappings", {
                         contexts.bindMacro(args, "-keys", function (params) params),
                         {
                             arg: args["-arg"],
-                            count: args["-count"],
+                            count: args["-count"] || !(args["-ex"] || args["-javascript"]),
                             noremap: args["-builtin"],
                             persist: !args["-nopersist"],
                             get rhs() String(this.action),
@@ -484,92 +554,92 @@ var Mappings = Module("mappings", {
             }
 
             const opts = {
-                    completer: function (context, args) {
-                        let mapmodes = array.uniq(args["-modes"].map(findMode));
-                        if (args.length == 1)
-                            return completion.userMapping(context, mapmodes, args["-group"]);
-                        if (args.length == 2) {
-                            if (args["-javascript"])
-                                return completion.javascript(context);
-                            if (args["-ex"])
-                                return completion.ex(context);
-                        }
+                identifier: "map",
+                completer: function (context, args) {
+                    let mapmodes = array.uniq(args["-modes"].map(findMode));
+                    if (args.length == 1)
+                        return completion.userMapping(context, mapmodes, args["-group"]);
+                    if (args.length == 2) {
+                        if (args["-javascript"])
+                            return completion.javascript(context);
+                        if (args["-ex"])
+                            return completion.ex(context);
+                    }
+                },
+                hereDoc: true,
+                literal: 1,
+                options: [
+                    {
+                        names: ["-arg", "-a"],
+                        description: "Accept an argument after the requisite key press"
                     },
-                    hereDoc: true,
-                    literal: 1,
-                    options: [
-                        {
-                            names: ["-arg", "-a"],
-                            description: "Accept an argument after the requisite key press",
-                        },
-                        {
-                            names: ["-builtin", "-b"],
-                            description: "Execute this mapping as if there were no user-defined mappings"
-                        },
-                        {
-                            names: ["-count", "-c"],
-                            description: "Accept a count before the requisite key press"
-                        },
-                        {
-                            names: ["-description", "-desc", "-d"],
-                            description: "A description of this mapping",
-                            default: "User-defined mapping",
-                            type: CommandOption.STRING
-                        },
-                        {
-                            names: ["-ex", "-e"],
-                            description: "Execute this mapping as an Ex command rather than keys"
-                        },
-                        contexts.GroupFlag("mappings"),
-                        {
-                            names: ["-javascript", "-js", "-j"],
-                            description: "Execute this mapping as JavaScript rather than keys"
-                        },
-                        update({}, modeFlag, {
-                            names: ["-modes", "-mode", "-m"],
-                            type: CommandOption.LIST,
-                            description: "Create this mapping in the given modes",
-                            default: mapmodes || ["n", "v"]
-                        }),
-                        {
-                            names: ["-nopersist", "-n"],
-                            description: "Do not save this mapping to an auto-generated RC file"
-                        },
-                        {
-                            names: ["-silent", "-s", "<silent>", "<Silent>"],
-                            description: "Do not echo any generated keys to the command line"
-                        }
-                    ],
-                    serialize: function () {
-                        return this.name != "map" ? [] :
-                            array(mappings.userHives)
-                                .filter(function (h) h.persist)
-                                .map(function (hive) [
-                                    {
-                                        command: "map",
-                                        options: array([
-                                            hive.name !== "user" && ["-group", hive.name],
-                                            ["-modes", uniqueModes(map.modes)],
-                                            ["-description", map.description],
-                                            map.silent && ["-silent"]])
-
-                                            .filter(util.identity)
-                                            .toObject(),
-                                        arguments: [map.names[0]],
-                                        literalArg: map.rhs,
-                                        ignoreDefaults: true
-                                    }
-                                    for (map in userMappings(hive))
-                                    if (map.persist)
-                                ])
-                                .flatten().array;
+                    {
+                        names: ["-builtin", "-b"],
+                        description: "Execute this mapping as if there were no user-defined mappings"
+                    },
+                    {
+                        names: ["-count", "-c"],
+                        description: "Accept a count before the requisite key press"
+                    },
+                    {
+                        names: ["-description", "-desc", "-d"],
+                        description: "A description of this mapping",
+                        default: /*L*/"User-defined mapping",
+                        type: CommandOption.STRING
+                    },
+                    {
+                        names: ["-ex", "-e"],
+                        description: "Execute this mapping as an Ex command rather than keys"
+                    },
+                    contexts.GroupFlag("mappings"),
+                    {
+                        names: ["-javascript", "-js", "-j"],
+                        description: "Execute this mapping as JavaScript rather than keys"
+                    },
+                    update({}, modeFlag, {
+                        names: ["-modes", "-mode", "-m"],
+                        type: CommandOption.LIST,
+                        description: "Create this mapping in the given modes",
+                        default: mapmodes || ["n", "v"]
+                    }),
+                    {
+                        names: ["-nopersist", "-n"],
+                        description: "Do not save this mapping to an auto-generated RC file"
+                    },
+                    {
+                        names: ["-silent", "-s", "<silent>", "<Silent>"],
+                        description: "Do not echo any generated keys to the command line"
                     }
+                ],
+                serialize: function () {
+                    return this.name != "map" ? [] :
+                        array(mappings.userHives)
+                            .filter(function (h) h.persist)
+                            .map(function (hive) [
+                                {
+                                    command: "map",
+                                    options: {
+                                        "-count": map.count ? null : undefined,
+                                        "-description": map.description,
+                                        "-group": hive.name == "user" ? undefined : hive.name,
+                                        "-modes": uniqueModes(map.modes),
+                                        "-silent": map.silent ? null : undefined
+                                    },
+                                    arguments: [map.names[0]],
+                                    literalArg: map.rhs,
+                                    ignoreDefaults: true
+                                }
+                                for (map in userMappings(hive))
+                                if (map.persist)
+                            ])
+                            .flatten().array;
+                }
             };
             function userMappings(hive) {
                 let seen = {};
                 for (let stack in values(hive.stacks))
                     for (let map in array.iterValues(stack))
-                        if (!set.add(seen, map.id))
+                        if (!Set.add(seen, map.id))
                             yield map;
             }
 
@@ -582,7 +652,7 @@ var Mappings = Module("mappings", {
             commands.add([ch + "no[remap]"],
                 "Map a key sequence without remapping keys" + modeDescription,
                 function (args) { map(args, true); },
-                update({}, opts));
+                update({ deprecated: ":" + ch + "map -builtin" }, opts));
 
             commands.add([ch + "unm[ap]"],
                 "Remove a mapping" + modeDescription,
@@ -606,6 +676,7 @@ var Mappings = Module("mappings", {
                         dactyl.echoerr(_("map.noSuch", args[0]));
                 },
                 {
+                    identifier: "unmap",
                     argCount: "?",
                     bang: true,
                     completer: opts.completer,
@@ -627,7 +698,7 @@ var Mappings = Module("mappings", {
             validator: function (value) Array.concat(value).every(findMode),
             completer: function () [[array.compact([mode.name.toLowerCase().replace(/_/g, "-"), mode.char]), mode.description]
                                     for (mode in values(modes.all))
-                                    if (!mode.hidden)],
+                                    if (!mode.hidden)]
         };
 
         function findMode(name) {
@@ -641,7 +712,9 @@ var Mappings = Module("mappings", {
         function uniqueModes(modes) {
             let chars = [k for ([k, v] in Iterator(modules.modes.modeChars))
                          if (v.every(function (mode) modes.indexOf(mode) >= 0))];
-            return array.uniq(modes.filter(function (m) chars.indexOf(m.char) < 0).concat(chars));
+            return array.uniq(modes.filter(function (m) chars.indexOf(m.char) < 0)
+                                   .map(function (m) m.name.toLowerCase())
+                                   .concat(chars));
         }
 
         commands.add(["feedkeys", "fk"],
@@ -664,14 +737,14 @@ var Mappings = Module("mappings", {
             if (mode.char && !commands.get(mode.char + "map", true))
                 addMapCommands(mode.char,
                                [m.mask for (m in modes.mainModes) if (m.char == mode.char)],
-                               [mode.name.toLowerCase()]);
+                               mode.displayName);
 
         let args = {
             getMode: function (args) findMode(args["-mode"]),
             iterate: function (args, mainOnly) {
                 let modes = [this.getMode(args)];
                 if (!mainOnly)
-                    modes = modes.concat(modes[0].bases);
+                    modes = modes[0].allBases;
 
                 let seen = {};
                 // Bloody hell. --Kris
@@ -679,29 +752,32 @@ var Mappings = Module("mappings", {
                     for (let hive in mappings.hives.iterValues())
                         for (let map in array.iterValues(hive.getStack(mode)))
                             for (let name in values(map.names))
-                                if (!set.add(seen, name)) {
+                                if (!Set.add(seen, name)) {
                                     yield {
                                         name: name,
                                         columns: [
-                                            i === 0 ? "" : <span highlight="Object" style="padding-right: 1em;">{mode.name}</span>,
-                                            hive == mappings.builtin ? "" : <span highlight="Object" style="padding-right: 1em;">{hive.name}</span>
+                                            i === 0 ? "" : ["span", { highlight: "Object", style: "padding-right: 1em;" },
+                                                                mode.name],
+                                            hive == mappings.builtin ? "" : ["span", { highlight: "Object", style: "padding-right: 1em;" },
+                                                                                 hive.name]
                                         ],
                                         __proto__: map
                                     };
                                 }
             },
             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>
-                            : <></>}
-                        {template.linkifyHelp(map.description + (map.rhs ? ": " + map.rhs : ""))}
-                </>),
+                description: function (map) [
+                        options.get("passkeys").has(map.name)
+                            ? ["span", { highlight: "URLExtra" },
+                                "(", template.linkifyHelp(_("option.passkeys.passedBy")), ")"]
+                            : [],
+                        template.linkifyHelp(map.description + (map.rhs ? ": " + map.rhs : ""))
+                ],
                 help: function (map) let (char = array.compact(map.modes.map(function (m) m.char))[0])
                     char === "n" ? map.name : char ? char + "_" + map.name : "",
                 headings: ["Command", "Mode", "Group", "Description"]
             }
-        }
+        };
 
         dactyl.addUsageCommand({
             __proto__: args,
@@ -722,11 +798,11 @@ var Mappings = Module("mappings", {
                     name: [mode.char + "listk[eys]", mode.char + "lk"],
                     iterateIndex: function (args)
                             let (self = this, prefix = /^[bCmn]$/.test(mode.char) ? "" : mode.char + "_",
-                                 tags = services["dactyl:"].HELP_TAGS)
+                                 haveTag = Set.has(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 || haveTag(prefix + map.name))),
+                    description: "List all " + mode.displayName + " mode mappings along with their short descriptions",
                     index: mode.char + "-map",
                     getMode: function (args) mode,
                     options: []
@@ -742,25 +818,17 @@ var Mappings = Module("mappings", {
         };
     },
     javascript: function initJavascript(dactyl, modules, window) {
-        JavaScript.setCompleter([mappings.get, mappings.builtin.get],
+        JavaScript.setCompleter([Mappings.prototype.get, MapHive.prototype.get],
             [
                 null,
                 function (context, obj, args) [[m.names, m.description] for (m in this.iterate(args[0]))]
             ]);
     },
-    options: function initOptions(dactyl, modules, window) {
-        options.add(["mapleader", "ml"],
-            "Define the replacement keys for the <Leader> pseudo-key",
-            "string", "\\", {
-                setter: function (value) {
-                    if (this.hasChanged)
-                        for (let hive in values(mappings.allHives))
-                            for (let stack in values(hive.stacks))
-                                delete stack.states;
-                    return value;
-                }
-            });
+    mappings: function initMappings(dactyl, modules, window) {
+        mappings.add([modes.COMMAND],
+             ["\\"], "Emits <Leader> pseudo-key",
+             function () { events.feedkeys("<Leader>"); });
     }
 });
 
-// vim: set fdm=marker sw=4 ts=4 et:
+// vim: set fdm=marker sw=4 sts=4 ts=8 et: