rename browser-follow-next/previous to follow-next/previous
[conkeror.git] / modules / keyboard.js
blobeca6d75be6e6ee69c88ea63056863b4e70de31d1
1 /**
2  * (C) Copyright 2004-2007 Shawn Betts
3  * (C) Copyright 2007-2008 John J. Foerch
4  * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
5  *
6  * Use, modification, and distribution are subject to the terms specified in the
7  * COPYING file.
8 **/
10 require("window.js");
11 require("command-line.js");
13 function get_default_keycode_to_charcode_tables() {
15     const KeyEvent = Ci.nsIDOMKeyEvent;
17     var unshifted_table = [];
18     var shifted_table = [];
19     for (var i = 0; i < 26; ++i) {
20         var keycode = KeyEvent.DOM_VK_A + i;
21         var charcode = keycode; // keycodes A-Z are same as ascii
22         shifted_table[keycode] = charcode;
24         unshifted_table[keycode] = "a".charCodeAt(0) + i;
25     }
27     for (var i = 0; i <= 9; ++i) {
28         var keycode = KeyEvent.DOM_VK_0 + i;
29         var charcode = keycode; // keycodes 0-9 are same as ascii
30         unshifted_table[keycode] = charcode;
31     }
33     function map(table, keycode, str) {
34         table[keycode] = str.charCodeAt(0);
35     }
37     map(unshifted_table, KeyEvent.DOM_VK_BACK_SLASH, "\\");
38     map(unshifted_table, KeyEvent.DOM_VK_OPEN_BRACKET, "[");
39     map(unshifted_table, KeyEvent.DOM_VK_CLOSE_BRACKET, "]");
40     map(unshifted_table, KeyEvent.DOM_VK_SEMICOLON, ";");
41     map(unshifted_table, KeyEvent.DOM_VK_COMMA, ",");
42     map(unshifted_table, KeyEvent.DOM_VK_PERIOD, ".");
43     map(unshifted_table, KeyEvent.DOM_VK_SLASH, "/");
44     map(unshifted_table, KeyEvent.DOM_VK_SUBTRACT, "-");
45     map(unshifted_table, KeyEvent.DOM_VK_EQUALS, "=");
47     map(shifted_table, KeyEvent.DOM_VK_SEMICOLON, ":");
48     map(shifted_table, KeyEvent.DOM_VK_1, "!");
49     map(shifted_table, KeyEvent.DOM_VK_2, "@");
50     map(shifted_table, KeyEvent.DOM_VK_3, "#");
51     map(shifted_table, KeyEvent.DOM_VK_4, "$");
52     map(shifted_table, KeyEvent.DOM_VK_5, "%");
53     map(shifted_table, KeyEvent.DOM_VK_6, "^");
54     map(shifted_table, KeyEvent.DOM_VK_7, "&");
55     map(shifted_table, KeyEvent.DOM_VK_8, "*");
56     map(shifted_table, KeyEvent.DOM_VK_9, "(");
57     map(shifted_table, KeyEvent.DOM_VK_0, ")");
58     map(shifted_table, KeyEvent.DOM_VK_EQUALS, "+");
59     map(shifted_table, KeyEvent.DOM_VK_SUBTRACT, "_");
60     map(shifted_table, KeyEvent.DOM_VK_COMMA, "<");
61     map(shifted_table, KeyEvent.DOM_VK_PERIOD, ">");
62     map(shifted_table, KeyEvent.DOM_VK_SLASH, "?");
64     return [unshifted_table, shifted_table];
67 /* Generate vk name table  */
68 var keycode_to_vk_name = [];
69 var vk_name_to_keycode = {};
71     let KeyEvent = Ci.nsIDOMKeyEvent;
72     let prefix = "DOM_VK_";
73     for (i in KeyEvent)
74     {
75         /* Check if this is a key binding */
76         if (i.substr(0, prefix.length) == prefix)
77         {
78             let name = i.substr(prefix.length).toLowerCase();
79             let code = KeyEvent[i];
80             keycode_to_vk_name[code] = name;
81             vk_name_to_keycode[name] = code;
82         }
83     }
86 function get_charcode_mapping_table_from_preferences()
88     var vals = conkeror.get_pref("conkeror.charCodeMappingTable");
89     if (!vals)
90         return null;
91     try {
92         return eval(vals);
93     } catch (e) {
94         return null;
95     }
98 var unshifted_keycode_to_charcode = null;
99 var shifted_keycode_to_charcode = null;
100 var charcode_to_keycodes = null;
102 var keycode_to_name = null;
103 var shifted_keycode_to_name = null;
104 function load_charcode_mapping_table()
106     var tables = get_charcode_mapping_table_from_preferences();
107     if (!tables)
108         tables = get_default_keycode_to_charcode_tables();
109     let [unshifted_table, shifted_table] = tables;
110     unshifted_keycode_to_charcode = unshifted_table;
111     shifted_keycode_to_charcode = shifted_table;
112     charcode_to_keycodes = [];
113     for each (let table in tables) {
114         var shifted = (table == tables[1]);
115         for (let x in table) {
116             let charcode = table[x];
117             if (charcode == null)
118                 continue;
119             var obj = charcode_to_keycodes[charcode];
120             if (obj == null)
121                 obj = charcode_to_keycodes[charcode] = [];
122             obj[obj.length] = [x, shifted];
123         }
124     }
126     keycode_to_name = keycode_to_vk_name.slice();
127     shifted_keycode_to_name = [];
128     for (let charcode in charcode_to_keycodes) {
129         let arr = charcode_to_keycodes[charcode];
130         if (arr.length != 1)
131             continue;
132         let [keycode, shift] = arr[0];
133         let table = shift ? shifted_keycode_to_name : keycode_to_name;
134         table[keycode] = String.fromCharCode(charcode);
135     }
137 load_charcode_mapping_table();
139 interactive("keyboard-setup", function (I) {
140     make_chrome_window("chrome://conkeror/content/keyboard-setup.xul");
143 command_line_handler("keyboard-setup", true, function () {
144     make_chrome_window("chrome://conkeror/content/keyboard-setup.xul");
148 var abort_key = null;
150 const MOD_CTRL = 0x1;
151 const MOD_META = 0x2;
152 const MOD_SHIFT = 0x4;
154 // Note: For elements of the modifier_names array, an element at index
155 // i should correspond to the modifier mask (1 << i).
156 var modifier_names = ["C", "M", "S"];
158 function format_key_press(code, modifiers)
160     if (code == 0)
161         return "<invalid>";
162     var name;
163     if ((modifiers & MOD_SHIFT) && (name = shifted_keycode_to_name[code]))  {
164         modifiers &= ~MOD_SHIFT;
165     } else
166         name = keycode_to_name[code] || ("<" + code + ">");
167     var out = "";
168     if (modifiers)
169     {
170         for (var i = 0; i < modifier_names.length; ++i)
171         {
172             if (modifiers & (1 << i))
173                 out = out + modifier_names[i] + "-";
174         }
175     }
176     out = out + name;
177     return out;
180 function format_key_spec(key) {
181     if (key.match_function) {
182         if (key.match_function == match_any_key)
183             return "<any-key>";
184         if (key.match_function == match_any_unmodified_key)
185             return "<any-unmodified-key>";
186         return "<match-function>";
187     }
188     return format_key_press(key.keyCode, key.modifiers);
191 function format_key_event(event)
193     return format_key_press(event.keyCode, get_modifiers(event));
196 function format_binding_sequence(seq) {
197     return seq.map(function (x) {
198             return format_key_spec(x.key);
199         }).join(" ");
202 // Key Matching Functions.  These are functions that may be passed to kbd
203 // in place of key code or char code.  They take an event object as their
204 // argument and turn true if the event matches the class of keys that they
205 // represent.
207 function match_any_key (event)
209     return true;
212 function meta_pressed (event)
214     return event.altKey || event.metaKey;
217 function get_modifiers(event)
219     // Shift is always included in the modifiers, if it is included in
220     // the event.
221     return (event.ctrlKey ? MOD_CTRL:0) |
222         (meta_pressed(event) ? MOD_META:0) |
223         (event.shiftKey ? MOD_SHIFT: 0) |
224         event.sticky_modifiers;
227 /* This function is no longer used for normal keymap lookups.  It is
228  * only used to check if the current key matches the abort key. */
229 function match_binding(key, event)
231     return (key.keyCode
232             && event.keyCode == key.keyCode
233             && get_modifiers(event) == key.modifiers)
234         || (key.match_function && key.match_function (event));
237 function lookup_key_binding(kmap, event)
239     do {
240         // Check if the key matches the keycode table
241         var mods = get_modifiers(event);
242         var keycode_binds = kmap.keycode_bindings;
243         var arr;;
244         var bind;
245         if ((arr = keycode_binds[event.keyCode]) != null &&
246             (bind = arr[mods]) != null)
247             return bind;
249         // Check if the key matches a predicate
250         var pred_binds = kmap.predicate_bindings;
251         for (var i = 0; i < pred_binds.length; ++i)
252         {
253             var bind = pred_binds[i];
254             if (bind.key.match_function(event))
255                 return bind;
256         }
257         kmap = kmap.parent;
258     } while (kmap);
259     return null;
262 function match_any_unmodified_key (event)
264     try {
265         return event.charCode
266             && !meta_pressed(event)
267             && !event.ctrlKey;
268     } catch (e) {return false; }
271 function kbd (spec, mods)
273     if (spec.is_kbd)
274         return spec;
276     var results = [];
277     results.is_kbd = true;
279     if (typeof spec == "function")
280         results[0] = {match_function: spec};
282     else if (typeof spec == "string")
283     {
284         /* Attempt to parse a key specification.  In order to allow
285          * the user to specify the "-" key literally, special case the
286          * parsing of that. */
287         var parts;
288         if (spec.substr(spec.length - 1) == "-")
289         {
290             parts = spec.substr(0, spec.length - 1).split("-");
291             parts.push("-");
292         } else
293             parts = spec.split("-");
294         var parsed_modifiers = 0;
295         if (parts.length > 1)
296         {
297             // Attempt to parse modifiers
298             for (var i = 0; i < parts.length - 1; ++i)
299             {
300                 var k = modifier_names.indexOf(parts[i]);
301                 if (k < 0)
302                     continue;
303                 var mod = 1 << k;
304                 parsed_modifiers |= mod;
305             }
306         }
307         // Attempt to lookup keycode
308         var name = parts[parts.length - 1];
310         if (mods)
311             parsed_modifiers |= mods;
313         if (name.length == 1) {
314             // Charcode, handle specially
316             var codes = charcode_to_keycodes[name.charCodeAt(0)];
317             if (!codes)
318                 throw "Invalid key specification: " + spec;
320             for each (let [keycode, shift] in codes) {
321                 results[results.length] = {keyCode: keycode, modifiers: parsed_modifiers | (shift ? MOD_SHIFT : 0)};
322             }
323         } else {
324             var code = vk_name_to_keycode[name];
325             if (code == null)
326                 throw "Invalid key specification: " + spec;
327             results[0] = {keyCode: code, modifiers: parsed_modifiers};
328         }
329     }
330     else {
331         results[0] = {keyCode: spec, modifiers: ((mods != null)? mods : 0)};
332     }
333     return results;
336 define_keywords("$fallthrough", "$hook", "$category");
337 function define_key_internal(ref, kmap, keys, new_command, new_keymap)
339     keywords(arguments);
341     var args = arguments;
343     var parent_kmap = kmap.parent;
345 outer:
346     for (var i = 0; i < keys.length; ++i) {
347         var key = keys[i];
348         var final_binding = (i == keys.length - 1);
350         /* Replace `bind' with the binding specified by (cmd, fallthrough) */
351         function replace_binding(bind)
352         {
353             if (final_binding) {
354                 bind.command = new_command;
355                 bind.keymap = new_keymap;
356                 bind.fallthrough = args.$fallthrough;
357                 bind.hook = args.$hook;
358                 bind.source_code_reference = ref;
359                 bind.category = args.$category;
360             } else {
361                 if (!bind.keymap)
362                     throw new Error("Key sequence has a non-keymap in prefix");
363                 kmap = bind.keymap;
364             }
365         }
367         function make_binding()
368         {
369             if (final_binding) {
370                 return {key: key, fallthrough: args.$fallthrough, hook: args.$hook,
371                         command: new_command, keymap: new_keymap,
372                         source_code_reference: ref,
373                         category: args.$category,
374                         bound_in: kmap};
375             }
376             else
377             {
378                 let old_kmap = kmap;
379                 // Check for a corresponding binding a parent
380                 kmap = new keymap($parent = parent_kmap);
381                 kmap.bound_in = old_kmap;
382                 return {key: key, keymap: kmap,
383                         source_code_reference: ref,
384                         bound_in: old_kmap};
385             }
386         }
388         // Check if the specified binding is already present in the kmap
389         if (key.match_function)
390         {
391             var pred_binds = kmap.predicate_bindings;
392             for (var i = 0; i < pred_binds.length; i++)
393             {
394                 var cur_bind = pred_binds[i];
395                 if (cur_bind.key.match_function == key.match_function)
396                 {
397                     replace_binding(cur_bind);
398                     continue outer;
399                 }
400             }
402             if (!final_binding && parent_kmap) {
403                 var parent_pred_binds = parent_kmap.predicate_bindings;
404                 parent_kmap = null;
405                 for (var i = 0; i < parent_pred_binds.length; i++)
406                 {
407                     var cur_bind = parent_pred_binds[i];
408                     if (cur_bind.key.match_function == key.match_function && cur_bind.keymap)
409                     {
410                         parent_kmap = cur_bind.keymap;
411                         break;
412                     }
413                 }
414             }
415             // Not already present, must be added
416             pred_binds.push(make_binding());
417         } else
418         {
419             // This is a binding by keycode: look it up in the table
420             var keycode_binds = kmap.keycode_bindings;
421             var arr = keycode_binds[key.keyCode];
423             if (arr && arr[key.modifiers])
424             {
425                 replace_binding(arr[key.modifiers]);
426                 continue outer;
427             }
429             if (!final_binding && parent_kmap) {
430                 var p_keycode_binds = parent_kmap.keycode_bindings;
431                 parent_kmap = null;
432                 var p_arr = p_keycode_binds[key.keyCode];
433                 var p_bind;
434                 if (p_arr && (p_bind = p_arr[key.modifiers]) != null && p_bind.keymap)
435                     parent_kmap = p_bind.keymap;
436             }
438             if (!arr)
439                 arr = (keycode_binds[key.keyCode] = []);
441             arr[key.modifiers] = make_binding();
442         }
443     }
446 // bind key to either the keymap or command in the keymap, kmap
447 define_keywords("$fallthrough", "$hook", "$category");
448 function define_key(kmap, keys, cmd)
450     var orig_keys = keys;
451     try {
452         var ref = get_caller_source_code_reference();
454         if (typeof(keys) == "string" && keys.length > 1)
455             keys = keys.split(" ");
457         if (!(typeof(keys) == "object") || ('is_kbd' in keys) || !(keys instanceof Array))
458             keys = [keys];
460         var new_command = null, new_keymap = null;
461         if (typeof(cmd) == "string" || typeof(cmd) == "function")
462             new_command = cmd;
463         else if (cmd instanceof keymap)
464             new_keymap = cmd;
465         else if (cmd != null)
466             throw new Error("Invalid `cmd' argument: " + cmd);
468         var args = arguments;
470         var input_keys = keys.map(function(x) kbd(x));
472         function helper(index, output_keys) {
473             if (index == input_keys.length) {
474                 define_key_internal(ref, kmap, output_keys, new_command, new_keymap,
475                                     forward_keywords(args));
476                 return;
477             }
478             var key = input_keys[index];
479             for (let i = 0; i < key.length; ++i)
480                 helper(index + 1, output_keys.concat(key[i]));
481         }
483         helper(0, []);
484     } catch (e if (typeof(e) == "string")) {
485         dumpln("Warning: Error occurred while binding keys: " + orig_keys);
486         dumpln(e);
487         dumpln("This may be due to an incorrect keyboard setup.");
488         dumpln("You may want to use the -keyboard-setup command-line option to fix your keyboard setup.");
489     }
492 define_keywords("$parent", "$help", "$name");
493 function keymap ()
495     keywords(arguments);
496     /* For efficiency, a table indexed by the key code, and then by
497      * the modifiers is used to lookup key bindings, rather than
498      * looping through all bindings in the key map to find one.  The
499      * array keycode_bindings is indexed by the keyCode; if the
500      * corresponding element for a keyCode is non-null, it is itself
501      * an array indexed by the result of get_modifiers (i.e. from 0 to 7).
502      * As before, match_function-based bindings are stored as a simple
503      * list, predicate_bindings. */
504     this.parent = arguments.$parent;
505     this.keycode_bindings = [];
506     this.predicate_bindings = [];
507     this.help = arguments.$help;
508     this.name = arguments.$name;
511 function define_keymap(name) {
512     this[name] = new keymap($name = name, forward_keywords(arguments));
515 function copy_event(event)
517     var ev = {};
518     ev.keyCode = event.keyCode;
519     ev.charCode = event.charCode;
520     ev.ctrlKey = event.ctrlKey;
521     ev.metaKey = event.metaKey;
522     ev.altKey = event.altKey;
523     ev.shiftKey = event.shiftKey;
524     ev.sticky_modifiers = event.sticky_modifiers;
525     return ev;
528 function key_down_handler(event)
530     var window = this;
531     //window.dumpln("key down: " + conkeror.format_key_press(event.keyCode, conkeror.get_modifiers(event)));
533     var state = window.keyboard;
534     state.last_key_down_event = copy_event(event);
535     state.last_char_code = null;
536     state.last_key_code = null;
539 define_variable("keyboard_key_sequence_help_timeout", 0,
540                 "Delay (in millseconds) before the current key sequence prefix is displayed in the minibuffer.");
542 define_window_local_hook("key_press_hook", RUN_HOOK_UNTIL_SUCCESS);
544 function key_press_handler(true_event)
546     try{
547         var window = this;
548         var state = window.keyboard;
550         /* ASSERT(state.last_key_down_event != null); */
552         var event = state.last_key_down_event;
553         event.charCode = true_event.charCode;
555         // If the true_event includes a keyCode, we can just use that
556         if (true_event.keyCode)
557             event.keyCode = true_event.keyCode;
559         /* Filter out events from keys like the Windows/Super/Hyper key */
560         if (event.keyCode == 0)
561             return;
563         /* Clear minibuffer message */
564         window.minibuffer.clear();
566         var binding = null;
567         var done = true;
569         var ctx;
570         if (!state.current_context)
571             ctx = state.current_context = { window: window, key_sequence: [], sticky_modifiers: 0 };
572         else
573             ctx = state.current_context;
575         event.sticky_modifiers = ctx.sticky_modifiers;
576         ctx.sticky_modifiers = 0;
578         ctx.event = event;
580         if (key_press_hook.run(window, ctx, true_event))
581             return;
583         var active_keymap =
584             state.active_keymap ||
585             state.override_keymap ||
586             window.buffers.current.keymap;
587         var overlay_keymap = ctx.overlay_keymap;
589         binding =
590             lookup_key_binding(active_keymap, event) ||
591             (overlay_keymap && lookup_key_binding(overlay_keymap, event));
593         ctx.overlay_keymap = null;
595         // Should we stop this event from being processed by the gui?
596         //
597         // 1) we have a binding, and the binding's fallthrough property is not
598         //    true.
599         //
600         // 2) we are in the middle of a key sequence, and we need to say that
601         //    the key sequence given has no command.
602         //
603         if (!binding || !binding.fallthrough)
604         {
605             true_event.preventDefault();
606             true_event.stopPropagation();
607         }
609         // Finally, process the binding.
610         ctx.key_sequence.push(format_key_event(event));
611         if (binding) {
612             if (binding.keymap) {
613                 if (binding.hook)
614                     binding.hook.call(null, ctx, active_keymap, overlay_keymap);
615                 state.active_keymap = binding.keymap;
616                 if (!state.help_displayed)
617                 {
618                     state.help_timer_ID = window.setTimeout(function () {
619                             window.minibuffer.show(ctx.key_sequence.join(" "));
620                             state.help_displayed = true;
621                             state.help_timer_ID = null;
622                         }, keyboard_key_sequence_help_timeout);
623                 }
624                 else
625                     window.minibuffer.show(ctx.key_sequence.join(" "));
627                 // We're going for another round
628                 done = false;
629             } else if (binding.command) {
630                 call_interactively(ctx, binding.command);
631             }
632         } else {
633             window.minibuffer.message(ctx.key_sequence.join(" ") + " is undefined");
634         }
636         // Clean up if we're done
637         if (done)
638         {
639             if (state.help_timer_ID != null)
640             {
641                 window.clearTimeout(state.help_timer_ID);
642                 state.help_timer_ID = null;
643             }
644             state.help_displayed = false;
645             state.active_keymap = null;
646             state.current_context = null;
647         }
648     } catch(e) { dump_error(e);}
651 function keyboard(window)
653     this.window = window;
656 keyboard.prototype = {
657     last_key_down_event : null,
658     current_context : null,
659     active_keymap : null,
660     help_timer_ID : null,
661     help_displayed : false,
663     /* If this is non-null, it is used instead of the current buffer's
664      * keymap. */
665     override_keymap : null,
667     set_override_keymap : function (keymap) {
668         /* Clear out any in-progress key sequence. */
669         this.active_keymap = null;
670         this.current_context = null;
671         if (this.help_timer_ID != null)
672         {
673             this.window.clearTimeout(this.help_timer_ID);
674             this.help_timer_ID = null;
675         }
676         this.override_keymap = keymap;
677     }
681 function keyboard_initialize_window(window)
683     window.keyboard = new keyboard(window);
685     window.addEventListener ("keydown", key_down_handler, true /* capture */,
686                             false /* ignore untrusted events */);
687     window.addEventListener ("keypress", key_press_handler, true /* capture */,
688                             false /* ignore untrusted events */);
691 add_hook("window_initialize_hook", keyboard_initialize_window);
693 function for_each_key_binding(keymap_or_buffer, callback) {
694     var keymap;
695     if (keymap_or_buffer instanceof conkeror.buffer) {
696         var buffer = keymap_or_buffer;
697         var window = buffer.window;
698         keymap = window.keyboard.override_keymap || buffer.keymap;
699     } else {
700         keymap = keymap_or_buffer;
701     }
702     var keymap_stack = [keymap];
703     var binding_stack = [];
704     function helper2(bind) {
705         binding_stack.push(bind);
706         callback(binding_stack);
707         if (bind.keymap && keymap_stack.indexOf(bind.keymap) == -1) {
708             keymap_stack.push(bind.keymap);
709             helper();
710             keymap_stack.pop();
711         }
712         binding_stack.pop();
713     }
714     function helper() {
715         var unmodified_keys_masked = false;
716         var keycode_masks = [];
717         while (true) {
718             var keymap = keymap_stack[keymap_stack.length - 1];
719             for (var i in keymap.keycode_bindings) {
720                 var b = keymap.keycode_bindings[i];
721                 if (!(i in keycode_masks))
722                     keycode_masks[i] = [];
723                 for (var j in b) {
724                     if (unmodified_keys_masked && ((j & MOD_SHIFT) == j))
725                         continue;
726                     if (!keycode_masks[i][j]) {
727                         helper2(b[j]);
728                         keycode_masks[i][j] = true;
729                     }
730                 }
731             }
732             for (var i in  keymap.predicate_bindings) {
733                 var bind = keymap.predicate_bindings[i];
734                 helper2(bind);
735                 var p = bind.key.match_function;
736                 if (p == match_any_key)
737                     return;
738                 if (p == match_any_unmodified_key)
739                     unmodified_keys_masked = true;
740             }
741             if (keymap.parent)
742                 keymap_stack[keymap_stack.length - 1] = keymap.parent;
743             else
744                 break;
745         }
746     }
747     helper();
750 function find_command_in_keymap(keymap_or_buffer, command) {
751     var list = [];
753     for_each_key_binding(keymap_or_buffer, function (bind_seq) {
754             var bind = bind_seq[bind_seq.length - 1];
755             if (bind.command == command)
756                 list.push(format_binding_sequence(bind_seq));
757         });
758     return list;
761 define_keymap("key_binding_reader_keymap");
762 define_key(key_binding_reader_keymap, match_any_key, "read-key-binding-key");
764 define_keywords("$buffer", "$keymap");
765 function key_binding_reader(continuation) {
766     keywords(arguments, $prompt = "Describe key:");
768     this.continuation = continuation;
770     if (arguments.$keymap)
771         this.target_keymap = arguments.$keymap;
772     else {
773         var buffer = arguments.$buffer;
774         var window = buffer.window;
775         this.target_keymap = window.keyboard.override_keymap || buffer.keymap;
776     }
778     this.key_sequence = [];
780     minibuffer_input_state.call(this, key_binding_reader_keymap, arguments.$prompt);
782 key_binding_reader.prototype = {
783     __proto__: minibuffer_input_state.prototype,
784     destroy: function () {
785         if (this.continuation)
786             this.continuation.throw(abort());
787     }
790 function invalid_key_binding(seq) {
791     var e = new Error(seq.join(" ") + " is undefined");
792     e.key_sequence = seq;
793     e.__proto__ = invalid_key_binding.prototype;
794     return e;
796 invalid_key_binding.prototype = {
797     __proto__: interactive_error.prototype
800 function read_key_binding_key(window, state, event) {
801     var binding = lookup_key_binding(state.target_keymap, event);
803     state.key_sequence.push(format_key_event(event));
805     if (binding == null) {
806         var c = state.continuation;
807         delete state.continuation;
808         window.minibuffer.pop_state();
809         c.throw(invalid_key_binding(state.key_sequence));
810         return;
811     }
813     if (binding.keymap) {
814         window.minibuffer._restore_normal_state();
815         window.minibuffer._input_text = state.key_sequence.join(" ") + " ";
816         state.target_keymap = binding.keymap;
817         return;
818     }
820     var c = state.continuation;
821     delete state.continuation;
823     window.minibuffer.pop_state();
825     if (c != null)
826         c([state.key_sequence, binding]);
828 interactive("read-key-binding-key", function (I) {
829     read_key_binding_key(I.window, I.minibuffer.check_state(key_binding_reader), I.event);
832 minibuffer.prototype.read_key_binding = function () {
833     keywords(arguments);
834     var s = new key_binding_reader((yield CONTINUATION), forward_keywords(arguments));
835     this.push_state(s);
836     var result = yield SUSPEND;
837     yield co_return(result);