detached f1c9159

fix(input): parse '+' key bindings

commit
f1c91596b9b308822fb4fa9a0ef9db733f649e27
author
brice · · 2025-12-26 08:47 UTC
parent
51d3a48
signature
unsigned
2 files · +27 -0
M internal/ui/input/shortcuts.go +15 -0
@@ -453,6 +453,10 @@ func ParseKeyString(s string) (KeyBinding, bool) {
453453 }
454454
455455 s = strings.ToLower(strings.TrimSpace(s))
456+ if s == "+" {
457+ return KeyBinding{Keyval: uint(gdk.KEY_plus), Modifiers: ModNone}, true
458+ }
459+
456460 parts := strings.Split(s, "+")
457461
458462 var modifiers Modifier
@@ -460,6 +464,9 @@ func ParseKeyString(s string) (KeyBinding, bool) {
460464
461465 for _, part := range parts {
462466 part = strings.TrimSpace(part)
467+ if part == "" {
468+ continue
469+ }
463470 switch part {
464471 case "ctrl", "control":
465472 modifiers |= ModCtrl
@@ -468,10 +475,18 @@ func ParseKeyString(s string) (KeyBinding, bool) {
468475 case "alt":
469476 modifiers |= ModAlt
470477 default:
478+ if keyPart != "" {
479+ return KeyBinding{}, false
480+ }
471481 keyPart = part
472482 }
473483 }
474484
485+ // Allow parsing "ctrl++" / "alt+shift++" where the key is "+".
486+ if keyPart == "" && strings.HasSuffix(s, "++") {
487+ keyPart = "+"
488+ }
489+
475490 if keyPart == "" {
476491 return KeyBinding{}, false
477492 }
@@ -453,6 +453,10 @@ func ParseKeyString(s string) (KeyBinding, bool) {
453 }453 }
454 454
455 s = strings.ToLower(strings.TrimSpace(s))455 s = strings.ToLower(strings.TrimSpace(s))
456+ if s == "+" {
457+ return KeyBinding{Keyval: uint(gdk.KEY_plus), Modifiers: ModNone}, true
458+ }
459+
456 parts := strings.Split(s, "+")460 parts := strings.Split(s, "+")
457 461
458 var modifiers Modifier462 var modifiers Modifier
@@ -460,6 +464,9 @@ func ParseKeyString(s string) (KeyBinding, bool) {
460 464
461 for _, part := range parts {465 for _, part := range parts {
462 part = strings.TrimSpace(part)466 part = strings.TrimSpace(part)
467+ if part == "" {
468+ continue
469+ }
463 switch part {470 switch part {
464 case "ctrl", "control":471 case "ctrl", "control":
465 modifiers |= ModCtrl472 modifiers |= ModCtrl
@@ -468,10 +475,18 @@ func ParseKeyString(s string) (KeyBinding, bool) {
468 case "alt":475 case "alt":
469 modifiers |= ModAlt476 modifiers |= ModAlt
470 default:477 default:
478+ if keyPart != "" {
479+ return KeyBinding{}, false
480+ }
471 keyPart = part481 keyPart = part
472 }482 }
473 }483 }
474 484
485+ // Allow parsing "ctrl++" / "alt+shift++" where the key is "+".
486+ if keyPart == "" && strings.HasSuffix(s, "++") {
487+ keyPart = "+"
488+ }
489+
475 if keyPart == "" {490 if keyPart == "" {
476 return KeyBinding{}, false491 return KeyBinding{}, false
477 }492 }
M internal/ui/input/shortcuts_test.go +12 -0
@@ -49,6 +49,12 @@ func TestParseKeyString_SingleKeys(t *testing.T) {
4949 want: KeyBinding{Keyval: uint(gdk.KEY_space), Modifiers: ModNone},
5050 wantOk: true,
5151 },
52+ {
53+ name: "plus symbol",
54+ input: "+",
55+ want: KeyBinding{Keyval: uint(gdk.KEY_plus), Modifiers: ModNone},
56+ wantOk: true,
57+ },
5258 {
5359 name: "f5",
5460 input: "f5",
@@ -150,6 +156,12 @@ func TestParseKeyString_WithModifiers(t *testing.T) {
150156 want: KeyBinding{Keyval: uint('r'), Modifiers: ModCtrl},
151157 wantOk: true,
152158 },
159+ {
160+ name: "ctrl++",
161+ input: "ctrl++",
162+ want: KeyBinding{Keyval: uint(gdk.KEY_plus), Modifiers: ModCtrl},
163+ wantOk: true,
164+ },
153165 }
154166
155167 for _, tt := range tests {
@@ -49,6 +49,12 @@ func TestParseKeyString_SingleKeys(t *testing.T) {
49 want: KeyBinding{Keyval: uint(gdk.KEY_space), Modifiers: ModNone},49 want: KeyBinding{Keyval: uint(gdk.KEY_space), Modifiers: ModNone},
50 wantOk: true,50 wantOk: true,
51 },51 },
52+ {
53+ name: "plus symbol",
54+ input: "+",
55+ want: KeyBinding{Keyval: uint(gdk.KEY_plus), Modifiers: ModNone},
56+ wantOk: true,
57+ },
52 {58 {
53 name: "f5",59 name: "f5",
54 input: "f5",60 input: "f5",
@@ -150,6 +156,12 @@ func TestParseKeyString_WithModifiers(t *testing.T) {
150 want: KeyBinding{Keyval: uint('r'), Modifiers: ModCtrl},156 want: KeyBinding{Keyval: uint('r'), Modifiers: ModCtrl},
151 wantOk: true,157 wantOk: true,
152 },158 },
159+ {
160+ name: "ctrl++",
161+ input: "ctrl++",
162+ want: KeyBinding{Keyval: uint(gdk.KEY_plus), Modifiers: ModCtrl},
163+ wantOk: true,
164+ },
153 }165 }
154 166
155 for _, tt := range tests {167 for _, tt := range tests {

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