]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/content/mappings.js
Imported Upstream version 1.1+hg7904
[dactyl.git] / common / content / mappings.js
index 155849e46fa73a6fb848beef87b9be44f2172129..5a1d6b269452d6c5e6439560b404591cc375335a 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-2014 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.
@@ -17,7 +17,7 @@
  *     *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}
@@ -29,7 +29,7 @@
  * @private
  */
 var Map = Class("Map", {
-    init: function (modes, keys, description, action, extraInfo) {
+    init: function (modes, keys, description, action, info) {
         this.id = ++Map.id;
         this.modes = modes;
         this._keys = keys;
@@ -38,16 +38,20 @@ var Map = Class("Map", {
 
         Object.freeze(this.modes);
 
-        if (extraInfo)
-            this.update(extraInfo);
+        if (info) {
+            if (hasOwnProperty(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))),
+    names: Class.Memoize(function () this._keys.map(k => DOM.Event.canonicalKeys(k))),
 
-    get toStringParams() [this.modes.map(function (m) m.name), this.names.map(String.quote)],
+    get toStringParams() [this.modes.map(m => m.name),
+                          this.names.map(String.quote)],
 
     get identifier() [this.modes[0].name, this.hive.prefix + this.names[0]].join("."),
 
@@ -69,12 +73,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
@@ -91,7 +107,7 @@ var Map = Class("Map", {
      */
     hasName: function (name) this.keys.indexOf(name) >= 0,
 
-    get keys() array.flatten(this.names.map(mappings.closure.expand)),
+    get keys() array.flatten(this.names.map(mappings.bound.expand)),
 
     /**
      * Execute the action for this mapping.
@@ -101,23 +117,26 @@ var Map = Class("Map", {
     execute: function (args) {
         if (!isObject(args)) // Backwards compatibility :(
             args = iter(["motion", "count", "arg", "command"])
-                .map(function ([i, prop]) [prop, this[i]], arguments)
+                .map(([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(_("map.recursive", 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();
         }
@@ -127,12 +146,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, {
@@ -147,10 +171,10 @@ var MapHive = Class("MapHive", Contexts.Hive, {
      * @param {[Modes.Mode]} modes The modes for which to return mappings.
      */
     iterate: function (modes) {
-        let stacks = Array.concat(modes).map(this.closure.getStack);
-        return values(stacks.shift().sort(function (m1, m2) String.localeCompare(m1.name, m2.name))
-            .filter(function (map) map.rhs &&
-                stacks.every(function (stack) stack.some(function (m) m.rhs && m.rhs === map.rhs && m.name === map.name))));
+        let stacks = Array.concat(modes).map(this.bound.getStack);
+        return values(stacks.shift().sort((m1, m2) => String.localeCompare(m1.name, m2.name))
+            .filter((map) => map.rhs &&
+                stacks.every(stack => stack.some(m => m.rhs && m.rhs === map.rhs && m.name === map.name))));
     },
 
     /**
@@ -163,9 +187,7 @@ var MapHive = Class("MapHive", Contexts.Hive, {
      * @param {Object} extra An optional extra configuration hash.
      * @optional
      */
-    add: function (modes, keys, description, action, extra) {
-        extra = extra || {};
-
+    add: function (modes, keys, description, action, extra={}) {
         modes = Array.concat(modes);
         if (!modes.every(util.identity))
             throw TypeError(/*L*/"Invalid modes: " + modes);
@@ -240,7 +262,7 @@ var MapHive = Class("MapHive", Contexts.Hive, {
                 map.names.splice(j, 1);
                 if (map.names.length == 0) // FIX ME.
                     for (let [mode, stack] in Iterator(this.stacks))
-                        this.stacks[mode] = MapHive.Stack(stack.filter(function (m) m != map));
+                        this.stacks[mode] = MapHive.Stack(stack.filter(m => m != map));
                 return;
             }
         }
@@ -272,7 +294,7 @@ var MapHive = Class("MapHive", Contexts.Hive, {
             delete this.states;
         },
 
-        states: Class.memoize(function () {
+        states: Class.Memoize(function () {
             var states = {
                 candidates: {},
                 mappings: {}
@@ -282,7 +304,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;
@@ -298,40 +320,70 @@ 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(),
 
     get allHives() contexts.allGroups.mappings,
 
-    get userHives() this.allHives.filter(function (h) h !== this.builtin, this),
+    get userHives() this.allHives.filter(h => h !== this.builtin),
 
-    expandLeader: function expandLeader(keyString) keyString.replace(/<Leader>/i, function () options["mapleader"]),
+    expandLeader: deprecated("your brain", function expandLeader(keyString) keyString),
 
-    prefixes: Class.memoize(function () {
-        let list = Array.map("CASM", function (s) s + "-");
+    prefixes: Class.Memoize(function () {
+        let list = Array.map("CASM", s => s + "-");
 
-        return iter(util.range(0, 1 << list.length)).map(function (mask)
-            list.filter(function (p, i) mask & (1 << i)).join("")).toArray().concat("*-");
+        return iter(util.range(0, 1 << list.length)).map(mask =>
+            list.filter((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));
+            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(k => DOM.Event.canonicalKeys(k));
+
+        if (keys != arguments[0])
+            return [arguments[0]].concat(keys);
+        return keys;
     },
 
     iterate: function (mode) {
-        let seen = {};
+        let seen = RealSet();
         for (let hive in this.hives.iterValues())
             for (let map in array(hive.getStack(mode)).iterValues())
-                if (!Set.add(seen, map.name))
+                if (!seen.add(map.name))
                     yield map;
     },
 
@@ -391,10 +443,11 @@ var Mappings = Module("mappings", {
      * @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,
+    get: function get(mode, cmd) this.hives.map(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 {Modes.Mode} mode The mode to search.
@@ -402,8 +455,8 @@ var Mappings = Module("mappings", {
      * @returns {[Map]}
      */
     getCandidates: function (mode, prefix)
-        this.hives.map(function (h) h.getCandidates(mode, prefix))
-                  .flatten(),
+        this.hives.map(h => h.getCandidates(mode, prefix))
+                  .reduce((a, b) => (a + b), 0),
 
     /**
      * Lists all user-defined mappings matching *filter* for the specified
@@ -414,48 +467,44 @@ var Mappings = Module("mappings", {
      * @param {[MapHive]} hives The map hives to list. @optional
      */
     list: function (modes, filter, hives) {
-        let modeSign = modes.map(function (m) m.char || "").join("")
-                     + modes.map(function (m) !m.char ? " " + m.name : "").join("");
+        let modeSign = modes.map(m => m.char || "").join("")
+                     + modes.map(m => !m.char ? " " + m.name : "").join("");
         modeSign = modeSign.replace(/^ /, "");
 
-        hives = (hives || mappings.userHives).map(function (h) [h, maps(h)])
-                                             .filter(function ([h, m]) m.length);
+        hives = (hives || mappings.userHives).map(h => [h, maps(h)])
+                                             .filter(([h, m]) => m.length);
 
         function maps(hive) {
             let maps = iter.toArray(hive.iterate(modes));
             if (filter)
-                maps = maps.filter(function (m) m.names[0] === filter);
+                maps = maps.filter(m => m.names[0] === filter);
             return maps;
         }
 
-        let list = <table>
-                <tr highlight="Title">
-                    <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;"/>
-                {
-                    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(([hive, maps]) => let (i = 0) [
+                    ["tr", { style: "height: .5ex;" }],
+                    maps.map(map =>
+                        map.names.map(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);
     }
 }, {
 }, {
@@ -479,7 +528,7 @@ var Mappings = Module("mappings", {
 
                 if (args[1] && !/^<nop>$/i.test(args[1])
                     && !args["-count"] && !args["-ex"] && !args["-javascript"]
-                    && mapmodes.every(function (m) m.count))
+                    && mapmodes.every(m => m.count))
                     args[1] = "<count>" + args[1];
 
                 let [lhs, rhs] = args;
@@ -487,14 +536,14 @@ var Mappings = Module("mappings", {
                     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"));
 
                     args["-group"].add(mapmodes, [lhs],
                         args["-description"],
-                        contexts.bindMacro(args, "-keys", function (params) params),
+                        contexts.bindMacro(args, "-keys", params => params),
                         {
                             arg: args["-arg"],
                             count: args["-count"] || !(args["-ex"] || args["-javascript"]),
@@ -524,7 +573,7 @@ var Mappings = Module("mappings", {
                 options: [
                     {
                         names: ["-arg", "-a"],
-                        description: "Accept an argument after the requisite key press",
+                        description: "Accept an argument after the requisite key press"
                     },
                     {
                         names: ["-builtin", "-b"],
@@ -567,18 +616,17 @@ var Mappings = Module("mappings", {
                 serialize: function () {
                     return this.name != "map" ? [] :
                         array(mappings.userHives)
-                            .filter(function (h) h.persist)
-                            .map(function (hive) [
+                            .filter(h => h.persist)
+                            .map(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(),
+                                    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
@@ -590,10 +638,10 @@ var Mappings = Module("mappings", {
                 }
             };
             function userMappings(hive) {
-                let seen = {};
+                let seen = RealSet();
                 for (let stack in values(hive.stacks))
                     for (let map in array.iterValues(stack))
-                        if (!Set.add(seen, map.id))
+                        if (!seen.add(map.id))
                             yield map;
             }
 
@@ -606,7 +654,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,
@@ -665,9 +713,10 @@ 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)
-                                   .map(function (m) m.name.toLowerCase())
+                         if (v.every(mode => modes.indexOf(mode) >= 0))];
+
+            return array.uniq(modes.filter(m => chars.indexOf(m.char) < 0)
+                                   .map(m => m.name.toLowerCase())
                                    .concat(chars));
         }
 
@@ -700,35 +749,37 @@ var Mappings = Module("mappings", {
                 if (!mainOnly)
                     modes = modes[0].allBases;
 
-                let seen = {};
+                let seen = RealSet();
                 // Bloody hell. --Kris
                 for (let [i, mode] in Iterator(modes))
                     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 (!seen.add(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 : ""))}
-                </>),
-                help: function (map) let (char = array.compact(map.modes.map(function (m) m.char))[0])
+                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(m => m.char))[0])
                     char === "n" ? map.name : char ? char + "_" + map.name : "",
                 headings: ["Command", "Mode", "Group", "Description"]
             }
-        }
+        };
 
         dactyl.addUsageCommand({
             __proto__: args,
@@ -749,10 +800,10 @@ 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 = k => hasOwnProperty(help.tags, k))
                                     ({ helpTag: prefix + map.name, __proto__: map }
                                      for (map in self.iterate(args, true))
-                                     if (map.hive === mappings.builtin || Set.has(tags, prefix + map.name))),
+                                     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,
@@ -761,33 +812,25 @@ var Mappings = Module("mappings", {
         });
     },
     completion: function initCompletion(dactyl, modules, window) {
-        completion.userMapping = function userMapping(context, modes_, hive) {
-            hive = hive || mappings.user;
-            modes_ = modes_ || [modes.NORMAL];
-            context.keys = { text: function (m) m.names[0], description: function (m) m.description + ": " + m.action };
+        completion.userMapping = function userMapping(context, modes_=[modes.NORMAL], hive=mappings.user) {
+            context.keys = { text: function (m) m.names[0],
+                             description: function (m) m.description + ": " + m.action };
             context.completions = hive.iterate(modes_);
         };
     },
     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]))]
+                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: