Add initial OpenSearch search engine support
[conkeror.git] / modules / keyboard.js
blob2460cad1a2c93bfe04e365c9b08752783c94bb1d
1 require("window.js");
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;
16     }
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;
22     }
24     function map(table, keycode, str) {
25         table[keycode] = str.charCodeAt(0);
26     }
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_";
64     for (i in KeyEvent)
65     {
66         /* Check if this is a key binding */
67         if (i.substr(0, prefix.length) == prefix)
68         {
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;
73         }
74     }
77 function get_charcode_mapping_table_from_preferences()
79     var vals = conkeror.get_pref("conkeror.charCodeMappingTable");
80     if (!vals)
81         return null;
82     try {
83         return eval(vals);
84     } catch (e) {
85         return null;
86     }
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();
98     if (!tables)
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)
109                 continue;
110             var obj = charcode_to_keycodes[charcode];
111             if (obj == null)
112                 obj = charcode_to_keycodes[charcode] = [];
113             obj[obj.length] = [x, shifted];
114         }
115     }
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];
121         if (arr.length != 1)
122             continue;
123         let [keycode, shift] = arr[0];
124         let table = shift ? shifted_keycode_to_name : keycode_to_name;
125         table[keycode] = String.fromCharCode(charcode);
126     }
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)
151     if (code == 0)
152         return "<invalid>";
153     var name;
154     if ((modifiers & MOD_SHIFT) && (name = shifted_keycode_to_name[code]))  {
155         modifiers &= ~MOD_SHIFT;
156     } else
157         name = keycode_to_name[code] || ("<" + code + ">");
158     var out = "";
159     if (modifiers)
160     {
161         for (var i = 0; i < modifier_names.length; ++i)
162         {
163             if (modifiers & (1 << i))
164                 out = out + modifier_names[i] + "-";
165         }
166     }
167     out = out + name;
168     return out;
171 function format_key_spec(key) {
172     if (key.match_function) {
173         if (key.match_function == match_any_key)
174             return "<any-key>";
175         if (key.match_function == match_any_unmodified_key)
176             return "<any-unmodified-key>";
177         return "<match-function>";
178     }
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);
190         }).join(" ");
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
196 // represent.
198 function match_any_key (event)
200     return true;
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
211     // the event.
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)
222     return (key.keyCode
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)
230     do {
231         // Check if the key matches the keycode table
232         var mods = get_modifiers(event);
233         var keycode_binds = kmap.keycode_bindings;
234         var arr;;
235         var bind;
236         if ((arr = keycode_binds[event.keyCode]) != null &&
237             (bind = arr[mods]) != null)
238             return bind;
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)
243         {
244             var bind = pred_binds[i];
245             if (bind.key.match_function(event))
246                 return bind;
247         }
248         kmap = kmap.parent;
249     } while (kmap);
250     return null;
253 function match_any_unmodified_key (event)
255     try {
256         return event.charCode
257             && !meta_pressed(event)
258             && !event.ctrlKey;
259     } catch (e) {return false; }
262 function kbd (spec, mods)
264     if (spec.is_kbd)
265         return spec;
267     var results = [];
268     results.is_kbd = true;
270     if (typeof spec == "function")
271         results[0] = {match_function: spec};
273     else if (typeof spec == "string")
274     {
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. */
278         var parts;
279         if (spec.substr(spec.length - 1) == "-")
280         {
281             parts = spec.substr(0, spec.length - 1).split("-");
282             parts.push("-");
283         } else
284             parts = spec.split("-");
285         var parsed_modifiers = 0;
286         if (parts.length > 1)
287         {
288             // Attempt to parse modifiers
289             for (var i = 0; i < parts.length - 1; ++i)
290             {
291                 var k = modifier_names.indexOf(parts[i]);
292                 if (k < 0)
293                     continue;
294                 var mod = 1 << k;
295                 parsed_modifiers |= mod;
296             }
297         }
298         // Attempt to lookup keycode
299         var name = parts[parts.length - 1];
301         if (mods)
302             parsed_modifiers |= mods;
304         if (name.length == 1) {
305             // Charcode, handle specially
307             var codes = charcode_to_keycodes[name.charCodeAt(0)];
308             if (!codes)
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)};
313             }
314         } else {
315             var code = vk_name_to_keycode[name];
316             if (code == null)
317                 throw "Invalid key specification: " + spec;
318             results[0] = {keyCode: code, modifiers: parsed_modifiers};
319         }
320     }
321     else {
322         results[0] = {keyCode: spec, modifiers: ((mods != null)? mods : 0)};
323     }
324     return results;
327 define_keywords("$fallthrough", "$hook", "$category");
328 function define_key_internal(ref, kmap, keys, new_command, new_keymap)
330     keywords(arguments);
332     var args = arguments;
334     var parent_kmap = kmap.parent;
336 outer:
337     for (var i = 0; i < keys.length; ++i) {
338         var key = keys[i];
339         var final_binding = (i == keys.length - 1);
341         /* Replace `bind' with the binding specified by (cmd, fallthrough) */
342         function replace_binding(bind)
343         {
344             if (final_binding) {
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;
351             } else {
352                 if (!bind.keymap)
353                     throw new Error("Key sequence has a non-keymap in prefix");
354                 kmap = bind.keymap;
355             }
356         }
358         function make_binding()
359         {
360             if (final_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,
365                         bound_in: kmap};
366             }
367             else
368             {
369                 let old_kmap = kmap;
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,
375                         bound_in: old_kmap};
376             }
377         }
379         // Check if the specified binding is already present in the kmap
380         if (key.match_function)
381         {
382             var pred_binds = kmap.predicate_bindings;
383             for (var i = 0; i < pred_binds.length; i++)
384             {
385                 var cur_bind = pred_binds[i];
386                 if (cur_bind.key.match_function == key.match_function)
387                 {
388                     replace_binding(cur_bind);
389                     continue outer;
390                 }
391             }
393             if (!final_binding && parent_kmap) {
394                 var parent_pred_binds = parent_kmap.predicate_bindings;
395                 parent_kmap = null;
396                 for (var i = 0; i < parent_pred_binds.length; i++)
397                 {
398                     var cur_bind = parent_pred_binds[i];
399                     if (cur_bind.key.match_function == key.match_function && cur_bind.keymap)
400                     {
401                         parent_kmap = cur_bind.keymap;
402                         break;
403                     }
404                 }
405             }
406             // Not already present, must be added
407             pred_binds.push(make_binding());
408         } else
409         {
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])
415             {
416                 replace_binding(arr[key.modifiers]);
417                 continue outer;
418             }
420             if (!final_binding && parent_kmap) {
421                 var p_keycode_binds = parent_kmap.keycode_bindings;
422                 parent_kmap = null;
423                 var p_arr = p_keycode_binds[key.keyCode];
424                 var p_bind;
425                 if (p_arr && (p_bind = p_arr[key.modifiers]) != null && p_bind.keymap)
426                     parent_kmap = p_bind.keymap;
427             }
429             if (!arr)
430                 arr = (keycode_binds[key.keyCode] = []);
432             arr[key.modifiers] = make_binding();
433         }
434     }
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;
442     try {
443         var ref = get_caller_source_code_reference();
444         
445         if (typeof(keys) == "string" && keys.length > 1)
446             keys = keys.split(" ");
448         if (!(typeof(keys) == "object") || ('is_kbd' in keys) || !(keys instanceof Array))
449             keys = [keys];
451         var new_command = null, new_keymap = null;
452         if (typeof(cmd) == "string" || typeof(cmd) == "function")
453             new_command = cmd;
454         else if (cmd instanceof keymap)
455             new_keymap = cmd;
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));
467                 return;
468             }
469             var key = input_keys[index];
470             for (let i = 0; i < key.length; ++i)
471                 helper(index + 1, output_keys.concat(key[i]));
472         }
474         helper(0, []);
475     } catch (e if (typeof(e) == "string")) {
476         dumpln("Warning: Error occurred while binding keys: " + orig_keys);
477         dumpln(e);
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.");
480     }
483 define_keywords("$parent", "$help", "$name");
484 function keymap ()
486     keywords(arguments);
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)
508     var ev = {};
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;
516     return ev;
519 function key_down_handler(event)
521     var window = this;
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)
537     try{
538         var window = this;
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)
552             return;
554         /* Clear minibuffer message */
555         window.minibuffer.clear();    
557         var binding = null;
558         var done = true;
560         var ctx;
561         if (!state.current_context)
562             ctx = state.current_context = { window: window, key_sequence: [], sticky_modifiers: 0 };
563         else
564             ctx = state.current_context;
566         event.sticky_modifiers = ctx.sticky_modifiers;
567         ctx.sticky_modifiers = 0;
569         ctx.event = event;
571         if (key_press_hook.run(window, ctx, true_event))
572             return;
574         var active_keymap =
575             state.active_keymap ||
576             state.override_keymap ||
577             window.buffers.current.keymap;
578         var overlay_keymap = ctx.overlay_keymap;
580         binding =
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?
587         //
588         // 1) we have a binding, and the binding's fallthrough property is not
589         //    true.
590         //
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.
593         //
594         if (!binding || !binding.fallthrough)
595         {
596             true_event.preventDefault();
597             true_event.stopPropagation();
598         }
600         // Finally, process the binding.
601         ctx.key_sequence.push(format_key_event(event));
602         if (binding) {
603             if (binding.keymap) {
604                 if (binding.hook)
605                     binding.hook.call(null, ctx, active_keymap, overlay_keymap);
606                 state.active_keymap = binding.keymap;
607                 if (!state.help_displayed)
608                 {
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);
614                 }
615                 else
616                     window.minibuffer.show(ctx.key_sequence.join(" "));
618                 // We're going for another round
619                 done = false;
620             } else if (binding.command) {
621                 call_interactively(ctx, binding.command);
622             }
623         } else {
624             window.minibuffer.message(ctx.key_sequence.join(" ") + " is undefined");
625         }
627         // Clean up if we're done
628         if (done)
629         {
630             if (state.help_timer_ID != null)
631             {
632                 window.clearTimeout(state.help_timer_ID);
633                 state.help_timer_ID = null;
634             }
635             state.help_displayed = false;
636             state.active_keymap = null;
637             state.current_context = null;
638         }
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
655      * keymap. */
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)
663         {
664             this.window.clearTimeout(this.help_timer_ID);
665             this.help_timer_ID = null;
666         }
667         this.override_keymap = keymap;
668     }
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) {
685     var keymap;
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;
690     } else {
691         keymap = keymap_or_buffer;
692     }
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);
700             helper();
701             keymap_stack.pop();
702         }
703         binding_stack.pop();
704     }
705     function helper() {
706         var unmodified_keys_masked = false;
707         var keycode_masks = [];
708         while (true) {
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] = [];
714                 for (var j in b) {
715                     if (unmodified_keys_masked && ((j & MOD_SHIFT) == j))
716                         continue;
717                     if (!keycode_masks[i][j]) {
718                         helper2(b[j]);
719                         keycode_masks[i][j] = true;
720                     }
721                 }
722             }
723             for (var i in  keymap.predicate_bindings) {
724                 var bind = keymap.predicate_bindings[i];
725                 helper2(bind);
726                 var p = bind.key.match_function;
727                 if (p == match_any_key)
728                     return;
729                 if (p == match_any_unmodified_key)
730                     unmodified_keys_masked = true;
731             }
732             if (keymap.parent)
733                 keymap_stack[keymap_stack.length - 1] = keymap.parent;
734             else
735                 break;
736         }
737     }
738     helper();
741 function find_command_in_keymap(keymap_or_buffer, command) {
742     var list = [];
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));
748         });
749     return list;
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;
763     else {
764         var buffer = arguments.$buffer;
765         var window = buffer.window;
766         this.target_keymap = window.keyboard.override_keymap || buffer.keymap;
767     }
769     this.key_sequence = [];
770     
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());
778     }
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;
785     return e;
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));
801         return;
802     }
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;
808         return;
809     }
811     var c = state.continuation;
812     delete state.continuation;
814     window.minibuffer.pop_state();
816     if (c != null)
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 () {
824     keywords(arguments);
825     var s = new key_binding_reader((yield CONTINUATION), forward_keywords(arguments));
826     this.push_state(s);
827     var result = yield SUSPEND;
828     yield co_return(result);