v0.6.0 b250c5e

payload.go RawHistoryFolder

1package core
2
3import (
4	"errors"
5	"fmt"
6	"strings"
7	"unicode/utf8"
8)
9
10const (
11	MaxGraphemeBytes  = 128
12	MaxHyperlinkBytes = 4096
13	// PayloadRecordLogicalBytes accounts for two fixed-width string descriptors.
14	PayloadRecordLogicalBytes uint64 = 16
15)
16
17var ErrInvalidCellPayload = errors.New("invalid cell payload")
18
19// CellPayload is an immutable semantic exceptional value. Its zero value is
20// empty. Storage support does not imply parsing support for additional terminal
21// protocols; a payload never exposes a page-local handle.
22type CellPayload struct {
23	grapheme  string
24	hyperlink string
25}
26
27// NewCellPayload validates bounded UTF-8 strings without terminal controls.
28// It copies nonempty strings so a small value cannot retain an oversized input.
29func NewCellPayload(grapheme, hyperlink string) (CellPayload, error) {
30	if !validPayloadString(grapheme, MaxGraphemeBytes) || !validPayloadString(hyperlink, MaxHyperlinkBytes) {
31		return CellPayload{}, ErrInvalidCellPayload
32	}
33	return CellPayload{grapheme: strings.Clone(grapheme), hyperlink: strings.Clone(hyperlink)}, nil
34}
35
36func validPayloadString(value string, limit int) bool {
37	if len(value) > limit || !utf8.ValidString(value) {
38		return false
39	}
40	for _, r := range value {
41		if r < 0x20 || r >= 0x7f && r <= 0x9f {
42			return false
43		}
44	}
45	return true
46}
47
48func (p CellPayload) Grapheme() string  { return p.grapheme }
49func (p CellPayload) Hyperlink() string { return p.hyperlink }
50func (p CellPayload) Empty() bool       { return p == CellPayload{} }
51func (p CellPayload) LogicalBytes() uint64 {
52	if p.Empty() {
53		return 0
54	}
55	return PayloadRecordLogicalBytes + uint64(len(p.grapheme)) + uint64(len(p.hyperlink))
56}
57
58type payloadSlot struct {
59	value CellPayload
60	refs  uint32
61}
62
63// internPayload returns a page-local handle and acquires one reference. Slot
64// zero is the implicit empty payload and requires no dictionary allocation.
65func (f Frame) internPayload(value CellPayload) uint32 {
66	if value.Empty() {
67		return 0
68	}
69	p := f.page
70	if id, ok := p.payloadIndex[value]; ok {
71		p.payloads[id-1].refs++
72		return id
73	}
74	if p.payloadIndex == nil {
75		p.payloadIndex = make(map[CellPayload]uint32)
76	}
77	var id uint32
78	if n := len(p.freePayloads); n > 0 {
79		id = p.freePayloads[n-1]
80		p.freePayloads = p.freePayloads[:n-1]
81		p.payloads[id-1] = payloadSlot{value: value, refs: 1}
82	} else {
83		p.payloads = append(p.payloads, payloadSlot{value: value, refs: 1})
84		id = uint32(len(p.payloads))
85	}
86	p.payloadIndex[value] = id
87	p.payloadBytes += value.LogicalBytes()
88	return id
89}
90
91func (f Frame) releasePayload(id uint32) {
92	if id == 0 {
93		return
94	}
95	p := f.page
96	slot := &p.payloads[id-1]
97	slot.refs--
98	if slot.refs != 0 {
99		return
100	}
101	delete(p.payloadIndex, slot.value)
102	p.payloadBytes -= slot.value.LogicalBytes()
103	*slot = payloadSlot{}
104	p.freePayloads = append(p.freePayloads, id)
105}
106
107func (f Frame) payload(id uint32) CellPayload {
108	if id == 0 {
109		return CellPayload{}
110	}
111	return f.page.payloads[id-1].value
112}
113
114func (f Frame) checkPayloadInvariants() error {
115	p := f.page
116	refs := make([]uint32, len(p.payloads))
117	for _, cell := range p.cells {
118		id := cell.payloadID
119		if id == 0 {
120			continue
121		}
122		if uint64(id) > uint64(len(refs)) {
123			return fmt.Errorf("unresolved payload ID %d", id)
124		}
125		refs[id-1]++
126	}
127	free := make([]bool, len(p.payloads))
128	for _, id := range p.freePayloads {
129		if id == 0 || uint64(id) > uint64(len(free)) || free[id-1] {
130			return fmt.Errorf("invalid free payload ID %d", id)
131		}
132		free[id-1] = true
133	}
134	var total uint64
135	used := 0
136	for i, slot := range p.payloads {
137		if slot.refs != refs[i] {
138			return fmt.Errorf("payload %d reference mismatch", i+1)
139		}
140		if slot.refs == 0 {
141			if !free[i] || !slot.value.Empty() {
142				return fmt.Errorf("unreclaimed payload %d", i+1)
143			}
144			continue
145		}
146		if free[i] || slot.value.Empty() || p.payloadIndex[slot.value] != uint32(i+1) {
147			return fmt.Errorf("invalid payload dictionary entry %d", i+1)
148		}
149		if !validPayloadString(slot.value.grapheme, MaxGraphemeBytes) || !validPayloadString(slot.value.hyperlink, MaxHyperlinkBytes) {
150			return ErrInvalidCellPayload
151		}
152		total += slot.value.LogicalBytes()
153		used++
154	}
155	if total != p.payloadBytes || used != len(p.payloadIndex) {
156		return fmt.Errorf("payload accounting mismatch")
157	}
158	return nil
159}

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