2
* (C) Copyright 2004-2007 Shawn Betts
3
* (C) Copyright 2007-2008 John J. Foerch
4
* (C) Copyright 2007-2008 Jeremy Maitin-Shepard
6
* Use, modification, and distribution are subject to the terms specified in the
11
require("command-line.js");
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.");
19
/* Generate vk name table */
20
var keycode_to_vk_name = [];
21
var vk_name_to_keycode = {};
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;
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;
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 &&
60
function (event) { event.shiftKey = true; })
62
var modifier_order = ['C', 'M', 'S'];
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(
70
return (event.keyCode &&
71
event.charCode == 0 &&
74
function (event) { event.altKey = true; });
75
modifier_order = ['C', 'M', 'A', 'S'];
77
modifiers.M = modifiers.A;
86
define_keywords("$parent", "$help", "$name", "$anonymous");
90
this.parent = arguments.$parent;
92
this.predicate_bindings = [];
93
this.help = arguments.$help;
94
this.name = arguments.$name;
95
this.anonymous = arguments.$anonymous;
98
function define_keymap(name) {
100
this[name] = new keymap($name = name, forward_keywords(arguments));
106
* Key Match Predicates.
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.
114
function match_any_key (event)
119
function match_any_unmodified_key (event)
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.
126
return event.charCode
130
&& !event.sticky_modifiers;
131
} catch (e) {return false; }
138
function format_key_spec(key) {
139
if (key.match_function) {
140
if (key.match_function == match_any_key)
142
if (key.match_function == match_any_unmodified_key)
143
return "<any-unmodified-key>";
144
return "<match-function>";
149
function format_binding_sequence(seq) {
150
return seq.map(function (x) {
151
return format_key_spec(x.key);
156
function lookup_key_binding(kmap, combo, event)
159
// Check if the key matches the keycode table
160
// var mods = get_modifiers(event);
161
var bindings = kmap.bindings;
163
if ((bind = bindings[combo]) != null)
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];
180
* $fallthrough and $repeat are as for define_key.
182
* ref is the source code reference of the call to define_key.
184
* kmap is the keymap in which the binding is to be defined.
186
* keys is the key sequence being bound. it may be necessary
187
* to auto-generate new keymaps to accomodate the key sequence.
189
* only one of new_command and new_keymap will be given.
190
* the one that is given is the thing being bound to.
192
define_keywords("$fallthrough", "$repeat");
193
function define_key_internal(ref, kmap, keys, new_command, new_keymap)
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.
201
/* Replace `bind' with the binding specified by (cmd, fallthrough) */
202
function replace_binding (bind) {
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;
211
throw new Error("Key sequence has a non-keymap in prefix");
216
function make_binding () {
219
fallthrough: args.$fallthrough,
220
command: new_command,
222
source_code_reference: ref,
223
repeat: args.$repeat,
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;
233
source_code_reference: ref,
239
for (var i = 0; i < keys.length; ++i) {
241
final_binding = (i == keys.length - 1);
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]);
253
if (!final_binding && parent_kmap) {
254
var parent_pred_binds = parent_kmap.predicate_bindings;
256
for (j = 0; j < parent_pred_binds.length; j++) {
257
if (parent_pred_binds[j].key == key &&
258
parent_pred_binds[j].keymap)
260
parent_kmap = parent_pred_binds[j].keymap;
265
// Not already present, must be added
266
pred_binds.push(make_binding());
268
var bindings = kmap.bindings;
269
var binding = bindings[key];
272
replace_binding(binding);
276
if (!final_binding) {
277
let temp_parent = parent_kmap;
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;
286
temp_parent = temp_parent.parent;
291
bindings[key] = make_binding();
296
// bind key to either the keymap or command in the keymap, kmap
299
// $fallthrough: (bool) let this key be process by the web page
302
// $repeat: (commnand name) shortcut command to call if a prefix
303
// command key is pressed twice in a row.
305
define_keywords("$fallthrough", "$repeat");
306
function define_key(kmap, keys, cmd)
309
var orig_keys = keys;
311
var ref = get_caller_source_code_reference();
313
if (typeof(keys) == "string" && keys.length > 1)
314
keys = keys.split(" ");
316
if (!(typeof(keys) == "object") || !(keys instanceof Array))
319
// normalize the order of modifiers in string key combos
322
if (typeof(k) == "string")
323
return format_key_combo(unformat_key_combo(k));
328
var new_command = null, new_keymap = null;
329
if (typeof(cmd) == "string" || typeof(cmd) == "function")
331
else if (cmd instanceof keymap)
333
else if (cmd != null)
334
throw new Error("Invalid `cmd' argument: " + cmd);
336
define_key_internal(ref, kmap, keys, new_command, new_keymap,
337
forward_keywords(arguments));
339
} catch (e if (typeof(e) == "string")) {
340
dumpln("Warning: Error occurred while binding keys: " + orig_keys);
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.");
355
define_window_local_hook("keypress_hook", RUN_HOOK_UNTIL_SUCCESS);
359
function copy_event (event) {
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;
371
function show_partial_key_sequence (window, state, ctx) {
372
if (!state.help_displayed)
374
state.help_timer_ID = window.setTimeout(
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);
382
window.minibuffer.show(ctx.key_sequence.join(" "));
385
function format_key_combo (event) {
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))
395
if (event.charCode) {
396
if (event.charCode == 32) {
399
combo += String.fromCharCode(event.charCode);
401
} else if (event.keyCode) {
402
combo += keycode_to_vk_name[event.keyCode];
407
function unformat_key_combo (combo) {
418
var len = combo.length - 2;
419
while (i < len && combo[i+1] == '-') {
421
modifiers[M].set_in_event(event);
424
var key = combo.substring(i);
425
if (key.length == 1) {
426
event.charCode = key.charCodeAt(0);
427
} else if (key == 'space') {
430
event.keyCode = vk_name_to_keycode[key];
435
function keypress_handler (true_event) {
438
var state = window.keyboard;
440
var event = copy_event(true_event);
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)
447
if (key_bindings_ignore_capslock && event.charCode) {
448
let c = String.fromCharCode(event.charCode);
450
event.charCode = c.toUpperCase().charCodeAt(0);
452
event.charCode = c.toLowerCase().charCodeAt(0);
455
/* Clear minibuffer message */
456
window.minibuffer.clear();
459
var done = true; // flag for end of key sequence
462
if (!state.current_context)
463
ctx = state.current_context = { window: window, key_sequence: [], sticky_modifiers: 0 };
465
ctx = state.current_context;
467
event.sticky_modifiers = ctx.sticky_modifiers;
468
ctx.sticky_modifiers = 0;
470
var combo = format_key_combo(event);
474
// keypress_hook is used, for example, by key aliases
475
if (keypress_hook.run(window, ctx, true_event))
479
state.override_keymap ||
480
window.buffers.current.keymap;
483
state.active_keymap ||
486
var overlay_keymap = ctx.overlay_keymap;
489
(overlay_keymap && lookup_key_binding(overlay_keymap, combo, event)) ||
490
lookup_key_binding(active_keymap, combo, event);
492
// Should we stop this event from being processed by the gui?
494
// 1) we have a binding, and the binding's fallthrough property is not
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.
500
if (!binding || !binding.fallthrough)
502
true_event.preventDefault();
503
true_event.stopPropagation();
506
// Finally, process the binding.
507
ctx.key_sequence.push(combo);
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
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)
522
state.active_keymap = null;
523
show_partial_key_sequence(window, state, ctx);
525
ctx.repeat = command;
530
window.minibuffer.message(ctx.key_sequence.join(" ") + " is undefined");
533
// Clean up if we're done
536
if (state.help_timer_ID != null)
538
window.clearTimeout(state.help_timer_ID);
539
state.help_timer_ID = null;
541
state.help_displayed = false;
542
state.active_keymap = null;
543
state.current_context = null;
545
} catch(e) { dump_error(e);}
548
function keyboard(window)
550
this.window = window;
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,
560
/* If this is non-null, it is used instead of the current buffer's
562
override_keymap : null,
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)
570
this.window.clearTimeout(this.help_timer_ID);
571
this.help_timer_ID = null;
573
this.override_keymap = keymap;
578
function keyboard_initialize_window(window)
580
window.keyboard = new keyboard(window);
582
window.addEventListener ("keypress", keypress_handler, true /* capture */,
583
false /* ignore untrusted events */);
586
add_hook("window_initialize_hook", keyboard_initialize_window);
588
function for_each_key_binding(keymap_or_buffer, callback) {
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;
595
keymap = keymap_or_buffer;
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);
611
var keymap = keymap_stack[keymap_stack.length - 1];
612
for (var i in keymap.bindings) {
613
var b = keymap.bindings[i];
616
for (i in keymap.predicate_bindings) {
617
var bind = keymap.predicate_bindings[i];
620
if (p == match_any_key)
624
keymap_stack[keymap_stack.length - 1] = keymap.parent;
632
function find_command_in_keymap(keymap_or_buffer, command) {
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));
643
define_keymap("key_binding_reader_keymap");
644
define_key(key_binding_reader_keymap, match_any_key, "read-key-binding-key");
646
define_keywords("$buffer", "$keymap");
647
function key_binding_reader(continuation) {
648
keywords(arguments, $prompt = "Describe key:");
650
this.continuation = continuation;
652
if (arguments.$keymap)
653
this.target_keymap = arguments.$keymap;
655
var buffer = arguments.$buffer;
656
var window = buffer.window;
657
this.target_keymap = window.keyboard.override_keymap || buffer.keymap;
660
this.key_sequence = [];
662
minibuffer_input_state.call(this, key_binding_reader_keymap, arguments.$prompt);
664
key_binding_reader.prototype = {
665
__proto__: minibuffer_input_state.prototype,
666
destroy: function () {
667
if (this.continuation)
668
this.continuation.throw(abort());
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;
678
invalid_key_binding.prototype = {
679
__proto__: interactive_error.prototype
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);
686
state.key_sequence.push(combo);
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));
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;
703
var c = state.continuation;
704
delete state.continuation;
706
window.minibuffer.pop_state();
709
c([state.key_sequence, binding]);
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);
715
minibuffer.prototype.read_key_binding = function () {
717
var s = new key_binding_reader((yield CONTINUATION), forward_keywords(arguments));
719
var result = yield SUSPEND;
720
yield co_return(result);