2 * (C) Copyright 2004-2007 Shawn Betts
3 * (C) Copyright 2007-2009 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.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;
47 * event_kill stops an event from being processed by any further handlers.
49 function event_kill (event) {
50 event.preventDefault();
51 event.stopPropagation();
56 * command_event is a special event type that tells input_handle_sequence
57 * to run the given command.
59 function command_event (command) {
60 this.type = "command";
61 this.command = command;
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.
70 function input_state (window) {
72 this.fallthrough = {};
74 input_state.prototype = {
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;
91 function input_show_partial_sequence (window, I) {
92 if (window.input.help_displayed)
93 window.minibuffer.show(I.key_sequence.join(" "));
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);
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 "+
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.
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.
132 function input_handle_sequence (event) {
135 var state = window.input.current;
136 state.continuation = yield CONTINUATION;
137 var I = new interactive_context(window.buffers.current);
139 I.sticky_modifiers = 0;
140 var keymaps = get_current_keymaps(window);
143 switch (event.type) {
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;
153 window.minibuffer.clear();
154 window.input.help_displayed = false;
155 input_help_timer_clear(window);
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);
164 clone.charCode = c.toUpperCase().charCodeAt(0);
166 clone.charCode = c.toLowerCase().charCodeAt(0);
169 // make the combo string
170 var combo = format_key_combo(clone);
171 I.key_sequence.push(combo);
175 if (keypress_hook.run(window, I, event))
178 var overlay_keymap = I.overlay_keymap;
180 var binding = (overlay_keymap && keymap_lookup([overlay_keymap], combo, event)) ||
181 keymap_lookup(keymaps, combo, event);
183 // kill event for any unbound key, or any bound key which
184 // is not marked fallthrough
185 if (!binding || !binding.fallthrough)
189 if (binding.browser_object != null)
190 I.binding_browser_object = binding.browser_object;
191 if (binding.constructor == Array) {
193 input_show_partial_sequence(window, I);
194 } else if (binding.command) {
195 let command = binding.command;
196 if (I.repeat == command)
197 command = binding.repeat;
198 yield call_interactively(I, command);
199 if (typeof command == "string" &&
200 interactive_commands.get(command).prefix)
202 keymaps = get_current_keymaps(window); //back to top keymap
203 input_show_partial_sequence(window, I);
210 break sequence; //reachable by keypress fallthroughs
213 window.minibuffer.message(I.key_sequence.join(" ") + " is undefined");
218 let (command = event.command) {
219 window.input.help_displayed = false;
220 input_help_timer_clear(window);
221 window.minibuffer.clear();
222 yield call_interactively(I, command);
223 if (! interactive_commands.get(command).prefix)
228 // should we expect more events?
230 event = yield SUSPEND;
236 delete state.continuation;
241 function input_handle_keydown (event) {
242 if (event.keyCode == 0 ||
243 event.keyCode == vk_name_to_keycode.shift ||
244 event.keyCode == vk_name_to_keycode.control ||
245 event.keyCode == vk_name_to_keycode.alt ||
246 event.keyCode == vk_name_to_keycode.caps_lock)
247 return event_kill(event);
249 var state = window.input.current;
250 if (state.continuation)
251 state.continuation(event);
253 co_call(input_handle_sequence.call(window, event));
257 function input_handle_keypress (event) {
258 if (event.keyCode == 0 && event.charCode == 0 ||
259 event.keyCode == vk_name_to_keycode.caps_lock)
260 return event_kill(event);
262 var state = window.input.current;
263 if (state.continuation)
264 state.continuation(event);
266 co_call(input_handle_sequence.call(window, event));
270 function input_handle_keyup (event) {
271 if (event.keyCode == 0 ||
272 event.keyCode == vk_name_to_keycode.shift ||
273 event.keyCode == vk_name_to_keycode.control ||
274 event.keyCode == vk_name_to_keycode.alt ||
275 event.keyCode == vk_name_to_keycode.caps_lock)
276 return event_kill(event);
278 var state = window.input.current;
279 if (state.fallthrough[event.keyCode])
280 delete state.fallthrough[event.keyCode];
286 // handler for command_event special events
287 function input_handle_command (event) {
289 var state = window.input.current;
290 if (state.continuation)
291 state.continuation(event);
293 co_call(input_handle_sequence.call(window, event));
297 // handler for special abort event
298 function input_sequence_abort (message) {
300 window.input.help_displayed = false;
301 input_help_timer_clear(window);
302 window.minibuffer.clear();
304 window.minibuffer.show(message);
305 delete window.input.current.continuation;
309 function input_initialize_window (window) {
310 window.input = [new input_state(window)]; // a stack of states
311 window.input.__defineGetter__("current", function () {
312 return this[this.length - 1];
314 window.input.begin_recursion = function () {
315 this.push(new input_state(window));
317 window.input.end_recursion = function () {
320 window.input.help_timer = null;
321 window.input.help_displayed = false;
322 //window.addEventListener("keydown", input_handle_keydown, true);
323 window.addEventListener("keypress", input_handle_keypress, true);
324 //window.addEventListener("keyup", input_handle_keyup, true);
325 //TO-DO: mousedown, mouseup, click, dblclick
328 add_hook("window_initialize_hook", input_initialize_window);