main 3d41bc5

hud.go RawHistoryFolder

1package indicator
2
3import (
4	"strconv"
5	"time"
6	"unicode"
7	"unicode/utf8"
8)
9
10// prefix starts the recording label; the elapsed time follows it.
11const prefix = "● REC "
12
13// maxLabelRunes bounds the optional target label so the line fits beside the
14// Stop button: 228 px of 13 px monospace is about 29 cells, and "● REC h:mm:ss · "
15// takes 16.
16const maxLabelRunes = 12
17
18// suffixFor returns " · label" with the label cut to maxLabelRunes (ending in
19// an ellipsis when cut), or "" for an empty label. Anything that is not a
20// printable character is dropped, so a workspace name cannot break the single
21// line: controls, unassigned and private-use code points, the line and
22// paragraph separators U+2028 and U+2029, and the bidirectional controls
23// (Unicode Bidi_Control) that could reorder the timer beside it.
24func suffixFor(label string) string {
25	var r [maxLabelRunes + 1]rune
26	n := 0
27	for _, c := range label {
28		if keepRune(c) {
29			r[n] = c
30			n++
31			if n > maxLabelRunes {
32				r[maxLabelRunes-1] = '…'
33				n = maxLabelRunes
34				break
35			}
36		}
37	}
38	if n == 0 {
39		return ""
40	}
41	// Encode prefix and label together: a long rune-to-string conversion
42	// followed by concatenation would allocate an intermediate string too.
43	var buf [len(" · ") + maxLabelRunes*utf8.UTFMax]byte
44	b := buf[:copy(buf[:], " · ")]
45	for _, c := range r[:n] {
46		b = utf8.AppendRune(b, c)
47	}
48	return string(b)
49}
50
51// keepRune reports whether c may appear in the label.
52func keepRune(c rune) bool {
53	if c == 0x2028 || c == 0x2029 || unicode.Is(unicode.Bidi_Control, c) {
54		return false
55	}
56	// IsPrint admits only letters, marks, numbers, punctuation, symbols and the
57	// ASCII space; other spaces, controls and format characters are dropped.
58	return unicode.IsPrint(c)
59}
60
61// hud is the indicator model. The view and every write run on the layerui
62// owner loop, so nothing is locked. The elapsed time is computed from the
63// monotonic clock reading the view passes to refresh, never from a counter
64// another goroutine advances. start carries a monotonic reading (time.Now), so
65// wall-clock steps do not move the timer.
66type hud struct {
67	start time.Time
68	stop  chan<- struct{}
69
70	suffix string // fixed for the session, built once
71	secs   int64  // seconds shown in text; -1 before the first view
72	text   string
73	buf    []byte
74}
75
76func newHUD(start time.Time, stop chan<- struct{}, label string) *hud {
77	return &hud{start: start, stop: stop, suffix: suffixFor(label), secs: -1}
78}
79
80// refresh returns the label for the reading now, rebuilding it only when the
81// displayed second changed. A reading before start shows zero.
82func (h *hud) refresh(now time.Time) string {
83	s := int64(now.Sub(h.start) / time.Second)
84	if s < 0 {
85		s = 0
86	}
87	if s == h.secs {
88		return h.text
89	}
90	h.secs = s
91	h.buf = append(appendElapsed(append(h.buf[:0], prefix...), s), h.suffix...)
92	h.text = string(h.buf)
93	return h.text
94}
95
96// requestStop notifies the owner without blocking. A full channel already
97// holds a pending stop request.
98func (h *hud) requestStop() {
99	select {
100	case h.stop <- struct{}{}:
101	default:
102	}
103}
104
105// appendElapsed appends mm:ss, or h:mm:ss from one hour.
106func appendElapsed(b []byte, secs int64) []byte {
107	h, m, s := secs/3600, secs/60%60, secs%60
108	if h > 0 {
109		b = strconv.AppendInt(b, h, 10)
110		b = append(b, ':')
111	}
112	b = appendTwo(b, m)
113	b = append(b, ':')
114	return appendTwo(b, s)
115}
116
117func appendTwo(b []byte, n int64) []byte {
118	return append(b, byte('0'+n/10), byte('0'+n%10))
119}

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 9

↑↓ moveenter openesc close