v0.4.1 03674e0

benchmark_test.go RawHistoryFolder

1package ansi
2
3import "testing"
4
5func markBenchmarkFrame(f *Frame) {
6	for y := range f.Height {
7		for x := range f.Width {
8			f.Set(x, y, Cell{Rune: rune('A' + (y*f.Width+x)%26), Style: DefaultStyle()})
9		}
10	}
11}
12
13func setBenchmarkShadow(r *Renderer, frame Frame) {
14	committed := r.committedFrame()
15	replaceFrame(&committed, frame)
16	r.setCommittedFrame(committed)
17}
18
19func BenchmarkRendererFullFrameDraw(b *testing.B) {
20	frame := NewFrame(120, 40)
21	markBenchmarkFrame(&frame)
22	r := New(Capabilities{})
23	damage := []Damage{FullRedraw()}
24	b.ReportAllocs()
25	var outBytes int64
26	for b.Loop() {
27		r.Reset()
28		out, err := r.Draw(frame, damage)
29		if err != nil {
30			b.Fatal(err)
31		}
32		if len(out) == 0 {
33			b.Fatal("expected renderer output")
34		}
35		outBytes += int64(len(out))
36	}
37	b.ReportMetric(float64(outBytes)/float64(b.N), "outbytes/op")
38}
39
40func BenchmarkRendererFragmentedDamage(b *testing.B) {
41	const (
42		width  = 120
43		height = 40
44	)
45
46	baseline := NewFrame(width, height)
47	markBenchmarkFrame(&baseline)
48	frame := NewFrame(width, height)
49	markBenchmarkFrame(&frame)
50
51	// Create 256 overlapping or vertically adjacent text rectangles in reverse
52	// row/column order. Canonical damage planning must sort and merge these into
53	// stable per-row spans before emission.
54	damage := make([]Damage, 0, 256)
55	for row := 15; row >= 0; row-- {
56		for col := 15; col >= 0; col-- {
57			x, y := col*7, row*2
58			damage = append(damage, Damage{Kind: DamageText, X: x, Y: y, Width: 8, Height: 2})
59			for dy := range 2 {
60				for dx := range 8 {
61					frame.Set(x+dx, y+dy, Cell{Rune: 'z', Style: DefaultStyle()})
62				}
63			}
64		}
65	}
66
67	r := New(Capabilities{})
68	b.ReportAllocs()
69	var outBytes int64
70	for b.Loop() {
71		// Each iteration starts with the identical pre-update terminal shadow.
72		setBenchmarkShadow(r, baseline)
73		out, err := r.Draw(frame, damage)
74		if err != nil {
75			b.Fatal(err)
76		}
77		if len(out) == 0 {
78			b.Fatal("expected renderer output")
79		}
80		outBytes += int64(len(out))
81	}
82	b.ReportMetric(float64(outBytes)/float64(b.N), "outbytes/op")
83}
84
85func BenchmarkRendererIncrementalOneCell(b *testing.B) {
86	for _, tt := range []struct {
87		name          string
88		prepareCommit bool
89	}{
90		{name: "PrepareCommit", prepareCommit: true},
91		{name: "Draw"},
92	} {
93		b.Run(tt.name, func(b *testing.B) {
94			frame := NewFrame(120, 40)
95			markBenchmarkFrame(&frame)
96			r := New(Capabilities{})
97			if tt.prepareCommit {
98				initial, err := r.Prepare(frame, []Damage{FullRedraw()}, false)
99				if err != nil {
100					b.Fatal(err)
101				}
102				initial.Commit()
103			} else if _, err := r.Draw(frame, []Damage{FullRedraw()}); err != nil {
104				b.Fatal(err)
105			}
106			damage := []Damage{{Kind: DamageText, X: 60, Y: 20, Width: 1, Height: 1}}
107
108			b.ReportAllocs()
109			var outBytes int64
110			changed := false
111			for b.Loop() {
112				changed = !changed
113				cell := Cell{Rune: 'X', Style: DefaultStyle()}
114				if changed {
115					cell.Rune = 'Y'
116				}
117				frame.Set(60, 20, cell)
118				if tt.prepareCommit {
119					prepared, err := r.Prepare(frame, damage, false)
120					if err != nil {
121						b.Fatal(err)
122					}
123					if len(prepared.Bytes()) == 0 {
124						b.Fatal("expected renderer output")
125					}
126					outBytes += int64(len(prepared.Bytes()))
127					prepared.Commit()
128					continue
129				}
130				out, err := r.Draw(frame, damage)
131				if err != nil {
132					b.Fatal(err)
133				}
134				if len(out) == 0 {
135					b.Fatal("expected renderer output")
136				}
137				outBytes += int64(len(out))
138			}
139			b.ReportMetric(float64(outBytes)/float64(b.N), "outbytes/op")
140		})
141	}
142}
143
144func BenchmarkRendererIncrementalNoBytePrepareCommit(b *testing.B) {
145	frame := NewFrame(120, 40)
146	markBenchmarkFrame(&frame)
147	r := New(Capabilities{})
148	initial, err := r.Prepare(frame, []Damage{FullRedraw()}, false)
149	if err != nil {
150		b.Fatal(err)
151	}
152	initial.Commit()
153
154	b.ReportAllocs()
155	for b.Loop() {
156		prepared, err := r.Prepare(frame, nil, false)
157		if err != nil {
158			b.Fatal(err)
159		}
160		if len(prepared.Bytes()) != 0 {
161			b.Fatal("expected no output")
162		}
163		prepared.Commit()
164	}
165}
166
167func BenchmarkRendererBroadRegularDamage(b *testing.B) {
168	const (
169		width  = 120
170		height = 40
171	)
172	baseline := NewFrame(width, height)
173	markBenchmarkFrame(&baseline)
174	frame := baseline.Clone()
175	for y := range height {
176		frame.Set(0, y, Cell{Rune: 'z', Style: DefaultStyle()})
177	}
178	damage := make([]Damage, height)
179	for y := range height {
180		damage[y] = Damage{Kind: DamageText, X: 0, Y: y, Width: width, Height: 1}
181	}
182
183	r := New(Capabilities{})
184	b.ReportAllocs()
185	var outBytes int64
186	for b.Loop() {
187		setBenchmarkShadow(r, baseline)
188		out, err := r.Draw(frame, damage)
189		if err != nil {
190			b.Fatal(err)
191		}
192		if len(out) == 0 {
193			b.Fatal("expected renderer output")
194		}
195		outBytes += int64(len(out))
196	}
197	b.ReportMetric(float64(outBytes)/float64(b.N), "outbytes/op")
198}
199
200func BenchmarkRendererScrollFastPath(b *testing.B) {
201	frame := NewFrame(120, 40)
202	markBenchmarkFrame(&frame)
203	r := New(Capabilities{})
204	if _, err := r.Draw(frame, []Damage{FullRedraw()}); err != nil {
205		b.Fatal(err)
206	}
207
208	scrolled := NewFrame(120, 40)
209	copy(scrolled.Cells[0:39*120], frame.Cells[120:40*120])
210	for i := 39 * 120; i < 40*120; i++ {
211		scrolled.Cells[i] = BlankCell()
212	}
213	for x, r := range []rune("new output line") {
214		scrolled.Set(x, 39, Cell{Rune: r, Style: DefaultStyle()})
215	}
216	damage := []Damage{
217		{Kind: DamageScrollUp, X: 0, Y: 0, Width: 120, Height: 40, Count: 1},
218		{Kind: DamageText, X: 0, Y: 39, Width: 120, Height: 1, Count: 1},
219	}
220
221	b.ReportAllocs()
222	var outBytes int64
223	for b.Loop() {
224		setBenchmarkShadow(r, frame)
225		out, err := r.Draw(scrolled, damage)
226		if err != nil {
227			b.Fatal(err)
228		}
229		if len(out) == 0 {
230			b.Fatal("expected renderer output")
231		}
232		outBytes += int64(len(out))
233	}
234	b.ReportMetric(float64(outBytes)/float64(b.N), "outbytes/op")
235}

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 2

Tags 13

↑↓ moveenter openesc close