diff --git a/doc/Possible-key-values.md b/doc/Possible-key-values.md index 1c0908f..2073627 100644 --- a/doc/Possible-key-values.md +++ b/doc/Possible-key-values.md @@ -1,9 +1,30 @@ # Key values -This is an exhaustive list of special values accepted for the `key0` through `key8` or `nw` through `se` attributes on a key. +A key value is the denomination of a key accepted in the "Add keys to the keyboard" option or for the `nw`, ..., `se` attributes in custom layouts (or `key0` ... `key8`). +It can be: -Any string that does not exactly match these will be printed verbatim. -A key can output multiple characters, but cannot combine multiple built-in key values. +- The name of a special key. An exhaustive list of the special keys follows. + +- An arbitrary sequence of characters not containing `:`. + This results in a key that writes the specified characters. + +- Using the syntax `symbol:key_def`. + `symbol` is the symbol that appears on the keyboard, it cannot contain `:`. + `key_def` can be: + + The name of a special key, as listed below. + + `'Arbitrary string'` An arbitrary string that can contain `:`. `'` can be added to the string as `` \' ``. + + `keyevent:keycode` An Android keycode. They are listed as `KEYCODE_...` in [KeyEvent](https://developer.android.com/reference/android/view/KeyEvent#summary). + + Examples: + + `⏯:keyevent:85` A play/pause key (which probably doesn't do anything in most apps). + + `my@:'my.email@domain.com'` An arbitrary string key + +- A macro, `symbol:key_def1,key_def2,...`. + This results in a key that behaves as if the sequence of `key_def` had been pressed in order. + + Examples: + + `CA:ctrl,a,ctrl,c` The sequence `ctrl+a`, `ctrl+c`. + + `Cd:ctrl,backspace` The shortcut `ctrl+backspace`. ## Escape codes Value | Escape code for @@ -152,50 +173,3 @@ These keys are known to do nothing. These keys are normally hidden unless the Fn modifier is activated. `f11_placeholder` | `f12_placeholder` - -## Complex keys - -More complex keys are of this form: - -``` -: : -``` - -Where `` is one of the kinds documented below and `` is a -space separated list of attributes. `` depends on the ``. - -Attributes are: -- `symbol='Sym'` specifies the symbol to be shown on the keyboard. -- `flags=''` changes the behavior of the key. - `` is a coma separated list of: - + `dim`: Make the symbol dimmer. - + `small`: Make the symbol smaller. - -### Kind `str` - -Defines a key that outputs an arbitrary string. `` is a string wrapped -in single-quotes (`'`), escaping of other single quotes is allowed with `\'`. - -For example: -- `:str:'Arbitrary string with a \' inside'` -- `:str symbol='Symbol':'Output string'` - -### Kind `char` - -Defines a key that outputs a single character. `` is the character to -output, unquoted. -This kind of key can be used to define a character key with a different symbol -on it. `char` keys can be modified by `ctrl` and other modifiers, unlike `str` -keys. - -For example: -- `:char symbol='љ':q`, which is used to implement `ctrl` shortcuts in cyrillic - layouts. - -### Kind `keyevent` - -Defines a key that sends an Android [key event](https://developer.android.com/reference/android/view/KeyEvent). -`` is the key event number. - -For example: -- `:keyevent symbol='⏯' flags='small':85` diff --git a/srcs/juloo.keyboard2/Autocapitalisation.java b/srcs/juloo.keyboard2/Autocapitalisation.java index bf28e9d..a77d2e5 100644 --- a/srcs/juloo.keyboard2/Autocapitalisation.java +++ b/srcs/juloo.keyboard2/Autocapitalisation.java @@ -88,6 +88,24 @@ public final class Autocapitalisation callback_now(true); } + /** Pause auto capitalisation until [unpause()] is called. */ + public boolean pause() + { + boolean was_enabled = _enabled; + stop(); + _enabled = false; + return was_enabled; + } + + /** Continue auto capitalisation after [pause()] was called. Argument is the + output of [pause()]. */ + public void unpause(boolean was_enabled) + { + _enabled = was_enabled; + _should_update_caps_mode = true; + callback_now(true); + } + public static interface Callback { public void update_shift_state(boolean should_enable, boolean should_disable); diff --git a/srcs/juloo.keyboard2/KeyEventHandler.java b/srcs/juloo.keyboard2/KeyEventHandler.java index 6809d88..d2546ab 100644 --- a/srcs/juloo.keyboard2/KeyEventHandler.java +++ b/srcs/juloo.keyboard2/KeyEventHandler.java @@ -97,11 +97,10 @@ public final class KeyEventHandler case Keyevent: send_key_down_up(key.getKeyevent()); break; case Modifier: break; case Editing: handle_editing_key(key.getEditing()); break; - case Compose_pending: - _recv.set_compose_pending(true); - break; + case Compose_pending: _recv.set_compose_pending(true); break; case Slider: handle_slider(key.getSlider(), key.getSliderRepeat()); break; case StringWithSymbol: send_text(key.getStringWithSymbol()); break; + case Macro: evaluate_macro(key.getMacro()); break; } update_meta_state(old_mods); } @@ -319,6 +318,35 @@ public final class KeyEventHandler send_key_down_up_repeat(KeyEvent.KEYCODE_DPAD_DOWN, d); } + void evaluate_macro(KeyValue[] keys) + { + final Pointers.Modifiers empty = Pointers.Modifiers.EMPTY; + // Ignore modifiers that are activated at the time the macro is evaluated + mods_changed(empty); + Pointers.Modifiers mods = empty; + final boolean autocap_paused = _autocap.pause(); + for (KeyValue kv : keys) + { + kv = KeyModifier.modify(kv, mods); + if (kv == null) + continue; + if (kv.hasFlagsAny(KeyValue.FLAG_LATCH)) + { + // Non-special latchable keys clear latched modifiers + if (!kv.hasFlagsAny(KeyValue.FLAG_SPECIAL)) + mods = empty; + mods = mods.with_extra_mod(kv); + } + else + { + key_down(kv, false); + key_up(kv, mods); + mods = empty; + } + } + _autocap.unpause(autocap_paused); + } + /** Repeat calls to [send_key_down_up]. */ void send_key_down_up_repeat(int event_code, int repeat) { diff --git a/srcs/juloo.keyboard2/KeyValue.java b/srcs/juloo.keyboard2/KeyValue.java index 60d49e4..b2ef5db 100644 --- a/srcs/juloo.keyboard2/KeyValue.java +++ b/srcs/juloo.keyboard2/KeyValue.java @@ -96,6 +96,7 @@ public final class KeyValue implements Comparable String, // [_payload] is also the string to output, value is unused. Slider, // [_payload] is a [KeyValue.Slider], value is slider repeatition. StringWithSymbol, // [_payload] is a [KeyValue.StringWithSymbol], value is unused. + Macro, // [_payload] is a [KeyValue.Macro], value is unused. } private static final int FLAGS_OFFSET = 20; @@ -105,7 +106,8 @@ public final class KeyValue implements Comparable public static final int FLAG_LATCH = (1 << FLAGS_OFFSET << 0); // Key can be locked by typing twice when enabled in settings public static final int FLAG_DOUBLE_TAP_LOCK = (1 << FLAGS_OFFSET << 1); - // Special keys are not repeated and don't clear latched modifiers. + // Special keys are not repeated. + // Special latchable keys don't clear latched modifiers. public static final int FLAG_SPECIAL = (1 << FLAGS_OFFSET << 2); // Whether the symbol should be greyed out. For example, keys that are not // part of the pending compose sequence. @@ -229,6 +231,12 @@ public final class KeyValue implements Comparable return ((StringWithSymbol)_payload).str; } + /** Defined only when [getKind() == Kind.Macro]. */ + public KeyValue[] getMacro() + { + return ((Macro)_payload).keys; + } + /* Update the char and the symbol. */ public KeyValue withChar(char c) { @@ -460,31 +468,35 @@ public final class KeyValue implements Comparable Kind.StringWithSymbol, 0, flags); } + public static KeyValue makeMacro(String symbol, KeyValue[] keys, int flags) + { + return new KeyValue(new Macro(keys, symbol), Kind.Macro, 0, flags); + } + /** Make a modifier key for passing to [KeyModifier]. */ public static KeyValue makeInternalModifier(Modifier mod) { 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]. */ + public static KeyValue getKeyByName(String name) { - if (str.length() < 2 || str.charAt(0) != ':') - return makeStringKey(str); + KeyValue k = getSpecialKeyByName(name); + if (k != null) + return k; try { - return KeyValueParser.parse(str); + return KeyValueParser.parse(name); } catch (KeyValueParser.ParseError _e) { - return makeStringKey(str); + return makeStringKey(name); } } - /** - * 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 getSpecialKeyByName(String name) { switch (name) { @@ -735,8 +747,7 @@ public final class KeyValue implements Comparable case "௲": case "௳": return makeStringKey(name, FLAG_SMALLER_FONT); - /* The key is not one of the special ones. */ - default: return parseKeyDefinition(name); + default: return null; } } @@ -787,4 +798,31 @@ public final class KeyValue implements Comparable @Override public String toString() { return symbol; } }; + + public static final class Macro implements Comparable + { + public final KeyValue[] keys; + private final String _symbol; + + public Macro(KeyValue[] keys_, String sym_) + { + keys = keys_; + _symbol = sym_; + } + + public String toString() { return _symbol; } + + @Override + public int compareTo(Macro snd) + { + int d = keys.length - snd.keys.length; + if (d != 0) return d; + for (int i = 0; i < keys.length; i++) + { + d = keys[i].compareTo(snd.keys[i]); + if (d != 0) return d; + } + return _symbol.compareTo(snd._symbol); + } + }; } diff --git a/srcs/juloo.keyboard2/KeyValueParser.java b/srcs/juloo.keyboard2/KeyValueParser.java index 0a5ce17..488f5d3 100644 --- a/srcs/juloo.keyboard2/KeyValueParser.java +++ b/srcs/juloo.keyboard2/KeyValueParser.java @@ -1,14 +1,22 @@ package juloo.keyboard2; +import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; /** Parse a key definition. The syntax for a key definition is: +- [(symbol):(key_action)] - [:(kind) (attributes):(payload)]. - If [str] doesn't start with a [:] character, it is interpreted as an 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. Examples: @@ -18,103 +26,232 @@ Examples: */ public final class KeyValueParser { - static Pattern START_PAT; - static Pattern ATTR_PAT; + static Pattern KEYDEF_TOKEN; static Pattern QUOTED_PAT; - static Pattern PAYLOAD_START_PAT; static Pattern WORD_PAT; - static public KeyValue parse(String str) throws ParseError + static public KeyValue parse(String input) throws ParseError { - String symbol = null; - int flags = 0; + 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 keydefs = new ArrayList(); init(); - // Kind - Matcher m = START_PAT.matcher(str); - if (!m.lookingAt()) - parseError("Expected kind, for example \":str ...\".", m); - String kind = m.group(1); - // Attributes - while (true) - { - if (!match(m, ATTR_PAT)) - break; - String attr_name = m.group(1); - String attr_value = parseSingleQuotedString(m); - switch (attr_name) - { - case "flags": - flags = parseFlags(attr_value, m); - break; - case "symbol": - symbol = attr_value; - break; - - default: - parseError("Unknown attribute "+attr_name, m); - } - } - // Payload - if (!match(m, PAYLOAD_START_PAT)) - parseError("Unexpected character", m); - String payload; - switch (kind) - { - case "str": - payload = parseSingleQuotedString(m); - if (symbol == null) - return KeyValue.makeStringKey(payload, flags); - return KeyValue.makeStringKeyWithSymbol(payload, symbol, flags); - - case "char": - payload = parsePayloadWord(m); - if (payload.length() != 1) - parseError("Expected a single character payload", m); - return KeyValue.makeCharKey(payload.charAt(0), symbol, flags); - - case "keyevent": - payload = parsePayloadWord(m); - int eventcode = 0; - try { eventcode = Integer.parseInt(payload); } - catch (Exception _e) - { parseError("Expected an integer payload", m); } - if (symbol == null) - symbol = String.valueOf(eventcode); - return KeyValue.keyeventKey(symbol, eventcode, flags); - - default: break; - } - parseError("Unknown kind '"+kind+"'", m, 1); - return null; // Unreachable + 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 String parseSingleQuotedString(Matcher m) throws ParseError + 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("Expected quoted string", m); - return m.group(1).replace("\\'", "'"); + parseError("Unterminated quoted string", m); + return KeyValue.makeStringKey(remove_escaping(m.group(1))); } - static String parsePayloadWord(Matcher m) throws ParseError + static KeyValue parse_keyevent_keydef(Matcher m) throws ParseError { if (!match(m, WORD_PAT)) - parseError("Expected a word after ':' made of [a-zA-Z0-9_]", m); - return m.group(0); + 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); } - static int parseFlags(String s, Matcher m) throws ParseError + /** 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 { - int flags = 0; - for (String f : s.split(",")) - { - switch (f) + 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) == '\\') { - case "dim": flags |= KeyValue.FLAG_SECONDARY; break; - case "small": flags |= KeyValue.FLAG_SMALLER_FONT; break; - default: parseError("Unknown flag "+f, m); + 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 ATTR_PAT; + static Pattern QUOTED_PAT; + static Pattern PAYLOAD_START_PAT; + static Pattern WORD_PAT; + + static public KeyValue parse(String str) throws ParseError + { + String symbol = null; + int flags = 0; + init(); + // Kind + Matcher m = START_PAT.matcher(str); + if (!m.lookingAt()) + parseError("Expected kind, for example \":str ...\".", m); + String kind = m.group(1); + // Attributes + while (true) + { + if (!match(m, ATTR_PAT)) + break; + String attr_name = m.group(1); + String attr_value = parseSingleQuotedString(m); + switch (attr_name) + { + case "flags": + flags = parseFlags(attr_value, m); + break; + case "symbol": + symbol = attr_value; + break; + + default: + parseError("Unknown attribute "+attr_name, m); + } + } + // Payload + if (!match(m, PAYLOAD_START_PAT)) + parseError("Unexpected character", m); + String payload; + switch (kind) + { + case "str": + payload = parseSingleQuotedString(m); + if (symbol == null) + return KeyValue.makeStringKey(payload, flags); + return KeyValue.makeStringKeyWithSymbol(payload, symbol, flags); + + case "char": + payload = parsePayloadWord(m); + if (payload.length() != 1) + parseError("Expected a single character payload", m); + return KeyValue.makeCharKey(payload.charAt(0), symbol, flags); + + case "keyevent": + payload = parsePayloadWord(m); + int eventcode = 0; + try { eventcode = Integer.parseInt(payload); } + catch (Exception _e) + { parseError("Expected an integer payload", m); } + if (symbol == null) + symbol = String.valueOf(eventcode); + return KeyValue.keyeventKey(symbol, eventcode, flags); + + default: break; + } + parseError("Unknown kind '"+kind+"'", m, 1); + return null; // Unreachable + } + + static String parseSingleQuotedString(Matcher m) throws ParseError + { + if (!match(m, QUOTED_PAT)) + parseError("Expected quoted string", m); + return m.group(1).replace("\\'", "'"); + } + + static String parsePayloadWord(Matcher m) throws ParseError + { + if (!match(m, WORD_PAT)) + parseError("Expected a word after ':' made of [a-zA-Z0-9_]", m); + return m.group(0); + } + + static int parseFlags(String s, Matcher m) throws ParseError + { + int flags = 0; + for (String f : s.split(",")) + { + switch (f) + { + case "dim": flags |= KeyValue.FLAG_SECONDARY; break; + case "small": flags |= KeyValue.FLAG_SMALLER_FONT; break; + default: parseError("Unknown flag "+f, m); + } + } + return flags; + } + + static boolean match(Matcher m, Pattern pat) + { + try { m.region(m.end(), m.regionEnd()); } catch (Exception _e) {} + m.usePattern(pat); + return m.lookingAt(); + } + + static void init() + { + if (START_PAT != null) + return; + START_PAT = Pattern.compile(":(\\w+)"); + ATTR_PAT = Pattern.compile("\\s*(\\w+)\\s*="); + QUOTED_PAT = Pattern.compile("'(([^'\\\\]+|\\\\')*)'"); + PAYLOAD_START_PAT = Pattern.compile("\\s*:"); + WORD_PAT = Pattern.compile("[a-zA-Z0-9_]*"); } - return flags; } static boolean match(Matcher m, Pattern pat) @@ -124,17 +261,6 @@ public final class KeyValueParser return m.lookingAt(); } - static void init() - { - if (START_PAT != null) - return; - START_PAT = Pattern.compile(":(\\w+)"); - ATTR_PAT = Pattern.compile("\\s*(\\w+)\\s*="); - QUOTED_PAT = Pattern.compile("'(([^'\\\\]+|\\\\')*)'"); - PAYLOAD_START_PAT = Pattern.compile("\\s*:"); - WORD_PAT = Pattern.compile("[a-zA-Z0-9_]*"); - } - static void parseError(String msg, Matcher m) throws ParseError { parseError(msg, m, m.regionStart()); @@ -145,8 +271,7 @@ public final class KeyValueParser StringBuilder msg_ = new StringBuilder("Syntax error"); try { - char c = m.group(0).charAt(0); - msg_.append(" at character '").append(c).append("'"); + msg_.append(" at token '").append(m.group(0)).append("'"); } catch (IllegalStateException _e) {} msg_.append(" at position "); msg_.append(i); diff --git a/srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java b/srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java index 6f4cf41..253e074 100644 --- a/srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java +++ b/srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java @@ -41,7 +41,7 @@ public class CustomExtraKeysPreference extends ListGroupPreference if (key_names != null) { 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; } diff --git a/test/juloo.keyboard2/KeyValueParserTest.java b/test/juloo.keyboard2/KeyValueParserTest.java index a636ebf..a041e8a 100644 --- a/test/juloo.keyboard2/KeyValueParserTest.java +++ b/test/juloo.keyboard2/KeyValueParserTest.java @@ -10,7 +10,100 @@ public class KeyValueParserTest public KeyValueParserTest() {} @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 flags='dim':'Foo'", KeyValue.makeStringKey("Foo", KeyValue.FLAG_SECONDARY)); @@ -32,11 +125,7 @@ public class KeyValueParserTest Utils.expect_error(":str flags='' "); Utils.expect_error(":str flags='':"); Utils.expect_error(":str flags='':'"); - } - - @Test - public void parseChar() throws Exception - { + // Char Utils.parse(":char symbol='a':b", KeyValue.makeCharKey('b', "a", 0)); Utils.parse(":char:b", KeyValue.makeCharKey('b', "b", 0)); } @@ -46,7 +135,7 @@ public class KeyValueParserTest { 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)