v0.1.0 7deb448

recovery_transcript.go RawHistoryFolder

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

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