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
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.");
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
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?
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;
49 * event_kill stops an event from being processed by any further handlers.
51 function event_kill (event) {
52 event.preventDefault();
53 event.stopPropagation();
58 * command_event is a special event type that tells input_handle_sequence
59 * to run the given command.
61 function command_event (command) {
62 this.type = "command";
63 this.command = command;
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.
72 function input_state (window) {
74 this.fallthrough = {};
76 input_state.prototype = {
77 constructor: input_state,
82 function input_help_timer_clear (window) {
83 if (window.input.help_timer != null) {
84 timer_cancel(window.input.help_timer);
85 window.input.help_timer = null;
90 function input_show_partial_sequence (window, I) {
91 if (window.input.help_displayed)
92 window.minibuffer.show(I.key_sequence.join(" "));
94 window.input.help_timer = call_after_timeout(function () {
95 window.minibuffer.show(I.key_sequence.join(" "));
96 window.input.help_displayed = true;
97 window.input.help_timer = null;
98 }, keyboard_key_sequence_help_timeout);
103 define_window_local_hook("keypress_hook", RUN_HOOK_UNTIL_SUCCESS,
104 "A run-until-success hook available for special keypress "+
105 "handling. Handlers receive as arguments the window, an "+
106 "interactive context, and the real keypress event. The "+
107 "handler is responsible for stopping event propagation, if "+
112 * get_current_keymaps returns the keymap stack for the current focus
113 * context of the given window. This is the top-level keymap stack, not
114 * the stack that represents any on-going key sequence.
116 function get_current_keymaps (window) {
117 var m = window.minibuffer;
118 var s = m.current_state;
119 if (m.active && s.keymap)
121 if (window.buffers.current.override_keymaps[0] !== undefined)
122 return window.buffers.current.override_keymaps;
123 return window.buffers.current.keymaps;
128 * input_handle_sequence is the main handler for all event types which
129 * can be part of a sequence. It is a coroutine procedure which gets
130 * started and resumed by various EventListeners, some of which have
131 * additional, special tasks.
133 function input_handle_sequence (event) {
136 var state = window.input.current;
137 state.continuation = yield CONTINUATION;
138 var I = new interactive_context(window.buffers.current);
140 I.sticky_modifiers = 0;
141 var keymaps = get_current_keymaps(window);
144 switch (event.type) {
146 //try the fallthrough predicates in our current keymap
147 if (keymap_lookup_fallthrough(keymaps[keymaps.length - 1], event)) {
148 //XXX: need to take account of modifers, too!
149 state.fallthrough[event.keyCode] = true;
154 window.minibuffer.clear();
155 window.input.help_displayed = false;
156 input_help_timer_clear(window);
159 var clone = new event_clone(event);
160 clone.sticky_modifiers = I.sticky_modifiers;
161 I.sticky_modifiers = 0;
162 if (key_bindings_ignore_capslock && clone.charCode) {
163 let c = String.fromCharCode(clone.charCode);
165 clone.charCode = c.toUpperCase().charCodeAt(0);
167 clone.charCode = c.toLowerCase().charCodeAt(0);
170 // make the combo string
171 var combo = format_key_combo(clone);
172 var canabort = I.key_sequence.push(combo) > 1;
176 // make active keymaps visible to commands
179 if (keypress_hook.run(window, I, event))
182 var overlay_keymap = I.overlay_keymap;
185 (canabort && keymap_lookup([sequence_abort_keymap], combo, event)) ||
186 (overlay_keymap && keymap_lookup([overlay_keymap], combo, event)) ||
187 keymap_lookup(keymaps, combo, event) ||
188 keymap_lookup([sequence_help_keymap], combo, event);
190 // kill event for any unbound key, or any bound key which
191 // is not marked fallthrough
192 if (!binding || !binding.fallthrough)
196 if (binding.browser_object != null)
197 I.binding_browser_object = binding.browser_object;
198 if (binding.constructor == Array) {
200 input_show_partial_sequence(window, I);
201 } else if (binding.command) {
202 let command = binding.command;
203 if (I.repeat == command)
204 command = binding.repeat;
205 yield call_interactively(I, command);
206 if (typeof command == "string" &&
207 interactive_commands.get(command).prefix)
209 keymaps = get_current_keymaps(window); //back to top keymap
210 input_show_partial_sequence(window, I);
217 break sequence; //reachable by keypress fallthroughs
220 window.minibuffer.message(I.key_sequence.join(" ") + " is undefined");
225 let (command = event.command) {
226 window.input.help_displayed = false;
227 input_help_timer_clear(window);
228 window.minibuffer.clear();
229 yield call_interactively(I, command);
230 if (! interactive_commands.get(command).prefix)
235 // should we expect more events?
237 event = yield SUSPEND;
243 delete state.continuation;
248 function input_handle_keydown (event) {
249 if (event.keyCode == 0 ||
250 event.keyCode == vk_name_to_keycode.shift ||
251 event.keyCode == vk_name_to_keycode.control ||
252 event.keyCode == vk_name_to_keycode.alt ||
253 event.keyCode == vk_name_to_keycode.caps_lock)
254 return event_kill(event);
256 var state = window.input.current;
257 if (state.continuation)
258 state.continuation(event);
260 co_call(input_handle_sequence.call(window, event));
264 function input_handle_keypress (event) {
265 if (event.keyCode == 0 && event.charCode == 0 ||
266 event.keyCode == vk_name_to_keycode.caps_lock)
267 return event_kill(event);
269 var state = window.input.current;
270 if (state.continuation)
271 state.continuation(event);
273 co_call(input_handle_sequence.call(window, event));
277 function input_handle_keyup (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);
285 var state = window.input.current;
286 if (state.fallthrough[event.keyCode])
287 delete state.fallthrough[event.keyCode];
293 // handler for command_event special events
294 function input_handle_command (event) {
296 var state = window.input.current;
297 if (typeof event == 'string')
298 event = new command_event(event);
299 if (state.continuation)
300 state.continuation(event);
302 co_call(input_handle_sequence.call(window, event));
306 // handler for special abort event
307 function input_sequence_abort (message) {
309 window.input.help_displayed = false;
310 input_help_timer_clear(window);
311 window.minibuffer.clear();
313 window.minibuffer.show(message);
314 delete window.input.current.continuation;
318 function input_initialize_window (window) {
319 window.input = [new input_state(window)]; // a stack of states
320 window.input.__defineGetter__("current", function () {
321 return this[this.length - 1];
323 window.input.begin_recursion = function () {
324 this.push(new input_state(window));
326 window.input.end_recursion = function () {
329 window.input.help_timer = null;
330 window.input.help_displayed = false;
331 //window.addEventListener("keydown", input_handle_keydown, true);
332 window.addEventListener("keypress", input_handle_keypress, true);
333 //window.addEventListener("keyup", input_handle_keyup, true);
334 //TO-DO: mousedown, mouseup, click, dblclick
337 add_hook("window_initialize_hook", input_initialize_window);
340 interactive("sequence-abort",
341 "Abort an ongoing key sequence.",
342 function (I) { I.minibuffer.message("abort sequence"); });