]> git.donarmstrong.com Git - dactyl.git/blob - common/content/mappings.js
Import r6948 from upstream hg supporting Firefox up to 24.*
[dactyl.git] / common / content / mappings.js
1 // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
2 // Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
3 // Copyright (c) 2008-2012 Kris Maglione <maglione.k at Gmail>
4 //
5 // This work is licensed for reuse under an MIT license. Details are
6 // given in the LICENSE.txt file included with this file.
7 "use strict";
8
9 /** @scope modules */
10
11 /**
12  * A class representing key mappings. Instances are created by the
13  * {@link Mappings} class.
14  *
15  * @param {[Modes.Mode]} modes The modes in which this mapping is active.
16  * @param {[string]} keys The key sequences which are bound to
17  *     *action*.
18  * @param {string} description A short one line description of the key mapping.
19  * @param {function} action The action invoked by each key sequence.
20  * @param {Object} info An optional extra configuration hash. The
21  *     following properties are supported.
22  *         arg     - see {@link Map#arg}
23  *         count   - see {@link Map#count}
24  *         motion  - see {@link Map#motion}
25  *         noremap - see {@link Map#noremap}
26  *         rhs     - see {@link Map#rhs}
27  *         silent  - see {@link Map#silent}
28  * @optional
29  * @private
30  */
31 var Map = Class("Map", {
32     init: function (modes, keys, description, action, info) {
33         this.id = ++Map.id;
34         this.modes = modes;
35         this._keys = keys;
36         this.action = action;
37         this.description = description;
38
39         Object.freeze(this.modes);
40
41         if (info) {
42             if (Set.has(Map.types, info.type))
43                 this.update(Map.types[info.type]);
44             this.update(info);
45         }
46     },
47
48     name: Class.Memoize(function () this.names[0]),
49
50     /** @property {[string]} All of this mapping's names (key sequences). */
51     names: Class.Memoize(function () this._keys.map(function (k) DOM.Event.canonicalKeys(k))),
52
53     get toStringParams() [this.modes.map(function (m) m.name), this.names.map(String.quote)],
54
55     get identifier() [this.modes[0].name, this.hive.prefix + this.names[0]].join("."),
56
57     /** @property {number} A unique ID for this mapping. */
58     id: null,
59     /** @property {[Modes.Mode]} All of the modes for which this mapping applies. */
60     modes: null,
61     /** @property {function (number)} The function called to execute this mapping. */
62     action: null,
63     /** @property {string} This mapping's description, as shown in :listkeys. */
64     description: Messages.Localized(""),
65
66     /** @property {boolean} Whether this mapping accepts an argument. */
67     arg: false,
68     /** @property {boolean} Whether this mapping accepts a count. */
69     count: false,
70     /**
71      * @property {boolean} Whether the mapping accepts a motion mapping
72      *     as an argument.
73      */
74     motion: false,
75
76     /** @property {boolean} Whether the RHS of the mapping should expand mappings recursively. */
77     noremap: false,
78
79     /** @property {function(object)} A function to be executed before this mapping. */
80     preExecute: function preExecute(args) {},
81     /** @property {function(object)} A function to be executed after this mapping. */
82     postExecute: function postExecute(args) {},
83
84     /** @property {boolean} Whether any output from the mapping should be echoed on the command line. */
85     silent: false,
86
87     /** @property {string} The literal RHS expansion of this mapping. */
88     rhs: null,
89
90     /** @property {string} The type of this mapping. */
91     type: "",
92
93     /**
94      * @property {boolean} Specifies whether this is a user mapping. User
95      *     mappings may be created by plugins, or directly by users. Users and
96      *     plugin authors should create only user mappings.
97      */
98     user: false,
99
100     /**
101      * Returns whether this mapping can be invoked by a key sequence matching
102      * *name*.
103      *
104      * @param {string} name The name to query.
105      * @returns {boolean}
106      */
107     hasName: function (name) this.keys.indexOf(name) >= 0,
108
109     get keys() array.flatten(this.names.map(mappings.closure.expand)),
110
111     /**
112      * Execute the action for this mapping.
113      *
114      * @param {object} args The arguments object for the given mapping.
115      */
116     execute: function (args) {
117         if (!isObject(args)) // Backwards compatibility :(
118             args = iter(["motion", "count", "arg", "command"])
119                 .map(function ([i, prop]) [prop, this[i]], arguments)
120                 .toObject();
121
122         args = this.hive.makeArgs(this.hive.group.lastDocument,
123                                   contexts.context,
124                                   args);
125
126         let repeat = () => this.action(args);
127         if (this.names[0] != ".") // FIXME: Kludge.
128             mappings.repeat = repeat;
129
130         if (this.executing)
131             util.assert(!args.keypressEvents[0].isMacro,
132                         _("map.recursive", args.command),
133                         false);
134
135         try {
136             dactyl.triggerObserver("mappings.willExecute", this, args);
137             mappings.pushCommand();
138             this.preExecute(args);
139             this.executing = true;
140             var res = repeat();
141         }
142         catch (e) {
143             events.feedingKeys = false;
144             dactyl.reportError(e, true);
145         }
146         finally {
147             this.executing = false;
148             mappings.popCommand();
149             this.postExecute(args);
150             dactyl.triggerObserver("mappings.executed", this, args);
151         }
152         return res;
153     }
154
155 }, {
156     id: 0,
157
158     types: {}
159 });
160
161 var MapHive = Class("MapHive", Contexts.Hive, {
162     init: function init(group) {
163         init.supercall(this, group);
164         this.stacks = {};
165     },
166
167     /**
168      * Iterates over all mappings present in all of the given *modes*.
169      *
170      * @param {[Modes.Mode]} modes The modes for which to return mappings.
171      */
172     iterate: function (modes) {
173         let stacks = Array.concat(modes).map(this.closure.getStack);
174         return values(stacks.shift().sort(function (m1, m2) String.localeCompare(m1.name, m2.name))
175             .filter(function (map) map.rhs &&
176                 stacks.every(function (stack) stack.some(function (m) m.rhs && m.rhs === map.rhs && m.name === map.name))));
177     },
178
179     /**
180      * Adds a new key mapping.
181      *
182      * @param {[Modes.Mode]} modes The modes that this mapping applies to.
183      * @param {[string]} keys The key sequences which are bound to *action*.
184      * @param {string} description A description of the key mapping.
185      * @param {function} action The action invoked by each key sequence.
186      * @param {Object} extra An optional extra configuration hash.
187      * @optional
188      */
189     add: function (modes, keys, description, action, extra) {
190         extra = extra || {};
191
192         modes = Array.concat(modes);
193         if (!modes.every(util.identity))
194             throw TypeError(/*L*/"Invalid modes: " + modes);
195
196         let map = Map(modes, keys, description, action, extra);
197         map.definedAt = contexts.getCaller(Components.stack.caller);
198         map.hive = this;
199
200         if (this.name !== "builtin")
201             for (let [, name] in Iterator(map.names))
202                 for (let [, mode] in Iterator(map.modes))
203                     this.remove(mode, name);
204
205         for (let mode in values(map.modes))
206             this.getStack(mode).add(map);
207         return map;
208     },
209
210     /**
211      * Returns the mapping stack for the given mode.
212      *
213      * @param {Modes.Mode} mode The mode to search.
214      * @returns {[Map]}
215      */
216     getStack: function getStack(mode) {
217         if (!(mode in this.stacks))
218             return this.stacks[mode] = MapHive.Stack();
219         return this.stacks[mode];
220     },
221
222     /**
223      * Returns the map from *mode* named *cmd*.
224      *
225      * @param {Modes.Mode} mode The mode to search.
226      * @param {string} cmd The map name to match.
227      * @returns {Map|null}
228      */
229     get: function (mode, cmd) this.getStack(mode).mappings[cmd],
230
231     /**
232      * Returns a count of maps with names starting with but not equal to
233      * *prefix*.
234      *
235      * @param {Modes.Mode} mode The mode to search.
236      * @param {string} prefix The map prefix string to match.
237      * @returns {number)
238      */
239     getCandidates: function (mode, prefix) this.getStack(mode).candidates[prefix] || 0,
240
241     /**
242      * Returns whether there is a user-defined mapping *cmd* for the specified
243      * *mode*.
244      *
245      * @param {Modes.Mode} mode The mode to search.
246      * @param {string} cmd The candidate key mapping.
247      * @returns {boolean}
248      */
249     has: function (mode, cmd) this.getStack(mode).mappings[cmd] != null,
250
251     /**
252      * Remove the mapping named *cmd* for *mode*.
253      *
254      * @param {Modes.Mode} mode The mode to search.
255      * @param {string} cmd The map name to match.
256      */
257     remove: function (mode, cmd) {
258         let stack = this.getStack(mode);
259         for (let [i, map] in array.iterItems(stack)) {
260             let j = map.names.indexOf(cmd);
261             if (j >= 0) {
262                 delete stack.states;
263                 map.names.splice(j, 1);
264                 if (map.names.length == 0) // FIX ME.
265                     for (let [mode, stack] in Iterator(this.stacks))
266                         this.stacks[mode] = MapHive.Stack(stack.filter(function (m) m != map));
267                 return;
268             }
269         }
270     },
271
272     /**
273      * Remove all user-defined mappings for *mode*.
274      *
275      * @param {Modes.Mode} mode The mode to remove all mappings from.
276      */
277     clear: function (mode) {
278         this.stacks[mode] = MapHive.Stack([]);
279     }
280 }, {
281     Stack: Class("Stack", Array, {
282         init: function (ary) {
283             let self = ary || [];
284             self.__proto__ = this.__proto__;
285             return self;
286         },
287
288         __iterator__: function () array.iterValues(this),
289
290         get candidates() this.states.candidates,
291         get mappings() this.states.mappings,
292
293         add: function (map) {
294             this.push(map);
295             delete this.states;
296         },
297
298         states: Class.Memoize(function () {
299             var states = {
300                 candidates: {},
301                 mappings: {}
302             };
303
304             for (let map in this)
305                 for (let name in values(map.keys)) {
306                     states.mappings[name] = map;
307                     let state = "";
308                     for (let key in DOM.Event.iterKeys(name)) {
309                         state += key;
310                         if (state !== name)
311                             states.candidates[state] = (states.candidates[state] || 0) + 1;
312                     }
313                 }
314             return states;
315         })
316     })
317 });
318
319 /**
320  * @instance mappings
321  */
322 var Mappings = Module("mappings", {
323     init: function () {
324         this.watches = [];
325         this._watchStack = 0;
326         this._yielders = 0;
327     },
328
329     afterCommands: function afterCommands(count, cmd, self) {
330         this.watches.push([cmd, self, Math.max(this._watchStack - 1, 0), count || 1]);
331     },
332
333     pushCommand: function pushCommand(cmd) {
334         this._watchStack++;
335         this._yielders = util.yielders;
336     },
337     popCommand: function popCommand(cmd) {
338         this._watchStack = Math.max(this._watchStack - 1, 0);
339         if (util.yielders > this._yielders)
340             this._watchStack = 0;
341
342         this.watches = this.watches.filter(function (elem) {
343             if (this._watchStack <= elem[2])
344                 elem[3]--;
345             if (elem[3] <= 0)
346                 elem[0].call(elem[1] || null);
347             return elem[3] > 0;
348         }, this);
349     },
350
351     repeat: Modes.boundProperty(),
352
353     get allHives() contexts.allGroups.mappings,
354
355     get userHives() this.allHives.filter(function (h) h !== this.builtin, this),
356
357     expandLeader: deprecated("your brain", function expandLeader(keyString) keyString),
358
359     prefixes: Class.Memoize(function () {
360         let list = Array.map("CASM", function (s) s + "-");
361
362         return iter(util.range(0, 1 << list.length)).map(function (mask)
363             list.filter(function (p, i) mask & (1 << i)).join("")).toArray().concat("*-");
364     }),
365
366     expand: function expand(keys) {
367         if (!/<\*-/.test(keys))
368             var res = keys;
369         else
370             res = util.debrace(DOM.Event.iterKeys(keys).map(function (key) {
371                 if (/^<\*-/.test(key))
372                     return ["<", this.prefixes, key.slice(3)];
373                 return key;
374             }, this).flatten().array).map(function (k) DOM.Event.canonicalKeys(k));
375
376         if (keys != arguments[0])
377             return [arguments[0]].concat(keys);
378         return keys;
379     },
380
381     iterate: function (mode) {
382         let seen = {};
383         for (let hive in this.hives.iterValues())
384             for (let map in array(hive.getStack(mode)).iterValues())
385                 if (!Set.add(seen, map.name))
386                     yield map;
387     },
388
389     // NOTE: just normal mode for now
390     /** @property {Iterator(Map)} */
391     __iterator__: function () this.iterate(modes.NORMAL),
392
393     getDefault: deprecated("mappings.builtin.get", function getDefault(mode, cmd) this.builtin.get(mode, cmd)),
394     getUserIterator: deprecated("mappings.user.iterator", function getUserIterator(modes) this.user.iterator(modes)),
395     hasMap: deprecated("group.mappings.has", function hasMap(mode, cmd) this.user.has(mode, cmd)),
396     remove: deprecated("group.mappings.remove", function remove(mode, cmd) this.user.remove(mode, cmd)),
397     removeAll: deprecated("group.mappings.clear", function removeAll(mode) this.user.clear(mode)),
398
399     /**
400      * Adds a new default key mapping.
401      *
402      * @param {[Modes.Mode]} modes The modes that this mapping applies to.
403      * @param {[string]} keys The key sequences which are bound to *action*.
404      * @param {string} description A description of the key mapping.
405      * @param {function} action The action invoked by each key sequence.
406      * @param {Object} extra An optional extra configuration hash.
407      * @optional
408      */
409     add: function add() {
410         let group = this.builtin;
411         if (!util.isDactyl(Components.stack.caller)) {
412             deprecated.warn(add, "mappings.add", "group.mappings.add");
413             group = this.user;
414         }
415
416         let map = group.add.apply(group, arguments);
417         map.definedAt = contexts.getCaller(Components.stack.caller);
418         return map;
419     },
420
421     /**
422      * Adds a new user-defined key mapping.
423      *
424      * @param {[Modes.Mode]} modes The modes that this mapping applies to.
425      * @param {[string]} keys The key sequences which are bound to *action*.
426      * @param {string} description A description of the key mapping.
427      * @param {function} action The action invoked by each key sequence.
428      * @param {Object} extra An optional extra configuration hash (see
429      *     {@link Map#extraInfo}).
430      * @optional
431      */
432     addUserMap: deprecated("group.mappings.add", function addUserMap() {
433         let map = this.user.add.apply(this.user, arguments);
434         map.definedAt = contexts.getCaller(Components.stack.caller);
435         return map;
436     }),
437
438     /**
439      * Returns the map from *mode* named *cmd*.
440      *
441      * @param {Modes.Mode} mode The mode to search.
442      * @param {string} cmd The map name to match.
443      * @returns {Map}
444      */
445     get: function get(mode, cmd) this.hives.map(function (h) h.get(mode, cmd)).compact()[0] || null,
446
447     /**
448      * Returns a count of maps with names starting with but not equal to
449      * *prefix*.
450      *
451      * @param {Modes.Mode} mode The mode to search.
452      * @param {string} prefix The map prefix string to match.
453      * @returns {[Map]}
454      */
455     getCandidates: function (mode, prefix)
456         this.hives.map(function (h) h.getCandidates(mode, prefix))
457                   .reduce(function (a, b) a + b, 0),
458
459     /**
460      * Lists all user-defined mappings matching *filter* for the specified
461      * *modes* in the specified *hives*.
462      *
463      * @param {[Modes.Mode]} modes An array of modes to search.
464      * @param {string} filter The filter string to match. @optional
465      * @param {[MapHive]} hives The map hives to list. @optional
466      */
467     list: function (modes, filter, hives) {
468         let modeSign = modes.map(function (m) m.char || "").join("")
469                      + modes.map(function (m) !m.char ? " " + m.name : "").join("");
470         modeSign = modeSign.replace(/^ /, "");
471
472         hives = (hives || mappings.userHives).map(function (h) [h, maps(h)])
473                                              .filter(function ([h, m]) m.length);
474
475         function maps(hive) {
476             let maps = iter.toArray(hive.iterate(modes));
477             if (filter)
478                 maps = maps.filter(function (m) m.names[0] === filter);
479             return maps;
480         }
481
482         let list = ["table", {},
483                 ["tr", { highlight: "Title" },
484                     ["td", {}],
485                     ["td", { style: "padding-right: 1em;" }, _("title.Mode")],
486                     ["td", { style: "padding-right: 1em;" }, _("title.Command")],
487                     ["td", { style: "padding-right: 1em;" }, _("title.Action")]],
488                 ["col", { style: "min-width: 6em; padding-right: 1em;" }],
489                 hives.map(function ([hive, maps]) let (i = 0) [
490                     ["tr", { style: "height: .5ex;" }],
491                     maps.map(function (map)
492                         map.names.map(function (name)
493                         ["tr", {},
494                             ["td", { highlight: "Title" }, !i++ ? hive.name : ""],
495                             ["td", {}, modeSign],
496                             ["td", {}, name],
497                             ["td", {}, map.rhs || map.action.toSource()]])),
498                     ["tr", { style: "height: .5ex;" }]])];
499
500         // E4X-FIXME
501         // // TODO: Move this to an ItemList to show this automatically
502         // if (list.*.length() === list.text().length() + 2)
503         //     dactyl.echomsg(_("map.none"));
504         // else
505         commandline.commandOutput(list);
506     }
507 }, {
508 }, {
509     contexts: function initContexts(dactyl, modules, window) {
510         update(Mappings.prototype, {
511             hives: contexts.Hives("mappings", MapHive),
512             user: contexts.hives.mappings.user,
513             builtin: contexts.hives.mappings.builtin
514         });
515     },
516     commands: function initCommands(dactyl, modules, window) {
517         function addMapCommands(ch, mapmodes, modeDescription) {
518             function map(args, noremap) {
519                 let mapmodes = array.uniq(args["-modes"].map(findMode));
520                 let hives = args.explicitOpts["-group"] ? [args["-group"]] : null;
521
522                 if (!args.length) {
523                     mappings.list(mapmodes, null, hives);
524                     return;
525                 }
526
527                 if (args[1] && !/^<nop>$/i.test(args[1])
528                     && !args["-count"] && !args["-ex"] && !args["-javascript"]
529                     && mapmodes.every(function (m) m.count))
530                     args[1] = "<count>" + args[1];
531
532                 let [lhs, rhs] = args;
533                 if (noremap)
534                     args["-builtin"] = true;
535
536                 if (!rhs) // list the mapping
537                     mappings.list(mapmodes, lhs, hives);
538                 else {
539                     util.assert(args["-group"].modifiable,
540                                 _("map.builtinImmutable"));
541
542                     args["-group"].add(mapmodes, [lhs],
543                         args["-description"],
544                         contexts.bindMacro(args, "-keys", function (params) params),
545                         {
546                             arg: args["-arg"],
547                             count: args["-count"] || !(args["-ex"] || args["-javascript"]),
548                             noremap: args["-builtin"],
549                             persist: !args["-nopersist"],
550                             get rhs() String(this.action),
551                             silent: args["-silent"]
552                         });
553                 }
554             }
555
556             const opts = {
557                 identifier: "map",
558                 completer: function (context, args) {
559                     let mapmodes = array.uniq(args["-modes"].map(findMode));
560                     if (args.length == 1)
561                         return completion.userMapping(context, mapmodes, args["-group"]);
562                     if (args.length == 2) {
563                         if (args["-javascript"])
564                             return completion.javascript(context);
565                         if (args["-ex"])
566                             return completion.ex(context);
567                     }
568                 },
569                 hereDoc: true,
570                 literal: 1,
571                 options: [
572                     {
573                         names: ["-arg", "-a"],
574                         description: "Accept an argument after the requisite key press"
575                     },
576                     {
577                         names: ["-builtin", "-b"],
578                         description: "Execute this mapping as if there were no user-defined mappings"
579                     },
580                     {
581                         names: ["-count", "-c"],
582                         description: "Accept a count before the requisite key press"
583                     },
584                     {
585                         names: ["-description", "-desc", "-d"],
586                         description: "A description of this mapping",
587                         default: /*L*/"User-defined mapping",
588                         type: CommandOption.STRING
589                     },
590                     {
591                         names: ["-ex", "-e"],
592                         description: "Execute this mapping as an Ex command rather than keys"
593                     },
594                     contexts.GroupFlag("mappings"),
595                     {
596                         names: ["-javascript", "-js", "-j"],
597                         description: "Execute this mapping as JavaScript rather than keys"
598                     },
599                     update({}, modeFlag, {
600                         names: ["-modes", "-mode", "-m"],
601                         type: CommandOption.LIST,
602                         description: "Create this mapping in the given modes",
603                         default: mapmodes || ["n", "v"]
604                     }),
605                     {
606                         names: ["-nopersist", "-n"],
607                         description: "Do not save this mapping to an auto-generated RC file"
608                     },
609                     {
610                         names: ["-silent", "-s", "<silent>", "<Silent>"],
611                         description: "Do not echo any generated keys to the command line"
612                     }
613                 ],
614                 serialize: function () {
615                     return this.name != "map" ? [] :
616                         array(mappings.userHives)
617                             .filter(function (h) h.persist)
618                             .map(function (hive) [
619                                 {
620                                     command: "map",
621                                     options: {
622                                         "-count": map.count ? null : undefined,
623                                         "-description": map.description,
624                                         "-group": hive.name == "user" ? undefined : hive.name,
625                                         "-modes": uniqueModes(map.modes),
626                                         "-silent": map.silent ? null : undefined
627                                     },
628                                     arguments: [map.names[0]],
629                                     literalArg: map.rhs,
630                                     ignoreDefaults: true
631                                 }
632                                 for (map in userMappings(hive))
633                                 if (map.persist)
634                             ])
635                             .flatten().array;
636                 }
637             };
638             function userMappings(hive) {
639                 let seen = {};
640                 for (let stack in values(hive.stacks))
641                     for (let map in array.iterValues(stack))
642                         if (!Set.add(seen, map.id))
643                             yield map;
644             }
645
646             modeDescription = modeDescription ? " in " + modeDescription + " mode" : "";
647             commands.add([ch ? ch + "m[ap]" : "map"],
648                 "Map a key sequence" + modeDescription,
649                 function (args) { map(args, false); },
650                 update({}, opts));
651
652             commands.add([ch + "no[remap]"],
653                 "Map a key sequence without remapping keys" + modeDescription,
654                 function (args) { map(args, true); },
655                 update({ deprecated: ":" + ch + "map -builtin" }, opts));
656
657             commands.add([ch + "unm[ap]"],
658                 "Remove a mapping" + modeDescription,
659                 function (args) {
660                     util.assert(args["-group"].modifiable, _("map.builtinImmutable"));
661
662                     util.assert(args.bang ^ !!args[0], _("error.argumentOrBang"));
663
664                     let mapmodes = array.uniq(args["-modes"].map(findMode));
665
666                     let found = 0;
667                     for (let mode in values(mapmodes))
668                         if (args.bang)
669                             args["-group"].clear(mode);
670                         else if (args["-group"].has(mode, args[0])) {
671                             args["-group"].remove(mode, args[0]);
672                             found++;
673                         }
674
675                     if (!found && !args.bang)
676                         dactyl.echoerr(_("map.noSuch", args[0]));
677                 },
678                 {
679                     identifier: "unmap",
680                     argCount: "?",
681                     bang: true,
682                     completer: opts.completer,
683                     options: [
684                         contexts.GroupFlag("mappings"),
685                         update({}, modeFlag, {
686                             names: ["-modes", "-mode", "-m"],
687                             type: CommandOption.LIST,
688                             description: "Remove mapping from the given modes",
689                             default: mapmodes || ["n", "v"]
690                         })
691                     ]
692                 });
693         }
694
695         let modeFlag = {
696             names: ["-mode", "-m"],
697             type: CommandOption.STRING,
698             validator: function (value) Array.concat(value).every(findMode),
699             completer: function () [[array.compact([mode.name.toLowerCase().replace(/_/g, "-"), mode.char]), mode.description]
700                                     for (mode in values(modes.all))
701                                     if (!mode.hidden)]
702         };
703
704         function findMode(name) {
705             if (name)
706                 for (let mode in values(modes.all))
707                     if (name == mode || name == mode.char
708                         || String.toLowerCase(name).replace(/-/g, "_") == mode.name.toLowerCase())
709                         return mode;
710             return null;
711         }
712         function uniqueModes(modes) {
713             let chars = [k for ([k, v] in Iterator(modules.modes.modeChars))
714                          if (v.every(function (mode) modes.indexOf(mode) >= 0))];
715             return array.uniq(modes.filter(function (m) chars.indexOf(m.char) < 0)
716                                    .map(function (m) m.name.toLowerCase())
717                                    .concat(chars));
718         }
719
720         commands.add(["feedkeys", "fk"],
721             "Fake key events",
722             function (args) { events.feedkeys(args[0] || "", args.bang, false, findMode(args["-mode"])); },
723             {
724                 argCount: "1",
725                 bang: true,
726                 literal: 0,
727                 options: [
728                     update({}, modeFlag, {
729                         description: "The mode in which to feed the keys"
730                     })
731                 ]
732             });
733
734         addMapCommands("", [modes.NORMAL, modes.VISUAL], "");
735
736         for (let mode in modes.mainModes)
737             if (mode.char && !commands.get(mode.char + "map", true))
738                 addMapCommands(mode.char,
739                                [m.mask for (m in modes.mainModes) if (m.char == mode.char)],
740                                mode.displayName);
741
742         let args = {
743             getMode: function (args) findMode(args["-mode"]),
744             iterate: function (args, mainOnly) {
745                 let modes = [this.getMode(args)];
746                 if (!mainOnly)
747                     modes = modes[0].allBases;
748
749                 let seen = {};
750                 // Bloody hell. --Kris
751                 for (let [i, mode] in Iterator(modes))
752                     for (let hive in mappings.hives.iterValues())
753                         for (let map in array.iterValues(hive.getStack(mode)))
754                             for (let name in values(map.names))
755                                 if (!Set.add(seen, name)) {
756                                     yield {
757                                         name: name,
758                                         columns: [
759                                             i === 0 ? "" : ["span", { highlight: "Object", style: "padding-right: 1em;" },
760                                                                 mode.name],
761                                             hive == mappings.builtin ? "" : ["span", { highlight: "Object", style: "padding-right: 1em;" },
762                                                                                  hive.name]
763                                         ],
764                                         __proto__: map
765                                     };
766                                 }
767             },
768             format: {
769                 description: function (map) [
770                         options.get("passkeys").has(map.name)
771                             ? ["span", { highlight: "URLExtra" },
772                                 "(", template.linkifyHelp(_("option.passkeys.passedBy")), ")"]
773                             : [],
774                         template.linkifyHelp(map.description + (map.rhs ? ": " + map.rhs : ""))
775                 ],
776                 help: function (map) let (char = array.compact(map.modes.map(function (m) m.char))[0])
777                     char === "n" ? map.name : char ? char + "_" + map.name : "",
778                 headings: ["Command", "Mode", "Group", "Description"]
779             }
780         };
781
782         dactyl.addUsageCommand({
783             __proto__: args,
784             name: ["listk[eys]", "lk"],
785             description: "List all mappings along with their short descriptions",
786             options: [
787                 update({}, modeFlag, {
788                     default: "n",
789                     description: "The mode for which to list mappings"
790                 })
791             ]
792         });
793
794         iter.forEach(modes.mainModes, function (mode) {
795             if (mode.char && !commands.get(mode.char + "listkeys", true))
796                 dactyl.addUsageCommand({
797                     __proto__: args,
798                     name: [mode.char + "listk[eys]", mode.char + "lk"],
799                     iterateIndex: function (args)
800                             let (self = this, prefix = /^[bCmn]$/.test(mode.char) ? "" : mode.char + "_",
801                                  haveTag = Set.has(help.tags))
802                                     ({ helpTag: prefix + map.name, __proto__: map }
803                                      for (map in self.iterate(args, true))
804                                      if (map.hive === mappings.builtin || haveTag(prefix + map.name))),
805                     description: "List all " + mode.displayName + " mode mappings along with their short descriptions",
806                     index: mode.char + "-map",
807                     getMode: function (args) mode,
808                     options: []
809                 });
810         });
811     },
812     completion: function initCompletion(dactyl, modules, window) {
813         completion.userMapping = function userMapping(context, modes_, hive) {
814             hive = hive || mappings.user;
815             modes_ = modes_ || [modes.NORMAL];
816             context.keys = { text: function (m) m.names[0], description: function (m) m.description + ": " + m.action };
817             context.completions = hive.iterate(modes_);
818         };
819     },
820     javascript: function initJavascript(dactyl, modules, window) {
821         JavaScript.setCompleter([Mappings.prototype.get, MapHive.prototype.get],
822             [
823                 null,
824                 function (context, obj, args) [[m.names, m.description] for (m in this.iterate(args[0]))]
825             ]);
826     },
827     mappings: function initMappings(dactyl, modules, window) {
828         mappings.add([modes.COMMAND],
829              ["\\"], "Emits <Leader> pseudo-key",
830              function () { events.feedkeys("<Leader>"); });
831     }
832 });
833
834 // vim: set fdm=marker sw=4 sts=4 ts=8 et: