detached 8c8c066

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 VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · 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) {
543543 a.keyboardHandler.SetOnModeChange(func(from, to input.Mode) {
544544 a.handleModeChange(ctx, from, to)
545545 })
546- a.keyboardHandler.SetShouldBypassInput(func() bool {
546+ a.keyboardHandler.SetShouldBypassInput(func(modifiers input.Modifier) bool {
547547 // Bypass keyboard handler when modals are visible
548548 if a.sessionManager != nil && a.sessionManager.IsVisible() {
549549 return true
@@ -555,7 +555,11 @@ func (a *App) initKeyboardHandler(ctx context.Context) {
555555 if wsView == nil {
556556 return false
557557 }
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
559563 })
560564 // Wire accent handler for dead keys support
561565 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 visible547 // 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 true549 return true
@@ -555,7 +555,11 @@ func (a *App) initKeyboardHandler(ctx context.Context) {
555 if wsView == nil {555 if wsView == nil {
556 return false556 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 support564 // 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 {
224224 wv.hideFindBarInternal()
225225 }
226226
227+ // Hide omnibox if active pane is changing
228+ if currentActiveID != paneID && wv.omnibox != nil {
229+ wv.hideOmniboxInternal()
230+ }
231+
227232 // Deactivate current active pane
228233 if currentActiveID != "" {
229234 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 pane232 // 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 {
4141 // Action handler callback
4242 onAction ActionHandler
4343 // Optional bypass check (e.g., omnibox visible)
44- shouldBypass func() bool
44+ shouldBypass func(modifiers Modifier) bool
4545 // Optional accent handler for dead keys support
4646 accentHandler AccentHandler
4747
@@ -99,7 +99,7 @@ func (h *KeyboardHandler) SetOnModeChange(fn func(from, to Mode)) {
9999
100100 // SetShouldBypassInput sets a hook to bypass keyboard handling entirely.
101101 // 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) {
103103 h.mu.Lock()
104104 defer h.mu.Unlock()
105105 h.shouldBypass = fn
@@ -191,7 +191,7 @@ func (h *KeyboardHandler) handleKeyPress(keyval, keycode uint, state gdk.Modifie
191191 return true
192192 }
193193
194- if shouldBypass != nil && shouldBypass() {
194+ if shouldBypass != nil && shouldBypass(modifiers) {
195195 log.Debug().Uint("keyval", keyval).Uint("keycode", keycode).Uint("state", uint(state)).Msg("keyboard handler bypassed")
196196 return false
197197 }
@@ -41,7 +41,7 @@ type KeyboardHandler struct {
41 // Action handler callback41 // Action handler callback
42 onAction ActionHandler42 onAction ActionHandler
43 // Optional bypass check (e.g., omnibox visible)43 // Optional bypass check (e.g., omnibox visible)
44- shouldBypass func() bool44+ shouldBypass func(modifiers Modifier) bool
45 // Optional accent handler for dead keys support45 // Optional accent handler for dead keys support
46 accentHandler AccentHandler46 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 = fn105 h.shouldBypass = fn
@@ -191,7 +191,7 @@ func (h *KeyboardHandler) handleKeyPress(keyval, keycode uint, state gdk.Modifie
191 return true191 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 false196 return false
197 }197 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close