detached f2001c9

fix(input): normalize shifted letter keyvals

commit
f2001c937ac6c678b2b35b756856db2394331292
author
brice · · 2025-12-27 16:04 UTC
parent
6d66691
signature
unsigned
2 files · +10 -2
M internal/ui/input/keyboard.go +8 -0
@@ -139,6 +139,14 @@ func (h *KeyboardHandler) handleKeyPress(keyval, keycode uint, state gdk.Modifie
139139
140140 // Build KeyBinding from event
141141 modifiers := Modifier(state) & modifierMask
142+
143+ // GTK reports shifted letters as uppercase keyvals (e.g. Shift+M -> gdk.KEY_M).
144+ // Normalize ASCII A-Z to lowercase so bindings can consistently use lowercase
145+ // keyvals and rely on ModShift to represent Shift.
146+ if keyval >= uint('A') && keyval <= uint('Z') {
147+ keyval = keyval + (uint('a') - uint('A'))
148+ }
149+
142150 binding := KeyBinding{
143151 Keyval: keyval,
144152 Modifiers: modifiers,
@@ -139,6 +139,14 @@ func (h *KeyboardHandler) handleKeyPress(keyval, keycode uint, state gdk.Modifie
139 139
140 // Build KeyBinding from event140 // Build KeyBinding from event
141 modifiers := Modifier(state) & modifierMask141 modifiers := Modifier(state) & modifierMask
142+
143+ // GTK reports shifted letters as uppercase keyvals (e.g. Shift+M -> gdk.KEY_M).
144+ // Normalize ASCII A-Z to lowercase so bindings can consistently use lowercase
145+ // keyvals and rely on ModShift to represent Shift.
146+ if keyval >= uint('A') && keyval <= uint('Z') {
147+ keyval = keyval + (uint('a') - uint('A'))
148+ }
149+
142 binding := KeyBinding{150 binding := KeyBinding{
143 Keyval: keyval,151 Keyval: keyval,
144 Modifiers: modifiers,152 Modifiers: modifiers,
M internal/ui/input/shortcuts.go +2 -2
@@ -356,7 +356,7 @@ func (s *ShortcutSet) registerStandardShortcuts() {
356356 s.Global[KeyBinding{uint(gdk.KEY_g), ModCtrl}] = ActionFindNext
357357 s.Global[KeyBinding{uint(gdk.KEY_g), ModCtrl | ModShift}] = ActionFindPrev
358358 s.Global[KeyBinding{uint(gdk.KEY_r), ModCtrl}] = ActionReload
359- s.Global[KeyBinding{uint(gdk.KEY_R), ModCtrl | ModShift}] = ActionHardReload
359+ s.Global[KeyBinding{uint('r'), ModCtrl | ModShift}] = ActionHardReload
360360 s.Global[KeyBinding{uint(gdk.KEY_F5), ModNone}] = ActionReload
361361 s.Global[KeyBinding{uint(gdk.KEY_F5), ModCtrl}] = ActionHardReload
362362 s.Global[KeyBinding{uint(gdk.KEY_F12), ModNone}] = ActionOpenDevTools
@@ -368,7 +368,7 @@ func (s *ShortcutSet) registerStandardShortcuts() {
368368 s.Global[KeyBinding{uint(gdk.KEY_0), ModCtrl}] = ActionZoomReset
369369 s.Global[KeyBinding{uint(gdk.KEY_q), ModCtrl}] = ActionQuit
370370 s.Global[KeyBinding{uint(gdk.KEY_F11), ModNone}] = ActionToggleFullscreen
371- s.Global[KeyBinding{uint(gdk.KEY_C), ModCtrl | ModShift}] = ActionCopyURL
371+ s.Global[KeyBinding{uint('c'), ModCtrl | ModShift}] = ActionCopyURL
372372 // Session management - direct shortcut to open session manager
373373 s.Global[KeyBinding{uint(gdk.KEY_s), ModCtrl | ModShift}] = ActionOpenSessionManager
374374 }
@@ -356,7 +356,7 @@ func (s *ShortcutSet) registerStandardShortcuts() {
356 s.Global[KeyBinding{uint(gdk.KEY_g), ModCtrl}] = ActionFindNext356 s.Global[KeyBinding{uint(gdk.KEY_g), ModCtrl}] = ActionFindNext
357 s.Global[KeyBinding{uint(gdk.KEY_g), ModCtrl | ModShift}] = ActionFindPrev357 s.Global[KeyBinding{uint(gdk.KEY_g), ModCtrl | ModShift}] = ActionFindPrev
358 s.Global[KeyBinding{uint(gdk.KEY_r), ModCtrl}] = ActionReload358 s.Global[KeyBinding{uint(gdk.KEY_r), ModCtrl}] = ActionReload
359- s.Global[KeyBinding{uint(gdk.KEY_R), ModCtrl | ModShift}] = ActionHardReload359+ s.Global[KeyBinding{uint('r'), ModCtrl | ModShift}] = ActionHardReload
360 s.Global[KeyBinding{uint(gdk.KEY_F5), ModNone}] = ActionReload360 s.Global[KeyBinding{uint(gdk.KEY_F5), ModNone}] = ActionReload
361 s.Global[KeyBinding{uint(gdk.KEY_F5), ModCtrl}] = ActionHardReload361 s.Global[KeyBinding{uint(gdk.KEY_F5), ModCtrl}] = ActionHardReload
362 s.Global[KeyBinding{uint(gdk.KEY_F12), ModNone}] = ActionOpenDevTools362 s.Global[KeyBinding{uint(gdk.KEY_F12), ModNone}] = ActionOpenDevTools
@@ -368,7 +368,7 @@ func (s *ShortcutSet) registerStandardShortcuts() {
368 s.Global[KeyBinding{uint(gdk.KEY_0), ModCtrl}] = ActionZoomReset368 s.Global[KeyBinding{uint(gdk.KEY_0), ModCtrl}] = ActionZoomReset
369 s.Global[KeyBinding{uint(gdk.KEY_q), ModCtrl}] = ActionQuit369 s.Global[KeyBinding{uint(gdk.KEY_q), ModCtrl}] = ActionQuit
370 s.Global[KeyBinding{uint(gdk.KEY_F11), ModNone}] = ActionToggleFullscreen370 s.Global[KeyBinding{uint(gdk.KEY_F11), ModNone}] = ActionToggleFullscreen
371- s.Global[KeyBinding{uint(gdk.KEY_C), ModCtrl | ModShift}] = ActionCopyURL371+ s.Global[KeyBinding{uint('c'), ModCtrl | ModShift}] = ActionCopyURL
372 // Session management - direct shortcut to open session manager372 // Session management - direct shortcut to open session manager
373 s.Global[KeyBinding{uint(gdk.KEY_s), ModCtrl | ModShift}] = ActionOpenSessionManager373 s.Global[KeyBinding{uint(gdk.KEY_s), ModCtrl | ModShift}] = ActionOpenSessionManager
374 }374 }

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