2 require("command-line.js");
4 function get_default_keycode_to_charcode_tables() {
6 const KeyEvent = Ci.nsIDOMKeyEvent;
8 var unshifted_table = [];
9 var shifted_table = [];
10 for (var i = 0; i < 26; ++i) {
11 var keycode = KeyEvent.DOM_VK_A + i;
12 var charcode = keycode; // keycodes A-Z are same as ascii
13 shifted_table[keycode] = charcode;
15 unshifted_table[keycode] = "a".charCodeAt(0) + i;
18 for (var i = 0; i <= 9; ++i) {
19 var keycode = KeyEvent.DOM_VK_0 + i;
20 var charcode = keycode; // keycodes 0-9 are same as ascii
21 unshifted_table[keycode] = charcode;
24 function map(table, keycode, str) {
25 table[keycode] = str.charCodeAt(0);
28 map(unshifted_table, KeyEvent.DOM_VK_BACK_SLASH, "\\");
29 map(unshifted_table, KeyEvent.DOM_VK_OPEN_BRACKET, "[");
30 map(unshifted_table, KeyEvent.DOM_VK_CLOSE_BRACKET, "]");
31 map(unshifted_table, KeyEvent.DOM_VK_SEMICOLON, ";");
32 map(unshifted_table, KeyEvent.DOM_VK_COMMA, ",");
33 map(unshifted_table, KeyEvent.DOM_VK_PERIOD, ".");
34 map(unshifted_table, KeyEvent.DOM_VK_SLASH, "/");
35 map(unshifted_table, KeyEvent.DOM_VK_SUBTRACT, "-");
36 map(unshifted_table, KeyEvent.DOM_VK_EQUALS, "=");
38 map(shifted_table, KeyEvent.DOM_VK_SEMICOLON, ":");
39 map(shifted_table, KeyEvent.DOM_VK_1, "!");
40 map(shifted_table, KeyEvent.DOM_VK_2, "@");
41 map(shifted_table, KeyEvent.DOM_VK_3, "#");
42 map(shifted_table, KeyEvent.DOM_VK_4, "$");
43 map(shifted_table, KeyEvent.DOM_VK_5, "%");
44 map(shifted_table, KeyEvent.DOM_VK_6, "^");
45 map(shifted_table, KeyEvent.DOM_VK_7, "&");
46 map(shifted_table, KeyEvent.DOM_VK_8, "*");
47 map(shifted_table, KeyEvent.DOM_VK_9, "(");
48 map(shifted_table, KeyEvent.DOM_VK_0, ")");
49 map(shifted_table, KeyEvent.DOM_VK_EQUALS, "+");
50 map(shifted_table, KeyEvent.DOM_VK_SUBTRACT, "_");
51 map(shifted_table, KeyEvent.DOM_VK_COMMA, "<");
52 map(shifted_table, KeyEvent.DOM_VK_PERIOD, ">");
53 map(shifted_table, KeyEvent.DOM_VK_SLASH, "?");
55 return [unshifted_table, shifted_table];
58 /* Generate vk name table */
59 var keycode_to_vk_name = [];
60 var vk_name_to_keycode = {};
62 let KeyEvent = Ci.nsIDOMKeyEvent;
63 let prefix = "DOM_VK_";
66 /* Check if this is a key binding */
67 if (i.substr(0, prefix.length) == prefix)
69 let name = i.substr(prefix.length).toLowerCase();
70 let code = KeyEvent[i];
71 keycode_to_vk_name[code] = name;
72 vk_name_to_keycode[name] = code;
77 function get_charcode_mapping_table_from_preferences()
79 var vals = conkeror.get_pref("conkeror.charCodeMappingTable");
89 var unshifted_keycode_to_charcode = null;
90 var shifted_keycode_to_charcode = null;
91 var charcode_to_keycodes = null;
93 var keycode_to_name = null;
94 var shifted_keycode_to_name = null;
95 function load_charcode_mapping_table()
97 var tables = get_charcode_mapping_table_from_preferences();
99 tables = get_default_keycode_to_charcode_tables();
100 let [unshifted_table, shifted_table] = tables;
101 unshifted_keycode_to_charcode = unshifted_table;
102 shifted_keycode_to_charcode = shifted_table;
103 charcode_to_keycodes = [];
104 for each (let table in tables) {
105 var shifted = (table == tables[1]);
106 for (let x in table) {
107 let charcode = table[x];
108 if (charcode == null)
110 var obj = charcode_to_keycodes[charcode];
112 obj = charcode_to_keycodes[charcode] = [];
113 obj[obj.length] = [x, shifted];
117 keycode_to_name = keycode_to_vk_name.slice();
118 shifted_keycode_to_name = [];
119 for (let charcode in charcode_to_keycodes) {
120 let arr = charcode_to_keycodes[charcode];
123 let [keycode, shift] = arr[0];
124 let table = shift ? shifted_keycode_to_name : keycode_to_name;
125 table[keycode] = String.fromCharCode(charcode);
128 load_charcode_mapping_table();
130 interactive("keyboard-setup", function (I) {
131 make_chrome_window("chrome://conkeror/content/keyboard-setup.xul");
134 command_line_handler("keyboard-setup", true, function () {
135 make_chrome_window("chrome://conkeror/content/keyboard-setup.xul");
139 var abort_key = null;
141 const MOD_CTRL = 0x1;
142 const MOD_META = 0x2;
143 const MOD_SHIFT = 0x4;
145 // Note: For elements of the modifier_names array, an element at index
146 // i should correspond to the modifier mask (1 << i).
147 var modifier_names = ["C", "M", "S"];
149 function format_key_press(code, modifiers)
154 if ((modifiers & MOD_SHIFT) && (name = shifted_keycode_to_name[code])) {
155 modifiers &= ~MOD_SHIFT;
157 name = keycode_to_name[code] || ("<" + code + ">");
161 for (var i = 0; i < modifier_names.length; ++i)
163 if (modifiers & (1 << i))
164 out = out + modifier_names[i] + "-";
171 function format_key_spec(key) {
172 if (key.match_function) {
173 if (key.match_function == match_any_key)
175 if (key.match_function == match_any_unmodified_key)
176 return "<any-unmodified-key>";
177 return "<match-function>";
179 return format_key_press(key.keyCode, key.modifiers);
182 function format_key_event(event)
184 return format_key_press(event.keyCode, get_modifiers(event));
187 function format_binding_sequence(seq) {
188 return seq.map(function (x) {
189 return format_key_spec(x.key);
193 // Key Matching Functions. These are functions that may be passed to kbd
194 // in place of key code or char code. They take an event object as their
195 // argument and turn true if the event matches the class of keys that they
198 function match_any_key (event)
203 function meta_pressed (event)
205 return event.altKey || event.metaKey;
208 function get_modifiers(event)
210 // Shift is always included in the modifiers, if it is included in
212 return (event.ctrlKey ? MOD_CTRL:0) |
213 (meta_pressed(event) ? MOD_META:0) |
214 (event.shiftKey ? MOD_SHIFT: 0) |
215 event.sticky_modifiers;
218 /* This function is no longer used for normal keymap lookups. It is
219 * only used to check if the current key matches the abort key. */
220 function match_binding(key, event)
223 && event.keyCode == key.keyCode
224 && get_modifiers(event) == key.modifiers)
225 || (key.match_function && key.match_function (event));
228 function lookup_key_binding(kmap, event)
231 // Check if the key matches the keycode table
232 var mods = get_modifiers(event);
233 var keycode_binds = kmap.keycode_bindings;
236 if ((arr = keycode_binds[event.keyCode]) != null &&
237 (bind = arr[mods]) != null)
240 // Check if the key matches a predicate
241 var pred_binds = kmap.predicate_bindings;
242 for (var i = 0; i < pred_binds.length; ++i)
244 var bind = pred_binds[i];
245 if (bind.key.match_function(event))
253 function match_any_unmodified_key (event)
256 return event.charCode
257 && !meta_pressed(event)
259 } catch (e) {return false; }
262 function kbd (spec, mods)
268 results.is_kbd = true;
270 if (typeof spec == "function")
271 results[0] = {match_function: spec};
273 else if (typeof spec == "string")
275 /* Attempt to parse a key specification. In order to allow
276 * the user to specify the "-" key literally, special case the
277 * parsing of that. */
279 if (spec.substr(spec.length - 1) == "-")
281 parts = spec.substr(0, spec.length - 1).split("-");
284 parts = spec.split("-");
285 var parsed_modifiers = 0;
286 if (parts.length > 1)
288 // Attempt to parse modifiers
289 for (var i = 0; i < parts.length - 1; ++i)
291 var k = modifier_names.indexOf(parts[i]);
295 parsed_modifiers |= mod;
298 // Attempt to lookup keycode
299 var name = parts[parts.length - 1];
302 parsed_modifiers |= mods;
304 if (name.length == 1) {
305 // Charcode, handle specially
307 var codes = charcode_to_keycodes[name.charCodeAt(0)];
309 throw "Invalid key specification: " + spec;
311 for each (let [keycode, shift] in codes) {
312 results[results.length] = {keyCode: keycode, modifiers: parsed_modifiers | (shift ? MOD_SHIFT : 0)};
315 var code = vk_name_to_keycode[name];
317 throw "Invalid key specification: " + spec;
318 results[0] = {keyCode: code, modifiers: parsed_modifiers};
322 results[0] = {keyCode: spec, modifiers: ((mods != null)? mods : 0)};
327 define_keywords("$fallthrough", "$hook", "$category");
328 function define_key_internal(ref, kmap, keys, new_command, new_keymap)
332 var args = arguments;
334 var parent_kmap = kmap.parent;
337 for (var i = 0; i < keys.length; ++i) {
339 var final_binding = (i == keys.length - 1);
341 /* Replace `bind' with the binding specified by (cmd, fallthrough) */
342 function replace_binding(bind)
345 bind.command = new_command;
346 bind.keymap = new_keymap;
347 bind.fallthrough = args.$fallthrough;
348 bind.hook = args.$hook;
349 bind.source_code_reference = ref;
350 bind.category = args.$category;
353 throw new Error("Key sequence has a non-keymap in prefix");
358 function make_binding()
361 return {key: key, fallthrough: args.$fallthrough, hook: args.$hook,
362 command: new_command, keymap: new_keymap,
363 source_code_reference: ref,
364 category: args.$category,
370 // Check for a corresponding binding a parent
371 kmap = new keymap($parent = parent_kmap);
372 kmap.bound_in = old_kmap;
373 return {key: key, keymap: kmap,
374 source_code_reference: ref,
379 // Check if the specified binding is already present in the kmap
380 if (key.match_function)
382 var pred_binds = kmap.predicate_bindings;
383 for (var i = 0; i < pred_binds.length; i++)
385 var cur_bind = pred_binds[i];
386 if (cur_bind.key.match_function == key.match_function)
388 replace_binding(cur_bind);
393 if (!final_binding && parent_kmap) {
394 var parent_pred_binds = parent_kmap.predicate_bindings;
396 for (var i = 0; i < parent_pred_binds.length; i++)
398 var cur_bind = parent_pred_binds[i];
399 if (cur_bind.key.match_function == key.match_function && cur_bind.keymap)
401 parent_kmap = cur_bind.keymap;
406 // Not already present, must be added
407 pred_binds.push(make_binding());
410 // This is a binding by keycode: look it up in the table
411 var keycode_binds = kmap.keycode_bindings;
412 var arr = keycode_binds[key.keyCode];
414 if (arr && arr[key.modifiers])
416 replace_binding(arr[key.modifiers]);
420 if (!final_binding && parent_kmap) {
421 var p_keycode_binds = parent_kmap.keycode_bindings;
423 var p_arr = p_keycode_binds[key.keyCode];
425 if (p_arr && (p_bind = p_arr[key.modifiers]) != null && p_bind.keymap)
426 parent_kmap = p_bind.keymap;
430 arr = (keycode_binds[key.keyCode] = []);
432 arr[key.modifiers] = make_binding();
437 // bind key to either the keymap or command in the keymap, kmap
438 define_keywords("$fallthrough", "$hook", "$category");
439 function define_key(kmap, keys, cmd)
441 var orig_keys = keys;
443 var ref = get_caller_source_code_reference();
445 if (typeof(keys) == "string" && keys.length > 1)
446 keys = keys.split(" ");
448 if (!(typeof(keys) == "object") || ('is_kbd' in keys) || !(keys instanceof Array))
451 var new_command = null, new_keymap = null;
452 if (typeof(cmd) == "string" || typeof(cmd) == "function")
454 else if (cmd instanceof keymap)
456 else if (cmd != null)
457 throw new Error("Invalid `cmd' argument: " + cmd);
459 var args = arguments;
461 var input_keys = keys.map(function(x) kbd(x));
463 function helper(index, output_keys) {
464 if (index == input_keys.length) {
465 define_key_internal(ref, kmap, output_keys, new_command, new_keymap,
466 forward_keywords(args));
469 var key = input_keys[index];
470 for (let i = 0; i < key.length; ++i)
471 helper(index + 1, output_keys.concat(key[i]));
475 } catch (e if (typeof(e) == "string")) {
476 dumpln("Warning: Error occurred while binding keys: " + orig_keys);
478 dumpln("This may be due to an incorrect keyboard setup.");
479 dumpln("You may want to use the -keyboard-setup command-line option to fix your keyboard setup.");
483 define_keywords("$parent", "$help", "$name");
487 /* For efficiency, a table indexed by the key code, and then by
488 * the modifiers is used to lookup key bindings, rather than
489 * looping through all bindings in the key map to find one. The
490 * array keycode_bindings is indexed by the keyCode; if the
491 * corresponding element for a keyCode is non-null, it is itself
492 * an array indexed by the result of get_modifiers (i.e. from 0 to 7).
493 * As before, match_function-based bindings are stored as a simple
494 * list, predicate_bindings. */
495 this.parent = arguments.$parent;
496 this.keycode_bindings = [];
497 this.predicate_bindings = [];
498 this.help = arguments.$help;
499 this.name = arguments.$name;
502 function define_keymap(name) {
503 this[name] = new keymap($name = name, forward_keywords(arguments));
506 function copy_event(event)
509 ev.keyCode = event.keyCode;
510 ev.charCode = event.charCode;
511 ev.ctrlKey = event.ctrlKey;
512 ev.metaKey = event.metaKey;
513 ev.altKey = event.altKey;
514 ev.shiftKey = event.shiftKey;
515 ev.sticky_modifiers = event.sticky_modifiers;
519 function key_down_handler(event)
522 //window.dumpln("key down: " + conkeror.format_key_press(event.keyCode, conkeror.get_modifiers(event)));
524 var state = window.keyboard;
525 state.last_key_down_event = copy_event(event);
526 state.last_char_code = null;
527 state.last_key_code = null;
530 define_variable("keyboard_key_sequence_help_timeout", 0,
531 "Delay (in millseconds) before the current key sequence prefix is displayed in the minibuffer.");
533 define_window_local_hook("key_press_hook", RUN_HOOK_UNTIL_SUCCESS);
535 function key_press_handler(true_event)
539 var state = window.keyboard;
541 /* ASSERT(state.last_key_down_event != null); */
543 var event = state.last_key_down_event;
544 event.charCode = true_event.charCode;
546 // If the true_event includes a keyCode, we can just use that
547 if (true_event.keyCode)
548 event.keyCode = true_event.keyCode;
550 /* Filter out events from keys like the Windows/Super/Hyper key */
551 if (event.keyCode == 0)
554 /* Clear minibuffer message */
555 window.minibuffer.clear();
561 if (!state.current_context)
562 ctx = state.current_context = { window: window, key_sequence: [], sticky_modifiers: 0 };
564 ctx = state.current_context;
566 event.sticky_modifiers = ctx.sticky_modifiers;
567 ctx.sticky_modifiers = 0;
571 if (key_press_hook.run(window, ctx, true_event))
575 state.active_keymap ||
576 state.override_keymap ||
577 window.buffers.current.keymap;
578 var overlay_keymap = ctx.overlay_keymap;
581 lookup_key_binding(active_keymap, event) ||
582 (overlay_keymap && lookup_key_binding(overlay_keymap, event));
584 ctx.overlay_keymap = null;
586 // Should we stop this event from being processed by the gui?
588 // 1) we have a binding, and the binding's fallthrough property is not
591 // 2) we are in the middle of a key sequence, and we need to say that
592 // the key sequence given has no command.
594 if (!binding || !binding.fallthrough)
596 true_event.preventDefault();
597 true_event.stopPropagation();
600 // Finally, process the binding.
601 ctx.key_sequence.push(format_key_event(event));
603 if (binding.keymap) {
605 binding.hook.call(null, ctx, active_keymap, overlay_keymap);
606 state.active_keymap = binding.keymap;
607 if (!state.help_displayed)
609 state.help_timer_ID = window.setTimeout(function () {
610 window.minibuffer.show(ctx.key_sequence.join(" "));
611 state.help_displayed = true;
612 state.help_timer_ID = null;
613 }, keyboard_key_sequence_help_timeout);
616 window.minibuffer.show(ctx.key_sequence.join(" "));
618 // We're going for another round
620 } else if (binding.command) {
621 call_interactively(ctx, binding.command);
624 window.minibuffer.message(ctx.key_sequence.join(" ") + " is undefined");
627 // Clean up if we're done
630 if (state.help_timer_ID != null)
632 window.clearTimeout(state.help_timer_ID);
633 state.help_timer_ID = null;
635 state.help_displayed = false;
636 state.active_keymap = null;
637 state.current_context = null;
639 } catch(e) { dump_error(e);}
642 function keyboard(window)
644 this.window = window;
647 keyboard.prototype = {
648 last_key_down_event : null,
649 current_context : null,
650 active_keymap : null,
651 help_timer_ID : null,
652 help_displayed : false,
654 /* If this is non-null, it is used instead of the current buffer's
656 override_keymap : null,
658 set_override_keymap : function (keymap) {
659 /* Clear out any in-progress key sequence. */
660 this.active_keymap = null;
661 this.current_context = null;
662 if (this.help_timer_ID != null)
664 this.window.clearTimeout(this.help_timer_ID);
665 this.help_timer_ID = null;
667 this.override_keymap = keymap;
672 function keyboard_initialize_window(window)
674 window.keyboard = new keyboard(window);
676 window.addEventListener ("keydown", key_down_handler, true /* capture */,
677 false /* ignore untrusted events */);
678 window.addEventListener ("keypress", key_press_handler, true /* capture */,
679 false /* ignore untrusted events */);
682 add_hook("window_initialize_hook", keyboard_initialize_window);
684 function for_each_key_binding(keymap_or_buffer, callback) {
686 if (keymap_or_buffer instanceof conkeror.buffer) {
687 var buffer = keymap_or_buffer;
688 var window = buffer.window;
689 keymap = window.keyboard.override_keymap || buffer.keymap;
691 keymap = keymap_or_buffer;
693 var keymap_stack = [keymap];
694 var binding_stack = [];
695 function helper2(bind) {
696 binding_stack.push(bind);
697 callback(binding_stack);
698 if (bind.keymap && keymap_stack.indexOf(bind.keymap) == -1) {
699 keymap_stack.push(bind.keymap);
706 var unmodified_keys_masked = false;
707 var keycode_masks = [];
709 var keymap = keymap_stack[keymap_stack.length - 1];
710 for (var i in keymap.keycode_bindings) {
711 var b = keymap.keycode_bindings[i];
712 if (!(i in keycode_masks))
713 keycode_masks[i] = [];
715 if (unmodified_keys_masked && ((j & MOD_SHIFT) == j))
717 if (!keycode_masks[i][j]) {
719 keycode_masks[i][j] = true;
723 for (var i in keymap.predicate_bindings) {
724 var bind = keymap.predicate_bindings[i];
726 var p = bind.key.match_function;
727 if (p == match_any_key)
729 if (p == match_any_unmodified_key)
730 unmodified_keys_masked = true;
733 keymap_stack[keymap_stack.length - 1] = keymap.parent;
741 function find_command_in_keymap(keymap_or_buffer, command) {
744 for_each_key_binding(keymap_or_buffer, function (bind_seq) {
745 var bind = bind_seq[bind_seq.length - 1];
746 if (bind.command == command)
747 list.push(format_binding_sequence(bind_seq));
752 define_keymap("key_binding_reader_keymap");
753 define_key(key_binding_reader_keymap, match_any_key, "read-key-binding-key");
755 define_keywords("$buffer", "$keymap");
756 function key_binding_reader(continuation) {
757 keywords(arguments, $prompt = "Describe key:");
759 this.continuation = continuation;
761 if (arguments.$keymap)
762 this.target_keymap = arguments.$keymap;
764 var buffer = arguments.$buffer;
765 var window = buffer.window;
766 this.target_keymap = window.keyboard.override_keymap || buffer.keymap;
769 this.key_sequence = [];
771 minibuffer_input_state.call(this, key_binding_reader_keymap, arguments.$prompt);
773 key_binding_reader.prototype = {
774 __proto__: minibuffer_input_state.prototype,
775 destroy: function () {
776 if (this.continuation)
777 this.continuation.throw(abort());
781 function invalid_key_binding(seq) {
782 var e = new Error(seq.join(" ") + " is undefined");
783 e.key_sequence = seq;
784 e.__proto__ = invalid_key_binding.prototype;
787 invalid_key_binding.prototype = {
788 __proto__: interactive_error.prototype
791 function read_key_binding_key(window, state, event) {
792 var binding = lookup_key_binding(state.target_keymap, event);
794 state.key_sequence.push(format_key_event(event));
796 if (binding == null) {
797 var c = state.continuation;
798 delete state.continuation;
799 window.minibuffer.pop_state();
800 c.throw(invalid_key_binding(state.key_sequence));
804 if (binding.keymap) {
805 window.minibuffer._restore_normal_state();
806 window.minibuffer._input_text = state.key_sequence.join(" ") + " ";
807 state.target_keymap = binding.keymap;
811 var c = state.continuation;
812 delete state.continuation;
814 window.minibuffer.pop_state();
817 c([state.key_sequence, binding]);
819 interactive("read-key-binding-key", function (I) {
820 read_key_binding_key(I.window, I.minibuffer.check_state(key_binding_reader), I.event);
823 minibuffer.prototype.read_key_binding = function () {
825 var s = new key_binding_reader((yield CONTINUATION), forward_keywords(arguments));
827 var result = yield SUSPEND;
828 yield co_return(result);