diff --git a/common/content/commandline.js b/common/content/commandline.js index 0a0f1a19..1de329da 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -1008,20 +1008,14 @@ var CommandLine = Module("commandline", { } // user pressed or arrow to cycle history completion else if (/^<(Up|Down|S-Up|S-Down)>$/.test(key)) { - // prevent tab from moving to the next field - event.preventDefault(); - event.stopPropagation(); - dactyl.assert(this._history); this._history.select(/Up/.test(key), !/S-/.test(key)); + return KILL; } // user pressed to get completions of a command else if (/^<(?:A-)?(?:S-)?Tab>$/.test(key)) { - // prevent tab from moving to the next field - event.preventDefault(); - event.stopPropagation(); - this._tabTimer.tell(event); + return KILL; } else if (key == "") { // reset the tab completion diff --git a/common/content/events.js b/common/content/events.js index 3aed5b81..98675c80 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -366,8 +366,9 @@ var Events = Module("events", { }; var t = TYPES[type]; var evt = doc.createEvent((t || "HTML") + "Events"); - evt["init" + t + "Event"].apply(evt, - [v for ([k, v] in Iterator(util.extend(DEFAULTS[t || "HTML"], opts)))]); + + let defaults = DEFAULTS[t || "HTML"] + evt["init" + t + "Event"].apply(evt, Object.keys(defaults).map(function (k) k in opts ? opts[k] : defaults[k])); return evt; }, @@ -874,7 +875,6 @@ var Events = Module("events", { // the command-line has focus // TODO: ...help me...please... onKeyPress: function onKeyPress(event) { - function kill(event) { event.preventDefault(); event.stopPropagation(); @@ -889,7 +889,7 @@ var Events = Module("events", { if (!key) return null; - if (modes.recording && (!this._input || !mappings.hasMap(modes.main, this._input.buffer + key))) + if (modes.recording && (!this._input || !mappings.userHive.hasMap(modes.main, this._input.buffer + key))) events._macroKeys.push(key); // feedingKeys needs to be separate from interrupted so @@ -953,61 +953,74 @@ var Events = Module("events", { if (key == "") util.interrupted = true; - if (key in config.ignoreKeys && (config.ignoreKeys[key] & mode.main)) + if (config.ignoreKeys[key] & mode.main) return null; - let keyModes = array(mode.keyModes || []).concat([mode.params.mainMode, mode.main]).compact(); - if (overrideMode) processors = [Events.KeyProcessor(overrideMode, mode.extended)]; else { - for (let m in keyModes.iterValues()) - if (m) - processors.push(Events.KeyProcessor(m, mode.extended)); + let keyModes = array(mode.params.keyModes || []).concat([mode.params.mainMode, mode.main]).compact(); - let input = processors[processors.length - 1]; - if (mode.params.preExecute) - input.preExecute = mode.params.preExecute; - if (mode.params.postExecute) - input.postExecute = mode.params.postExecute; - if (mode.params.onEvent) - input.fallthrough = function (event) { - // Bloody hell. - if (events.toString(event) === "") - event.dactylString = ""; + let hives = mappings.hives.slice(event.noremap ? -1 : 0); - return mode.params.onEvent(event) === false; - }; + processors = keyModes.map(function (m) hives.map(function (h) Events.KeyProcessor(m, mode.extended, h))) + .flatten().array; + + for (let [i, input] in Iterator(processors)) { + if (input.main == mode.main) { + if (mode.params.preExecute) + input.preExecute = mode.params.preExecute; + if (mode.params.postExecute) + input.postExecute = mode.params.postExecute; + if (mode.params.onEvent && i == processors.length - 1) + input.fallthrough = function (event) { + // Bloody hell. + if (events.toString(event) === "") + event.dactylString = ""; + + return mode.params.onEvent(event) === false ? KILL : PASS; + }; + }} } } const KILL = true, PASS = false, WAIT = null; - let refeed; + let refeed, ownsBuffer = 0, buffer, waiting = 0; for (let input in values(processors)) { var res = input.process(event); - if (isArray(res)) + ownsBuffer += !!input.main.ownsBuffer; + waiting += res == WAIT; + buffer = buffer || input.inputBuffer; + + if (isArray(res) && !waiting) refeed = res; - else if (res == WAIT || res === PASS) - continue; - else if (res === KILL) { + else if (res !== PASS) refeed = null; + if (res === KILL) break; - } } - if (res == WAIT) + if (!ownsBuffer) + statusline.updateInputBuffer(buffer); + + if (waiting) this._processors = processors; else if (res !== KILL && (mode.main & (modes.TEXT_EDIT | modes.VISUAL))) dactyl.beep(); + if (refeed && refeed.length == 1 && !refeed[0].getPreventDefault()) + [refeed, res] = [null, PASS]; + if (res !== PASS) kill(event); if (refeed) for (let [i, event] in Iterator(refeed)) - if (event.originalTarget) - events.dispatch(event.originalTarget, event, i == 0 && { skipmap: true }); + if (event.originalTarget) { + let evt = events.create(event.originalTarget.ownerDocument, event.type, event); + events.dispatch(event.originalTarget, evt, i == 0 && { skipmap: true }); + } else if (i > 0) events.onKeyPress(event); } @@ -1085,13 +1098,14 @@ var Events = Module("events", { } }, { KeyProcessor: Class("KeyProcessor", { - init: function init(main, extended) { + init: function init(main, extended, hive) { this.main = main; this.extended = extended; this.events = []; + this.hive = hive; }, - toString: function () "[instance KeyProcessor(" + this.main.name + ")]", + toString: function () ["[instance KeyProcessor(" + this.main.name, this.extended, this.hive.name + ")]"].join(", "), buffer: "", // partial command storage pendingMotionMap: null, // e.g. "d{motion}" if we wait for a motion of the "d" command @@ -1116,28 +1130,25 @@ var Events = Module("events", { let res = this.onKeyPress(event); if (res === KILL) kill(event); - else if (isArray(res)) { - if (this.fallthrough) { - let r = dactyl.trapErrors(this.fallthrough, this, res[0]); - if (r === KILL) - kill(res[0]); - res = r === KILL ? KILL : PASS; - /* - if (r === KILL) - res = res.slice(1); - else - res = r == WAIT ? res : false; - */ - } + else if (this.fallthrough) { + let evt = isArray(res) ? res[0] : event; + let r = dactyl.trapErrors(this.fallthrough, this, evt); + if (r === KILL) + kill(evt); + res = r === KILL ? KILL : PASS; + /* + if (r === KILL) + res = res.slice(1); + else + res = r == WAIT ? res : false; + */ } - if (!this.main.ownsBuffer) { - if (res != WAIT) - statusline.updateInputBuffer(""); - else { - let motionMap = (this.pendingMotionMap && this.pendingMotionMap.names[0]) || ""; - statusline.updateInputBuffer(motionMap + this.buffer); - } + if (res != WAIT) + this.inputBuffer = ""; + else { + let motionMap = (this.pendingMotionMap && this.pendingMotionMap.names[0]) || ""; + this.inputBuffer = motionMap + this.buffer; } return res; @@ -1150,7 +1161,7 @@ var Events = Module("events", { let key = events.toString(event); let [, countStr, command] = /^((?:[1-9][0-9]*)?)(.*)/.exec(this.buffer + key); - let map = mappings[event.noremap ? "getDefault" : "get"](this.main, command); + let map = this.hive.get(this.main, command); function execute(map) { if (self.preExecute) @@ -1161,7 +1172,7 @@ var Events = Module("events", { return res; } - let candidates = mappings.getCandidates(this.main, command); + let candidates = this.hive.getCandidates(this.main, command); if (candidates.length == 0 && !map) { [map] = this.pendingMap || []; this.pendingMap = null; @@ -1215,7 +1226,7 @@ var Events = Module("events", { return execute(map, null, this.count, null, command) && map.route ? PASS : KILL; } } - else if (!event.skipmap && mappings.getCandidates(this.main, command).length > 0) { + else if (!event.skipmap && this.hive.getCandidates(this.main, command).length > 0) { this.append(event); this.pendingMap = [map, command]; } diff --git a/common/content/mappings.js b/common/content/mappings.js index 142bb44b..3d253d9d 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -95,7 +95,7 @@ var Map = Class("Map", { */ hasName: function (name) this.keys.indexOf(name) >= 0, - keys: Class.memoize(function () this.names.map(mappings._expandLeader)), + keys: Class.memoize(function () this.names.map(mappings.expandLeader)), /** * Execute the action for this mapping. @@ -122,87 +122,150 @@ var Map = Class("Map", { id: 0 }); -/** - * @instance mappings - */ -var Mappings = Module("mappings", { - init: function () { - this._main = []; // default mappings - this._user = []; // user created mappings - - dactyl.registerObserver("mode-add", function (mode) { - if (!(mode.mask in this._user || mode.mask in this._main)) { - this._main[mode.mask] = []; - this._user[mode.mask] = []; - } - }); +var MapHive = Class("MapHive", { + init: function init(name) { + this.name = name; + this.stacks = {}; }, - _addMap: function (map) { - let where = map.user ? this._user : this._main; - map.definedAt = commands.getCaller(Components.stack.caller.caller); - map.modes.forEach(function (mode) { - if (!(mode in where)) - where[mode] = []; - where[mode].push(map); - }); + /** + * Iterates over all mappings present in all of the given *modes*. + * + * @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)))); }, - _getMap: function (mode, cmd, stack) { - let maps = stack[mode] || []; - - for (let [, map] in Iterator(maps)) - if (map.hasName(cmd)) - return map; - - return null; + /** + * Returns the mapping stack for the given mode. + * + * @param {Modes.Mode} mode The mode to search. + * @returns {[Map]} + */ + getStack: function getStack(mode) { + if (!(mode in this.stacks)) + return this.stacks[mode] = []; + return this.stacks[mode]; }, - _removeMap: function (mode, cmd) { - let maps = this._user[mode] || []; - let names; + /** + * Adds a new Map object to the given mode. + * + * @param {Modes.Mode} mode The mode to search. + * @param {string} cmd The map name to match. + * @returns {Map|null} + */ + add: function (mode, map) { + this.getStack(mode).push(map); + }, - for (let [i, map] in Iterator(maps)) { + /** + * Returns the map from *mode* named *cmd*. + * + * @param {Modes.Mode} mode The mode to search. + * @param {string} cmd The map name to match. + * @returns {Map|null} + */ + get: function (mode, cmd) array.nth(this.getStack(mode), function (m) m.hasName(cmd), 0), + + /** + * Returns an array of maps with names starting with but not equal to + * *prefix*. + * + * @param {Modes.Mode} mode The mode to search. + * @param {string} prefix The map prefix string to match. + * @returns {Map[]} + */ + getCandidates: function (mode, prefix) { + let filter = util.regexp("^" + util.regexp.escape(prefix) + ".").closure.test; + return this.getStack(mode) + .filter(function (map) map.keys.some(filter)); + }, + + /** + * Returns whether there is a user-defined mapping *cmd* for the specified + * *mode*. + * + * @param {Modes.Mode} mode The mode to search. + * @param {string} cmd The candidate key mapping. + * @returns {boolean} + */ + has: function (mode, cmd) this.getStack(mode).some(function (map) map.hasName(cmd)), + + /** + * Remove the mapping named *cmd* for *mode*. + * + * @param {Modes.Mode} mode The mode to search. + * @param {string} cmd The map name to match. + */ + remove: function (mode, cmd) { + for (let [i, map] in Iterator(this.getStack(mode))) { let j = map.names.indexOf(cmd); if (j >= 0) { map.names.splice(j, 1); if (map.names.length == 0) // FIX ME. - for (let [mode, stack] in Iterator(this._user)) - this._user[mode] = (stack || []).filter(function (m) m != map); + for (let [mode, stack] in Iterator(this.stacks)) + this.stacks[mode] = stack.filter(function (m) m != map); return; } } }, - _expandLeader: function (keyString) keyString.replace(//i, options["mapleader"]), + /** + * Remove all user-defined mappings for *mode*. + * + * @param {Modes.Mode} mode The mode to remove all mappings from. + */ + clear: function (mode) { + this.stacks[mode] = []; + } +}); - // Return all mappings present in all @modes - _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 (map.rhs && modes.every(function (mode) stack[mode]. - some(function (m) m.rhs && m.rhs === map.rhs && m.name === map.name)))); +/** + * @instance mappings + */ +var Mappings = Module("mappings", { + init: function () { + this.userHive = MapHive("user"); + this.builtinHive = MapHive("builtin"); + this.hives = array([this.userHive, this.builtinHive]); }, + _addMap: function (map) { + let hive = map.user ? this.userHive : this.builtinHive; + + map.definedAt = commands.getCaller(Components.stack.caller.caller); + map.modes.forEach(function (mode) { + hive.add(mode, map); + }); + }, + + expandLeader: function (keyString) keyString.replace(//i, options["mapleader"]), + iterate: function (mode) { let seen = {}; - for (let map in iter(values(this._user[mode]), values(this._main[mode]))) - if (!set.add(seen, map.name)) - yield map; + for (let hive in this.hives.iterValues()) + for (let map in values(hive.getStack(mode))) + if (!set.add(seen, map.name)) + yield map; }, // NOTE: just normal mode for now /** @property {Iterator(Map)} */ __iterator__: function () this.iterate(modes.NORMAL), - // used by :mkpentadactylrc to save mappings - /** - * Returns a user-defined mappings iterator for the specified *mode*. - * - * @param {number} mode The mode to return mappings from. - * @returns {Iterator(Map)} - */ - getUserIterator: function (mode) this._mappingsIterator(mode, this._user), + getDefault: deprecated("mappings.builtinHive.get", + function getDefault(mode, cmd) this.builtinHive.get(mode, cmd)), + getUserIterator: deprecated("mappings.userHive.iterator", + function getUserIterator(modes) this.userHive.iterator(modes)), + hasMap: deprecated("mappings.userHive.has", function hasMap(mode, cmd) this.userHive.has(mode, cmd)), + remove: deprecated("mappings.userHive.remove", function remove(mode, cmd) this.userHive.remove(mode, cmd)), + removeAll: deprecated("mappings.userHive.clear", function removeAll(mode) this.userHive.clear(mode)), + /** * Adds a new default key mapping. @@ -237,7 +300,7 @@ var Mappings = Module("mappings", { // remove all old mappings to this key sequence for (let [, name] in Iterator(map.names)) for (let [, mode] in Iterator(map.modes)) - this._removeMap(mode, name); + this.userHive.remove(mode, name); this._addMap(map); }, @@ -249,21 +312,8 @@ var Mappings = Module("mappings", { * @param {string} cmd The map name to match. * @returns {Map} */ - get: function (mode, cmd) { - mode = mode || modes.NORMAL; - return this._getMap(mode, cmd, this._user) || this._getMap(mode, cmd, this._main); - }, - - /** - * Returns the default map from *mode* named *cmd*. - * - * @param {number} mode The mode to search. - * @param {string} cmd The map name to match. - * @returns {Map} - */ - getDefault: function (mode, cmd) { - mode = mode || modes.NORMAL; - return this._getMap(mode, cmd, this._main); + get: function get(mode, cmd) { + return this.hives.nth(function (h) h.get(mode, command), 0); }, /** @@ -275,39 +325,8 @@ var Mappings = Module("mappings", { * @returns {Map[]} */ getCandidates: function (mode, prefix) - this._user[mode].concat(this._main[mode]) - .filter(function (map) map.keys.some( - function (name) name.indexOf(prefix) == 0 && name.length > prefix.length - && (prefix != "<" || !/^<.+>/.test(name)))), - - /** - * Returns whether there is a user-defined mapping *cmd* for the specified - * *mode*. - * - * @param {number} mode The mode to search. - * @param {string} cmd The candidate key mapping. - * @returns {boolean} - */ - hasMap: function (mode, cmd) this._user[mode].some(function (map) map.hasName(cmd)), - - /** - * Remove the user-defined mapping named *cmd* for *mode*. - * - * @param {number} mode The mode to search. - * @param {string} cmd The map name to match. - */ - remove: function (mode, cmd) { - this._removeMap(mode, cmd); - }, - - /** - * Remove all user-defined mappings for *mode*. - * - * @param {number} mode The mode to remove all mappings from. - */ - removeAll: function (mode) { - this._user[mode] = []; - }, + this.hives.map(function (h) h.getCandidates(mode, prefix)) + .flatten(), /** * Lists all user-defined mappings matching *filter* for the specified @@ -327,7 +346,7 @@ var Mappings = Module("mappings", { modeSign += m.char; }); - let maps = this._mappingsIterator(modes, this._user); + let maps = this.userHive.iterate(modes); if (filter) maps = [map for (map in maps) if (map.names[0] == filter)]; @@ -365,7 +384,7 @@ var Mappings = Module("mappings", { args["-builtin"] = true; if (!rhs) // list the mapping - mappings.list(mapmodes, mappings._expandLeader(lhs)); + mappings.list(mapmodes, mappings.expandLeader(lhs)); else { mappings.addUserMap(mapmodes, [lhs], args["-description"], @@ -453,7 +472,7 @@ var Mappings = Module("mappings", { }; function userMappings() { let seen = {}; - for (let [, stack] in Iterator(mappings._user)) + for (let stack in values(mappings.userHive.stacks)) for (let map in values(stack)) if (!set.add(seen, map.id)) yield map; @@ -472,7 +491,7 @@ var Mappings = Module("mappings", { commands.add([ch + "mapc[lear]"], "Remove all mappings" + modeDescription, - function () { mapmodes.forEach(function (mode) { mappings.removeAll(mode); }); }, + function () { mapmodes.forEach(function (mode) { mappings.userHive.clear(mode); }); }, { argCount: "0", options: [ update({}, modeFlag, { @@ -491,8 +510,8 @@ var Mappings = Module("mappings", { let found = false; for (let [, mode] in Iterator(mapmodes)) { - if (mappings.hasMap(mode, args[0])) { - mappings.remove(mode, args[0]); + if (mappings.userHive.hasMap(mode, args[0])) { + mappings.userHive.remove(mode, args[0]); found = true; } } @@ -608,7 +627,8 @@ var Mappings = Module("mappings", { completion.userMapping = function userMapping(context, modes) { // FIXME: have we decided on a 'standard' way to handle this clash? --djk modes = modes || [modules.modes.NORMAL]; - context.completions = [[m.names[0], m.description + ": " + m.action] for (m in mappings.getUserIterator(modes))]; + context.keys = { text: function (m) m.names[0], description: function (m) m.description + ": " + m.action }; + context.completions = mappings.userHive.iterate(modes); }; }, javascript: function () { @@ -619,26 +639,20 @@ var Mappings = Module("mappings", { let mode = args[0]; return array.flatten([ [[name, map.description] for ([i, name] in Iterator(map.names))] - for ([i, map] in Iterator(mappings._user[mode].concat(mappings._main[mode]))) + for (map in mappings.iterate(mode)) ]); } ]); }, - modes: function () { - for (let mode in modes) { - this._main[mode] = []; - this._user[mode] = []; - } - }, options: function () { 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._user, mappings._main])) - for (let mode in values(hive)) - for (let map in values(mode)) + for (let hive in mappings.hives.iterValues()) + for (let stack in values(hive.stacks)) + for (let map in values(stack)) delete map.keys; return value; }