1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2026-02-17 23:45:48 +01:00

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. <S-@>
This commit is contained in:
Conrad Irwin
2009-06-08 00:56:46 +01:00
parent d12f77e347
commit 2d345fc10d
2 changed files with 149 additions and 109 deletions

View File

@@ -449,7 +449,7 @@ function Events() //{{{
key_code[name.toLowerCase()] = v 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)) if (! ("<" in key_code))
{ {
key_code["<"] = 60; 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 "<lt>";
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 * Pushes keys onto the event queue from liberator. It is similar to
* Vim's feedkeys() method, but cannot cope with 2 partially-fed * Vim's feedkeys() method, but cannot cope with 2 partially-fed
@@ -924,68 +875,25 @@ function Events() //{{{
{ {
liberator.threadYield(1, true); 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 <a>
else if (shift)
keyname = keyname.toUpperCase();
charCode = keyname.charCodeAt(0);
}
else if (keyname == "nop")
string = "<Nop>";
else if (keyCode = key_code[keyname])
charCode = 0;
else // an invalid key like <A-xxx> 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 elem = liberator.focus || window.content;
let evt = events.create(doc, "keypress", { let evt = events.create(doc, "keypress", evt_obj);
ctrlKey: ctrl, altKey: alt, shiftKey: shift, metaKey: meta ,
keyCode: keyCode, charCode: charCode
});
if (typeof noremap == "object") if (typeof noremap == "object")
for (let [k, v] in Iterator(noremap)) for (let [k, v] in Iterator(noremap))
evt[k] = v; evt[k] = v;
else else
evt.noremap = !!noremap; evt.noremap = !!noremap;
evt.isMacro = true; evt.isMacro = true;
// A special hack for liberator-specific key names. // 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. <Nop>
evt.liberatorShift = evt_obj.liberatorShift; // for untypable shift keys e.g. <S-1>
events.onKeyPress(evt); events.onKeyPress(evt);
} }
else else
elem.dispatchEvent(evt); 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) create: function (doc, type, opts)
{ {
var DEFAULTS = { var DEFAULTS = {
@@ -1048,8 +966,117 @@ function Events() //{{{
}, },
/** /**
* Converts the specified key event to a string in liberator key-code * Converts a user-input string of keys into a canonical representation.
* notation. Returns null for an unknown key event. *
* <C-A> maps to <C-a>, <C-S-a> maps to <C-S-A>
* <C- > maps to <C-Space>, <S-a> maps to A
* << maps to <lt><lt>
*
* <S-@> 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 <Nop> is passed as liberatorString, and liberatorShift is set when
* a user specifies <S-@> 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 <a>
!(keyname.length == 1 || key_code[keyname] || keyname == "nop" || /mouse$/.test(keyname)))) // disallow <misteak>
{
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 = "<Nop>";
}
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("<lt>" + 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; // <Space>
if (evt_obj.keyCode == 60 || evt_obj.charCode == 60)
evt_obj.charCode = evt_obj.keyCode = 60 // <lt>
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 "<C-n>". * E.g. pressing ctrl+n would result in the string "<C-n>".
* *
@@ -1118,15 +1145,23 @@ function Events() //{{{
{ {
key = String.fromCharCode(event.charCode); 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 (<Space> and <lt>) space can be shifted, <lt> must be forced
if ((key.match(/^\s$/) && event.shiftKey) || event.liberatorShift)
modifier += "S-"; modifier += "S-";
key = code_key[key_code[key]]; key = code_key[key_code[key]];
} }
else if (modifier.length == 0) //Otherwise, we may be able to just return the character else
return key; {
// 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) if (key == null)
return; return;

View File

@@ -13,9 +13,14 @@ will echo the current date to the command line when [m]<F2>[m] is pressed.
There are separate key mapping tables for each of the Normal, Insert, and There are separate key mapping tables for each of the Normal, Insert, and
Command-line modes. Command-line modes.
Please note that, unlike Vim and other applications, mappings containing Please note that, like Vim and other applications, modifier mappings containing
the Shift key are specified with the capital letter, so e.g. [m]<C-n>[m] is the Shift key are specified with a [m]S-[m] modifier. For example, [m]<C-N>[m]
different from [m]<C-N>[m], the latter being the only way to map Shift-Ctrl-n. is exactly the same as [m]<C-n>[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]<C-S-N>[m] or [m]<C-S-n>[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]<S-1>[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] subsection:Map{nbsp}commands[:map-commands]