detached 099f719

fix(outputs): the last column of a workspace does not move to another output

commit
099f7198912af0b1cab8968332bf0c00ba418106
author
brice · · 2026-09-25 04:51 UTC
parent
0764a10
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +22 -1
M internal/core/action.go +5 -0
@@ -124,6 +124,11 @@ func (c *Core) applyAction(a Action) Effect {
124124 }
125125 edge := (dir < 0 && w.Focus == 0) || (dir > 0 && w.Focus == len(w.Columns)-1)
126126 if i := c.neighbor(dir); edge && i >= 0 {
127+ // The last column stays: a workspace on screen is never
128+ // empty, and moving it would only spawn a new terminal.
129+ if len(w.Columns) < 2 {
130+ return Effect{}
131+ }
127132 col, ok := w.takeColumn()
128133 if !ok {
129134 return Effect{}
@@ -124,6 +124,11 @@ func (c *Core) applyAction(a Action) Effect {
124 }124 }
125 edge := (dir < 0 && w.Focus == 0) || (dir > 0 && w.Focus == len(w.Columns)-1)125 edge := (dir < 0 && w.Focus == 0) || (dir > 0 && w.Focus == len(w.Columns)-1)
126 if i := c.neighbor(dir); edge && i >= 0 {126 if i := c.neighbor(dir); edge && i >= 0 {
127+ // The last column stays: a workspace on screen is never
128+ // empty, and moving it would only spawn a new terminal.
129+ if len(w.Columns) < 2 {
130+ return Effect{}
131+ }
127 col, ok := w.takeColumn()132 col, ok := w.takeColumn()
128 if !ok {133 if !ok {
129 return Effect{}134 return Effect{}
M internal/core/screens_test.go +17 -1
@@ -361,7 +361,14 @@ func TestMoveColumnCrossesOutputs(t *testing.T) {
361361 if out := lastOutputs(t, r.commands); out.Focused != "DP-2" {
362362 t.Fatal(out.Focused)
363363 }
364- // Back left: it lands on the right side of DP-1.
364+ // It is DP-2's only column now: it stays. With a second one, the
365+ // focused column goes back and lands on the right side of DP-1.
366+ set = r.key(t, "Left", ports.ModAlt|ports.ModShift)
367+ if got := shown(set); len(got["DP-1"]) != 1 || len(got["DP-2"]) != 1 {
368+ t.Fatal(got)
369+ }
370+ r.mapWindow(t, 3)
371+ r.key(t, "Left", ports.ModAlt) // focus window 2, the left column
365372 set = r.key(t, "Left", ports.ModAlt|ports.ModShift)
366373 if got := shown(set)["DP-1"]; len(got) != 2 {
367374 t.Fatal(got)
@@ -419,3 +426,12 @@ func TestEmptyWorkspaceGetsTerminal(t *testing.T) {
419426 t.Fatal("spawned twice")
420427 }
421428 }
429+
430+func TestLastColumnStaysOnItsOutput(t *testing.T) {
431+ r := startMulti(t, nil, left, right)
432+ r.mapWindow(t, 1)
433+ set := r.key(t, "Right", ports.ModAlt|ports.ModShift)
434+ if got := shown(set); len(got["DP-1"]) != 1 || len(got["DP-2"]) != 0 {
435+ t.Fatal(got)
436+ }
437+}
@@ -361,7 +361,14 @@ func TestMoveColumnCrossesOutputs(t *testing.T) {
361 if out := lastOutputs(t, r.commands); out.Focused != "DP-2" {361 if out := lastOutputs(t, r.commands); out.Focused != "DP-2" {
362 t.Fatal(out.Focused)362 t.Fatal(out.Focused)
363 }363 }
364- // Back left: it lands on the right side of DP-1.364+ // It is DP-2's only column now: it stays. With a second one, the
365+ // focused column goes back and lands on the right side of DP-1.
366+ set = r.key(t, "Left", ports.ModAlt|ports.ModShift)
367+ if got := shown(set); len(got["DP-1"]) != 1 || len(got["DP-2"]) != 1 {
368+ t.Fatal(got)
369+ }
370+ r.mapWindow(t, 3)
371+ r.key(t, "Left", ports.ModAlt) // focus window 2, the left column
365 set = r.key(t, "Left", ports.ModAlt|ports.ModShift)372 set = r.key(t, "Left", ports.ModAlt|ports.ModShift)
366 if got := shown(set)["DP-1"]; len(got) != 2 {373 if got := shown(set)["DP-1"]; len(got) != 2 {
367 t.Fatal(got)374 t.Fatal(got)
@@ -419,3 +426,12 @@ func TestEmptyWorkspaceGetsTerminal(t *testing.T) {
419 t.Fatal("spawned twice")426 t.Fatal("spawned twice")
420 }427 }
421 }428 }
429+
430+func TestLastColumnStaysOnItsOutput(t *testing.T) {
431+ r := startMulti(t, nil, left, right)
432+ r.mapWindow(t, 1)
433+ set := r.key(t, "Right", ports.ModAlt|ports.ModShift)
434+ if got := shown(set); len(got["DP-1"]) != 1 || len(got["DP-2"]) != 0 {
435+ t.Fatal(got)
436+ }
437+}

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