whitespace
[conkeror/arlinius.git] / modules / input.js
blob4c9b6a086324fc693461fd41afa5037391519abe
1 /**
2  * (C) Copyright 2004-2007 Shawn Betts
3  * (C) Copyright 2007-2009 John J. Foerch
4  * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
5  *
6  * Use, modification, and distribution are subject to the terms specified in the
7  * COPYING file.
8 **/
10 require("window.js");
11 require("keymap.js");
12 require("interactive.js");
14 define_variable("key_bindings_ignore_capslock", false,
15     "When true, the case of characters in key bindings will be based "+
16     "only on whether shift was pressed--upper-case if yes, lower-case if "+
17     "no.  Effectively, this overrides the capslock key.  This option has "+
18     "no effect on ordinary typing in input fields.");
20 define_variable("keyboard_key_sequence_help_timeout", 0,
21     "Delay (in millseconds) before the current key sequence prefix is "+
22     "displayed in the minibuffer.");
25 /**
26  * event_clone is used to make a copy of an event which is safe to keep
27  * references to, because it will not reference any DOM trees directly
28  * or indirectly.
29  *
30  * A pertinent question would be, is this needed?  Are there instances
31  * in Conkeror when an event reference is stored across a navigation
32  * boundary or buffer/window closing?
33  */
34 function event_clone (event) {
35     this.type = event.type;
36     this.keyCode = event.keyCode;
37     this.charCode = event.charCode;
38     this.ctrlKey = event.ctrlKey;
39     this.metaKey = event.metaKey;
40     this.altKey = event.altKey;
41     this.shiftKey = event.shiftKey;
42     this.sticky_modifiers = event.sticky_modifiers;
46 /**
47  * event_kill stops an event from being processed by any further handlers.
48  */
49 function event_kill (event) {
50     event.preventDefault();
51     event.stopPropagation();
55 /**
56  * command_event is a special event type that tells input_handle_sequence
57  * to run the given command.
58  */
59 function command_event (command) {
60     this.type = "command";
61     this.command = command;
65 /**
66  * input_state makes an object that holds the state of a single key sequence.
67  * As a small measure of efficiency, these objects get recycled from one
68  * sequence to the next.
69  */
70 function input_state (window) {
71     this.window = window;
72     this.fallthrough = {};
74 input_state.prototype = {
75     continuation: null,
77     // If this is non-null, it is used instead of the current buffer's
78     // keymap.  Used for minibuffer.
79     override_keymap: null //this should be stored in the minibuffer state, right?
83 function input_help_timer_clear (window) {
84     if (window.input.help_timer != null) {
85         timer_cancel(window.input.help_timer);
86         window.input.help_timer = null;
87     }
91 function input_show_partial_sequence (window, I) {
92     if (window.input.help_displayed)
93         window.minibuffer.show(I.key_sequence.join(" "));
94     else {
95         window.input.help_timer = call_after_timeout(function () {
96             window.minibuffer.show(I.key_sequence.join(" "));
97             window.input.help_displayed = true;
98             window.input.help_timer = null;
99         }, keyboard_key_sequence_help_timeout);
100     }
104 define_window_local_hook("keypress_hook", RUN_HOOK_UNTIL_SUCCESS,
105     "A run-until-success hook available for special keypress "+
106     "handling.  Handlers receive as arguments the window, an "+
107     "interactive context, and the real keypress event.  The "+
108     "handler is responsible for stopping event propagation, if "+
109     "that is desired.");
113  * get_current_keymaps returns the keymap stack for the current focus
114  * context of the given window.  This is the top-level keymap stack, not
115  * the stack that represents any on-going key sequence.
116  */
117 function get_current_keymaps (window) {
118     if (window.input.current.override_keymap)
119         return [window.input.current.override_keymap];
120     if (window.buffers.current.override_keymaps[0] !== undefined)
121         return window.buffers.current.override_keymaps;
122     return window.buffers.current.keymaps;
127  * input_handle_sequence is the main handler for all event types which
128  * can be part of a sequence.  It is a coroutine procedure which gets
129  * started and resumed by various EventListeners, some of which have
130  * additional, special tasks.
131  */
132 function input_handle_sequence (event) {
133     try {
134         var window = this;
135         var state = window.input.current;
136         state.continuation = yield CONTINUATION;
137         var I = new interactive_context(window.buffers.current);
138         I.key_sequence = [];
139         I.sticky_modifiers = 0;
140         var keymaps = get_current_keymaps(window);
141 sequence:
142         while (true) {
143             switch (event.type) {
144             case "keydown":
145                 //try the fallthrough predicates in our current keymap
146                 if (keymap_lookup_fallthrough(keymaps[keymaps.length - 1], event)) {
147                     //XXX: need to take account of modifers, too!
148                     state.fallthrough[event.keyCode] = true;
149                 } else
150                     event_kill(event);
151                 break;
152             case "keypress":
153                 window.minibuffer.clear();
154                 window.input.help_displayed = false;
155                 input_help_timer_clear(window);
157                 // prepare the clone
158                 var clone = new event_clone(event);
159                 clone.sticky_modifiers = I.sticky_modifiers;
160                 I.sticky_modifiers = 0;
161                 if (key_bindings_ignore_capslock && clone.charCode) {
162                     let c = String.fromCharCode(clone.charCode);
163                     if (clone.shiftKey)
164                         clone.charCode = c.toUpperCase().charCodeAt(0);
165                     else
166                         clone.charCode = c.toLowerCase().charCodeAt(0);
167                 }
169                 // make the combo string
170                 var combo = format_key_combo(clone);
171                 var canabort = I.key_sequence.push(combo) > 1;
172                 I.combo = combo;
173                 I.event = clone;
175                 // make active keymaps visible to commands
176                 I.keymaps = keymaps;
178                 if (keypress_hook.run(window, I, event))
179                     break;
181                 var overlay_keymap = I.overlay_keymap;
183                 var binding =
184                     (canabort && keymap_lookup([sequence_abort_keymap], combo, event)) ||
185                     (overlay_keymap && keymap_lookup([overlay_keymap], combo, event)) ||
186                     keymap_lookup(keymaps, combo, event) ||
187                     keymap_lookup([sequence_help_keymap], combo, event);
189                 // kill event for any unbound key, or any bound key which
190                 // is not marked fallthrough
191                 if (!binding || !binding.fallthrough)
192                     event_kill(event);
194                 if (binding) {
195                     if (binding.browser_object != null)
196                         I.binding_browser_object = binding.browser_object;
197                     if (binding.constructor == Array) {
198                         keymaps = binding;
199                         input_show_partial_sequence(window, I);
200                     } else if (binding.command) {
201                         let command = binding.command;
202                         if (I.repeat == command)
203                             command = binding.repeat;
204                         yield call_interactively(I, command);
205                         if (typeof command == "string" &&
206                             interactive_commands.get(command).prefix)
207                         {
208                             keymaps = get_current_keymaps(window); //back to top keymap
209                             input_show_partial_sequence(window, I);
210                             if (binding.repeat)
211                                 I.repeat = command;
212                         } else {
213                             break sequence;
214                         }
215                     } else {
216                         break sequence; //reachable by keypress fallthroughs
217                     }
218                 } else {
219                     window.minibuffer.message(I.key_sequence.join(" ") + " is undefined");
220                     break sequence;
221                 }
222                 break;
223             case "command":
224                 let (command = event.command) {
225                     window.input.help_displayed = false;
226                     input_help_timer_clear(window);
227                     window.minibuffer.clear();
228                     yield call_interactively(I, command);
229                     if (! interactive_commands.get(command).prefix)
230                         break sequence;
231                 }
232                 break;
233             }
234             // should we expect more events?
235             event = null;
236             event = yield SUSPEND;
237         }
238     } catch (e) {
239         dump_error(e);
240     } finally {
241         // sequence is done
242         delete state.continuation;
243     }
247 function input_handle_keydown (event) {
248     if (event.keyCode == 0 ||
249         event.keyCode == vk_name_to_keycode.shift ||
250         event.keyCode == vk_name_to_keycode.control ||
251         event.keyCode == vk_name_to_keycode.alt ||
252         event.keyCode == vk_name_to_keycode.caps_lock)
253         return event_kill(event);
254     var window = this;
255     var state = window.input.current;
256     if (state.continuation)
257         state.continuation(event);
258     else
259         co_call(input_handle_sequence.call(window, event));
263 function input_handle_keypress (event) {
264     if (event.keyCode == 0 && event.charCode == 0 ||
265         event.keyCode == vk_name_to_keycode.caps_lock)
266         return event_kill(event);
267     var window = this;
268     var state = window.input.current;
269     if (state.continuation)
270         state.continuation(event);
271     else
272         co_call(input_handle_sequence.call(window, event));
276 function input_handle_keyup (event) {
277     if (event.keyCode == 0 ||
278         event.keyCode == vk_name_to_keycode.shift ||
279         event.keyCode == vk_name_to_keycode.control ||
280         event.keyCode == vk_name_to_keycode.alt ||
281         event.keyCode == vk_name_to_keycode.caps_lock)
282         return event_kill(event);
283     var window = this;
284     var state = window.input.current;
285     if (state.fallthrough[event.keyCode])
286         delete state.fallthrough[event.keyCode];
287     else
288         event_kill(event);
292 // handler for command_event special events
293 function input_handle_command (event) {
294     var window = this;
295     var state = window.input.current;
296     if (state.continuation)
297         state.continuation(event);
298     else
299         co_call(input_handle_sequence.call(window, event));
303 // handler for special abort event
304 function input_sequence_abort (message) {
305     var window = this;
306     window.input.help_displayed = false;
307     input_help_timer_clear(window);
308     window.minibuffer.clear();
309     if (message)
310         window.minibuffer.show(message);
311     delete window.input.current.continuation;
315 function input_initialize_window (window) {
316     window.input = [new input_state(window)]; // a stack of states
317     window.input.__defineGetter__("current", function () {
318         return this[this.length - 1];
319     });
320     window.input.begin_recursion = function () {
321         this.push(new input_state(window));
322     };
323     window.input.end_recursion = function () {
324         this.pop();
325     };
326     window.input.help_timer = null;
327     window.input.help_displayed = false;
328     //window.addEventListener("keydown", input_handle_keydown, true);
329     window.addEventListener("keypress", input_handle_keypress, true);
330     //window.addEventListener("keyup", input_handle_keyup, true);
331     //TO-DO: mousedown, mouseup, click, dblclick
334 add_hook("window_initialize_hook", input_initialize_window);
337 interactive("sequence-abort",
338     "Abort an ongoing key sequence.",
339     function (I) { I.minibuffer.message("abort sequence"); });