~ubuntu-branches/ubuntu/lucid/conkeror/lucid

« back to all changes in this revision

Viewing changes to modules/keyboard.js

  • Committer: Bazaar Package Importer
  • Author(s): Micah Gersten
  • Date: 2010-03-26 18:55:20 UTC
  • mfrom: (3.1.3 sid)
  • Revision ID: james.westby@ubuntu.com-20100326185520-ysgua8nfa2lxr9m4
Tags: 0.9.1+git100220-1ubuntu1
* Merge from debian testing.  Remaining changes:
  - debian/control: Change Depends to xulrunner-1.9.2 only (LP #537900)

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
**/
9
 
 
10
 
require("window.js");
11
 
require("command-line.js");
12
 
 
13
 
define_variable("key_bindings_ignore_capslock", false,
14
 
    "When true, the case of characters in key bindings will be based "+
15
 
    "only on whether shift was pressed--upper-case if yes, lower-case if "+
16
 
    "no.  Effectively, this overrides the capslock key.  This option has "+
17
 
    "no effect on ordinary typing in input fields.");
18
 
 
19
 
/* Generate vk name table  */
20
 
var keycode_to_vk_name = [];
21
 
var vk_name_to_keycode = {};
22
 
{
23
 
    let KeyEvent = Ci.nsIDOMKeyEvent;
24
 
    let prefix = "DOM_VK_";
25
 
    for (var i in KeyEvent) {
26
 
        /* Check if this is a key binding */
27
 
        if (i.substr(0, prefix.length) == prefix) {
28
 
            let name = i.substr(prefix.length).toLowerCase();
29
 
            let code = KeyEvent[i];
30
 
            keycode_to_vk_name[code] = name;
31
 
            vk_name_to_keycode[name] = code;
32
 
        }
33
 
    }
34
 
}
35
 
 
36
 
var abort_key = null;
37
 
 
38
 
 
39
 
/*
40
 
 * Modifiers
41
 
 */
42
 
 
43
 
function modifier (in_event_p, set_in_event) {
44
 
    this.in_event_p = in_event_p;
45
 
    this.set_in_event = set_in_event;
46
 
}
47
 
 
48
 
var modifiers = {
49
 
    A: new modifier(function (event) { return event.altKey; },
50
 
                    function (event) { event.altKey = true; }),
51
 
    C: new modifier(function (event) { return event.ctrlKey; },
52
 
                    function (event) { event.ctrlKey = true; }),
53
 
    M: new modifier(function (event) { return event.metaKey; },
54
 
                    function (event) { event.metaKey = true; }),
55
 
    S: new modifier(function (event) {
56
 
                        return (event.keyCode &&
57
 
                                event.charCode == 0 &&
58
 
                                event.shiftKey);
59
 
                    },
60
 
                    function (event) { event.shiftKey = true; })
61
 
};
62
 
var modifier_order = ['C', 'M', 'S'];
63
 
 
64
 
// check the platform and guess whether we should treat Alt as Meta
65
 
if (get_os() == 'Darwin') {
66
 
    // In OS X, alt is a shift-like modifier, in that we
67
 
    // only care about it for non-character events.
68
 
    modifiers.A = new modifier(
69
 
        function (event) {
70
 
            return (event.keyCode &&
71
 
                    event.charCode == 0 &&
72
 
                    event.altKey);
73
 
        },
74
 
        function (event) { event.altKey = true; });
75
 
    modifier_order = ['C', 'M', 'A', 'S'];
76
 
} else {
77
 
    modifiers.M = modifiers.A;
78
 
}
79
 
 
80
 
 
81
 
 
82
 
/*
83
 
 * Keymap datatype
84
 
 */
85
 
 
86
 
define_keywords("$parent", "$help", "$name", "$anonymous");
87
 
function keymap ()
88
 
{
89
 
    keywords(arguments);
90
 
    this.parent = arguments.$parent;
91
 
    this.bindings = {};
92
 
    this.predicate_bindings = [];
93
 
    this.help = arguments.$help;
94
 
    this.name = arguments.$name;
95
 
    this.anonymous = arguments.$anonymous;
96
 
}
97
 
 
98
 
function define_keymap(name) {
99
 
    keywords(arguments);
100
 
    this[name] = new keymap($name = name, forward_keywords(arguments));
101
 
}
102
 
 
103
 
 
104
 
 
105
 
/*
106
 
 * Key Match Predicates.
107
 
 *
108
 
 *  Predicate bindings are tried for a match after the ordinary key-combo
109
 
 * bindings.  They are predicate functions on the keypress event object.
110
 
 * When such a predicate returns a true value, its associated command,
111
 
 * keymap, or fallthrough declaration is performed.
112
 
 */
113
 
 
114
 
function match_any_key (event)
115
 
{
116
 
    return true;
117
 
}
118
 
 
119
 
function match_any_unmodified_key (event)
120
 
{
121
 
    //XXX: the meaning of "unmodified" is platform dependent. for example,
122
 
    // on OS X, Alt is used in combination with the character keys to type
123
 
    // an alternate character.  A possible solution is to set the altKey
124
 
    // property of the event to null for all keypress events on OS X.
125
 
    try {
126
 
        return event.charCode
127
 
            && !event.altKey
128
 
            && !event.metaKey
129
 
            && !event.ctrlKey
130
 
            && !event.sticky_modifiers;
131
 
    } catch (e) {return false; }
132
 
}
133
 
 
134
 
 
135
 
/*
136
 
 */
137
 
 
138
 
function format_key_spec(key) {
139
 
    if (key.match_function) {
140
 
        if (key.match_function == match_any_key)
141
 
            return "<any-key>";
142
 
        if (key.match_function == match_any_unmodified_key)
143
 
            return "<any-unmodified-key>";
144
 
        return "<match-function>";
145
 
    }
146
 
    return key;
147
 
}
148
 
 
149
 
function format_binding_sequence(seq) {
150
 
    return seq.map(function (x) {
151
 
            return format_key_spec(x.key);
152
 
        }).join(" ");
153
 
}
154
 
 
155
 
 
156
 
function lookup_key_binding(kmap, combo, event)
157
 
{
158
 
    do {
159
 
        // Check if the key matches the keycode table
160
 
        // var mods = get_modifiers(event);
161
 
        var bindings = kmap.bindings;
162
 
        var bind;
163
 
        if ((bind = bindings[combo]) != null)
164
 
            return bind;
165
 
 
166
 
        // Check if the key matches a predicate
167
 
        var pred_binds = kmap.predicate_bindings;
168
 
        for (var i = 0; i < pred_binds.length; ++i) {
169
 
            bind = pred_binds[i];
170
 
            if (bind.key(event))
171
 
                return bind;
172
 
        }
173
 
        kmap = kmap.parent;
174
 
    } while (kmap);
175
 
    return null;
176
 
}
177
 
 
178
 
 
179
 
/*
180
 
 * $fallthrough and $repeat are as for define_key.
181
 
 *
182
 
 * ref is the source code reference of the call to define_key.
183
 
 *
184
 
 * kmap is the keymap in which the binding is to be defined.
185
 
 *
186
 
 * keys is the key sequence being bound.  it may be necessary
187
 
 * to auto-generate new keymaps to accomodate the key sequence.
188
 
 *
189
 
 * only one of new_command and new_keymap will be given.
190
 
 * the one that is given is the thing being bound to.
191
 
 */
192
 
define_keywords("$fallthrough", "$repeat");
193
 
function define_key_internal(ref, kmap, keys, new_command, new_keymap)
194
 
{
195
 
    keywords(arguments);
196
 
    var args = arguments;
197
 
    var parent_kmap = kmap.parent;
198
 
    var final_binding; // flag to indicate the final key combo in the sequence.
199
 
    var key; // current key combo as we iterate through the sequence.
200
 
 
201
 
    /* Replace `bind' with the binding specified by (cmd, fallthrough) */
202
 
    function replace_binding (bind) {
203
 
        if (final_binding) {
204
 
            bind.command = new_command;
205
 
            bind.keymap = new_keymap;
206
 
            bind.fallthrough = args.$fallthrough;
207
 
            bind.source_code_reference = ref;
208
 
            bind.repeat = args.$repeat;
209
 
        } else {
210
 
            if (!bind.keymap)
211
 
                throw new Error("Key sequence has a non-keymap in prefix");
212
 
            kmap = bind.keymap;
213
 
        }
214
 
    }
215
 
 
216
 
    function make_binding () {
217
 
        if (final_binding) {
218
 
            return {key: key,
219
 
                    fallthrough: args.$fallthrough,
220
 
                    command: new_command,
221
 
                    keymap: new_keymap,
222
 
                    source_code_reference: ref,
223
 
                    repeat: args.$repeat,
224
 
                    bound_in: kmap};
225
 
        } else {
226
 
            let old_kmap = kmap;
227
 
            // Check for a corresponding binding a parent
228
 
            kmap = new keymap($parent = parent_kmap, $anonymous,
229
 
                              $name = old_kmap.name + " " + format_key_spec(key));
230
 
            kmap.bound_in = old_kmap;
231
 
            return {key: key,
232
 
                    keymap: kmap,
233
 
                    source_code_reference: ref,
234
 
                    bound_in: old_kmap};
235
 
        }
236
 
    }
237
 
 
238
 
outer:
239
 
    for (var i = 0; i < keys.length; ++i) {
240
 
        key = keys[i];
241
 
        final_binding = (i == keys.length - 1);
242
 
 
243
 
        // Check if the specified binding is already present in the kmap
244
 
        if (typeof(key) == "function") { // it's a match predicate
245
 
            var pred_binds = kmap.predicate_bindings;
246
 
            for (var j = 0; j < pred_binds.length; j++) {
247
 
                if (pred_binds[j].key == key) {
248
 
                    replace_binding(pred_binds[j]);
249
 
                    continue outer;
250
 
                }
251
 
            }
252
 
 
253
 
            if (!final_binding && parent_kmap) {
254
 
                var parent_pred_binds = parent_kmap.predicate_bindings;
255
 
                parent_kmap = null;
256
 
                for (j = 0; j < parent_pred_binds.length; j++) {
257
 
                    if (parent_pred_binds[j].key == key &&
258
 
                        parent_pred_binds[j].keymap)
259
 
                    {
260
 
                        parent_kmap = parent_pred_binds[j].keymap;
261
 
                        break;
262
 
                    }
263
 
                }
264
 
            }
265
 
            // Not already present, must be added
266
 
            pred_binds.push(make_binding());
267
 
        } else {
268
 
            var bindings = kmap.bindings;
269
 
            var binding = bindings[key];
270
 
 
271
 
            if (binding) {
272
 
                replace_binding(binding);
273
 
                continue outer;
274
 
            }
275
 
 
276
 
            if (!final_binding) {
277
 
                let temp_parent = parent_kmap;
278
 
                parent_kmap = null;
279
 
                while (temp_parent) {
280
 
                    let p_bindings = temp_parent.bindings;
281
 
                    let p_binding = p_bindings[key];
282
 
                    if (p_binding && p_binding.keymap) {
283
 
                        parent_kmap = p_binding.keymap;
284
 
                        break;
285
 
                    } else {
286
 
                        temp_parent = temp_parent.parent;
287
 
                    }
288
 
                }
289
 
            }
290
 
 
291
 
            bindings[key] = make_binding();
292
 
        }
293
 
    }
294
 
}
295
 
 
296
 
// bind key to either the keymap or command in the keymap, kmap
297
 
// keywords:
298
 
//
299
 
//  $fallthrough: (bool) let this key be process by the web page
300
 
//      or gecko.
301
 
//
302
 
//  $repeat: (commnand name) shortcut command to call if a prefix
303
 
//      command key is pressed twice in a row.
304
 
//
305
 
define_keywords("$fallthrough", "$repeat");
306
 
function define_key(kmap, keys, cmd)
307
 
{
308
 
    keywords(arguments);
309
 
    var orig_keys = keys;
310
 
    try {
311
 
        var ref = get_caller_source_code_reference();
312
 
 
313
 
        if (typeof(keys) == "string" && keys.length > 1)
314
 
            keys = keys.split(" ");
315
 
 
316
 
        if (!(typeof(keys) == "object") || !(keys instanceof Array))
317
 
            keys = [keys];
318
 
 
319
 
        // normalize the order of modifiers in string key combos
320
 
        keys = keys.map(
321
 
            function (k) {
322
 
                if (typeof(k) == "string")
323
 
                    return format_key_combo(unformat_key_combo(k));
324
 
                else
325
 
                    return k;
326
 
            });
327
 
 
328
 
        var new_command = null, new_keymap = null;
329
 
        if (typeof(cmd) == "string" || typeof(cmd) == "function")
330
 
            new_command = cmd;
331
 
        else if (cmd instanceof keymap)
332
 
            new_keymap = cmd;
333
 
        else if (cmd != null)
334
 
            throw new Error("Invalid `cmd' argument: " + cmd);
335
 
 
336
 
        define_key_internal(ref, kmap, keys, new_command, new_keymap,
337
 
                            forward_keywords(arguments));
338
 
 
339
 
    } catch (e if (typeof(e) == "string")) {
340
 
        dumpln("Warning: Error occurred while binding keys: " + orig_keys);
341
 
        dumpln(e);
342
 
    }
343
 
}
344
 
 
345
 
 
346
 
 
347
 
/*
348
 
 * Keypress Handler
349
 
 */
350
 
 
351
 
define_variable("keyboard_key_sequence_help_timeout", 0,
352
 
                "Delay (in millseconds) before the current key sequence "+
353
 
                "prefix is displayed in the minibuffer.");
354
 
 
355
 
define_window_local_hook("keypress_hook", RUN_HOOK_UNTIL_SUCCESS);
356
 
 
357
 
 
358
 
 
359
 
function copy_event (event) {
360
 
    var ev = {};
361
 
    ev.keyCode = event.keyCode;
362
 
    ev.charCode = event.charCode;
363
 
    ev.ctrlKey = event.ctrlKey;
364
 
    ev.metaKey = event.metaKey;
365
 
    ev.altKey = event.altKey;
366
 
    ev.shiftKey = event.shiftKey;
367
 
    ev.sticky_modifiers = event.sticky_modifiers;
368
 
    return ev;
369
 
}
370
 
 
371
 
function show_partial_key_sequence (window, state, ctx) {
372
 
    if (!state.help_displayed)
373
 
    {
374
 
        state.help_timer_ID = window.setTimeout(
375
 
            function () {
376
 
                window.minibuffer.show(ctx.key_sequence.join(" "));
377
 
                state.help_displayed = true;
378
 
                state.help_timer_ID = null;
379
 
            }, keyboard_key_sequence_help_timeout);
380
 
    }
381
 
    else
382
 
        window.minibuffer.show(ctx.key_sequence.join(" "));
383
 
}
384
 
 
385
 
function format_key_combo (event) {
386
 
    var combo = '';
387
 
    for each (var M in modifier_order) {
388
 
        if (modifiers[M].in_event_p(event) ||
389
 
            (event.sticky_modifiers &&
390
 
             event.sticky_modifiers.indexOf(M) != -1))
391
 
        {
392
 
            combo += (M + '-');
393
 
        }
394
 
    }
395
 
    if (event.charCode) {
396
 
        if (event.charCode == 32) {
397
 
            combo += 'space';
398
 
        } else {
399
 
            combo += String.fromCharCode(event.charCode);
400
 
        }
401
 
    } else if (event.keyCode) {
402
 
        combo += keycode_to_vk_name[event.keyCode];
403
 
    }
404
 
    return combo;
405
 
}
406
 
 
407
 
function unformat_key_combo (combo) {
408
 
    var event = {
409
 
        keyCode: 0,
410
 
        charCode: 0,
411
 
        altKey: false,
412
 
        ctrlKey: false,
413
 
        metaKey: false,
414
 
        shiftKey: false
415
 
    };
416
 
    var M;
417
 
    var i = 0;
418
 
    var len = combo.length - 2;
419
 
    while (i < len && combo[i+1] == '-') {
420
 
        M = combo[i];
421
 
        modifiers[M].set_in_event(event);
422
 
        i+=2;
423
 
    }
424
 
    var key = combo.substring(i);
425
 
    if (key.length == 1) {
426
 
        event.charCode = key.charCodeAt(0);
427
 
    } else if (key == 'space') {
428
 
        event.charCode = 32;
429
 
    } else {
430
 
        event.keyCode = vk_name_to_keycode[key];
431
 
    }
432
 
    return event;
433
 
}
434
 
 
435
 
function keypress_handler (true_event) {
436
 
    try{
437
 
        var window = this;
438
 
        var state = window.keyboard;
439
 
 
440
 
        var event = copy_event(true_event);
441
 
 
442
 
        /* Filter out events from keys like the Windows/Super/Hyper key */
443
 
        if (event.keyCode == 0 && event.charCode == 0 ||
444
 
            event.keyCode == vk_name_to_keycode.caps_lock)
445
 
            return;
446
 
 
447
 
        if (key_bindings_ignore_capslock && event.charCode) {
448
 
            let c = String.fromCharCode(event.charCode);
449
 
            if (event.shiftKey)
450
 
                event.charCode = c.toUpperCase().charCodeAt(0);
451
 
            else
452
 
                event.charCode = c.toLowerCase().charCodeAt(0);
453
 
        }
454
 
 
455
 
        /* Clear minibuffer message */
456
 
        window.minibuffer.clear();
457
 
 
458
 
        var binding = null;
459
 
        var done = true; // flag for end of key sequence
460
 
 
461
 
        var ctx;
462
 
        if (!state.current_context)
463
 
            ctx = state.current_context = { window: window, key_sequence: [], sticky_modifiers: 0 };
464
 
        else
465
 
            ctx = state.current_context;
466
 
 
467
 
        event.sticky_modifiers = ctx.sticky_modifiers;
468
 
        ctx.sticky_modifiers = 0;
469
 
 
470
 
        var combo = format_key_combo(event);
471
 
        ctx.combo = combo;
472
 
        ctx.event = event;
473
 
 
474
 
        // keypress_hook is used, for example, by key aliases
475
 
        if (keypress_hook.run(window, ctx, true_event))
476
 
            return;
477
 
 
478
 
        var top_keymap =
479
 
            state.override_keymap ||
480
 
            window.buffers.current.keymap;
481
 
 
482
 
        var active_keymap =
483
 
            state.active_keymap ||
484
 
            top_keymap;
485
 
 
486
 
        var overlay_keymap = ctx.overlay_keymap;
487
 
 
488
 
        binding =
489
 
            (overlay_keymap && lookup_key_binding(overlay_keymap, combo, event)) ||
490
 
            lookup_key_binding(active_keymap, combo, event);
491
 
 
492
 
        // Should we stop this event from being processed by the gui?
493
 
        //
494
 
        // 1) we have a binding, and the binding's fallthrough property is not
495
 
        //    true.
496
 
        //
497
 
        // 2) we are in the middle of a key sequence, and we need to say that
498
 
        //    the key sequence given has no command.
499
 
        //
500
 
        if (!binding || !binding.fallthrough)
501
 
        {
502
 
            true_event.preventDefault();
503
 
            true_event.stopPropagation();
504
 
        }
505
 
 
506
 
        // Finally, process the binding.
507
 
        ctx.key_sequence.push(combo);
508
 
        if (binding) {
509
 
            if (binding.keymap) {
510
 
                state.active_keymap = binding.keymap;
511
 
                show_partial_key_sequence(window, state, ctx);
512
 
                // We're going for another round
513
 
                done = false;
514
 
            } else if (binding.command) {
515
 
                let command = binding.command;
516
 
                if (ctx.repeat == command)
517
 
                    command = binding.repeat;
518
 
                call_interactively(ctx, command);
519
 
                if (typeof(command) == "string" &&
520
 
                    interactive_commands.get(command).prefix)
521
 
                {
522
 
                    state.active_keymap = null;
523
 
                    show_partial_key_sequence(window, state, ctx);
524
 
                    if (binding.repeat)
525
 
                        ctx.repeat = command;
526
 
                    done = false;
527
 
                }
528
 
            }
529
 
        } else {
530
 
            window.minibuffer.message(ctx.key_sequence.join(" ") + " is undefined");
531
 
        }
532
 
 
533
 
        // Clean up if we're done
534
 
        if (done)
535
 
        {
536
 
            if (state.help_timer_ID != null)
537
 
            {
538
 
                window.clearTimeout(state.help_timer_ID);
539
 
                state.help_timer_ID = null;
540
 
            }
541
 
            state.help_displayed = false;
542
 
            state.active_keymap = null;
543
 
            state.current_context = null;
544
 
        }
545
 
    } catch(e) { dump_error(e);}
546
 
}
547
 
 
548
 
function keyboard(window)
549
 
{
550
 
    this.window = window;
551
 
}
552
 
 
553
 
keyboard.prototype = {
554
 
    last_key_down_event : null,
555
 
    current_context : null,
556
 
    active_keymap : null,
557
 
    help_timer_ID : null,
558
 
    help_displayed : false,
559
 
 
560
 
    /* If this is non-null, it is used instead of the current buffer's
561
 
     * keymap. */
562
 
    override_keymap : null,
563
 
 
564
 
    set_override_keymap : function (keymap) {
565
 
        /* Clear out any in-progress key sequence. */
566
 
        this.active_keymap = null;
567
 
        this.current_context = null;
568
 
        if (this.help_timer_ID != null)
569
 
        {
570
 
            this.window.clearTimeout(this.help_timer_ID);
571
 
            this.help_timer_ID = null;
572
 
        }
573
 
        this.override_keymap = keymap;
574
 
    }
575
 
};
576
 
 
577
 
 
578
 
function keyboard_initialize_window(window)
579
 
{
580
 
    window.keyboard = new keyboard(window);
581
 
 
582
 
    window.addEventListener ("keypress", keypress_handler, true /* capture */,
583
 
                            false /* ignore untrusted events */);
584
 
}
585
 
 
586
 
add_hook("window_initialize_hook", keyboard_initialize_window);
587
 
 
588
 
function for_each_key_binding(keymap_or_buffer, callback) {
589
 
    var keymap;
590
 
    if (keymap_or_buffer instanceof conkeror.buffer) {
591
 
        var buffer = keymap_or_buffer;
592
 
        var window = buffer.window;
593
 
        keymap = window.keyboard.override_keymap || buffer.keymap;
594
 
    } else {
595
 
        keymap = keymap_or_buffer;
596
 
    }
597
 
    var keymap_stack = [keymap];
598
 
    var binding_stack = [];
599
 
    function helper2(bind) {
600
 
        binding_stack.push(bind);
601
 
        callback(binding_stack);
602
 
        if (bind.keymap && keymap_stack.indexOf(bind.keymap) == -1) {
603
 
            keymap_stack.push(bind.keymap);
604
 
            helper();
605
 
            keymap_stack.pop();
606
 
        }
607
 
        binding_stack.pop();
608
 
    }
609
 
    function helper() {
610
 
        while (true) {
611
 
            var keymap = keymap_stack[keymap_stack.length - 1];
612
 
            for (var i in keymap.bindings) {
613
 
                var b = keymap.bindings[i];
614
 
                helper2(b);
615
 
            }
616
 
            for (i in  keymap.predicate_bindings) {
617
 
                var bind = keymap.predicate_bindings[i];
618
 
                helper2(bind);
619
 
                var p = bind.key;
620
 
                if (p == match_any_key)
621
 
                    return;
622
 
            }
623
 
            if (keymap.parent)
624
 
                keymap_stack[keymap_stack.length - 1] = keymap.parent;
625
 
            else
626
 
                break;
627
 
        }
628
 
    }
629
 
    helper();
630
 
}
631
 
 
632
 
function find_command_in_keymap(keymap_or_buffer, command) {
633
 
    var list = [];
634
 
 
635
 
    for_each_key_binding(keymap_or_buffer, function (bind_seq) {
636
 
            var bind = bind_seq[bind_seq.length - 1];
637
 
            if (bind.command == command)
638
 
                list.push(format_binding_sequence(bind_seq));
639
 
        });
640
 
    return list;
641
 
}
642
 
 
643
 
define_keymap("key_binding_reader_keymap");
644
 
define_key(key_binding_reader_keymap, match_any_key, "read-key-binding-key");
645
 
 
646
 
define_keywords("$buffer", "$keymap");
647
 
function key_binding_reader(continuation) {
648
 
    keywords(arguments, $prompt = "Describe key:");
649
 
 
650
 
    this.continuation = continuation;
651
 
 
652
 
    if (arguments.$keymap)
653
 
        this.target_keymap = arguments.$keymap;
654
 
    else {
655
 
        var buffer = arguments.$buffer;
656
 
        var window = buffer.window;
657
 
        this.target_keymap = window.keyboard.override_keymap || buffer.keymap;
658
 
    }
659
 
 
660
 
    this.key_sequence = [];
661
 
 
662
 
    minibuffer_input_state.call(this, key_binding_reader_keymap, arguments.$prompt);
663
 
}
664
 
key_binding_reader.prototype = {
665
 
    __proto__: minibuffer_input_state.prototype,
666
 
    destroy: function () {
667
 
        if (this.continuation)
668
 
            this.continuation.throw(abort());
669
 
    }
670
 
};
671
 
 
672
 
function invalid_key_binding(seq) {
673
 
    var e = new Error(seq.join(" ") + " is undefined");
674
 
    e.key_sequence = seq;
675
 
    e.__proto__ = invalid_key_binding.prototype;
676
 
    return e;
677
 
}
678
 
invalid_key_binding.prototype = {
679
 
    __proto__: interactive_error.prototype
680
 
};
681
 
 
682
 
function read_key_binding_key(window, state, event) {
683
 
    var combo = format_key_combo(event);
684
 
    var binding = lookup_key_binding(state.target_keymap, combo, event);
685
 
 
686
 
    state.key_sequence.push(combo);
687
 
 
688
 
    if (binding == null) {
689
 
        var c = state.continuation;
690
 
        delete state.continuation;
691
 
        window.minibuffer.pop_state();
692
 
        c.throw(invalid_key_binding(state.key_sequence));
693
 
        return;
694
 
    }
695
 
 
696
 
    if (binding.keymap) {
697
 
        window.minibuffer._restore_normal_state();
698
 
        window.minibuffer._input_text = state.key_sequence.join(" ") + " ";
699
 
        state.target_keymap = binding.keymap;
700
 
        return;
701
 
    }
702
 
 
703
 
    var c = state.continuation;
704
 
    delete state.continuation;
705
 
 
706
 
    window.minibuffer.pop_state();
707
 
 
708
 
    if (c != null)
709
 
        c([state.key_sequence, binding]);
710
 
}
711
 
interactive("read-key-binding-key", null, function (I) {
712
 
    read_key_binding_key(I.window, I.minibuffer.check_state(key_binding_reader), I.event);
713
 
});
714
 
 
715
 
minibuffer.prototype.read_key_binding = function () {
716
 
    keywords(arguments);
717
 
    var s = new key_binding_reader((yield CONTINUATION), forward_keywords(arguments));
718
 
    this.push_state(s);
719
 
    var result = yield SUSPEND;
720
 
    yield co_return(result);
721
 
};