Added API to manipulate external launchers for MIME types.
[conkeror.git] / modules / keyboard.js
blob4a974bb8474e4c4e681659695ce090f374cb6f2d
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", null, 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             && !event.sticky_modifiers;
269     } catch (e) {return false; }
272 function kbd (spec, mods)
274     if (spec.is_kbd)
275         return spec;
277     var results = [];
278     results.is_kbd = true;
280     if (typeof spec == "function")
281         results[0] = {match_function: spec};
283     else if (typeof spec == "string")
284     {
285         /* Attempt to parse a key specification.  In order to allow
286          * the user to specify the "-" key literally, special case the
287          * parsing of that. */
288         var parts;
289         if (spec.substr(spec.length - 1) == "-")
290         {
291             parts = spec.substr(0, spec.length - 1).split("-");
292             parts.push("-");
293         } else
294             parts = spec.split("-");
295         var parsed_modifiers = 0;
296         if (parts.length > 1)
297         {
298             // Attempt to parse modifiers
299             for (var i = 0; i < parts.length - 1; ++i)
300             {
301                 var k = modifier_names.indexOf(parts[i]);
302                 if (k < 0)
303                     continue;
304                 var mod = 1 << k;
305                 parsed_modifiers |= mod;
306             }
307         }
308         // Attempt to lookup keycode
309         var name = parts[parts.length - 1];
311         if (mods)
312             parsed_modifiers |= mods;
314         if (name.length == 1) {
315             // Charcode, handle specially
317             var codes = charcode_to_keycodes[name.charCodeAt(0)];
318             if (!codes)
319                 throw "Invalid key specification: " + spec;
321             for each (let [keycode, shift] in codes) {
322                 results[results.length] = {keyCode: keycode, modifiers: parsed_modifiers | (shift ? MOD_SHIFT : 0)};
323             }
324         } else {
325             var code = vk_name_to_keycode[name];
326             if (code == null)
327                 throw "Invalid key specification: " + spec;
328             results[0] = {keyCode: code, modifiers: parsed_modifiers};
329         }
330     }
331     else {
332         results[0] = {keyCode: spec, modifiers: ((mods != null)? mods : 0)};
333     }
334     return results;
337 define_keywords("$fallthrough", "$category");
338 function define_key_internal(ref, kmap, keys, new_command, new_keymap)
340     keywords(arguments);
342     var args = arguments;
344     var parent_kmap = kmap.parent;
346 outer:
347     for (var i = 0; i < keys.length; ++i) {
348         var key = keys[i];
349         var final_binding = (i == keys.length - 1);
351         /* Replace `bind' with the binding specified by (cmd, fallthrough) */
352         function replace_binding(bind)
353         {
354             if (final_binding) {
355                 bind.command = new_command;
356                 bind.keymap = new_keymap;
357                 bind.fallthrough = args.$fallthrough;
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,
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", "$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     function show_partial_key_sequence (window, state, ctx) {
547         if (!state.help_displayed)
548         {
549             state.help_timer_ID = window.setTimeout(function () {
550                 window.minibuffer.show(ctx.key_sequence.join(" "));
551                 state.help_displayed = true;
552                 state.help_timer_ID = null;
553             }, keyboard_key_sequence_help_timeout);
554         }
555         else
556             window.minibuffer.show(ctx.key_sequence.join(" "));
557     }
558     try{
559         var window = this;
560         var state = window.keyboard;
562         /* ASSERT(state.last_key_down_event != null); */
564         var event = state.last_key_down_event;
565         event.charCode = true_event.charCode;
567         // If the true_event includes a keyCode, we can just use that
568         if (true_event.keyCode)
569             event.keyCode = true_event.keyCode;
571         /* Filter out events from keys like the Windows/Super/Hyper key */
572         if (event.keyCode == 0)
573             return;
575         /* Clear minibuffer message */
576         window.minibuffer.clear();
578         var binding = null;
579         var done = true;
581         var ctx;
582         if (!state.current_context)
583             ctx = state.current_context = { window: window, key_sequence: [], sticky_modifiers: 0 };
584         else
585             ctx = state.current_context;
587         event.sticky_modifiers = ctx.sticky_modifiers;
588         ctx.sticky_modifiers = 0;
590         ctx.event = event;
592         if (key_press_hook.run(window, ctx, true_event))
593             return;
595         var top_keymap =
596             state.override_keymap ||
597             window.buffers.current.keymap;
599         var active_keymap =
600             state.active_keymap ||
601             top_keymap;
603         var overlay_keymap = ctx.overlay_keymap;
605         binding =
606             (overlay_keymap && lookup_key_binding(overlay_keymap, event)) ||
607             lookup_key_binding(active_keymap, event) ;
609         // Should we stop this event from being processed by the gui?
610         //
611         // 1) we have a binding, and the binding's fallthrough property is not
612         //    true.
613         //
614         // 2) we are in the middle of a key sequence, and we need to say that
615         //    the key sequence given has no command.
616         //
617         if (!binding || !binding.fallthrough)
618         {
619             true_event.preventDefault();
620             true_event.stopPropagation();
621         }
623         // Finally, process the binding.
624         ctx.key_sequence.push(format_key_event(event));
625         if (binding) {
626             if (binding.keymap) {
627                 state.active_keymap = binding.keymap;
628                 show_partial_key_sequence(window, state, ctx);
629                 // We're going for another round
630                 done = false;
631             } else if (binding.command) {
632                 call_interactively(ctx, binding.command);
633                 if (interactive_commands.get(binding.command).prefix) {
634                     state.active_keymap = null;
635                     show_partial_key_sequence(window, state, ctx);
636                     done = false;
637                 }
638             }
639         } else {
640             window.minibuffer.message(ctx.key_sequence.join(" ") + " is undefined");
641         }
643         // Clean up if we're done
644         if (done)
645         {
646             if (state.help_timer_ID != null)
647             {
648                 window.clearTimeout(state.help_timer_ID);
649                 state.help_timer_ID = null;
650             }
651             state.help_displayed = false;
652             state.active_keymap = null;
653             state.current_context = null;
654         }
655     } catch(e) { dump_error(e);}
658 function keyboard(window)
660     this.window = window;
663 keyboard.prototype = {
664     last_key_down_event : null,
665     current_context : null,
666     active_keymap : null,
667     help_timer_ID : null,
668     help_displayed : false,
670     /* If this is non-null, it is used instead of the current buffer's
671      * keymap. */
672     override_keymap : null,
674     set_override_keymap : function (keymap) {
675         /* Clear out any in-progress key sequence. */
676         this.active_keymap = null;
677         this.current_context = null;
678         if (this.help_timer_ID != null)
679         {
680             this.window.clearTimeout(this.help_timer_ID);
681             this.help_timer_ID = null;
682         }
683         this.override_keymap = keymap;
684     }
688 function keyboard_initialize_window(window)
690     window.keyboard = new keyboard(window);
692     window.addEventListener ("keydown", key_down_handler, true /* capture */,
693                             false /* ignore untrusted events */);
694     window.addEventListener ("keypress", key_press_handler, true /* capture */,
695                             false /* ignore untrusted events */);
698 add_hook("window_initialize_hook", keyboard_initialize_window);
700 function for_each_key_binding(keymap_or_buffer, callback) {
701     var keymap;
702     if (keymap_or_buffer instanceof conkeror.buffer) {
703         var buffer = keymap_or_buffer;
704         var window = buffer.window;
705         keymap = window.keyboard.override_keymap || buffer.keymap;
706     } else {
707         keymap = keymap_or_buffer;
708     }
709     var keymap_stack = [keymap];
710     var binding_stack = [];
711     function helper2(bind) {
712         binding_stack.push(bind);
713         callback(binding_stack);
714         if (bind.keymap && keymap_stack.indexOf(bind.keymap) == -1) {
715             keymap_stack.push(bind.keymap);
716             helper();
717             keymap_stack.pop();
718         }
719         binding_stack.pop();
720     }
721     function helper() {
722         var unmodified_keys_masked = false;
723         var keycode_masks = [];
724         while (true) {
725             var keymap = keymap_stack[keymap_stack.length - 1];
726             for (var i in keymap.keycode_bindings) {
727                 var b = keymap.keycode_bindings[i];
728                 if (!(i in keycode_masks))
729                     keycode_masks[i] = [];
730                 for (var j in b) {
731                     if (unmodified_keys_masked && ((j & MOD_SHIFT) == j))
732                         continue;
733                     if (!keycode_masks[i][j]) {
734                         helper2(b[j]);
735                         keycode_masks[i][j] = true;
736                     }
737                 }
738             }
739             for (var i in  keymap.predicate_bindings) {
740                 var bind = keymap.predicate_bindings[i];
741                 helper2(bind);
742                 var p = bind.key.match_function;
743                 if (p == match_any_key)
744                     return;
745                 if (p == match_any_unmodified_key)
746                     unmodified_keys_masked = true;
747             }
748             if (keymap.parent)
749                 keymap_stack[keymap_stack.length - 1] = keymap.parent;
750             else
751                 break;
752         }
753     }
754     helper();
757 function find_command_in_keymap(keymap_or_buffer, command) {
758     var list = [];
760     for_each_key_binding(keymap_or_buffer, function (bind_seq) {
761             var bind = bind_seq[bind_seq.length - 1];
762             if (bind.command == command)
763                 list.push(format_binding_sequence(bind_seq));
764         });
765     return list;
768 define_keymap("key_binding_reader_keymap");
769 define_key(key_binding_reader_keymap, match_any_key, "read-key-binding-key");
771 define_keywords("$buffer", "$keymap");
772 function key_binding_reader(continuation) {
773     keywords(arguments, $prompt = "Describe key:");
775     this.continuation = continuation;
777     if (arguments.$keymap)
778         this.target_keymap = arguments.$keymap;
779     else {
780         var buffer = arguments.$buffer;
781         var window = buffer.window;
782         this.target_keymap = window.keyboard.override_keymap || buffer.keymap;
783     }
785     this.key_sequence = [];
787     minibuffer_input_state.call(this, key_binding_reader_keymap, arguments.$prompt);
789 key_binding_reader.prototype = {
790     __proto__: minibuffer_input_state.prototype,
791     destroy: function () {
792         if (this.continuation)
793             this.continuation.throw(abort());
794     }
797 function invalid_key_binding(seq) {
798     var e = new Error(seq.join(" ") + " is undefined");
799     e.key_sequence = seq;
800     e.__proto__ = invalid_key_binding.prototype;
801     return e;
803 invalid_key_binding.prototype = {
804     __proto__: interactive_error.prototype
807 function read_key_binding_key(window, state, event) {
808     var binding = lookup_key_binding(state.target_keymap, event);
810     state.key_sequence.push(format_key_event(event));
812     if (binding == null) {
813         var c = state.continuation;
814         delete state.continuation;
815         window.minibuffer.pop_state();
816         c.throw(invalid_key_binding(state.key_sequence));
817         return;
818     }
820     if (binding.keymap) {
821         window.minibuffer._restore_normal_state();
822         window.minibuffer._input_text = state.key_sequence.join(" ") + " ";
823         state.target_keymap = binding.keymap;
824         return;
825     }
827     var c = state.continuation;
828     delete state.continuation;
830     window.minibuffer.pop_state();
832     if (c != null)
833         c([state.key_sequence, binding]);
835 interactive("read-key-binding-key", null, function (I) {
836     read_key_binding_key(I.window, I.minibuffer.check_state(key_binding_reader), I.event);
839 minibuffer.prototype.read_key_binding = function () {
840     keywords(arguments);
841     var s = new key_binding_reader((yield CONTINUATION), forward_keywords(arguments));
842     this.push_state(s);
843     var result = yield SUSPEND;
844     yield co_return(result);