v0.2.0 80c61e3

remote_preview.go RawHistoryFolder

1package ports
2
3import (
4	"errors"
5	"math"
6	"unicode"
7	"unicode/utf8"
8
9	renderer "github.com/bnema/vev-vt"
10	"github.com/bnema/vev/internal/domain"
11)
12
13const (
14	RemotePreviewMaxWidth  = 256
15	RemotePreviewMaxHeight = 128
16	RemotePreviewMaxCells  = RemotePreviewMaxWidth * RemotePreviewMaxHeight
17	RemotePreviewMaxBytes  = 1 << 20
18)
19
20var (
21	ErrInvalidRemotePreviewRequest   = errors.New("ports: invalid remote preview request")
22	ErrInvalidRemotePreview          = errors.New("ports: invalid remote preview")
23	ErrRemotePreviewTooLarge         = errors.New("ports: remote preview exceeds size limit")
24	ErrRemotePreviewTimeout          = errors.New("ports: remote preview command timed out")
25	ErrRemotePreviewUnsupportedStyle = errors.New("ports: remote preview has unsupported style")
26)
27
28func ValidateRemotePreviewRequest(request RemotePreviewRequest) error {
29	if request.Version != RemotePreviewSchemaVersion || request.Target.Stopped || request.Target.Validate() != nil {
30		return ErrInvalidRemotePreviewRequest
31	}
32	// These fields are encoded with uint16 byte lengths. Reject an oversized
33	// nested route before putString truncates it on the wire.
34	if len(request.Target.Endpoint) > math.MaxUint16 ||
35		len(request.Target.DisplayOrigin) > math.MaxUint16 ||
36		len(request.Target.SessionName) > math.MaxUint16 ||
37		len(request.Target.LiveTabID) > math.MaxUint16 {
38		return ErrInvalidRemotePreviewRequest
39	}
40	if request.Width == 0 || request.Height == 0 || int(request.Width) > RemotePreviewMaxWidth || int(request.Height) > RemotePreviewMaxHeight {
41		return ErrInvalidRemotePreviewRequest
42	}
43	return nil
44}
45
46func ValidateRemotePreview(preview RemotePreview) error {
47	if preview.Version != RemotePreviewSchemaVersion || preview.Status > RemotePreviewTooLarge {
48		return ErrInvalidRemotePreview
49	}
50	if preview.Status != RemotePreviewOK {
51		if preview.Width != 0 || preview.Height != 0 || len(preview.Cells) != 0 {
52			return ErrInvalidRemotePreview
53		}
54		return nil
55	}
56	if preview.LifecycleID == (domain.SessionLifecycleID{}) || domain.ValidateTabStableID(preview.TabID) != nil || preview.Revision == 0 {
57		return ErrInvalidRemotePreview
58	}
59	if preview.Width == 0 || preview.Height == 0 || int(preview.Width) > RemotePreviewMaxWidth || int(preview.Height) > RemotePreviewMaxHeight {
60		return ErrInvalidRemotePreview
61	}
62	want := int(preview.Width) * int(preview.Height)
63	if want > RemotePreviewMaxCells || len(preview.Cells) != want {
64		return ErrRemotePreviewTooLarge
65	}
66	width := int(preview.Width)
67	for i, cell := range preview.Cells {
68		if !utf8.ValidRune(cell.Rune) || (cell.Rune != 0 && unicode.IsControl(cell.Rune)) || !validRemotePreviewStyle(cell.Style) {
69			return ErrInvalidRemotePreview
70		}
71		rowStart := (i / width) * width
72		rowEnd := rowStart + width
73		if cell.Continuation {
74			if cell.Rune != 0 || i == rowStart || preview.Cells[i-1].Continuation || renderer.RuneWidth(preview.Cells[i-1].Rune) != 2 {
75				return ErrInvalidRemotePreview
76			}
77		} else if renderer.RuneWidth(cell.Rune) == 2 && (i+1 >= rowEnd || !preview.Cells[i+1].Continuation) {
78			return ErrInvalidRemotePreview
79		}
80	}
81	return nil
82}
83
84func validRemotePreviewStyle(style renderer.Style) bool {
85	return style.Attrs&^(renderer.AttrDim|renderer.AttrUnderline|renderer.AttrBlink|renderer.AttrStrikethrough) == 0 &&
86		style.UnderlineStyle <= renderer.UnderlineDashed &&
87		style.Foreground >= math.MinInt16 && style.Foreground <= math.MaxInt16 &&
88		style.Background >= math.MinInt16 && style.Background <= math.MaxInt16 &&
89		style.UnderlineColor >= math.MinInt16 && style.UnderlineColor <= math.MaxInt16
90}
91
92func (p RemotePreview) FrameRows() [][]renderer.Cell {
93	if p.Width == 0 || p.Height == 0 || len(p.Cells) != int(p.Width)*int(p.Height) {
94		return nil
95	}
96	rows := make([][]renderer.Cell, p.Height)
97	width := int(p.Width)
98	for y := range rows {
99		rows[y] = append([]renderer.Cell(nil), p.Cells[y*width:(y+1)*width]...)
100	}
101	return rows
102}

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