X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fcontent%2Fmappings.js;h=68bba94c6e9bed082211128e30069ddb5cf53b8e;hb=3d837eb266a3a01d424192aa4ec1a167366178c5;hp=668d8ce106a02fffd666ae93fb797ee425a6621b;hpb=eeed0be1a8abf7e3c97f43b63c1d595e940fef21;p=dactyl.git diff --git a/common/content/mappings.js b/common/content/mappings.js index 668d8ce..68bba94 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott // Copyright (c) 2007-2011 by Doug Kearns -// Copyright (c) 2008-2011 by Kris Maglione +// Copyright (c) 2008-2012 Kris Maglione // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -12,12 +12,12 @@ * 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} @@ -29,28 +29,26 @@ * @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(//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 = - - - - - - - { - template.map(hives, function ([hive, maps]) let (i = 0) - + - template.map(maps, function (map) - template.map(map.names, function (name) - - - - - - )) + - ) - } -
- ModeCommandAction
{!i++ ? hive.name : ""}{modeSign}{name}{map.rhs || map.action.toSource()}
; - - // 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] && !/^$/i.test(args[1]) + && !args["-count"] && !args["-ex"] && !args["-javascript"] + && mapmodes.every(function (m) m.count)) + args[1] = "" + 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", "", ""], - 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", "", ""], + 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 ? "" : {mode.name}, - hive == mappings.builtin ? "" : {hive.name} + 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) - ? (passed by {template.helpLink("'passkeys'")}) - : <>} - {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 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 pseudo-key", + function () { events.feedkeys(""); }); } }); -// vim: set fdm=marker sw=4 ts=4 et: +// vim: set fdm=marker sw=4 sts=4 ts=8 et: