Debian package: Add support for xulrunner 18
[conkeror.git] / modules / input.js
blob0fe3f6a82b89d4144b9cf95298dacd715f4e948d
1 /**
2  * (C) Copyright 2004-2007 Shawn Betts
3  * (C) Copyright 2007-2010 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.button = event.button;
39     this.command = event.command;
40     this.ctrlKey = event.ctrlKey;
41     this.metaKey = event.metaKey;
42     this.altKey = event.altKey;
43     this.shiftKey = event.shiftKey;
44     this.sticky_modifiers = event.sticky_modifiers;
48 /**
49  * event_kill stops an event from being processed by any further handlers.
50  */
51 function event_kill (event) {
52     event.preventDefault();
53     event.stopPropagation();
57 /**
58  * command_event is a special event type that tells input_handle_sequence
59  * to run the given command.
60  */
61 function command_event (command) {
62     this.type = "command";
63     this.command = command;
67 /**
68  * input_state makes an object that holds the state of a single key sequence.
69  * As a small measure of efficiency, these objects get recycled from one
70  * sequence to the next.
71  */
72 function input_state () {
73     this.fallthrough = {};
75 input_state.prototype = {
76     constructor: input_state,
77     continuation: null,
78     fallthrough: null
82 /**
83  * input_stack is a stack of input_states, which is to say a stack of
84  * recursed sequences.  input recursion happens, for example, when a
85  * minibuffer read takes place in the middle of another sequence.
86  */
87 function input_stack () {
88     this.array = [new input_state()];
90 input_stack.prototype = {
91     constructor: input_stack,
93     array: null,
94     help_timer: null,
95     help_displayed: false,
97     toString: function () {
98         return "[input_stack ("+this.array.length+")]";
99     },
100     get current () {
101         return this.array[this.array.length - 1];
102     },
103     begin_recursion: function () {
104         this.array.push(new input_state());
105     },
106     end_recursion: function () {
107         this.array.pop();
108     }
112 function input_help_timer_clear (window) {
113     if (window.input.help_timer != null) {
114         timer_cancel(window.input.help_timer);
115         window.input.help_timer = null;
116     }
120 function input_show_partial_sequence (window, I) {
121     if (window.input.help_displayed)
122         window.minibuffer.show(I.key_sequence.join(" "));
123     else {
124         window.input.help_timer = call_after_timeout(function () {
125             window.minibuffer.show(I.key_sequence.join(" "));
126             window.input.help_displayed = true;
127             window.input.help_timer = null;
128         }, keyboard_key_sequence_help_timeout);
129     }
133 define_window_local_hook("keypress_hook", RUN_HOOK_UNTIL_SUCCESS,
134     "A run-until-success hook available for special keypress "+
135     "handling.  Handlers receive as arguments the window, an "+
136     "interactive context, and the real keypress event.  The "+
137     "handler is responsible for stopping event propagation, if "+
138     "that is desired.");
142  * get_current_keymaps returns the keymap stack for the current focus
143  * context of the given window.  This is the top-level keymap stack, not
144  * the stack that represents any on-going key sequence.
145  */
146 function get_current_keymaps (window) {
147     var m = window.minibuffer;
148     var s = m.current_state;
149     if (m.active && s.keymaps)
150         return s.keymaps;
151     return window.buffers.current.keymaps;
156  * input_handle_sequence is the main handler for all event types which
157  * can be part of a sequence.  It is a coroutine procedure which gets
158  * started and resumed by various EventListeners, some of which have
159  * additional, special tasks.
160  */
161 function input_handle_sequence (event) {
162     try {
163         var window = this;
164         var state = window.input.current;
165         state.continuation = yield CONTINUATION;
166         var I = new interactive_context(window.buffers.current);
167         I.key_sequence = [];
168         I.sticky_modifiers = 0;
169         var keymaps = get_current_keymaps(window);
170 sequence:
171         while (true) {
172             switch (event.type) {
173             case "keydown":
174                 //try the fallthrough predicates in our current keymap
175                 if (keymap_lookup_fallthrough(keymaps[keymaps.length - 1], event)) {
176                     //XXX: need to take account of modifers, too!
177                     state.fallthrough[event.keyCode] = true;
178                 } else
179                     event_kill(event);
180                 break;
181             case "keypress":
182             case "AppCommand":
183                 window.minibuffer.clear();
184                 window.input.help_displayed = false;
185                 input_help_timer_clear(window);
187                 // prepare the clone
188                 var clone = new event_clone(event);
189                 clone.sticky_modifiers = I.sticky_modifiers;
190                 I.sticky_modifiers = 0;
191                 if (key_bindings_ignore_capslock && clone.charCode) {
192                     let c = String.fromCharCode(clone.charCode);
193                     if (clone.shiftKey)
194                         clone.charCode = c.toUpperCase().charCodeAt(0);
195                     else
196                         clone.charCode = c.toLowerCase().charCodeAt(0);
197                 }
199                 // make the combo string
200                 var combo = format_key_combo(clone);
201                 var canabort = I.key_sequence.push(combo) > 1;
202                 I.combo = combo;
203                 I.event = clone;
205                 // make active keymaps visible to commands
206                 I.keymaps = keymaps;
208                 if (keypress_hook.run(window, I, event))
209                     break;
211                 var overlay_keymap = I.overlay_keymap;
213                 var binding =
214                     (canabort && keymap_lookup([sequence_abort_keymap], combo, event)) ||
215                     (overlay_keymap && keymap_lookup([overlay_keymap], combo, event)) ||
216                     keymap_lookup(keymaps, combo, event) ||
217                     keymap_lookup([sequence_help_keymap], combo, event);
219                 // kill event for any unbound key, or any bound key which
220                 // is not marked fallthrough
221                 if (!binding || !binding.fallthrough)
222                     event_kill(event);
224                 if (binding) {
225                     if (binding.browser_object !== undefined)
226                         I.binding_browser_object = binding.browser_object;
227                     if (binding.constructor == Array) {
228                         keymaps = binding;
229                         input_show_partial_sequence(window, I);
230                     } else if (binding.command) {
231                         let command = binding.command;
232                         if (I.repeat == command)
233                             command = binding.repeat;
234                         yield call_interactively(I, command);
235                         if (typeof command == "string" &&
236                             interactive_commands[command].prefix)
237                         {
238                             keymaps = get_current_keymaps(window); //back to top keymap
239                             input_show_partial_sequence(window, I);
240                             if (binding.repeat)
241                                 I.repeat = command;
242                         } else {
243                             break sequence;
244                         }
245                     } else {
246                         break sequence; //reachable by keypress fallthroughs
247                     }
248                 } else {
249                     window.minibuffer.message(I.key_sequence.join(" ") + " is undefined");
250                     break sequence;
251                 }
252                 break;
253             case "command":
254                 let (command = event.command) {
255                     window.input.help_displayed = false;
256                     input_help_timer_clear(window);
257                     window.minibuffer.clear();
258                     yield call_interactively(I, command);
259                     if (! interactive_commands[command].prefix)
260                         break sequence;
261                 }
262                 break;
263             }
264             // should we expect more events?
265             event = null;
266             event = yield SUSPEND;
267         }
268     } catch (e) {
269         dump_error(e);
270     } finally {
271         // sequence is done
272         delete state.continuation;
273     }
277 function input_handle_keydown (event) {
278     if (event.keyCode == 0 ||
279         event.keyCode == vk_name_to_keycode.shift ||
280         event.keyCode == vk_name_to_keycode.control ||
281         event.keyCode == vk_name_to_keycode.alt ||
282         event.keyCode == vk_name_to_keycode.caps_lock)
283         return event_kill(event);
284     var window = this;
285     var state = window.input.current;
286     if (state.continuation)
287         state.continuation(event);
288     else
289         co_call(input_handle_sequence.call(window, event));
293 function input_handle_keypress (event) {
294     if (event.keyCode == 0 && event.charCode == 0 ||
295         event.keyCode == vk_name_to_keycode.caps_lock)
296         return event_kill(event);
297     var window = this;
298     var state = window.input.current;
299     if (state.continuation)
300         state.continuation(event);
301     else
302         co_call(input_handle_sequence.call(window, event));
306 function input_handle_keyup (event) {
307     if (event.keyCode == 0 ||
308         event.keyCode == vk_name_to_keycode.shift ||
309         event.keyCode == vk_name_to_keycode.control ||
310         event.keyCode == vk_name_to_keycode.alt ||
311         event.keyCode == vk_name_to_keycode.caps_lock)
312         return event_kill(event);
313     var window = this;
314     var state = window.input.current;
315     if (state.fallthrough[event.keyCode])
316         delete state.fallthrough[event.keyCode];
317     else
318         event_kill(event);
322 function input_handle_appcommand (event) {
323     var window = this;
324     var state = window.input.current;
325     if (state.continuation)
326         state.continuation(event);
327     else
328         co_call(input_handle_sequence.call(window, event));
332 // handler for command_event special events
333 function input_handle_command (event) {
334     var window = this;
335     var state = window.input.current;
336     if (typeof event == 'string')
337         event = new command_event(event);
338     if (state.continuation)
339         state.continuation(event);
340     else
341         co_call(input_handle_sequence.call(window, event));
345 // handler for special abort event
346 function input_sequence_abort (message) {
347     var window = this;
348     window.input.help_displayed = false;
349     input_help_timer_clear(window);
350     window.minibuffer.clear();
351     if (message)
352         window.minibuffer.show(message);
353     delete window.input.current.continuation;
357 function input_initialize_window (window) {
358     window.input = new input_stack();
359     //window.addEventListener("keydown", input_handle_keydown, true);
360     window.addEventListener("keypress", input_handle_keypress, true);
361     //window.addEventListener("keyup", input_handle_keyup, true);
362     //TO-DO: mousedown, mouseup, click, dblclick
363     window.addEventListener("AppCommand", input_handle_appcommand, true);
366 add_hook("window_initialize_hook", input_initialize_window);
369 interactive("sequence-abort",
370     "Abort an ongoing key sequence.",
371     function (I) { I.minibuffer.message("abort sequence"); });
373 provide("input");