main
Raw Download raw file
  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		if s.xtermLike {
206			s.move(0, bot)
207		} else {
208			s.move(0, top)
209		}
210		s.updatePen(blank)
211		if s.xtermLike {
212			s.buf.WriteString(ansi.ScrollUp(n))
213		} else {
214			s.buf.WriteString(strings.Repeat("\n", n))
215		}
216	} else if bot == maxY {
217		s.move(0, top)
218		s.updatePen(blank)
219		s.buf.WriteString(ansi.DeleteLine(n))
220	} else {
221		return false
222	}
223	return true
224}
225
226// scrollDown scrolls the screen down by n lines.
227func (s *Screen) scrollDown(n, top, bot, minY, maxY int, blank *Cell) bool {
228	if n == 1 && top == minY && bot == maxY {
229		s.move(0, top)
230		s.updatePen(blank)
231		s.buf.WriteString(ansi.ReverseIndex)
232	} else if n == 1 && bot == maxY {
233		s.move(0, top)
234		s.updatePen(blank)
235		s.buf.WriteString(ansi.InsertLine(1))
236	} else if top == minY && bot == maxY {
237		s.move(0, top)
238		s.updatePen(blank)
239		if s.xtermLike {
240			s.buf.WriteString(ansi.ScrollDown(n))
241		} else {
242			s.buf.WriteString(strings.Repeat(ansi.ReverseIndex, n))
243		}
244	} else if bot == maxY {
245		s.move(0, top)
246		s.updatePen(blank)
247		s.buf.WriteString(ansi.InsertLine(n))
248	} else {
249		return false
250	}
251	return true
252}
253
254// scrollIdl scrolls the screen n lines by using [ansi.DL] at del and using
255// [ansi.IL] at ins.
256func (s *Screen) scrollIdl(n, del, ins int, blank *Cell) bool {
257	if n < 0 {
258		return false
259	}
260
261	// Delete lines
262	s.move(0, del)
263	s.updatePen(blank)
264	s.buf.WriteString(ansi.DeleteLine(n))
265
266	// Insert lines
267	s.move(0, ins)
268	s.updatePen(blank)
269	s.buf.WriteString(ansi.InsertLine(n))
270
271	return true
272}