3 var keycode_to_name = [];
4 var name_to_keycode = new Object();
5 var shifted_keycode_to_name = [];
6 var name_to_shifted_keycode = [];
8 /* Generate keyCode to string and string to keyCode mapping tables. */
9 function generate_key_tables()
11 var KeyEvent = Ci.nsIDOMKeyEvent;
12 function map(code, name) {
13 keycode_to_name[code] = name;
14 name_to_keycode[name] = code;
17 var prefix = "DOM_VK_";
20 /* Check if this is a key binding */
21 if (i.substr(0, prefix.length) == prefix)
23 var name = i.substr(prefix.length).toLowerCase();
24 var code = KeyEvent[i];
25 keycode_to_name[code] = name;
26 name_to_keycode[name] = code;
30 /* Add additional mappings for improved display */
31 map(KeyEvent.DOM_VK_BACK_SLASH, "\\");
32 map(KeyEvent.DOM_VK_OPEN_BRACKET, "[");
33 map(KeyEvent.DOM_VK_CLOSE_BRACKET, "]");
34 map(KeyEvent.DOM_VK_SEMICOLON, ";");
35 map(KeyEvent.DOM_VK_COMMA, ",");
36 map(KeyEvent.DOM_VK_PERIOD, ".");
37 map(KeyEvent.DOM_VK_SLASH, "/");
38 map(KeyEvent.DOM_VK_SUBTRACT, "-");
39 map(KeyEvent.DOM_VK_PAGE_DOWN, "pgdn");
40 map(KeyEvent.DOM_VK_PAGE_UP, "pgup");
41 map(KeyEvent.DOM_VK_EQUALS, "=");
44 /* Add additional shifted keycodes for improved display */
45 function smap(code, name) {
46 shifted_keycode_to_name[code] = name;
47 name_to_shifted_keycode[name] = code;
50 for (var i = 0; i < 26; ++i) {
51 var code = KeyEvent.DOM_VK_A + i;
52 var name = String.fromCharCode(i + "A".charCodeAt(0));
55 smap(KeyEvent.DOM_VK_SEMICOLON, ":");
56 smap(KeyEvent.DOM_VK_1, "!");
57 smap(KeyEvent.DOM_VK_2, "@");
58 smap(KeyEvent.DOM_VK_3, "#");
59 smap(KeyEvent.DOM_VK_4, "$");
60 smap(KeyEvent.DOM_VK_5, "%");
61 smap(KeyEvent.DOM_VK_6, "^");
62 smap(KeyEvent.DOM_VK_7, "&");
63 smap(KeyEvent.DOM_VK_8, "*");
64 smap(KeyEvent.DOM_VK_9, "(");
65 smap(KeyEvent.DOM_VK_0, ")");
66 smap(KeyEvent.DOM_VK_EQUALS, "+");
67 smap(KeyEvent.DOM_VK_SUBTRACT, "_");
68 smap(KeyEvent.DOM_VK_COMMA, "<");
69 smap(KeyEvent.DOM_VK_PERIOD, ">");
70 smap(KeyEvent.DOM_VK_SLASH, "?");
74 generate_key_tables();
80 const MOD_SHIFT = 0x4;
82 // Note: For elements of the modifier_names array, an element at index
83 // i should correspond to the modifier mask (1 << i).
84 var modifier_names = ["C", "M", "S"];
86 function format_key_press(code, modifiers)
91 if ((modifiers & MOD_SHIFT) && (code in shifted_keycode_to_name)) {
92 name = shifted_keycode_to_name[code];
93 modifiers &= ~MOD_SHIFT;
95 name = keycode_to_name[code];
97 name = String.fromCharCode(code);
101 for (var i = 0; i < modifier_names.length; ++i)
103 if (modifiers & (1 << i))
104 out = out + modifier_names[i] + "-";
111 function format_key_spec(key) {
112 if (key.match_function) {
113 if (key.match_function == match_any_key)
115 if (key.match_function == match_any_unmodified_key)
116 return "<any-unmodified-key>";
117 return "<match-function>";
119 return format_key_press(key.keyCode, key.modifiers);
122 function format_key_event(event)
124 return format_key_press(event.keyCode, get_modifiers(event));
127 function format_binding_sequence(seq) {
128 return seq.map(function (x) {
129 return format_key_spec(x.key);
133 // Key Matching Functions. These are functions that may be passed to kbd
134 // in place of key code or char code. They take an event object as their
135 // argument and turn true if the event matches the class of keys that they
138 function match_any_key (event)
143 function meta_pressed (event)
145 return event.altKey || event.metaKey;
148 function get_modifiers(event)
150 // Shift is always included in the modifiers, if it is included in
152 return (event.ctrlKey ? MOD_CTRL:0) |
153 (meta_pressed(event) ? MOD_META:0) |
154 (event.shiftKey ? MOD_SHIFT: 0);
157 /* This function is no longer used for normal keymap lookups. It is
158 * only used to check if the current key matches the abort key. */
159 function match_binding(key, event)
162 && event.keyCode == key.keyCode
163 && get_modifiers(event) == key.modifiers)
164 || (key.match_function && key.match_function (event));
167 function lookup_key_binding(kmap, event)
170 // Check if the key matches the keycode table
171 var mods = get_modifiers(event);
172 var keycode_binds = kmap.keycode_bindings;
175 if ((arr = keycode_binds[event.keyCode]) != null &&
176 (bind = arr[mods]) != null)
179 // Check if the key matches a predicate
180 var pred_binds = kmap.predicate_bindings;
181 for (var i = 0; i < pred_binds.length; ++i)
183 var bind = pred_binds[i];
184 if (bind.key.match_function(event))
192 function match_any_unmodified_key (event)
195 return event.charCode
196 && !meta_pressed(event)
198 } catch (e) {return false; }
201 function kbd (spec, mods)
204 if (typeof spec == "function")
205 key.match_function = spec;
206 else if (typeof spec == "string")
208 /* Attempt to parse a key specification. In order to allow
209 * the user to specify the "-" key literally, special case the
210 * parsing of that. */
212 if (spec.substr(spec.length - 1) == "-")
214 parts = spec.substr(0, spec.length - 1).split("-");
217 parts = spec.split("-");
218 var parsed_modifiers = 0;
219 if (parts.length > 1)
221 // Attempt to parse modifiers
222 for (var i = 0; i < parts.length - 1; ++i)
224 var k = modifier_names.indexOf(parts[i]);
228 parsed_modifiers |= mod;
231 // Attempt to lookup keycode
232 var name = parts[parts.length - 1];
234 if (name in name_to_keycode)
235 code = name_to_keycode[name];
236 else if (name in name_to_shifted_keycode) {
237 code = name_to_shifted_keycode[name];
238 parsed_modifiers |= MOD_SHIFT;
240 throw "Invalid key specification: " + spec;
243 parsed_modifiers |= mods;
244 key.modifiers = parsed_modifiers;
249 key.modifiers = mods;
257 // bind key to either the keymap or command in the keymap, kmap
258 define_keywords("$fallthrough", "$hook");
259 function define_key(kmap, keys, cmd)
263 var ref = get_caller_source_code_reference();
265 var args = arguments;
267 if (typeof(keys) == "string" && keys.length > 1)
268 keys = keys.split(" ");
269 if (!(keys instanceof Array))
272 var new_command = null, new_keymap = null;
273 if (typeof(cmd) == "string")
275 else if (cmd instanceof keymap)
277 else if (cmd != null)
278 throw new Error("Invalid `cmd' argument: " + cmd);
280 var parent_kmap = kmap.parent;
283 for (var i = 0; i < keys.length; ++i) {
285 var final_binding = (i == keys.length - 1);
287 if (typeof key == "string")
289 else if (typeof key == "function")
292 /* Replace `bind' with the binding specified by (cmd, fallthrough) */
293 function replace_binding(bind)
296 bind.command = new_command;
297 bind.keymap = new_keymap;
298 bind.fallthrough = args.$fallthrough;
299 bind.hook = args.$hook;
300 bind.source_code_reference = ref;
303 throw new Error("Key sequence has a non-keymap in prefix");
308 function make_binding()
311 return {key: key, fallthrough: args.$fallthrough, hook: args.$hook,
312 command: new_command, keymap: new_keymap,
313 source_code_reference: ref};
317 // Check for a corresponding binding a parent
318 kmap = new keymap($parent = parent_kmap);
319 return {key: key, keymap: kmap,
320 source_code_reference: ref};
324 // Check if the specified binding is already present in the kmap
325 if (key.match_function)
327 var pred_binds = kmap.predicate_bindings;
328 for (var i = 0; i < pred_binds.length; i++)
330 var cur_bind = pred_binds[i];
331 if (cur_bind.key.match_function == key.match_function)
333 replace_binding(cur_bind);
338 if (!final_binding && parent_kmap) {
339 var parent_pred_binds = parent_kmap.predicate_bindings;
341 for (var i = 0; i < parent_pred_binds.length; i++)
343 var cur_bind = parent_pred_binds[i];
344 if (cur_bind.key.match_function == key.match_function && cur_bind.keymap)
346 parent_kmap = cur_bind.keymap;
351 // Not already present, must be added
352 pred_binds.push(make_binding());
355 // This is a binding by keycode: look it up in the table
356 var keycode_binds = kmap.keycode_bindings;
357 var arr = keycode_binds[key.keyCode];
359 if (arr && arr[key.modifiers])
361 replace_binding(arr[key.modifiers]);
365 if (!final_binding && parent_kmap) {
366 var p_keycode_binds = parent_kmap.keycode_bindings;
368 var p_arr = p_keycode_binds[key.keyCode];
370 if (p_arr && (p_bind = p_arr[key.modifiers]) != null && p_bind.keymap)
371 parent_kmap = p_bind.keymap;
375 arr = (keycode_binds[key.keyCode] = []);
377 arr[key.modifiers] = make_binding();
382 define_keywords("$parent", "$help");
386 /* For efficiency, a table indexed by the key code, and then by
387 * the modifiers is used to lookup key bindings, rather than
388 * looping through all bindings in the key map to find one. The
389 * array keycode_bindings is indexed by the keyCode; if the
390 * corresponding element for a keyCode is non-null, it is itself
391 * an array indexed by the result of get_modifiers (i.e. from 0 to 7).
392 * As before, match_function-based bindings are stored as a simple
393 * list, predicate_bindings. */
394 this.parent = arguments.$parent;
395 this.keycode_bindings = [];
396 this.predicate_bindings = [];
397 this.help = arguments.$help;
400 function copy_event(event)
403 ev.keyCode = event.keyCode;
404 ev.charCode = event.charCode;
405 ev.ctrlKey = event.ctrlKey;
406 ev.metaKey = event.metaKey;
407 ev.altKey = event.altKey;
408 ev.shiftKey = event.shiftKey;
412 function key_down_handler(event)
415 //window.dumpln("key down: " + conkeror.format_key_press(event.keyCode, conkeror.get_modifiers(event)));
417 var state = window.keyboard;
418 state.last_key_down_event = copy_event(event);
419 state.last_char_code = null;
420 state.last_key_code = null;
423 define_user_variable("keyboard_key_sequence_help_timeout", 0,
424 "Delay (in millseconds) before the current key sequence prefix is displayed in the minibuffer.");
426 function key_press_handler(true_event)
430 var state = window.keyboard;
432 /* ASSERT(state.last_key_down_event != null); */
434 var event = state.last_key_down_event;
435 event.charCode = true_event.charCode;
437 // If the true_event includes a keyCode, we can just use that
438 if (true_event.keyCode)
439 event.keyCode = true_event.keyCode;
441 /* Filter out events from keys like the Windows/Super/Hyper key */
442 if (event.keyCode == 0)
445 /* Clear minibuffer message */
446 window.minibuffer.clear();
452 if (!state.current_context)
453 ctx = state.current_context = { window: window, key_sequence: [] };
455 ctx = state.current_context;
460 state.active_keymap ||
461 state.override_keymap ||
462 window.buffers.current.keymap;
463 var overlay_keymap = ctx.overlay_keymap;
466 lookup_key_binding(active_keymap, event) ||
467 (overlay_keymap && lookup_key_binding(overlay_keymap, event));
469 ctx.overlay_keymap = null;
471 // Should we stop this event from being processed by the gui?
473 // 1) we have a binding, and the binding's fallthrough property is not
476 // 2) we are in the middle of a key sequence, and we need to say that
477 // the key sequence given has no command.
479 if (!binding || !binding.fallthrough)
481 true_event.preventDefault();
482 true_event.stopPropagation();
485 // Finally, process the binding.
486 ctx.key_sequence.push(format_key_event(event));
488 if (binding.keymap) {
490 binding.hook.call(null, ctx, active_keymap, overlay_keymap);
491 state.active_keymap = binding.keymap;
492 if (!state.help_displayed)
494 state.help_timer_ID = window.setTimeout(function () {
495 window.minibuffer.show(ctx.key_sequence.join(" "));
496 state.help_displayed = true;
497 state.help_timer_ID = null;
498 }, keyboard_key_sequence_help_timeout);
501 window.minibuffer.show(ctx.key_sequence.join(" "));
503 // We're going for another round
505 } else if (binding.command) {
506 call_interactively(ctx, binding.command);
509 window.minibuffer.message(ctx.key_sequence.join(" ") + " is undefined");
512 // Clean up if we're done
515 if (state.help_timer_ID != null)
517 window.clearTimeout(state.help_timer_ID);
518 state.help_timer_ID = null;
520 state.help_displayed = false;
521 state.active_keymap = null;
522 state.current_context = null;
524 } catch(e) { dump_error(e);}
527 function keyboard(window)
529 this.window = window;
532 keyboard.prototype = {
533 last_key_down_event : null,
534 current_context : null,
535 active_keymap : null,
536 help_timer_ID : null,
537 help_displayed : false,
539 /* If this is non-null, it is used instead of the current buffer's
541 override_keymap : null,
543 set_override_keymap : function (keymap) {
544 /* Clear out any in-progress key sequence. */
545 this.active_keymap = null;
546 this.current_context = null;
547 if (this.help_timer_ID != null)
549 this.window.clearTimeout(this.help_timer_ID);
550 this.help_timer_ID = null;
552 this.override_keymap = keymap;
557 function keyboard_initialize_window(window)
559 window.keyboard = new keyboard(window);
561 window.addEventListener ("keydown", key_down_handler, true /* capture */,
562 false /* ignore untrusted events */);
563 window.addEventListener ("keypress", key_press_handler, true /* capture */,
564 false /* ignore untrusted events */);
567 add_hook("window_initialize_hook", keyboard_initialize_window);
569 function for_each_key_binding(keymap_or_buffer, callback) {
571 if (keymap_or_buffer instanceof conkeror.buffer) {
572 var buffer = keymap_or_buffer;
573 var window = buffer.window;
574 keymap = window.keyboard.override_keymap || buffer.keymap;
576 keymap = keymap_or_buffer;
578 var keymap_stack = [keymap];
579 var binding_stack = [];
580 function helper2(bind) {
581 binding_stack.push(bind);
582 callback(binding_stack);
583 if (bind.keymap && keymap_stack.indexOf(bind.keymap) == -1) {
584 keymap_stack.push(bind.keymap);
591 var modifier_keys_masked = false;
592 var keycode_masks = [];
594 var keymap = keymap_stack[keymap_stack.length - 1];
595 for (var i in keymap.keycode_bindings) {
596 var b = keymap.keycode_bindings[i];
597 if (!(i in keycode_masks))
598 keycode_masks[i] = [];
600 if (modifier_keys_masked && j != 0)
602 if (!keycode_masks[i][j]) {
604 keycode_masks[i][j] = true;
608 for (var i in keymap.predicate_bindings) {
609 var bind = keymap.predicate_bindings[i];
611 var p = bind.key.match_function;
612 if (p == match_any_key)
614 if (p == match_any_unmodified_key)
615 modifier_keys_masked = true;
618 keymap_stack[keymap_stack.length - 1] = keymap.parent;
626 function find_command_in_keymap(keymap_or_buffer, command) {
629 for_each_key_binding(keymap_or_buffer, function (bind_seq) {
630 var bind = bind_seq[bind_seq.length - 1];
631 if (bind.command == command)
632 list.push(format_binding_sequence(bind_seq));
637 var key_binding_reader_keymap = new keymap();
638 define_key(key_binding_reader_keymap, match_any_key, "read-key-binding-key");
640 define_keywords("$buffer", "$keymap");
641 function key_binding_reader(continuation) {
642 keywords(arguments, $prompt = "Describe key:");
644 this.continuation = continuation;
646 if (arguments.$keymap)
647 this.target_keymap = arguments.$keymap;
649 var buffer = arguments.$buffer;
650 var window = buffer.window;
651 this.target_keymap = window.keyboard.override_keymap || buffer.keymap;
654 this.key_sequence = [];
656 minibuffer_state.call(this, key_binding_reader_keymap, arguments.$prompt);
658 key_binding_reader.prototype = {
659 __proto__: minibuffer_state.prototype,
660 destroy: function () {
661 if (this.continuation)
662 this.continuation.throw(abort());
666 function invalid_key_binding(seq) {
667 var e = new Error(seq.join(" ") + " is undefined");
668 e.key_sequence = seq;
669 e.__proto__ = invalid_key_binding.prototype;
672 invalid_key_binding.prototype = {
673 __proto__: interactive_error.prototype
676 function read_key_binding_key(window, state, event) {
677 var binding = lookup_key_binding(state.target_keymap, event);
679 state.key_sequence.push(format_key_event(event));
681 if (binding == null) {
682 var c = state.continuation;
683 delete state.continuation;
684 window.minibuffer.pop_state();
685 c.throw(invalid_key_binding(state.key_sequence));
689 if (binding.keymap) {
690 window.minibuffer._ensure_input_area_showing();
691 window.minibuffer._input_text = state.key_sequence.join(" ") + " ";
692 state.target_keymap = binding.keymap;
696 var c = state.continuation;
697 delete state.continuation;
699 window.minibuffer.pop_state();
702 c([state.key_sequence, binding]);
704 interactive("read-key-binding-key", function (I) {
705 read_key_binding_key(I.window, I.minibuffer.check_state(key_binding_reader), I.event);
708 minibuffer.prototype.read_key_binding = function () {
710 var s = new key_binding_reader((yield CONTINUATION), forward_keywords(arguments));
712 var result = yield SUSPEND;
713 yield co_return(result);