]> git.donarmstrong.com Git - dactyl.git/blob - common/content/key-processors.js
Import 1.0rc1 supporting Firefox up to 11.*
[dactyl.git] / common / content / key-processors.js
1 // Copyright (c) 2008-2011 by Kris Maglione <maglione.k at Gmail>
2 //
3 // This work is licensed for reuse under an MIT license. Details are
4 // given in the LICENSE.txt file included with this file.
5 /* use strict */
6
7 /** @scope modules */
8
9 var ProcessorStack = Class("ProcessorStack", {
10     init: function (mode, hives, builtin) {
11         this.main = mode.main;
12         this._actions = [];
13         this.actions = [];
14         this.buffer = "";
15         this.events = [];
16
17         events.dbg("STACK " + mode);
18
19         let main = { __proto__: mode.main, params: mode.params };
20         this.modes = array([mode.params.keyModes, main, mode.main.allBases.slice(1)]).flatten().compact();
21
22         if (builtin)
23             hives = hives.filter(function (h) h.name === "builtin");
24
25         this.processors = this.modes.map(function (m) hives.map(function (h) KeyProcessor(m, h)))
26                                     .flatten().array;
27         this.ownsBuffer = !this.processors.some(function (p) p.main.ownsBuffer);
28
29         for (let [i, input] in Iterator(this.processors)) {
30             let params = input.main.params;
31
32             if (params.preExecute)
33                 input.preExecute = params.preExecute;
34
35             if (params.postExecute)
36                 input.postExecute = params.postExecute;
37
38             if (params.onKeyPress && input.hive === mappings.builtin)
39                 input.fallthrough = function fallthrough(events) {
40                     return params.onKeyPress(events) === false ? Events.KILL : Events.PASS;
41                 };
42             }
43
44         let hive = options.get("passkeys")[this.main.input ? "inputHive" : "commandHive"];
45         if (!builtin && hive.active && (!dactyl.focusedElement || events.isContentNode(dactyl.focusedElement)))
46             this.processors.unshift(KeyProcessor(modes.BASE, hive));
47     },
48
49     passUnknown: Class.Memoize(function () options.get("passunknown").getKey(this.modes)),
50
51     notify: function () {
52         events.dbg("NOTIFY()");
53         events.keyEvents = [];
54         events.processor = null;
55         if (!this.execute(undefined, true)) {
56             events.processor = this;
57             events.keyEvents = this.keyEvents;
58         }
59     },
60
61     _result: function (result) (result === Events.KILL         ? "KILL"  :
62                                 result === Events.PASS         ? "PASS"  :
63                                 result === Events.PASS_THROUGH ? "PASS_THROUGH"  :
64                                 result === Events.ABORT        ? "ABORT" :
65                                 callable(result) ? result.toSource().substr(0, 50) : result),
66
67     execute: function execute(result, force) {
68         events.dbg("EXECUTE(" + this._result(result) + ", " + force + ") events:" + this.events.length
69                    + " processors:" + this.processors.length + " actions:" + this.actions.length);
70
71         let processors = this.processors;
72         let length = 1;
73
74         if (force)
75             this.processors = [];
76
77         if (this.ownsBuffer)
78             statusline.inputBuffer = this.processors.length ? this.buffer : "";
79
80         if (!this.processors.some(function (p) !p.extended) && this.actions.length) {
81             // We have matching actions and no processors other than
82             // those waiting on further arguments. Execute actions as
83             // long as they continue to return PASS.
84
85             for (var action in values(this.actions)) {
86                 while (callable(action)) {
87                     length = action.eventLength;
88                     action = dactyl.trapErrors(action);
89                     events.dbg("ACTION RES: " + length + " " + this._result(action));
90                 }
91                 if (action !== Events.PASS)
92                     break;
93             }
94
95             // Result is the result of the last action. Unless it's
96             // PASS, kill any remaining argument processors.
97             result = action !== undefined ? action : Events.KILL;
98             if (action !== Events.PASS)
99                 this.processors.length = 0;
100         }
101         else if (this.processors.length) {
102             // We're still waiting on the longest matching processor.
103             // Kill the event, set a timeout to give up waiting if applicable.
104
105             result = Events.KILL;
106             if (options["timeout"] && (this.actions.length || events.hasNativeKey(this.events[0], this.main, this.passUnknown)))
107                 this.timer = services.Timer(this, options["timeoutlen"], services.Timer.TYPE_ONE_SHOT);
108         }
109         else if (result !== Events.KILL && !this.actions.length &&
110                  !(this.events[0].isReplay || this.passUnknown
111                    || this.modes.some(function (m) m.passEvent(this), this.events[0]))) {
112             // No patching processors, this isn't a fake, pass-through
113             // event, we're not in pass-through mode, and we're not
114             // choosing to pass unknown keys. Kill the event and beep.
115
116             result = Events.ABORT;
117             if (!Events.isEscape(this.events.slice(-1)[0]))
118                 dactyl.beep();
119             events.feedingKeys = false;
120         }
121         else if (result === undefined)
122             // No matching processors, we're willing to pass this event,
123             // and we don't have a default action from a processor. Just
124             // pass the event.
125             result = Events.PASS;
126
127         events.dbg("RESULT: " + length + " " + this._result(result) + "\n\n");
128
129         if (result !== Events.PASS || this.events.length > 1)
130             if (result !== Events.ABORT || !this.events[0].isReplay)
131                 Events.kill(this.events[this.events.length - 1]);
132
133         if (result === Events.PASS_THROUGH || result === Events.PASS && this.passUnknown)
134             events.passing = true;
135
136         if (result === Events.PASS_THROUGH && this.keyEvents.length)
137             events.dbg("PASS_THROUGH:\n\t" + this.keyEvents.map(function (e) [e.type, DOM.Event.stringify(e)]).join("\n\t"));
138
139         if (result === Events.PASS_THROUGH)
140             events.feedevents(null, this.keyEvents, { skipmap: true, isMacro: true, isReplay: true });
141         else {
142             let list = this.events.filter(function (e) e.getPreventDefault() && !e.dactylDefaultPrevented);
143
144             if (result === Events.PASS)
145                 events.dbg("PASS THROUGH: " + list.slice(0, length).filter(function (e) e.type === "keypress").map(DOM.Event.closure.stringify));
146             if (list.length > length)
147                 events.dbg("REFEED: " + list.slice(length).filter(function (e) e.type === "keypress").map(DOM.Event.closure.stringify));
148
149             if (result === Events.PASS)
150                 events.feedevents(null, list.slice(0, length), { skipmap: true, isMacro: true, isReplay: true });
151             if (list.length > length && this.processors.length === 0)
152                 events.feedevents(null, list.slice(length));
153         }
154
155         return this.processors.length === 0;
156     },
157
158     process: function process(event) {
159         if (this.timer)
160             this.timer.cancel();
161
162         let key = DOM.Event.stringify(event);
163         this.events.push(event);
164         if (this.keyEvents)
165             this.keyEvents.push(event);
166
167         this.buffer += key;
168
169         let actions = [];
170         let processors = [];
171
172         events.dbg("PROCESS(" + key + ") skipmap: " + event.skipmap + " macro: " + event.isMacro + " replay: " + event.isReplay);
173
174         for (let [i, input] in Iterator(this.processors)) {
175             let res = input.process(event);
176             if (res !== Events.ABORT)
177                 var result = res;
178
179             events.dbg("RES: " + input + " " + this._result(res));
180
181             if (res === Events.KILL)
182                 break;
183
184             if (callable(res))
185                 actions.push(res);
186
187             if (res === Events.WAIT || input.waiting)
188                 processors.push(input);
189             if (isinstance(res, KeyProcessor))
190                 processors.push(res);
191         }
192
193         events.dbg("RESULT: " + event.getPreventDefault() + " " + this._result(result));
194         events.dbg("ACTIONS: " + actions.length + " " + this.actions.length);
195         events.dbg("PROCESSORS:", processors, "\n");
196
197         this._actions = actions;
198         this.actions = actions.concat(this.actions);
199
200         for (let action in values(actions))
201             if (!("eventLength" in action))
202                 action.eventLength = this.events.length;
203
204         if (result === Events.KILL)
205             this.actions = [];
206         else if (!this.actions.length && !processors.length)
207             for (let input in values(this.processors))
208                 if (input.fallthrough) {
209                     if (result === Events.KILL)
210                         break;
211                     result = dactyl.trapErrors(input.fallthrough, input, this.events);
212                 }
213
214         this.processors = processors;
215
216         return this.execute(result, options["timeout"] && options["timeoutlen"] === 0);
217     }
218 });
219
220 var KeyProcessor = Class("KeyProcessor", {
221     init: function init(main, hive) {
222         this.main = main;
223         this.events = [];
224         this.hive = hive;
225         this.wantCount = this.main.count;
226     },
227
228     get toStringParams() [this.main.name, this.hive.name],
229
230     countStr: "",
231     command: "",
232     get count() this.countStr ? Number(this.countStr) : this.main.params.count || null,
233
234     append: function append(event) {
235         this.events.push(event);
236         let key = DOM.Event.stringify(event);
237
238         if (this.wantCount && !this.command &&
239                 (this.countStr ? /^[0-9]$/ : /^[1-9]$/).test(key))
240             this.countStr += key;
241         else
242             this.command += key;
243         return this.events;
244     },
245
246     process: function process(event) {
247         this.append(event);
248         this.waiting = false;
249         return this.onKeyPress(event);
250     },
251
252     execute: function execute(map, args)
253         let (self = this)
254             function execute() {
255                 if (self.preExecute)
256                     self.preExecute.apply(self, args);
257
258                 args.self = self.main.params.mappingSelf || self.main.mappingSelf || map;
259                 let res = map.execute.call(map, args);
260
261                 if (self.postExecute)
262                     self.postExecute.apply(self, args);
263                 return res;
264             },
265
266     onKeyPress: function onKeyPress(event) {
267         if (event.skipmap)
268             return Events.ABORT;
269
270         if (!this.command)
271             return Events.WAIT;
272
273         var map = this.hive.get(this.main, this.command);
274         this.waiting = this.hive.getCandidates(this.main, this.command);
275         if (map) {
276             if (map.arg)
277                 return KeyArgProcessor(this, map, false, "arg");
278             else if (map.motion)
279                 return KeyArgProcessor(this, map, true, "motion");
280
281             return this.execute(map, {
282                 command: this.command,
283                 count: this.count,
284                 keyEvents: events.keyEvents,
285                 keypressEvents: this.events
286             });
287         }
288
289         if (!this.waiting)
290             return this.main.insert ? Events.PASS : Events.ABORT;
291
292         return Events.WAIT;
293     }
294 });
295
296 var KeyArgProcessor = Class("KeyArgProcessor", KeyProcessor, {
297     init: function init(input, map, wantCount, argName) {
298         init.supercall(this, input.main, input.hive);
299         this.map = map;
300         this.parent = input;
301         this.argName = argName;
302         this.wantCount = wantCount;
303     },
304
305     extended: true,
306
307     onKeyPress: function onKeyPress(event) {
308         if (Events.isEscape(event))
309             return Events.KILL;
310         if (!this.command)
311             return Events.WAIT;
312
313         let args = {
314             command: this.parent.command,
315             count:   this.count || this.parent.count,
316             keyEvents: events.keyEvents,
317             keypressEvents: this.parent.events.concat(this.events)
318         };
319         args[this.argName] = this.command;
320
321         return this.execute(this.map, args);
322     }
323 });
324