From 2d345fc10daaa67913f32b731368e4eb48e75231 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jun 2009 00:56:46 +0100 Subject: [PATCH] Refactor events keyhandling and document changes. Re-introduced S- into the canonical mappings so that re-canonicalising will not differ from the original canonicalised form. Documented recent changes to S- modifier in help. Refactor events.js to avoid replicating parsing key-strings in two places. The new fromString is the opposite of toString, and canonicalisation now proceeds by doing both. Added the .liberatorShift property to event-representations to allow for key-combinations that cannot be typed, but can be mapped to. e.g. --- common/content/events.js | 247 ++++++++++++++++++-------------- vimperator/locale/en-US/map.txt | 11 +- 2 files changed, 149 insertions(+), 109 deletions(-) diff --git a/common/content/events.js b/common/content/events.js index 2cabcef1..e2656b9c 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -449,7 +449,7 @@ function Events() //{{{ key_code[name.toLowerCase()] = v } - //HACK: as firefox does not include an event for <, we must add this in manually. + // HACK: as firefox does not include an event for <, we must add this in manually. if (! ("<" in key_code)) { key_code["<"] = 60; @@ -846,55 +846,6 @@ function Events() //{{{ } }, - splitKeys: function(keys) { - let re = RegExp("<.*?>|[^<]|<(?!.*>)", "g"); - let match; - while (match = re.exec(keys)) - yield match[0]; - }, - - canonicalKeys: function (keys) - { - var res = util.map(events.splitKeys(keys), function (key) { - let keyCode = 0; - - if (key == "<") - return ""; - - if (key[0] == "<") - { - let [match, modifier, keyname] = key.toLowerCase().match(/^<((?:[csma]-)*)(.+?)>$/) || []; - if (keyname) - { - modifier = modifier.toUpperCase(); - - if (modifier.length > 0 && keyname.length == 1 && keyname.toUpperCase() != keyname.toLowerCase()) - { - if (modifier.indexOf("S-") >= 0) - keyname = keyname.toUpperCase(); - else - keyname = keyname.toLowerCase(); - modifier = modifier.replace("S-", ""); - } - - key = [k + "-" for ([i, k] in Iterator("CASM")) if (modifier.indexOf(k + "-") >= 0)]; - keyCode = key_code[keyname]; - - let c = String.fromCharCode(keyCode); - if (key.length == 0 && c == code_key[keyCode]) - return c.toLowerCase(); - else - return ["<"].concat(key, code_key[keyCode] || keyname, ">"); - } - } - else // a simple key - { - return key; - } - }); - return util.Array.flatten(res).join(""); - }, - /** * Pushes keys onto the event queue from liberator. It is similar to * Vim's feedkeys() method, but cannot cope with 2 partially-fed @@ -924,68 +875,25 @@ function Events() //{{{ { liberator.threadYield(1, true); - for (let key in events.splitKeys(keys)) + for (let [,evt_obj] in Iterator(events.fromString(keys))) { - let charCode = key.charCodeAt(0); - let keyCode = 0; - let [shift, ctrl, alt, meta] = [false, false, false, false]; - let string = null; - - if (key[0] == "<") - { - let [match, modifier, keyname] = key.match(/^<((?:[CSMA]-)*)(.+?)>$/i) || []; - if (keyname) - { - keyname = keyname.toLowerCase(); - if (modifier) // check for modifiers - { - ctrl = /C-/i.test(modifier); - alt = /A-/i.test(modifier); - shift = /S-/i.test(modifier); - meta = /M-/i.test(modifier); - } - if (keyname.length == 1) - { - if (!ctrl && !alt && !shift && !meta) - return false; // an invalid key like - else if (shift) - keyname = keyname.toUpperCase(); - charCode = keyname.charCodeAt(0); - } - else if (keyname == "nop") - string = ""; - else if (keyCode = key_code[keyname]) - charCode = 0; - else // an invalid key like was found, stop propagation here (like Vim) - break; - - if (keyCode == 32) - charCode = 32; - } - } - else // a simple key - { - shift = key != key.toLowerCase(); - } - let elem = liberator.focus || window.content; - let evt = events.create(doc, "keypress", { - ctrlKey: ctrl, altKey: alt, shiftKey: shift, metaKey: meta , - keyCode: keyCode, charCode: charCode - }); + let evt = events.create(doc, "keypress", evt_obj); + if (typeof noremap == "object") for (let [k, v] in Iterator(noremap)) evt[k] = v; else evt.noremap = !!noremap; evt.isMacro = true; - // A special hack for liberator-specific key names. - if (string) + if (evt_obj.liberatorString || evt_obj.liberatorShift) { - evt.liberatorString = string; + evt.liberatorString = evt_obj.liberatorString; // for key-less keypress events e.g. + evt.liberatorShift = evt_obj.liberatorShift; // for untypable shift keys e.g. events.onKeyPress(evt); } + else elem.dispatchEvent(evt); @@ -1013,6 +921,16 @@ function Events() //{{{ } }, + /** + * Creates an actual event from a pseudo-event object. + * + * The pseudo-event object (such as may be retrieved from events.fromString) + * should have any properties you want the event to have. + * + * @param {Document} doc The DOM document to associate this event with + * @param {Type} type The type of event (keypress, click, etc.) + * @param {Object} opts The pseudo-event. + */ create: function (doc, type, opts) { var DEFAULTS = { @@ -1048,8 +966,117 @@ function Events() //{{{ }, /** - * Converts the specified key event to a string in liberator key-code - * notation. Returns null for an unknown key event. + * Converts a user-input string of keys into a canonical representation. + * + * maps to , maps to + * maps to , maps to A + * << maps to + * + * is preserved, as in vim, to allow untypable key-combinations in macros + * + * canonicalKeys(canonicalKeys(x)) == canonicalKeys(x) for all values of x. + * + * @param {String} keys messy form + * @returns {String} canonical form + */ + canonicalKeys: function (keys) + { + return events.fromString(keys).map(events.toString).join(""); + }, + + /** + * Converts an event string into an array of pseudo-event objects. + * + * These objects can be used as arguments to events.toString or events.create, + * though they are unlikely to be much use for other purposes. They have many + * of the properties you'd expect to find on a real event, but none of the methods. + * + * Also may contain two "special" parameters, .liberatorString and .liberatorShift + * these are set for characters that can never by typed, but may appear in mappings, + * for example is passed as liberatorString, and liberatorShift is set when + * a user specifies where @ is a non-case-changable, non-space character. + * + * @param {String} keys The string to parse + * @return {Array[Object]} + */ + fromString: function (input) + { + let out = [] + + let re = RegExp("<.*?>?>|[^<]|<(?!.*>)", "g"); + let match; + + while (match = re.exec(input)) + { + let evt_str = match[0]; + let evt_obj = {ctrlKey:false, shiftKey:false,altKey:false, metaKey: false, + keyCode: 0, charCode: 0, type: "keypress"}; + + if (evt_str.length > 1) // <.*?> + { + let [match, modifier, keyname] = evt_str.match(/^<((?:[CSMA]-)*)(.+?)>$/i) || [false, '', '']; + modifier = modifier.toUpperCase(); + keyname = keyname.toLowerCase(); + + if (keyname && !(keyname.length == 1 && modifier.length == 0 || // disallow <> and + !(keyname.length == 1 || key_code[keyname] || keyname == "nop" || /mouse$/.test(keyname)))) // disallow + { + evt_obj.ctrlKey = /C-/.test(modifier); + evt_obj.altKey = /A-/.test(modifier); + evt_obj.shiftKey = /S-/.test(modifier); + evt_obj.metaKey = /M-/.test(modifier); + + if (keyname.length == 1) // normal characters + { + if (evt_obj.shiftKey) + { + keyname = keyname.toUpperCase() + if (keyname == keyname.toLowerCase()) + evt_obj.liberatorShift = true; + } + + evt_obj.charCode = keyname.charCodeAt(0); + } + else if (keyname == "nop") + { + evt_obj.liberatorString = ""; + } + else if (/mouse$/.test(keyname)) // mouse events + { + evt_obj.type = (/2-/.test(modifier) ? "dblclick" : "click"); + evt_obj.button = ["leftmouse", "middlemouse", "rightmouse"].indexOf(keyname); + delete evt_obj.keyCode; + delete evt_obj.charCode; + } + else // spaces, control characters, and < + { + evt_obj.keyCode = key_code[keyname] + evt_obj.charCode = 0; + } + } + else // an invalid sequence starting with <, treat as a literal + { + out = out.concat(events.fromString("" + evt_str.substr(1))); + continue; + } + } + else // a simple key (no <...>) + evt_obj.charCode = evt_str.charCodeAt(0); + + // TODO: make a list of characters that need keyCode and charCode somewhere + if (evt_obj.keyCode == 32 || evt_obj.charCode == 32) + evt_obj.charCode = evt_obj.keyCode = 32; // + if (evt_obj.keyCode == 60 || evt_obj.charCode == 60) + evt_obj.charCode = evt_obj.keyCode = 60 // + + out.push(evt_obj); + } + return out; + }, + + /** + * Converts the specified event to a string in liberator key-code + * notation. Returns null for an unknown event. * * E.g. pressing ctrl+n would result in the string "". * @@ -1118,15 +1145,23 @@ function Events() //{{{ { key = String.fromCharCode(event.charCode); - if (key in key_code) //If a character has a name, use that. Space characters can have S- safely. + if (key in key_code) { - if (key.match(/^\s$/) && event.shiftKey) + // a named charcode key ( and ) space can be shifted, must be forced + if ((key.match(/^\s$/) && event.shiftKey) || event.liberatorShift) modifier += "S-"; key = code_key[key_code[key]]; } - else if (modifier.length == 0) //Otherwise, we may be able to just return the character - return key; + else + { + // a shift modifier is only allowed if the key is alphabetical and used in a C-A-M- mapping in the uppercase, + // or if the shift has been forced for a non-alphabetical character by the user while :map-ping + if ((key != key.toLowerCase() && (event.ctrlKey || event.altKey || event.metaKey)) || event.liberatorShift) + modifier += "S-"; + else if (modifier.length == 0) + return key; + } } if (key == null) return; diff --git a/vimperator/locale/en-US/map.txt b/vimperator/locale/en-US/map.txt index c8ceac44..dcfc2459 100644 --- a/vimperator/locale/en-US/map.txt +++ b/vimperator/locale/en-US/map.txt @@ -13,9 +13,14 @@ will echo the current date to the command line when [m][m] is pressed. There are separate key mapping tables for each of the Normal, Insert, and Command-line modes. -Please note that, unlike Vim and other applications, mappings containing -the Shift key are specified with the capital letter, so e.g. [m][m] is -different from [m][m], the latter being the only way to map Shift-Ctrl-n. +Please note that, like Vim and other applications, modifier mappings containing +the Shift key are specified with a [m]S-[m] modifier. For example, [m][m] +is exactly the same as [m][m], it represents holding the Control key and a +lowercase [m]n[m]. In order to map the Control key with an uppercase [m]N[m], either +[m][m] or [m][m] can be used. For numbers and punctuation characters that +cannot change case, the [m]S-[m] is not used, even if you need to hold shift to type +that character. The mapping [m][m] can never be typed on a keyboard, but can +be used to create a mapping that can be activated by another map. subsection:Map{nbsp}commands[:map-commands]