Files
morns-keyboard-basic/srcs/juloo.keyboard2/ComposeKey.java
T
nyra 08c895aa12 Add live usage gauge key (GPT gradient + DeepSeek spend) beside L
- UsageGauge: WireGuard-only JSON fetch, cached, background thread, stale-aware
- Gauge key: gradient fill rises with the tightest Codex window, %.1f of today's
  DeepSeek spend, red below a $1.00 balance, flat grey when stale/offline
- Display-only: marker lives in the key indication field, no character, touch swallowed
- Settings entries; cleartext permitted only for 10.10.10.1
- ComposeKey: composing to U+00A0 keeps the named nbsp key, since adding a layout
  key perturbs the generated compose table and would otherwise emit the display glyph
- 5 new unit tests; all 21 tests pass
2026-09-27 17:38:18 +00:00

115 lines
3.9 KiB
Java

package juloo.keyboard2;
import java.util.Arrays;
public final class ComposeKey
{
/** Apply the pending compose sequence to [kv]. Returns [null] if no sequence
matched. */
public static KeyValue apply(int state, KeyValue kv)
{
switch (kv.getKind())
{
case Char: return apply(state, kv.getChar());
case String: return apply(state, kv.getString());
case Editing:
switch (kv.getEditing())
{
case SPACE_BAR: return apply(state, ' ');
}
break;
}
return null;
}
/** Apply a char to the pending compose sequence. Returns [null] if no
sequence matched. */
public static KeyValue apply(int state, char c)
{
char[] states = ComposeKeyData.states;
char[] edges = ComposeKeyData.edges;
int state_length = edges[state];
int next = Arrays.binarySearch(states, state + 1, state + state_length, c);
if (next < 0)
return null;
next = edges[next];
int next_header = states[next];
if (next_header == 0) // Enter a new intermediate state.
return KeyValue.makeComposePending(String.valueOf(c), next, 0);
else if (next_header == 0xFFFF) // String final state
{
int next_length = edges[next];
return KeyValue.getKeyByName(
new String(states, next + 1, next_length - 1));
}
else // Character final state.
{
// Preserve the named-key identity used by compose sequences for NBSP.
if (next_header == '\u00a0')
return KeyValue.getKeyByName("nbsp");
return KeyValue.makeCharKey((char)next_header);
}
}
/** Apply char [c] to the pending compose sequence. If the application resolves
to a final char state, return it, otherwise return [0]. This is faster
than [apply] but do not support sequences of more than 1 char. Used to
apply substitutions for word suggestions. */
public static char transform_char(int state, char c)
{
char[] states = ComposeKeyData.states;
char[] edges = ComposeKeyData.edges;
int state_length = edges[state];
int next = Arrays.binarySearch(states, state + 1, state + state_length, c);
if (next < 0)
return 0;
next = edges[next];
int next_header = states[next];
if (next_header == 0 || next_header == 0xFFFF)
return 0;
return (char)next_header;
}
/** Apply each char of a string to a sequence. Returns [null] if no sequence
matched. */
public static KeyValue apply(int prev, String s)
{
final int len = s.length();
int i = 0;
if (len == 0) return null;
while (true)
{
KeyValue k = apply(prev, s.charAt(i));
i++;
if (k == null) return null;
if (i >= len) return k;
if (k.getKind() != KeyValue.Kind.Compose_pending)
return null; // Found a final state before the end of [s].
prev = k.getPendingCompose();
}
}
/** The state machine is comprised of two arrays.
The [states] array represents the different states and the associated
transitions:
- The first cell is the header cell, [states[s]].
- If the header is equal to [0],
The remaining cells are the transitions characters, sorted
alphabetically.
- If the header is positive,
This is a final state, [states[s]] is the result of the sequence.
In this case, [edges[s]] must be equal to [1].
- If the header is equal to [-1],
This is a final state, the remaining cells represent the result string
which starts at index [s + 1] and has a length of [edges[s] - 1].
The [edges] array represents the transition state corresponding to each
accepted inputs.
- If [states[s]] is a header cell, [edges[s]] is the number of cells
occupied by the state [s], including the header cell.
- If [states[s]] is a transition, [edges[s]] is the index of the state to
jump into.
- If [states[s]] is a part of a final state, [edges[s]] is not used. */
}