Added sugar for manipulating caches.
[conkeror.git] / modules / keyboard.js
blob89ea3606fcd7df88558d50d118dee73ffe4a71cc
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 numpad_keycode = KeyEvent.DOM_VK_NUMPAD0 + i;
30         var charcode = keycode; // keycodes 0-9 are same as ascii
31         unshifted_table[keycode] = charcode;
32         unshifted_table[numpad_keycode] = charcode;
33     }
35     function map(table, keycode, str) {
36         table[keycode] = str.charCodeAt(0);
37     }
39     map(unshifted_table, KeyEvent.DOM_VK_BACK_SLASH, "\\");
40     map(unshifted_table, KeyEvent.DOM_VK_OPEN_BRACKET, "[");
41     map(unshifted_table, KeyEvent.DOM_VK_CLOSE_BRACKET, "]");
42     map(unshifted_table, KeyEvent.DOM_VK_SEMICOLON, ";");
43     map(unshifted_table, KeyEvent.DOM_VK_COMMA, ",");
44     map(unshifted_table, KeyEvent.DOM_VK_PERIOD, ".");
45     map(unshifted_table, KeyEvent.DOM_VK_SLASH, "/");
46     map(unshifted_table, KeyEvent.DOM_VK_SUBTRACT, "-");
47     map(unshifted_table, KeyEvent.DOM_VK_EQUALS, "=");
49     map(shifted_table, KeyEvent.DOM_VK_SEMICOLON, ":");
50     map(shifted_table, KeyEvent.DOM_VK_1, "!");
51     map(shifted_table, KeyEvent.DOM_VK_2, "@");
52     map(shifted_table, KeyEvent.DOM_VK_3, "#");
53     map(shifted_table, KeyEvent.DOM_VK_4, "$");
54     map(shifted_table, KeyEvent.DOM_VK_5, "%");
55     map(shifted_table, KeyEvent.DOM_VK_6, "^");
56     map(shifted_table, KeyEvent.DOM_VK_7, "&");
57     map(shifted_table, KeyEvent.DOM_VK_8, "*");
58     map(shifted_table, KeyEvent.DOM_VK_9, "(");
59     map(shifted_table, KeyEvent.DOM_VK_0, ")");
60     map(shifted_table, KeyEvent.DOM_VK_EQUALS, "+");
61     map(shifted_table, KeyEvent.DOM_VK_SUBTRACT, "_");
62     map(shifted_table, KeyEvent.DOM_VK_COMMA, "<");
63     map(shifted_table, KeyEvent.DOM_VK_PERIOD, ">");
64     map(shifted_table, KeyEvent.DOM_VK_SLASH, "?");
66     return [unshifted_table, shifted_table];
69 /* Generate vk name table  */
70 var keycode_to_vk_name = [];
71 var vk_name_to_keycode = {};
73     let KeyEvent = Ci.nsIDOMKeyEvent;
74     let prefix = "DOM_VK_";
75     for (i in KeyEvent)
76     {
77         /* Check if this is a key binding */
78         if (i.substr(0, prefix.length) == prefix)
79         {
80             let name = i.substr(prefix.length).toLowerCase();
81             let code = KeyEvent[i];
82             keycode_to_vk_name[code] = name;
83             vk_name_to_keycode[name] = code;
84         }
85     }
88 function get_charcode_mapping_table_from_preferences()
90     var vals = conkeror.get_pref("conkeror.charCodeMappingTable");
91     if (!vals)
92         return null;
93     try {
94         return eval(vals);
95     } catch (e) {
96         return null;
97     }
100 var unshifted_keycode_to_charcode = null;
101 var shifted_keycode_to_charcode = null;
102 var charcode_to_keycodes = null;
104 var keycode_to_name = null;
105 var shifted_keycode_to_name = null;
106 function load_charcode_mapping_table()
108     var tables = get_charcode_mapping_table_from_preferences();
109     if (!tables)
110         tables = get_default_keycode_to_charcode_tables();
111     let [unshifted_table, shifted_table] = tables;
112     unshifted_keycode_to_charcode = unshifted_table;
113     shifted_keycode_to_charcode = shifted_table;
114     charcode_to_keycodes = [];
115     for each (let table in tables) {
116         var shifted = (table == tables[1]);
117         for (let x in table) {
118             let charcode = table[x];
119             if (charcode == null)
120                 continue;
121             var obj = charcode_to_keycodes[charcode];
122             if (obj == null)
123                 obj = charcode_to_keycodes[charcode] = [];
124             obj[obj.length] = [x, shifted];
125         }
126     }
128     keycode_to_name = keycode_to_vk_name.slice();
129     shifted_keycode_to_name = [];
130     for (let charcode in charcode_to_keycodes) {
131         let arr = charcode_to_keycodes[charcode];
132         if (arr.length != 1)
133             continue;
134         let [keycode, shift] = arr[0];
135         let table = shift ? shifted_keycode_to_name : keycode_to_name;
136         table[keycode] = String.fromCharCode(charcode);
137     }
139 load_charcode_mapping_table();
141 interactive("keyboard-setup", null, function (I) {
142     make_chrome_window("chrome://conkeror/content/keyboard-setup.xul");
145 command_line_handler("keyboard-setup", true, function () {
146     make_chrome_window("chrome://conkeror/content/keyboard-setup.xul");
150 var abort_key = null;
152 const MOD_CTRL = 0x1;
153 const MOD_META = 0x2;
154 const MOD_SHIFT = 0x4;
156 // Note: For elements of the modifier_names array, an element at index
157 // i should correspond to the modifier mask (1 << i).
158 var modifier_names = ["C", "M", "S"];
160 function format_key_press(code, modifiers)
162     if (code == 0)
163         return "<invalid>";
164     var name;
165     if ((modifiers & MOD_SHIFT) && (name = shifted_keycode_to_name[code]))  {
166         modifiers &= ~MOD_SHIFT;
167     } else
168         name = keycode_to_name[code] || ("<" + code + ">");
169     var out = "";
170     if (modifiers)
171     {
172         for (var i = 0; i < modifier_names.length; ++i)
173         {
174             if (modifiers & (1 << i))
175                 out = out + modifier_names[i] + "-";
176         }
177     }
178     out = out + name;
179     return out;
182 function format_key_spec(key) {
183     if (key.match_function) {
184         if (key.match_function == match_any_key)
185             return "<any-key>";
186         if (key.match_function == match_any_unmodified_key)
187             return "<any-unmodified-key>";
188         return "<match-function>";
189     }
190     return format_key_press(key.keyCode, key.modifiers);
193 function format_key_event(event)
195     return format_key_press(event.keyCode, get_modifiers(event));
198 function format_binding_sequence(seq) {
199     return seq.map(function (x) {
200             return format_key_spec(x.key);
201         }).join(" ");
204 // Key Matching Functions.  These are functions that may be passed to kbd
205 // in place of key code or char code.  They take an event object as their
206 // argument and turn true if the event matches the class of keys that they
207 // represent.
209 function match_any_key (event)
211     return true;
214 function meta_pressed (event)
216     return event.altKey || event.metaKey;
219 function get_modifiers(event)
221     // Shift is always included in the modifiers, if it is included in
222     // the event.
223     return (event.ctrlKey ? MOD_CTRL:0) |
224         (meta_pressed(event) ? MOD_META:0) |
225         (event.shiftKey ? MOD_SHIFT: 0) |
226         event.sticky_modifiers;
229 /* This function is no longer used for normal keymap lookups.  It is
230  * only used to check if the current key matches the abort key. */
231 function match_binding(key, event)
233     return (key.keyCode
234             && event.keyCode == key.keyCode
235             && get_modifiers(event) == key.modifiers)
236         || (key.match_function && key.match_function (event));
239 function lookup_key_binding(kmap, event)
241     do {
242         // Check if the key matches the keycode table
243         var mods = get_modifiers(event);
244         var keycode_binds = kmap.keycode_bindings;
245         var arr;;
246         var bind;
247         if ((arr = keycode_binds[event.keyCode]) != null &&
248             (bind = arr[mods]) != null)
249             return bind;
251         // Check if the key matches a predicate
252         var pred_binds = kmap.predicate_bindings;
253         for (var i = 0; i < pred_binds.length; ++i)
254         {
255             var bind = pred_binds[i];
256             if (bind.key.match_function(event))
257                 return bind;
258         }
259         kmap = kmap.parent;
260     } while (kmap);
261     return null;
264 function match_any_unmodified_key (event)
266     try {
267         return event.charCode
268             && !meta_pressed(event)
269             && !event.ctrlKey
270             && !event.sticky_modifiers;
271     } catch (e) {return false; }
274 function kbd (spec, mods)
276     if (spec.is_kbd)
277         return spec;
279     var results = [];
280     results.is_kbd = true;
282     if (typeof spec == "function")
283         results[0] = {match_function: spec};
285     else if (typeof spec == "string")
286     {
287         /* Attempt to parse a key specification.  In order to allow
288          * the user to specify the "-" key literally, special case the
289          * parsing of that. */
290         var parts;
291         if (spec.substr(spec.length - 1) == "-")
292         {
293             parts = spec.substr(0, spec.length - 1).split("-");
294             parts.push("-");
295         } else
296             parts = spec.split("-");
297         var parsed_modifiers = 0;
298         if (parts.length > 1)
299         {
300             // Attempt to parse modifiers
301             for (var i = 0; i < parts.length - 1; ++i)
302             {
303                 var k = modifier_names.indexOf(parts[i]);
304                 if (k < 0)
305                     continue;
306                 var mod = 1 << k;
307                 parsed_modifiers |= mod;
308             }
309         }
310         // Attempt to lookup keycode
311         var name = parts[parts.length - 1];
313         if (mods)
314             parsed_modifiers |= mods;
316         if (name.length == 1) {
317             // Charcode, handle specially
319             var codes = charcode_to_keycodes[name.charCodeAt(0)];
320             if (!codes)
321                 throw "Invalid key specification: " + spec;
323             for each (let [keycode, shift] in codes) {
324                 results[results.length] = {keyCode: keycode, modifiers: parsed_modifiers | (shift ? MOD_SHIFT : 0)};
325             }
326         } else {
327             var code = vk_name_to_keycode[name];
328             if (code == null)
329                 throw "Invalid key specification: " + spec;
330             results[0] = {keyCode: code, modifiers: parsed_modifiers};
331         }
332     }
333     else {
334         results[0] = {keyCode: spec, modifiers: ((mods != null)? mods : 0)};
335     }
336     return results;
339 define_keywords("$fallthrough", "$category");
340 function define_key_internal(ref, kmap, keys, new_command, new_keymap)
342     keywords(arguments);
344     var args = arguments;
346     var parent_kmap = kmap.parent;
348 outer:
349     for (var i = 0; i < keys.length; ++i) {
350         var key = keys[i];
351         var final_binding = (i == keys.length - 1);
353         /* Replace `bind' with the binding specified by (cmd, fallthrough) */
354         function replace_binding(bind)
355         {
356             if (final_binding) {
357                 bind.command = new_command;
358                 bind.keymap = new_keymap;
359                 bind.fallthrough = args.$fallthrough;
360                 bind.source_code_reference = ref;
361                 bind.category = args.$category;
362             } else {
363                 if (!bind.keymap)
364                     throw new Error("Key sequence has a non-keymap in prefix");
365                 kmap = bind.keymap;
366             }
367         }
369         function make_binding()
370         {
371             if (final_binding) {
372                 return {key: key, fallthrough: args.$fallthrough,
373                         command: new_command, keymap: new_keymap,
374                         source_code_reference: ref,
375                         category: args.$category,
376                         bound_in: kmap};
377             }
378             else
379             {
380                 let old_kmap = kmap;
381                 // Check for a corresponding binding a parent
382                 kmap = new keymap($parent = parent_kmap);
383                 kmap.bound_in = old_kmap;
384                 return {key: key, keymap: kmap,
385                         source_code_reference: ref,
386                         bound_in: old_kmap};
387             }
388         }
390         // Check if the specified binding is already present in the kmap
391         if (key.match_function)
392         {
393             var pred_binds = kmap.predicate_bindings;
394             for (var i = 0; i < pred_binds.length; i++)
395             {
396                 var cur_bind = pred_binds[i];
397                 if (cur_bind.key.match_function == key.match_function)
398                 {
399                     replace_binding(cur_bind);
400                     continue outer;
401                 }
402             }
404             if (!final_binding && parent_kmap) {
405                 var parent_pred_binds = parent_kmap.predicate_bindings;
406                 parent_kmap = null;
407                 for (var i = 0; i < parent_pred_binds.length; i++)
408                 {
409                     var cur_bind = parent_pred_binds[i];
410                     if (cur_bind.key.match_function == key.match_function && cur_bind.keymap)
411                     {
412                         parent_kmap = cur_bind.keymap;
413                         break;
414                     }
415                 }
416             }
417             // Not already present, must be added
418             pred_binds.push(make_binding());
419         } else
420         {
421             // This is a binding by keycode: look it up in the table
422             var keycode_binds = kmap.keycode_bindings;
423             var arr = keycode_binds[key.keyCode];
425             if (arr && arr[key.modifiers])
426             {
427                 replace_binding(arr[key.modifiers]);
428                 continue outer;
429             }
431             if (!final_binding && parent_kmap) {
432                 var p_keycode_binds = parent_kmap.keycode_bindings;
433                 parent_kmap = null;
434                 var p_arr = p_keycode_binds[key.keyCode];
435                 var p_bind;
436                 if (p_arr && (p_bind = p_arr[key.modifiers]) != null && p_bind.keymap)
437                     parent_kmap = p_bind.keymap;
438             }
440             if (!arr)
441                 arr = (keycode_binds[key.keyCode] = []);
443             arr[key.modifiers] = make_binding();
444         }
445     }
448 // bind key to either the keymap or command in the keymap, kmap
449 define_keywords("$fallthrough", "$category");
450 function define_key(kmap, keys, cmd)
452     keywords(arguments);
453     var orig_keys = keys;
454     try {
455         var ref = get_caller_source_code_reference();
457         if (typeof(keys) == "string" && keys.length > 1)
458             keys = keys.split(" ");
460         if (!(typeof(keys) == "object") || ('is_kbd' in keys) || !(keys instanceof Array))
461             keys = [keys];
463         var new_command = null, new_keymap = null;
464         if (typeof(cmd) == "string" || typeof(cmd) == "function")
465             new_command = cmd;
466         else if (cmd instanceof keymap)
467             new_keymap = cmd;
468         else if (cmd != null)
469             throw new Error("Invalid `cmd' argument: " + cmd);
471         var args = arguments;
473         var input_keys = keys.map(function(x) kbd(x));
475         function helper(index, output_keys) {
476             if (index == input_keys.length) {
477                 define_key_internal(ref, kmap, output_keys, new_command, new_keymap,
478                                     forward_keywords(args));
479                 return;
480             }
481             var key = input_keys[index];
482             for (let i = 0; i < key.length; ++i)
483                 helper(index + 1, output_keys.concat(key[i]));
484         }
486         helper(0, []);
487     } catch (e if (typeof(e) == "string")) {
488         dumpln("Warning: Error occurred while binding keys: " + orig_keys);
489         dumpln(e);
490         dumpln("This may be due to an incorrect keyboard setup.");
491         dumpln("You may want to use the -keyboard-setup command-line option to fix your keyboard setup.");
492     }
495 define_keywords("$parent", "$help", "$name");
496 function keymap ()
498     keywords(arguments);
499     /* For efficiency, a table indexed by the key code, and then by
500      * the modifiers is used to lookup key bindings, rather than
501      * looping through all bindings in the key map to find one.  The
502      * array keycode_bindings is indexed by the keyCode; if the
503      * corresponding element for a keyCode is non-null, it is itself
504      * an array indexed by the result of get_modifiers (i.e. from 0 to 7).
505      * As before, match_function-based bindings are stored as a simple
506      * list, predicate_bindings. */
507     this.parent = arguments.$parent;
508     this.keycode_bindings = [];
509     this.predicate_bindings = [];
510     this.help = arguments.$help;
511     this.name = arguments.$name;
514 function define_keymap(name) {
515     keywords(arguments);
516     this[name] = new keymap($name = name, forward_keywords(arguments));
519 function copy_event(event)
521     var ev = {};
522     ev.keyCode = event.keyCode;
523     ev.charCode = event.charCode;
524     ev.ctrlKey = event.ctrlKey;
525     ev.metaKey = event.metaKey;
526     ev.altKey = event.altKey;
527     ev.shiftKey = event.shiftKey;
528     ev.sticky_modifiers = event.sticky_modifiers;
529     return ev;
532 function key_down_handler(event)
534     var window = this;
535     //window.dumpln("key down: " + conkeror.format_key_press(event.keyCode, conkeror.get_modifiers(event)));
537     var state = window.keyboard;
538     state.last_key_down_event = copy_event(event);
539     state.last_char_code = null;
540     state.last_key_code = null;
543 define_variable("keyboard_key_sequence_help_timeout", 0,
544                 "Delay (in millseconds) before the current key sequence prefix is displayed in the minibuffer.");
546 define_window_local_hook("key_press_hook", RUN_HOOK_UNTIL_SUCCESS);
548 function key_press_handler(true_event)
550     function show_partial_key_sequence (window, state, ctx) {
551         if (!state.help_displayed)
552         {
553             state.help_timer_ID = window.setTimeout(function () {
554                 window.minibuffer.show(ctx.key_sequence.join(" "));
555                 state.help_displayed = true;
556                 state.help_timer_ID = null;
557             }, keyboard_key_sequence_help_timeout);
558         }
559         else
560             window.minibuffer.show(ctx.key_sequence.join(" "));
561     }
562     try{
563         var window = this;
564         var state = window.keyboard;
566         /* ASSERT(state.last_key_down_event != null); */
568         var event = state.last_key_down_event;
569         event.charCode = true_event.charCode;
571         // If the true_event includes a keyCode, we can just use that
572         if (true_event.keyCode)
573             event.keyCode = true_event.keyCode;
575         /* Filter out events from keys like the Windows/Super/Hyper key */
576         if (event.keyCode == 0)
577             return;
579         /* Clear minibuffer message */
580         window.minibuffer.clear();
582         var binding = null;
583         var done = true;
585         var ctx;
586         if (!state.current_context)
587             ctx = state.current_context = { window: window, key_sequence: [], sticky_modifiers: 0 };
588         else
589             ctx = state.current_context;
591         event.sticky_modifiers = ctx.sticky_modifiers;
592         ctx.sticky_modifiers = 0;
594         ctx.event = event;
596         if (key_press_hook.run(window, ctx, true_event))
597             return;
599         var top_keymap =
600             state.override_keymap ||
601             window.buffers.current.keymap;
603         var active_keymap =
604             state.active_keymap ||
605             top_keymap;
607         var overlay_keymap = ctx.overlay_keymap;
609         binding =
610             (overlay_keymap && lookup_key_binding(overlay_keymap, event)) ||
611             lookup_key_binding(active_keymap, event) ;
613         // Should we stop this event from being processed by the gui?
614         //
615         // 1) we have a binding, and the binding's fallthrough property is not
616         //    true.
617         //
618         // 2) we are in the middle of a key sequence, and we need to say that
619         //    the key sequence given has no command.
620         //
621         if (!binding || !binding.fallthrough)
622         {
623             true_event.preventDefault();
624             true_event.stopPropagation();
625         }
627         // Finally, process the binding.
628         ctx.key_sequence.push(format_key_event(event));
629         if (binding) {
630             if (binding.keymap) {
631                 state.active_keymap = binding.keymap;
632                 show_partial_key_sequence(window, state, ctx);
633                 // We're going for another round
634                 done = false;
635             } else if (binding.command) {
636                 call_interactively(ctx, binding.command);
637                 if (interactive_commands.get(binding.command).prefix) {
638                     state.active_keymap = null;
639                     show_partial_key_sequence(window, state, ctx);
640                     done = false;
641                 }
642             }
643         } else {
644             window.minibuffer.message(ctx.key_sequence.join(" ") + " is undefined");
645         }
647         // Clean up if we're done
648         if (done)
649         {
650             if (state.help_timer_ID != null)
651             {
652                 window.clearTimeout(state.help_timer_ID);
653                 state.help_timer_ID = null;
654             }
655             state.help_displayed = false;
656             state.active_keymap = null;
657             state.current_context = null;
658         }
659     } catch(e) { dump_error(e);}
662 function keyboard(window)
664     this.window = window;
667 keyboard.prototype = {
668     last_key_down_event : null,
669     current_context : null,
670     active_keymap : null,
671     help_timer_ID : null,
672     help_displayed : false,
674     /* If this is non-null, it is used instead of the current buffer's
675      * keymap. */
676     override_keymap : null,
678     set_override_keymap : function (keymap) {
679         /* Clear out any in-progress key sequence. */
680         this.active_keymap = null;
681         this.current_context = null;
682         if (this.help_timer_ID != null)
683         {
684             this.window.clearTimeout(this.help_timer_ID);
685             this.help_timer_ID = null;
686         }
687         this.override_keymap = keymap;
688     }
692 function keyboard_initialize_window(window)
694     window.keyboard = new keyboard(window);
696     window.addEventListener ("keydown", key_down_handler, true /* capture */,
697                             false /* ignore untrusted events */);
698     window.addEventListener ("keypress", key_press_handler, true /* capture */,
699                             false /* ignore untrusted events */);
702 add_hook("window_initialize_hook", keyboard_initialize_window);
704 function for_each_key_binding(keymap_or_buffer, callback) {
705     var keymap;
706     if (keymap_or_buffer instanceof conkeror.buffer) {
707         var buffer = keymap_or_buffer;
708         var window = buffer.window;
709         keymap = window.keyboard.override_keymap || buffer.keymap;
710     } else {
711         keymap = keymap_or_buffer;
712     }
713     var keymap_stack = [keymap];
714     var binding_stack = [];
715     function helper2(bind) {
716         binding_stack.push(bind);
717         callback(binding_stack);
718         if (bind.keymap && keymap_stack.indexOf(bind.keymap) == -1) {
719             keymap_stack.push(bind.keymap);
720             helper();
721             keymap_stack.pop();
722         }
723         binding_stack.pop();
724     }
725     function helper() {
726         var unmodified_keys_masked = false;
727         var keycode_masks = [];
728         while (true) {
729             var keymap = keymap_stack[keymap_stack.length - 1];
730             for (var i in keymap.keycode_bindings) {
731                 var b = keymap.keycode_bindings[i];
732                 if (!(i in keycode_masks))
733                     keycode_masks[i] = [];
734                 for (var j in b) {
735                     if (unmodified_keys_masked && ((j & MOD_SHIFT) == j))
736                         continue;
737                     if (!keycode_masks[i][j]) {
738                         helper2(b[j]);
739                         keycode_masks[i][j] = true;
740                     }
741                 }
742             }
743             for (var i in  keymap.predicate_bindings) {
744                 var bind = keymap.predicate_bindings[i];
745                 helper2(bind);
746                 var p = bind.key.match_function;
747                 if (p == match_any_key)
748                     return;
749                 if (p == match_any_unmodified_key)
750                     unmodified_keys_masked = true;
751             }
752             if (keymap.parent)
753                 keymap_stack[keymap_stack.length - 1] = keymap.parent;
754             else
755                 break;
756         }
757     }
758     helper();
761 function find_command_in_keymap(keymap_or_buffer, command) {
762     var list = [];
764     for_each_key_binding(keymap_or_buffer, function (bind_seq) {
765             var bind = bind_seq[bind_seq.length - 1];
766             if (bind.command == command)
767                 list.push(format_binding_sequence(bind_seq));
768         });
769     return list;
772 define_keymap("key_binding_reader_keymap");
773 define_key(key_binding_reader_keymap, match_any_key, "read-key-binding-key");
775 define_keywords("$buffer", "$keymap");
776 function key_binding_reader(continuation) {
777     keywords(arguments, $prompt = "Describe key:");
779     this.continuation = continuation;
781     if (arguments.$keymap)
782         this.target_keymap = arguments.$keymap;
783     else {
784         var buffer = arguments.$buffer;
785         var window = buffer.window;
786         this.target_keymap = window.keyboard.override_keymap || buffer.keymap;
787     }
789     this.key_sequence = [];
791     minibuffer_input_state.call(this, key_binding_reader_keymap, arguments.$prompt);
793 key_binding_reader.prototype = {
794     __proto__: minibuffer_input_state.prototype,
795     destroy: function () {
796         if (this.continuation)
797             this.continuation.throw(abort());
798     }
801 function invalid_key_binding(seq) {
802     var e = new Error(seq.join(" ") + " is undefined");
803     e.key_sequence = seq;
804     e.__proto__ = invalid_key_binding.prototype;
805     return e;
807 invalid_key_binding.prototype = {
808     __proto__: interactive_error.prototype
811 function read_key_binding_key(window, state, event) {
812     var binding = lookup_key_binding(state.target_keymap, event);
814     state.key_sequence.push(format_key_event(event));
816     if (binding == null) {
817         var c = state.continuation;
818         delete state.continuation;
819         window.minibuffer.pop_state();
820         c.throw(invalid_key_binding(state.key_sequence));
821         return;
822     }
824     if (binding.keymap) {
825         window.minibuffer._restore_normal_state();
826         window.minibuffer._input_text = state.key_sequence.join(" ") + " ";
827         state.target_keymap = binding.keymap;
828         return;
829     }
831     var c = state.continuation;
832     delete state.continuation;
834     window.minibuffer.pop_state();
836     if (c != null)
837         c([state.key_sequence, binding]);
839 interactive("read-key-binding-key", null, function (I) {
840     read_key_binding_key(I.window, I.minibuffer.check_state(key_binding_reader), I.event);
843 minibuffer.prototype.read_key_binding = function () {
844     keywords(arguments);
845     var s = new key_binding_reader((yield CONTINUATION), forward_keywords(arguments));
846     this.push_state(s);
847     var result = yield SUSPEND;
848     yield co_return(result);