2 * (C) Copyright 2004-2007 Shawn Betts
3 * (C) Copyright 2007-2010 John J. Foerch
4 * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
6 * Use, modification, and distribution are subject to the terms specified in the
10 /* Generate vk name table */
11 var keycode_to_vk_name = [];
12 var vk_name_to_keycode = {};
13 let (KeyEvent = Ci.nsIDOMKeyEvent,
15 for (var i in KeyEvent) {
16 /* Check if this is a key binding */
17 if (i.substr(0, prefix.length) == prefix) {
18 let name = i.substr(prefix.length).toLowerCase();
19 let code = KeyEvent[i];
20 keycode_to_vk_name[code] = name;
21 vk_name_to_keycode[name] = code;
31 function modifier (in_event_p, set_in_event) {
32 this.in_event_p = in_event_p;
33 this.set_in_event = set_in_event;
37 A: new modifier(function (event) { return event.altKey; },
38 function (event) { event.altKey = true; }),
39 C: new modifier(function (event) { return event.ctrlKey; },
40 function (event) { event.ctrlKey = true; }),
41 M: new modifier(function (event) { return event.metaKey; },
42 function (event) { event.metaKey = true; }),
43 S: new modifier(function (event) {
47 name = keycode_to_vk_name[event.keyCode];
48 return ((name && name.length > 1) ||
49 (event.charCode == 32) ||
50 (event.button != null));
54 function (event) { event.shiftKey = true; })
56 var modifier_order = ['C', 'M', 'S'];
58 // check the platform and guess whether we should treat Alt as Meta
59 if (get_os() == 'Darwin') {
60 // In OS X, alt is a shift-like modifier, in that we
61 // only care about it for non-character events.
62 modifiers.A = new modifier(
67 name = keycode_to_vk_name[event.keyCode];
68 return ((name && name.length > 1) ||
69 (event.charCode == 32) ||
70 (event.button != null));
74 function (event) { event.altKey = true; });
75 modifier_order = ['C', 'M', 'A', 'S'];
77 modifiers.M = modifiers.A;
86 function format_key_combo (event) {
88 for each (var M in modifier_order) {
89 if (modifiers[M].in_event_p(event) ||
90 (event.sticky_modifiers &&
91 event.sticky_modifiers.indexOf(M) != -1))
97 if (event.charCode == 32)
100 combo += String.fromCharCode(event.charCode);
101 } else if (event.keyCode) {
102 combo += keycode_to_vk_name[event.keyCode];
103 } else if (event.button != null) {
104 combo += "mouse" + (event.button + 1);
105 } else if (event.command) {
106 combo += "cmd" + event.command.toLowerCase();
111 function unformat_key_combo (combo) {
124 var len = combo.length - 2;
126 while (i < len && combo[i+1] == '-') {
128 modifiers[M].set_in_event(event);
131 var key = combo.substring(i);
133 event.charCode = key.charCodeAt(0);
134 else if (key == 'space')
136 else if (key.substring(0, 3) == 'cmd')
137 event.command = key.substr(3, 1).toUpperCase() +
139 else if (vk_name_to_keycode[key])
140 event.keyCode = vk_name_to_keycode[key];
141 else if (key.substring(0, 5) == 'mouse')
142 event.button = parseInt(key.substring(5));
152 define_keywords("$parent", "$help", "$name", "$anonymous",
153 "$display_name", "$notify");
156 this.parent = arguments.$parent;
158 this.predicate_bindings = [];
159 this.fallthrough = [];
160 this.help = arguments.$help;
161 this.name = arguments.$name;
162 this.display_name = arguments.$display_name;
163 this.notify = arguments.$notify;
164 this.anonymous = arguments.$anonymous;
168 toString: function () "#<keymap>"
171 function define_keymap (name) {
173 this[name] = new keymap($name = name, forward_keywords(arguments));
179 * Key Match Predicates.
182 function define_key_match_predicate (name, description, predicate) {
183 conkeror[name] = predicate;
184 conkeror[name].name = name;
185 conkeror[name].description = description;
186 return conkeror[name];
189 define_key_match_predicate('match_any_key', 'any key',
190 function (event) true);
192 // should be renamed to match_any_unmodified_character
193 define_key_match_predicate('match_any_unmodified_character', 'any unmodified character',
195 // this predicate can be used for both keypress and keydown
198 return ((event.type == 'keypress' && event.charCode)
199 || event.keyCode > 31)
200 && !modifiers.A.in_event_p(event)
203 && !event.sticky_modifiers;
204 } catch (e) { return false; }
207 define_key_match_predicate('match_checkbox_keys', 'checkbox keys',
209 return event.keyCode == 32
214 //XXX: keycode fallthroughs don't support sticky modifiers
217 define_key_match_predicate('match_text_keys', 'text editing keys',
219 return ((event.type == 'keypress' && event.charCode)
220 || event.keyCode == 13 || event.keyCode > 31)
223 && !modifiers.A.in_event_p(event);
224 //XXX: keycode fallthroughs don't support sticky modifiers
227 define_key_match_predicate('match_not_escape_key', 'any key but escape',
229 return event.keyCode != 27 ||
232 event.metaKey || // M-escape can also leave this mode, so we
233 // need to use an accurate determination of
234 // whether the "M" modifier was pressed,
235 // which is not necessarily the same as
244 function format_key_spec (key) {
245 if (key instanceof Function) {
247 return "<"+key.description+">";
249 return "<"+key.name+">";
250 return "<anonymous match function>";
255 function format_binding_sequence (seq) {
256 return seq.map(function (x) {
257 return format_key_spec(x.key);
262 function keymap_lookup (keymaps, combo, event) {
263 var i = keymaps.length - 1;
266 var kmap = keymaps[i];
269 var bindings = kmap.bindings;
270 var bind = bindings[combo];
274 new_kmaps.unshift(bind.keymap);
280 new_kmaps = [bind.keymap];
284 var pred_binds = kmap.predicate_bindings;
285 for (var j = 0, pblen = pred_binds.length; j < pblen; ++j) {
286 bind = pred_binds[j];
303 function keymap_lookup_fallthrough (keymap, event) {
304 var predicates = keymap.fallthrough;
305 for (var i = 0, plen = predicates.length; i < plen; ++i) {
306 if (predicates[i](event))
313 function for_each_key_binding (keymaps, callback) {
314 var binding_sequence = [];
316 function helper (keymap) {
317 if (in_keymaps.indexOf(keymap) >= 0)
319 in_keymaps.push(keymap);
321 for each (binding in keymap.bindings) {
322 binding_sequence.push(binding);
323 callback(binding_sequence);
325 helper(binding.keymap);
326 binding_sequence.pop();
328 for each (binding in keymap.predicate_bindings) {
329 binding_sequence.push(binding);
330 callback(binding_sequence);
332 helper(binding.keymap);
333 binding_sequence.pop();
337 //outer loop is to go down the parent-chain of keymaps
338 var i = keymaps.length - 1;
339 var keymap = keymaps[i];
343 keymap = keymap.parent;
345 keymap = keymaps[--i];
352 function keymap_lookup_command (keymaps, command) {
354 for_each_key_binding(keymaps, function (bind_seq) {
355 var bind = bind_seq[bind_seq.length - 1];
356 if (bind.command == command)
357 list.push(format_binding_sequence(bind_seq));
368 * $fallthrough, $repeat and $browser_object are as for define_key.
370 * ref is the source code reference of the call to define_key.
372 * kmap is the keymap in which the binding is to be defined.
374 * seq is the key sequence being bound. it may be necessary
375 * to auto-generate new keymaps to accomodate the key sequence.
377 * only one of new_command and new_keymap will be given.
378 * the one that is given is the thing being bound to.
380 define_keywords("$fallthrough", "$repeat", "$browser_object");
381 function define_key_internal (ref, kmap, seq, new_command, new_keymap) {
383 var args = arguments;
384 var last_in_sequence; // flag to indicate the final key combo in the sequence.
385 var key; // current key combo as we iterate through the sequence.
386 var undefine_key = (new_command == null) &&
387 (new_keymap == null) &&
388 (! args.$fallthrough);
390 /* Replace `bind' with the binding specified by (cmd, fallthrough) */
391 function replace_binding (bind) {
392 if (last_in_sequence) {
393 bind.command = new_command;
394 bind.keymap = new_keymap;
395 bind.fallthrough = args.$fallthrough;
396 bind.source_code_reference = ref;
397 bind.repeat = args.$repeat;
398 bind.browser_object = args.$browser_object;
401 throw new Error("Key sequence has a non-keymap in prefix");
406 function make_binding () {
407 if (last_in_sequence) {
409 fallthrough: args.$fallthrough,
410 command: new_command,
412 source_code_reference: ref,
413 repeat: args.$repeat,
414 browser_object: args.$browser_object,
418 kmap = new keymap($anonymous,
419 $name = old_kmap.name + " " + format_key_spec(key));
420 kmap.bound_in = old_kmap;
423 source_code_reference: ref,
424 bound_in: old_kmap };
429 for (var i = 0, slen = seq.length; i < slen; ++i) {
431 last_in_sequence = (i == slen - 1);
433 if (typeof key == "function") { // it's a match predicate
434 // Check if the binding is already present in the keymap
435 var pred_binds = kmap.predicate_bindings;
436 for (var j = 0, pblen = pred_binds.length; j < pblen; j++) {
437 if (pred_binds[j].key == key) {
438 if (last_in_sequence && undefine_key)
439 pred_binds.splice(j, 1);
441 replace_binding(pred_binds[j]);
446 pred_binds.push(make_binding());
448 // Check if the binding is already present in the keymap
449 var bindings = kmap.bindings;
450 var binding = bindings[key];
452 if (last_in_sequence && undefine_key)
453 delete bindings[key];
455 replace_binding(binding);
459 bindings[key] = make_binding();
465 * bind SEQ to a keymap or command CMD in keymap KMAP.
469 * $fallthrough: specifies that the keypress event will fall through
470 * to gecko. Note, by this method, only keypress will fall through.
471 * Keyup and keydown will still be blocked.
473 * $repeat: (command name) shortcut command to call if a prefix
474 * command key is pressed twice in a row.
476 * $browser_object: (browser object) Override the default
477 * browser-object for the command.
479 define_keywords("$fallthrough", "$repeat", "$browser_object");
480 function define_key (kmap, seq, cmd) {
484 var ref = get_caller_source_code_reference();
486 if (typeof seq == "string" && seq.length > 1)
487 seq = seq.split(" ");
489 if (!(typeof seq == "object") || !(seq instanceof Array))
492 // normalize the order of modifiers in key combos
495 if (typeof(k) == "string")
496 return format_key_combo(unformat_key_combo(k));
501 var new_command = null;
502 var new_keymap = null;
503 if (typeof cmd == "string" || typeof cmd == "function")
505 else if (cmd instanceof keymap)
507 else if (cmd != null)
508 throw new Error("Invalid `cmd' argument: " + cmd);
510 define_key_internal(ref, kmap, seq, new_command, new_keymap,
511 forward_keywords(arguments));
513 } catch (e if (typeof e == "string")) {
514 dumpln("Warning: Error occurred while binding sequence: " + orig_seq);
520 function undefine_key (kmap, seq) {
521 define_key(kmap, seq);
528 * Takes a keymap and a predicate on an event. Fallthroughs defined by
529 * these means will cause all three of keydown, keypress, and keyup to
530 * fall through to gecko, whereas those defined by the $fallthrough
531 * keyword to define_key only affect keypress events.
533 * The limitations of this method are that only the keyCode is available
534 * to the predicate, not the charCode, and keymap inheritance is not
535 * available for these "bindings".
537 function define_fallthrough (keymap, predicate) {
538 keymap.fallthrough.push(predicate);
543 * Minibuffer Read Key Binding
546 define_keymap("key_binding_reader_keymap");
547 define_key(key_binding_reader_keymap, match_any_key, "read-key-binding-key");
549 define_keywords("$keymap");
550 function key_binding_reader (minibuffer, continuation) {
551 keywords(arguments, $prompt = "Describe key:");
552 minibuffer_input_state.call(this, minibuffer, key_binding_reader_keymap, arguments.$prompt);
553 this.continuation = continuation;
554 if (arguments.$keymap)
555 this.target_keymap = arguments.$keymap;
557 this.target_keymap = get_current_keymaps(this.minibuffer.window).slice();
558 this.key_sequence = [];
560 key_binding_reader.prototype = {
561 constructor: key_binding_reader,
562 __proto__: minibuffer_input_state.prototype,
563 destroy: function () {
564 if (this.continuation)
565 this.continuation.throw(abort());
566 minibuffer_input_state.prototype.destroy.call(this);
570 function invalid_key_binding (seq) {
571 var e = new Error(seq.join(" ") + " is undefined");
572 e.key_sequence = seq;
573 e.__proto__ = invalid_key_binding.prototype;
576 invalid_key_binding.prototype = {
577 constructor: invalid_key_binding,
578 __proto__: interactive_error.prototype
581 function read_key_binding_key (window, state, event) {
582 var combo = format_key_combo(event);
583 var binding = keymap_lookup(state.target_keymap, combo, event);
585 state.key_sequence.push(combo);
587 if (binding == null) {
588 var c = state.continuation;
589 delete state.continuation;
590 window.minibuffer.pop_state();
591 c.throw(invalid_key_binding(state.key_sequence));
595 if (binding.constructor == Array) { //keymaps stack
596 window.minibuffer._restore_normal_state();
597 window.minibuffer._input_text = state.key_sequence.join(" ") + " ";
598 state.target_keymap = binding;
602 var c = state.continuation;
603 delete state.continuation;
605 window.minibuffer.pop_state();
608 c([state.key_sequence, binding]);
610 interactive("read-key-binding-key",
611 "Handle a keystroke in the key binding reader minibuffer state.",
613 read_key_binding_key(I.window,
614 I.minibuffer.check_state(key_binding_reader),
618 minibuffer.prototype.read_key_binding = function () {
620 var s = new key_binding_reader(this, (yield CONTINUATION), forward_keywords(arguments));
622 var result = yield SUSPEND;
623 yield co_return(result);