feat: allow Alt+hjkl pane navigation while omnibox is open
Pass modifier state to the shouldBypass callback so Alt-modified keys skip the bypass and reach the keyboard handler for pane navigation. Hide the omnibox when the active pane changes, mirroring find bar behavior.
- commit
- 8c8c066c2d47c85ef4f8073ccc889951be8c03de
- author
- brice · · 2026-01-29 07:40 UTC
- committer
- brice · · 2026-01-29 08:42 UTC
- parent
- 8849bda
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +14 -5
M internal/ui/app.go +6 -2
| @@ -543,7 +543,7 @@ func (a *App) initKeyboardHandler(ctx context.Context) { | |||
| 543 | 543 | a.keyboardHandler.SetOnModeChange(func(from, to input.Mode) { | |
| 544 | 544 | a.handleModeChange(ctx, from, to) | |
| 545 | 545 | }) | |
| 546 | - | a.keyboardHandler.SetShouldBypassInput(func() bool { | |
| 546 | + | a.keyboardHandler.SetShouldBypassInput(func(modifiers input.Modifier) bool { | |
| 547 | 547 | // Bypass keyboard handler when modals are visible | |
| 548 | 548 | if a.sessionManager != nil && a.sessionManager.IsVisible() { | |
| 549 | 549 | return true | |
| @@ -555,7 +555,11 @@ func (a *App) initKeyboardHandler(ctx context.Context) { | |||
| 555 | 555 | if wsView == nil { | |
| 556 | 556 | return false | |
| 557 | 557 | } | |
| 558 | - | return wsView.IsOmniboxVisible() | |
| 558 | + | if wsView.IsOmniboxVisible() { | |
| 559 | + | // Let Alt-modified keys through for pane navigation | |
| 560 | + | return modifiers&input.ModAlt == 0 | |
| 561 | + | } | |
| 562 | + | return false | |
| 559 | 563 | }) | |
| 560 | 564 | // Wire accent handler for dead keys support | |
| 561 | 565 | if a.insertAccentUC != nil { | |
| @@ -543,7 +543,7 @@ func (a *App) initKeyboardHandler(ctx context.Context) { | |||||
| 543 | a.keyboardHandler.SetOnModeChange(func(from, to input.Mode) { | 543 | a.keyboardHandler.SetOnModeChange(func(from, to input.Mode) { | ||
| 544 | a.handleModeChange(ctx, from, to) | 544 | a.handleModeChange(ctx, from, to) | ||
| 545 | }) | 545 | }) | ||
| 546 | - | a.keyboardHandler.SetShouldBypassInput(func() bool { | 546 | + | a.keyboardHandler.SetShouldBypassInput(func(modifiers input.Modifier) bool { |
| 547 | // Bypass keyboard handler when modals are visible | 547 | // Bypass keyboard handler when modals are visible | ||
| 548 | if a.sessionManager != nil && a.sessionManager.IsVisible() { | 548 | if a.sessionManager != nil && a.sessionManager.IsVisible() { | ||
| 549 | return true | 549 | return true | ||
| @@ -555,7 +555,11 @@ func (a *App) initKeyboardHandler(ctx context.Context) { | |||||
| 555 | if wsView == nil { | 555 | if wsView == nil { | ||
| 556 | return false | 556 | return false | ||
| 557 | } | 557 | } | ||
| 558 | - | return wsView.IsOmniboxVisible() | 558 | + | if wsView.IsOmniboxVisible() { |
| 559 | + | // Let Alt-modified keys through for pane navigation | |||
| 560 | + | return modifiers&input.ModAlt == 0 | |||
| 561 | + | } | |||
| 562 | + | return false | |||
| 559 | }) | 563 | }) | ||
| 560 | // Wire accent handler for dead keys support | 564 | // Wire accent handler for dead keys support | ||
| 561 | if a.insertAccentUC != nil { | 565 | if a.insertAccentUC != nil { | ||
M internal/ui/component/workspace_view.go +5 -0
| @@ -224,6 +224,11 @@ func (wv *WorkspaceView) setActivePaneIDInternal(paneID entity.PaneID) error { | |||
| 224 | 224 | wv.hideFindBarInternal() | |
| 225 | 225 | } | |
| 226 | 226 | ||
| 227 | + | // Hide omnibox if active pane is changing | |
| 228 | + | if currentActiveID != paneID && wv.omnibox != nil { | |
| 229 | + | wv.hideOmniboxInternal() | |
| 230 | + | } | |
| 231 | + | ||
| 227 | 232 | // Deactivate current active pane | |
| 228 | 233 | if currentActiveID != "" { | |
| 229 | 234 | if oldPV, ok := wv.paneViews[currentActiveID]; ok { | |
| @@ -224,6 +224,11 @@ func (wv *WorkspaceView) setActivePaneIDInternal(paneID entity.PaneID) error { | |||||
| 224 | wv.hideFindBarInternal() | 224 | wv.hideFindBarInternal() | ||
| 225 | } | 225 | } | ||
| 226 | 226 | ||||
| 227 | + | // Hide omnibox if active pane is changing | |||
| 228 | + | if currentActiveID != paneID && wv.omnibox != nil { | |||
| 229 | + | wv.hideOmniboxInternal() | |||
| 230 | + | } | |||
| 231 | + | ||||
| 227 | // Deactivate current active pane | 232 | // Deactivate current active pane | ||
| 228 | if currentActiveID != "" { | 233 | if currentActiveID != "" { | ||
| 229 | if oldPV, ok := wv.paneViews[currentActiveID]; ok { | 234 | if oldPV, ok := wv.paneViews[currentActiveID]; ok { | ||
M internal/ui/input/keyboard.go +3 -3
| @@ -41,7 +41,7 @@ type KeyboardHandler struct { | |||
| 41 | 41 | // Action handler callback | |
| 42 | 42 | onAction ActionHandler | |
| 43 | 43 | // Optional bypass check (e.g., omnibox visible) | |
| 44 | - | shouldBypass func() bool | |
| 44 | + | shouldBypass func(modifiers Modifier) bool | |
| 45 | 45 | // Optional accent handler for dead keys support | |
| 46 | 46 | accentHandler AccentHandler | |
| 47 | 47 | ||
| @@ -99,7 +99,7 @@ func (h *KeyboardHandler) SetOnModeChange(fn func(from, to Mode)) { | |||
| 99 | 99 | ||
| 100 | 100 | // SetShouldBypassInput sets a hook to bypass keyboard handling entirely. | |
| 101 | 101 | // When true, events propagate to focused widgets instead. | |
| 102 | - | func (h *KeyboardHandler) SetShouldBypassInput(fn func() bool) { | |
| 102 | + | func (h *KeyboardHandler) SetShouldBypassInput(fn func(modifiers Modifier) bool) { | |
| 103 | 103 | h.mu.Lock() | |
| 104 | 104 | defer h.mu.Unlock() | |
| 105 | 105 | h.shouldBypass = fn | |
| @@ -191,7 +191,7 @@ func (h *KeyboardHandler) handleKeyPress(keyval, keycode uint, state gdk.Modifie | |||
| 191 | 191 | return true | |
| 192 | 192 | } | |
| 193 | 193 | ||
| 194 | - | if shouldBypass != nil && shouldBypass() { | |
| 194 | + | if shouldBypass != nil && shouldBypass(modifiers) { | |
| 195 | 195 | log.Debug().Uint("keyval", keyval).Uint("keycode", keycode).Uint("state", uint(state)).Msg("keyboard handler bypassed") | |
| 196 | 196 | return false | |
| 197 | 197 | } | |
| @@ -41,7 +41,7 @@ type KeyboardHandler struct { | |||||
| 41 | // Action handler callback | 41 | // Action handler callback | ||
| 42 | onAction ActionHandler | 42 | onAction ActionHandler | ||
| 43 | // Optional bypass check (e.g., omnibox visible) | 43 | // Optional bypass check (e.g., omnibox visible) | ||
| 44 | - | shouldBypass func() bool | 44 | + | shouldBypass func(modifiers Modifier) bool |
| 45 | // Optional accent handler for dead keys support | 45 | // Optional accent handler for dead keys support | ||
| 46 | accentHandler AccentHandler | 46 | accentHandler AccentHandler | ||
| 47 | 47 | ||||
| @@ -99,7 +99,7 @@ func (h *KeyboardHandler) SetOnModeChange(fn func(from, to Mode)) { | |||||
| 99 | 99 | ||||
| 100 | // SetShouldBypassInput sets a hook to bypass keyboard handling entirely. | 100 | // SetShouldBypassInput sets a hook to bypass keyboard handling entirely. | ||
| 101 | // When true, events propagate to focused widgets instead. | 101 | // When true, events propagate to focused widgets instead. | ||
| 102 | - | func (h *KeyboardHandler) SetShouldBypassInput(fn func() bool) { | 102 | + | func (h *KeyboardHandler) SetShouldBypassInput(fn func(modifiers Modifier) bool) { |
| 103 | h.mu.Lock() | 103 | h.mu.Lock() | ||
| 104 | defer h.mu.Unlock() | 104 | defer h.mu.Unlock() | ||
| 105 | h.shouldBypass = fn | 105 | h.shouldBypass = fn | ||
| @@ -191,7 +191,7 @@ func (h *KeyboardHandler) handleKeyPress(keyval, keycode uint, state gdk.Modifie | |||||
| 191 | return true | 191 | return true | ||
| 192 | } | 192 | } | ||
| 193 | 193 | ||||
| 194 | - | if shouldBypass != nil && shouldBypass() { | 194 | + | if shouldBypass != nil && shouldBypass(modifiers) { |
| 195 | log.Debug().Uint("keyval", keyval).Uint("keycode", keycode).Uint("state", uint(state)).Msg("keyboard handler bypassed") | 195 | log.Debug().Uint("keyval", keyval).Uint("keycode", keycode).Uint("state", uint(state)).Msg("keyboard handler bypassed") | ||
| 196 | return false | 196 | return false | ||
| 197 | } | 197 | } | ||