Creating Input Methods
Input Method Structure
An input method in Keywrite is a JavaScript object that defines mappings from keystrokes to characters. Understanding this structure is essential for creating your own input methods.
Basic Structure
const inputMethod = {
[key]: {
value: string | null,
next: InputMethod | null,
},
};
Components
Every input method consists of SymbolMaps - objects with two required properties:
value: The character to output (string) ornullif this is an intermediate statenext: A nested input method object for multi-keystroke sequences, ornullif this is a terminal state
How Keywrite Processes Input
When you type a key:
- Keywrite looks up the key in the current input method object
- If found, it outputs the
value(if notnull) - If
nextexists, Keywrite moves to that nested input method for the next keystroke - If
nextisnull, the composition is complete and Keywrite resets to the root level
Single-Keystroke Mappings
The simplest input method maps one key to one character:
const simpleMapping = {
a: { value: 'α', next: null },
b: { value: 'β', next: null },
g: { value: 'γ', next: null },
};
Behavior:
- Press
a→ outputsα, composition complete - Press
b→ outputsβ, composition complete - Press
g→ outputsγ, composition complete
Multi-Keystroke Sequences
For multi-keystroke input, use nested structures:
const multiKeyMapping = {
a: {
value: '∀',
next: null,
},
e: {
value: '∈',
next: {
e: {
value: '∉',
next: null,
},
},
},
};
Behavior:
- Press
a→ outputs∀, composition complete - Press
e→ outputs∈, composition continues (next is not null) - Press
eagain → replaces∈with∉, composition complete
Intermediate States with Null Values
You can create intermediate states that don't output characters immediately:
const intermediateState = {
x: {
value: null, // No immediate output
next: {
a: { value: 'χα', next: null },
b: { value: 'χβ', next: null },
},
},
};
Behavior:
- Press
x→ no output, waiting for next key - Press
a→ outputsχα, composition complete - Press
b→ outputsχβ, composition complete
Arbitrary Nesting Depth
Keywrite supports unlimited nesting levels:
const deepNesting = {
c: {
value: '⊂',
next: {
u: {
value: '⊆',
next: {
n: {
value: '⊈',
next: null,
},
},
},
},
},
};
Behavior:
- Press
c→ outputs⊂, composition continues - Press
u→ replaces⊂with⊆, composition continues - Press
n→ replaces⊆with⊈, composition complete
Character Replacement Behavior
When a keystroke produces a new character and next is not null, Keywrite replaces the previous character rather than appending:
const replacement = {
m: {
value: 'ም',
next: {
a: { value: 'ማ', next: null },
i: { value: 'ሚ', next: null },
},
},
};
Behavior:
- Press
m→ outputsም - Press
a→ምis replaced withማ(notምማ) - Press
i→ምis replaced withሚ(notምሚ)
Multiple Entry Points
An input method can have multiple top-level keys:
const multipleRoots = {
a: { value: 'አ', next: null },
b: { value: 'በ', next: null },
c: {
value: 'ች',
next: {
a: { value: 'ቻ', next: null },
},
},
};
Each top-level key starts an independent composition sequence.
Key Naming and Special Characters
Keys can be any string, including special characters:
const specialKeys = {
'<': { value: '‹', next: null },
'>': { value: '›', next: null },
'!': {
value: '¬',
next: {
'=': { value: '≠', next: null },
},
},
};
Example: Mathematical Symbols
Here's a complete example showing various composition patterns:
const mathInputMethod = {
// Single keystroke
a: { value: '∀', next: null },
e: { value: '∃', next: null },
// Two-level nesting
c: {
value: '⊂',
next: {
n: { value: '⊄', next: null },
u: {
value: '⊆',
// Three-level nesting
next: {
n: { value: '⊈', next: null },
},
},
},
},
// Using special characters as keys
'!': {
value: '¬',
next: {
'=': { value: '≠', next: null },
},
},
// Intermediate null value
s: {
value: null,
next: {
s: { value: 'ß', next: null },
e: { value: '∈', next: null },
},
},
};
Usage scenarios:
a→∀(simple)c→⊂→cn→⊄(two steps)c→⊂→cu→⊆→cun→⊈(three steps)!→¬→!=→≠(special characters)s→ (nothing) →ss→ß(null intermediate)
Understanding State Management
Keywrite maintains an internal state machine:
- Root state: Waiting for initial keystroke
- Active state: In the middle of a composition (when
nextexists) - Reset triggers: Composition ends when
nextisnullor an unmapped key is pressed
// State progression example
const stateExample = {
m: {
// Press 'm'
value: 'ም', // → Outputs 'ም', enters Active state
next: {
a: {
// Press 'a'
value: 'ማ', // → Replaces with 'ማ', still Active
next: {
a: {
// Press 'a' again
value: 'ማ', // → Outputs 'ማ'
next: null, // → Returns to Root state
},
},
},
},
},
};
TypeScript Type Definitions
For TypeScript users, here are the exact type definitions:
interface SymbolMap {
value: string | null;
next: InputMethod | null;
}
type InputMethod = Record<string, SymbolMap>;
// Usage
const myInputMethod: InputMethod = {
a: { value: 'α', next: null },
};
What Happens with Unmapped Keys
When a key is pressed that doesn't exist in the current state:
- The unmapped key is processed normally (outputs its default character)
- Keywrite resets to the root state
- The next keystroke starts fresh from the root
const partial = {
m: {
value: 'ም',
next: {
a: { value: 'ማ', next: null },
},
},
};
// Behavior:
// Type: m → outputs 'ም', waiting for next key
// Type: x → 'x' is not mapped, outputs 'x', resets to root
// Type: m → outputs 'ም' again (fresh start)