forked from extern/Unexpected-Keyboard
Add complex keys (#774)
This allows to add new kinds of keys that need more data without making KeyValue's footprint bigger for common keys. This changes the [_symbol] field into [_payload], which holds the same as the previous field for more common keys but can hold bigger objects for keys of the new "Complex" kind. This also adds a complex key: String keys with a symbol different than the outputted string. Unit tests are added as the Java language is not helpful in making robust code.
This commit is contained in:
parent
e309b76c0a
commit
9f22e53a3b
11
build.gradle
11
build.gradle
@ -2,6 +2,10 @@ plugins {
|
|||||||
id 'com.android.application' version '8.1.1'
|
id 'com.android.application' version '8.1.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation "junit:junit:4.13.2"
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'juloo.keyboard2'
|
namespace 'juloo.keyboard2'
|
||||||
compileSdk 34
|
compileSdk 34
|
||||||
@ -21,6 +25,10 @@ android {
|
|||||||
res.srcDirs = ['res', 'build/generated-resources']
|
res.srcDirs = ['res', 'build/generated-resources']
|
||||||
assets.srcDirs = ['assets']
|
assets.srcDirs = ['assets']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
java.srcDirs = ['test']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
@ -84,9 +92,6 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.register('buildKeyboardFont') {
|
tasks.register('buildKeyboardFont') {
|
||||||
println "\nBuilding assets/special_font.ttf"
|
println "\nBuilding assets/special_font.ttf"
|
||||||
mkdir "$buildDir"
|
mkdir "$buildDir"
|
||||||
|
@ -119,7 +119,7 @@ Keys ending in `_placeholder` are normally hidden unless the Fn key is pressed.
|
|||||||
`ole`, `ole_placeholder`,
|
`ole`, `ole_placeholder`,
|
||||||
`meteg`, `meteg_placeholder`
|
`meteg`, `meteg_placeholder`
|
||||||
|
|
||||||
## Unexpected Keyboard specific
|
## Keyboard behavior keys
|
||||||
Value | Meaning
|
Value | Meaning
|
||||||
:--------------------- | :------
|
:--------------------- | :------
|
||||||
`config` | Gear icon; opens Unexpected Keyboard settings.
|
`config` | Gear icon; opens Unexpected Keyboard settings.
|
||||||
@ -148,3 +148,28 @@ These keys are known to do nothing.
|
|||||||
These keys are normally hidden unless the Fn modifier is activated.
|
These keys are normally hidden unless the Fn modifier is activated.
|
||||||
|
|
||||||
`f11_placeholder` | `f12_placeholder`
|
`f11_placeholder` | `f12_placeholder`
|
||||||
|
|
||||||
|
## Complex keys
|
||||||
|
|
||||||
|
More complex keys are of this form:
|
||||||
|
|
||||||
|
```
|
||||||
|
:<kind> <attributes>:<payload>
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `<kind>` is one of the kinds documented below and `<attributes>` is a
|
||||||
|
space separated list of attributes. `<payload>` depends on the `<kind>`.
|
||||||
|
|
||||||
|
Attributes are:
|
||||||
|
- `symbol='Sym'` is the symbol to be shown on the keyboard.
|
||||||
|
- `flags='<flags>'` is a collection of flags that change the behavior of the key.
|
||||||
|
`<flags>` 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. `<payload>` is a string wrapped
|
||||||
|
in single-quotes (`'`), escaping of other single quotes is allowed with `\'`.
|
||||||
|
|
||||||
|
For example: `:str symbol='Sym':'Output string'`
|
||||||
|
@ -210,8 +210,7 @@ public final class Config
|
|||||||
KeyValue action_key()
|
KeyValue action_key()
|
||||||
{
|
{
|
||||||
// Update the name to avoid caching in KeyModifier
|
// Update the name to avoid caching in KeyModifier
|
||||||
return (actionLabel == null) ? null :
|
return (actionLabel == null) ? null : KeyValue.makeActionKey(actionLabel);
|
||||||
KeyValue.getKeyByName("action").withSymbol(actionLabel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update the layout according to the configuration.
|
/** Update the layout according to the configuration.
|
||||||
|
@ -97,6 +97,7 @@ public final class KeyEventHandler
|
|||||||
_recv.set_compose_pending(true);
|
_recv.set_compose_pending(true);
|
||||||
break;
|
break;
|
||||||
case Cursor_move: move_cursor(key.getCursorMove()); break;
|
case Cursor_move: move_cursor(key.getCursorMove()); break;
|
||||||
|
case Complex: send_complex_key(key.getComplexKind(), key.getComplex()); break;
|
||||||
}
|
}
|
||||||
update_meta_state(old_mods);
|
update_meta_state(old_mods);
|
||||||
}
|
}
|
||||||
@ -215,6 +216,16 @@ public final class KeyEventHandler
|
|||||||
conn.performContextMenuAction(id);
|
conn.performContextMenuAction(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void send_complex_key(KeyValue.Complex.Kind kind, KeyValue.Complex val)
|
||||||
|
{
|
||||||
|
switch (kind)
|
||||||
|
{
|
||||||
|
case StringWithSymbol:
|
||||||
|
send_text(((KeyValue.Complex.StringWithSymbol)val).str);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
void handle_editing_key(KeyValue.Editing ev)
|
void handle_editing_key(KeyValue.Editing ev)
|
||||||
{
|
{
|
||||||
|
@ -91,7 +91,8 @@ public final class KeyValue implements Comparable<KeyValue>
|
|||||||
{
|
{
|
||||||
Char, String, Keyevent, Event, Compose_pending, Hangul_initial,
|
Char, String, Keyevent, Event, Compose_pending, Hangul_initial,
|
||||||
Hangul_medial, Modifier, Editing, Placeholder,
|
Hangul_medial, Modifier, Editing, Placeholder,
|
||||||
Cursor_move // Value is encoded as a 16-bit integer
|
Cursor_move, // Value is encoded as a 16-bit integer.
|
||||||
|
Complex, // [_payload] is a [KeyValue.Complex], value is [Complex.Kind].
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final int FLAGS_OFFSET = 19;
|
private static final int FLAGS_OFFSET = 19;
|
||||||
@ -129,7 +130,13 @@ public final class KeyValue implements Comparable<KeyValue>
|
|||||||
check((((Kind.values().length - 1) << KIND_OFFSET) & ~KIND_BITS) == 0);
|
check((((Kind.values().length - 1) << KIND_OFFSET) & ~KIND_BITS) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String _symbol;
|
/**
|
||||||
|
* The symbol that is rendered on the keyboard as a [String].
|
||||||
|
* Except for keys of kind:
|
||||||
|
* - [String], this is also the string to output.
|
||||||
|
* - [Complex], this is an instance of [KeyValue.Complex].
|
||||||
|
*/
|
||||||
|
private final Object _payload;
|
||||||
|
|
||||||
/** This field encodes three things: Kind, flags and value. */
|
/** This field encodes three things: Kind, flags and value. */
|
||||||
private final int _code;
|
private final int _code;
|
||||||
@ -153,7 +160,9 @@ public final class KeyValue implements Comparable<KeyValue>
|
|||||||
When [getKind() == Kind.String], also the string to send. */
|
When [getKind() == Kind.String], also the string to send. */
|
||||||
public String getString()
|
public String getString()
|
||||||
{
|
{
|
||||||
return _symbol;
|
if (getKind() == Kind.Complex)
|
||||||
|
return ((Complex)_payload).getSymbol();
|
||||||
|
return (String)_payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Defined only when [getKind() == Kind.Char]. */
|
/** Defined only when [getKind() == Kind.Char]. */
|
||||||
@ -211,25 +220,32 @@ public final class KeyValue implements Comparable<KeyValue>
|
|||||||
return (short)(_code & VALUE_BITS);
|
return (short)(_code & VALUE_BITS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Defined only when [getKind() == Kind.Complex]. */
|
||||||
|
public Complex getComplex()
|
||||||
|
{
|
||||||
|
return (Complex)_payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Defined only when [getKind() == Kind.Complex]. */
|
||||||
|
public Complex.Kind getComplexKind()
|
||||||
|
{
|
||||||
|
return Complex.Kind.values()[(_code & VALUE_BITS)];
|
||||||
|
}
|
||||||
|
|
||||||
/* Update the char and the symbol. */
|
/* Update the char and the symbol. */
|
||||||
public KeyValue withChar(char c)
|
public KeyValue withChar(char c)
|
||||||
{
|
{
|
||||||
return new KeyValue(String.valueOf(c), Kind.Char, c, getFlags());
|
return new KeyValue(String.valueOf(c), Kind.Char, c, getFlags());
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyValue withSymbol(String s)
|
|
||||||
{
|
|
||||||
return new KeyValue(s, (_code & KIND_BITS), (_code & VALUE_BITS), getFlags());
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyValue withKeyevent(int code)
|
public KeyValue withKeyevent(int code)
|
||||||
{
|
{
|
||||||
return new KeyValue(_symbol, Kind.Keyevent, code, getFlags());
|
return new KeyValue(getString(), Kind.Keyevent, code, getFlags());
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyValue withFlags(int f)
|
public KeyValue withFlags(int f)
|
||||||
{
|
{
|
||||||
return new KeyValue(_symbol, (_code & KIND_BITS), (_code & VALUE_BITS), f);
|
return new KeyValue(getString(), (_code & KIND_BITS), (_code & VALUE_BITS), f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -247,7 +263,9 @@ public final class KeyValue implements Comparable<KeyValue>
|
|||||||
d = _code - snd._code;
|
d = _code - snd._code;
|
||||||
if (d != 0)
|
if (d != 0)
|
||||||
return d;
|
return d;
|
||||||
return _symbol.compareTo(snd._symbol);
|
if (getKind() == Kind.Complex)
|
||||||
|
return ((Complex)_payload).compareTo((Complex)snd._payload);
|
||||||
|
return ((String)_payload).compareTo((String)snd._payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Type-safe alternative to [equals]. */
|
/** Type-safe alternative to [equals]. */
|
||||||
@ -255,24 +273,36 @@ public final class KeyValue implements Comparable<KeyValue>
|
|||||||
{
|
{
|
||||||
if (snd == null)
|
if (snd == null)
|
||||||
return false;
|
return false;
|
||||||
return _symbol.equals(snd._symbol) && _code == snd._code;
|
return _code == snd._code && _payload.equals(snd._payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode()
|
public int hashCode()
|
||||||
{
|
{
|
||||||
return _symbol.hashCode() + _code;
|
return _payload.hashCode() + _code;
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyValue(String s, int kind, int value, int flags)
|
public String toString()
|
||||||
{
|
{
|
||||||
_symbol = s;
|
int value = _code & VALUE_BITS;
|
||||||
|
return "[KeyValue " + getKind().toString() + "+" + getFlags() + "+" + value + " \"" + getString() + "\"]";
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyValue(Object p, int kind, int value, int flags)
|
||||||
|
{
|
||||||
|
_payload = p;
|
||||||
_code = (kind & KIND_BITS) | (flags & FLAGS_BITS) | (value & VALUE_BITS);
|
_code = (kind & KIND_BITS) | (flags & FLAGS_BITS) | (value & VALUE_BITS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyValue(String s, Kind k, int v, int f)
|
public KeyValue(Complex p, Complex.Kind value, int flags)
|
||||||
{
|
{
|
||||||
this(s, (k.ordinal() << KIND_OFFSET), v, f);
|
this((Object)p, (Kind.Complex.ordinal() << KIND_OFFSET), value.ordinal(),
|
||||||
|
flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyValue(String p, Kind k, int v, int f)
|
||||||
|
{
|
||||||
|
this(p, (k.ordinal() << KIND_OFFSET), v, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static KeyValue charKey(String symbol, char c, int flags)
|
private static KeyValue charKey(String symbol, char c, int flags)
|
||||||
@ -397,6 +427,11 @@ public final class KeyValue implements Comparable<KeyValue>
|
|||||||
return KeyValue.makeCharKey((char)precomposed);
|
return KeyValue.makeCharKey((char)precomposed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static KeyValue makeActionKey(String symbol)
|
||||||
|
{
|
||||||
|
return eventKey(symbol, Event.ACTION, FLAG_SMALLER_FONT);
|
||||||
|
}
|
||||||
|
|
||||||
/** Make a key that types a string. A char key is returned for a string of
|
/** Make a key that types a string. A char key is returned for a string of
|
||||||
length 1. */
|
length 1. */
|
||||||
public static KeyValue makeStringKey(String str, int flags)
|
public static KeyValue makeStringKey(String str, int flags)
|
||||||
@ -407,12 +442,36 @@ public final class KeyValue implements Comparable<KeyValue>
|
|||||||
return new KeyValue(str, Kind.String, 0, flags | FLAG_SMALLER_FONT);
|
return new KeyValue(str, Kind.String, 0, flags | FLAG_SMALLER_FONT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static KeyValue makeStringKeyWithSymbol(String str, String symbol, int flags)
|
||||||
|
{
|
||||||
|
return new KeyValue(new Complex.StringWithSymbol(str, symbol),
|
||||||
|
Complex.Kind.StringWithSymbol, flags);
|
||||||
|
}
|
||||||
|
|
||||||
/** Make a modifier key for passing to [KeyModifier]. */
|
/** Make a modifier key for passing to [KeyModifier]. */
|
||||||
public static KeyValue makeInternalModifier(Modifier mod)
|
public static KeyValue makeInternalModifier(Modifier mod)
|
||||||
{
|
{
|
||||||
return new KeyValue("", Kind.Modifier, mod.ordinal(), 0);
|
return new KeyValue("", Kind.Modifier, mod.ordinal(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static KeyValue parseKeyDefinition(String str)
|
||||||
|
{
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
switch (name)
|
switch (name)
|
||||||
@ -599,8 +658,8 @@ public final class KeyValue implements Comparable<KeyValue>
|
|||||||
case "ㅍ": return makeHangulInitial("ㅍ", 17);
|
case "ㅍ": return makeHangulInitial("ㅍ", 17);
|
||||||
case "ㅎ": return makeHangulInitial("ㅎ", 18);
|
case "ㅎ": return makeHangulInitial("ㅎ", 18);
|
||||||
|
|
||||||
/* Fallback to a string key that types its name */
|
/* The key is not one of the special ones. */
|
||||||
default: return makeStringKey(name);
|
default: return parseKeyDefinition(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -610,4 +669,49 @@ public final class KeyValue implements Comparable<KeyValue>
|
|||||||
if (!b)
|
if (!b)
|
||||||
throw new RuntimeException("Assertion failure");
|
throw new RuntimeException("Assertion failure");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static abstract class Complex
|
||||||
|
{
|
||||||
|
public abstract String getSymbol();
|
||||||
|
|
||||||
|
/** [compareTo] can assume that [snd] is an instance of the same class. */
|
||||||
|
public abstract int compareTo(Complex snd);
|
||||||
|
|
||||||
|
public boolean equals(Object snd)
|
||||||
|
{
|
||||||
|
if (snd instanceof Complex)
|
||||||
|
return compareTo((Complex)snd) == 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** [hashCode] will be called on this class. */
|
||||||
|
|
||||||
|
/** The kind is stored in the [value] field of the key. */
|
||||||
|
public static enum Kind
|
||||||
|
{
|
||||||
|
StringWithSymbol,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class StringWithSymbol extends Complex
|
||||||
|
{
|
||||||
|
public final String str;
|
||||||
|
private final String _symbol;
|
||||||
|
|
||||||
|
public StringWithSymbol(String _str, String _sym)
|
||||||
|
{
|
||||||
|
str = _str;
|
||||||
|
_symbol = _sym;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSymbol() { return _symbol; }
|
||||||
|
|
||||||
|
public int compareTo(Complex _snd)
|
||||||
|
{
|
||||||
|
StringWithSymbol snd = (StringWithSymbol)_snd;
|
||||||
|
int d = str.compareTo(snd.str);
|
||||||
|
if (d != 0) return d;
|
||||||
|
return _symbol.compareTo(snd._symbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
150
srcs/juloo.keyboard2/KeyValueParser.java
Normal file
150
srcs/juloo.keyboard2/KeyValueParser.java
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
package juloo.keyboard2;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Parse a key definition. The syntax for a key definition is:
|
||||||
|
- [:(kind) (attributes):(payload)].
|
||||||
|
- If [str] doesn't start with a [:] character, it is interpreted as an
|
||||||
|
arbitrary string key.
|
||||||
|
|
||||||
|
[(kind)] specifies the kind of the key, it can be:
|
||||||
|
- [str]: An arbitrary string key. The payload is the string to output when
|
||||||
|
typed and is quoted by single quotes ([']). The payload can contain single
|
||||||
|
quotes if they are escaped with a backslash ([\']).
|
||||||
|
|
||||||
|
The [(attributes)] part is a space-separated list of attributes, all optional,
|
||||||
|
of the form: [attrname='attrvalue'].
|
||||||
|
|
||||||
|
Attributes can be:
|
||||||
|
- [flags]: Add flags that change the behavior of the key.
|
||||||
|
Value is a coma separated list of:
|
||||||
|
- [dim]: Make the symbol dimmer on the keyboard.
|
||||||
|
- [small]: Make the symbol smaller on the keyboard.
|
||||||
|
- [symbol]: Specify the symbol that is rendered on the keyboard.
|
||||||
|
It can contain single quotes if they are escaped: ([\']).
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- [:str flags=dim,small symbol='MyKey':'My arbitrary string'].
|
||||||
|
- [:str:'My arbitrary string'].
|
||||||
|
|
||||||
|
*/
|
||||||
|
public final class KeyValueParser
|
||||||
|
{
|
||||||
|
static Pattern START_PAT;
|
||||||
|
static Pattern ATTR_PAT;
|
||||||
|
static Pattern QUOTED_PAT;
|
||||||
|
static Pattern PAYLOAD_START_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);
|
||||||
|
switch (kind)
|
||||||
|
{
|
||||||
|
case "str":
|
||||||
|
String payload = parseSingleQuotedString(m);
|
||||||
|
if (symbol == null)
|
||||||
|
return KeyValue.makeStringKey(payload, flags);
|
||||||
|
return KeyValue.makeStringKeyWithSymbol(payload, symbol, 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 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*:");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parseError(String msg, Matcher m) throws ParseError
|
||||||
|
{
|
||||||
|
parseError(msg, m, m.regionStart());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parseError(String msg, Matcher m, int i) throws ParseError
|
||||||
|
{
|
||||||
|
StringBuilder msg_ = new StringBuilder("Syntax error");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
char c = m.group(0).charAt(0);
|
||||||
|
msg_.append(" at character '").append(c).append("'");
|
||||||
|
} catch (IllegalStateException _e) {}
|
||||||
|
msg_.append(" at position ");
|
||||||
|
msg_.append(i);
|
||||||
|
msg_.append(": ");
|
||||||
|
msg_.append(msg);
|
||||||
|
throw new ParseError(msg_.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ParseError extends Exception
|
||||||
|
{
|
||||||
|
public ParseError(String msg) { super(msg); }
|
||||||
|
};
|
||||||
|
}
|
@ -40,7 +40,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.makeStringKey(key_name), KeyboardData.PreferredPos.DEFAULT);
|
kvs.put(KeyValue.parseKeyDefinition(key_name), KeyboardData.PreferredPos.DEFAULT);
|
||||||
}
|
}
|
||||||
return kvs;
|
return kvs;
|
||||||
}
|
}
|
||||||
|
54
test/juloo.keyboard2/KeyValueParserTest.java
Normal file
54
test/juloo.keyboard2/KeyValueParserTest.java
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package juloo.keyboard2;
|
||||||
|
|
||||||
|
import juloo.keyboard2.KeyValue;
|
||||||
|
import juloo.keyboard2.KeyValueParser;
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class KeyValueParserTest
|
||||||
|
{
|
||||||
|
public KeyValueParserTest() {}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parse() throws Exception
|
||||||
|
{
|
||||||
|
Utils.parse(":str:'Foo'", KeyValue.makeStringKey("Foo"));
|
||||||
|
Utils.parse(":str flags='dim':'Foo'", KeyValue.makeStringKey("Foo", KeyValue.FLAG_SECONDARY));
|
||||||
|
Utils.parse(":str symbol='Symbol':'Foo'", KeyValue.makeStringKeyWithSymbol("Foo", "Symbol", 0));
|
||||||
|
Utils.parse(":str symbol='Symbol' flags='dim':'Foo'", KeyValue.makeStringKeyWithSymbol("Foo", "Symbol", KeyValue.FLAG_SECONDARY));
|
||||||
|
Utils.parse(":str flags='dim,small':'Foo'", KeyValue.makeStringKey("Foo", KeyValue.FLAG_SECONDARY | KeyValue.FLAG_SMALLER_FONT));
|
||||||
|
Utils.parse(":str flags=',,':'Foo'", KeyValue.makeStringKey("Foo")); // Unintentional
|
||||||
|
Utils.expect_error(":unknown:Foo"); // Unknown kind
|
||||||
|
Utils.expect_error(":str:Foo"); // Unquoted string
|
||||||
|
Utils.expect_error(":str flags:'Foo'"); // Malformed flags
|
||||||
|
Utils.expect_error(":str flags=dim:'Foo'"); // Unquoted flags
|
||||||
|
Utils.expect_error(":str unknown='foo':'Foo'"); // Unknown flags
|
||||||
|
// Unterminated
|
||||||
|
Utils.expect_error(":str");
|
||||||
|
Utils.expect_error(":str ");
|
||||||
|
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='':'");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** JUnit removes these functions from stacktraces. */
|
||||||
|
static class Utils
|
||||||
|
{
|
||||||
|
static void parse(String key_descr, KeyValue ref) throws Exception
|
||||||
|
{
|
||||||
|
assertEquals(ref, KeyValueParser.parse(key_descr));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void expect_error(String key_descr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fail("Expected failure but got " + KeyValueParser.parse(key_descr));
|
||||||
|
}
|
||||||
|
catch (KeyValueParser.ParseError e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
test/juloo.keyboard2/KeyValueTest.java
Normal file
16
test/juloo.keyboard2/KeyValueTest.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package juloo.keyboard2;
|
||||||
|
|
||||||
|
import juloo.keyboard2.KeyValue;
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class KeyValueTest
|
||||||
|
{
|
||||||
|
public KeyValueTest() {}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void equals()
|
||||||
|
{
|
||||||
|
assertEquals(KeyValue.makeStringKeyWithSymbol("Foo", "Symbol", 0), KeyValue.makeStringKeyWithSymbol("Foo", "Symbol", 0));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user