v0.7.1 ff50ee4

frame_copy.go RawHistoryFolder

1package core
2
3import "maps"
4
5// CopyFrom makes f an independent structural copy of src, like Replace, but
6// reuses f's existing page storage (cells, row descriptors, style and payload
7// tables and dictionaries) when f already owns a page. It therefore performs no
8// allocation once f's page has grown to src's size. A page more than four times
9// larger than src is replaced by a clone, so shrinking a terminal releases the
10// old storage.
11//
12// Page-local style and payload IDs and physical row rotation are copied
13// verbatim, exactly as Clone does. When the page is reused, every Frame value
14// sharing f's page observes the overwrite. An invalid or empty source leaves f
15// unchanged. Copying a frame onto itself only normalizes the dimensions.
16func (f *Frame) CopyFrom(src Frame) {
17	if f == nil {
18		return
19	}
20	if err := src.validateStorage(); err != nil || src.Width <= 0 || src.Height <= 0 {
21		return
22	}
23	if f.page == src.page {
24		f.Width, f.Height = src.Width, src.Height
25		return
26	}
27	if f.page == nil || cap(f.page.cells) > 4*len(src.page.cells) {
28		*f = src.Clone()
29		return
30	}
31	f.Width, f.Height = src.Width, src.Height
32	dst, from := f.page, src.page
33	dst.cells = append(dst.cells[:0], from.cells...)
34	dst.rows = append(dst.rows[:0], from.rows...)
35	dst.styles = append(dst.styles[:0], from.styles...)
36	dst.freeStyles = append(dst.freeStyles[:0], from.freeStyles...)
37	dst.styleCount = from.styleCount
38	// The cache refers to a slot in the style table copied above, so copying it
39	// keeps it valid. Leaving the destination's old cache would point at a slot
40	// that may now hold a different style.
41	dst.styleCache, dst.styleCacheID, dst.styleCacheOK = from.styleCache, from.styleCacheID, from.styleCacheOK
42	if dst.styleIndex == nil {
43		dst.styleIndex = make(map[Style]uint32, len(from.styleIndex))
44	} else {
45		clear(dst.styleIndex)
46	}
47	maps.Copy(dst.styleIndex, from.styleIndex)
48	// Payloads hold strings: clear the reused tail so stale graphemes and
49	// hyperlinks are not kept alive by the backing array.
50	oldPayloads := len(dst.payloads)
51	dst.payloads = append(dst.payloads[:0], from.payloads...)
52	if oldPayloads > len(dst.payloads) {
53		clear(dst.payloads[len(dst.payloads):oldPayloads])
54	}
55	dst.freePayloads = append(dst.freePayloads[:0], from.freePayloads...)
56	dst.payloadBytes = from.payloadBytes
57	if dst.payloadIndex == nil && len(from.payloadIndex) > 0 {
58		dst.payloadIndex = make(map[CellPayload]uint32, len(from.payloadIndex))
59	} else {
60		clear(dst.payloadIndex)
61	}
62	maps.Copy(dst.payloadIndex, from.payloadIndex)
63}
64
65// RowsEqualAt reports whether logical row ya of a and logical row yb of b hold
66// semantically equal cells (the same comparison as Cell.Equal for every
67// column). It compares compact stored cells directly and resolves page-local
68// style and payload IDs only when they are not both the default, so it does
69// not allocate and does not require the two frames to share ID tables. Frames
70// of different width, an out-of-range row, or inconsistent storage compare
71// unequal.
72func RowsEqualAt(a Frame, ya int, b Frame, yb int) bool {
73	if a.Width != b.Width || a.Width <= 0 || a.page == nil || b.page == nil ||
74		ya < 0 || ya >= a.Height || yb < 0 || yb >= b.Height ||
75		ya >= len(a.page.rows) || yb >= len(b.page.rows) {
76		return false
77	}
78	width := a.Width
79	offA, offB := int(a.page.rows[ya]), int(b.page.rows[yb])
80	if offA+width > len(a.page.cells) || offB+width > len(b.page.cells) {
81		return false
82	}
83	rowA := a.page.cells[offA : offA+width]
84	rowB := b.page.cells[offB : offB+width]
85
86	// Remember the last style ID pair that compared equal so runs of one style
87	// cost a single table lookup.
88	var lastStyleA, lastStyleB uint32
89	for x := range rowA {
90		ca, cb := rowA[x], rowB[x]
91		if ca.rune != cb.rune || ca.flags != cb.flags {
92			return false
93		}
94		if ca.styleID != 0 || cb.styleID != 0 {
95			if ca.styleID != lastStyleA || cb.styleID != lastStyleB {
96				if !a.storedStylesEqual(b, ca.styleID, cb.styleID) {
97					return false
98				}
99				lastStyleA, lastStyleB = ca.styleID, cb.styleID
100			}
101		}
102		if ca.payloadID != 0 || cb.payloadID != 0 {
103			if ca.payloadID == 0 || cb.payloadID == 0 ||
104				int(ca.payloadID) > len(a.page.payloads) || int(cb.payloadID) > len(b.page.payloads) ||
105				a.payload(ca.payloadID) != b.payload(cb.payloadID) {
106				return false
107			}
108		}
109	}
110	return true
111}
112
113func (f Frame) storedStylesEqual(other Frame, id, otherID uint32) bool {
114	if int(id) >= len(f.page.styles) || int(otherID) >= len(other.page.styles) {
115		return false
116	}
117	sa, sb := f.page.styles[id].style, other.page.styles[otherID].style
118	return sa == sb || sa.Equal(sb)
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 13

↑↓ moveenter openesc close