//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
-"use strict";
+/* 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) : 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.
this.unlisten(null);
},
+ _events: function _events(event, callback) {
+ if (!isObject(event))
+ var [self, events] = [null, array.toObject([[event, callback]])];
+ else
+ [self, events] = [event, event[callback || "events"]];
+
+ if (Set.has(events, "input") && !Set.has(events, "dactyl-input"))
+ events["dactyl-input"] = events.input;
+
+ return [self, events];
+ },
+
/**
* Adds an event listener for this session and removes it on
* dactyl shutdown.
* untrusted events.
*/
listen: function (target, event, callback, capture, allowUntrusted) {
- if (!isObject(event))
- var [self, events] = [null, array.toObject([[event, callback]])];
- else {
- [self, events] = [event, event[callback || "events"]];
- [, , capture, allowUntrusted] = arguments;
- }
-
- if (Set.has(events, "input") && !Set.has(events, "dactyl-input"))
- events["dactyl-input"] = events.input;
+ var [self, events] = this._events(event, callback);
for (let [event, callback] in Iterator(events)) {
- let args = [Cu.getWeakReference(target),
+ let args = [util.weakReference(target),
+ util.weakReference(self),
event,
this.wrapListener(callback, self),
capture,
allowUntrusted];
- target.addEventListener.apply(target, args.slice(1));
+ target.addEventListener.apply(target, args.slice(2));
this.sessionListeners.push(args);
}
},
* phase, otherwise during the bubbling phase.
*/
unlisten: function (target, event, callback, capture) {
+ if (target != null)
+ var [self, events] = this._events(event, callback);
+
this.sessionListeners = this.sessionListeners.filter(function (args) {
- if (target == null || args[0].get() == target && args[1] == event && args[2] == callback && args[3] == capture) {
- args[0].get().removeEventListener.apply(args[0].get(), args.slice(1));
+ let elem = args[0].get();
+ if (target == null || elem == target
+ && self == args[1].get()
+ && Set.has(events, args[2])
+ && args[3].wrapped == events[args[2]]
+ && args[4] == capture) {
+
+ elem.removeEventListener.apply(elem, args.slice(2));
return false;
}
- return !args[0].get();
+ return elem;
});
- }
+ },
+
+ get wrapListener() events.closure.wrapListener
});
/**
init: function () {
this.keyEvents = [];
- update(this, {
- hives: contexts.Hives("events", EventHive),
- user: contexts.hives.events.user,
- builtin: contexts.hives.events.builtin
- });
-
- EventHive.prototype.wrapListener = this.closure.wrapListener;
-
XML.ignoreWhitespace = true;
- util.overlayWindow(window, {
+ overlay.overlayWindow(window, {
append: <e4x xmlns={XUL}>
<window id={document.documentElement.id}>
<!-- http://developer.mozilla.org/en/docs/XUL_Tutorial:Updating_Commands -->
this._macroKeys = [];
this._lastMacro = "";
- this._macros = storage.newMap("macros", { privateData: true, store: true });
- for (let [k, m] in this._macros)
- if (isString(m))
- m = { keys: m, timeRecorded: Date.now() };
-
- // NOTE: the order of ["Esc", "Escape"] or ["Escape", "Esc"]
- // matters, so use that string as the first item, that you
- // want to refer to within dactyl's source code for
- // comparisons like if (key == "<Esc>") { ... }
- this._keyTable = {
- add: ["Plus", "Add"],
- back_space: ["BS"],
- count: ["count"],
- delete: ["Del"],
- escape: ["Esc", "Escape"],
- insert: ["Insert", "Ins"],
- leader: ["Leader"],
- left_shift: ["LT", "<"],
- nop: ["Nop"],
- pass: ["Pass"],
- return: ["Return", "CR", "Enter"],
- right_shift: [">"],
- space: ["Space", " "],
- subtract: ["Minus", "Subtract"]
- };
+ this._macros = storage.newMap("registers", { privateData: true, store: true });
+ if (storage.exists("macros")) {
+ for (let [k, m] in storage.newMap("macros", { store: true }))
+ this._macros.set(k, { text: m.keys, timestamp: m.timeRecorded * 1000 });
+ storage.remove("macros");
+ }
- this._pseudoKeys = Set(["count", "leader", "nop", "pass"]);
+ this.popups = {
+ active: [],
- this._key_key = {};
- this._code_key = {};
- this._key_code = {};
- this._code_nativeKey = {};
+ activeMenubar: null,
- for (let list in values(this._keyTable))
- for (let v in values(list)) {
- if (v.length == 1)
- v = v.toLowerCase();
- this._key_key[v.toLowerCase()] = v;
- }
+ update: function update(elem) {
+ if (elem) {
+ if (elem instanceof Ci.nsIAutoCompletePopup
+ || elem.localName == "tooltip"
+ || !elem.popupBoxObject)
+ return;
+
+ if (!~this.active.indexOf(elem))
+ this.active.push(elem);
+ }
+
+ this.active = this.active.filter(function (e) e.popupBoxObject.popupState != "closed");
+
+ if (!this.active.length && !this.activeMenubar)
+ modes.remove(modes.MENU, true);
+ else if (modes.main != modes.MENU)
+ modes.push(modes.MENU);
+ },
- for (let [k, v] in Iterator(KeyEvent)) {
- this._code_nativeKey[v] = k.substr(4);
+ events: {
+ DOMMenuBarActive: function onDOMMenuBarActive(event) {
+ this.activeMenubar = event.target;
+ if (modes.main != modes.MENU)
+ modes.push(modes.MENU);
+ },
- k = k.substr(7).toLowerCase();
- let names = [k.replace(/(^|_)(.)/g, function (m, n1, n2) n2.toUpperCase())
- .replace(/^NUMPAD/, "k")];
+ DOMMenuBarInactive: function onDOMMenuBarInactive(event) {
+ this.activeMenubar = null;
+ modes.remove(modes.MENU, true);
+ },
- if (names[0].length == 1)
- names[0] = names[0].toLowerCase();
+ popupshowing: function onPopupShowing(event) {
+ this.update(event.originalTarget);
+ },
+
+ popupshown: function onPopupShown(event) {
+ let elem = event.originalTarget;
+ this.update(elem);
- if (k in this._keyTable)
- names = this._keyTable[k];
- this._code_key[v] = names[0];
- for (let [, name] in Iterator(names)) {
- this._key_key[name.toLowerCase()] = name;
- this._key_code[name.toLowerCase()] = v;
+ if (elem instanceof Ci.nsIAutoCompletePopup) {
+ if (modes.main != modes.AUTOCOMPLETE)
+ modes.push(modes.AUTOCOMPLETE);
+ }
+ else if (elem.hidePopup && elem.localName !== "tooltip"
+ && Events.isHidden(elem)
+ && Events.isHidden(elem.parentNode)) {
+ elem.hidePopup();
+ }
+ },
+
+ popuphidden: function onPopupHidden(event) {
+ this.update();
+ modes.remove(modes.AUTOCOMPLETE);
+ }
}
- }
+ };
- // HACK: as Gecko does not include an event for <, we must add this in manually.
- if (!("<" in this._key_code)) {
- this._key_code["<"] = 60;
- this._key_code["lt"] = 60;
- this._code_key[60] = "lt";
- }
+ this.listen(window, this, "events", true);
+ this.listen(window, this.popups, "events", true);
+ },
- this._activeMenubar = false;
- this.listen(window, this, "events");
+ cleanup: function cleanup() {
+ let elem = dactyl.focusedElement;
+ if (DOM(elem).isEditable)
+ util.trapErrors("removeEditActionListener",
+ DOM(elem).editor, editor);
},
signals: {
*/
wrapListener: function wrapListener(method, self) {
self = self || this;
- method.wrapped = wrappedListener;
+ method.wrapper = wrappedListener;
+ wrappedListener.wrapped = method;
function wrappedListener(event) {
try {
method.apply(self, arguments);
dactyl.assert(macro == null || /[a-zA-Z0-9]/.test(macro),
_("macro.invalid", macro));
- modes.recording = !!macro;
+ modes.recording = macro;
- if (/[A-Z]/.test(macro)) { // uppercase (append)
+ if (/[A-Z]/.test(macro)) { // Append.
macro = macro.toLowerCase();
- this._macroKeys = events.fromString((this._macros.get(macro) || { keys: "" }).keys, true)
- .map(events.closure.toString);
+ this._macroKeys = DOM.Event.iterKeys(editor.getRegister(macro))
+ .toArray();
}
- else if (macro) {
+ else if (macro) { // Record afresh.
this._macroKeys = [];
}
- else {
- this._macros.set(this.recording, {
- keys: this._macroKeys.join(""),
- timeRecorded: Date.now()
- });
+ else if (this.recording) { // Save.
+ editor.setRegister(this.recording, this._macroKeys.join(""));
dactyl.log(_("macro.recorded", this.recording, this._macroKeys.join("")), 9);
dactyl.echomsg(_("macro.recorded", this.recording));
* @returns {boolean}
*/
playMacro: function (macro) {
- let res = false;
- dactyl.assert(/^[a-zA-Z0-9@]$/.test(macro), _("macro.invalid", macro));
+ dactyl.assert(/^[a-zA-Z0-9@]$/.test(macro),
+ _("macro.invalid", macro));
if (macro == "@")
dactyl.assert(this._lastMacro, _("macro.noPrevious"));
else
this._lastMacro = macro.toLowerCase(); // XXX: sets last played macro, even if it does not yet exist
- if (this._macros.get(this._lastMacro)) {
- try {
- modes.replaying = true;
- res = events.feedkeys(this._macros.get(this._lastMacro).keys, { noremap: true });
- }
- finally {
- modes.replaying = false;
- }
- }
- else
- // TODO: ignore this like Vim?
- dactyl.echoerr(_("macro.noSuch", this._lastMacro));
- return res;
+ let keys = editor.getRegister(this._lastMacro);
+ if (keys)
+ return modes.withSavedValues(["replaying"], function () {
+ this.replaying = true;
+ return events.feedkeys(keys, { noremap: true });
+ });
+
+ // TODO: ignore this like Vim?
+ dactyl.echoerr(_("macro.noSuch", this._lastMacro));
+ return false;
},
/**
*/
getMacros: function (filter) {
let re = RegExp(filter || "");
- return ([k, m.keys] for ([k, m] in events._macros) if (re.test(k)));
+ return ([k, m.text] for ([k, m] in editor.registers) if (re.test(k)));
},
/**
*/
deleteMacros: function (filter) {
let re = RegExp(filter || "");
- for (let [item, ] in this._macros) {
+ for (let [item, ] in editor.registers) {
if (!filter || re.test(item))
- this._macros.remove(item);
+ editor.registers.remove(item);
}
},
let elem = target || event.originalTarget;
if (elem) {
let doc = elem.ownerDocument || elem.document || elem;
- let evt = events.create(doc, event.type, event);
- events.dispatch(elem, evt, extra);
+ let evt = DOM.Event(doc, event.type, event);
+ DOM.Event.dispatch(elem, evt, extra);
}
else if (i > 0 && event.type === "keypress")
events.events.keypress.call(events, event);
keys = mappings.expandLeader(keys);
- for (let [, evt_obj] in Iterator(events.fromString(keys))) {
+ for (let [, evt_obj] in Iterator(DOM.Event.parse(keys))) {
let now = Date.now();
- let key = events.toString(evt_obj);
+ let key = DOM.Event.stringify(evt_obj);
for (let type in values(["keydown", "keypress", "keyup"])) {
let evt = update({}, evt_obj, { type: type });
if (type !== "keypress" && !evt.keyCode)
evt.isMacro = true;
evt.dactylMode = mode;
evt.dactylSavedEvents = savedEvents;
- this.feedingEvent = evt;
+ DOM.Event.feedingEvent = evt;
let doc = document.commandDispatcher.focusedWindow.document;
- let event = events.create(doc, type, evt);
+
let target = dactyl.focusedElement
|| ["complete", "interactive"].indexOf(doc.readyState) >= 0 && doc.documentElement
|| doc.defaultView;
["<Return>", "<Space>"].indexOf(key) == -1)
target = target.ownerDocument.documentElement;
+ let event = DOM.Event(doc, type, evt);
if (!evt_obj.dactylString && !mode)
- events.dispatch(target, event, evt);
+ DOM.Event.dispatch(target, event, evt);
else if (type === "keypress")
events.events.keypress.call(events, event);
}
util.reportError(e);
}
finally {
- this.feedingEvent = null;
+ DOM.Event.feedingEvent = null;
this.feedingKeys = wasFeeding;
if (quiet)
commandline.quiet = wasQuiet;
return true;
},
- /**
- * Creates an actual event from a pseudo-event object.
- *
- * The pseudo-event object (such as may be retrieved from events.fromString)
- * should have any properties you want the event to have.
- *
- * @param {Document} doc The DOM document to associate this event with
- * @param {Type} type The type of event (keypress, click, etc.)
- * @param {Object} opts The pseudo-event. @optional
- */
- create: function (doc, type, opts) {
- const DEFAULTS = {
- HTML: {
- type: type, bubbles: true, cancelable: false
- },
- Key: {
- type: type,
- bubbles: true, cancelable: true,
- view: doc.defaultView,
- ctrlKey: false, altKey: false, shiftKey: false, metaKey: false,
- keyCode: 0, charCode: 0
- },
- Mouse: {
- type: type,
- bubbles: true, cancelable: true,
- view: doc.defaultView,
- detail: 1,
- screenX: 0, screenY: 0,
- clientX: 0, clientY: 0,
- ctrlKey: false, altKey: false, shiftKey: false, metaKey: false,
- button: 0,
- relatedTarget: null
- }
- };
-
- opts = opts || {};
-
- var t = this._create_types[type];
- var evt = doc.createEvent((t || "HTML") + "Events");
-
- let defaults = DEFAULTS[t || "HTML"];
+ canonicalKeys: deprecated("DOM.Event.canonicalKeys", { get: function canonicalKeys() DOM.Event.closure.canonicalKeys }),
+ create: deprecated("DOM.Event", function create() DOM.Event.apply(null, arguments)),
+ dispatch: deprecated("DOM.Event.dispatch", function dispatch() DOM.Event.dispatch.apply(DOM.Event, arguments)),
+ fromString: deprecated("DOM.Event.parse", { get: function fromString() DOM.Event.closure.parse }),
+ iterKeys: deprecated("DOM.Event.iterKeys", { get: function iterKeys() DOM.Event.closure.iterKeys }),
- let args = Object.keys(defaults)
- .map(function (k) k in opts ? opts[k] : defaults[k]);
-
- evt["init" + t + "Event"].apply(evt, args);
- return evt;
- },
-
- _create_types: Class.memoize(function () iter(
- {
- Mouse: "click mousedown mouseout mouseover mouseup",
- Key: "keydown keypress keyup",
- "": "change dactyl-input input submit"
- }
- ).map(function ([k, v]) v.split(" ").map(function (v) [v, k]))
- .flatten()
- .toObject()),
+ toString: function toString() {
+ if (!arguments.length)
+ return toString.supercall(this);
- /**
- * Converts a user-input string of keys into a canonical
- * representation.
- *
- * <C-A> maps to <C-a>, <C-S-a> maps to <C-S-A>
- * <C- > maps to <C-Space>, <S-a> maps to A
- * << maps to <lt><lt>
- *
- * <S-@> is preserved, as in Vim, to allow untypeable key-combinations
- * in macros.
- *
- * canonicalKeys(canonicalKeys(x)) == canonicalKeys(x) for all values
- * of x.
- *
- * @param {string} keys Messy form.
- * @param {boolean} unknownOk Whether unknown keys are passed
- * through rather than being converted to <lt>keyname>.
- * @default false
- * @returns {string} Canonical form.
- */
- canonicalKeys: function (keys, unknownOk) {
- if (arguments.length === 1)
- unknownOk = true;
- return events.fromString(keys, unknownOk).map(events.closure.toString).join("");
+ deprecated.warn(toString, "toString", "DOM.Event.stringify");
+ return DOM.Event.stringify.apply(DOM.Event, arguments);
},
- iterKeys: function (keys) iter(function () {
- let match, re = /<.*?>?>|[^<]/g;
- while (match = re.exec(keys))
- yield match[0];
- }()),
-
- /**
- * Dispatches an event to an element as if it were a native event.
- *
- * @param {Node} target The DOM node to which to dispatch the event.
- * @param {Event} event The event to dispatch.
- */
- dispatch: Class.memoize(function ()
- util.haveGecko("2b")
- ? function dispatch(target, event, extra) {
- try {
- this.feedingEvent = extra;
- if (target instanceof Element)
- // This causes a crash on Gecko<2.0, it seems.
- return (target.ownerDocument || target.document || target).defaultView
- .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
- .dispatchDOMEventViaPresShell(target, event, true);
- else {
- target.dispatchEvent(event);
- return !event.getPreventDefault();
- }
- }
- catch (e) {
- util.reportError(e);
- }
- finally {
- this.feedingEvent = null;
- }
- }
- : function dispatch(target, event, extra) {
- try {
- this.feedingEvent = extra;
- target.dispatchEvent(update(event, extra));
- }
- finally {
- this.feedingEvent = null;
- }
- }),
-
get defaultTarget() dactyl.focusedElement || content.document.body || document.documentElement,
- /**
- * Converts an event string into an array of pseudo-event objects.
- *
- * These objects can be used as arguments to events.toString or
- * events.create, though they are unlikely to be much use for other
- * purposes. They have many of the properties you'd expect to find on a
- * real event, but none of the methods.
- *
- * Also may contain two "special" parameters, .dactylString and
- * .dactylShift these are set for characters that can never by
- * typed, but may appear in mappings, for example <Nop> is passed as
- * dactylString, and dactylShift is set when a user specifies
- * <S-@> where @ is a non-case-changeable, non-space character.
- *
- * @param {string} keys The string to parse.
- * @param {boolean} unknownOk Whether unknown keys are passed
- * through rather than being converted to <lt>keyname>.
- * @default false
- * @returns {Array[Object]}
- */
- fromString: function (input, unknownOk) {
-
- if (arguments.length === 1)
- unknownOk = true;
-
- let out = [];
- for (let match in util.regexp.iterate(/<.*?>?>|[^<]|<(?!.*>)/g, input)) {
- let evt_str = match[0];
-
- let evt_obj = { ctrlKey: false, shiftKey: false, altKey: false, metaKey: false,
- keyCode: 0, charCode: 0, type: "keypress" };
-
- if (evt_str.length == 1) {
- evt_obj.charCode = evt_str.charCodeAt(0);
- evt_obj._keyCode = this._key_code[evt_str[0].toLowerCase()];
- evt_obj.shiftKey = evt_str !== evt_str.toLowerCase();
- }
- else {
- let [match, modifier, keyname] = evt_str.match(/^<((?:[*12CASM⌘]-)*)(.+?)>$/i) || [false, '', ''];
- modifier = Set(modifier.toUpperCase());
- keyname = keyname.toLowerCase();
- evt_obj.dactylKeyname = keyname;
- if (/^u[0-9a-f]+$/.test(keyname))
- keyname = String.fromCharCode(parseInt(keyname.substr(1), 16));
-
- if (keyname && (unknownOk || keyname.length == 1 || /mouse$/.test(keyname) ||
- this._key_code[keyname] || Set.has(this._pseudoKeys, keyname))) {
- evt_obj.globKey ="*" in modifier;
- evt_obj.ctrlKey ="C" in modifier;
- evt_obj.altKey ="A" in modifier;
- evt_obj.shiftKey ="S" in modifier;
- evt_obj.metaKey ="M" in modifier || "⌘" in modifier;
- evt_obj.dactylShift = evt_obj.shiftKey;
-
- if (keyname.length == 1) { // normal characters
- if (evt_obj.shiftKey)
- keyname = keyname.toUpperCase();
-
- evt_obj.charCode = keyname.charCodeAt(0);
- evt_obj._keyCode = this._key_code[keyname.toLowerCase()];
- }
- else if (Set.has(this._pseudoKeys, keyname)) {
- evt_obj.dactylString = "<" + this._key_key[keyname] + ">";
- }
- else if (/mouse$/.test(keyname)) { // mouse events
- evt_obj.type = (/2-/.test(modifier) ? "dblclick" : "click");
- evt_obj.button = ["leftmouse", "middlemouse", "rightmouse"].indexOf(keyname);
- delete evt_obj.keyCode;
- delete evt_obj.charCode;
- }
- else { // spaces, control characters, and <
- evt_obj.keyCode = this._key_code[keyname];
- evt_obj.charCode = 0;
- }
- }
- else { // an invalid sequence starting with <, treat as a literal
- out = out.concat(events.fromString("<lt>" + evt_str.substr(1)));
- continue;
- }
- }
-
- // TODO: make a list of characters that need keyCode and charCode somewhere
- if (evt_obj.keyCode == 32 || evt_obj.charCode == 32)
- evt_obj.charCode = evt_obj.keyCode = 32; // <Space>
- if (evt_obj.keyCode == 60 || evt_obj.charCode == 60)
- evt_obj.charCode = evt_obj.keyCode = 60; // <lt>
-
- evt_obj.modifiers = (evt_obj.ctrlKey && Ci.nsIDOMNSEvent.CONTROL_MASK)
- | (evt_obj.altKey && Ci.nsIDOMNSEvent.ALT_MASK)
- | (evt_obj.shiftKey && Ci.nsIDOMNSEvent.SHIFT_MASK)
- | (evt_obj.metaKey && Ci.nsIDOMNSEvent.META_MASK);
-
- out.push(evt_obj);
- }
- return out;
- },
-
- /**
- * Converts the specified event to a string in dactyl key-code
- * notation. Returns null for an unknown event.
- *
- * @param {Event} event
- * @returns {string}
- */
- toString: function toString(event) {
- if (!event)
- return toString.supercall(this);
-
- if (event.dactylString)
- return event.dactylString;
-
- let key = null;
- let modifier = "";
-
- if (event.globKey)
- modifier += "*-";
- if (event.ctrlKey)
- modifier += "C-";
- if (event.altKey)
- modifier += "A-";
- if (event.metaKey)
- modifier += "M-";
-
- if (/^key/.test(event.type)) {
- let charCode = event.type == "keyup" ? 0 : event.charCode; // Why? --Kris
- if (charCode == 0) {
- if (event.keyCode in this._code_key) {
- key = this._code_key[event.keyCode];
-
- if (event.shiftKey && (key.length > 1 || event.ctrlKey || event.altKey || event.metaKey) || event.dactylShift)
- modifier += "S-";
- else if (!modifier && key.length === 1)
- if (event.shiftKey)
- key = key.toUpperCase();
- else
- key = key.toLowerCase();
-
- if (!modifier && /^[a-z0-9]$/i.test(key))
- return key;
- }
- }
- // [Ctrl-Bug] special handling of mysterious <C-[>, <C-\\>, <C-]>, <C-^>, <C-_> bugs (OS/X)
- // (i.e., cntrl codes 27--31)
- // ---
- // For more information, see:
- // [*] Referenced mailing list msg: http://www.mozdev.org/pipermail/pentadactyl/2008-May/001548.html
- // [*] Mozilla bug 416227: event.charCode in keypress handler has unexpected values on Mac for Ctrl with chars in "[ ] _ \"
- // https://bugzilla.mozilla.org/show_bug.cgi?id=416227
- // [*] Mozilla bug 432951: Ctrl+'foo' doesn't seem same charCode as Meta+'foo' on Cocoa
- // https://bugzilla.mozilla.org/show_bug.cgi?id=432951
- // ---
- //
- // The following fixes are only activated if util.OS.isMacOSX.
- // Technically, they prevent mappings from <C-Esc> (and
- // <C-C-]> if your fancy keyboard permits such things<?>), but
- // these <C-control> mappings are probably pathological (<C-Esc>
- // certainly is on Windows), and so it is probably
- // harmless to remove the util.OS.isMacOSX if desired.
- //
- else if (util.OS.isMacOSX && event.ctrlKey && charCode >= 27 && charCode <= 31) {
- if (charCode == 27) { // [Ctrl-Bug 1/5] the <C-[> bug
- key = "Esc";
- modifier = modifier.replace("C-", "");
- }
- else // [Ctrl-Bug 2,3,4,5/5] the <C-\\>, <C-]>, <C-^>, <C-_> bugs
- key = String.fromCharCode(charCode + 64);
- }
- // a normal key like a, b, c, 0, etc.
- else if (charCode > 0) {
- key = String.fromCharCode(charCode);
-
- if (!/^[a-z0-9]$/i.test(key) && key in this._key_code) {
- // a named charCode key (<Space> and <lt>) space can be shifted, <lt> must be forced
- if ((key.match(/^\s$/) && event.shiftKey) || event.dactylShift)
- modifier += "S-";
-
- key = this._code_key[this._key_code[key]];
- }
- else {
- // a shift modifier is only allowed if the key is alphabetical and used in a C-A-M- mapping in the uppercase,
- // or if the shift has been forced for a non-alphabetical character by the user while :map-ping
- if (key !== key.toLowerCase() && (event.ctrlKey || event.altKey || event.metaKey) || event.dactylShift)
- modifier += "S-";
- if (/^\s$/.test(key))
- key = let (s = charCode.toString(16)) "U" + "0000".substr(4 - s.length) + s;
- else if (modifier.length == 0)
- return key;
- }
- }
- if (key == null) {
- if (event.shiftKey)
- modifier += "S-";
- key = this._key_key[event.dactylKeyname] || event.dactylKeyname;
- }
- if (key == null)
- return null;
- }
- else if (event.type == "click" || event.type == "dblclick") {
- if (event.shiftKey)
- modifier += "S-";
- if (event.type == "dblclick")
- modifier += "2-";
- // TODO: triple and quadruple click
-
- switch (event.button) {
- case 0:
- key = "LeftMouse";
- break;
- case 1:
- key = "MiddleMouse";
- break;
- case 2:
- key = "RightMouse";
- break;
- }
- }
-
- if (key == null)
- return null;
-
- return "<" + modifier + key + ">";
- },
-
/**
* Returns true if there's a known native key handler for the given
* event in the given mode.
["key", key.toLowerCase()]);
}
- let accel = util.OS.isMacOSX ? "metaKey" : "ctrlKey";
+ let accel = config.OS.isMacOSX ? "metaKey" : "ctrlKey";
let access = iter({ 1: "shiftKey", 2: "ctrlKey", 4: "altKey", 8: "metaKey" })
.filter(function ([k, v]) this & k, prefs.get("ui.key.chromeAccess"))
},
events: {
- DOMMenuBarActive: function () {
- this._activeMenubar = true;
- if (modes.main != modes.MENU)
- modes.push(modes.MENU);
- },
-
- DOMMenuBarInactive: function () {
- this._activeMenubar = false;
- modes.remove(modes.MENU, true);
- },
-
blur: function onBlur(event) {
let elem = event.originalTarget;
+ if (DOM(elem).isEditable)
+ util.trapErrors("removeEditActionListener",
+ DOM(elem).editor, editor);
+
if (elem instanceof Window && services.focus.activeWindow == null
&& document.commandDispatcher.focusedWindow !== window) {
// Deals with circumstances where, after the main window
// TODO: Merge with onFocusChange
focus: function onFocus(event) {
let elem = event.originalTarget;
+ if (DOM(elem).isEditable)
+ util.trapErrors("addEditActionListener",
+ DOM(elem).editor, editor);
+
+ if (elem == window)
+ overlay.activeWindow = window;
+ overlay.setData(elem, "had-focus", true);
if (event.target instanceof Ci.nsIDOMXULTextBoxElement)
if (Events.isHidden(elem, true))
elem.blur();
let win = (elem.ownerDocument || elem).defaultView || elem;
- if (!(services.focus.getLastFocusMethod(win) & 0x7000)
+ if (!(services.focus.getLastFocusMethod(win) & 0x3000)
&& events.isContentNode(elem)
&& !buffer.focusAllowed(elem)
&& isinstance(elem, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, Window])) {
let duringFeed = this.duringFeed || [];
this.duringFeed = [];
try {
- if (this.feedingEvent)
- for (let [k, v] in Iterator(this.feedingEvent))
+ if (DOM.Event.feedingEvent)
+ for (let [k, v] in Iterator(DOM.Event.feedingEvent))
if (!(k in event))
event[k] = v;
- this.feedingEvent = null;
+ DOM.Event.feedingEvent = null;
- let key = events.toString(event);
+ let key = DOM.Event.stringify(event);
// Hack to deal with <BS> and so forth not dispatching input
// events
elem.dactylKeyPress = elem.value;
util.timeout(function () {
if (elem.dactylKeyPress !== undefined && elem.value !== elem.dactylKeyPress)
- events.dispatch(elem, events.create(elem.ownerDocument, "dactyl-input"));
+ DOM(elem).dactylInput();
elem.dactylKeyPress = undefined;
});
}
else
for (let event in values(duringFeed))
try {
- this.dispatch(event.originalTarget, event, event);
+ DOM.Event.dispatch(event.originalTarget, event, event);
}
catch (e) {
util.reportError(e);
this.keyEvents = [];
let pass = this.passing && !event.isMacro ||
- this.feedingEvent && this.feedingEvent.isReplay ||
+ DOM.Event.feedingEvent && DOM.Event.feedingEvent.isReplay ||
event.isReplay ||
modes.main == modes.PASS_THROUGH ||
modes.main == modes.QUOTE
!modes.passThrough && this.shouldPass(event) ||
!this.processor && event.type === "keydown"
&& options.get("passunknown").getKey(modes.main.allBases)
- && let (key = events.toString(event))
+ && let (key = DOM.Event.stringify(event))
!modes.main.allBases.some(
function (mode) mappings.hives.some(
function (hive) hive.get(mode, key) || hive.getCandidates(mode, key)));
+ events.dbg("ON " + event.type.toUpperCase() + " " + DOM.Event.stringify(event) +
+ " passing: " + this.passing + " " +
+ " pass: " + pass +
+ " replay: " + event.isReplay +
+ " macro: " + event.isMacro);
+
if (event.type === "keydown")
this.passing = pass;
- events.dbg("ON " + event.type.toUpperCase() + " " + this.toString(event) + " pass: " + pass + " replay: " + event.isReplay + " macro: " + event.isMacro);
-
// Prevents certain sites from transferring focus to an input box
// before we get a chance to process our key bindings on the
// "keypress" event.
for (; win; win = win != win.parent && win.parent) {
for (; elem instanceof Element; elem = elem.parentNode)
- elem.dactylFocusAllowed = true;
- win.document.dactylFocusAllowed = true;
- }
- },
-
- popupshown: function onPopupShown(event) {
- let elem = event.originalTarget;
- if (elem instanceof Ci.nsIAutoCompletePopup) {
- if (modes.main != modes.AUTOCOMPLETE)
- modes.push(modes.AUTOCOMPLETE);
+ overlay.setData(elem, "focus-allowed", true);
+ overlay.setData(win.document, "focus-allowed", true);
}
- else if (elem.localName !== "tooltip")
- if (Events.isHidden(elem)) {
- if (elem.hidePopup && Events.isHidden(elem.parentNode))
- elem.hidePopup();
- }
- else if (modes.main != modes.MENU)
- modes.push(modes.MENU);
- },
-
- popuphidden: function onPopupHidden(event) {
- if (window.gContextMenu == null && !this._activeMenubar)
- modes.remove(modes.MENU, true);
- modes.remove(modes.AUTOCOMPLETE);
},
resize: function onResize(event) {
dactyl.triggerObserver("fullscreen", this._fullscreen);
autocommands.trigger("Fullscreen", { url: this._fullscreen ? "on" : "off", state: this._fullscreen });
}
+ statusline.updateZoomLevel();
}
},
// argument "event" is deliberately not used, as i don't seem to have
// access to the real focus target
// Huh? --djk
- onFocusChange: function onFocusChange(event) {
+ onFocusChange: util.wrapCallback(function onFocusChange(event) {
function hasHTMLDocument(win) win && win.document && win.document instanceof HTMLDocument
if (dactyl.ignoreFocus)
return;
return;
if (isinstance(elem, [HTMLEmbedElement, HTMLEmbedElement])) {
- modes.push(modes.EMBED);
+ if (!modes.main.passthrough && modes.main != modes.EMBED)
+ modes.push(modes.EMBED);
return;
}
let haveInput = modes.stack.some(function (m) m.main.input);
- if (elem instanceof HTMLTextAreaElement
- || elem instanceof Element && util.computedStyle(elem).MozUserModify === "read-write"
- || elem == null && win && Editor.getEditor(win)) {
-
- if (modes.main == modes.VISUAL && elem.selectionEnd == elem.selectionStart)
- modes.pop();
-
+ if (DOM(elem || win).isEditable) {
if (!haveInput)
- if (options["insertmode"])
- modes.push(modes.INSERT);
- else {
- modes.push(modes.TEXT_EDIT);
- if (elem.selectionEnd - elem.selectionStart > 0)
- modes.push(modes.VISUAL);
- }
+ if (!isinstance(modes.main, [modes.INPUT, modes.TEXT_EDIT, modes.VISUAL]))
+ if (options["insertmode"])
+ modes.push(modes.INSERT);
+ else {
+ modes.push(modes.TEXT_EDIT);
+ if (elem.selectionEnd - elem.selectionStart > 0)
+ modes.push(modes.VISUAL);
+ }
if (hasHTMLDocument(win))
- buffer.lastInputField = elem;
+ buffer.lastInputField = elem || win;
return;
}
- if (Events.isInputElement(elem)) {
+ if (elem && Events.isInputElement(elem)) {
if (!haveInput)
modes.push(modes.INSERT);
if (elem == null && urlbar && urlbar.inputField == this._lastFocus)
util.threadYield(true); // Why? --Kris
- while (modes.main.ownsFocus && modes.topOfStack.params.ownsFocus != elem
+ while (modes.main.ownsFocus
+ && modes.topOfStack.params.ownsFocus != elem
+ && modes.topOfStack.params.ownsFocus != win
&& !modes.topOfStack.params.holdFocus)
modes.pop(null, { fromFocus: true });
}
if (modes.main.ownsFocus)
modes.topOfStack.params.ownsFocus = elem;
}
- },
+ }),
onSelectionChange: function onSelectionChange(event) {
+ // Ignore selection events caused by editor commands.
+ if (editor.inEditMap || modes.main == modes.OPERATOR)
+ return;
+
let controller = document.commandDispatcher.getControllerForCommand("cmd_copy");
let couldCopy = controller && controller.isCommandEnabled("cmd_copy");
- if (modes.main == modes.VISUAL) {
- if (!couldCopy)
- modes.pop(); // Really not ideal.
- }
- else if (couldCopy) {
- if (modes.main == modes.TEXT_EDIT && !options["insertmode"])
+ if (couldCopy) {
+ if (modes.main == modes.TEXT_EDIT)
modes.push(modes.VISUAL);
else if (modes.main == modes.CARET)
modes.push(modes.VISUAL);
shouldPass: function shouldPass(event)
!event.noremap && (!dactyl.focusedElement || events.isContentNode(dactyl.focusedElement)) &&
- options.get("passkeys").has(events.toString(event))
+ options.get("passkeys").has(DOM.Event.stringify(event))
}, {
ABORT: {},
KILL: true,
WAIT: null,
isEscape: function isEscape(event)
- let (key = isString(event) ? event : events.toString(event))
+ let (key = isString(event) ? event : DOM.Event.stringify(event))
key === "<Esc>" || key === "<C-[>",
isHidden: function isHidden(elem, aggressive) {
- if (util.computedStyle(elem).visibility !== "visible")
+ if (DOM(elem).style.visibility !== "visible")
return true;
if (aggressive)
},
isInputElement: function isInputElement(elem) {
- return elem instanceof HTMLInputElement && Set.has(util.editableInputs, elem.type) ||
- isinstance(elem, [HTMLEmbedElement,
- HTMLObjectElement, HTMLSelectElement,
- HTMLTextAreaElement,
- Ci.nsIDOMXULTextBoxElement]) ||
- elem instanceof Window && Editor.getEditor(elem);
+ return DOM(elem).isEditable ||
+ isinstance(elem, [HTMLEmbedElement, HTMLObjectElement,
+ HTMLSelectElement])
},
kill: function kill(event) {
event.preventDefault();
}
}, {
+ contexts: function initContexts(dactyl, modules, window) {
+ update(Events.prototype, {
+ hives: contexts.Hives("events", EventHive),
+ user: contexts.hives.events.user,
+ builtin: contexts.hives.events.builtin
+ });
+ },
+
commands: function () {
commands.add(["delmac[ros]"],
"Delete macros",
mappings.add([modes.MAIN],
["<C-z>", "<pass-all-keys>"], "Temporarily ignore all " + config.appName + " key bindings",
- function () { modes.push(modes.PASS_THROUGH); });
+ function () {
+ if (modes.main != modes.PASS_THROUGH)
+ modes.push(modes.PASS_THROUGH);
+ });
mappings.add([modes.MAIN, modes.PASS_THROUGH, modes.QUOTE],
["<C-v>", "<pass-next-key>"], "Pass through next key",
mappings.add([modes.COMMAND],
["q", "<record-macro>"], "Record a key sequence into a macro",
function ({ arg }) {
+ util.assert(arg == null || /^[a-z]$/i.test(arg));
events._macroKeys.pop();
events.recording = arg;
},
get pass() (this.flush(), this.pass),
- keepQuotes: true,
-
- setter: function (values) {
- values.forEach(function (filter) {
+ parse: function parse() {
+ let value = parse.superapply(this, arguments);
+ value.forEach(function (filter) {
let vals = Option.splitList(filter.result);
- filter.keys = events.fromString(vals[0]).map(events.closure.toString);
+ filter.keys = DOM.Event.parse(vals[0]).map(DOM.Event.closure.stringify);
- filter.commandKeys = vals.slice(1).map(events.closure.canonicalKeys);
+ filter.commandKeys = vals.slice(1).map(DOM.Event.closure.canonicalKeys);
filter.inputKeys = filter.commandKeys.filter(bind("test", /^<[ACM]-/));
});
+ return value;
+ },
+
+ keepQuotes: true,
+
+ setter: function (value) {
this.flush();
- return values;
+ return value;
}
});
options.add(["timeoutlen", "tmol"],
"Maximum time (milliseconds) to wait for a longer key command when a shorter one exists",
"number", 1000);
- },
- sanitizer: function () {
- sanitizer.addItem("macros", {
- description: "Saved macros",
- persistent: true,
- action: function (timespan, host) {
- if (!host)
- for (let [k, m] in events._macros)
- if (timespan.contains(m.timeRecorded * 1000))
- events._macros.remove(k);
- }
- });
}
});