main 7a3b92c

names_test.go RawHistoryFolder

1package glyph
2
3import (
4	"encoding/binary"
5	"os"
6	"strings"
7	"testing"
8)
9
10// nerdFont returns the path of an installed Nerd Font, or skips the test.
11func nerdFont(t *testing.T) string {
12	t.Helper()
13	paths, err := FindFonts("JetBrainsMono Nerd Font Mono")
14	if err != nil || !strings.Contains(strings.ToLower(paths[0]), "nerd") {
15		t.Skip("no Nerd Font installed")
16	}
17	return paths[0]
18}
19
20func TestNamesLookup(t *testing.T) {
21	n, err := LoadNames(nerdFont(t))
22	if err != nil {
23		t.Fatal(err)
24	}
25	defer n.Close()
26	if n.Len() < 1000 {
27		t.Fatalf("only %d names indexed", n.Len())
28	}
29	for name, want := range map[string]rune{"fa-steam": 0xF1B6, "md-steam": 0xF04D3} {
30		if r, ok := n.Lookup(name); !ok || r != want {
31			t.Errorf("Lookup(%q) = %U, %v; want %U", name, r, ok, want)
32		}
33	}
34	if _, ok := n.Lookup("no-such-glyph"); ok {
35		t.Error("an unknown name was found")
36	}
37	if allocs := testing.AllocsPerRun(100, func() { n.Lookup("fa-steam") }); allocs != 0 {
38		t.Errorf("Lookup allocates %v times", allocs)
39	}
40}
41
42func TestNilNamesLookup(t *testing.T) {
43	var n *Names
44	if _, ok := n.Lookup("fa-steam"); ok {
45		t.Error("a nil index found a name")
46	}
47}
48
49// postFont builds a font file holding only a "post" table of the given
50// version, with one glyph index per entry of indices and the custom names
51// after them.
52func postFont(version uint32, indices []uint16, names ...string) []byte {
53	post := make([]byte, 34+2*len(indices))
54	binary.BigEndian.PutUint32(post, version)
55	binary.BigEndian.PutUint16(post[32:], uint16(len(indices)))
56	for i, k := range indices {
57		binary.BigEndian.PutUint16(post[34+2*i:], k)
58	}
59	for _, s := range names {
60		post = append(post, byte(len(s)))
61		post = append(post, s...)
62	}
63	b := make([]byte, 12+16, 12+16+len(post))
64	b[5] = 1 // one table
65	copy(b[12:], "post")
66	binary.BigEndian.PutUint32(b[12+8:], 28)
67	binary.BigEndian.PutUint32(b[12+12:], uint32(len(post)))
68	return append(b, post...)
69}
70
71func TestPostNames(t *testing.T) {
72	// Glyph 0 has a standard name, 1 and 2 custom ones, 3 a custom index
73	// past the names.
74	data := postFont(0x00020000, []uint16{3, 259, 258, 300}, "first", "second")
75	spans := postNames(data)
76	if len(spans) != 4 {
77		t.Fatalf("%d spans, want 4", len(spans))
78	}
79	name := func(g int) string { return string(data[spans[g].off : spans[g].off+spans[g].n]) }
80	if spans[0].n != 0 || spans[3].n != 0 {
81		t.Errorf("standard or missing names gave spans: %v", spans)
82	}
83	if name(1) != "second" || name(2) != "first" {
84		t.Errorf("names = %q, %q", name(1), name(2))
85	}
86}
87
88func TestPostNamesVersion3(t *testing.T) {
89	// A version 3 table stores no names: nothing to index, no error.
90	if spans := postNames(postFont(0x00030000, []uint16{259}, "x")); spans != nil {
91		t.Fatalf("got %d spans", len(spans))
92	}
93}
94
95func TestPostNamesDamagedFont(t *testing.T) {
96	// Truncated or garbage files give no names and never panic.
97	var good []byte
98	if paths, err := FindFonts("monospace"); err == nil {
99		good, _ = os.ReadFile(paths[0])
100	}
101	inputs := [][]byte{nil, {0, 1, 0, 0}, make([]byte, 64)}
102	for _, cut := range []int{12, 100, 1000, len(good) / 2} {
103		if cut <= len(good) {
104			inputs = append(inputs, good[:cut])
105		}
106	}
107	for _, b := range inputs {
108		_ = postNames(b)
109	}
110	// A "post" table that claims more glyphs than it holds.
111	b := postFont(0x00020000, nil)
112	binary.BigEndian.PutUint16(b[28+32:], 0xff)
113	if spans := postNames(b); spans != nil {
114		t.Fatalf("got %d spans from a bad table", len(spans))
115	}
116}
117
118func FuzzPostNames(f *testing.F) {
119	f.Add(postFont(0x00020000, []uint16{3, 259, 258, 300}, "first", "second"))
120	f.Add(postFont(0x00030000, nil))
121	f.Add([]byte{0, 1, 0, 0})
122	f.Fuzz(func(t *testing.T, data []byte) {
123		for g, s := range postNames(data) {
124			if uint64(s.off)+uint64(s.n) > uint64(len(data)) {
125				t.Fatalf("glyph %d: span %v past %d bytes", g, s, len(data))
126			}
127		}
128	})
129}

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 5

Tags 4

↑↓ moveenter openesc close