mirror of
https://github.com/Julow/Unexpected-Keyboard.git
synced 2025-06-27 05:02:00 +02:00
New syntax for macros
Macros can now be written in a more elegant syntax: symbol:key1,key2,.. The symbol cannot contain a : character. 'key1', 'key2', etc.. are: - 'String with \' escaping' The key will generate the specified string. - keyevent:123 The key will send a keyevent. - The name of any special key
This commit is contained in:
parent
f3a0c89da1
commit
8d90e3c4d2
@ -479,30 +479,21 @@ public final class KeyValue implements Comparable<KeyValue>
|
|||||||
return new KeyValue("", Kind.Modifier, mod.ordinal(), 0);
|
return new KeyValue("", Kind.Modifier, mod.ordinal(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KeyValue parseKeyDefinition(String str)
|
/** Return a key by its name. If the given name doesn't correspond to any
|
||||||
{
|
special key, it is parsed with [KeyValueParser]. */
|
||||||
if (str.length() < 2 || str.charAt(0) != ':')
|
|
||||||
return makeStringKey(str);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return KeyValueParser.parse(str);
|
|
||||||
}
|
|
||||||
catch (KeyValueParser.ParseError _e)
|
|
||||||
{
|
|
||||||
return makeStringKey(str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a key by its name. If the given name doesn't correspond to a key
|
|
||||||
* defined in this function, it is passed to [parseStringKey] as a fallback.
|
|
||||||
*/
|
|
||||||
public static KeyValue getKeyByName(String name)
|
public static KeyValue getKeyByName(String name)
|
||||||
{
|
{
|
||||||
KeyValue k = getSpecialKeyByName(name);
|
KeyValue k = getSpecialKeyByName(name);
|
||||||
if (k == null)
|
if (k != null)
|
||||||
return parseKeyDefinition(name);
|
|
||||||
return k;
|
return k;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return KeyValueParser.parse(name);
|
||||||
|
}
|
||||||
|
catch (KeyValueParser.ParseError _e)
|
||||||
|
{
|
||||||
|
return makeStringKey(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KeyValue getSpecialKeyByName(String name)
|
public static KeyValue getSpecialKeyByName(String name)
|
||||||
@ -821,6 +812,7 @@ public final class KeyValue implements Comparable<KeyValue>
|
|||||||
|
|
||||||
public String toString() { return _symbol; }
|
public String toString() { return _symbol; }
|
||||||
|
|
||||||
|
@Override
|
||||||
public int compareTo(Macro snd)
|
public int compareTo(Macro snd)
|
||||||
{
|
{
|
||||||
int d = keys.length - snd.keys.length;
|
int d = keys.length - snd.keys.length;
|
||||||
|
@ -6,10 +6,17 @@ import java.util.regex.Pattern;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Parse a key definition. The syntax for a key definition is:
|
Parse a key definition. The syntax for a key definition is:
|
||||||
|
- [(symbol):(key_action)]
|
||||||
- [:(kind) (attributes):(payload)].
|
- [:(kind) (attributes):(payload)].
|
||||||
- If [str] doesn't start with a [:] character, it is interpreted as an
|
- If [str] doesn't start with a [:] character, it is interpreted as an
|
||||||
arbitrary string key.
|
arbitrary string key.
|
||||||
|
|
||||||
|
[key_action] is:
|
||||||
|
- ['Arbitrary string']
|
||||||
|
- [(key_action),(key_action),...]
|
||||||
|
- [keyevent:(code)]
|
||||||
|
- [(key_name)]
|
||||||
|
|
||||||
For the different kinds and attributes, see doc/Possible-key-values.md.
|
For the different kinds and attributes, see doc/Possible-key-values.md.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@ -18,6 +25,116 @@ Examples:
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
public final class KeyValueParser
|
public final class KeyValueParser
|
||||||
|
{
|
||||||
|
static Pattern KEYDEF_TOKEN;
|
||||||
|
static Pattern QUOTED_PAT;
|
||||||
|
static Pattern WORD_PAT;
|
||||||
|
|
||||||
|
static public KeyValue parse(String input) throws ParseError
|
||||||
|
{
|
||||||
|
int symbol_ends = 0;
|
||||||
|
final int input_len = input.length();
|
||||||
|
while (symbol_ends < input_len && input.charAt(symbol_ends) != ':')
|
||||||
|
symbol_ends++;
|
||||||
|
if (symbol_ends == 0) // Old syntax
|
||||||
|
return Starting_with_colon.parse(input);
|
||||||
|
if (symbol_ends == input_len) // String key
|
||||||
|
return KeyValue.makeStringKey(input);
|
||||||
|
String symbol = input.substring(0, symbol_ends);
|
||||||
|
ArrayList<KeyValue> keydefs = new ArrayList<KeyValue>();
|
||||||
|
init();
|
||||||
|
Matcher m = KEYDEF_TOKEN.matcher(input);
|
||||||
|
m.region(symbol_ends + 1, input_len);
|
||||||
|
do { keydefs.add(parse_key_def(m)); }
|
||||||
|
while (parse_comma(m));
|
||||||
|
for (KeyValue k : keydefs)
|
||||||
|
if (k == null)
|
||||||
|
parseError("Contains null key", m);
|
||||||
|
return KeyValue.makeMacro(symbol, keydefs.toArray(new KeyValue[]{}), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void init()
|
||||||
|
{
|
||||||
|
if (KEYDEF_TOKEN != null)
|
||||||
|
return;
|
||||||
|
KEYDEF_TOKEN = Pattern.compile("'|,|keyevent:|(?:[^\\\\',]+|\\\\.)+");
|
||||||
|
QUOTED_PAT = Pattern.compile("((?:[^'\\\\]+|\\\\')*)'");
|
||||||
|
WORD_PAT = Pattern.compile("[a-zA-Z0-9_]+|.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static KeyValue key_by_name_or_str(String str)
|
||||||
|
{
|
||||||
|
KeyValue k = KeyValue.getSpecialKeyByName(str);
|
||||||
|
if (k != null)
|
||||||
|
return k;
|
||||||
|
return KeyValue.makeStringKey(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
static KeyValue parse_key_def(Matcher m) throws ParseError
|
||||||
|
{
|
||||||
|
if (!match(m, KEYDEF_TOKEN))
|
||||||
|
parseError("Expected key definition", m);
|
||||||
|
String token = m.group(0);
|
||||||
|
switch (token)
|
||||||
|
{
|
||||||
|
case "'": return parse_string_keydef(m);
|
||||||
|
case ",": parseError("Unexpected comma", m); return null;
|
||||||
|
case "keyevent:": return parse_keyevent_keydef(m);
|
||||||
|
default: return key_by_name_or_str(remove_escaping(token));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static KeyValue parse_string_keydef(Matcher m) throws ParseError
|
||||||
|
{
|
||||||
|
if (!match(m, QUOTED_PAT))
|
||||||
|
parseError("Unterminated quoted string", m);
|
||||||
|
return KeyValue.makeStringKey(remove_escaping(m.group(1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static KeyValue parse_keyevent_keydef(Matcher m) throws ParseError
|
||||||
|
{
|
||||||
|
if (!match(m, WORD_PAT))
|
||||||
|
parseError("Expected keyevent code", m);
|
||||||
|
int eventcode = 0;
|
||||||
|
try { eventcode = Integer.parseInt(m.group(0)); }
|
||||||
|
catch (Exception _e)
|
||||||
|
{ parseError("Expected an integer payload", m); }
|
||||||
|
return KeyValue.keyeventKey("", eventcode, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns [true] if the next token is a comma, [false] if it is the end of the input. Throws an error otherwise. */
|
||||||
|
static boolean parse_comma(Matcher m) throws ParseError
|
||||||
|
{
|
||||||
|
if (!match(m, KEYDEF_TOKEN))
|
||||||
|
return false;
|
||||||
|
String token = m.group(0);
|
||||||
|
if (!token.equals(","))
|
||||||
|
parseError("Expected comma instead of '"+ token + "'", m);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String remove_escaping(String s)
|
||||||
|
{
|
||||||
|
if (!s.contains("\\"))
|
||||||
|
return s;
|
||||||
|
StringBuilder out = new StringBuilder(s.length());
|
||||||
|
final int len = s.length();
|
||||||
|
int prev = 0, i = 0;
|
||||||
|
for (; i < len; i++)
|
||||||
|
if (s.charAt(i) == '\\')
|
||||||
|
{
|
||||||
|
out.append(s, prev, i);
|
||||||
|
prev = i + 1;
|
||||||
|
}
|
||||||
|
out.append(s, prev, i);
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Parse a key definition starting with a [:]. This is the old syntax and is
|
||||||
|
kept for compatibility.
|
||||||
|
*/
|
||||||
|
final static class Starting_with_colon
|
||||||
{
|
{
|
||||||
static Pattern START_PAT;
|
static Pattern START_PAT;
|
||||||
static Pattern ATTR_PAT;
|
static Pattern ATTR_PAT;
|
||||||
@ -168,6 +285,7 @@ public final class KeyValueParser
|
|||||||
out.add(parseKeyValue(m));
|
out.add(parseKeyValue(m));
|
||||||
return out.toArray(new KeyValue[]{});
|
return out.toArray(new KeyValue[]{});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static boolean match(Matcher m, Pattern pat)
|
static boolean match(Matcher m, Pattern pat)
|
||||||
{
|
{
|
||||||
@ -186,8 +304,7 @@ public final class KeyValueParser
|
|||||||
StringBuilder msg_ = new StringBuilder("Syntax error");
|
StringBuilder msg_ = new StringBuilder("Syntax error");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
char c = m.group(0).charAt(0);
|
msg_.append(" at token '").append(m.group(0)).append("'");
|
||||||
msg_.append(" at character '").append(c).append("'");
|
|
||||||
} catch (IllegalStateException _e) {}
|
} catch (IllegalStateException _e) {}
|
||||||
msg_.append(" at position ");
|
msg_.append(" at position ");
|
||||||
msg_.append(i);
|
msg_.append(i);
|
||||||
|
@ -41,7 +41,7 @@ public class CustomExtraKeysPreference extends ListGroupPreference<String>
|
|||||||
if (key_names != null)
|
if (key_names != null)
|
||||||
{
|
{
|
||||||
for (String key_name : key_names)
|
for (String key_name : key_names)
|
||||||
kvs.put(KeyValue.parseKeyDefinition(key_name), KeyboardData.PreferredPos.DEFAULT);
|
kvs.put(KeyValue.getKeyByName(key_name), KeyboardData.PreferredPos.DEFAULT);
|
||||||
}
|
}
|
||||||
return kvs;
|
return kvs;
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,100 @@ public class KeyValueParserTest
|
|||||||
public KeyValueParserTest() {}
|
public KeyValueParserTest() {}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseStr() throws Exception
|
public void parse_key_value() throws Exception
|
||||||
|
{
|
||||||
|
Utils.parse("'", KeyValue.makeStringKey("'"));
|
||||||
|
Utils.parse("\\'", KeyValue.makeStringKey("\\'"));
|
||||||
|
Utils.parse("\\,", KeyValue.makeStringKey("\\,"));
|
||||||
|
Utils.parse("a\\'b", KeyValue.makeStringKey("a\\'b"));
|
||||||
|
Utils.parse("a\\,b", KeyValue.makeStringKey("a\\,b"));
|
||||||
|
Utils.parse("a", KeyValue.makeStringKey("a"));
|
||||||
|
Utils.parse("abc", KeyValue.makeStringKey("abc"));
|
||||||
|
Utils.parse("shift", KeyValue.getSpecialKeyByName("shift"));
|
||||||
|
Utils.parse("'a", KeyValue.makeStringKey("'a"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parse_macro() throws Exception
|
||||||
|
{
|
||||||
|
Utils.parse("symbol:abc", KeyValue.makeMacro("symbol", new KeyValue[]{
|
||||||
|
KeyValue.makeStringKey("abc")
|
||||||
|
}, 0));
|
||||||
|
Utils.parse("copy:ctrl,a,ctrl,c", KeyValue.makeMacro("copy", new KeyValue[]{
|
||||||
|
KeyValue.getSpecialKeyByName("ctrl"),
|
||||||
|
KeyValue.makeStringKey("a"),
|
||||||
|
KeyValue.getSpecialKeyByName("ctrl"),
|
||||||
|
KeyValue.makeStringKey("c")
|
||||||
|
}, 0));
|
||||||
|
Utils.parse("macro:abc,\\'", KeyValue.makeMacro("macro", new KeyValue[]{
|
||||||
|
KeyValue.makeStringKey("abc"),
|
||||||
|
KeyValue.makeStringKey("'")
|
||||||
|
}, 0));
|
||||||
|
Utils.parse("macro:abc,\\,", KeyValue.makeMacro("macro", new KeyValue[]{
|
||||||
|
KeyValue.makeStringKey("abc"),
|
||||||
|
KeyValue.makeStringKey(",")
|
||||||
|
}, 0));
|
||||||
|
Utils.parse("<2:ctrl,backspace", KeyValue.makeMacro("<2", new KeyValue[]{
|
||||||
|
KeyValue.getSpecialKeyByName("ctrl"),
|
||||||
|
KeyValue.getSpecialKeyByName("backspace")
|
||||||
|
}, 0));
|
||||||
|
Utils.expect_error("symbol:");
|
||||||
|
Utils.expect_error("unterminated_string:'");
|
||||||
|
Utils.expect_error("unterminated_string:abc,'");
|
||||||
|
Utils.expect_error("unexpected_quote:abc,,");
|
||||||
|
Utils.expect_error("unexpected_quote:,");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parse_string_key() throws Exception
|
||||||
|
{
|
||||||
|
Utils.parse("symbol:'str'", KeyValue.makeMacro("symbol", new KeyValue[]{
|
||||||
|
KeyValue.makeStringKey("str")
|
||||||
|
}, 0));
|
||||||
|
Utils.parse("symbol:'str\\''", KeyValue.makeMacro("symbol", new KeyValue[]{
|
||||||
|
KeyValue.makeStringKey("str'")
|
||||||
|
}, 0));
|
||||||
|
Utils.parse("macro:'str',abc", KeyValue.makeMacro("macro", new KeyValue[]{
|
||||||
|
KeyValue.makeStringKey("str"),
|
||||||
|
KeyValue.makeStringKey("abc")
|
||||||
|
}, 0));
|
||||||
|
Utils.parse("macro:abc,'str'", KeyValue.makeMacro("macro", new KeyValue[]{
|
||||||
|
KeyValue.makeStringKey("abc"),
|
||||||
|
KeyValue.makeStringKey("str")
|
||||||
|
}, 0));
|
||||||
|
Utils.parse("macro:\\',\\,", KeyValue.makeMacro("macro", new KeyValue[]{
|
||||||
|
KeyValue.makeStringKey("'"),
|
||||||
|
KeyValue.makeStringKey(","),
|
||||||
|
}, 0));
|
||||||
|
Utils.parse("macro:a\\'b,a\\,b,a\\xb", KeyValue.makeMacro("macro", new KeyValue[]{
|
||||||
|
KeyValue.makeStringKey("a'b"),
|
||||||
|
KeyValue.makeStringKey("a,b"),
|
||||||
|
KeyValue.makeStringKey("axb")
|
||||||
|
}, 0));
|
||||||
|
Utils.expect_error("symbol:'");
|
||||||
|
Utils.expect_error("symbol:'foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parse_key_event() throws Exception
|
||||||
|
{
|
||||||
|
Utils.parse("symbol:keyevent:85", KeyValue.makeMacro("symbol", new KeyValue[]{
|
||||||
|
KeyValue.keyeventKey("", 85, 0)
|
||||||
|
}, 0));
|
||||||
|
Utils.parse("macro:keyevent:85,abc", KeyValue.makeMacro("macro", new KeyValue[]{
|
||||||
|
KeyValue.keyeventKey("", 85, 0),
|
||||||
|
KeyValue.makeStringKey("abc")
|
||||||
|
}, 0));
|
||||||
|
Utils.parse("macro:abc,keyevent:85", KeyValue.makeMacro("macro", new KeyValue[]{
|
||||||
|
KeyValue.makeStringKey("abc"),
|
||||||
|
KeyValue.keyeventKey("", 85, 0)
|
||||||
|
}, 0));
|
||||||
|
Utils.expect_error("symbol:keyevent:");
|
||||||
|
Utils.expect_error("symbol:keyevent:85a");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parse_old_syntax() throws Exception
|
||||||
{
|
{
|
||||||
Utils.parse(":str:'Foo'", KeyValue.makeStringKey("Foo"));
|
Utils.parse(":str:'Foo'", KeyValue.makeStringKey("Foo"));
|
||||||
Utils.parse(":str flags='dim':'Foo'", KeyValue.makeStringKey("Foo", KeyValue.FLAG_SECONDARY));
|
Utils.parse(":str flags='dim':'Foo'", KeyValue.makeStringKey("Foo", KeyValue.FLAG_SECONDARY));
|
||||||
@ -32,47 +125,17 @@ public class KeyValueParserTest
|
|||||||
Utils.expect_error(":str flags='' ");
|
Utils.expect_error(":str flags='' ");
|
||||||
Utils.expect_error(":str flags='':");
|
Utils.expect_error(":str flags='':");
|
||||||
Utils.expect_error(":str flags='':'");
|
Utils.expect_error(":str flags='':'");
|
||||||
}
|
// Char
|
||||||
|
|
||||||
@Test
|
|
||||||
public void parseChar() throws Exception
|
|
||||||
{
|
|
||||||
Utils.parse(":char symbol='a':b", KeyValue.makeCharKey('b', "a", 0));
|
Utils.parse(":char symbol='a':b", KeyValue.makeCharKey('b', "a", 0));
|
||||||
Utils.parse(":char:b", KeyValue.makeCharKey('b', "b", 0));
|
Utils.parse(":char:b", KeyValue.makeCharKey('b', "b", 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void parseKeyValue() throws Exception
|
|
||||||
{
|
|
||||||
Utils.parse("\'", KeyValue.makeStringKey("\'"));
|
|
||||||
Utils.parse("a", KeyValue.makeStringKey("a"));
|
|
||||||
Utils.parse("abc", KeyValue.makeStringKey("abc"));
|
|
||||||
Utils.parse("shift", KeyValue.getSpecialKeyByName("shift"));
|
|
||||||
Utils.expect_error("\'a");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void parseMacro() throws Exception
|
|
||||||
{
|
|
||||||
Utils.parse(":macro symbol='copy':ctrl,a,ctrl,c", KeyValue.makeMacro("copy", new KeyValue[]{
|
|
||||||
KeyValue.getSpecialKeyByName("ctrl"),
|
|
||||||
KeyValue.makeStringKey("a"),
|
|
||||||
KeyValue.getSpecialKeyByName("ctrl"),
|
|
||||||
KeyValue.makeStringKey("c")
|
|
||||||
}, 0));
|
|
||||||
Utils.parse(":macro:abc,'", KeyValue.makeMacro("macro", new KeyValue[]{
|
|
||||||
KeyValue.makeStringKey("abc"),
|
|
||||||
KeyValue.makeStringKey("'")
|
|
||||||
}, 0));
|
|
||||||
Utils.expect_error(":macro:");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** JUnit removes these functions from stacktraces. */
|
/** JUnit removes these functions from stacktraces. */
|
||||||
static class Utils
|
static class Utils
|
||||||
{
|
{
|
||||||
static void parse(String key_descr, KeyValue ref) throws Exception
|
static void parse(String key_descr, KeyValue ref) throws Exception
|
||||||
{
|
{
|
||||||
assertEquals(ref, KeyValueParser.parse(key_descr));
|
assertEquals(ref, KeyValue.getKeyByName(key_descr));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void expect_error(String key_descr)
|
static void expect_error(String key_descr)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user