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) {
46 return ("getModifierState" in event &&
47 event.getModifierState("OS"));
49 function (event) { event.superKey = true; }),
50 S: new modifier(function (event) {
54 name = keycode_to_vk_name[event.keyCode];
55 return ((name && name.length > 1) ||
56 (event.charCode == 32) ||
57 (event.button != null));
61 function (event) { event.shiftKey = true; })
63 var modifier_order = ['C', 'M', 's', 'S'];
65 // check the platform and guess whether we should treat Alt as Meta
66 if (get_os() == 'Darwin') {
67 // In OS X, alt is a shift-like modifier, in that we
68 // only care about it for non-character events.
69 modifiers.A = new modifier(
74 name = keycode_to_vk_name[event.keyCode];
75 return ((name && name.length > 1) ||
76 (event.charCode == 32) ||
77 (event.button != null));
81 function (event) { event.altKey = true; });
82 modifier_order = ['C', 'M', 'A', 'S'];
84 modifiers.M = modifiers.A;
93 function format_key_combo (event) {
95 for each (var M in modifier_order) {
96 if (modifiers[M].in_event_p(event) ||
97 (event.sticky_modifiers &&
98 event.sticky_modifiers.indexOf(M) != -1))
103 if (event.charCode) {
104 if (event.charCode == 32)
107 combo += String.fromCharCode(event.charCode);
108 } else if (event.keyCode) {
109 combo += keycode_to_vk_name[event.keyCode];
110 } else if (event.button != null) {
111 combo += "mouse" + (event.button + 1);
112 } else if (event.command) {
113 combo += "cmd" + event.command.toLowerCase();
118 function unformat_key_combo (combo) {
132 var len = combo.length - 2;
134 while (i < len && combo[i+1] == '-') {
136 modifiers[M].set_in_event(event);
139 var key = combo.substring(i);
141 event.charCode = key.charCodeAt(0);
142 else if (key == 'space')
144 else if (key.substring(0, 3) == 'cmd')
145 event.command = key.substr(3, 1).toUpperCase() +
147 else if (vk_name_to_keycode[key])
148 event.keyCode = vk_name_to_keycode[key];
149 else if (key.substring(0, 5) == 'mouse')
150 event.button = parseInt(key.substring(5));
160 define_keywords("$parent", "$help", "$name", "$anonymous",
161 "$display_name", "$notify");
164 this.parent = arguments.$parent;
166 this.predicate_bindings = [];
167 this.fallthrough = [];
168 this.help = arguments.$help;
169 this.name = arguments.$name;
170 this.display_name = arguments.$display_name;
171 this.notify = arguments.$notify;
172 this.anonymous = arguments.$anonymous;
176 toString: function () "#<keymap>"
179 function define_keymap (name) {
181 this[name] = new keymap($name = name, forward_keywords(arguments));
187 * Key Match Predicates.
190 function define_key_match_predicate (name, description, predicate) {
191 conkeror[name] = predicate;
192 conkeror[name].name = name;
193 conkeror[name].description = description;
194 return conkeror[name];
197 define_key_match_predicate('match_any_key', 'any key',
198 function (event) true);
200 // should be renamed to match_any_unmodified_character
201 define_key_match_predicate('match_any_unmodified_character', 'any unmodified character',
203 // this predicate can be used for both keypress and keydown
206 return ((event.type == 'keypress' && event.charCode)
207 || event.keyCode > 31)
208 && !modifiers.A.in_event_p(event)
211 && !modifiers.s.in_event_p(event)
212 && !event.sticky_modifiers;
213 } catch (e) { return false; }
216 define_key_match_predicate('match_checkbox_keys', 'checkbox keys',
218 return event.keyCode == 32
223 && !modifiers.s.in_event_p(event);
224 //XXX: keycode fallthroughs don't support sticky modifiers
227 define_key_match_predicate('match_text_keys', 'text editing keys',
229 return ((event.type == 'keypress' && event.charCode)
230 || event.keyCode == 13 || event.keyCode > 31)
233 && !modifiers.A.in_event_p(event)
234 && !modifiers.s.in_event_p(event);
235 //XXX: keycode fallthroughs don't support sticky modifiers
238 define_key_match_predicate('match_not_escape_key', 'any key but escape',
240 return event.keyCode != 27 ||
243 event.metaKey || // M-escape can also leave this mode, so we
244 // need to use an accurate determination of
245 // whether the "M" modifier was pressed,
246 // which is not necessarily the same as
249 modifiers.s.in_event_p(event);
256 function format_key_spec (key) {
257 if (key instanceof Function) {
259 return "<"+key.description+">";
261 return "<"+key.name+">";
262 return "<anonymous match function>";
267 function format_binding_sequence (seq) {
268 return seq.map(function (x) {
269 return format_key_spec(x.key);
274 function keymap_lookup (keymaps, combo, event) {
275 var i = keymaps.length - 1;
278 var kmap = keymaps[i];
281 var bindings = kmap.bindings;
282 var bind = bindings[combo];
286 new_kmaps.unshift(bind.keymap);
292 new_kmaps = [bind.keymap];
296 var pred_binds = kmap.predicate_bindings;
297 for (var j = 0, pblen = pred_binds.length; j < pblen; ++j) {
298 bind = pred_binds[j];
315 function keymap_lookup_fallthrough (keymap, event) {
316 var predicates = keymap.fallthrough;
317 for (var i = 0, plen = predicates.length; i < plen; ++i) {
318 if (predicates[i](event))
325 function for_each_key_binding (keymaps, callback) {
326 var binding_sequence = [];
328 function helper (keymap) {
329 if (in_keymaps.indexOf(keymap) >= 0)
331 in_keymaps.push(keymap);
333 for each (binding in keymap.bindings) {
334 binding_sequence.push(binding);
335 callback(binding_sequence);
337 helper(binding.keymap);
338 binding_sequence.pop();
340 for each (binding in keymap.predicate_bindings) {
341 binding_sequence.push(binding);
342 callback(binding_sequence);
344 helper(binding.keymap);
345 binding_sequence.pop();
349 //outer loop is to go down the parent-chain of keymaps
350 var i = keymaps.length - 1;
351 var keymap = keymaps[i];
355 keymap = keymap.parent;
357 keymap = keymaps[--i];
364 function keymap_lookup_command (keymaps, command) {
366 for_each_key_binding(keymaps, function (bind_seq) {
367 var bind = bind_seq[bind_seq.length - 1];
368 if (bind.command && bind.command == command)
369 list.push(format_binding_sequence(bind_seq));
380 * $fallthrough, $repeat and $browser_object are as for define_key.
382 * ref is the source code reference of the call to define_key.
384 * kmap is the keymap in which the binding is to be defined.
386 * seq is the key sequence being bound. it may be necessary
387 * to auto-generate new keymaps to accomodate the key sequence.
389 * only one of new_command and new_keymap will be given.
390 * the one that is given is the thing being bound to.
392 define_keywords("$fallthrough", "$repeat", "$browser_object");
393 function define_key_internal (ref, kmap, seq, new_command, new_keymap) {
395 var args = arguments;
396 var last_in_sequence; // flag to indicate the final key combo in the sequence.
397 var key; // current key combo as we iterate through the sequence.
398 var undefine_key = (new_command == null) &&
399 (new_keymap == null) &&
400 (! args.$fallthrough);
402 /* Replace `bind' with the binding specified by (cmd, fallthrough) */
403 function replace_binding (bind) {
404 if (last_in_sequence) {
405 bind.command = new_command;
406 bind.keymap = new_keymap;
407 bind.fallthrough = args.$fallthrough;
408 bind.source_code_reference = ref;
409 bind.repeat = args.$repeat;
410 bind.browser_object = args.$browser_object;
413 throw new Error("Key sequence has a non-keymap in prefix");
418 function make_binding () {
419 if (last_in_sequence) {
421 fallthrough: args.$fallthrough,
422 command: new_command,
424 source_code_reference: ref,
425 repeat: args.$repeat,
426 browser_object: args.$browser_object,
430 kmap = new keymap($anonymous,
431 $name = old_kmap.name + " " + format_key_spec(key));
432 kmap.bound_in = old_kmap;
435 source_code_reference: ref,
436 bound_in: old_kmap };
441 for (var i = 0, slen = seq.length; i < slen; ++i) {
443 last_in_sequence = (i == slen - 1);
445 if (typeof key == "function") { // it's a match predicate
446 // Check if the binding is already present in the keymap
447 var pred_binds = kmap.predicate_bindings;
448 for (var j = 0, pblen = pred_binds.length; j < pblen; j++) {
449 if (pred_binds[j].key == key) {
450 if (last_in_sequence && undefine_key)
451 pred_binds.splice(j, 1);
453 replace_binding(pred_binds[j]);
458 pred_binds.push(make_binding());
460 // Check if the binding is already present in the keymap
461 var bindings = kmap.bindings;
462 var binding = bindings[key];
464 if (last_in_sequence && undefine_key)
465 delete bindings[key];
467 replace_binding(binding);
471 bindings[key] = make_binding();
477 * bind SEQ to a keymap or command CMD in keymap KMAP.
481 * $fallthrough: specifies that the keypress event will fall through
482 * to gecko. Note, by this method, only keypress will fall through.
483 * Keyup and keydown will still be blocked.
485 * $repeat: (command name) shortcut command to call if a prefix
486 * command key is pressed twice in a row.
488 * $browser_object: (browser object) Override the default
489 * browser-object for the command.
491 define_keywords("$fallthrough", "$repeat", "$browser_object");
492 function define_key (kmap, seq, cmd) {
496 var ref = get_caller_source_code_reference();
498 if (typeof seq == "string" && seq.length > 1)
499 seq = seq.split(" ");
504 // normalize the order of modifiers in key combos
507 if (typeof(k) == "string")
508 return format_key_combo(unformat_key_combo(k));
513 var new_command = null;
514 var new_keymap = null;
515 if (typeof cmd == "string" || typeof cmd == "function")
517 else if (cmd instanceof keymap)
519 else if (cmd != null)
520 throw new Error("Invalid `cmd' argument: " + cmd);
522 define_key_internal(ref, kmap, seq, new_command, new_keymap,
523 forward_keywords(arguments));
525 } catch (e if (typeof e == "string")) {
526 dumpln("Warning: Error occurred while binding sequence: " + orig_seq);
532 function undefine_key (kmap, seq) {
533 define_key(kmap, seq);
540 * Takes a keymap and a predicate on an event. Fallthroughs defined by
541 * these means will cause all three of keydown, keypress, and keyup to
542 * fall through to gecko, whereas those defined by the $fallthrough
543 * keyword to define_key only affect keypress events.
545 * The limitations of this method are that only the keyCode is available
546 * to the predicate, not the charCode, and keymap inheritance is not
547 * available for these "bindings".
549 function define_fallthrough (keymap, predicate) {
550 keymap.fallthrough.push(predicate);
555 * Minibuffer Read Key Binding
558 define_keymap("key_binding_reader_keymap");
559 define_key(key_binding_reader_keymap, match_any_key, "read-key-binding-key");
561 define_keywords("$keymap");
562 function key_binding_reader (minibuffer) {
563 keywords(arguments, $prompt = "Describe key:");
564 minibuffer_input_state.call(this, minibuffer, key_binding_reader_keymap, arguments.$prompt);
565 this.deferred = Promise.defer();
566 this.promise = make_simple_cancelable(this.deferred);
567 if (arguments.$keymap)
568 this.target_keymap = arguments.$keymap;
570 this.target_keymap = get_current_keymaps(this.minibuffer.window).slice();
571 this.key_sequence = [];
573 key_binding_reader.prototype = {
574 constructor: key_binding_reader,
575 __proto__: minibuffer_input_state.prototype,
576 destroy: function () {
577 this.promise.cancel();
578 minibuffer_input_state.prototype.destroy.call(this);
582 function invalid_key_binding (seq) {
583 var e = new Error(seq.join(" ") + " is undefined");
584 e.key_sequence = seq;
585 e.__proto__ = invalid_key_binding.prototype;
588 invalid_key_binding.prototype = {
589 constructor: invalid_key_binding,
590 __proto__: interactive_error.prototype
593 function read_key_binding_key (window, state, event) {
594 var combo = format_key_combo(event);
595 var binding = keymap_lookup(state.target_keymap, combo, event);
597 state.key_sequence.push(combo);
599 if (binding == null) {
600 state.deferred.reject(invalid_key_binding(state.key_sequence));
601 window.minibuffer.pop_state();
605 if (array_p(binding)) { //keymaps stack
606 window.minibuffer._restore_normal_state();
607 window.minibuffer._input_text = state.key_sequence.join(" ") + " ";
608 state.target_keymap = binding;
612 state.deferred.resolve([state.key_sequence, binding]);
613 window.minibuffer.pop_state();
615 interactive("read-key-binding-key",
616 "Handle a keystroke in the key binding reader minibuffer state.",
618 read_key_binding_key(I.window,
619 I.minibuffer.check_state(key_binding_reader),
623 minibuffer.prototype.read_key_binding = function () {
625 var s = new key_binding_reader(this, forward_keywords(arguments));
627 yield co_return(yield s.promise);