vevenv.go RawHistoryFolder

1package app
2
3import "strings"
4
5type vevEnv struct {
6	session string
7	tab     string
8	pane    string
9}
10
11// parseVEVEnv strictly parses the identity injected into pane PTYs. All three
12// unique fields are required and components use the daemon's uppercase %XX
13// escaping.
14func parseVEVEnv(value string) (vevEnv, bool) {
15	if value == "" {
16		return vevEnv{}, false
17	}
18	var out vevEnv
19	seen := make(map[string]bool, 3)
20	for part := range strings.SplitSeq(value, ",") {
21		key, encoded, found := strings.Cut(part, "=")
22		if !found || seen[key] {
23			return vevEnv{}, false
24		}
25		decoded, ok := unescapeVEVComponent(encoded)
26		if !ok || decoded == "" {
27			return vevEnv{}, false
28		}
29		switch key {
30		case "session":
31			out.session = decoded
32		case "tab":
33			out.tab = decoded
34		case "pane":
35			out.pane = decoded
36		default:
37			return vevEnv{}, false
38		}
39		seen[key] = true
40	}
41	if len(seen) != 3 || out.session == "" || out.tab == "" || out.pane == "" {
42		return vevEnv{}, false
43	}
44	return out, true
45}
46
47func unescapeVEVComponent(value string) (string, bool) {
48	var out strings.Builder
49	for i := 0; i < len(value); i++ {
50		if value[i] != '%' {
51			if !isUnescapedVEVByte(value[i]) {
52				return "", false
53			}
54			out.WriteByte(value[i])
55			continue
56		}
57		if i+2 >= len(value) {
58			return "", false
59		}
60		hi, highOK := upperHexValue(value[i+1])
61		lo, lowOK := upperHexValue(value[i+2])
62		if !highOK || !lowOK {
63			return "", false
64		}
65		out.WriteByte(hi<<4 | lo)
66		i += 2
67	}
68	return out.String(), true
69}
70
71func isUnescapedVEVByte(value byte) bool {
72	return value >= 'A' && value <= 'Z' ||
73		value >= 'a' && value <= 'z' ||
74		value >= '0' && value <= '9' ||
75		value == '.' || value == '_' || value == '-'
76}
77
78func upperHexValue(value byte) (byte, bool) {
79	switch {
80	case value >= '0' && value <= '9':
81		return value - '0', true
82	case value >= 'A' && value <= 'F':
83		return value - 'A' + 10, true
84	default:
85		return 0, false
86	}
87}

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 12

Tags 11

↑↓ moveenter openesc close