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) |
155 event.sticky_modifiers;
158 /* This function is no longer used for normal keymap lookups. It is
159 * only used to check if the current key matches the abort key. */
160 function match_binding(key, event)
163 && event.keyCode == key.keyCode
164 && get_modifiers(event) == key.modifiers)
165 || (key.match_function && key.match_function (event));
168 function lookup_key_binding(kmap, event)
171 // Check if the key matches the keycode table
172 var mods = get_modifiers(event);
173 var keycode_binds = kmap.keycode_bindings;
176 if ((arr = keycode_binds[event.keyCode]) != null &&
177 (bind = arr[mods]) != null)
180 // Check if the key matches a predicate
181 var pred_binds = kmap.predicate_bindings;
182 for (var i = 0; i < pred_binds.length; ++i)
184 var bind = pred_binds[i];
185 if (bind.key.match_function(event))
193 function match_any_unmodified_key (event)
196 return event.charCode
197 && !meta_pressed(event)
199 } catch (e) {return false; }
202 function kbd (spec, mods)
204 if (typeof(spec) == "object")
207 if (typeof spec == "function")
208 key.match_function = spec;
209 else if (typeof spec == "string")
211 /* Attempt to parse a key specification. In order to allow
212 * the user to specify the "-" key literally, special case the
213 * parsing of that. */
215 if (spec.substr(spec.length - 1) == "-")
217 parts = spec.substr(0, spec.length - 1).split("-");
220 parts = spec.split("-");
221 var parsed_modifiers = 0;
222 if (parts.length > 1)
224 // Attempt to parse modifiers
225 for (var i = 0; i < parts.length - 1; ++i)
227 var k = modifier_names.indexOf(parts[i]);
231 parsed_modifiers |= mod;
234 // Attempt to lookup keycode
235 var name = parts[parts.length - 1];
237 if (name in name_to_keycode)
238 code = name_to_keycode[name];
239 else if (name in name_to_shifted_keycode) {
240 code = name_to_shifted_keycode[name];
241 parsed_modifiers |= MOD_SHIFT;
243 throw "Invalid key specification: " + spec;
246 parsed_modifiers |= mods;
247 key.modifiers = parsed_modifiers;
252 key.modifiers = mods;
260 // bind key to either the keymap or command in the keymap, kmap
261 define_keywords("$fallthrough", "$hook", "$category");
262 function define_key(kmap, keys, cmd)
266 var ref = get_caller_source_code_reference();
268 var args = arguments;
270 if (typeof(keys) == "string" && keys.length > 1)
271 keys = keys.split(" ");
272 if (!(keys instanceof Array))
275 var new_command = null, new_keymap = null;
276 if (typeof(cmd) == "string" || typeof(cmd) == "function")
278 else if (cmd instanceof keymap)
280 else if (cmd != null)
281 throw new Error("Invalid `cmd' argument: " + cmd);
283 var parent_kmap = kmap.parent;
286 for (var i = 0; i < keys.length; ++i) {
288 var final_binding = (i == keys.length - 1);
290 if (typeof key == "string")
292 else if (typeof key == "function")
295 /* Replace `bind' with the binding specified by (cmd, fallthrough) */
296 function replace_binding(bind)
299 bind.command = new_command;
300 bind.keymap = new_keymap;
301 bind.fallthrough = args.$fallthrough;
302 bind.hook = args.$hook;
303 bind.source_code_reference = ref;
304 bind.category = args.$category;
307 throw new Error("Key sequence has a non-keymap in prefix");
312 function make_binding()
315 return {key: key, fallthrough: args.$fallthrough, hook: args.$hook,
316 command: new_command, keymap: new_keymap,
317 source_code_reference: ref,
318 category: args.$category,
324 // Check for a corresponding binding a parent
325 kmap = new keymap($parent = parent_kmap);
326 kmap.bound_in = old_kmap;
327 return {key: key, keymap: kmap,
328 source_code_reference: ref,
333 // Check if the specified binding is already present in the kmap
334 if (key.match_function)
336 var pred_binds = kmap.predicate_bindings;
337 for (var i = 0; i < pred_binds.length; i++)
339 var cur_bind = pred_binds[i];
340 if (cur_bind.key.match_function == key.match_function)
342 replace_binding(cur_bind);
347 if (!final_binding && parent_kmap) {
348 var parent_pred_binds = parent_kmap.predicate_bindings;
350 for (var i = 0; i < parent_pred_binds.length; i++)
352 var cur_bind = parent_pred_binds[i];
353 if (cur_bind.key.match_function == key.match_function && cur_bind.keymap)
355 parent_kmap = cur_bind.keymap;
360 // Not already present, must be added
361 pred_binds.push(make_binding());
364 // This is a binding by keycode: look it up in the table
365 var keycode_binds = kmap.keycode_bindings;
366 var arr = keycode_binds[key.keyCode];
368 if (arr && arr[key.modifiers])
370 replace_binding(arr[key.modifiers]);
374 if (!final_binding && parent_kmap) {
375 var p_keycode_binds = parent_kmap.keycode_bindings;
377 var p_arr = p_keycode_binds[key.keyCode];
379 if (p_arr && (p_bind = p_arr[key.modifiers]) != null && p_bind.keymap)
380 parent_kmap = p_bind.keymap;
384 arr = (keycode_binds[key.keyCode] = []);
386 arr[key.modifiers] = make_binding();
391 define_keywords("$parent", "$help", "$name");
395 /* For efficiency, a table indexed by the key code, and then by
396 * the modifiers is used to lookup key bindings, rather than
397 * looping through all bindings in the key map to find one. The
398 * array keycode_bindings is indexed by the keyCode; if the
399 * corresponding element for a keyCode is non-null, it is itself
400 * an array indexed by the result of get_modifiers (i.e. from 0 to 7).
401 * As before, match_function-based bindings are stored as a simple
402 * list, predicate_bindings. */
403 this.parent = arguments.$parent;
404 this.keycode_bindings = [];
405 this.predicate_bindings = [];
406 this.help = arguments.$help;
407 this.name = arguments.$name;
410 function define_keymap(name) {
411 this[name] = new keymap($name = name, forward_keywords(arguments));
414 function copy_event(event)
417 ev.keyCode = event.keyCode;
418 ev.charCode = event.charCode;
419 ev.ctrlKey = event.ctrlKey;
420 ev.metaKey = event.metaKey;
421 ev.altKey = event.altKey;
422 ev.shiftKey = event.shiftKey;
423 ev.sticky_modifiers = event.sticky_modifiers;
427 function key_down_handler(event)
430 //window.dumpln("key down: " + conkeror.format_key_press(event.keyCode, conkeror.get_modifiers(event)));
432 var state = window.keyboard;
433 state.last_key_down_event = copy_event(event);
434 state.last_char_code = null;
435 state.last_key_code = null;
438 define_variable("keyboard_key_sequence_help_timeout", 0,
439 "Delay (in millseconds) before the current key sequence prefix is displayed in the minibuffer.");
441 define_window_local_hook("key_press_hook", RUN_HOOK_UNTIL_SUCCESS);
443 function key_press_handler(true_event)
447 var state = window.keyboard;
449 /* ASSERT(state.last_key_down_event != null); */
451 var event = state.last_key_down_event;
452 event.charCode = true_event.charCode;
454 // If the true_event includes a keyCode, we can just use that
455 if (true_event.keyCode)
456 event.keyCode = true_event.keyCode;
458 /* Filter out events from keys like the Windows/Super/Hyper key */
459 if (event.keyCode == 0)
462 /* Clear minibuffer message */
463 window.minibuffer.clear();
469 if (!state.current_context)
470 ctx = state.current_context = { window: window, key_sequence: [], sticky_modifiers: 0 };
472 ctx = state.current_context;
474 event.sticky_modifiers = ctx.sticky_modifiers;
475 ctx.sticky_modifiers = 0;
479 if (key_press_hook.run(window, ctx, true_event))
483 state.active_keymap ||
484 state.override_keymap ||
485 window.buffers.current.keymap;
486 var overlay_keymap = ctx.overlay_keymap;
489 lookup_key_binding(active_keymap, event) ||
490 (overlay_keymap && lookup_key_binding(overlay_keymap, event));
492 ctx.overlay_keymap = null;
494 // Should we stop this event from being processed by the gui?
496 // 1) we have a binding, and the binding's fallthrough property is not
499 // 2) we are in the middle of a key sequence, and we need to say that
500 // the key sequence given has no command.
502 if (!binding || !binding.fallthrough)
504 true_event.preventDefault();
505 true_event.stopPropagation();
508 // Finally, process the binding.
509 ctx.key_sequence.push(format_key_event(event));
511 if (binding.keymap) {
513 binding.hook.call(null, ctx, active_keymap, overlay_keymap);
514 state.active_keymap = binding.keymap;
515 if (!state.help_displayed)
517 state.help_timer_ID = window.setTimeout(function () {
518 window.minibuffer.show(ctx.key_sequence.join(" "));
519 state.help_displayed = true;
520 state.help_timer_ID = null;
521 }, keyboard_key_sequence_help_timeout);
524 window.minibuffer.show(ctx.key_sequence.join(" "));
526 // We're going for another round
528 } else if (binding.command) {
529 call_interactively(ctx, binding.command);
532 window.minibuffer.message(ctx.key_sequence.join(" ") + " is undefined");
535 // Clean up if we're done
538 if (state.help_timer_ID != null)
540 window.clearTimeout(state.help_timer_ID);
541 state.help_timer_ID = null;
543 state.help_displayed = false;
544 state.active_keymap = null;
545 state.current_context = null;
547 } catch(e) { dump_error(e);}
550 function keyboard(window)
552 this.window = window;
555 keyboard.prototype = {
556 last_key_down_event : null,
557 current_context : null,
558 active_keymap : null,
559 help_timer_ID : null,
560 help_displayed : false,
562 /* If this is non-null, it is used instead of the current buffer's
564 override_keymap : null,
566 set_override_keymap : function (keymap) {
567 /* Clear out any in-progress key sequence. */
568 this.active_keymap = null;
569 this.current_context = null;
570 if (this.help_timer_ID != null)
572 this.window.clearTimeout(this.help_timer_ID);
573 this.help_timer_ID = null;
575 this.override_keymap = keymap;
580 function keyboard_initialize_window(window)
582 window.keyboard = new keyboard(window);
584 window.addEventListener ("keydown", key_down_handler, true /* capture */,
585 false /* ignore untrusted events */);
586 window.addEventListener ("keypress", key_press_handler, true /* capture */,
587 false /* ignore untrusted events */);
590 add_hook("window_initialize_hook", keyboard_initialize_window);
592 function for_each_key_binding(keymap_or_buffer, callback) {
594 if (keymap_or_buffer instanceof conkeror.buffer) {
595 var buffer = keymap_or_buffer;
596 var window = buffer.window;
597 keymap = window.keyboard.override_keymap || buffer.keymap;
599 keymap = keymap_or_buffer;
601 var keymap_stack = [keymap];
602 var binding_stack = [];
603 function helper2(bind) {
604 binding_stack.push(bind);
605 callback(binding_stack);
606 if (bind.keymap && keymap_stack.indexOf(bind.keymap) == -1) {
607 keymap_stack.push(bind.keymap);
614 var unmodified_keys_masked = false;
615 var keycode_masks = [];
617 var keymap = keymap_stack[keymap_stack.length - 1];
618 for (var i in keymap.keycode_bindings) {
619 var b = keymap.keycode_bindings[i];
620 if (!(i in keycode_masks))
621 keycode_masks[i] = [];
623 if (unmodified_keys_masked && ((j & MOD_SHIFT) == j))
625 if (!keycode_masks[i][j]) {
627 keycode_masks[i][j] = true;
631 for (var i in keymap.predicate_bindings) {
632 var bind = keymap.predicate_bindings[i];
634 var p = bind.key.match_function;
635 if (p == match_any_key)
637 if (p == match_any_unmodified_key)
638 unmodified_keys_masked = true;
641 keymap_stack[keymap_stack.length - 1] = keymap.parent;
649 function find_command_in_keymap(keymap_or_buffer, command) {
652 for_each_key_binding(keymap_or_buffer, function (bind_seq) {
653 var bind = bind_seq[bind_seq.length - 1];
654 if (bind.command == command)
655 list.push(format_binding_sequence(bind_seq));
660 define_keymap("key_binding_reader_keymap");
661 define_key(key_binding_reader_keymap, match_any_key, "read-key-binding-key");
663 define_keywords("$buffer", "$keymap");
664 function key_binding_reader(continuation) {
665 keywords(arguments, $prompt = "Describe key:");
667 this.continuation = continuation;
669 if (arguments.$keymap)
670 this.target_keymap = arguments.$keymap;
672 var buffer = arguments.$buffer;
673 var window = buffer.window;
674 this.target_keymap = window.keyboard.override_keymap || buffer.keymap;
677 this.key_sequence = [];
679 minibuffer_state.call(this, key_binding_reader_keymap, arguments.$prompt);
681 key_binding_reader.prototype = {
682 __proto__: minibuffer_state.prototype,
683 destroy: function () {
684 if (this.continuation)
685 this.continuation.throw(abort());
689 function invalid_key_binding(seq) {
690 var e = new Error(seq.join(" ") + " is undefined");
691 e.key_sequence = seq;
692 e.__proto__ = invalid_key_binding.prototype;
695 invalid_key_binding.prototype = {
696 __proto__: interactive_error.prototype
699 function read_key_binding_key(window, state, event) {
700 var binding = lookup_key_binding(state.target_keymap, event);
702 state.key_sequence.push(format_key_event(event));
704 if (binding == null) {
705 var c = state.continuation;
706 delete state.continuation;
707 window.minibuffer.pop_state();
708 c.throw(invalid_key_binding(state.key_sequence));
712 if (binding.keymap) {
713 window.minibuffer._ensure_input_area_showing();
714 window.minibuffer._input_text = state.key_sequence.join(" ") + " ";
715 state.target_keymap = binding.keymap;
719 var c = state.continuation;
720 delete state.continuation;
722 window.minibuffer.pop_state();
725 c([state.key_sequence, binding]);
727 interactive("read-key-binding-key", function (I) {
728 read_key_binding_key(I.window, I.minibuffer.check_state(key_binding_reader), I.event);
731 minibuffer.prototype.read_key_binding = function () {
733 var s = new key_binding_reader((yield CONTINUATION), forward_keywords(arguments));
735 var result = yield SUSPEND;
736 yield co_return(result);