Further adapt debian webjumps as suggested by John J. Foerch
[conkeror.git] / modules / input.js
blob68f17c9315328e5ad88e63b2bda879e97ad19d39
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 in_module(null);
12 require("window.js");
13 require("keymap.js");
14 require("interactive.js");
16 define_variable("key_bindings_ignore_capslock", false,
17     "When true, the case of characters in key bindings will be based "+
18     "only on whether shift was pressed--upper-case if yes, lower-case if "+
19     "no.  Effectively, this overrides the capslock key.  This option has "+
20     "no effect on ordinary typing in input fields.");
22 define_variable("keyboard_key_sequence_help_timeout", 0,
23     "Delay (in millseconds) before the current key sequence prefix is "+
24     "displayed in the minibuffer.");
27 /**
28  * event_clone is used to make a copy of an event which is safe to keep
29  * references to, because it will not reference any DOM trees directly
30  * or indirectly.
31  *
32  * A pertinent question would be, is this needed?  Are there instances
33  * in Conkeror when an event reference is stored across a navigation
34  * boundary or buffer/window closing?
35  */
36 function event_clone (event) {
37     this.type = event.type;
38     this.keyCode = event.keyCode;
39     this.charCode = event.charCode;
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 (window) {
73     this.window = window;
74     this.fallthrough = {};
76 input_state.prototype = {
77     continuation: null,
79     // If this is non-null, it is used instead of the current buffer's
80     // keymap.  Used for minibuffer.
81     override_keymap: null //this should be stored in the minibuffer state, right?
85 function input_help_timer_clear (window) {
86     if (window.input.help_timer != null) {
87         timer_cancel(window.input.help_timer);
88         window.input.help_timer = null;
89     }
93 function input_show_partial_sequence (window, I) {
94     if (window.input.help_displayed)
95         window.minibuffer.show(I.key_sequence.join(" "));
96     else {
97         window.input.help_timer = call_after_timeout(function () {
98             window.minibuffer.show(I.key_sequence.join(" "));
99             window.input.help_displayed = true;
100             window.input.help_timer = null;
101         }, keyboard_key_sequence_help_timeout);
102     }
106 define_window_local_hook("keypress_hook", RUN_HOOK_UNTIL_SUCCESS,
107     "A run-until-success hook available for special keypress "+
108     "handling.  Handlers receive as arguments the window, an "+
109     "interactive context, and the real keypress event.  The "+
110     "handler is responsible for stopping event propagation, if "+
111     "that is desired.");
115  * get_current_keymaps returns the keymap stack for the current focus
116  * context of the given window.  This is the top-level keymap stack, not
117  * the stack that represents any on-going key sequence.
118  */
119 function get_current_keymaps (window) {
120     if (window.input.current.override_keymap)
121         return [window.input.current.override_keymap];
122     if (window.buffers.current.override_keymaps[0] !== undefined)
123         return window.buffers.current.override_keymaps;
124     return window.buffers.current.keymaps;
129  * input_handle_sequence is the main handler for all event types which
130  * can be part of a sequence.  It is a coroutine procedure which gets
131  * started and resumed by various EventListeners, some of which have
132  * additional, special tasks.
133  */
134 function input_handle_sequence (event) {
135     try {
136         var window = this;
137         var state = window.input.current;
138         state.continuation = yield CONTINUATION;
139         var I = new interactive_context(window.buffers.current);
140         I.key_sequence = [];
141         I.sticky_modifiers = 0;
142         var keymaps = get_current_keymaps(window);
143 sequence:
144         while (true) {
145             switch (event.type) {
146             case "keydown":
147                 //try the fallthrough predicates in our current keymap
148                 if (keymap_lookup_fallthrough(keymaps[keymaps.length - 1], event)) {
149                     //XXX: need to take account of modifers, too!
150                     state.fallthrough[event.keyCode] = true;
151                 } else
152                     event_kill(event);
153                 break;
154             case "keypress":
155                 window.minibuffer.clear();
156                 window.input.help_displayed = false;
157                 input_help_timer_clear(window);
159                 // prepare the clone
160                 var clone = new event_clone(event);
161                 clone.sticky_modifiers = I.sticky_modifiers;
162                 I.sticky_modifiers = 0;
163                 if (key_bindings_ignore_capslock && clone.charCode) {
164                     let c = String.fromCharCode(clone.charCode);
165                     if (clone.shiftKey)
166                         clone.charCode = c.toUpperCase().charCodeAt(0);
167                     else
168                         clone.charCode = c.toLowerCase().charCodeAt(0);
169                 }
171                 // make the combo string
172                 var combo = format_key_combo(clone);
173                 var canabort = I.key_sequence.push(combo) > 1;
174                 I.combo = combo;
175                 I.event = clone;
177                 // make active keymaps visible to commands
178                 I.keymaps = keymaps;
180                 if (keypress_hook.run(window, I, event))
181                     break;
183                 var overlay_keymap = I.overlay_keymap;
185                 var binding =
186                     (canabort && keymap_lookup([sequence_abort_keymap], combo, event)) ||
187                     (overlay_keymap && keymap_lookup([overlay_keymap], combo, event)) ||
188                     keymap_lookup(keymaps, combo, event) ||
189                     keymap_lookup([sequence_help_keymap], combo, event);
191                 // kill event for any unbound key, or any bound key which
192                 // is not marked fallthrough
193                 if (!binding || !binding.fallthrough)
194                     event_kill(event);
196                 if (binding) {
197                     if (binding.browser_object != null)
198                         I.binding_browser_object = binding.browser_object;
199                     if (binding.constructor == Array) {
200                         keymaps = binding;
201                         input_show_partial_sequence(window, I);
202                     } else if (binding.command) {
203                         let command = binding.command;
204                         if (I.repeat == command)
205                             command = binding.repeat;
206                         yield call_interactively(I, command);
207                         if (typeof command == "string" &&
208                             interactive_commands.get(command).prefix)
209                         {
210                             keymaps = get_current_keymaps(window); //back to top keymap
211                             input_show_partial_sequence(window, I);
212                             if (binding.repeat)
213                                 I.repeat = command;
214                         } else {
215                             break sequence;
216                         }
217                     } else {
218                         break sequence; //reachable by keypress fallthroughs
219                     }
220                 } else {
221                     window.minibuffer.message(I.key_sequence.join(" ") + " is undefined");
222                     break sequence;
223                 }
224                 break;
225             case "command":
226                 let (command = event.command) {
227                     window.input.help_displayed = false;
228                     input_help_timer_clear(window);
229                     window.minibuffer.clear();
230                     yield call_interactively(I, command);
231                     if (! interactive_commands.get(command).prefix)
232                         break sequence;
233                 }
234                 break;
235             }
236             // should we expect more events?
237             event = null;
238             event = yield SUSPEND;
239         }
240     } catch (e) {
241         dump_error(e);
242     } finally {
243         // sequence is done
244         delete state.continuation;
245     }
249 function input_handle_keydown (event) {
250     if (event.keyCode == 0 ||
251         event.keyCode == vk_name_to_keycode.shift ||
252         event.keyCode == vk_name_to_keycode.control ||
253         event.keyCode == vk_name_to_keycode.alt ||
254         event.keyCode == vk_name_to_keycode.caps_lock)
255         return event_kill(event);
256     var window = this;
257     var state = window.input.current;
258     if (state.continuation)
259         state.continuation(event);
260     else
261         co_call(input_handle_sequence.call(window, event));
265 function input_handle_keypress (event) {
266     if (event.keyCode == 0 && event.charCode == 0 ||
267         event.keyCode == vk_name_to_keycode.caps_lock)
268         return event_kill(event);
269     var window = this;
270     var state = window.input.current;
271     if (state.continuation)
272         state.continuation(event);
273     else
274         co_call(input_handle_sequence.call(window, event));
278 function input_handle_keyup (event) {
279     if (event.keyCode == 0 ||
280         event.keyCode == vk_name_to_keycode.shift ||
281         event.keyCode == vk_name_to_keycode.control ||
282         event.keyCode == vk_name_to_keycode.alt ||
283         event.keyCode == vk_name_to_keycode.caps_lock)
284         return event_kill(event);
285     var window = this;
286     var state = window.input.current;
287     if (state.fallthrough[event.keyCode])
288         delete state.fallthrough[event.keyCode];
289     else
290         event_kill(event);
294 // handler for command_event special events
295 function input_handle_command (event) {
296     var window = this;
297     var state = window.input.current;
298     if (typeof event == 'string')
299         event = new command_event(event);
300     if (state.continuation)
301         state.continuation(event);
302     else
303         co_call(input_handle_sequence.call(window, event));
307 // handler for special abort event
308 function input_sequence_abort (message) {
309     var window = this;
310     window.input.help_displayed = false;
311     input_help_timer_clear(window);
312     window.minibuffer.clear();
313     if (message)
314         window.minibuffer.show(message);
315     delete window.input.current.continuation;
319 function input_initialize_window (window) {
320     window.input = [new input_state(window)]; // a stack of states
321     window.input.__defineGetter__("current", function () {
322         return this[this.length - 1];
323     });
324     window.input.begin_recursion = function () {
325         this.push(new input_state(window));
326     };
327     window.input.end_recursion = function () {
328         this.pop();
329     };
330     window.input.help_timer = null;
331     window.input.help_displayed = false;
332     //window.addEventListener("keydown", input_handle_keydown, true);
333     window.addEventListener("keypress", input_handle_keypress, true);
334     //window.addEventListener("keyup", input_handle_keyup, true);
335     //TO-DO: mousedown, mouseup, click, dblclick
338 add_hook("window_initialize_hook", input_initialize_window);
341 interactive("sequence-abort",
342     "Abort an ongoing key sequence.",
343     function (I) { I.minibuffer.message("abort sequence"); });
345 provide("input");