main 7c2aaeb

secret.go RawHistoryFolder

1package neferclient
2
3import "unicode/utf8"
4
5// maxSecretCapacity bounds [NewSecretBuffer].
6const maxSecretCapacity = 4096
7
8// SecretBuffer collects text typed while it is installed with
9// [Seat.SetSecret], for example a password. Its storage is allocated once at
10// full size, so it never reallocates and leaves no stale copies behind; the
11// text is never turned into a string by the library.
12//
13// Only the owner goroutine (the one calling [Conn.Dispatch]) may use it.
14type SecretBuffer struct {
15	buf    []byte // capacity*utf8.UTFMax bytes, fixed
16	n      int    // bytes in use
17	points int    // code points in use
18	max    int    // capacity in code points
19}
20
21// NewSecretBuffer returns a buffer holding up to capacity code points.
22// capacity is clamped to 1..4096.
23func NewSecretBuffer(capacity int) *SecretBuffer {
24	capacity = min(max(capacity, 1), maxSecretCapacity)
25	return &SecretBuffer{buf: make([]byte, capacity*utf8.UTFMax), max: capacity}
26}
27
28// Len returns the number of code points held.
29func (b *SecretBuffer) Len() int {
30	if b == nil {
31		return 0
32	}
33	return b.points
34}
35
36// Bytes returns the UTF-8 text held. The slice aliases the buffer: do not
37// retain it, convert it to a string or log it; it changes on the next edit.
38func (b *SecretBuffer) Bytes() []byte {
39	if b == nil {
40		return nil
41	}
42	return b.buf[:b.n:b.n]
43}
44
45// Wipe zeroes the whole storage and empties the buffer.
46func (b *SecretBuffer) Wipe() {
47	if b == nil {
48		return
49	}
50	clear(b.buf)
51	b.n, b.points = 0, 0
52}
53
54// appendText adds text, which must be valid UTF-8. It reports false and
55// changes nothing when the text does not fit.
56func (b *SecretBuffer) appendText(text []byte) bool {
57	cp := utf8.RuneCount(text)
58	if cp == 0 || b.points+cp > b.max {
59		return false
60	}
61	b.n += copy(b.buf[b.n:], text)
62	b.points += cp
63	return true
64}
65
66// backspace removes the last code point and wipes its bytes.
67func (b *SecretBuffer) backspace() bool {
68	if b.points == 0 {
69		return false
70	}
71	_, size := utf8.DecodeLastRune(b.buf[:b.n])
72	clear(b.buf[b.n-size : b.n])
73	b.n -= size
74	b.points--
75	return true
76}
77
78// classifySecretText reports whether text is printable input for a secret:
79// non-empty, valid UTF-8 and free of C0 controls and DEL.
80func classifySecretText(text []byte) bool {
81	if len(text) == 0 || !utf8.Valid(text) {
82		return false
83	}
84	for rest := text; len(rest) > 0; {
85		r, size := utf8.DecodeRune(rest)
86		if r < 0x20 || r == 0x7f {
87			return false
88		}
89		rest = rest[size:]
90	}
91	return true
92}

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 1

Tags 4

↑↓ moveenter openesc close