detached 3a090b4

fix(overview): select the stack of a workspace stepped to when its stash carousel wraps round

commit
3a090b4dc3f18bc582049784e0f6246eeb9465db
author
brice · · 2026-10-16 13:40 UTC
parent
6648883
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +49 -2
M docs/config.md +1 -1
@@ -276,7 +276,7 @@ The window must appear within 5 seconds, otherwise it opens normally. Apps that
276276
277277 `toggle-overview` (or a four-finger swipe up) shows previews of your workspaces. Arrows, `h/j/k/l` or three-finger swipes move the selection; `Return` or a click opens it; `Escape` goes back. `close-window` and the move-to-workspace binds work on the selected preview.
278278
279-A workspace's stash shows as a pile of cards. Selecting the pile unfolds it into a carousel of every stashed window: a row under the workspace with `layout.overflow = cascade`, a column on its left otherwise. The selected card stays in the middle, the earlier cards before it and the later ones after it, wrapping round; a wider gap separates the newest card from the oldest. Moves along the carousel pick a card. With fewer than three cards, or on a workspace holding only stashed windows, it does not wrap round. Leaving it folds it again.
279+A workspace's stash shows as a pile of cards. Selecting the pile unfolds it into a carousel of every stashed window: a row under the workspace with `layout.overflow = cascade`, a column on its left otherwise. The selected card stays in the middle, the earlier cards before it and the later ones after it, wrapping round; a wider gap separates the newest card from the oldest. Moves along the carousel pick a card. With fewer than three cards, or on a workspace holding only stashed windows, it does not wrap round. Leaving it folds it again. Moving to another workspace selects its windows rather than a carousel that wraps round, even when its stash has the focus, so browsing workspaces never stops on one.
280280
281281 With `layout.overflow = cascade`, workspaces sit side by side, each band of columns is a lane, and the stash sits under the lanes. Down (or fingers down on the touchpad, with natural scroll off) moves to the lane below; past the last lane it unfolds the stash. Left/right walk the cards; where the carousel does not wrap round, they go to the neighbor workspace past either end. Up leaves the stash for the last lane, at the column you came from.
282282
@@ -276,7 +276,7 @@ The window must appear within 5 seconds, otherwise it opens normally. Apps that
276 276
277 `toggle-overview` (or a four-finger swipe up) shows previews of your workspaces. Arrows, `h/j/k/l` or three-finger swipes move the selection; `Return` or a click opens it; `Escape` goes back. `close-window` and the move-to-workspace binds work on the selected preview.277 `toggle-overview` (or a four-finger swipe up) shows previews of your workspaces. Arrows, `h/j/k/l` or three-finger swipes move the selection; `Return` or a click opens it; `Escape` goes back. `close-window` and the move-to-workspace binds work on the selected preview.
278 278
279-A workspace's stash shows as a pile of cards. Selecting the pile unfolds it into a carousel of every stashed window: a row under the workspace with `layout.overflow = cascade`, a column on its left otherwise. The selected card stays in the middle, the earlier cards before it and the later ones after it, wrapping round; a wider gap separates the newest card from the oldest. Moves along the carousel pick a card. With fewer than three cards, or on a workspace holding only stashed windows, it does not wrap round. Leaving it folds it again.279+A workspace's stash shows as a pile of cards. Selecting the pile unfolds it into a carousel of every stashed window: a row under the workspace with `layout.overflow = cascade`, a column on its left otherwise. The selected card stays in the middle, the earlier cards before it and the later ones after it, wrapping round; a wider gap separates the newest card from the oldest. Moves along the carousel pick a card. With fewer than three cards, or on a workspace holding only stashed windows, it does not wrap round. Leaving it folds it again. Moving to another workspace selects its windows rather than a carousel that wraps round, even when its stash has the focus, so browsing workspaces never stops on one.
280 280
281 With `layout.overflow = cascade`, workspaces sit side by side, each band of columns is a lane, and the stash sits under the lanes. Down (or fingers down on the touchpad, with natural scroll off) moves to the lane below; past the last lane it unfolds the stash. Left/right walk the cards; where the carousel does not wrap round, they go to the neighbor workspace past either end. Up leaves the stash for the last lane, at the column you came from.281 With `layout.overflow = cascade`, workspaces sit side by side, each band of columns is a lane, and the stash sits under the lanes. Down (or fingers down on the touchpad, with natural scroll off) moves to the lane below; past the last lane it unfolds the stash. Left/right walk the cards; where the carousel does not wrap round, they go to the neighbor workspace past either end. Up leaves the stash for the last lane, at the column you came from.
282 282
M internal/core/overview.go +6 -1
@@ -554,12 +554,17 @@ func (m *Monitor) overviewVertical(dy int) {
554554 }
555555
556556 // overviewStep shows the workspace d rows from the current one; it stays
557-// on the first or last row.
557+// on the first or last row. The row selects its stack even when its stash
558+// has the focus, if the stash wraps round (wrapsCards): the moves that
559+// stepped there would only walk the carousel, never reaching the next row.
558560 func (m *Monitor) overviewStep(d int) {
559561 rows := m.overviewWorkspaces()
560562 i := indexOf(rows, m.Current()) + d
561563 if i >= 0 && i < len(rows) {
562564 m.showOverview(rows[i])
565+ if w := m.Current(); m.cardAt(w) >= 0 && w.wrapsCards() {
566+ m.clearCard()
567+ }
563568 }
564569 }
565570
@@ -554,12 +554,17 @@ func (m *Monitor) overviewVertical(dy int) {
554 }554 }
555 555
556 // overviewStep shows the workspace d rows from the current one; it stays556 // overviewStep shows the workspace d rows from the current one; it stays
557-// on the first or last row.557+// on the first or last row. The row selects its stack even when its stash
558+// has the focus, if the stash wraps round (wrapsCards): the moves that
559+// stepped there would only walk the carousel, never reaching the next row.
558 func (m *Monitor) overviewStep(d int) {560 func (m *Monitor) overviewStep(d int) {
559 rows := m.overviewWorkspaces()561 rows := m.overviewWorkspaces()
560 i := indexOf(rows, m.Current()) + d562 i := indexOf(rows, m.Current()) + d
561 if i >= 0 && i < len(rows) {563 if i >= 0 && i < len(rows) {
562 m.showOverview(rows[i])564 m.showOverview(rows[i])
565+ if w := m.Current(); m.cardAt(w) >= 0 && w.wrapsCards() {
566+ m.clearCard()
567+ }
563 }568 }
564 }569 }
565 570
M internal/core/overview_unfold_test.go +42 -0
@@ -359,3 +359,45 @@ func TestOverviewStashOnlyCarouselScrolls(t *testing.T) {
359359 t.Fatalf("last card ends at %d, area at %d", r.X+r.W, u.X+u.W)
360360 }
361361 }
362+
363+// Stepping to a workspace whose stash has the focus selects its stack when
364+// the carousel wraps round, so the next step goes on to the next workspace;
365+// a carousel that does not wrap is still selected.
366+func TestOverviewStepSkipsWrappingCarousel(t *testing.T) {
367+ for _, overflow := range []Overflow{OverflowCascade, OverflowScroll} {
368+ for _, n := range []int{2, 3} {
369+ m := overviewMonitor()
370+ m.Focus(2)
371+ m.AddWindow(30)
372+ m.Focus(1)
373+ w := m.Current()
374+ for id := WindowID(40); id < WindowID(40+n); id++ {
375+ w.AddWindow(id)
376+ w.FocusID(id)
377+ w.ToggleWindowStash()
378+ }
379+ for _, ws := range m.Workspaces[:3] {
380+ ws.Overflow = overflow
381+ }
382+ m.Focus(0)
383+ if !w.stashFocused() {
384+ t.Fatal("setup: the stash does not have the focus")
385+ }
386+ m.ToggleOverview()
387+ m.overviewFocus(ActionFocusWorkspaceNext)
388+ if m.Current() != w {
389+ t.Fatalf("%s, %d cards: on workspace %d", overflow, n, indexOf(m.Workspaces, m.Current()))
390+ }
391+ if got, want := m.cardAt(w) >= 0, n < 3; got != want {
392+ t.Fatalf("%s, %d cards: card selected %v, want %v", overflow, n, got, want)
393+ }
394+ if n < 3 {
395+ continue
396+ }
397+ m.overviewFocus(ActionFocusWorkspaceNext)
398+ if m.Current() != m.Workspaces[2] {
399+ t.Fatalf("%s: the next step stayed on workspace %d", overflow, indexOf(m.Workspaces, m.Current()))
400+ }
401+ }
402+ }
403+}
@@ -359,3 +359,45 @@ func TestOverviewStashOnlyCarouselScrolls(t *testing.T) {
359 t.Fatalf("last card ends at %d, area at %d", r.X+r.W, u.X+u.W)359 t.Fatalf("last card ends at %d, area at %d", r.X+r.W, u.X+u.W)
360 }360 }
361 }361 }
362+
363+// Stepping to a workspace whose stash has the focus selects its stack when
364+// the carousel wraps round, so the next step goes on to the next workspace;
365+// a carousel that does not wrap is still selected.
366+func TestOverviewStepSkipsWrappingCarousel(t *testing.T) {
367+ for _, overflow := range []Overflow{OverflowCascade, OverflowScroll} {
368+ for _, n := range []int{2, 3} {
369+ m := overviewMonitor()
370+ m.Focus(2)
371+ m.AddWindow(30)
372+ m.Focus(1)
373+ w := m.Current()
374+ for id := WindowID(40); id < WindowID(40+n); id++ {
375+ w.AddWindow(id)
376+ w.FocusID(id)
377+ w.ToggleWindowStash()
378+ }
379+ for _, ws := range m.Workspaces[:3] {
380+ ws.Overflow = overflow
381+ }
382+ m.Focus(0)
383+ if !w.stashFocused() {
384+ t.Fatal("setup: the stash does not have the focus")
385+ }
386+ m.ToggleOverview()
387+ m.overviewFocus(ActionFocusWorkspaceNext)
388+ if m.Current() != w {
389+ t.Fatalf("%s, %d cards: on workspace %d", overflow, n, indexOf(m.Workspaces, m.Current()))
390+ }
391+ if got, want := m.cardAt(w) >= 0, n < 3; got != want {
392+ t.Fatalf("%s, %d cards: card selected %v, want %v", overflow, n, got, want)
393+ }
394+ if n < 3 {
395+ continue
396+ }
397+ m.overviewFocus(ActionFocusWorkspaceNext)
398+ if m.Current() != m.Workspaces[2] {
399+ t.Fatalf("%s: the next step stayed on workspace %d", overflow, indexOf(m.Workspaces, m.Current()))
400+ }
401+ }
402+ }
403+}

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