]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/content/key-processors.js
Import 1.0rc1 supporting Firefox up to 11.*
[dactyl.git] / common / content / key-processors.js
diff --git a/common/content/key-processors.js b/common/content/key-processors.js
new file mode 100644 (file)
index 0000000..e1ba321
--- /dev/null
@@ -0,0 +1,324 @@
+// Copyright (c) 2008-2011 by Kris Maglione <maglione.k at Gmail>
+//
+// 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, DOM.Event.stringify(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(DOM.Event.closure.stringify));
+            if (list.length > length)
+                events.dbg("REFEED: " + list.slice(length).filter(function (e) e.type === "keypress").map(DOM.Event.closure.stringify));
+
+            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 = DOM.Event.stringify(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 = DOM.Event.stringify(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, {
+                command: this.command,
+                count: this.count,
+                keyEvents: events.keyEvents,
+                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,
+            keyEvents: events.keyEvents,
+            keypressEvents: this.parent.events.concat(this.events)
+        };
+        args[this.argName] = this.command;
+
+        return this.execute(this.map, args);
+    }
+});
+