detached 5c6654f

feat(input): route tab through overlays

commit
5c6654fa400d203bfcff8fa021c1fa20ad41f122
author
brice · · 2026-07-14 11:26 UTC
parent
a50b4a9
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +21 -1
M internal/usecase/daemon/overlay.go +4 -0
@@ -9,6 +9,7 @@ type overlayEvents struct {
99 rune func(rune)
1010 backspace func()
1111 enter func()
12+ tab func()
1213 cancel func()
1314 up func()
1415 down func()
@@ -51,6 +52,9 @@ func routeOverlayBytes(data []byte, pending *[]byte, ev overlayEvents) {
5152 case '\r', '\n':
5253 call(ev.enter)
5354 i++
55+ case 0x09:
56+ call(ev.tab)
57+ i++
5458 case 0x7f, 0x08:
5559 call(ev.backspace)
5660 i++
@@ -9,6 +9,7 @@ type overlayEvents struct {
9 rune func(rune)9 rune func(rune)
10 backspace func()10 backspace func()
11 enter func()11 enter func()
12+ tab func()
12 cancel func()13 cancel func()
13 up func()14 up func()
14 down func()15 down func()
@@ -51,6 +52,9 @@ func routeOverlayBytes(data []byte, pending *[]byte, ev overlayEvents) {
51 case '\r', '\n':52 case '\r', '\n':
52 call(ev.enter)53 call(ev.enter)
53 i++54 i++
55+ case 0x09:
56+ call(ev.tab)
57+ i++
54 case 0x7f, 0x08:58 case 0x7f, 0x08:
55 call(ev.backspace)59 call(ev.backspace)
56 i++60 i++
M internal/usecase/daemon/overlay_test.go +17 -1
@@ -19,6 +19,8 @@ func TestRouteOverlayBytes(t *testing.T) {
1919 wantCancel int
2020 wantUp int
2121 wantDown int
22+ wantTab int
23+ callback bool
2224 }{
2325 {
2426 name: "text and split utf8",
@@ -76,13 +78,23 @@ func TestRouteOverlayBytes(t *testing.T) {
7678 chunks: [][]byte{{'a', 0x01, 0xff, 'b'}},
7779 wantRunes: []rune{'a', 'b'},
7880 },
81+ {
82+ name: "tab routes to callback",
83+ chunks: [][]byte{{0x09}},
84+ wantTab: 1,
85+ callback: true,
86+ },
87+ {
88+ name: "tab without callback is consumed",
89+ chunks: [][]byte{{0x09}},
90+ },
7991 }
8092
8193 for _, tt := range tests {
8294 t.Run(tt.name, func(t *testing.T) {
8395 var pending []byte
8496 var gotRunes []rune
85- var gotBackspace, gotEnter, gotCancel, gotUp, gotDown int
97+ var gotBackspace, gotEnter, gotCancel, gotUp, gotDown, gotTab int
8698 ev := overlayEvents{
8799 rune: func(r rune) { gotRunes = append(gotRunes, r) },
88100 backspace: func() { gotBackspace++ },
@@ -91,6 +103,9 @@ func TestRouteOverlayBytes(t *testing.T) {
91103 up: func() { gotUp++ },
92104 down: func() { gotDown++ },
93105 }
106+ if tt.callback {
107+ ev.tab = func() { gotTab++ }
108+ }
94109
95110 for _, chunk := range tt.chunks {
96111 routeOverlayBytes(chunk, &pending, ev)
@@ -103,6 +118,7 @@ func TestRouteOverlayBytes(t *testing.T) {
103118 require.Equal(t, tt.wantCancel, gotCancel)
104119 require.Equal(t, tt.wantUp, gotUp)
105120 require.Equal(t, tt.wantDown, gotDown)
121+ require.Equal(t, tt.wantTab, gotTab)
106122 })
107123 }
108124 }
@@ -19,6 +19,8 @@ func TestRouteOverlayBytes(t *testing.T) {
19 wantCancel int19 wantCancel int
20 wantUp int20 wantUp int
21 wantDown int21 wantDown int
22+ wantTab int
23+ callback bool
22 }{24 }{
23 {25 {
24 name: "text and split utf8",26 name: "text and split utf8",
@@ -76,13 +78,23 @@ func TestRouteOverlayBytes(t *testing.T) {
76 chunks: [][]byte{{'a', 0x01, 0xff, 'b'}},78 chunks: [][]byte{{'a', 0x01, 0xff, 'b'}},
77 wantRunes: []rune{'a', 'b'},79 wantRunes: []rune{'a', 'b'},
78 },80 },
81+ {
82+ name: "tab routes to callback",
83+ chunks: [][]byte{{0x09}},
84+ wantTab: 1,
85+ callback: true,
86+ },
87+ {
88+ name: "tab without callback is consumed",
89+ chunks: [][]byte{{0x09}},
90+ },
79 }91 }
80 92
81 for _, tt := range tests {93 for _, tt := range tests {
82 t.Run(tt.name, func(t *testing.T) {94 t.Run(tt.name, func(t *testing.T) {
83 var pending []byte95 var pending []byte
84 var gotRunes []rune96 var gotRunes []rune
85- var gotBackspace, gotEnter, gotCancel, gotUp, gotDown int97+ var gotBackspace, gotEnter, gotCancel, gotUp, gotDown, gotTab int
86 ev := overlayEvents{98 ev := overlayEvents{
87 rune: func(r rune) { gotRunes = append(gotRunes, r) },99 rune: func(r rune) { gotRunes = append(gotRunes, r) },
88 backspace: func() { gotBackspace++ },100 backspace: func() { gotBackspace++ },
@@ -91,6 +103,9 @@ func TestRouteOverlayBytes(t *testing.T) {
91 up: func() { gotUp++ },103 up: func() { gotUp++ },
92 down: func() { gotDown++ },104 down: func() { gotDown++ },
93 }105 }
106+ if tt.callback {
107+ ev.tab = func() { gotTab++ }
108+ }
94 109
95 for _, chunk := range tt.chunks {110 for _, chunk := range tt.chunks {
96 routeOverlayBytes(chunk, &pending, ev)111 routeOverlayBytes(chunk, &pending, ev)
@@ -103,6 +118,7 @@ func TestRouteOverlayBytes(t *testing.T) {
103 require.Equal(t, tt.wantCancel, gotCancel)118 require.Equal(t, tt.wantCancel, gotCancel)
104 require.Equal(t, tt.wantUp, gotUp)119 require.Equal(t, tt.wantUp, gotUp)
105 require.Equal(t, tt.wantDown, gotDown)120 require.Equal(t, tt.wantDown, gotDown)
121+ require.Equal(t, tt.wantTab, gotTab)
106 })122 })
107 }123 }
108 }124 }

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 12

Tags 11

↑↓ moveenter openesc close