diff --git a/common/content/events.js b/common/content/events.js index 99843730..2693b9a9 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -8,321 +8,6 @@ /** @scope modules */ -var ProcessorStack = Class("ProcessorStack", { - init: function (mode, hives, builtin) { - this.main = mode.main; - this._actions = []; - this.actions = []; - this.buffer = ""; - this.events = []; - - events.dbg("STACK " + mode); - - let main = { __proto__: mode.main, params: mode.params }; - this.modes = array([mode.params.keyModes, main, mode.main.allBases.slice(1)]).flatten().compact(); - - if (builtin) - hives = hives.filter(function (h) h.name === "builtin"); - - this.processors = this.modes.map(function (m) hives.map(function (h) KeyProcessor(m, h))) - .flatten().array; - this.ownsBuffer = !this.processors.some(function (p) p.main.ownsBuffer); - - for (let [i, input] in Iterator(this.processors)) { - let params = input.main.params; - - if (params.preExecute) - input.preExecute = params.preExecute; - - if (params.postExecute) - input.postExecute = params.postExecute; - - if (params.onKeyPress && input.hive === mappings.builtin) - input.fallthrough = function fallthrough(events) { - return params.onKeyPress(events) === false ? Events.KILL : Events.PASS; - }; - } - - let hive = options.get("passkeys")[this.main.input ? "inputHive" : "commandHive"]; - if (!builtin && hive.active && (!dactyl.focusedElement || events.isContentNode(dactyl.focusedElement))) - this.processors.unshift(KeyProcessor(modes.BASE, hive)); - }, - - passUnknown: Class.Memoize(function () options.get("passunknown").getKey(this.modes)), - - notify: function () { - events.dbg("NOTIFY()"); - events.keyEvents = []; - events.processor = null; - if (!this.execute(undefined, true)) { - events.processor = this; - events.keyEvents = this.keyEvents; - } - }, - - _result: function (result) (result === Events.KILL ? "KILL" : - result === Events.PASS ? "PASS" : - result === Events.PASS_THROUGH ? "PASS_THROUGH" : - result === Events.ABORT ? "ABORT" : - callable(result) ? result.toSource().substr(0, 50) : result), - - execute: function execute(result, force) { - events.dbg("EXECUTE(" + this._result(result) + ", " + force + ") events:" + this.events.length - + " processors:" + this.processors.length + " actions:" + this.actions.length); - - let processors = this.processors; - let length = 1; - - if (force) - this.processors = []; - - if (this.ownsBuffer) - statusline.inputBuffer = this.processors.length ? this.buffer : ""; - - if (!this.processors.some(function (p) !p.extended) && this.actions.length) { - // We have matching actions and no processors other than - // those waiting on further arguments. Execute actions as - // long as they continue to return PASS. - - for (var action in values(this.actions)) { - while (callable(action)) { - length = action.eventLength; - action = dactyl.trapErrors(action); - events.dbg("ACTION RES: " + length + " " + this._result(action)); - } - if (action !== Events.PASS) - break; - } - - // Result is the result of the last action. Unless it's - // PASS, kill any remaining argument processors. - result = action !== undefined ? action : Events.KILL; - if (action !== Events.PASS) - this.processors.length = 0; - } - else if (this.processors.length) { - // We're still waiting on the longest matching processor. - // Kill the event, set a timeout to give up waiting if applicable. - - result = Events.KILL; - if (options["timeout"] && (this.actions.length || events.hasNativeKey(this.events[0], this.main, this.passUnknown))) - this.timer = services.Timer(this, options["timeoutlen"], services.Timer.TYPE_ONE_SHOT); - } - else if (result !== Events.KILL && !this.actions.length && - !(this.events[0].isReplay || this.passUnknown - || this.modes.some(function (m) m.passEvent(this), this.events[0]))) { - // No patching processors, this isn't a fake, pass-through - // event, we're not in pass-through mode, and we're not - // choosing to pass unknown keys. Kill the event and beep. - - result = Events.ABORT; - if (!Events.isEscape(this.events.slice(-1)[0])) - dactyl.beep(); - events.feedingKeys = false; - } - else if (result === undefined) - // No matching processors, we're willing to pass this event, - // and we don't have a default action from a processor. Just - // pass the event. - result = Events.PASS; - - events.dbg("RESULT: " + length + " " + this._result(result) + "\n\n"); - - if (result !== Events.PASS || this.events.length > 1) - if (result !== Events.ABORT || !this.events[0].isReplay) - Events.kill(this.events[this.events.length - 1]); - - if (result === Events.PASS_THROUGH || result === Events.PASS && this.passUnknown) - events.passing = true; - - if (result === Events.PASS_THROUGH && this.keyEvents.length) - events.dbg("PASS_THROUGH:\n\t" + this.keyEvents.map(function (e) [e.type, events.toString(e)]).join("\n\t")); - - if (result === Events.PASS_THROUGH) - events.feedevents(null, this.keyEvents, { skipmap: true, isMacro: true, isReplay: true }); - else { - let list = this.events.filter(function (e) e.getPreventDefault() && !e.dactylDefaultPrevented); - - if (result === Events.PASS) - events.dbg("PASS THROUGH: " + list.slice(0, length).filter(function (e) e.type === "keypress").map(events.closure.toString)); - if (list.length > length) - events.dbg("REFEED: " + list.slice(length).filter(function (e) e.type === "keypress").map(events.closure.toString)); - - if (result === Events.PASS) - events.feedevents(null, list.slice(0, length), { skipmap: true, isMacro: true, isReplay: true }); - if (list.length > length && this.processors.length === 0) - events.feedevents(null, list.slice(length)); - } - - return this.processors.length === 0; - }, - - process: function process(event) { - if (this.timer) - this.timer.cancel(); - - let key = events.toString(event); - this.events.push(event); - if (this.keyEvents) - this.keyEvents.push(event); - - this.buffer += key; - - let actions = []; - let processors = []; - - events.dbg("PROCESS(" + key + ") skipmap: " + event.skipmap + " macro: " + event.isMacro + " replay: " + event.isReplay); - - for (let [i, input] in Iterator(this.processors)) { - let res = input.process(event); - if (res !== Events.ABORT) - var result = res; - - events.dbg("RES: " + input + " " + this._result(res)); - - if (res === Events.KILL) - break; - - if (callable(res)) - actions.push(res); - - if (res === Events.WAIT || input.waiting) - processors.push(input); - if (isinstance(res, KeyProcessor)) - processors.push(res); - } - - events.dbg("RESULT: " + event.getPreventDefault() + " " + this._result(result)); - events.dbg("ACTIONS: " + actions.length + " " + this.actions.length); - events.dbg("PROCESSORS:", processors, "\n"); - - this._actions = actions; - this.actions = actions.concat(this.actions); - - for (let action in values(actions)) - if (!("eventLength" in action)) - action.eventLength = this.events.length; - - if (result === Events.KILL) - this.actions = []; - else if (!this.actions.length && !processors.length) - for (let input in values(this.processors)) - if (input.fallthrough) { - if (result === Events.KILL) - break; - result = dactyl.trapErrors(input.fallthrough, input, this.events); - } - - this.processors = processors; - - return this.execute(result, options["timeout"] && options["timeoutlen"] === 0); - } -}); - -var KeyProcessor = Class("KeyProcessor", { - init: function init(main, hive) { - this.main = main; - this.events = []; - this.hive = hive; - this.wantCount = this.main.count; - }, - - get toStringParams() [this.main.name, this.hive.name], - - countStr: "", - command: "", - get count() this.countStr ? Number(this.countStr) : this.main.params.count || null, - - append: function append(event) { - this.events.push(event); - let key = events.toString(event); - - if (this.wantCount && !this.command && - (this.countStr ? /^[0-9]$/ : /^[1-9]$/).test(key)) - this.countStr += key; - else - this.command += key; - return this.events; - }, - - process: function process(event) { - this.append(event); - this.waiting = false; - return this.onKeyPress(event); - }, - - execute: function execute(map, args) - let (self = this) - function execute() { - if (self.preExecute) - self.preExecute.apply(self, args); - - args.self = self.main.params.mappingSelf || self.main.mappingSelf || map; - let res = map.execute.call(map, args); - - if (self.postExecute) - self.postExecute.apply(self, args); - return res; - }, - - onKeyPress: function onKeyPress(event) { - if (event.skipmap) - return Events.ABORT; - - if (!this.command) - return Events.WAIT; - - var map = this.hive.get(this.main, this.command); - this.waiting = this.hive.getCandidates(this.main, this.command); - if (map) { - if (map.arg) - return KeyArgProcessor(this, map, false, "arg"); - else if (map.motion) - return KeyArgProcessor(this, map, true, "motion"); - - return this.execute(map, { - keyEvents: this.keyEvents, - command: this.command, - count: this.count, - keypressEvents: this.events - }); - } - - if (!this.waiting) - return this.main.insert ? Events.PASS : Events.ABORT; - - return Events.WAIT; - } -}); - -var KeyArgProcessor = Class("KeyArgProcessor", KeyProcessor, { - init: function init(input, map, wantCount, argName) { - init.supercall(this, input.main, input.hive); - this.map = map; - this.parent = input; - this.argName = argName; - this.wantCount = wantCount; - }, - - extended: true, - - onKeyPress: function onKeyPress(event) { - if (Events.isEscape(event)) - return Events.KILL; - if (!this.command) - return Events.WAIT; - - let args = { - command: this.parent.command, - count: this.count || this.parent.count, - events: this.parent.events.concat(this.events) - }; - args[this.argName] = this.command; - - return this.execute(this.map, args); - } -}); - /** * A hive used mainly for tracking event listeners and cleaning them up when a * group is destroyed. @@ -491,12 +176,11 @@ var Events = Module("events", { this._code_key[60] = "lt"; } - this._activeMenubar = false; - this.listen(window, this, "events", true); - this.popups = { active: [], + activeMenubar: null, + update: function update(elem) { if (elem) { if (elem instanceof Ci.nsIAutoCompletePopup @@ -510,12 +194,52 @@ var Events = Module("events", { this.active = this.active.filter(function (e) e.popupBoxObject.popupState != "closed"); - if (!this.active.length && !events._activeMenubar) + if (!this.active.length && !this.activeMenubar) modes.remove(modes.MENU, true); else if (modes.main != modes.MENU) modes.push(modes.MENU); + }, + + events: { + DOMMenuBarActive: function onDOMMenuBarActive(event) { + this.activeMenubar = event.target; + if (modes.main != modes.MENU) + modes.push(modes.MENU); + }, + + DOMMenuBarInactive: function onDOMMenuBarInactive(event) { + this.activeMenubar = null; + modes.remove(modes.MENU, true); + }, + + popupshowing: function onPopupShowing(event) { + this.update(event.originalTarget); + }, + + popupshown: function onPopupShown(event) { + let elem = event.originalTarget; + this.update(elem); + + if (elem instanceof Ci.nsIAutoCompletePopup) { + if (modes.main != modes.AUTOCOMPLETE) + modes.push(modes.AUTOCOMPLETE); + } + else if (elem.localName !== "tooltip") { + if (Events.isHidden(elem)) + if (elem.hidePopup && Events.isHidden(elem.parentNode)) + elem.hidePopup(); + } + }, + + popuphidden: function onPopupHidden(event) { + this.update(); + modes.remove(modes.AUTOCOMPLETE); + } } }; + + this.listen(window, this, "events", true); + this.listen(window, this.popups, "events", true); }, signals: { @@ -1141,17 +865,6 @@ var Events = Module("events", { }, events: { - DOMMenuBarActive: function onDOMMenuBarActive() { - this._activeMenubar = true; - if (modes.main != modes.MENU) - modes.push(modes.MENU); - }, - - DOMMenuBarInactive: function onDOMMenuBarInactive() { - this._activeMenubar = false; - modes.remove(modes.MENU, true); - }, - blur: function onBlur(event) { let elem = event.originalTarget; if (elem instanceof Window && services.focus.activeWindow == null @@ -1399,30 +1112,6 @@ var Events = Module("events", { } }, - popupshowing: function onPopupShowing(event) { - this.popups.update(event.originalTarget); - }, - - popupshown: function onPopupShown(event) { - let elem = event.originalTarget; - this.popups.update(elem); - - if (elem instanceof Ci.nsIAutoCompletePopup) { - if (modes.main != modes.AUTOCOMPLETE) - modes.push(modes.AUTOCOMPLETE); - } - else if (elem.localName !== "tooltip") { - if (Events.isHidden(elem)) - if (elem.hidePopup && Events.isHidden(elem.parentNode)) - elem.hidePopup(); - } - }, - - popuphidden: function onPopupHidden(event) { - this.popups.update(); - modes.remove(modes.AUTOCOMPLETE); - }, - resize: function onResize(event) { if (window.fullScreen != this._fullscreen) { statusline.statusBar.removeAttribute("moz-collapsed"); diff --git a/common/content/key-processors.js b/common/content/key-processors.js new file mode 100644 index 00000000..966f300a --- /dev/null +++ b/common/content/key-processors.js @@ -0,0 +1,323 @@ +// Copyright (c) 2008-2011 by Kris Maglione +// +// This work is licensed for reuse under an MIT license. Details are +// given in the LICENSE.txt file included with this file. +"use strict"; + +/** @scope modules */ + +var ProcessorStack = Class("ProcessorStack", { + init: function (mode, hives, builtin) { + this.main = mode.main; + this._actions = []; + this.actions = []; + this.buffer = ""; + this.events = []; + + events.dbg("STACK " + mode); + + let main = { __proto__: mode.main, params: mode.params }; + this.modes = array([mode.params.keyModes, main, mode.main.allBases.slice(1)]).flatten().compact(); + + if (builtin) + hives = hives.filter(function (h) h.name === "builtin"); + + this.processors = this.modes.map(function (m) hives.map(function (h) KeyProcessor(m, h))) + .flatten().array; + this.ownsBuffer = !this.processors.some(function (p) p.main.ownsBuffer); + + for (let [i, input] in Iterator(this.processors)) { + let params = input.main.params; + + if (params.preExecute) + input.preExecute = params.preExecute; + + if (params.postExecute) + input.postExecute = params.postExecute; + + if (params.onKeyPress && input.hive === mappings.builtin) + input.fallthrough = function fallthrough(events) { + return params.onKeyPress(events) === false ? Events.KILL : Events.PASS; + }; + } + + let hive = options.get("passkeys")[this.main.input ? "inputHive" : "commandHive"]; + if (!builtin && hive.active && (!dactyl.focusedElement || events.isContentNode(dactyl.focusedElement))) + this.processors.unshift(KeyProcessor(modes.BASE, hive)); + }, + + passUnknown: Class.Memoize(function () options.get("passunknown").getKey(this.modes)), + + notify: function () { + events.dbg("NOTIFY()"); + events.keyEvents = []; + events.processor = null; + if (!this.execute(undefined, true)) { + events.processor = this; + events.keyEvents = this.keyEvents; + } + }, + + _result: function (result) (result === Events.KILL ? "KILL" : + result === Events.PASS ? "PASS" : + result === Events.PASS_THROUGH ? "PASS_THROUGH" : + result === Events.ABORT ? "ABORT" : + callable(result) ? result.toSource().substr(0, 50) : result), + + execute: function execute(result, force) { + events.dbg("EXECUTE(" + this._result(result) + ", " + force + ") events:" + this.events.length + + " processors:" + this.processors.length + " actions:" + this.actions.length); + + let processors = this.processors; + let length = 1; + + if (force) + this.processors = []; + + if (this.ownsBuffer) + statusline.inputBuffer = this.processors.length ? this.buffer : ""; + + if (!this.processors.some(function (p) !p.extended) && this.actions.length) { + // We have matching actions and no processors other than + // those waiting on further arguments. Execute actions as + // long as they continue to return PASS. + + for (var action in values(this.actions)) { + while (callable(action)) { + length = action.eventLength; + action = dactyl.trapErrors(action); + events.dbg("ACTION RES: " + length + " " + this._result(action)); + } + if (action !== Events.PASS) + break; + } + + // Result is the result of the last action. Unless it's + // PASS, kill any remaining argument processors. + result = action !== undefined ? action : Events.KILL; + if (action !== Events.PASS) + this.processors.length = 0; + } + else if (this.processors.length) { + // We're still waiting on the longest matching processor. + // Kill the event, set a timeout to give up waiting if applicable. + + result = Events.KILL; + if (options["timeout"] && (this.actions.length || events.hasNativeKey(this.events[0], this.main, this.passUnknown))) + this.timer = services.Timer(this, options["timeoutlen"], services.Timer.TYPE_ONE_SHOT); + } + else if (result !== Events.KILL && !this.actions.length && + !(this.events[0].isReplay || this.passUnknown + || this.modes.some(function (m) m.passEvent(this), this.events[0]))) { + // No patching processors, this isn't a fake, pass-through + // event, we're not in pass-through mode, and we're not + // choosing to pass unknown keys. Kill the event and beep. + + result = Events.ABORT; + if (!Events.isEscape(this.events.slice(-1)[0])) + dactyl.beep(); + events.feedingKeys = false; + } + else if (result === undefined) + // No matching processors, we're willing to pass this event, + // and we don't have a default action from a processor. Just + // pass the event. + result = Events.PASS; + + events.dbg("RESULT: " + length + " " + this._result(result) + "\n\n"); + + if (result !== Events.PASS || this.events.length > 1) + if (result !== Events.ABORT || !this.events[0].isReplay) + Events.kill(this.events[this.events.length - 1]); + + if (result === Events.PASS_THROUGH || result === Events.PASS && this.passUnknown) + events.passing = true; + + if (result === Events.PASS_THROUGH && this.keyEvents.length) + events.dbg("PASS_THROUGH:\n\t" + this.keyEvents.map(function (e) [e.type, events.toString(e)]).join("\n\t")); + + if (result === Events.PASS_THROUGH) + events.feedevents(null, this.keyEvents, { skipmap: true, isMacro: true, isReplay: true }); + else { + let list = this.events.filter(function (e) e.getPreventDefault() && !e.dactylDefaultPrevented); + + if (result === Events.PASS) + events.dbg("PASS THROUGH: " + list.slice(0, length).filter(function (e) e.type === "keypress").map(events.closure.toString)); + if (list.length > length) + events.dbg("REFEED: " + list.slice(length).filter(function (e) e.type === "keypress").map(events.closure.toString)); + + if (result === Events.PASS) + events.feedevents(null, list.slice(0, length), { skipmap: true, isMacro: true, isReplay: true }); + if (list.length > length && this.processors.length === 0) + events.feedevents(null, list.slice(length)); + } + + return this.processors.length === 0; + }, + + process: function process(event) { + if (this.timer) + this.timer.cancel(); + + let key = events.toString(event); + this.events.push(event); + if (this.keyEvents) + this.keyEvents.push(event); + + this.buffer += key; + + let actions = []; + let processors = []; + + events.dbg("PROCESS(" + key + ") skipmap: " + event.skipmap + " macro: " + event.isMacro + " replay: " + event.isReplay); + + for (let [i, input] in Iterator(this.processors)) { + let res = input.process(event); + if (res !== Events.ABORT) + var result = res; + + events.dbg("RES: " + input + " " + this._result(res)); + + if (res === Events.KILL) + break; + + if (callable(res)) + actions.push(res); + + if (res === Events.WAIT || input.waiting) + processors.push(input); + if (isinstance(res, KeyProcessor)) + processors.push(res); + } + + events.dbg("RESULT: " + event.getPreventDefault() + " " + this._result(result)); + events.dbg("ACTIONS: " + actions.length + " " + this.actions.length); + events.dbg("PROCESSORS:", processors, "\n"); + + this._actions = actions; + this.actions = actions.concat(this.actions); + + for (let action in values(actions)) + if (!("eventLength" in action)) + action.eventLength = this.events.length; + + if (result === Events.KILL) + this.actions = []; + else if (!this.actions.length && !processors.length) + for (let input in values(this.processors)) + if (input.fallthrough) { + if (result === Events.KILL) + break; + result = dactyl.trapErrors(input.fallthrough, input, this.events); + } + + this.processors = processors; + + return this.execute(result, options["timeout"] && options["timeoutlen"] === 0); + } +}); + +var KeyProcessor = Class("KeyProcessor", { + init: function init(main, hive) { + this.main = main; + this.events = []; + this.hive = hive; + this.wantCount = this.main.count; + }, + + get toStringParams() [this.main.name, this.hive.name], + + countStr: "", + command: "", + get count() this.countStr ? Number(this.countStr) : this.main.params.count || null, + + append: function append(event) { + this.events.push(event); + let key = events.toString(event); + + if (this.wantCount && !this.command && + (this.countStr ? /^[0-9]$/ : /^[1-9]$/).test(key)) + this.countStr += key; + else + this.command += key; + return this.events; + }, + + process: function process(event) { + this.append(event); + this.waiting = false; + return this.onKeyPress(event); + }, + + execute: function execute(map, args) + let (self = this) + function execute() { + if (self.preExecute) + self.preExecute.apply(self, args); + + args.self = self.main.params.mappingSelf || self.main.mappingSelf || map; + let res = map.execute.call(map, args); + + if (self.postExecute) + self.postExecute.apply(self, args); + return res; + }, + + onKeyPress: function onKeyPress(event) { + if (event.skipmap) + return Events.ABORT; + + if (!this.command) + return Events.WAIT; + + var map = this.hive.get(this.main, this.command); + this.waiting = this.hive.getCandidates(this.main, this.command); + if (map) { + if (map.arg) + return KeyArgProcessor(this, map, false, "arg"); + else if (map.motion) + return KeyArgProcessor(this, map, true, "motion"); + + return this.execute(map, { + keyEvents: this.keyEvents, + command: this.command, + count: this.count, + keypressEvents: this.events + }); + } + + if (!this.waiting) + return this.main.insert ? Events.PASS : Events.ABORT; + + return Events.WAIT; + } +}); + +var KeyArgProcessor = Class("KeyArgProcessor", KeyProcessor, { + init: function init(input, map, wantCount, argName) { + init.supercall(this, input.main, input.hive); + this.map = map; + this.parent = input; + this.argName = argName; + this.wantCount = wantCount; + }, + + extended: true, + + onKeyPress: function onKeyPress(event) { + if (Events.isEscape(event)) + return Events.KILL; + if (!this.command) + return Events.WAIT; + + let args = { + command: this.parent.command, + count: this.count || this.parent.count, + events: this.parent.events.concat(this.events) + }; + args[this.argName] = this.command; + + return this.execute(this.map, args); + } +}); + diff --git a/common/content/modes.js b/common/content/modes.js index 3fd8fa9f..c5ddb74c 100644 --- a/common/content/modes.js +++ b/common/content/modes.js @@ -609,7 +609,7 @@ var Modes = Module("modes", { mappings.add([modes.MENU], [""], "Close the current popup", function () { - if (modes.popup.active.length) + if (events.popups.active.length) return Events.PASS_THROUGH; modes.pop(); }); diff --git a/common/modules/config.jsm b/common/modules/config.jsm index bfcc36fc..c053f448 100644 --- a/common/modules/config.jsm +++ b/common/modules/config.jsm @@ -94,6 +94,7 @@ var ConfigBase = Class("ConfigBase", { "editor", "events", "hints", + "key-processors", "mappings", "marks", "mow",