v0.4.1 03674e0

recovery_transcript.go RawHistoryFolder

1package vt
2
3import renderer "github.com/bnema/vev-vt/core"
4
5// RecoveryTranscriptSnapshot is an owned, immutable capture of the viewport
6// rows that should be replayed after retained terminal history during recovery.
7type RecoveryTranscriptSnapshot struct {
8	segments  []recoveryTranscriptSegment
9	nextRowID RowID
10}
11
12type recoveryTranscriptSegment struct {
13	rows   [][]renderer.Cell
14	bounds []LineBound
15	rowIDs []RowID
16}
17
18// RecoveryTranscriptSnapshot captures the active primary viewport, or the
19// saved primary viewport followed by the active alternate viewport.
20func (s *Screen) RecoveryTranscriptSnapshot() RecoveryTranscriptSnapshot {
21	if s == nil {
22		return RecoveryTranscriptSnapshot{}
23	}
24
25	buffers := []*buffer{s.buffer}
26	if s.alternate != nil {
27		buffers = []*buffer{s.alternate.buffer, s.buffer}
28	}
29
30	nextRowID := s.nextRowID
31	if nextRowID < ^RowID(0) {
32		nextRowID++
33	}
34	snapshot := RecoveryTranscriptSnapshot{segments: make([]recoveryTranscriptSegment, 0, len(buffers)), nextRowID: nextRowID}
35	for _, b := range buffers {
36		segment := captureRecoveryTranscriptSegment(b)
37		if len(segment.rows) > 0 {
38			snapshot.segments = append(snapshot.segments, segment)
39		}
40	}
41	return snapshot
42}
43
44func captureRecoveryTranscriptSegment(b *buffer) recoveryTranscriptSegment {
45	if b == nil {
46		return recoveryTranscriptSegment{}
47	}
48
49	rowCount := b.frame.Height
50	for rowCount > 0 && recoveryTranscriptRowUntouched(b.frame.Row(rowCount-1), b.bound(rowCount-1)) {
51		rowCount--
52	}
53	if rowCount == 0 {
54		return recoveryTranscriptSegment{}
55	}
56
57	segment := recoveryTranscriptSegment{
58		rows:   make([][]renderer.Cell, rowCount),
59		bounds: append([]LineBound(nil), b.boundaries[:rowCount]...),
60		rowIDs: append([]RowID(nil), b.rowIDs[:rowCount]...),
61	}
62	cells := make([]renderer.Cell, rowCount*b.frame.Width)
63	for y := range rowCount {
64		start := y * b.frame.Width
65		end := start + b.frame.Width
66		segment.rows[y] = cells[start:end:end]
67		copy(segment.rows[y], b.frame.Row(y))
68	}
69	segment.bounds[rowCount-1].Soft = false
70	return segment
71}
72
73func recoveryTranscriptRowUntouched(row []renderer.Cell, bound LineBound) bool {
74	if bound.End != 0 || bound.Soft {
75		return false
76	}
77	blank := renderer.BlankCell()
78	for _, cell := range row {
79		if !cell.Equal(blank) {
80			return false
81		}
82	}
83	return true
84}
85
86// Marshal encodes the captured rows as canonical terminal history without
87// consulting or mutating live Screen history.
88func (snapshot RecoveryTranscriptSnapshot) Marshal() ([]byte, error) {
89	rows := 0
90	cells := 0
91	for _, segment := range snapshot.segments {
92		rows += len(segment.rows)
93		for _, row := range segment.rows {
94			cells += len(row)
95		}
96	}
97
98	view := HistoryView{rows: rows, cells: cells, nextRowID: snapshot.nextRowID}
99	for _, segment := range snapshot.segments {
100		for start := 0; start < len(segment.rows); {
101			space := maxHistoryChunkRows
102			if len(view.chunks) > 0 {
103				last := view.chunks[len(view.chunks)-1]
104				if len(last.rows) < maxHistoryChunkRows {
105					space = maxHistoryChunkRows - len(last.rows)
106					end := min(start+space, len(segment.rows))
107					last.rows = append(last.rows, segment.rows[start:end]...)
108					last.bounds = append(last.bounds, segment.bounds[start:end]...)
109					last.rowIDs = append(last.rowIDs, segment.rowIDs[start:end]...)
110					start = end
111					continue
112				}
113			}
114
115			end := min(start+space, len(segment.rows))
116			view.chunks = append(view.chunks, &HistoryChunk{
117				rows:   append([][]renderer.Cell(nil), segment.rows[start:end]...),
118				bounds: append([]LineBound(nil), segment.bounds[start:end]...),
119				rowIDs: append([]RowID(nil), segment.rowIDs[start:end]...),
120			})
121			start = end
122		}
123	}
124	return MarshalHistory(view)
125}

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