feat(vim): gt / gT cycle workspace tabs (T-405 part 2)
Closes out the T-403 cross-pane vim layer. gt/gT bind to the existing workspace.tab.next/previous commands (also on ctrl+pagedown/up for every preset), resolved by the focused editor's matcher or a pane's PaneKeyNav — bare-g sequences stay editor/pane-local (T-404's global matcher only engages on modified-chord prefixes), so no global-matcher surgery. - vim.yaml: g t -> command:workspace.tab.next, g shift+t -> .previous (vim.normal); shares the `g` prefix with `g g` (docStart / nav.top), distinguished by the final chord. - PaneKeyNav now EXECUTES non-editor.vim.* command intents (e.g. workspace.tab.*) instead of swallowing all command intents, so gt/gT work from a focused pane; editor.vim.* buffer edits stay blocked in panes. Tests: vim-preset resolution (gt/gT bind, gg intact), PaneKeyNav executes the command from a pane, and editor.vim.* is never run from a pane. make test green; analyze + format clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -79,6 +79,27 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('vim preset tab motions (T-405)', () {
|
||||
bool seqEq(List<KeyChord> seq, List<String> chords) {
|
||||
if (seq.length != chords.length) return false;
|
||||
for (var i = 0; i < chords.length; i++) {
|
||||
if (seq[i] != KeyChord.parse(chords[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool hasSeq(KeymapService svc, List<String> chords, String cmd) =>
|
||||
svc.keymap!.effectiveBindings.any((b) => seqEq(b.sequence, chords) && isCommand(b.intent, cmd));
|
||||
|
||||
test('gt / gT bind workspace tab cycling, sharing the g prefix with gg', () async {
|
||||
final svc = await activate('vim');
|
||||
expect(hasSeq(svc, ['g', 't'], 'workspace.tab.next'), isTrue, reason: 'gt → workspace.tab.next');
|
||||
expect(hasSeq(svc, ['g', 'shift+t'], 'workspace.tab.previous'), isTrue, reason: 'gT → workspace.tab.previous');
|
||||
// The shared `g` prefix must not break gg (docStart) — different final.
|
||||
expect(hasSeq(svc, ['g', 'g'], 'editor.vim.docStart'), isTrue, reason: 'gg → docStart intact');
|
||||
});
|
||||
});
|
||||
|
||||
group('jetbrains preset (T-66)', () {
|
||||
test('setPreset activates it and the representative subset resolves', () async {
|
||||
final svc = await activate('jetbrains');
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
/// end-to-end against the real vim preset and scope flags.
|
||||
library;
|
||||
|
||||
import 'package:clide/clide.dart';
|
||||
import 'package:clide/extension/src/contribution.dart';
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@@ -67,6 +69,47 @@ void main() {
|
||||
expect(got, isEmpty);
|
||||
});
|
||||
|
||||
testWidgets('gt executes a non-editor command (workspace.tab.next) from a focused pane (T-405)', (tester) async {
|
||||
var ran = 0;
|
||||
f.services.commands.register(
|
||||
CommandContribution(
|
||||
id: 'workspace.tab.next',
|
||||
command: 'workspace.tab.next',
|
||||
title: 'Next Workspace Tab',
|
||||
run: (_) async {
|
||||
ran++;
|
||||
return IpcResponse.ok(id: '', data: const {});
|
||||
},
|
||||
),
|
||||
);
|
||||
await pump(tester, scope: {'vim.normal': true});
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.keyG);
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.keyT);
|
||||
await tester.pump();
|
||||
expect(ran, 1);
|
||||
});
|
||||
|
||||
testWidgets('editor.vim.* buffer edits never execute from a pane (T-405 guard)', (tester) async {
|
||||
var ran = 0;
|
||||
// If the pane wrongly ran editor.vim.*, this would fire on `d d`.
|
||||
f.services.commands.register(
|
||||
CommandContribution(
|
||||
id: 'editor.vim.deleteLine',
|
||||
command: 'editor.vim.deleteLine',
|
||||
title: 'vim: delete line',
|
||||
run: (_) async {
|
||||
ran++;
|
||||
return IpcResponse.ok(id: '', data: const {});
|
||||
},
|
||||
),
|
||||
);
|
||||
await pump(tester, scope: {'vim.normal': true});
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.keyD);
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.keyD);
|
||||
await tester.pump();
|
||||
expect(ran, 0);
|
||||
});
|
||||
|
||||
testWidgets('keys pass through outside vim normal mode', (tester) async {
|
||||
final got = await pump(tester, scope: {'vim.insert': true});
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.keyJ);
|
||||
|
||||
Reference in New Issue
Block a user