2 * (C) Copyright 2004-2007 Shawn Betts
3 * (C) Copyright 2007-2009 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) {
45 return ((event.keyCode && event.keyCode < 41) ||
46 (event.charCode == 32) ||
47 (event.button != null));
50 function (event) { event.shiftKey = true; })
52 var modifier_order = ['C', 'M', 'S'];
54 // check the platform and guess whether we should treat Alt as Meta
55 if (get_os() == 'Darwin') {
56 // In OS X, alt is a shift-like modifier, in that we
57 // only care about it for non-character events.
58 modifiers.A = new modifier(
61 return ((event.keyCode && event.keyCode < 41) ||
62 (event.charCode == 32) ||
63 (event.button != null));
66 function (event) { event.altKey = true; });
67 modifier_order = ['C', 'M', 'A', 'S'];
69 modifiers.M = modifiers.A;
78 function format_key_combo (event) {
80 for each (var M in modifier_order) {
81 if (modifiers[M].in_event_p(event) ||
82 (event.sticky_modifiers &&
83 event.sticky_modifiers.indexOf(M) != -1))
89 if (event.charCode == 32)
92 combo += String.fromCharCode(event.charCode);
93 } else if (event.keyCode) {
94 combo += keycode_to_vk_name[event.keyCode];
95 } else if (event.button != null) {
96 combo += "mouse" + (event.button + 1);
101 function unformat_key_combo (combo) {
112 var len = combo.length - 2;
114 while (i < len && combo[i+1] == '-') {
116 modifiers[M].set_in_event(event);
119 var key = combo.substring(i);
121 event.charCode = key.charCodeAt(0);
122 else if (key == 'space')
124 else if (vk_name_to_keycode[key])
125 event.keyCode = vk_name_to_keycode[key];
126 else if (key.substring(0, 5) == 'mouse')
127 event.button = parseInt(key.substring(5));
137 define_keywords("$parent", "$help", "$name", "$anonymous");
140 this.parent = arguments.$parent;
142 this.predicate_bindings = [];
143 this.fallthrough = [];
144 this.help = arguments.$help;
145 this.name = arguments.$name;
146 this.anonymous = arguments.$anonymous;
149 function define_keymap (name) {
151 this[name] = new keymap($name = name, forward_keywords(arguments));
157 * Key Match Predicates.
160 function define_key_match_predicate (name, description, predicate) {
161 conkeror[name] = predicate;
162 conkeror[name].name = name;
163 conkeror[name].description = description;
164 return conkeror[name];
167 define_key_match_predicate('match_any_key', 'any key',
168 function (event) true);
170 // should be renamed to match_any_unmodified_character
171 define_key_match_predicate('match_any_unmodified_character', 'any unmodified character',
173 // this predicate can be used for both keypress and keydown
176 return ((event.type == 'keypress' && event.charCode)
177 || event.keyCode > 31)
178 && !modifiers.A.in_event_p(event)
181 && !event.sticky_modifiers;
182 } catch (e) { return false; }
185 define_key_match_predicate('match_checkbox_keys', 'checkbox keys',
187 return event.keyCode == 32
192 //XXX: keycode fallthroughs don't support sticky modifiers
195 define_key_match_predicate('match_text_keys', 'text editing keys',
197 return (event.keyCode == 13 || event.keyCode > 31)
200 && !modifiers.A.in_event_p(event);
201 //XXX: keycode fallthroughs don't support sticky modifiers
208 function format_key_spec (key) {
209 if (key instanceof Function) {
211 return "<"+key.description+">";
213 return "<"+key.name+">";
214 return "<anonymous match function>";
219 function format_binding_sequence (seq) {
220 return seq.map(function (x) {
221 return format_key_spec(x.key);
226 function keymap_lookup (keymaps, combo, event) {
227 var i = keymaps.length - 1;
228 var kmap = keymaps[i];
231 var bindings = kmap.bindings;
232 var bind = bindings[combo];
236 new_kmaps.unshift(bind.keymap);
242 new_kmaps = [bind.keymap];
246 var pred_binds = kmap.predicate_bindings;
247 for (var j = 0; j < pred_binds.length; ++j) {
248 bind = pred_binds[j];
265 function keymap_lookup_fallthrough (keymap, event) {
266 var predicates = keymap.fallthrough;
267 for (var i = 0; i < predicates.length; ++i) {
268 if (predicates[i](event))
275 function for_each_key_binding (keymaps, callback) {
276 var binding_sequence = [];
277 function helper (keymap) {
279 for each (binding in keymap.bindings) {
280 binding_sequence.push(binding);
281 callback(binding_sequence);
283 helper(binding.keymap);
284 binding_sequence.pop();
286 for each (binding in keymap.predicate_bindings) {
287 binding_sequence.push(binding);
288 callback(binding_sequence);
290 helper(binding.keymap);
291 binding_sequence.pop();
294 //outer loop is to go down the parent-chain of keymaps
295 var i = keymaps.length - 1;
296 var keymap = keymaps[i];
300 keymap = keymap.parent;
302 keymap = keymaps[--i];
309 function keymap_lookup_command (keymaps, command) {
311 for_each_key_binding(keymaps, function (bind_seq) {
312 var bind = bind_seq[bind_seq.length - 1];
313 if (bind.command == command)
314 list.push(format_binding_sequence(bind_seq));
325 * $fallthrough, $repeat and $browser_object are as for define_key.
327 * ref is the source code reference of the call to define_key.
329 * kmap is the keymap in which the binding is to be defined.
331 * seq is the key sequence being bound. it may be necessary
332 * to auto-generate new keymaps to accomodate the key sequence.
334 * only one of new_command and new_keymap will be given.
335 * the one that is given is the thing being bound to.
337 define_keywords("$fallthrough", "$repeat", "$browser_object");
338 function define_key_internal (ref, kmap, seq, new_command, new_keymap) {
340 var args = arguments;
341 var last_in_sequence; // flag to indicate the final key combo in the sequence.
342 var key; // current key combo as we iterate through the sequence.
343 var undefine_key = (new_command == null) &&
344 (new_keymap == null) &&
345 (! args.$fallthrough);
347 /* Replace `bind' with the binding specified by (cmd, fallthrough) */
348 function replace_binding (bind) {
349 if (last_in_sequence) {
350 bind.command = new_command;
351 bind.keymap = new_keymap;
352 bind.fallthrough = args.$fallthrough;
353 bind.source_code_reference = ref;
354 bind.repeat = args.$repeat;
355 bind.browser_object = args.$browser_object;
358 throw new Error("Key sequence has a non-keymap in prefix");
363 function make_binding () {
364 if (last_in_sequence) {
366 fallthrough: args.$fallthrough,
367 command: new_command,
369 source_code_reference: ref,
370 repeat: args.$repeat,
371 browser_object: args.$browser_object,
375 kmap = new keymap($anonymous,
376 $name = old_kmap.name + " " + format_key_spec(key));
377 kmap.bound_in = old_kmap;
380 source_code_reference: ref,
381 bound_in: old_kmap };
386 for (var i = 0; i < seq.length; ++i) {
388 last_in_sequence = (i == seq.length - 1);
390 if (typeof key == "function") { // it's a match predicate
391 // Check if the binding is already present in the keymap
392 var pred_binds = kmap.predicate_bindings;
393 for (var j = 0; j < pred_binds.length; j++) {
394 if (pred_binds[j].key == key) {
395 if (last_in_sequence && undefine_key)
396 delete pred_binds[j];
398 replace_binding(pred_binds[j]);
402 pred_binds.push(make_binding());
404 // Check if the binding is already present in the keymap
405 var bindings = kmap.bindings;
406 var binding = bindings[key];
408 if (last_in_sequence && undefine_key)
409 delete bindings[key];
411 replace_binding(binding);
415 bindings[key] = make_binding();
421 * bind SEQ to a keymap or command CMD in keymap KMAP.
423 * If CMD is the special value `fallthrough', it will be bound as a
428 * $fallthrough: specifies that the keypress event will fall through
429 * to gecko. Note, by this method, only keypress will fall through.
430 * Keyup and keydown will still be blocked.
432 * $repeat: (command name) shortcut command to call if a prefix
433 * command key is pressed twice in a row.
435 * $browser_object: (browser object) Override the default
436 * browser-object for the command.
438 define_keywords("$fallthrough", "$repeat", "$browser_object");
439 function define_key (kmap, seq, cmd) {
443 var ref = get_caller_source_code_reference();
445 if (typeof seq == "string" && seq.length > 1)
446 seq = seq.split(" ");
448 if (!(typeof seq == "object") || !(seq instanceof Array))
451 // normalize the order of modifiers in key combos
454 if (typeof(k) == "string")
455 return format_key_combo(unformat_key_combo(k));
460 var new_command = null;
461 var new_keymap = null;
462 if (typeof cmd == "string" || typeof cmd == "function")
464 else if (cmd instanceof keymap)
466 else if (cmd != null)
467 throw new Error("Invalid `cmd' argument: " + cmd);
469 define_key_internal(ref, kmap, seq, new_command, new_keymap,
470 forward_keywords(arguments));
472 } catch (e if (typeof e == "string")) {
473 dumpln("Warning: Error occurred while binding sequence: " + orig_seq);
479 function undefine_key (kmap, seq) {
480 define_key(kmap, seq);
487 * Takes a keymap and a predicate on an event. Fallthroughs defined by
488 * these means will cause all three of keydown, keypress, and keyup to
489 * fall through to gecko, whereas those defined by the $fallthrough
490 * keyword to define_key only affect keypress events.
492 * The limitations of this method are that only the keyCode is available
493 * to the predicate, not the charCode, and keymap inheritance is not
494 * available for these "bindings".
496 function define_fallthrough (keymap, predicate) {
497 keymap.fallthrough.push(predicate);
502 * Minibuffer Read Key Binding
505 define_keymap("key_binding_reader_keymap");
506 define_key(key_binding_reader_keymap, match_any_key, "read-key-binding-key");
508 define_keywords("$buffer", "$keymap");
509 function key_binding_reader (window, continuation) {
510 keywords(arguments, $prompt = "Describe key:");
512 this.continuation = continuation;
514 if (arguments.$keymap)
515 this.target_keymap = arguments.$keymap;
517 var buffer = arguments.$buffer;
518 this.target_keymap = get_current_keymaps(window);
521 this.key_sequence = [];
523 minibuffer_input_state.call(this, window, key_binding_reader_keymap, arguments.$prompt);
525 key_binding_reader.prototype = {
526 __proto__: minibuffer_input_state.prototype,
527 destroy: function (window) {
528 if (this.continuation)
529 this.continuation.throw(abort());
530 minibuffer_input_state.prototype.destroy.call(this, window);
534 function invalid_key_binding (seq) {
535 var e = new Error(seq.join(" ") + " is undefined");
536 e.key_sequence = seq;
537 e.__proto__ = invalid_key_binding.prototype;
540 invalid_key_binding.prototype = {
541 __proto__: interactive_error.prototype
544 function read_key_binding_key (window, state, event) {
545 var combo = format_key_combo(event);
546 var binding = keymap_lookup(state.target_keymap, combo, event);
548 state.key_sequence.push(combo);
550 if (binding == null) {
551 var c = state.continuation;
552 delete state.continuation;
553 window.minibuffer.pop_state();
554 c.throw(invalid_key_binding(state.key_sequence));
558 if (binding.keymap) {
559 window.minibuffer._restore_normal_state();
560 window.minibuffer._input_text = state.key_sequence.join(" ") + " ";
561 state.target_keymap = binding.keymap;
565 var c = state.continuation;
566 delete state.continuation;
568 window.minibuffer.pop_state();
571 c([state.key_sequence, binding]);
573 interactive("read-key-binding-key", null, function (I) {
574 read_key_binding_key(I.window, I.minibuffer.check_state(key_binding_reader), I.event);
577 minibuffer.prototype.read_key_binding = function () {
579 var s = new key_binding_reader(this.window, (yield CONTINUATION), forward_keywords(arguments));
581 var result = yield SUSPEND;
582 yield co_return(result);