detached 51b8f8a

fix(input): fix off-by-one in dead key range, extract test config helper

Fix IsTextInputKey dead key upper bound: 0xfe8b -> 0xfe8c to include
GDK_KEY_dead_greek. Extract newTestConfig() helper to reduce test
boilerplate across 5 test functions.
commit
51b8f8a25fff192c975cc0294521fd942d8debb8
author
bnema · · 2026-03-10 22:17 UTC
committer
brice · · 2026-03-11 04:05 UTC
parent
56250d7
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +18 -27
M internal/ui/input/keyboard.go +2 -2
@@ -509,8 +509,8 @@ func IsShortcutModified(modifiers Modifier) bool {
509509 // WebKit IM pipeline for compose/dead-key processing.
510510 func IsTextInputKey(keyval uint) bool {
511511 // Dead keys used by compose sequences (US International, etc.)
512- // GDK dead key range: KEY_dead_grave (0xfe50) to KEY_dead_greek (0xfe8b)
513- if keyval >= 0xfe50 && keyval <= 0xfe8b {
512+ // GDK dead key range: KEY_dead_grave (0xfe50) to KEY_dead_greek (0xfe8c)
513+ if keyval >= 0xfe50 && keyval <= 0xfe8c {
514514 return true
515515 }
516516
@@ -509,8 +509,8 @@ func IsShortcutModified(modifiers Modifier) bool {
509 // WebKit IM pipeline for compose/dead-key processing.509 // WebKit IM pipeline for compose/dead-key processing.
510 func IsTextInputKey(keyval uint) bool {510 func IsTextInputKey(keyval uint) bool {
511 // Dead keys used by compose sequences (US International, etc.)511 // Dead keys used by compose sequences (US International, etc.)
512- // GDK dead key range: KEY_dead_grave (0xfe50) to KEY_dead_greek (0xfe8b)512+ // GDK dead key range: KEY_dead_grave (0xfe50) to KEY_dead_greek (0xfe8c)
513- if keyval >= 0xfe50 && keyval <= 0xfe8b {513+ if keyval >= 0xfe50 && keyval <= 0xfe8c {
514 return true514 return true
515 }515 }
516 516
M internal/ui/input/keyboard_test.go +16 -25
@@ -9,6 +9,17 @@ import (
99 "github.com/stretchr/testify/assert"
1010 )
1111
12+// newTestConfig creates a minimal Config with activation shortcuts set.
13+// Used by tests that construct a KeyboardHandler.
14+func newTestConfig() *config.Config {
15+ cfg := &config.Config{}
16+ cfg.Workspace.TabMode.ActivationShortcut = "ctrl+t"
17+ cfg.Workspace.PaneMode.ActivationShortcut = "ctrl+p"
18+ cfg.Workspace.ResizeMode.ActivationShortcut = "ctrl+alt+r"
19+ cfg.Session.SessionMode.ActivationShortcut = "ctrl+s"
20+ return cfg
21+}
22+
1223 func TestIsShortcutModified(t *testing.T) {
1324 tests := []struct {
1425 name string
@@ -81,11 +92,7 @@ func TestIsTextInputKey(t *testing.T) {
8192
8293 func TestHandleKeyPress_RoutePassToWidget(t *testing.T) {
8394 ctx := context.Background()
84- cfg := &config.Config{}
85- cfg.Workspace.TabMode.ActivationShortcut = "ctrl+t"
86- cfg.Workspace.PaneMode.ActivationShortcut = "ctrl+p"
87- cfg.Workspace.ResizeMode.ActivationShortcut = "ctrl+alt+r"
88- cfg.Session.SessionMode.ActivationShortcut = "ctrl+s"
95+ cfg := newTestConfig()
8996
9097 h := NewKeyboardHandler(ctx, cfg)
9198
@@ -116,11 +123,7 @@ func TestHandleKeyPress_RoutePassToWidget(t *testing.T) {
116123
117124 func TestHandleKeyPress_RouteAccentDetection_NoAccent(t *testing.T) {
118125 ctx := context.Background()
119- cfg := &config.Config{}
120- cfg.Workspace.TabMode.ActivationShortcut = "ctrl+t"
121- cfg.Workspace.PaneMode.ActivationShortcut = "ctrl+p"
122- cfg.Workspace.ResizeMode.ActivationShortcut = "ctrl+alt+r"
123- cfg.Session.SessionMode.ActivationShortcut = "ctrl+s"
126+ cfg := newTestConfig()
124127
125128 h := NewKeyboardHandler(ctx, cfg)
126129
@@ -136,11 +139,7 @@ func TestHandleKeyPress_RouteAccentDetection_NoAccent(t *testing.T) {
136139
137140 func TestHandleKeyPress_ShortcutsWorkWithRouting(t *testing.T) {
138141 ctx := context.Background()
139- cfg := &config.Config{}
140- cfg.Workspace.TabMode.ActivationShortcut = "ctrl+t"
141- cfg.Workspace.PaneMode.ActivationShortcut = "ctrl+p"
142- cfg.Workspace.ResizeMode.ActivationShortcut = "ctrl+alt+r"
143- cfg.Session.SessionMode.ActivationShortcut = "ctrl+s"
142+ cfg := newTestConfig()
144143
145144 h := NewKeyboardHandler(ctx, cfg)
146145
@@ -163,11 +162,7 @@ func TestHandleKeyPress_ShortcutsWorkWithRouting(t *testing.T) {
163162
164163 func TestHandleKeyPress_ModalModeIgnoresRoute(t *testing.T) {
165164 ctx := context.Background()
166- cfg := &config.Config{}
167- cfg.Workspace.TabMode.ActivationShortcut = "ctrl+t"
168- cfg.Workspace.PaneMode.ActivationShortcut = "ctrl+p"
169- cfg.Workspace.ResizeMode.ActivationShortcut = "ctrl+alt+r"
170- cfg.Session.SessionMode.ActivationShortcut = "ctrl+s"
165+ cfg := newTestConfig()
171166
172167 h := NewKeyboardHandler(ctx, cfg)
173168 h.EnterTabMode()
@@ -207,11 +202,7 @@ func TestKeyboardHandler_SetRouteKey(t *testing.T) {
207202
208203 func TestHandleKeyPress_NoRouteCallback_DefaultsToShortcuts(t *testing.T) {
209204 ctx := context.Background()
210- cfg := &config.Config{}
211- cfg.Workspace.TabMode.ActivationShortcut = "ctrl+t"
212- cfg.Workspace.PaneMode.ActivationShortcut = "ctrl+p"
213- cfg.Workspace.ResizeMode.ActivationShortcut = "ctrl+alt+r"
214- cfg.Session.SessionMode.ActivationShortcut = "ctrl+s"
205+ cfg := newTestConfig()
215206
216207 h := NewKeyboardHandler(ctx, cfg)
217208
@@ -9,6 +9,17 @@ import (
9 "github.com/stretchr/testify/assert"9 "github.com/stretchr/testify/assert"
10 )10 )
11 11
12+// newTestConfig creates a minimal Config with activation shortcuts set.
13+// Used by tests that construct a KeyboardHandler.
14+func newTestConfig() *config.Config {
15+ cfg := &config.Config{}
16+ cfg.Workspace.TabMode.ActivationShortcut = "ctrl+t"
17+ cfg.Workspace.PaneMode.ActivationShortcut = "ctrl+p"
18+ cfg.Workspace.ResizeMode.ActivationShortcut = "ctrl+alt+r"
19+ cfg.Session.SessionMode.ActivationShortcut = "ctrl+s"
20+ return cfg
21+}
22+
12 func TestIsShortcutModified(t *testing.T) {23 func TestIsShortcutModified(t *testing.T) {
13 tests := []struct {24 tests := []struct {
14 name string25 name string
@@ -81,11 +92,7 @@ func TestIsTextInputKey(t *testing.T) {
81 92
82 func TestHandleKeyPress_RoutePassToWidget(t *testing.T) {93 func TestHandleKeyPress_RoutePassToWidget(t *testing.T) {
83 ctx := context.Background()94 ctx := context.Background()
84- cfg := &config.Config{}95+ cfg := newTestConfig()
85- cfg.Workspace.TabMode.ActivationShortcut = "ctrl+t"
86- cfg.Workspace.PaneMode.ActivationShortcut = "ctrl+p"
87- cfg.Workspace.ResizeMode.ActivationShortcut = "ctrl+alt+r"
88- cfg.Session.SessionMode.ActivationShortcut = "ctrl+s"
89 96
90 h := NewKeyboardHandler(ctx, cfg)97 h := NewKeyboardHandler(ctx, cfg)
91 98
@@ -116,11 +123,7 @@ func TestHandleKeyPress_RoutePassToWidget(t *testing.T) {
116 123
117 func TestHandleKeyPress_RouteAccentDetection_NoAccent(t *testing.T) {124 func TestHandleKeyPress_RouteAccentDetection_NoAccent(t *testing.T) {
118 ctx := context.Background()125 ctx := context.Background()
119- cfg := &config.Config{}126+ cfg := newTestConfig()
120- cfg.Workspace.TabMode.ActivationShortcut = "ctrl+t"
121- cfg.Workspace.PaneMode.ActivationShortcut = "ctrl+p"
122- cfg.Workspace.ResizeMode.ActivationShortcut = "ctrl+alt+r"
123- cfg.Session.SessionMode.ActivationShortcut = "ctrl+s"
124 127
125 h := NewKeyboardHandler(ctx, cfg)128 h := NewKeyboardHandler(ctx, cfg)
126 129
@@ -136,11 +139,7 @@ func TestHandleKeyPress_RouteAccentDetection_NoAccent(t *testing.T) {
136 139
137 func TestHandleKeyPress_ShortcutsWorkWithRouting(t *testing.T) {140 func TestHandleKeyPress_ShortcutsWorkWithRouting(t *testing.T) {
138 ctx := context.Background()141 ctx := context.Background()
139- cfg := &config.Config{}142+ cfg := newTestConfig()
140- cfg.Workspace.TabMode.ActivationShortcut = "ctrl+t"
141- cfg.Workspace.PaneMode.ActivationShortcut = "ctrl+p"
142- cfg.Workspace.ResizeMode.ActivationShortcut = "ctrl+alt+r"
143- cfg.Session.SessionMode.ActivationShortcut = "ctrl+s"
144 143
145 h := NewKeyboardHandler(ctx, cfg)144 h := NewKeyboardHandler(ctx, cfg)
146 145
@@ -163,11 +162,7 @@ func TestHandleKeyPress_ShortcutsWorkWithRouting(t *testing.T) {
163 162
164 func TestHandleKeyPress_ModalModeIgnoresRoute(t *testing.T) {163 func TestHandleKeyPress_ModalModeIgnoresRoute(t *testing.T) {
165 ctx := context.Background()164 ctx := context.Background()
166- cfg := &config.Config{}165+ cfg := newTestConfig()
167- cfg.Workspace.TabMode.ActivationShortcut = "ctrl+t"
168- cfg.Workspace.PaneMode.ActivationShortcut = "ctrl+p"
169- cfg.Workspace.ResizeMode.ActivationShortcut = "ctrl+alt+r"
170- cfg.Session.SessionMode.ActivationShortcut = "ctrl+s"
171 166
172 h := NewKeyboardHandler(ctx, cfg)167 h := NewKeyboardHandler(ctx, cfg)
173 h.EnterTabMode()168 h.EnterTabMode()
@@ -207,11 +202,7 @@ func TestKeyboardHandler_SetRouteKey(t *testing.T) {
207 202
208 func TestHandleKeyPress_NoRouteCallback_DefaultsToShortcuts(t *testing.T) {203 func TestHandleKeyPress_NoRouteCallback_DefaultsToShortcuts(t *testing.T) {
209 ctx := context.Background()204 ctx := context.Background()
210- cfg := &config.Config{}205+ cfg := newTestConfig()
211- cfg.Workspace.TabMode.ActivationShortcut = "ctrl+t"
212- cfg.Workspace.PaneMode.ActivationShortcut = "ctrl+p"
213- cfg.Workspace.ResizeMode.ActivationShortcut = "ctrl+alt+r"
214- cfg.Session.SessionMode.ActivationShortcut = "ctrl+s"
215 206
216 h := NewKeyboardHandler(ctx, cfg)207 h := NewKeyboardHandler(ctx, cfg)
217 208

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