okami.mom/ultraviolet

git clone https://git.nkp.pet/okami.mom/ultraviolet

Kieran Klukasrefactor(renderer): stop keeping a touch list nobody reads13b333e

main
5.7 KiB229 linesraw
1package uv
2
3import (
4	"strings"
5
6	"github.com/charmbracelet/x/ansi"
7)
8
9// scrollOptimize optimizes the screen to transform the old buffer into the new
10// buffer.
11func (s *TerminalRenderer) scrollOptimize(newbuf *RenderBuffer) {
12	height := newbuf.Height()
13	if s.oldnum == nil || len(s.oldnum) < height {
14		s.oldnum = append(s.oldnum, make([]int, height-len(s.oldnum))...)
15	}
16
17	// Calculate the indices
18	s.updateHashmap(newbuf)
19	if len(s.hashtab) < height {
20		return
21	}
22
23	// Pass 1 - from top to bottom scrolling up
24	for i := 0; i < height; {
25		for i < height && (s.oldnum[i] == newIndex || s.oldnum[i] <= i) {
26			i++
27		}
28		if i >= height {
29			break
30		}
31
32		shift := s.oldnum[i] - i // shift > 0
33		start := i
34
35		i++
36		for i < height && s.oldnum[i] != newIndex && s.oldnum[i]-i == shift {
37			i++
38		}
39		end := i - 1 + shift
40
41		if !s.scrolln(newbuf, shift, start, end, height-1) {
42			continue
43		}
44	}
45
46	// Pass 2 - from bottom to top scrolling down
47	for i := height - 1; i >= 0; {
48		for i >= 0 && (s.oldnum[i] == newIndex || s.oldnum[i] >= i) {
49			i--
50		}
51		if i < 0 {
52			break
53		}
54
55		shift := s.oldnum[i] - i // shift < 0
56		end := i
57
58		i--
59		for i >= 0 && s.oldnum[i] != newIndex && s.oldnum[i]-i == shift {
60			i--
61		}
62
63		start := i + 1 - (-shift)
64		if !s.scrolln(newbuf, shift, start, end, height-1) {
65			continue
66		}
67	}
68}
69
70// scrolln scrolls the screen up by n lines.
71func (s *TerminalRenderer) scrolln(newbuf *RenderBuffer, n, top, bot, maxY int) (v bool) { //nolint:unparam
72	blank := s.clearBlank()
73	if n > 0 { //nolint:nestif
74		// Scroll up (forward)
75		v = s.scrollUp(newbuf, n, top, bot, 0, maxY, blank)
76		if !v {
77			s.buf.WriteString(ansi.SetTopBottomMargins(top+1, bot+1))
78
79			// XXX: How should we handle this in inline mode when not using alternate screen?
80			s.cur.X, s.cur.Y = -1, -1
81			v = s.scrollUp(newbuf, n, top, bot, top, bot, blank)
82			s.buf.WriteString(ansi.SetTopBottomMargins(1, maxY+1))
83			s.cur.X, s.cur.Y = -1, -1
84		}
85
86		if !v {
87			v = s.scrollIdl(newbuf, n, top, bot-n+1, blank)
88		}
89	} else if n < 0 {
90		// Scroll down (backward)
91		v = s.scrollDown(newbuf, -n, top, bot, 0, maxY, blank)
92		if !v {
93			s.buf.WriteString(ansi.SetTopBottomMargins(top+1, bot+1))
94
95			// XXX: How should we handle this in inline mode when not using alternate screen?
96			s.cur.X, s.cur.Y = -1, -1
97			v = s.scrollDown(newbuf, -n, top, bot, top, bot, blank)
98			s.buf.WriteString(ansi.SetTopBottomMargins(1, maxY+1))
99			s.cur.X, s.cur.Y = -1, -1
100
101			if !v {
102				v = s.scrollIdl(newbuf, -n, bot+n+1, top, blank)
103			}
104		}
105	}
106
107	if !v {
108		return false
109	}
110
111	s.scrollBuffer(s.curbuf, n, top, bot, blank)
112
113	// Every row in the range moved, whether or not the application drew into
114	// it. A row the scroll already put in the right place costs a comparison
115	// and no output, which is the whole point of scrolling in hardware.
116	s.damage(top, bot-top+1)
117
118	// shift hash values too, they can be reused
119	s.scrollOldhash(n, top, bot)
120
121	return true
122}
123
124// scrollBuffer scrolls the buffer by n lines.
125func (s *TerminalRenderer) scrollBuffer(b *RenderBuffer, n, top, bot int, blank *Cell) {
126	if top < 0 || bot < top || bot >= b.Height() {
127		// Nothing to scroll
128		return
129	}
130
131	if n < 0 {
132		// shift n lines downwards
133		limit := top - n
134		for line := bot; line >= limit && line >= 0 && line >= top; line-- {
135			copy(b.Lines[line], b.Lines[line+n])
136		}
137		for line := top; line < limit && line <= b.Height()-1 && line <= bot; line++ {
138			b.FillArea(blank, Rect(0, line, b.Width(), 1))
139		}
140	}
141
142	if n > 0 {
143		// shift n lines upwards
144		limit := bot - n
145		for line := top; line <= limit && line <= b.Height()-1 && line <= bot; line++ {
146			copy(b.Lines[line], b.Lines[line+n])
147		}
148		for line := bot; line > limit && line >= 0 && line >= top; line-- {
149			b.FillArea(blank, Rect(0, line, b.Width(), 1))
150		}
151	}
152}
153
154// scrollUp scrolls the screen up by n lines.
155func (s *TerminalRenderer) scrollUp(newbuf *RenderBuffer, n, top, bot, minY, maxY int, blank *Cell) bool {
156	if n == 1 && top == minY && bot == maxY { //nolint:nestif
157		s.move(newbuf, 0, bot)
158		s.updatePen(blank)
159		s.buf.WriteByte('\n')
160	} else if n == 1 && bot == maxY {
161		s.move(newbuf, 0, top)
162		s.updatePen(blank)
163		s.buf.WriteString(ansi.DeleteLine(1))
164	} else if top == minY && bot == maxY {
165		supportsSU := s.caps.Contains(capSU)
166		s.move(newbuf, 0, bot)
167		s.updatePen(blank)
168		if supportsSU {
169			s.buf.WriteString(ansi.ScrollUp(n))
170		} else {
171			s.buf.WriteString(strings.Repeat("\n", n))
172		}
173	} else if bot == maxY {
174		s.move(newbuf, 0, top)
175		s.updatePen(blank)
176		s.buf.WriteString(ansi.DeleteLine(n))
177	} else {
178		return false
179	}
180	return true
181}
182
183// scrollDown scrolls the screen down by n lines.
184func (s *TerminalRenderer) scrollDown(newbuf *RenderBuffer, n, top, bot, minY, maxY int, blank *Cell) bool {
185	if n == 1 && top == minY && bot == maxY { //nolint:nestif
186		s.move(newbuf, 0, top)
187		s.updatePen(blank)
188		s.buf.WriteString(ansi.ReverseIndex)
189	} else if n == 1 && bot == maxY {
190		s.move(newbuf, 0, top)
191		s.updatePen(blank)
192		s.buf.WriteString(ansi.InsertLine(1))
193	} else if top == minY && bot == maxY {
194		s.move(newbuf, 0, top)
195		s.updatePen(blank)
196		if s.caps.Contains(capSD) {
197			s.buf.WriteString(ansi.ScrollDown(n))
198		} else {
199			s.buf.WriteString(strings.Repeat(ansi.ReverseIndex, n))
200		}
201	} else if bot == maxY {
202		s.move(newbuf, 0, top)
203		s.updatePen(blank)
204		s.buf.WriteString(ansi.InsertLine(n))
205	} else {
206		return false
207	}
208	return true
209}
210
211// scrollIdl scrolls the screen n lines by using [ansi.DL] at del and using
212// [ansi.IL] at ins.
213func (s *TerminalRenderer) scrollIdl(newbuf *RenderBuffer, n, del, ins int, blank *Cell) bool {
214	if n < 0 {
215		return false
216	}
217
218	// Delete lines
219	s.move(newbuf, 0, del)
220	s.updatePen(blank)
221	s.buf.WriteString(ansi.DeleteLine(n))
222
223	// Insert lines
224	s.move(newbuf, 0, ins)
225	s.updatePen(blank)
226	s.buf.WriteString(ansi.InsertLine(n))
227
228	return true
229}