detached 480a380

feat(config): bind cmd+tab to the column switcher

commit
480a380abc11ed47d8ea001a472c0410748c8422
author
brice · · 2026-10-08 17:31 UTC
parent
1f8087b
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +18 -1
M internal/adapters/config/config.go +4 -0
@@ -44,6 +44,8 @@ var defaultBinds = []struct{ combo, action string }{
4444 {"cmd+s", "toggle-stash-visible"},
4545 {"cmd+shift+s", "toggle-window-stash"},
4646 {"cmd+o", "toggle-overview"},
47+ {"cmd+tab", "switch-column-next"},
48+ {"cmd+shift+tab", "switch-column-prev"},
4749 {"cmd+q", "close-window"},
4850 {"ctrl+alt+backspace", "quit"},
4951 {"ctrl+cmd+space", "spawn fuzzel"},
@@ -100,6 +102,8 @@ var actions = map[string]bool{
100102 "toggle-window-stash": true,
101103 "toggle-stash-visible": true,
102104 "toggle-overview": true,
105+ "switch-column-next": true,
106+ "switch-column-prev": true,
103107 "close-window": true,
104108 "quit": true,
105109 "move-window-up": true,
@@ -44,6 +44,8 @@ var defaultBinds = []struct{ combo, action string }{
44 {"cmd+s", "toggle-stash-visible"},44 {"cmd+s", "toggle-stash-visible"},
45 {"cmd+shift+s", "toggle-window-stash"},45 {"cmd+shift+s", "toggle-window-stash"},
46 {"cmd+o", "toggle-overview"},46 {"cmd+o", "toggle-overview"},
47+ {"cmd+tab", "switch-column-next"},
48+ {"cmd+shift+tab", "switch-column-prev"},
47 {"cmd+q", "close-window"},49 {"cmd+q", "close-window"},
48 {"ctrl+alt+backspace", "quit"},50 {"ctrl+alt+backspace", "quit"},
49 {"ctrl+cmd+space", "spawn fuzzel"},51 {"ctrl+cmd+space", "spawn fuzzel"},
@@ -100,6 +102,8 @@ var actions = map[string]bool{
100 "toggle-window-stash": true,102 "toggle-window-stash": true,
101 "toggle-stash-visible": true,103 "toggle-stash-visible": true,
102 "toggle-overview": true,104 "toggle-overview": true,
105+ "switch-column-next": true,
106+ "switch-column-prev": true,
103 "close-window": true,107 "close-window": true,
104 "quit": true,108 "quit": true,
105 "move-window-up": true,109 "move-window-up": true,
M internal/adapters/config/config_test.go +14 -1
@@ -25,7 +25,7 @@ func parseString(t *testing.T, s string) (ports.Config, []Warning) {
2525 func TestDefaultsAndLoad(t *testing.T) {
2626 t.Setenv("TERMINAL", "")
2727 d := Defaults()
28- if d.Keyboard.RepeatRate != 25 || d.Keyboard.CmdKey != "super" || !d.Render.DirectScanout || len(d.Binds) != 77 || d.Layout.MaxColumns != 2 || d.Floating.Dim != 0.3 {
28+ if d.Keyboard.RepeatRate != 25 || d.Keyboard.CmdKey != "super" || !d.Render.DirectScanout || len(d.Binds) != 79 || d.Layout.MaxColumns != 2 || d.Floating.Dim != 0.3 {
2929 t.Fatalf("defaults: %+v", d)
3030 }
3131 if d.Binds["Cmd+s"] != "toggle-stash-visible" || d.Binds["Cmd+Shift+s"] != "toggle-window-stash" || d.Binds["Cmd+o"] != "toggle-overview" || d.Binds["Cmd+f"] != "maximize-column" || d.Binds["Cmd+Shift+f"] != "toggle-fullscreen" || d.Binds["Cmd+Shift+h"] != "move-column-left" || d.Binds["Cmd+j"] != "focus-window-down" || d.Binds["Cmd+Shift+code:2"] != "move-column-to-workspace 1" || d.Focus.FollowMove {
@@ -400,6 +400,19 @@ func TestBindResolvedCollision(t *testing.T) {
400400 }
401401 }
402402
403+func TestColumnSwitcherBinds(t *testing.T) {
404+ d := Defaults()
405+ if d.Binds["Cmd+Tab"] != "switch-column-next" || d.Binds["Cmd+Shift+Tab"] != "switch-column-prev" {
406+ t.Fatal(d.Binds)
407+ }
408+ for _, action := range []string{"switch-column-next", "switch-column-prev"} {
409+ c, w := parseString(t, "bind.cmd+x = "+action+"\n")
410+ if c.Binds["Cmd+x"] != action || len(w) != 0 {
411+ t.Fatalf("%s: binds %v warnings %v", action, c.Binds, w)
412+ }
413+ }
414+}
415+
403416 func TestInvalidDuplicateKeepsEarlierValue(t *testing.T) {
404417 c, w := parseString(t, "background = #000000\nbackground = red\nbackground = #111111\n")
405418 if c.Background.Color != "#111111" || len(w) != 2 || w[0].Line != 2 || w[1].Line != 3 || !strings.Contains(w[1].Msg, "overrides line 1") {
@@ -25,7 +25,7 @@ func parseString(t *testing.T, s string) (ports.Config, []Warning) {
25 func TestDefaultsAndLoad(t *testing.T) {25 func TestDefaultsAndLoad(t *testing.T) {
26 t.Setenv("TERMINAL", "")26 t.Setenv("TERMINAL", "")
27 d := Defaults()27 d := Defaults()
28- if d.Keyboard.RepeatRate != 25 || d.Keyboard.CmdKey != "super" || !d.Render.DirectScanout || len(d.Binds) != 77 || d.Layout.MaxColumns != 2 || d.Floating.Dim != 0.3 {28+ if d.Keyboard.RepeatRate != 25 || d.Keyboard.CmdKey != "super" || !d.Render.DirectScanout || len(d.Binds) != 79 || d.Layout.MaxColumns != 2 || d.Floating.Dim != 0.3 {
29 t.Fatalf("defaults: %+v", d)29 t.Fatalf("defaults: %+v", d)
30 }30 }
31 if d.Binds["Cmd+s"] != "toggle-stash-visible" || d.Binds["Cmd+Shift+s"] != "toggle-window-stash" || d.Binds["Cmd+o"] != "toggle-overview" || d.Binds["Cmd+f"] != "maximize-column" || d.Binds["Cmd+Shift+f"] != "toggle-fullscreen" || d.Binds["Cmd+Shift+h"] != "move-column-left" || d.Binds["Cmd+j"] != "focus-window-down" || d.Binds["Cmd+Shift+code:2"] != "move-column-to-workspace 1" || d.Focus.FollowMove {31 if d.Binds["Cmd+s"] != "toggle-stash-visible" || d.Binds["Cmd+Shift+s"] != "toggle-window-stash" || d.Binds["Cmd+o"] != "toggle-overview" || d.Binds["Cmd+f"] != "maximize-column" || d.Binds["Cmd+Shift+f"] != "toggle-fullscreen" || d.Binds["Cmd+Shift+h"] != "move-column-left" || d.Binds["Cmd+j"] != "focus-window-down" || d.Binds["Cmd+Shift+code:2"] != "move-column-to-workspace 1" || d.Focus.FollowMove {
@@ -400,6 +400,19 @@ func TestBindResolvedCollision(t *testing.T) {
400 }400 }
401 }401 }
402 402
403+func TestColumnSwitcherBinds(t *testing.T) {
404+ d := Defaults()
405+ if d.Binds["Cmd+Tab"] != "switch-column-next" || d.Binds["Cmd+Shift+Tab"] != "switch-column-prev" {
406+ t.Fatal(d.Binds)
407+ }
408+ for _, action := range []string{"switch-column-next", "switch-column-prev"} {
409+ c, w := parseString(t, "bind.cmd+x = "+action+"\n")
410+ if c.Binds["Cmd+x"] != action || len(w) != 0 {
411+ t.Fatalf("%s: binds %v warnings %v", action, c.Binds, w)
412+ }
413+ }
414+}
415+
403 func TestInvalidDuplicateKeepsEarlierValue(t *testing.T) {416 func TestInvalidDuplicateKeepsEarlierValue(t *testing.T) {
404 c, w := parseString(t, "background = #000000\nbackground = red\nbackground = #111111\n")417 c, w := parseString(t, "background = #000000\nbackground = red\nbackground = #111111\n")
405 if c.Background.Color != "#111111" || len(w) != 2 || w[0].Line != 2 || w[1].Line != 3 || !strings.Contains(w[1].Msg, "overrides line 1") {418 if c.Background.Color != "#111111" || len(w) != 2 || w[0].Line != 2 || w[1].Line != 3 || !strings.Contains(w[1].Msg, "overrides line 1") {

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 23

Tags 10

↑↓ moveenter openesc close