diff --git a/common/content/commands.js b/common/content/commands.js index cf704727..b9623754 100644 --- a/common/content/commands.js +++ b/common/content/commands.js @@ -696,7 +696,7 @@ const Commands = Module("commands", { if (opt.multiple) args[opt.names[0]] = (args[opt.names[0]] || []).concat(arg); else - args[opt.names[0]] = opt.type == this.OPTION_NOARG || arg; + args[opt.names[0]] = opt.type == CommandOption.NOARG || arg; i += optname.length + count; if (i == str.length) @@ -919,7 +919,7 @@ const Commands = Module("commands", { return; } - [prefix] = context.filter.match(/^(?:\w*[\s!]|!)\s*/); + [prefix] = context.filter.match(/^(?:\w*[\s!])?\s*/); let cmdContext = context.fork(command.name, prefix.length); let argContext = context.fork("args", prefix.length); args = command.parseArgs(cmdContext.filter, argContext, { count: count, bang: bang }); diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 892026db..62c44361 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -326,7 +326,7 @@ const Dactyl = Module("dactyl", { * userContext global. */ userfunc: function () { - return this.userEval( + return this.usereval( "(function (" + Array.slice(arguments, 0, -1).join(", ") + ") { " + arguments[arguments.length - 1] + " })") diff --git a/common/content/mappings.js b/common/content/mappings.js index 74489a17..4dcc3e42 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -33,6 +33,7 @@ const Map = Class("Map", { init: function (modes, keys, description, action, extraInfo) { modes = Array.concat(modes).map(function (m) isobject(m) ? m.mask : m); + this.id = ++Map.id; this.modes = modes; this.names = keys.map(events.canonicalKeys); this.name = this.names[0]; @@ -117,6 +118,8 @@ const Map = Class("Map", { return dactyl.trapErrors(repeat); } +}, { + id: 0 }); /** @@ -177,8 +180,8 @@ const Mappings = Module("mappings", { _mappingsIterator: function (modes, stack) { modes = modes.slice(); return (map for ([i, map] in Iterator(stack[modes.shift()].sort(function (m1, m2) String.localeCompare(m1.name, m2.name)))) - if (modes.every(function (mode) stack[mode].some( - function (m) m.rhs == map.rhs && m.name == map.name)))) + if (modes.every(function (mode) stack[mode]. + some(function (m) array.equals(m.rhs, map.rhs) && m.name == map.name)))) }, // NOTE: just normal mode for now @@ -356,7 +359,7 @@ const Mappings = Module("mappings", { {modeSign} {name} {map.noremap ? "*" : " "} - {map.rhs || "function () { ... }"} + {map.rhs ? map.rhs.join(" ") : "function () { ... }"} )) } ; @@ -370,32 +373,49 @@ const Mappings = Module("mappings", { }, { }, { commands: function () { - function addMapCommands(ch, modes, modeDescription) { + const stockDescription = "User defined mapping"; + function addMapCommands(ch, mapmodes, modeDescription) { // 0 args -> list all maps // 1 arg -> list the maps starting with args // 2 args -> map arg1 to arg* - function map(args, modes, noremap) { + function map(args, mapmodes, noremap) { + mapmodes = getModes(args, mapmodes); if (!args.length) { - mappings.list(modes); + mappings.list(mapmodes); return; } let [lhs, rhs] = args; if (!rhs) // list the mapping - mappings.list(modes, mappings._expandLeader(lhs)); + mappings.list(mapmodes, mappings._expandLeader(lhs)); else { - // this matches Vim's behaviour - if (/^$/i.test(rhs)) - noremap = true; + if (args["-javascript"]) { + rhs = ["-javascript", rhs]; + var action = dactyl.userfunc("count", rhs); + } + else if (args["-ex"]) { + rhs = ["-ex", rhs]; + action = function (count) { + dactyl.execute(commands.replaceTokens(rhs[1], { count: count })); + }; + } + else { + rhs = [events.canonicalKeys(rhs)]; + action = function (count) { + events.feedkeys(commands.replaceTokens(rhs[0], { count: count }), + this.noremap, this.silent); + }; + } - mappings.addUserMap(modes, [lhs], - "User defined mapping", - function (count) { events.feedkeys((count || "") + this.rhs, this.noremap, this.silent); }, { - count: true, - rhs: events.canonicalKeys(rhs), - noremap: !!noremap, - silent: "" in args + mappings.addUserMap(mapmodes, [lhs], + args["-description"] || stockDescription, + action, { + count: args["-count"], + rhs: rhs, + noremap: "-builtin" in args || noremap, + persist: !args["-nopersist"], + silent: "-silent" in args }); } } @@ -409,38 +429,109 @@ const Mappings = Module("mappings", { && /^[nv](nore)?map$/.test(cmd); } + function findMode(name) { + if (typeof name == "number") + return name; + for (let mode in modes.mainModes) + if (name == mode.char || name == mode.name.toLowerCase()) + return mode.mask; + return null; + } + function getModes(args, def) + array.uniq((args["-modes"] || def || ["n", "v"]).map(findMode)); + function uniqueModes(modes) { + modes = modes.map(modules.modes.closure.getMode); + 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)); + } + const opts = { - completer: function (context, args) completion.userMapping(context, args, modes), + completer: function (context, args) { + if (args.length == 1) + return completion.userMapping(context, args, mapmodes) + if (args["-javascript"]) + return completion.javascript(context); + if (args["-ex"]) + return completion.ex(context); + }, literal: 1, - options: [{ names: ["", ""] }], + options: [ + { + names: ["-builtin", "-b"], + description: "Execute this mapping as if there were no user-defined mappings" + }, + { + names: ["-descripion", "-d"], + type: CommandOption.STRING, + description: "A discription of this mapping" + }, + { + names: ["-ex", "-e"], + description: "Execute this mapping as an Ex command rather than keys" + }, + { + names: ["-javascript", "-js", "-j"], + description: "Execute this mapping as JavaScript rather than keys" + }, + { + names: ["-modes", "-mode", "-m"], + type: CommandOption.LIST, + description: "Create this mapping in the given modes", + validator: function (list) !list || list.every(findMode), + completer: function () [[array.compact([mode.name.toLowerCase(), mode.char]), mode.disp] + for (mode in modes.mainModes)], + }, + { + 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 () { - let noremap = this.name.indexOf("noremap") > -1; - return [ + return this.name == "map" ? [ { command: this.name, - options: map.silent ? { "": null } : {}, + options: array([ + ["-modes", uniqueModes(map.modes)], + map.noremap && ["-builtin"], + map.description != stockDescription && ["-description", map.description], + map.rhs.length > 1 && [map.rhs[0]], + map.silent && ["-silent"]]) + .filter(util.identity) + .toObject(), arguments: [map.names[0]], - literalArg: map.rhs + literalArg: map.rhs[map.rhs.length - 1] } - for (map in mappings._mappingsIterator(modes, mappings._user)) - if (map.rhs && map.noremap == noremap && !isMultiMode(map, this.name)) - ]; + for (map in userMappings()) + if (map.persist) + ] : []; } }; + function userMappings() { + let seen = {}; + for (let [, stack] in Iterator(mappings._user)) + for (let map in values(stack)) + if (!set.add(seen, map.id)) + yield map; + } commands.add([ch ? ch + "m[ap]" : "map"], "Map a key sequence" + modeDescription, - function (args) { map(args, modes, false); }, + function (args) { map(args, mapmodes, false); }, opts); commands.add([ch + "no[remap]"], "Map a key sequence without remapping keys" + modeDescription, - function (args) { map(args, modes, true); }, + function (args) { map(args, mapmodes, true); }, opts); commands.add([ch + "mapc[lear]"], "Remove all mappings" + modeDescription, - function () { modes.forEach(function (mode) { mappings.removeAll(mode); }); }, + function () { mapmodes.forEach(function (mode) { mappings.removeAll(mode); }); }, { argCount: "0" }); commands.add([ch + "unm[ap]"], @@ -449,7 +540,7 @@ const Mappings = Module("mappings", { args = args[0]; let found = false; - for (let [, mode] in Iterator(modes)) { + for (let [, mode] in Iterator(mapmodes)) { if (mappings.hasMap(mode, args)) { mappings.remove(mode, args); found = true; @@ -460,7 +551,7 @@ const Mappings = Module("mappings", { }, { argCount: "1", - completer: function (context, args) completion.userMapping(context, args, modes) + completer: function (context, args) completion.userMapping(context, args, mapmodes) }); } diff --git a/common/content/modes.js b/common/content/modes.js index 52fe6911..922238b8 100644 --- a/common/content/modes.js +++ b/common/content/modes.js @@ -10,6 +10,7 @@ const Modes = Module("modes", { init: function () { + this.modeChars = {}; this._main = 1; // NORMAL this._extended = 0; // NONE @@ -143,6 +144,11 @@ const Modes = Module("modes", { name: name, disp: disp }, options); + if (mode.char) { + this.modeChars[mode.char] = this.modeChars[mode.char] || []; + this.modeChars[mode.char].push(mode); + } + mode.display = mode.display || function () disp; this._modeMap[name] = mode; this._modeMap[this[name]] = mode; diff --git a/common/locale/en-US/map.xml b/common/locale/en-US/map.xml index 93053adc..13754b8b 100644 --- a/common/locale/en-US/map.xml +++ b/common/locale/en-US/map.xml @@ -62,8 +62,6 @@ prefixed with one of the above letters. For instance, :imap creates a new key mapping in insert mode, while :cunmap removes a key mapping from command-line mode. - Although other modes do exist, their mappings may currently only - be altered via JavaScript.

@@ -103,6 +101,24 @@ +

Map options

+

+ Any of the map commands may be given the following options: +

+ +
+
+ +
-builtin
Execute this mapping as if there were no user-defined mappings (short name -b)
+
-descripion
A discription of this mapping (short name -d)
+
-ex
Execute rhs as an Ex command rather than keys (short name -e)
+
-javascript
Execute rhs as JavaScript rather than keys (short names -js, -j)
+
-modes
Create this mapping in the given modes (short names -mode, -m)
+
-nopersist
Do not save this mapping to an auto-generated rc file (short name -n)
+
-silent
Do not echo any generated keys to the command-line (short name -s, also <silent> for Vim compatibility)
+
+ + :no :noremap :noremap lhs rhs diff --git a/common/modules/base.jsm b/common/modules/base.jsm index dd701aaf..aa0a3e2f 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -937,6 +937,17 @@ const array = Class("array", Array, { */ compact: function compact(ary) ary.filter(function (item) item != null), + /** + * Returns true if each element of ary1 is equal to the + * corresponding element in ary2. + * + * @param {Array} ary1 + * @param {Array} ary2 + * @returns {boolean} + */ + equals: function (ary1, ary2) + ary1.length == ary2.length && Array.every(ary1, function (e, i) e == ary2[i]), + /** * Flattens an array, such that all elements of the array are * joined into a single array: diff --git a/pentadactyl/NEWS b/pentadactyl/NEWS index 35d27921..6ccbdbab 100644 --- a/pentadactyl/NEWS +++ b/pentadactyl/NEWS @@ -28,6 +28,9 @@ * Added ‘transliterated’ option to 'hintmatching' * Added 'autocomplete' option for specifying which completion contexts should be autocompleted + * Added several new options to :map + * Removed the :source line at the end of files generated by + :mkpentadactylrc * gf now toggles between source and content view. The | key binding has been removed. * :extadd now supports remote URLs as well as local files on