Add facility to allow user functions to transform typed URLs.
[conkeror.git] / modules / keyboard.js
blobb8f823f7256bad732c2d8121b60f888c1ca531b4
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;
340 define_keywords("$fallthrough", "$category", "$repeat");
341 function define_key_internal(ref, kmap, keys, new_command, new_keymap)
343     keywords(arguments);
345     var args = arguments;
347     var parent_kmap = kmap.parent;
349 outer:
350     for (var i = 0; i < keys.length; ++i) {
351         var key = keys[i];
352         var final_binding = (i == keys.length - 1);
354         /* Replace `bind' with the binding specified by (cmd, fallthrough) */
355         function replace_binding(bind)
356         {
357             if (final_binding) {
358                 bind.command = new_command;
359                 bind.keymap = new_keymap;
360                 bind.fallthrough = args.$fallthrough;
361                 bind.source_code_reference = ref;
362                 bind.category = args.$category;
363                 bind.repeat = args.$repeat;
364             } else {
365                 if (!bind.keymap)
366                     throw new Error("Key sequence has a non-keymap in prefix");
367                 kmap = bind.keymap;
368             }
369         }
371         function make_binding()
372         {
373             if (final_binding) {
374                 return {key: key, fallthrough: args.$fallthrough,
375                         command: new_command, keymap: new_keymap,
376                         source_code_reference: ref,
377                         category: args.$category,
378                         repeat: args.$repeat,
379                         bound_in: kmap};
380             }
381             else
382             {
383                 let old_kmap = kmap;
384                 // Check for a corresponding binding a parent
385                 kmap = new keymap($parent = parent_kmap);
386                 kmap.bound_in = old_kmap;
387                 return {key: key, keymap: kmap,
388                         source_code_reference: ref,
389                         bound_in: old_kmap};
390             }
391         }
393         // Check if the specified binding is already present in the kmap
394         if (key.match_function)
395         {
396             var pred_binds = kmap.predicate_bindings;
397             for (var i = 0; i < pred_binds.length; i++)
398             {
399                 var cur_bind = pred_binds[i];
400                 if (cur_bind.key.match_function == key.match_function)
401                 {
402                     replace_binding(cur_bind);
403                     continue outer;
404                 }
405             }
407             if (!final_binding && parent_kmap) {
408                 var parent_pred_binds = parent_kmap.predicate_bindings;
409                 parent_kmap = null;
410                 for (var i = 0; i < parent_pred_binds.length; i++)
411                 {
412                     var cur_bind = parent_pred_binds[i];
413                     if (cur_bind.key.match_function == key.match_function && cur_bind.keymap)
414                     {
415                         parent_kmap = cur_bind.keymap;
416                         break;
417                     }
418                 }
419             }
420             // Not already present, must be added
421             pred_binds.push(make_binding());
422         } else
423         {
424             // This is a binding by keycode: look it up in the table
425             var keycode_binds = kmap.keycode_bindings;
426             var arr = keycode_binds[key.keyCode];
428             if (arr && arr[key.modifiers])
429             {
430                 replace_binding(arr[key.modifiers]);
431                 continue outer;
432             }
434             if (!final_binding && parent_kmap) {
435                 var p_keycode_binds = parent_kmap.keycode_bindings;
436                 parent_kmap = null;
437                 var p_arr = p_keycode_binds[key.keyCode];
438                 var p_bind;
439                 if (p_arr && (p_bind = p_arr[key.modifiers]) != null && p_bind.keymap)
440                     parent_kmap = p_bind.keymap;
441             }
443             if (!arr)
444                 arr = (keycode_binds[key.keyCode] = []);
446             arr[key.modifiers] = make_binding();
447         }
448     }
451 // bind key to either the keymap or command in the keymap, kmap
452 define_keywords("$fallthrough", "$category", "$repeat");
453 function define_key(kmap, keys, cmd)
455     keywords(arguments);
456     var orig_keys = keys;
457     try {
458         var ref = get_caller_source_code_reference();
460         if (typeof(keys) == "string" && keys.length > 1)
461             keys = keys.split(" ");
463         if (!(typeof(keys) == "object") || ('is_kbd' in keys) || !(keys instanceof Array))
464             keys = [keys];
466         var new_command = null, new_keymap = null;
467         if (typeof(cmd) == "string" || typeof(cmd) == "function")
468             new_command = cmd;
469         else if (cmd instanceof keymap)
470             new_keymap = cmd;
471         else if (cmd != null)
472             throw new Error("Invalid `cmd' argument: " + cmd);
474         var args = arguments;
476         var input_keys = keys.map(function(x) { return kbd(x); });
478         function helper(index, output_keys) {
479             if (index == input_keys.length) {
480                 define_key_internal(ref, kmap, output_keys, new_command, new_keymap,
481                                     forward_keywords(args));
482                 return;
483             }
484             var key = input_keys[index];
485             for (let i = 0; i < key.length; ++i)
486                 helper(index + 1, output_keys.concat(key[i]));
487         }
489         helper(0, []);
490     } catch (e if (typeof(e) == "string")) {
491         dumpln("Warning: Error occurred while binding keys: " + orig_keys);
492         dumpln(e);
493         dumpln("This may be due to an incorrect keyboard setup.");
494         dumpln("You may want to use the -keyboard-setup command-line option to fix your keyboard setup.");
495     }
498 define_keywords("$parent", "$help", "$name");
499 function keymap ()
501     keywords(arguments);
502     /* For efficiency, a table indexed by the key code, and then by
503      * the modifiers is used to lookup key bindings, rather than
504      * looping through all bindings in the key map to find one.  The
505      * array keycode_bindings is indexed by the keyCode; if the
506      * corresponding element for a keyCode is non-null, it is itself
507      * an array indexed by the result of get_modifiers (i.e. from 0 to 7).
508      * As before, match_function-based bindings are stored as a simple
509      * list, predicate_bindings. */
510     this.parent = arguments.$parent;
511     this.keycode_bindings = [];
512     this.predicate_bindings = [];
513     this.help = arguments.$help;
514     this.name = arguments.$name;
517 function define_keymap(name) {
518     keywords(arguments);
519     this[name] = new keymap($name = name, forward_keywords(arguments));
522 function copy_event(event)
524     var ev = {};
525     ev.keyCode = event.keyCode;
526     ev.charCode = event.charCode;
527     ev.ctrlKey = event.ctrlKey;
528     ev.metaKey = event.metaKey;
529     ev.altKey = event.altKey;
530     ev.shiftKey = event.shiftKey;
531     ev.sticky_modifiers = event.sticky_modifiers;
532     return ev;
535 function key_down_handler(event)
537     var window = this;
538     //window.dumpln("key down: " + conkeror.format_key_press(event.keyCode, conkeror.get_modifiers(event)));
540     var state = window.keyboard;
541     state.last_key_down_event = copy_event(event);
542     state.last_char_code = null;
543     state.last_key_code = null;
546 define_variable("keyboard_key_sequence_help_timeout", 0,
547                 "Delay (in millseconds) before the current key sequence prefix is displayed in the minibuffer.");
549 define_window_local_hook("key_press_hook", RUN_HOOK_UNTIL_SUCCESS);
551 function key_press_handler(true_event)
553     function show_partial_key_sequence (window, state, ctx) {
554         if (!state.help_displayed)
555         {
556             state.help_timer_ID = window.setTimeout(function () {
557                 window.minibuffer.show(ctx.key_sequence.join(" "));
558                 state.help_displayed = true;
559                 state.help_timer_ID = null;
560             }, keyboard_key_sequence_help_timeout);
561         }
562         else
563             window.minibuffer.show(ctx.key_sequence.join(" "));
564     }
565     try{
566         var window = this;
567         var state = window.keyboard;
569         /* ASSERT(state.last_key_down_event != null); */
571         var event = state.last_key_down_event;
572         event.charCode = true_event.charCode;
574         // If the true_event includes a keyCode, we can just use that
575         if (true_event.keyCode)
576             event.keyCode = true_event.keyCode;
578         /* Filter out events from keys like the Windows/Super/Hyper key */
579         if (event.keyCode == 0)
580             return;
582         /* Clear minibuffer message */
583         window.minibuffer.clear();
585         var binding = null;
586         var done = true;
588         var ctx;
589         if (!state.current_context)
590             ctx = state.current_context = { window: window, key_sequence: [], sticky_modifiers: 0 };
591         else
592             ctx = state.current_context;
594         event.sticky_modifiers = ctx.sticky_modifiers;
595         ctx.sticky_modifiers = 0;
597         ctx.event = event;
599         if (key_press_hook.run(window, ctx, true_event))
600             return;
602         var top_keymap =
603             state.override_keymap ||
604             window.buffers.current.keymap;
606         var active_keymap =
607             state.active_keymap ||
608             top_keymap;
610         var overlay_keymap = ctx.overlay_keymap;
612         binding =
613             (overlay_keymap && lookup_key_binding(overlay_keymap, event)) ||
614             lookup_key_binding(active_keymap, event);
616         // Should we stop this event from being processed by the gui?
617         //
618         // 1) we have a binding, and the binding's fallthrough property is not
619         //    true.
620         //
621         // 2) we are in the middle of a key sequence, and we need to say that
622         //    the key sequence given has no command.
623         //
624         if (!binding || !binding.fallthrough)
625         {
626             true_event.preventDefault();
627             true_event.stopPropagation();
628         }
630         // Finally, process the binding.
631         ctx.key_sequence.push(format_key_event(event));
632         if (binding) {
633             if (binding.keymap) {
634                 state.active_keymap = binding.keymap;
635                 show_partial_key_sequence(window, state, ctx);
636                 // We're going for another round
637                 done = false;
638             } else if (binding.command) {
639                 let command = binding.command;
640                 if (ctx.repeat == command)
641                     command = binding.repeat;
642                 call_interactively(ctx, command);
643                 if (typeof(command) == "string" &&
644                     interactive_commands.get(command).prefix)
645                 {
646                     state.active_keymap = null;
647                     show_partial_key_sequence(window, state, ctx);
648                     if (binding.repeat)
649                         ctx.repeat = command;
650                     done = false;
651                 }
652             }
653         } else {
654             window.minibuffer.message(ctx.key_sequence.join(" ") + " is undefined");
655         }
657         // Clean up if we're done
658         if (done)
659         {
660             if (state.help_timer_ID != null)
661             {
662                 window.clearTimeout(state.help_timer_ID);
663                 state.help_timer_ID = null;
664             }
665             state.help_displayed = false;
666             state.active_keymap = null;
667             state.current_context = null;
668         }
669     } catch(e) { dump_error(e);}
672 function keyboard(window)
674     this.window = window;
677 keyboard.prototype = {
678     last_key_down_event : null,
679     current_context : null,
680     active_keymap : null,
681     help_timer_ID : null,
682     help_displayed : false,
684     /* If this is non-null, it is used instead of the current buffer's
685      * keymap. */
686     override_keymap : null,
688     set_override_keymap : function (keymap) {
689         /* Clear out any in-progress key sequence. */
690         this.active_keymap = null;
691         this.current_context = null;
692         if (this.help_timer_ID != null)
693         {
694             this.window.clearTimeout(this.help_timer_ID);
695             this.help_timer_ID = null;
696         }
697         this.override_keymap = keymap;
698     }
702 function keyboard_initialize_window(window)
704     window.keyboard = new keyboard(window);
706     window.addEventListener ("keydown", key_down_handler, true /* capture */,
707                             false /* ignore untrusted events */);
708     window.addEventListener ("keypress", key_press_handler, true /* capture */,
709                             false /* ignore untrusted events */);
712 add_hook("window_initialize_hook", keyboard_initialize_window);
714 function for_each_key_binding(keymap_or_buffer, callback) {
715     var keymap;
716     if (keymap_or_buffer instanceof conkeror.buffer) {
717         var buffer = keymap_or_buffer;
718         var window = buffer.window;
719         keymap = window.keyboard.override_keymap || buffer.keymap;
720     } else {
721         keymap = keymap_or_buffer;
722     }
723     var keymap_stack = [keymap];
724     var binding_stack = [];
725     function helper2(bind) {
726         binding_stack.push(bind);
727         callback(binding_stack);
728         if (bind.keymap && keymap_stack.indexOf(bind.keymap) == -1) {
729             keymap_stack.push(bind.keymap);
730             helper();
731             keymap_stack.pop();
732         }
733         binding_stack.pop();
734     }
735     function helper() {
736         var unmodified_keys_masked = false;
737         var keycode_masks = [];
738         while (true) {
739             var keymap = keymap_stack[keymap_stack.length - 1];
740             for (var i in keymap.keycode_bindings) {
741                 var b = keymap.keycode_bindings[i];
742                 if (!(i in keycode_masks))
743                     keycode_masks[i] = [];
744                 for (var j in b) {
745                     if (unmodified_keys_masked && ((j & MOD_SHIFT) == j))
746                         continue;
747                     if (!keycode_masks[i][j]) {
748                         helper2(b[j]);
749                         keycode_masks[i][j] = true;
750                     }
751                 }
752             }
753             for (var i in  keymap.predicate_bindings) {
754                 var bind = keymap.predicate_bindings[i];
755                 helper2(bind);
756                 var p = bind.key.match_function;
757                 if (p == match_any_key)
758                     return;
759                 if (p == match_any_unmodified_key)
760                     unmodified_keys_masked = true;
761             }
762             if (keymap.parent)
763                 keymap_stack[keymap_stack.length - 1] = keymap.parent;
764             else
765                 break;
766         }
767     }
768     helper();
771 function find_command_in_keymap(keymap_or_buffer, command) {
772     var list = [];
774     for_each_key_binding(keymap_or_buffer, function (bind_seq) {
775             var bind = bind_seq[bind_seq.length - 1];
776             if (bind.command == command)
777                 list.push(format_binding_sequence(bind_seq));
778         });
779     return list;
782 define_keymap("key_binding_reader_keymap");
783 define_key(key_binding_reader_keymap, match_any_key, "read-key-binding-key");
785 define_keywords("$buffer", "$keymap");
786 function key_binding_reader(continuation) {
787     keywords(arguments, $prompt = "Describe key:");
789     this.continuation = continuation;
791     if (arguments.$keymap)
792         this.target_keymap = arguments.$keymap;
793     else {
794         var buffer = arguments.$buffer;
795         var window = buffer.window;
796         this.target_keymap = window.keyboard.override_keymap || buffer.keymap;
797     }
799     this.key_sequence = [];
801     minibuffer_input_state.call(this, key_binding_reader_keymap, arguments.$prompt);
803 key_binding_reader.prototype = {
804     __proto__: minibuffer_input_state.prototype,
805     destroy: function () {
806         if (this.continuation)
807             this.continuation.throw(abort());
808     }
811 function invalid_key_binding(seq) {
812     var e = new Error(seq.join(" ") + " is undefined");
813     e.key_sequence = seq;
814     e.__proto__ = invalid_key_binding.prototype;
815     return e;
817 invalid_key_binding.prototype = {
818     __proto__: interactive_error.prototype
821 function read_key_binding_key(window, state, event) {
822     var binding = lookup_key_binding(state.target_keymap, event);
824     state.key_sequence.push(format_key_event(event));
826     if (binding == null) {
827         var c = state.continuation;
828         delete state.continuation;
829         window.minibuffer.pop_state();
830         c.throw(invalid_key_binding(state.key_sequence));
831         return;
832     }
834     if (binding.keymap) {
835         window.minibuffer._restore_normal_state();
836         window.minibuffer._input_text = state.key_sequence.join(" ") + " ";
837         state.target_keymap = binding.keymap;
838         return;
839     }
841     var c = state.continuation;
842     delete state.continuation;
844     window.minibuffer.pop_state();
846     if (c != null)
847         c([state.key_sequence, binding]);
849 interactive("read-key-binding-key", null, function (I) {
850     read_key_binding_key(I.window, I.minibuffer.check_state(key_binding_reader), I.event);
853 minibuffer.prototype.read_key_binding = function () {
854     keywords(arguments);
855     var s = new key_binding_reader((yield CONTINUATION), forward_keywords(arguments));
856     this.push_state(s);
857     var result = yield SUSPEND;
858     yield co_return(result);