fix(vim): pass tab focus keys to pages
- commit
- 04133a4fda49219376fe99985268c00c5efc6a20
- author
- brice · · 2026-08-14 12:09 UTC
- parent
- 4000758
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +38 -2
M internal/ui/input/keyboard.go +6 -2
| @@ -613,12 +613,16 @@ func normalizeKeyval(keyval uint) uint { | |||
| 613 | 613 | } | |
| 614 | 614 | ||
| 615 | 615 | func shouldPassthroughNativeVimModeNavigation(mode Mode, keyval uint, modifiers Modifier) bool { | |
| 616 | - | if mode != ModeVim || modifiers != ModNone { | |
| 616 | + | if mode != ModeVim { | |
| 617 | 617 | return false | |
| 618 | 618 | } | |
| 619 | 619 | switch keyval { | |
| 620 | 620 | case uint(gdk.KEY_Left), uint(gdk.KEY_Right), uint(gdk.KEY_Up), uint(gdk.KEY_Down): | |
| 621 | - | return true | |
| 621 | + | return modifiers == ModNone | |
| 622 | + | case uint(gdk.KEY_Tab): | |
| 623 | + | return modifiers == ModNone || modifiers == ModShift | |
| 624 | + | case uint(gdk.KEY_ISO_Left_Tab): | |
| 625 | + | return modifiers == ModNone || modifiers == ModShift | |
| 622 | 626 | default: | |
| 623 | 627 | return false | |
| 624 | 628 | } | |
| @@ -613,12 +613,16 @@ func normalizeKeyval(keyval uint) uint { | |||||
| 613 | } | 613 | } | ||
| 614 | 614 | ||||
| 615 | func shouldPassthroughNativeVimModeNavigation(mode Mode, keyval uint, modifiers Modifier) bool { | 615 | func shouldPassthroughNativeVimModeNavigation(mode Mode, keyval uint, modifiers Modifier) bool { | ||
| 616 | - | if mode != ModeVim || modifiers != ModNone { | 616 | + | if mode != ModeVim { |
| 617 | return false | 617 | return false | ||
| 618 | } | 618 | } | ||
| 619 | switch keyval { | 619 | switch keyval { | ||
| 620 | case uint(gdk.KEY_Left), uint(gdk.KEY_Right), uint(gdk.KEY_Up), uint(gdk.KEY_Down): | 620 | case uint(gdk.KEY_Left), uint(gdk.KEY_Right), uint(gdk.KEY_Up), uint(gdk.KEY_Down): | ||
| 621 | - | return true | 621 | + | return modifiers == ModNone |
| 622 | + | case uint(gdk.KEY_Tab): | |||
| 623 | + | return modifiers == ModNone || modifiers == ModShift | |||
| 624 | + | case uint(gdk.KEY_ISO_Left_Tab): | |||
| 625 | + | return modifiers == ModNone || modifiers == ModShift | |||
| 622 | default: | 626 | default: | ||
| 623 | return false | 627 | return false | ||
| 624 | } | 628 | } | ||
M internal/ui/input/keyboard_test.go +32 -0
| @@ -884,6 +884,38 @@ func TestHandleKeyPress_VimModeArrowKeysPassThroughNatively(t *testing.T) { | |||
| 884 | 884 | } | |
| 885 | 885 | } | |
| 886 | 886 | ||
| 887 | + | func TestHandleKeyPress_VimModeTabKeysPassThroughNatively(t *testing.T) { | |
| 888 | + | ctx := context.Background() | |
| 889 | + | workspace := newTestWorkspace() | |
| 890 | + | workspace.VimMode = entity.VimModeConfig{ActivationShortcut: "ctrl+y"} | |
| 891 | + | ||
| 892 | + | h := NewKeyboardHandler(ctx, workspace, newTestSession()) | |
| 893 | + | h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | |
| 894 | + | if h.Mode() != ModeVim { | |
| 895 | + | t.Fatal("failed to enter vim mode") | |
| 896 | + | } | |
| 897 | + | ||
| 898 | + | for _, test := range []struct { | |
| 899 | + | name string | |
| 900 | + | keyval uint | |
| 901 | + | modifiers gdk.ModifierType | |
| 902 | + | }{ | |
| 903 | + | {name: "tab", keyval: uint(gdk.KEY_Tab)}, | |
| 904 | + | {name: "shift tab", keyval: uint(gdk.KEY_Tab), modifiers: gdk.ShiftMaskValue}, | |
| 905 | + | {name: "iso left tab", keyval: uint(gdk.KEY_ISO_Left_Tab), modifiers: gdk.ShiftMaskValue}, | |
| 906 | + | } { | |
| 907 | + | t.Run(test.name, func(t *testing.T) { | |
| 908 | + | consumed := h.handleKeyPress(test.keyval, 0, test.modifiers) | |
| 909 | + | if consumed { | |
| 910 | + | t.Fatal("tab key should pass through to native page focus navigation in vim mode") | |
| 911 | + | } | |
| 912 | + | if h.Mode() != ModeVim { | |
| 913 | + | t.Fatal("tab key should keep vim mode active") | |
| 914 | + | } | |
| 915 | + | }) | |
| 916 | + | } | |
| 917 | + | } | |
| 918 | + | ||
| 887 | 919 | func TestHandleKeyPress_VimModeBlocksGlobalShortcutFallback(t *testing.T) { | |
| 888 | 920 | ctx := context.Background() | |
| 889 | 921 | workspace := newTestWorkspace() | |
| @@ -884,6 +884,38 @@ func TestHandleKeyPress_VimModeArrowKeysPassThroughNatively(t *testing.T) { | |||||
| 884 | } | 884 | } | ||
| 885 | } | 885 | } | ||
| 886 | 886 | ||||
| 887 | + | func TestHandleKeyPress_VimModeTabKeysPassThroughNatively(t *testing.T) { | |||
| 888 | + | ctx := context.Background() | |||
| 889 | + | workspace := newTestWorkspace() | |||
| 890 | + | workspace.VimMode = entity.VimModeConfig{ActivationShortcut: "ctrl+y"} | |||
| 891 | + | ||||
| 892 | + | h := NewKeyboardHandler(ctx, workspace, newTestSession()) | |||
| 893 | + | h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | |||
| 894 | + | if h.Mode() != ModeVim { | |||
| 895 | + | t.Fatal("failed to enter vim mode") | |||
| 896 | + | } | |||
| 897 | + | ||||
| 898 | + | for _, test := range []struct { | |||
| 899 | + | name string | |||
| 900 | + | keyval uint | |||
| 901 | + | modifiers gdk.ModifierType | |||
| 902 | + | }{ | |||
| 903 | + | {name: "tab", keyval: uint(gdk.KEY_Tab)}, | |||
| 904 | + | {name: "shift tab", keyval: uint(gdk.KEY_Tab), modifiers: gdk.ShiftMaskValue}, | |||
| 905 | + | {name: "iso left tab", keyval: uint(gdk.KEY_ISO_Left_Tab), modifiers: gdk.ShiftMaskValue}, | |||
| 906 | + | } { | |||
| 907 | + | t.Run(test.name, func(t *testing.T) { | |||
| 908 | + | consumed := h.handleKeyPress(test.keyval, 0, test.modifiers) | |||
| 909 | + | if consumed { | |||
| 910 | + | t.Fatal("tab key should pass through to native page focus navigation in vim mode") | |||
| 911 | + | } | |||
| 912 | + | if h.Mode() != ModeVim { | |||
| 913 | + | t.Fatal("tab key should keep vim mode active") | |||
| 914 | + | } | |||
| 915 | + | }) | |||
| 916 | + | } | |||
| 917 | + | } | |||
| 918 | + | ||||
| 887 | func TestHandleKeyPress_VimModeBlocksGlobalShortcutFallback(t *testing.T) { | 919 | func TestHandleKeyPress_VimModeBlocksGlobalShortcutFallback(t *testing.T) { | ||
| 888 | ctx := context.Background() | 920 | ctx := context.Background() | ||
| 889 | workspace := newTestWorkspace() | 921 | workspace := newTestWorkspace() | ||