add keymap multi-chord sequences and a stateful matcher
T-205, the resolver foundation for Vim motions (dd, gg, dw, ciw) and repeat counts (5j). KeymapBinding now holds an ordered chord sequence (length 1 for the common single-chord case); `keys:` parses a space- separated spec into that sequence (D-82). Keymap.resolve keeps the single-chord fast path; a new stateless Keymap.match answers exact/prefix/none for a pending buffer. SequenceMatcher wraps that query with a pending buffer, a repeat-count prefix (leading digits, 0 excluded since it's the line-start motion), the d-vs-dd timeout case (flush fires the buffered exact), and broken- sequence recovery (discard, restart on the last chord). It is headless — no keyboard reads, no event swallowing — so the editor (T-206) can drive it from Focus.onKeyEvent and act on the result. Also drops a stray unused import in the Vim indicator test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -83,6 +83,27 @@ class KeyChord {
|
||||
return KeyChord(modifiers: mods, key: key);
|
||||
}
|
||||
|
||||
/// Parse a sequence spec — one or more chords separated by whitespace,
|
||||
/// e.g. `d d`, `g g`, `ctrl+k ctrl+s`. A single chord yields a
|
||||
/// one-element list. Whitespace means "then" (D-82); the space *key*
|
||||
/// is always spelled `space`, so a literal space never collides.
|
||||
/// Throws [FormatException] on empty input or an unknown chord.
|
||||
static List<KeyChord> parseSequence(String spec) {
|
||||
final parts = spec.trim().split(RegExp(r'\s+')).where((s) => s.isNotEmpty).toList();
|
||||
if (parts.isEmpty) throw FormatException('empty key sequence: "$spec"');
|
||||
return [for (final p in parts) KeyChord.parse(p)];
|
||||
}
|
||||
|
||||
/// The digit 0–9 if this is a bare digit key with no modifiers, else
|
||||
/// null. Used to capture Vim repeat-count prefixes (`5j`).
|
||||
int? get digit {
|
||||
if (modifiers.isNotEmpty) return null;
|
||||
for (var d = 0; d <= 9; d++) {
|
||||
if (key == _digitKeys[d]) return d;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Canonical YAML form: `ctrl+shift+p`.
|
||||
String get canonical {
|
||||
final modPart = modifiers.map((m) => m.yaml).join('+');
|
||||
@@ -201,6 +222,19 @@ const Map<String, LogicalKeyboardKey> _byName = {
|
||||
'backquote': LogicalKeyboardKey.backquote, '`': LogicalKeyboardKey.backquote,
|
||||
};
|
||||
|
||||
const List<LogicalKeyboardKey> _digitKeys = [
|
||||
LogicalKeyboardKey.digit0,
|
||||
LogicalKeyboardKey.digit1,
|
||||
LogicalKeyboardKey.digit2,
|
||||
LogicalKeyboardKey.digit3,
|
||||
LogicalKeyboardKey.digit4,
|
||||
LogicalKeyboardKey.digit5,
|
||||
LogicalKeyboardKey.digit6,
|
||||
LogicalKeyboardKey.digit7,
|
||||
LogicalKeyboardKey.digit8,
|
||||
LogicalKeyboardKey.digit9,
|
||||
];
|
||||
|
||||
LogicalKeyboardKey? _keyByName(String name) => _byName[name.toLowerCase()];
|
||||
|
||||
String _keyName(LogicalKeyboardKey key) {
|
||||
|
||||
Reference in New Issue
Block a user