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");
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;
306 throw new Error("Key sequence has a non-keymap in prefix");
311 function make_binding()
314 return {key: key, fallthrough: args.$fallthrough, hook: args.$hook,
315 command: new_command, keymap: new_keymap,
316 source_code_reference: ref};
320 // Check for a corresponding binding a parent
321 kmap = new keymap($parent = parent_kmap);
322 return {key: key, keymap: kmap,
323 source_code_reference: ref};
327 // Check if the specified binding is already present in the kmap
328 if (key.match_function)
330 var pred_binds = kmap.predicate_bindings;
331 for (var i = 0; i < pred_binds.length; i++)
333 var cur_bind = pred_binds[i];
334 if (cur_bind.key.match_function == key.match_function)
336 replace_binding(cur_bind);
341 if (!final_binding && parent_kmap) {
342 var parent_pred_binds = parent_kmap.predicate_bindings;
344 for (var i = 0; i < parent_pred_binds.length; i++)
346 var cur_bind = parent_pred_binds[i];
347 if (cur_bind.key.match_function == key.match_function && cur_bind.keymap)
349 parent_kmap = cur_bind.keymap;
354 // Not already present, must be added
355 pred_binds.push(make_binding());
358 // This is a binding by keycode: look it up in the table
359 var keycode_binds = kmap.keycode_bindings;
360 var arr = keycode_binds[key.keyCode];
362 if (arr && arr[key.modifiers])
364 replace_binding(arr[key.modifiers]);
368 if (!final_binding && parent_kmap) {
369 var p_keycode_binds = parent_kmap.keycode_bindings;
371 var p_arr = p_keycode_binds[key.keyCode];
373 if (p_arr && (p_bind = p_arr[key.modifiers]) != null && p_bind.keymap)
374 parent_kmap = p_bind.keymap;
378 arr = (keycode_binds[key.keyCode] = []);
380 arr[key.modifiers] = make_binding();
385 define_keywords("$parent", "$help");
389 /* For efficiency, a table indexed by the key code, and then by
390 * the modifiers is used to lookup key bindings, rather than
391 * looping through all bindings in the key map to find one. The
392 * array keycode_bindings is indexed by the keyCode; if the
393 * corresponding element for a keyCode is non-null, it is itself
394 * an array indexed by the result of get_modifiers (i.e. from 0 to 7).
395 * As before, match_function-based bindings are stored as a simple
396 * list, predicate_bindings. */
397 this.parent = arguments.$parent;
398 this.keycode_bindings = [];
399 this.predicate_bindings = [];
400 this.help = arguments.$help;
403 function copy_event(event)
406 ev.keyCode = event.keyCode;
407 ev.charCode = event.charCode;
408 ev.ctrlKey = event.ctrlKey;
409 ev.metaKey = event.metaKey;
410 ev.altKey = event.altKey;
411 ev.shiftKey = event.shiftKey;
412 ev.sticky_modifiers = event.sticky_modifiers;
416 function key_down_handler(event)
419 //window.dumpln("key down: " + conkeror.format_key_press(event.keyCode, conkeror.get_modifiers(event)));
421 var state = window.keyboard;
422 state.last_key_down_event = copy_event(event);
423 state.last_char_code = null;
424 state.last_key_code = null;
427 define_variable("keyboard_key_sequence_help_timeout", 0,
428 "Delay (in millseconds) before the current key sequence prefix is displayed in the minibuffer.");
430 define_window_local_hook("key_press_hook", RUN_HOOK_UNTIL_SUCCESS);
432 function key_press_handler(true_event)
436 var state = window.keyboard;
438 /* ASSERT(state.last_key_down_event != null); */
440 var event = state.last_key_down_event;
441 event.charCode = true_event.charCode;
443 // If the true_event includes a keyCode, we can just use that
444 if (true_event.keyCode)
445 event.keyCode = true_event.keyCode;
447 /* Filter out events from keys like the Windows/Super/Hyper key */
448 if (event.keyCode == 0)
451 /* Clear minibuffer message */
452 window.minibuffer.clear();
458 if (!state.current_context)
459 ctx = state.current_context = { window: window, key_sequence: [], sticky_modifiers: 0 };
461 ctx = state.current_context;
463 event.sticky_modifiers = ctx.sticky_modifiers;
464 ctx.sticky_modifiers = 0;
468 if (key_press_hook.run(window, ctx, true_event))
472 state.active_keymap ||
473 state.override_keymap ||
474 window.buffers.current.keymap;
475 var overlay_keymap = ctx.overlay_keymap;
478 lookup_key_binding(active_keymap, event) ||
479 (overlay_keymap && lookup_key_binding(overlay_keymap, event));
481 ctx.overlay_keymap = null;
483 // Should we stop this event from being processed by the gui?
485 // 1) we have a binding, and the binding's fallthrough property is not
488 // 2) we are in the middle of a key sequence, and we need to say that
489 // the key sequence given has no command.
491 if (!binding || !binding.fallthrough)
493 true_event.preventDefault();
494 true_event.stopPropagation();
497 // Finally, process the binding.
498 ctx.key_sequence.push(format_key_event(event));
500 if (binding.keymap) {
502 binding.hook.call(null, ctx, active_keymap, overlay_keymap);
503 state.active_keymap = binding.keymap;
504 if (!state.help_displayed)
506 state.help_timer_ID = window.setTimeout(function () {
507 window.minibuffer.show(ctx.key_sequence.join(" "));
508 state.help_displayed = true;
509 state.help_timer_ID = null;
510 }, keyboard_key_sequence_help_timeout);
513 window.minibuffer.show(ctx.key_sequence.join(" "));
515 // We're going for another round
517 } else if (binding.command) {
518 call_interactively(ctx, binding.command);
521 window.minibuffer.message(ctx.key_sequence.join(" ") + " is undefined");
524 // Clean up if we're done
527 if (state.help_timer_ID != null)
529 window.clearTimeout(state.help_timer_ID);
530 state.help_timer_ID = null;
532 state.help_displayed = false;
533 state.active_keymap = null;
534 state.current_context = null;
536 } catch(e) { dump_error(e);}
539 function keyboard(window)
541 this.window = window;
544 keyboard.prototype = {
545 last_key_down_event : null,
546 current_context : null,
547 active_keymap : null,
548 help_timer_ID : null,
549 help_displayed : false,
551 /* If this is non-null, it is used instead of the current buffer's
553 override_keymap : null,
555 set_override_keymap : function (keymap) {
556 /* Clear out any in-progress key sequence. */
557 this.active_keymap = null;
558 this.current_context = null;
559 if (this.help_timer_ID != null)
561 this.window.clearTimeout(this.help_timer_ID);
562 this.help_timer_ID = null;
564 this.override_keymap = keymap;
569 function keyboard_initialize_window(window)
571 window.keyboard = new keyboard(window);
573 window.addEventListener ("keydown", key_down_handler, true /* capture */,
574 false /* ignore untrusted events */);
575 window.addEventListener ("keypress", key_press_handler, true /* capture */,
576 false /* ignore untrusted events */);
579 add_hook("window_initialize_hook", keyboard_initialize_window);
581 function for_each_key_binding(keymap_or_buffer, callback) {
583 if (keymap_or_buffer instanceof conkeror.buffer) {
584 var buffer = keymap_or_buffer;
585 var window = buffer.window;
586 keymap = window.keyboard.override_keymap || buffer.keymap;
588 keymap = keymap_or_buffer;
590 var keymap_stack = [keymap];
591 var binding_stack = [];
592 function helper2(bind) {
593 binding_stack.push(bind);
594 callback(binding_stack);
595 if (bind.keymap && keymap_stack.indexOf(bind.keymap) == -1) {
596 keymap_stack.push(bind.keymap);
603 var unmodified_keys_masked = false;
604 var keycode_masks = [];
606 var keymap = keymap_stack[keymap_stack.length - 1];
607 for (var i in keymap.keycode_bindings) {
608 var b = keymap.keycode_bindings[i];
609 if (!(i in keycode_masks))
610 keycode_masks[i] = [];
612 if (unmodified_keys_masked && ((j & MOD_SHIFT) == j))
614 if (!keycode_masks[i][j]) {
616 keycode_masks[i][j] = true;
620 for (var i in keymap.predicate_bindings) {
621 var bind = keymap.predicate_bindings[i];
623 var p = bind.key.match_function;
624 if (p == match_any_key)
626 if (p == match_any_unmodified_key)
627 unmodified_keys_masked = true;
630 keymap_stack[keymap_stack.length - 1] = keymap.parent;
638 function find_command_in_keymap(keymap_or_buffer, command) {
641 for_each_key_binding(keymap_or_buffer, function (bind_seq) {
642 var bind = bind_seq[bind_seq.length - 1];
643 if (bind.command == command)
644 list.push(format_binding_sequence(bind_seq));
649 var key_binding_reader_keymap = new keymap();
650 define_key(key_binding_reader_keymap, match_any_key, "read-key-binding-key");
652 define_keywords("$buffer", "$keymap");
653 function key_binding_reader(continuation) {
654 keywords(arguments, $prompt = "Describe key:");
656 this.continuation = continuation;
658 if (arguments.$keymap)
659 this.target_keymap = arguments.$keymap;
661 var buffer = arguments.$buffer;
662 var window = buffer.window;
663 this.target_keymap = window.keyboard.override_keymap || buffer.keymap;
666 this.key_sequence = [];
668 minibuffer_state.call(this, key_binding_reader_keymap, arguments.$prompt);
670 key_binding_reader.prototype = {
671 __proto__: minibuffer_state.prototype,
672 destroy: function () {
673 if (this.continuation)
674 this.continuation.throw(abort());
678 function invalid_key_binding(seq) {
679 var e = new Error(seq.join(" ") + " is undefined");
680 e.key_sequence = seq;
681 e.__proto__ = invalid_key_binding.prototype;
684 invalid_key_binding.prototype = {
685 __proto__: interactive_error.prototype
688 function read_key_binding_key(window, state, event) {
689 var binding = lookup_key_binding(state.target_keymap, event);
691 state.key_sequence.push(format_key_event(event));
693 if (binding == null) {
694 var c = state.continuation;
695 delete state.continuation;
696 window.minibuffer.pop_state();
697 c.throw(invalid_key_binding(state.key_sequence));
701 if (binding.keymap) {
702 window.minibuffer._ensure_input_area_showing();
703 window.minibuffer._input_text = state.key_sequence.join(" ") + " ";
704 state.target_keymap = binding.keymap;
708 var c = state.continuation;
709 delete state.continuation;
711 window.minibuffer.pop_state();
714 c([state.key_sequence, binding]);
716 interactive("read-key-binding-key", function (I) {
717 read_key_binding_key(I.window, I.minibuffer.check_state(key_binding_reader), I.event);
720 minibuffer.prototype.read_key_binding = function () {
722 var s = new key_binding_reader((yield CONTINUATION), forward_keywords(arguments));
724 var result = yield SUSPEND;
725 yield co_return(result);