detached 04334a4

test(core): benchmark layout, snap points and border resize motion

commit
04334a42120ff5ea116f50b3ab0fbb44b15892db
author
brice · · 2026-10-21 08:04 UTC
committer
brice · · 2026-10-21 08:07 UTC
parent
fac869b
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +116 -0
A internal/core/layout_bench_test.go +116 -0
@@ -0,0 +1,116 @@
1+package core
2+
3+import (
4+ "context"
5+ "fmt"
6+ "testing"
7+
8+ "github.com/bnema/neferwl/internal/ports"
9+)
10+
11+func benchLane(n int, stamped bool) *Workspace {
12+ w := spanWorkspace(OverflowScroll, n)
13+ if stamped {
14+ stampSpans(w, 0, n-1)
15+ }
16+ w.Focus = n / 2
17+ return w
18+}
19+
20+func benchLanes(b *testing.B, f func(b *testing.B, w *Workspace)) {
21+ for _, n := range []int{16, 64} {
22+ for _, stamped := range []bool{false, true} {
23+ b.Run(fmt.Sprintf("cols=%d/stamped=%t", n, stamped), func(b *testing.B) {
24+ w := benchLane(n, stamped)
25+ b.ReportAllocs()
26+ b.ResetTimer()
27+ f(b, w)
28+ })
29+ }
30+ }
31+}
32+
33+func BenchmarkSnapPoints(b *testing.B) {
34+ benchLanes(b, func(b *testing.B, w *Workspace) {
35+ for b.Loop() {
36+ _ = w.snapPoints()
37+ }
38+ })
39+}
40+
41+func BenchmarkLayout(b *testing.B) {
42+ benchLanes(b, func(b *testing.B, w *Workspace) {
43+ for b.Loop() {
44+ _ = w.Layout()
45+ }
46+ })
47+}
48+
49+func BenchmarkColumnRects(b *testing.B) {
50+ benchLanes(b, func(b *testing.B, w *Workspace) {
51+ var buf []Rect
52+ for b.Loop() {
53+ buf = w.columnRectsInto(buf, false)
54+ }
55+ })
56+}
57+
58+func BenchmarkScroll(b *testing.B) {
59+ benchLanes(b, func(b *testing.B, w *Workspace) {
60+ for b.Loop() {
61+ w.scroll()
62+ }
63+ })
64+}
65+
66+// BenchmarkBorderMotion is one pointer motion of a border resize with a
67+// publish; "same" repeats a position, so nothing changes after the first.
68+func BenchmarkBorderMotion(b *testing.B) {
69+ for _, mode := range []string{"moving", "same"} {
70+ b.Run(mode, func(b *testing.B) {
71+ var cfg ports.Config
72+ cfg.Keyboard.CmdKey = "super"
73+ cfg.Layout.MaxColumns = 3
74+ scenes, cmds := make(chan []ports.Scene, 1), make(chan ports.ClientCommand, 1024)
75+ c, err := New(cfg, Channels{Scenes: scenes, Commands: cmds}, Options{})
76+ if err != nil {
77+ b.Fatal(err)
78+ }
79+ c.addScreen(ports.OutputInfo{Name: "A", Width: 1920, Height: 1080})
80+ sc := c.cur()
81+ w := sc.mon.Current()
82+ w.Overflow = OverflowScroll
83+ for id := WindowID(1); id <= 12; id++ {
84+ w.AddWindow(id)
85+ }
86+ sc.arrange()
87+ ctx := context.Background()
88+ if err := c.publish(ctx); err != nil {
89+ b.Fatal(err)
90+ }
91+ rs := w.columnRects()
92+ g := newBorderResize(c, sc, w, border{kind: borderColumn, col: 0}, btnRight)
93+ x := float64(rs[0].X + rs[0].W)
94+ c.cursorY = 100
95+ n := 0
96+ b.ReportAllocs()
97+ b.ResetTimer()
98+ for b.Loop() {
99+ c.cursorX = x
100+ if mode == "moving" {
101+ c.cursorX += float64(n%2*8) + 8
102+ n++
103+ }
104+ for len(scenes) > 0 {
105+ <-scenes
106+ }
107+ for len(cmds) > 0 {
108+ <-cmds
109+ }
110+ if err := g.motion(ctx, 0); err != nil {
111+ b.Fatal(err)
112+ }
113+ }
114+ })
115+ }
116+}
@@ -0,0 +1,116 @@
1+package core
2+
3+import (
4+ "context"
5+ "fmt"
6+ "testing"
7+
8+ "github.com/bnema/neferwl/internal/ports"
9+)
10+
11+func benchLane(n int, stamped bool) *Workspace {
12+ w := spanWorkspace(OverflowScroll, n)
13+ if stamped {
14+ stampSpans(w, 0, n-1)
15+ }
16+ w.Focus = n / 2
17+ return w
18+}
19+
20+func benchLanes(b *testing.B, f func(b *testing.B, w *Workspace)) {
21+ for _, n := range []int{16, 64} {
22+ for _, stamped := range []bool{false, true} {
23+ b.Run(fmt.Sprintf("cols=%d/stamped=%t", n, stamped), func(b *testing.B) {
24+ w := benchLane(n, stamped)
25+ b.ReportAllocs()
26+ b.ResetTimer()
27+ f(b, w)
28+ })
29+ }
30+ }
31+}
32+
33+func BenchmarkSnapPoints(b *testing.B) {
34+ benchLanes(b, func(b *testing.B, w *Workspace) {
35+ for b.Loop() {
36+ _ = w.snapPoints()
37+ }
38+ })
39+}
40+
41+func BenchmarkLayout(b *testing.B) {
42+ benchLanes(b, func(b *testing.B, w *Workspace) {
43+ for b.Loop() {
44+ _ = w.Layout()
45+ }
46+ })
47+}
48+
49+func BenchmarkColumnRects(b *testing.B) {
50+ benchLanes(b, func(b *testing.B, w *Workspace) {
51+ var buf []Rect
52+ for b.Loop() {
53+ buf = w.columnRectsInto(buf, false)
54+ }
55+ })
56+}
57+
58+func BenchmarkScroll(b *testing.B) {
59+ benchLanes(b, func(b *testing.B, w *Workspace) {
60+ for b.Loop() {
61+ w.scroll()
62+ }
63+ })
64+}
65+
66+// BenchmarkBorderMotion is one pointer motion of a border resize with a
67+// publish; "same" repeats a position, so nothing changes after the first.
68+func BenchmarkBorderMotion(b *testing.B) {
69+ for _, mode := range []string{"moving", "same"} {
70+ b.Run(mode, func(b *testing.B) {
71+ var cfg ports.Config
72+ cfg.Keyboard.CmdKey = "super"
73+ cfg.Layout.MaxColumns = 3
74+ scenes, cmds := make(chan []ports.Scene, 1), make(chan ports.ClientCommand, 1024)
75+ c, err := New(cfg, Channels{Scenes: scenes, Commands: cmds}, Options{})
76+ if err != nil {
77+ b.Fatal(err)
78+ }
79+ c.addScreen(ports.OutputInfo{Name: "A", Width: 1920, Height: 1080})
80+ sc := c.cur()
81+ w := sc.mon.Current()
82+ w.Overflow = OverflowScroll
83+ for id := WindowID(1); id <= 12; id++ {
84+ w.AddWindow(id)
85+ }
86+ sc.arrange()
87+ ctx := context.Background()
88+ if err := c.publish(ctx); err != nil {
89+ b.Fatal(err)
90+ }
91+ rs := w.columnRects()
92+ g := newBorderResize(c, sc, w, border{kind: borderColumn, col: 0}, btnRight)
93+ x := float64(rs[0].X + rs[0].W)
94+ c.cursorY = 100
95+ n := 0
96+ b.ReportAllocs()
97+ b.ResetTimer()
98+ for b.Loop() {
99+ c.cursorX = x
100+ if mode == "moving" {
101+ c.cursorX += float64(n%2*8) + 8
102+ n++
103+ }
104+ for len(scenes) > 0 {
105+ <-scenes
106+ }
107+ for len(cmds) > 0 {
108+ <-cmds
109+ }
110+ if err := g.motion(ctx, 0); err != nil {
111+ b.Fatal(err)
112+ }
113+ }
114+ })
115+ }
116+}

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