v0.7.0 36c429d

encode.go RawHistoryFolder

1package html
2
3import (
4	"fmt"
5	"strconv"
6	"unicode/utf8"
7)
8
9// appendUpdateJSON appends the canonical JSON encoding of update to dst. The
10// bytes are identical to encoding/json.Marshal(update) (including HTML
11// escaping of text), but it writes into one caller-sized buffer instead of
12// going through reflection and an intermediate copy.
13func appendUpdateJSON(dst []byte, update Update) ([]byte, error) {
14	dst = append(dst, `{"schemaVersion":`...)
15	dst = strconv.AppendUint(dst, uint64(update.SchemaVersion), 10)
16	dst = append(dst, `,"width":`...)
17	dst = strconv.AppendInt(dst, int64(update.Width), 10)
18	dst = append(dst, `,"height":`...)
19	dst = strconv.AppendInt(dst, int64(update.Height), 10)
20	dst = append(dst, `,"snapshot":`...)
21	dst = strconv.AppendBool(dst, update.Snapshot)
22	dst = append(dst, `,"rows":`...)
23	if update.Rows == nil {
24		dst = append(dst, "null"...)
25	} else {
26		dst = append(dst, '[')
27		for i := range update.Rows {
28			if i > 0 {
29				dst = append(dst, ',')
30			}
31			dst = appendRowJSON(dst, &update.Rows[i])
32		}
33		dst = append(dst, ']')
34	}
35	dst = append(dst, `,"styles":`...)
36	if update.Styles == nil {
37		dst = append(dst, "null"...)
38	} else {
39		dst = append(dst, '[')
40		for i := range update.Styles {
41			if i > 0 {
42				dst = append(dst, ',')
43			}
44			var err error
45			if dst, err = appendStyleJSON(dst, &update.Styles[i]); err != nil {
46				return nil, err
47			}
48		}
49		dst = append(dst, ']')
50	}
51	dst = append(dst, `,"cursor":{"row":`...)
52	dst = strconv.AppendInt(dst, int64(update.Cursor.Row), 10)
53	dst = append(dst, `,"column":`...)
54	dst = strconv.AppendInt(dst, int64(update.Cursor.Column), 10)
55	dst = append(dst, `,"visible":`...)
56	dst = strconv.AppendBool(dst, update.Cursor.Visible)
57	dst = append(dst, `,"style":`...)
58	dst = strconv.AppendUint(dst, uint64(update.Cursor.Style), 10)
59	dst = append(dst, `,"styleSet":`...)
60	dst = strconv.AppendBool(dst, update.Cursor.StyleSet)
61	dst = append(dst, "}}"...)
62	return dst, nil
63}
64
65func appendRowJSON(dst []byte, row *RowUpdate) []byte {
66	dst = append(dst, `{"row":`...)
67	dst = strconv.AppendInt(dst, int64(row.Row), 10)
68	dst = append(dst, `,"cells":`...)
69	if row.Cells == nil {
70		return append(dst, "null}"...)
71	}
72	dst = append(dst, '[')
73	for i := range row.Cells {
74		cell := &row.Cells[i]
75		if i > 0 {
76			dst = append(dst, ',')
77		}
78		dst = append(dst, `{"column":`...)
79		dst = strconv.AppendInt(dst, int64(cell.Column), 10)
80		dst = append(dst, `,"width":`...)
81		dst = strconv.AppendInt(dst, int64(cell.Width), 10)
82		dst = append(dst, `,"text":`...)
83		dst = appendJSONString(dst, cell.Text)
84		dst = append(dst, `,"style":`...)
85		dst = strconv.AppendInt(dst, int64(cell.Style), 10)
86		dst = append(dst, '}')
87	}
88	return append(dst, "]}"...)
89}
90
91func appendStyleJSON(dst []byte, style *Style) ([]byte, error) {
92	dst = append(dst, '{')
93	first := true
94	flag := func(name string, set bool) {
95		if !set {
96			return
97		}
98		if !first {
99			dst = append(dst, ',')
100		}
101		first = false
102		dst = append(dst, '"')
103		dst = append(dst, name...)
104		dst = append(dst, `":true`...)
105	}
106	flag("bold", style.Bold)
107	flag("italic", style.Italic)
108	flag("inverse", style.Inverse)
109	flag("dim", style.Dim)
110	flag("blink", style.Blink)
111	flag("strikethrough", style.Strikethrough)
112	flag("underline", style.Underline)
113	if style.UnderlineStyle != 0 {
114		if !first {
115			dst = append(dst, ',')
116		}
117		first = false
118		dst = append(dst, `"underlineStyle":`...)
119		dst = strconv.AppendUint(dst, uint64(style.UnderlineStyle), 10)
120	}
121	if !first {
122		dst = append(dst, ',')
123	}
124	var err error
125	dst = append(dst, `"foreground":`...)
126	if dst, err = appendColorJSON(dst, style.Foreground); err != nil {
127		return nil, err
128	}
129	dst = append(dst, `,"background":`...)
130	if dst, err = appendColorJSON(dst, style.Background); err != nil {
131		return nil, err
132	}
133	dst = append(dst, `,"underlineColor":`...)
134	if dst, err = appendColorJSON(dst, style.UnderlineColor); err != nil {
135		return nil, err
136	}
137	return append(dst, '}'), nil
138}
139
140// appendColorJSON mirrors Color.MarshalJSON.
141func appendColorJSON(dst []byte, color Color) ([]byte, error) {
142	switch color.Kind {
143	case ColorDefault:
144		return append(dst, `{"kind":0}`...), nil
145	case ColorIndexed:
146		dst = append(dst, `{"kind":1,"index":`...)
147		dst = strconv.AppendUint(dst, uint64(color.Index), 10)
148		return append(dst, '}'), nil
149	case ColorRGB:
150		dst = append(dst, `{"kind":2,"rgb":{"r":`...)
151		dst = strconv.AppendUint(dst, uint64(color.RGB.R), 10)
152		dst = append(dst, `,"g":`...)
153		dst = strconv.AppendUint(dst, uint64(color.RGB.G), 10)
154		dst = append(dst, `,"b":`...)
155		dst = strconv.AppendUint(dst, uint64(color.RGB.B), 10)
156		return append(dst, "}}"...), nil
157	default:
158		return nil, fmt.Errorf("html: invalid color kind %d", color.Kind)
159	}
160}
161
162const hexDigits = "0123456789abcdef"
163
164// appendJSONString appends s as a JSON string using encoding/json's default
165// escaping: quotes, backslashes, control characters, <, >, & and U+2028/2029
166// are escaped. Cell text is always valid UTF-8; an invalid byte, should one
167// appear, is replaced by U+FFFD so the output stays valid JSON.
168func appendJSONString(dst []byte, s string) []byte {
169	dst = append(dst, '"')
170	start := 0
171	for i := 0; i < len(s); {
172		b := s[i]
173		if b < utf8.RuneSelf {
174			if b >= 0x20 && b != '"' && b != '\\' && b != '<' && b != '>' && b != '&' {
175				i++
176				continue
177			}
178			dst = append(dst, s[start:i]...)
179			switch b {
180			case '"', '\\':
181				dst = append(dst, '\\', b)
182			case '\b':
183				dst = append(dst, '\\', 'b')
184			case '\f':
185				dst = append(dst, '\\', 'f')
186			case '\n':
187				dst = append(dst, '\\', 'n')
188			case '\r':
189				dst = append(dst, '\\', 'r')
190			case '\t':
191				dst = append(dst, '\\', 't')
192			default:
193				dst = append(dst, '\\', 'u', '0', '0', hexDigits[b>>4], hexDigits[b&0xF])
194			}
195			i++
196			start = i
197			continue
198		}
199		r, size := utf8.DecodeRuneInString(s[i:])
200		if r == utf8.RuneError && size == 1 {
201			dst = append(dst, s[start:i]...)
202			dst = append(dst, "\uFFFD"...)
203			i += size
204			start = i
205			continue
206		}
207		if r == '\u2028' || r == '\u2029' {
208			dst = append(dst, s[start:i]...)
209			dst = append(dst, '\\', 'u', '2', '0', '2', hexDigits[r&0xF])
210			i += size
211			start = i
212			continue
213		}
214		i += size
215	}
216	dst = append(dst, s[start:]...)
217	return append(dst, '"')
218}
219
220// estimateUpdateJSONSize returns a capacity hint close to the encoded size so
221// the output buffer normally grows zero times.
222func estimateUpdateJSONSize(update Update) int {
223	size := 256 + 256*len(update.Styles)
224	for i := range update.Rows {
225		size += 32 + 48*len(update.Rows[i].Cells)
226		for j := range update.Rows[i].Cells {
227			size += len(update.Rows[i].Cells[j].Text)
228		}
229	}
230	return size
231}

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