detached d1a6612

perf(overview): lay the stash pile out without per-frame allocations

The folded pile went through fan with one slice per card, and the
unfolded strip was built in its own slice then copied: 8 and 1 extra
allocations per overview frame. Both now append straight into the
overview layout, and fan loses its circular mode, used only by the
pile. pileWidth reuses cardSize, and comments describe the pile band
under the selected lane.
commit
d1a6612e19479dd42ab180bcf6fcb6f6cefeb03b
author
brice · · 2026-10-15 05:23 UTC
parent
fc79ddf
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +77 -46
M internal/core/overview.go +52 -45
@@ -60,8 +60,9 @@ type rowSnapshot struct {
6060 // unfolds along the overview axis into a strip of every card (unfold), the
6161 // selection framed, and the moves along that axis walk it. A vertical
6262 // overview pins the pile to the left edge of its row, which takes the width
63-// the pile leaves. A horizontal one centres it on its row, straddling the
64-// bottom edge of the selected lane, over the tiles.
63+// the pile leaves. A horizontal one centres it on its row, in a band
64+// reserved under the selected lane (pileBand): the lane and the band are
65+// centred together.
6566 const (
6667 overviewCardZoom = 0.2
6768 overviewCards = 4
@@ -650,7 +651,7 @@ func (m *Monitor) overviewAxis() layoutAxis { return m.Current().policy().worksp
650651 //
651652 // Vertical axis: full-width slots, the current one centred; the rule is
652653 // horizontal. Horizontal axis: full-height slots as wide as their content,
653-// stack peeks included (the stash pile sits over the row), the current one
654+// stack peeks included (the stash pile takes a band of the slot), the current one
654655 // centred; the rule is vertical, in the spacing between two slots.
655656 func (m *Monitor) overviewRows() (map[*Workspace]Rect, []ports.Separator) {
656657 cur := m.Current()
@@ -733,7 +734,8 @@ func (m *Monitor) overviewLayout(result []Placement) []Placement {
733734 axis := m.overviewAxis()
734735 // The unfolded stash goes last: its strip may run over the neighbor
735736 // rows, which must not cover it.
736- var unfolded []Placement
737+ var unfolded *Workspace
738+ var unfoldedSlot Rect
737739 for w := range m.all() {
738740 slot, shown := rows[w]
739741 if shown {
@@ -742,12 +744,10 @@ func (m *Monitor) overviewLayout(result []Placement) []Placement {
742744 slot.H -= w.pileBand(axis)
743745 at := m.cardAt(w)
744746 result = append(result, m.stackRow(w, axis, slot, w != cur, at < 0)...)
745- if len(w.Stash) > 0 {
746- if cards := w.pile(axis, slot, w != cur, at); at >= 0 {
747- unfolded = cards
748- } else {
749- result = append(result, cards...)
750- }
747+ if at >= 0 {
748+ unfolded, unfoldedSlot = w, slot
749+ } else {
750+ result = w.pile(result, axis, slot, w != cur, -1)
751751 }
752752 }
753753 for _, f := range w.Floats {
@@ -760,13 +760,16 @@ func (m *Monitor) overviewLayout(result []Placement) []Placement {
760760 result = w.appendHidden(result, false)
761761 }
762762 }
763- return append(result, unfolded...)
763+ if unfolded != nil {
764+ result = unfolded.pile(result, axis, unfoldedSlot, false, m.cardAt(unfolded))
765+ }
766+ return result
764767 }
765768
766769 // fan places front and neighboring cards, farthest visible first and front
767770 // last. A card may contain several tiles; omitted cards stay in the layout
768771 // hidden so their clients are not left showing on screen.
769-func fan(cards [][]Placement, front, maxBehind, maxBefore int, circular bool,
772+func fan(cards [][]Placement, front, maxBehind, maxBefore int,
770773 behind, before func(int) (int, int), dim, lit bool) []Placement {
771774 n := len(cards)
772775 if n == 0 {
@@ -792,9 +795,6 @@ func fan(cards [][]Placement, front, maxBehind, maxBefore int, circular bool,
792795 // Behind and before are in nearest-first order; paint farthest first.
793796 for d := min(maxBehind, n-1); d >= 1; d-- {
794797 i := front + d
795- if circular {
796- i = (front + d) % n
797- }
798798 if i >= n {
799799 continue
800800 }
@@ -803,13 +803,7 @@ func fan(cards [][]Placement, front, maxBehind, maxBefore int, circular bool,
803803 }
804804 for d := min(maxBefore, n-1); d >= 1; d-- {
805805 i := front - d
806- if circular {
807- i = (front - d + n) % n
808- }
809- if i < 0 {
810- continue
811- }
812- if shown[i] {
806+ if i < 0 || shown[i] {
813807 continue
814808 }
815809 add(i, d, before, true)
@@ -837,7 +831,7 @@ func (w *Workspace) pileWidth() int {
837831 if len(w.Stash) == 0 {
838832 return 0
839833 }
840- card := int(math.Round(float64(w.stashRect().W) * overviewCardZoom))
834+ card, _ := w.cardSize()
841835 // Never more than a third of the width: the columns keep the rest.
842836 a := w.overviewArea()
843837 return min(card+(min(len(w.Stash), overviewCards)-1)*w.cardStep()+a.W*2/100, a.W/3)
@@ -867,14 +861,15 @@ func (w *Workspace) cardStep() int {
867861 return min(overviewCardStep, a.W/100, a.H/50)
868862 }
869863
870-// pile places the stash of w as cards in its slot along axis (see the
871-// overview's stash comment). With entry at selected (at >= 0, on the
872-// current row: dim false) the pile unfolds; else it is folded, the stash
873-// selection in front, the next ones behind it, dimmed, the rest hidden.
874-func (w *Workspace) pile(axis layoutAxis, slot Rect, dim bool, at int) []Placement {
864+// pile appends the stash of w to dst as cards in its slot along axis (see
865+// the overview's stash comment). With entry at selected (at >= 0, on the
866+// current row: dim false) the pile unfolds; else it is folded: the stash
867+// selection in front, painted last, up to overviewCards-1 next ones
868+// (wrapping round) behind it, farthest first, dimmed; the rest hidden.
869+func (w *Workspace) pile(dst []Placement, axis layoutAxis, slot Rect, dim bool, at int) []Placement {
875870 n := len(w.Stash)
876871 if n == 0 {
877- return nil
872+ return dst
878873 }
879874 u := w.overviewArea()
880875 cw, ch := w.cardSize()
@@ -897,16 +892,28 @@ func (w *Workspace) pile(axis layoutAxis, slot Rect, dim bool, at int) []Placeme
897892 }
898893 folded := Rect{X: x0, Y: y0, W: cw, H: ch}
899894 if at >= 0 && !dim {
900- return w.unfold(axis, folded, min(at, n-1))
895+ return w.unfold(dst, axis, folded, min(at, n-1))
896+ }
897+ front, behind, step := min(max(w.stashAt, 0), n-1), min(n, overviewCards)-1, w.cardStep()
898+ card := func(i, d int) Placement {
899+ p := Placement{ID: w.Stash[i].ID, Rect: folded, Preview: overviewCardZoom, Card: true}
900+ p.Rect.X, p.Rect.Y = p.Rect.X+d*step, p.Rect.Y-d*step
901+ if d > 0 || dim {
902+ p.peeking(1)
903+ }
904+ return p
905+ }
906+ for d := behind; d >= 0; d-- {
907+ dst = append(dst, card((front+d)%n, d))
901908 }
902- cards := make([][]Placement, n)
903909 for i := range w.Stash {
904- cards[i] = []Placement{{ID: w.Stash[i].ID, Rect: folded, Preview: overviewCardZoom, Card: true}}
910+ // The cards neither in front nor behind it: hidden, so their
911+ // clients are not left showing.
912+ if (i-front+n)%n > behind {
913+ dst = append(dst, Placement{ID: w.Stash[i].ID, Rect: folded, Card: true, Hidden: true})
914+ }
905915 }
906- step := w.cardStep()
907- return fan(cards, min(max(w.stashAt, 0), n-1), min(n, overviewCards)-1, 0, true,
908- func(d int) (int, int) { return d * step, -d * step },
909- func(int) (int, int) { return 0, 0 }, dim, false)
916+ return dst
910917 }
911918
912919 // unfold lays the stash of w out as a strip of every card along axis, from
@@ -914,8 +921,9 @@ func (w *Workspace) pile(axis layoutAxis, slot Rect, dim bool, at int) []Placeme
914921 // a vertical one. The strip is centred on the pile and kept in the overview
915922 // area. Longer than the area, it scrolls like a wide row: the selected card
916923 // at is centred, the strip stopping at the area's edges. The other cards
917-// are dimmed; those out of the area are hidden.
918-func (w *Workspace) unfold(axis layoutAxis, folded Rect, at int) []Placement {
924+// are dimmed; those out of the area are hidden. The cards are appended to
925+// dst.
926+func (w *Workspace) unfold(dst []Placement, axis layoutAxis, folded Rect, at int) []Placement {
919927 n := len(w.Stash)
920928 u := w.overviewArea()
921929 size, origin, lo, hi := folded.W, folded.X, u.X, u.X+u.W
@@ -931,7 +939,6 @@ func (w *Workspace) unfold(axis layoutAxis, folded Rect, at int) []Placement {
931939 } else {
932940 start = min(max(start, lo), hi-total)
933941 }
934- out := make([]Placement, n)
935942 for i := range w.Stash {
936943 r := folded
937944 if axis == verticalAxis {
@@ -946,9 +953,9 @@ func (w *Workspace) unfold(axis layoutAxis, folded Rect, at int) []Placement {
946953 if !r.Overlaps(u) {
947954 p.Hidden, p.Preview, p.Focused = true, 0, false
948955 }
949- out[i] = p
956+ dst = append(dst, p)
950957 }
951- return out
958+ return dst
952959 }
953960
954961 // overviewZoom is the scale that fits every column of w in the usable
@@ -1138,7 +1145,8 @@ func (w *Workspace) previewRowTiles(axis layoutAxis, slot Rect, dim, lit, lanes
11381145 }
11391146 // Rows stay centred on the output like rows without a stash; in a
11401147 // vertical overview the pile only pushes a row right when they would
1141- // overlap. A horizontal one draws the pile over the row.
1148+ // overlap. A horizontal one gives the pile a band under the selected
1149+ // lane (pileBand).
11421150 left := 0
11431151 if axis == verticalAxis {
11441152 left = w.pileWidth()
@@ -1312,9 +1320,8 @@ func overviewIn(layout []Placement, x, y float64) WindowID {
13121320 return 0
13131321 }
13141322
1315-// overviewHit is overviewIn with the stash piles first: a pile is drawn
1316-// over its row (a horizontal overview), the cards behind its front one
1317-// included.
1323+// overviewHit is overviewIn with the stash piles first: the cards behind a
1324+// folded pile's front one peek out where tiles may be drawn over them.
13181325 func (m *Monitor) overviewHit(layout []Placement, x, y float64) WindowID {
13191326 var piles []Placement
13201327 for _, p := range layout {
@@ -60,8 +60,9 @@ type rowSnapshot struct {
60 // unfolds along the overview axis into a strip of every card (unfold), the60 // unfolds along the overview axis into a strip of every card (unfold), the
61 // selection framed, and the moves along that axis walk it. A vertical61 // selection framed, and the moves along that axis walk it. A vertical
62 // overview pins the pile to the left edge of its row, which takes the width62 // overview pins the pile to the left edge of its row, which takes the width
63-// the pile leaves. A horizontal one centres it on its row, straddling the63+// the pile leaves. A horizontal one centres it on its row, in a band
64-// bottom edge of the selected lane, over the tiles.64+// reserved under the selected lane (pileBand): the lane and the band are
65+// centred together.
65 const (66 const (
66 overviewCardZoom = 0.267 overviewCardZoom = 0.2
67 overviewCards = 468 overviewCards = 4
@@ -650,7 +651,7 @@ func (m *Monitor) overviewAxis() layoutAxis { return m.Current().policy().worksp
650 //651 //
651 // Vertical axis: full-width slots, the current one centred; the rule is652 // Vertical axis: full-width slots, the current one centred; the rule is
652 // horizontal. Horizontal axis: full-height slots as wide as their content,653 // horizontal. Horizontal axis: full-height slots as wide as their content,
653-// stack peeks included (the stash pile sits over the row), the current one654+// stack peeks included (the stash pile takes a band of the slot), the current one
654 // centred; the rule is vertical, in the spacing between two slots.655 // centred; the rule is vertical, in the spacing between two slots.
655 func (m *Monitor) overviewRows() (map[*Workspace]Rect, []ports.Separator) {656 func (m *Monitor) overviewRows() (map[*Workspace]Rect, []ports.Separator) {
656 cur := m.Current()657 cur := m.Current()
@@ -733,7 +734,8 @@ func (m *Monitor) overviewLayout(result []Placement) []Placement {
733 axis := m.overviewAxis()734 axis := m.overviewAxis()
734 // The unfolded stash goes last: its strip may run over the neighbor735 // The unfolded stash goes last: its strip may run over the neighbor
735 // rows, which must not cover it.736 // rows, which must not cover it.
736- var unfolded []Placement737+ var unfolded *Workspace
738+ var unfoldedSlot Rect
737 for w := range m.all() {739 for w := range m.all() {
738 slot, shown := rows[w]740 slot, shown := rows[w]
739 if shown {741 if shown {
@@ -742,12 +744,10 @@ func (m *Monitor) overviewLayout(result []Placement) []Placement {
742 slot.H -= w.pileBand(axis)744 slot.H -= w.pileBand(axis)
743 at := m.cardAt(w)745 at := m.cardAt(w)
744 result = append(result, m.stackRow(w, axis, slot, w != cur, at < 0)...)746 result = append(result, m.stackRow(w, axis, slot, w != cur, at < 0)...)
745- if len(w.Stash) > 0 {747+ if at >= 0 {
746- if cards := w.pile(axis, slot, w != cur, at); at >= 0 {748+ unfolded, unfoldedSlot = w, slot
747- unfolded = cards749+ } else {
748- } else {750+ result = w.pile(result, axis, slot, w != cur, -1)
749- result = append(result, cards...)
750- }
751 }751 }
752 }752 }
753 for _, f := range w.Floats {753 for _, f := range w.Floats {
@@ -760,13 +760,16 @@ func (m *Monitor) overviewLayout(result []Placement) []Placement {
760 result = w.appendHidden(result, false)760 result = w.appendHidden(result, false)
761 }761 }
762 }762 }
763- return append(result, unfolded...)763+ if unfolded != nil {
764+ result = unfolded.pile(result, axis, unfoldedSlot, false, m.cardAt(unfolded))
765+ }
766+ return result
764 }767 }
765 768
766 // fan places front and neighboring cards, farthest visible first and front769 // fan places front and neighboring cards, farthest visible first and front
767 // last. A card may contain several tiles; omitted cards stay in the layout770 // last. A card may contain several tiles; omitted cards stay in the layout
768 // hidden so their clients are not left showing on screen.771 // hidden so their clients are not left showing on screen.
769-func fan(cards [][]Placement, front, maxBehind, maxBefore int, circular bool,772+func fan(cards [][]Placement, front, maxBehind, maxBefore int,
770 behind, before func(int) (int, int), dim, lit bool) []Placement {773 behind, before func(int) (int, int), dim, lit bool) []Placement {
771 n := len(cards)774 n := len(cards)
772 if n == 0 {775 if n == 0 {
@@ -792,9 +795,6 @@ func fan(cards [][]Placement, front, maxBehind, maxBefore int, circular bool,
792 // Behind and before are in nearest-first order; paint farthest first.795 // Behind and before are in nearest-first order; paint farthest first.
793 for d := min(maxBehind, n-1); d >= 1; d-- {796 for d := min(maxBehind, n-1); d >= 1; d-- {
794 i := front + d797 i := front + d
795- if circular {
796- i = (front + d) % n
797- }
798 if i >= n {798 if i >= n {
799 continue799 continue
800 }800 }
@@ -803,13 +803,7 @@ func fan(cards [][]Placement, front, maxBehind, maxBefore int, circular bool,
803 }803 }
804 for d := min(maxBefore, n-1); d >= 1; d-- {804 for d := min(maxBefore, n-1); d >= 1; d-- {
805 i := front - d805 i := front - d
806- if circular {806+ if i < 0 || shown[i] {
807- i = (front - d + n) % n
808- }
809- if i < 0 {
810- continue
811- }
812- if shown[i] {
813 continue807 continue
814 }808 }
815 add(i, d, before, true)809 add(i, d, before, true)
@@ -837,7 +831,7 @@ func (w *Workspace) pileWidth() int {
837 if len(w.Stash) == 0 {831 if len(w.Stash) == 0 {
838 return 0832 return 0
839 }833 }
840- card := int(math.Round(float64(w.stashRect().W) * overviewCardZoom))834+ card, _ := w.cardSize()
841 // Never more than a third of the width: the columns keep the rest.835 // Never more than a third of the width: the columns keep the rest.
842 a := w.overviewArea()836 a := w.overviewArea()
843 return min(card+(min(len(w.Stash), overviewCards)-1)*w.cardStep()+a.W*2/100, a.W/3)837 return min(card+(min(len(w.Stash), overviewCards)-1)*w.cardStep()+a.W*2/100, a.W/3)
@@ -867,14 +861,15 @@ func (w *Workspace) cardStep() int {
867 return min(overviewCardStep, a.W/100, a.H/50)861 return min(overviewCardStep, a.W/100, a.H/50)
868 }862 }
869 863
870-// pile places the stash of w as cards in its slot along axis (see the864+// pile appends the stash of w to dst as cards in its slot along axis (see
871-// overview's stash comment). With entry at selected (at >= 0, on the865+// the overview's stash comment). With entry at selected (at >= 0, on the
872-// current row: dim false) the pile unfolds; else it is folded, the stash866+// current row: dim false) the pile unfolds; else it is folded: the stash
873-// selection in front, the next ones behind it, dimmed, the rest hidden.867+// selection in front, painted last, up to overviewCards-1 next ones
874-func (w *Workspace) pile(axis layoutAxis, slot Rect, dim bool, at int) []Placement {868+// (wrapping round) behind it, farthest first, dimmed; the rest hidden.
869+func (w *Workspace) pile(dst []Placement, axis layoutAxis, slot Rect, dim bool, at int) []Placement {
875 n := len(w.Stash)870 n := len(w.Stash)
876 if n == 0 {871 if n == 0 {
877- return nil872+ return dst
878 }873 }
879 u := w.overviewArea()874 u := w.overviewArea()
880 cw, ch := w.cardSize()875 cw, ch := w.cardSize()
@@ -897,16 +892,28 @@ func (w *Workspace) pile(axis layoutAxis, slot Rect, dim bool, at int) []Placeme
897 }892 }
898 folded := Rect{X: x0, Y: y0, W: cw, H: ch}893 folded := Rect{X: x0, Y: y0, W: cw, H: ch}
899 if at >= 0 && !dim {894 if at >= 0 && !dim {
900- return w.unfold(axis, folded, min(at, n-1))895+ return w.unfold(dst, axis, folded, min(at, n-1))
896+ }
897+ front, behind, step := min(max(w.stashAt, 0), n-1), min(n, overviewCards)-1, w.cardStep()
898+ card := func(i, d int) Placement {
899+ p := Placement{ID: w.Stash[i].ID, Rect: folded, Preview: overviewCardZoom, Card: true}
900+ p.Rect.X, p.Rect.Y = p.Rect.X+d*step, p.Rect.Y-d*step
901+ if d > 0 || dim {
902+ p.peeking(1)
903+ }
904+ return p
905+ }
906+ for d := behind; d >= 0; d-- {
907+ dst = append(dst, card((front+d)%n, d))
901 }908 }
902- cards := make([][]Placement, n)
903 for i := range w.Stash {909 for i := range w.Stash {
904- cards[i] = []Placement{{ID: w.Stash[i].ID, Rect: folded, Preview: overviewCardZoom, Card: true}}910+ // The cards neither in front nor behind it: hidden, so their
911+ // clients are not left showing.
912+ if (i-front+n)%n > behind {
913+ dst = append(dst, Placement{ID: w.Stash[i].ID, Rect: folded, Card: true, Hidden: true})
914+ }
905 }915 }
906- step := w.cardStep()916+ return dst
907- return fan(cards, min(max(w.stashAt, 0), n-1), min(n, overviewCards)-1, 0, true,
908- func(d int) (int, int) { return d * step, -d * step },
909- func(int) (int, int) { return 0, 0 }, dim, false)
910 }917 }
911 918
912 // unfold lays the stash of w out as a strip of every card along axis, from919 // unfold lays the stash of w out as a strip of every card along axis, from
@@ -914,8 +921,9 @@ func (w *Workspace) pile(axis layoutAxis, slot Rect, dim bool, at int) []Placeme
914 // a vertical one. The strip is centred on the pile and kept in the overview921 // a vertical one. The strip is centred on the pile and kept in the overview
915 // area. Longer than the area, it scrolls like a wide row: the selected card922 // area. Longer than the area, it scrolls like a wide row: the selected card
916 // at is centred, the strip stopping at the area's edges. The other cards923 // at is centred, the strip stopping at the area's edges. The other cards
917-// are dimmed; those out of the area are hidden.924+// are dimmed; those out of the area are hidden. The cards are appended to
918-func (w *Workspace) unfold(axis layoutAxis, folded Rect, at int) []Placement {925+// dst.
926+func (w *Workspace) unfold(dst []Placement, axis layoutAxis, folded Rect, at int) []Placement {
919 n := len(w.Stash)927 n := len(w.Stash)
920 u := w.overviewArea()928 u := w.overviewArea()
921 size, origin, lo, hi := folded.W, folded.X, u.X, u.X+u.W929 size, origin, lo, hi := folded.W, folded.X, u.X, u.X+u.W
@@ -931,7 +939,6 @@ func (w *Workspace) unfold(axis layoutAxis, folded Rect, at int) []Placement {
931 } else {939 } else {
932 start = min(max(start, lo), hi-total)940 start = min(max(start, lo), hi-total)
933 }941 }
934- out := make([]Placement, n)
935 for i := range w.Stash {942 for i := range w.Stash {
936 r := folded943 r := folded
937 if axis == verticalAxis {944 if axis == verticalAxis {
@@ -946,9 +953,9 @@ func (w *Workspace) unfold(axis layoutAxis, folded Rect, at int) []Placement {
946 if !r.Overlaps(u) {953 if !r.Overlaps(u) {
947 p.Hidden, p.Preview, p.Focused = true, 0, false954 p.Hidden, p.Preview, p.Focused = true, 0, false
948 }955 }
949- out[i] = p956+ dst = append(dst, p)
950 }957 }
951- return out958+ return dst
952 }959 }
953 960
954 // overviewZoom is the scale that fits every column of w in the usable961 // overviewZoom is the scale that fits every column of w in the usable
@@ -1138,7 +1145,8 @@ func (w *Workspace) previewRowTiles(axis layoutAxis, slot Rect, dim, lit, lanes
1138 }1145 }
1139 // Rows stay centred on the output like rows without a stash; in a1146 // Rows stay centred on the output like rows without a stash; in a
1140 // vertical overview the pile only pushes a row right when they would1147 // vertical overview the pile only pushes a row right when they would
1141- // overlap. A horizontal one draws the pile over the row.1148+ // overlap. A horizontal one gives the pile a band under the selected
1149+ // lane (pileBand).
1142 left := 01150 left := 0
1143 if axis == verticalAxis {1151 if axis == verticalAxis {
1144 left = w.pileWidth()1152 left = w.pileWidth()
@@ -1312,9 +1320,8 @@ func overviewIn(layout []Placement, x, y float64) WindowID {
1312 return 01320 return 0
1313 }1321 }
1314 1322
1315-// overviewHit is overviewIn with the stash piles first: a pile is drawn1323+// overviewHit is overviewIn with the stash piles first: the cards behind a
1316-// over its row (a horizontal overview), the cards behind its front one1324+// folded pile's front one peek out where tiles may be drawn over them.
1317-// included.
1318 func (m *Monitor) overviewHit(layout []Placement, x, y float64) WindowID {1325 func (m *Monitor) overviewHit(layout []Placement, x, y float64) WindowID {
1319 var piles []Placement1326 var piles []Placement
1320 for _, p := range layout {1327 for _, p := range layout {
M internal/core/overview_stack.go +1 -1
@@ -366,6 +366,6 @@ func (m *Monitor) stackRow(w *Workspace, axis layoutAxis, slot Rect, dim, lit bo
366366 }
367367
368368 // fan's behind side advances through the linear stack.
369- return fan(cards, at, 2, 2, false,
369+ return fan(cards, at, 2, 2,
370370 func(d int) (int, int) { return along(-d) }, along, dim, lit)
371371 }
@@ -366,6 +366,6 @@ func (m *Monitor) stackRow(w *Workspace, axis layoutAxis, slot Rect, dim, lit bo
366 }366 }
367 367
368 // fan's behind side advances through the linear stack.368 // fan's behind side advances through the linear stack.
369- return fan(cards, at, 2, 2, false,369+ return fan(cards, at, 2, 2,
370 func(d int) (int, int) { return along(-d) }, along, dim, lit)370 func(d int) (int, int) { return along(-d) }, along, dim, lit)
371 }371 }
M internal/core/overview_unfold_test.go +24 -0
@@ -159,6 +159,30 @@ func TestOverviewStashUnfoldScrolls(t *testing.T) {
159159 }
160160 }
161161
162+// The stash pile, folded or unfolded, is laid out straight into the
163+// overview layout every frame: it adds no allocation to the same layout
164+// without a stash.
165+func TestOverviewStashLayoutAllocations(t *testing.T) {
166+ allocs := func(m *Monitor) float64 {
167+ buf := m.layoutInto(nil)
168+ return testing.AllocsPerRun(50, func() { buf = m.layoutInto(buf) })
169+ }
170+ bare := cascadeOverview()
171+ bare.ToggleOverview()
172+ want := allocs(bare)
173+ for _, unfolded := range []bool{false, true} {
174+ m := cascadeOverview()
175+ withStash(m, 6)
176+ m.ToggleOverview()
177+ if unfolded {
178+ m.selectCard(m.Current(), 0)
179+ }
180+ if got := allocs(m); got > want {
181+ t.Errorf("unfolded %v: %v allocs per layout, %v without a stash", unfolded, got, want)
182+ }
183+ }
184+}
185+
162186 // A scroll overview unfolds its pile into a column on the left of the row;
163187 // up and down walk it, right goes back to the columns.
164188 func TestOverviewScrollStashUnfolds(t *testing.T) {
@@ -159,6 +159,30 @@ func TestOverviewStashUnfoldScrolls(t *testing.T) {
159 }159 }
160 }160 }
161 161
162+// The stash pile, folded or unfolded, is laid out straight into the
163+// overview layout every frame: it adds no allocation to the same layout
164+// without a stash.
165+func TestOverviewStashLayoutAllocations(t *testing.T) {
166+ allocs := func(m *Monitor) float64 {
167+ buf := m.layoutInto(nil)
168+ return testing.AllocsPerRun(50, func() { buf = m.layoutInto(buf) })
169+ }
170+ bare := cascadeOverview()
171+ bare.ToggleOverview()
172+ want := allocs(bare)
173+ for _, unfolded := range []bool{false, true} {
174+ m := cascadeOverview()
175+ withStash(m, 6)
176+ m.ToggleOverview()
177+ if unfolded {
178+ m.selectCard(m.Current(), 0)
179+ }
180+ if got := allocs(m); got > want {
181+ t.Errorf("unfolded %v: %v allocs per layout, %v without a stash", unfolded, got, want)
182+ }
183+ }
184+}
185+
162 // A scroll overview unfolds its pile into a column on the left of the row;186 // A scroll overview unfolds its pile into a column on the left of the row;
163 // up and down walk it, right goes back to the columns.187 // up and down walk it, right goes back to the columns.
164 func TestOverviewScrollStashUnfolds(t *testing.T) {188 func TestOverviewScrollStashUnfolds(t *testing.T) {

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