2 * (C) Copyright 2004-2007 Shawn Betts
3 * (C) Copyright 2007-2010 John J. Foerch
4 * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
6 * Use, modification, and distribution are subject to the terms specified in the
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.");
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
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?
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.superKey = modifiers.s.in_event_p(event);
45 this.sticky_modifiers = event.sticky_modifiers;
50 * event_kill stops an event from being processed by any further handlers.
52 function event_kill (event) {
53 event.preventDefault();
54 event.stopPropagation();
59 * command_event is a special event type that tells input_handle_sequence
60 * to run the given command.
62 function command_event (command) {
63 this.type = "command";
64 this.command = command;
69 * input_state makes an object that holds the state of a single key sequence.
70 * As a small measure of efficiency, these objects get recycled from one
71 * sequence to the next.
73 function input_state () {
74 this.fallthrough = {};
76 input_state.prototype = {
77 constructor: input_state,
84 * input_stack is a stack of input_states, which is to say a stack of
85 * recursed sequences. input recursion happens, for example, when a
86 * minibuffer read takes place in the middle of another sequence.
88 function input_stack () {
89 this.array = [new input_state()];
91 input_stack.prototype = {
92 constructor: input_stack,
96 help_displayed: false,
98 toString: function () {
99 return "[input_stack ("+this.array.length+")]";
102 return this.array[this.array.length - 1];
104 begin_recursion: function () {
105 this.array.push(new input_state());
107 end_recursion: function () {
113 function input_help_timer_clear (window) {
114 if (window.input.help_timer != null) {
115 timer_cancel(window.input.help_timer);
116 window.input.help_timer = null;
121 function input_show_partial_sequence (window, I) {
122 if (window.input.help_displayed)
123 window.minibuffer.show(I.key_sequence.join(" "));
125 window.input.help_timer = call_after_timeout(function () {
126 window.minibuffer.show(I.key_sequence.join(" "));
127 window.input.help_displayed = true;
128 window.input.help_timer = null;
129 }, keyboard_key_sequence_help_timeout);
134 define_window_local_hook("keypress_hook", RUN_HOOK_UNTIL_SUCCESS,
135 "A run-until-success hook available for special keypress "+
136 "handling. Handlers receive as arguments the window, an "+
137 "interactive context, and the real keypress event. The "+
138 "handler is responsible for stopping event propagation, if "+
143 * get_current_keymaps returns the keymap stack for the current focus
144 * context of the given window. This is the top-level keymap stack, not
145 * the stack that represents any on-going key sequence.
147 function get_current_keymaps (window) {
148 var m = window.minibuffer;
149 var s = m.current_state;
150 if (m.active && s.keymaps)
152 return window.buffers.current.keymaps;
157 * input_handle_sequence is the main handler for all event types which
158 * can be part of a sequence. It is a coroutine procedure which gets
159 * started and resumed by various EventListeners, some of which have
160 * additional, special tasks.
162 function input_handle_sequence (event) {
165 var state = window.input.current;
166 state.continuation = yield CONTINUATION;
167 var I = new interactive_context(window.buffers.current);
169 I.sticky_modifiers = 0;
170 var keymaps = get_current_keymaps(window);
173 switch (event.type) {
175 //try the fallthrough predicates in our current keymap
176 if (keymap_lookup_fallthrough(keymaps[keymaps.length - 1], event)) {
177 //XXX: need to take account of modifers, too!
178 state.fallthrough[event.keyCode] = true;
184 window.minibuffer.clear();
185 window.input.help_displayed = false;
186 input_help_timer_clear(window);
189 var clone = new event_clone(event);
190 clone.sticky_modifiers = I.sticky_modifiers;
191 I.sticky_modifiers = 0;
192 if (key_bindings_ignore_capslock && clone.charCode) {
193 let c = String.fromCharCode(clone.charCode);
195 clone.charCode = c.toUpperCase().charCodeAt(0);
197 clone.charCode = c.toLowerCase().charCodeAt(0);
200 // make the combo string
201 var combo = format_key_combo(clone);
202 var canabort = I.key_sequence.push(combo) > 1;
206 // make active keymaps visible to commands
209 if (keypress_hook.run(window, I, event))
212 var overlay_keymap = I.overlay_keymap;
215 (canabort && keymap_lookup([sequence_abort_keymap], combo, event)) ||
216 (overlay_keymap && keymap_lookup([overlay_keymap], combo, event)) ||
217 keymap_lookup(keymaps, combo, event) ||
218 keymap_lookup([sequence_help_keymap], combo, event);
220 // kill event for any unbound key, or any bound key which
221 // is not marked fallthrough
222 if (!binding || !binding.fallthrough)
226 if (binding.browser_object !== undefined)
227 I.binding_browser_object = binding.browser_object;
228 if (array_p(binding)) {
230 input_show_partial_sequence(window, I);
231 } else if (binding.command) {
232 let command = binding.command;
233 if (I.repeat == command)
234 command = binding.repeat;
235 yield call_interactively(I, command);
236 if (typeof command == "string" &&
237 interactive_commands[command].prefix)
239 keymaps = get_current_keymaps(window); //back to top keymap
240 input_show_partial_sequence(window, I);
247 break sequence; //reachable by keypress fallthroughs
250 window.minibuffer.message(I.key_sequence.join(" ") + " is undefined");
255 let (command = event.command) {
256 window.input.help_displayed = false;
257 input_help_timer_clear(window);
258 window.minibuffer.clear();
259 yield call_interactively(I, command);
260 if (! interactive_commands[command].prefix)
265 // should we expect more events?
267 event = yield SUSPEND;
273 delete state.continuation;
278 function input_handle_keydown (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);
286 var state = window.input.current;
287 if (state.continuation)
288 state.continuation(event);
290 co_call(input_handle_sequence.call(window, event));
294 function input_handle_keypress (event) {
295 if (event.keyCode == 0 && event.charCode == 0 ||
296 event.keyCode == vk_name_to_keycode.caps_lock)
297 return event_kill(event);
299 var state = window.input.current;
300 if (state.continuation)
301 state.continuation(event);
303 co_call(input_handle_sequence.call(window, event));
307 function input_handle_keyup (event) {
308 if (event.keyCode == 0 ||
309 event.keyCode == vk_name_to_keycode.shift ||
310 event.keyCode == vk_name_to_keycode.control ||
311 event.keyCode == vk_name_to_keycode.alt ||
312 event.keyCode == vk_name_to_keycode.caps_lock)
313 return event_kill(event);
315 var state = window.input.current;
316 if (state.fallthrough[event.keyCode])
317 delete state.fallthrough[event.keyCode];
323 function input_handle_appcommand (event) {
325 var state = window.input.current;
326 if (state.continuation)
327 state.continuation(event);
329 co_call(input_handle_sequence.call(window, event));
333 // handler for command_event special events
334 function input_handle_command (event) {
336 var state = window.input.current;
337 if (typeof event == 'string')
338 event = new command_event(event);
339 if (state.continuation)
340 state.continuation(event);
342 co_call(input_handle_sequence.call(window, event));
346 // handler for special abort event
347 function input_sequence_abort (message) {
349 window.input.help_displayed = false;
350 input_help_timer_clear(window);
351 window.minibuffer.clear();
353 window.minibuffer.show(message);
354 delete window.input.current.continuation;
358 function input_initialize_window (window) {
359 window.input = new input_stack();
360 //window.addEventListener("keydown", input_handle_keydown, true);
361 window.addEventListener("keypress", input_handle_keypress, true);
362 //window.addEventListener("keyup", input_handle_keyup, true);
363 //TO-DO: mousedown, mouseup, click, dblclick
364 window.addEventListener("AppCommand", input_handle_appcommand, true);
367 add_hook("window_initialize_hook", input_initialize_window);
370 interactive("sequence-abort",
371 "Abort an ongoing key sequence.",
372 function (I) { I.minibuffer.message("abort sequence"); });