detached d4c54ab

fix(core): find a pushed cascade column by its windows in order

The first remembered window still heading a column names it, so a window
expelled from a pushed stack no longer comes back in its place.
commit
d4c54abc899c21403fa986cc620fb5e691a96be7
author
brice · · 2026-10-16 16:59 UTC
parent
efa1a9f
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +23 -4
M internal/core/cascade.go +11 -4
@@ -350,9 +350,11 @@ func (w *Workspace) shiftBand(lo, hi, dir, at int) bool {
350350 return true
351351 }
352352
353-// columnIDs lists the windows of each column, its identity for
354-// restoreBand: a column whose first window is one of them is that column,
355-// even after its first window closed or the stack changed order.
353+// columnIDs lists the windows of each column, in order: its identity for
354+// restoreBand. The column is the one headed by the first of them still
355+// heading a column, so it is found after its first window closed or its
356+// stack changed order, and a window that left it for a column of its own
357+// comes after the one that stayed first.
356358 func columnIDs(cols []Column) [][]WindowID {
357359 ids := make([][]WindowID, len(cols))
358360 for i, c := range cols {
@@ -443,7 +445,12 @@ func (w *Workspace) restoreBand() bool {
443445 if rev {
444446 e = ids[len(ids)-1-k]
445447 }
446- i := slices.IndexFunc(bands[nb], func(c Column) bool { return slices.Contains(e, c.Windows[0]) })
448+ i := -1
449+ for _, v := range e {
450+ if i = slices.IndexFunc(bands[nb], func(c Column) bool { return c.Windows[0] == v }); i >= 0 {
451+ break
452+ }
453+ }
447454 if i < 0 {
448455 continue
449456 }
@@ -350,9 +350,11 @@ func (w *Workspace) shiftBand(lo, hi, dir, at int) bool {
350 return true350 return true
351 }351 }
352 352
353-// columnIDs lists the windows of each column, its identity for353+// columnIDs lists the windows of each column, in order: its identity for
354-// restoreBand: a column whose first window is one of them is that column,354+// restoreBand. The column is the one headed by the first of them still
355-// even after its first window closed or the stack changed order.355+// heading a column, so it is found after its first window closed or its
356+// stack changed order, and a window that left it for a column of its own
357+// comes after the one that stayed first.
356 func columnIDs(cols []Column) [][]WindowID {358 func columnIDs(cols []Column) [][]WindowID {
357 ids := make([][]WindowID, len(cols))359 ids := make([][]WindowID, len(cols))
358 for i, c := range cols {360 for i, c := range cols {
@@ -443,7 +445,12 @@ func (w *Workspace) restoreBand() bool {
443 if rev {445 if rev {
444 e = ids[len(ids)-1-k]446 e = ids[len(ids)-1-k]
445 }447 }
446- i := slices.IndexFunc(bands[nb], func(c Column) bool { return slices.Contains(e, c.Windows[0]) })448+ i := -1
449+ for _, v := range e {
450+ if i = slices.IndexFunc(bands[nb], func(c Column) bool { return c.Windows[0] == v }); i >= 0 {
451+ break
452+ }
453+ }
447 if i < 0 {454 if i < 0 {
448 continue455 continue
449 }456 }
M internal/core/cascade_bands_test.go +12 -0
@@ -224,6 +224,18 @@ func TestCascadeRestoreBandMemory(t *testing.T) {
224224 w.ToggleFullWidth()
225225 wantBands(t, w, []WindowID{1, 2, 3}, []WindowID{5})
226226 })
227+ t.Run("a window expelled from the pushed column stays", func(t *testing.T) {
228+ _, w := cascadeMovesCore(t, 5, 4, false) // {1 2 3 4} {5}
229+ w.FocusID(4)
230+ w.ConsumeOrExpel(-1) // {1 2 3/4} {5}
231+ w.FocusID(2)
232+ w.ToggleFullWidth() // {1} {2*} {3/4 5}
233+ w.FocusID(4)
234+ w.ConsumeOrExpel(-1) // {1} {2*} {4 3 5}
235+ w.FocusID(2)
236+ w.ToggleFullWidth()
237+ wantBands(t, w, []WindowID{1, 2, 3}, []WindowID{4, 5})
238+ })
227239 t.Run("first window of a pushed column closed", func(t *testing.T) {
228240 _, w := cascadeMovesCore(t, 4, 3, false) // {1 2 3} {4}
229241 w.FocusID(4)
@@ -224,6 +224,18 @@ func TestCascadeRestoreBandMemory(t *testing.T) {
224 w.ToggleFullWidth()224 w.ToggleFullWidth()
225 wantBands(t, w, []WindowID{1, 2, 3}, []WindowID{5})225 wantBands(t, w, []WindowID{1, 2, 3}, []WindowID{5})
226 })226 })
227+ t.Run("a window expelled from the pushed column stays", func(t *testing.T) {
228+ _, w := cascadeMovesCore(t, 5, 4, false) // {1 2 3 4} {5}
229+ w.FocusID(4)
230+ w.ConsumeOrExpel(-1) // {1 2 3/4} {5}
231+ w.FocusID(2)
232+ w.ToggleFullWidth() // {1} {2*} {3/4 5}
233+ w.FocusID(4)
234+ w.ConsumeOrExpel(-1) // {1} {2*} {4 3 5}
235+ w.FocusID(2)
236+ w.ToggleFullWidth()
237+ wantBands(t, w, []WindowID{1, 2, 3}, []WindowID{4, 5})
238+ })
227 t.Run("first window of a pushed column closed", func(t *testing.T) {239 t.Run("first window of a pushed column closed", func(t *testing.T) {
228 _, w := cascadeMovesCore(t, 4, 3, false) // {1 2 3} {4}240 _, w := cascadeMovesCore(t, 4, 3, false) // {1 2 3} {4}
229 w.FocusID(4)241 w.FocusID(4)

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 25

Tags 10

↑↓ moveenter openesc close