hardscroll.go

  1package cellbuf
  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 *Screen) scrollOptimize() {
 12	height := s.newbuf.Height()
 13	if s.oldnum == nil || len(s.oldnum) < height {
 14		s.oldnum = make([]int, height)
 15	}
 16
 17	// Calculate the indices
 18	s.updateHashmap()
 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(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(shift, start, end, height-1) {
 65			continue
 66		}
 67	}
 68}
 69
 70// scrolln scrolls the screen up by n lines.
 71func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
 72	const (
 73		nonDestScrollRegion = false
 74		memoryBelow         = false
 75	)
 76
 77	blank := s.clearBlank()
 78	if n > 0 {
 79		// Scroll up (forward)
 80		v = s.scrollUp(n, top, bot, 0, maxY, blank)
 81		if !v {
 82			s.buf.WriteString(ansi.SetTopBottomMargins(top+1, bot+1))
 83
 84			// XXX: How should we handle this in inline mode when not using alternate screen?
 85			s.cur.X, s.cur.Y = -1, -1
 86			v = s.scrollUp(n, top, bot, top, bot, blank)
 87			s.buf.WriteString(ansi.SetTopBottomMargins(1, maxY+1))
 88			s.cur.X, s.cur.Y = -1, -1
 89		}
 90
 91		if !v {
 92			v = s.scrollIdl(n, top, bot-n+1, blank)
 93		}
 94
 95		// Clear newly shifted-in lines.
 96		if v &&
 97			(nonDestScrollRegion || (memoryBelow && bot == maxY)) {
 98			if bot == maxY {
 99				s.move(0, bot-n+1)
100				s.clearToBottom(nil)
101			} else {
102				for i := 0; i < n; i++ {
103					s.move(0, bot-i)
104					s.clearToEnd(nil, false)
105				}
106			}
107		}
108	} else if n < 0 {
109		// Scroll down (backward)
110		v = s.scrollDown(-n, top, bot, 0, maxY, blank)
111		if !v {
112			s.buf.WriteString(ansi.SetTopBottomMargins(top+1, bot+1))
113
114			// XXX: How should we handle this in inline mode when not using alternate screen?
115			s.cur.X, s.cur.Y = -1, -1
116			v = s.scrollDown(-n, top, bot, top, bot, blank)
117			s.buf.WriteString(ansi.SetTopBottomMargins(1, maxY+1))
118			s.cur.X, s.cur.Y = -1, -1
119
120			if !v {
121				v = s.scrollIdl(-n, bot+n+1, top, blank)
122			}
123
124			// Clear newly shifted-in lines.
125			if v &&
126				(nonDestScrollRegion || (memoryBelow && top == 0)) {
127				for i := 0; i < -n; i++ {
128					s.move(0, top+i)
129					s.clearToEnd(nil, false)
130				}
131			}
132		}
133	}
134
135	if !v {
136		return
137	}
138
139	s.scrollBuffer(s.curbuf, n, top, bot, blank)
140
141	// shift hash values too, they can be reused
142	s.scrollOldhash(n, top, bot)
143
144	return true
145}
146
147// scrollBuffer scrolls the buffer by n lines.
148func (s *Screen) scrollBuffer(b *Buffer, n, top, bot int, blank *Cell) {
149	if top < 0 || bot < top || bot >= b.Height() {
150		// Nothing to scroll
151		return
152	}
153
154	if n < 0 {
155		// shift n lines downwards
156		limit := top - n
157		for line := bot; line >= limit && line >= 0 && line >= top; line-- {
158			copy(b.Lines[line], b.Lines[line+n])
159		}
160		for line := top; line < limit && line <= b.Height()-1 && line <= bot; line++ {
161			b.FillRect(blank, Rect(0, line, b.Width(), 1))
162		}
163	}
164
165	if n > 0 {
166		// shift n lines upwards
167		limit := bot - n
168		for line := top; line <= limit && line <= b.Height()-1 && line <= bot; line++ {
169			copy(b.Lines[line], b.Lines[line+n])
170		}
171		for line := bot; line > limit && line >= 0 && line >= top; line-- {
172			b.FillRect(blank, Rect(0, line, b.Width(), 1))
173		}
174	}
175
176	s.touchLine(b.Width(), b.Height(), top, bot-top+1, true)
177}
178
179// touchLine marks the line as touched.
180func (s *Screen) touchLine(width, height, y, n int, changed bool) {
181	if n < 0 || y < 0 || y >= height {
182		return // Nothing to touch
183	}
184
185	for i := y; i < y+n && i < height; i++ {
186		if changed {
187			s.touch[i] = lineData{firstCell: 0, lastCell: width - 1}
188		} else {
189			delete(s.touch, i)
190		}
191	}
192}
193
194// scrollUp scrolls the screen up by n lines.
195func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
196	if n == 1 && top == minY && bot == maxY {
197		s.move(0, bot)
198		s.updatePen(blank)
199		s.buf.WriteByte('\n')
200	} else if n == 1 && bot == maxY {
201		s.move(0, top)
202		s.updatePen(blank)
203		s.buf.WriteString(ansi.DeleteLine(1))
204	} else if top == minY && bot == maxY {
205		supportsSU := s.caps.Contains(capSU)
206		if supportsSU {
207			s.move(0, bot)
208		} else {
209			s.move(0, top)
210		}
211		s.updatePen(blank)
212		if supportsSU {
213			s.buf.WriteString(ansi.ScrollUp(n))
214		} else {
215			s.buf.WriteString(strings.Repeat("\n", n))
216		}
217	} else if bot == maxY {
218		s.move(0, top)
219		s.updatePen(blank)
220		s.buf.WriteString(ansi.DeleteLine(n))
221	} else {
222		return false
223	}
224	return true
225}
226
227// scrollDown scrolls the screen down by n lines.
228func (s *Screen) scrollDown(n, top, bot, minY, maxY int, blank *Cell) bool {
229	if n == 1 && top == minY && bot == maxY {
230		s.move(0, top)
231		s.updatePen(blank)
232		s.buf.WriteString(ansi.ReverseIndex)
233	} else if n == 1 && bot == maxY {
234		s.move(0, top)
235		s.updatePen(blank)
236		s.buf.WriteString(ansi.InsertLine(1))
237	} else if top == minY && bot == maxY {
238		s.move(0, top)
239		s.updatePen(blank)
240		if s.caps.Contains(capSD) {
241			s.buf.WriteString(ansi.ScrollDown(n))
242		} else {
243			s.buf.WriteString(strings.Repeat(ansi.ReverseIndex, n))
244		}
245	} else if bot == maxY {
246		s.move(0, top)
247		s.updatePen(blank)
248		s.buf.WriteString(ansi.InsertLine(n))
249	} else {
250		return false
251	}
252	return true
253}
254
255// scrollIdl scrolls the screen n lines by using [ansi.DL] at del and using
256// [ansi.IL] at ins.
257func (s *Screen) scrollIdl(n, del, ins int, blank *Cell) bool {
258	if n < 0 {
259		return false
260	}
261
262	// Delete lines
263	s.move(0, del)
264	s.updatePen(blank)
265	s.buf.WriteString(ansi.DeleteLine(n))
266
267	// Insert lines
268	s.move(0, ins)
269	s.updatePen(blank)
270	s.buf.WriteString(ansi.InsertLine(n))
271
272	return true
273}