vim ctrl+w window-command family + global multi-chord matcher (T-404)
The structural piece: a global SequenceMatcher in root_shell, at the HardwareKeyboard level so a focused editor/pane can't swallow the second chord. It only STARTS on a modified chord that prefixes a sequence (ctrl+w), so bare-key sequences (gg, dd) stay editor/pane-local and single-chord presets are untouched; bare ctrl+w still fires editor.close after the D-82 timeout. vim.yaml binds the window family under vim.normal||vim.visual: ctrl+w h/l → panel.focus.left/right, j → dock.toggle, w / ctrl+w → focus.nextPanel, shift+w → focus.previousPanel, o → panel.focusMode, q/c → editor.close. Tests: ctrl+w sequence resolution at the keymap layer, plus app-level integration (ctrl+w o toggles focus mode; bare ctrl+w closes the editor after the timeout; a bare g is not grabbed globally). This is the global matcher T-405 part 2 (gt/gT) was waiting on — though bare-g sequences need more thought (g is editor-local), noted for that follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -236,6 +236,58 @@ void main() {
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets('ctrl+w o fires a window command via the global matcher, not editor.close (T-404)', (tester) async {
|
||||
await tester.runAsync(() => f.services.keymap.setPreset('vim'));
|
||||
f.services.keymap.setScopeFlag('vim.normal', true);
|
||||
addTearDown(() => f.services.keymap.clearScopeFlag('vim.normal'));
|
||||
await pumpApp(tester);
|
||||
expect(f.services.arrangement.isInFocusMode, isFalse);
|
||||
|
||||
// ctrl+w (chord) then a BARE o → panel.focusMode. The second chord is
|
||||
// consumed at the hardware level, so a focused pane can't swallow it.
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.keyW);
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.keyO);
|
||||
await tester.pump();
|
||||
|
||||
expect(f.services.arrangement.isInFocusMode, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('bare ctrl+w closes the editor after the ambiguity timeout (T-404)', (tester) async {
|
||||
await tester.runAsync(() => f.services.keymap.setPreset('vim'));
|
||||
f.services.keymap.setScopeFlag('vim.normal', true);
|
||||
addTearDown(() => f.services.keymap.clearScopeFlag('vim.normal'));
|
||||
await pumpApp(tester);
|
||||
f.services.arrangement.openEditor();
|
||||
expect(f.services.arrangement.editorOpen, isTrue);
|
||||
|
||||
// ctrl+w with no completing chord: pends, then the timeout flushes the
|
||||
// exact bare-ctrl+w binding (editor.close from the contributions layer).
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.keyW);
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
|
||||
await tester.pump(const Duration(milliseconds: 450));
|
||||
|
||||
expect(f.services.arrangement.editorOpen, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('a bare-key sequence prefix (g) is not grabbed by the global matcher (T-404)', (tester) async {
|
||||
await tester.runAsync(() => f.services.keymap.setPreset('vim'));
|
||||
f.services.keymap.setScopeFlag('vim.normal', true);
|
||||
addTearDown(() => f.services.keymap.clearScopeFlag('vim.normal'));
|
||||
await pumpApp(tester);
|
||||
f.services.arrangement.openEditor();
|
||||
|
||||
// `g` is a prefix (gg) but bare → editor/pane-local. The global matcher must
|
||||
// NOT consume it or fire a window command; the editor stays open.
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.keyG);
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.keyG);
|
||||
await tester.pump();
|
||||
expect(f.services.arrangement.isInFocusMode, isFalse);
|
||||
expect(f.services.arrangement.editorOpen, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('window control buttons render and tap as no-ops in tests', (tester) async {
|
||||
await pumpApp(tester);
|
||||
// _RightHatContent renders ClideTappable window buttons on non-macOS;
|
||||
|
||||
Reference in New Issue
Block a user