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}