okami.mom/ultraviolet

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

Kieran Klukasfix(renderer): erase rows a frame no longer draws after a resize8786532

main
23.2 KiB862 linesraw
1package uv
2
3import (
4	"bytes"
5	"image"
6	"io"
7	"strings"
8
9	"github.com/charmbracelet/x/ansi"
10)
11
12// Position represents a position in a coordinate system.
13type Position = image.Point
14
15// Pos is a shorthand for creating a new [Position].
16func Pos(x, y int) Position {
17	return Position{X: x, Y: y}
18}
19
20// Rectangle represents a rectangular area.
21type Rectangle = image.Rectangle
22
23// Rect is a shorthand for creating a new [Rectangle].
24func Rect(x, y, w, h int) Rectangle {
25	return Rectangle{Min: image.Point{X: x, Y: y}, Max: image.Point{X: x + w, Y: y + h}}
26}
27
28// Line represents cells in a line.
29type Line []Cell
30
31// NewLine creates a new line with the given width, filled with empty cells.
32func NewLine(width int) Line {
33	l := make(Line, width)
34	for i := range l {
35		l[i] = EmptyCell
36	}
37	return l
38}
39
40// Set sets the cell at the given x position.
41func (l Line) Set(x int, c *Cell) {
42	lineWidth := len(l)
43	if x < 0 || x >= lineWidth {
44		return
45	}
46
47	// When a wide cell is partially overwritten, we need
48	// to fill the rest of the cell with space cells to
49	// avoid rendering issues.
50	var prev *Cell
51	if prev = l.At(x); prev != nil { //nolint:nestif
52		if pw := prev.Width; pw > 1 {
53			// Writing to the first wide cell
54			for j := 0; j < pw && x+j < lineWidth; j++ {
55				l[x+j] = *prev
56				l[x+j].Empty()
57			}
58		} else if pw == 0 {
59			// Writing to wide cell placeholders
60			for j := 1; x-j >= 0; j++ {
61				if wide := l.At(x - j); wide != nil {
62					if ww := wide.Width; ww > 1 && j < ww {
63						for k := range ww {
64							l[x-j+k] = *wide
65							l[x-j+k].Empty()
66						}
67						break
68					}
69				}
70			}
71		}
72	}
73
74	if c == nil {
75		// Nil cells are treated as blank empty cells.
76		l[x] = EmptyCell
77		return
78	}
79
80	l[x] = *c
81	cw := c.Width
82	if x+cw > lineWidth {
83		// If the cell is too wide, we write blanks with the same style.
84		for i := 0; i < cw && x+i < lineWidth; i++ {
85			l[x+i] = *c
86			l[x+i].Empty()
87		}
88		return
89	}
90
91	if cw > 1 {
92		// Mark wide cells with zero-width placeholder cells.
93		for j := 1; j < cw && x+j < lineWidth; j++ {
94			l[x+j] = Cell{}
95		}
96	}
97}
98
99// At returns the cell at the given x position.
100// If the cell does not exist, it returns nil.
101func (l Line) At(x int) *Cell {
102	if x < 0 || x >= len(l) {
103		return nil
104	}
105
106	return &l[x]
107}
108
109// String returns the string representation of the line. Any trailing spaces
110// are removed.
111func (l Line) String() string {
112	var buf strings.Builder
113	var pending bytes.Buffer
114	for _, c := range l {
115		if c.IsZero() {
116			continue
117		}
118		if c.Equal(&EmptyCell) {
119			pending.WriteByte(' ')
120			continue
121		}
122		if pending.Len() > 0 {
123			buf.WriteString(pending.String())
124			pending.Reset()
125		}
126		buf.WriteString(c.String())
127	}
128
129	return buf.String()
130}
131
132// Render renders the line to a string with all the required attributes and
133// styles.
134func (l Line) Render() string {
135	var buf strings.Builder
136	renderLine(&buf, l)
137	return buf.String()
138}
139
140func renderLine(buf io.StringWriter, l Line) {
141	var pen Style
142	var link Link
143	var pending bytes.Buffer
144
145	for _, c := range l {
146		if c.IsZero() {
147			continue
148		}
149		if c.Equal(&EmptyCell) {
150			if !pen.IsZero() {
151				_, _ = buf.WriteString(ansi.ResetStyle)
152				pen = Style{}
153			}
154			if !link.IsZero() {
155				_, _ = buf.WriteString(ansi.ResetHyperlink())
156				link = Link{}
157			}
158			pending.WriteByte(' ')
159			continue
160		}
161
162		if pending.Len() > 0 {
163			_, _ = buf.WriteString(pending.String())
164			pending.Reset()
165		}
166
167		if c.Style.IsZero() && !pen.IsZero() {
168			_, _ = buf.WriteString(ansi.ResetStyle)
169			pen = Style{}
170		}
171		if !c.Style.Equal(&pen) {
172			seq := c.Style.Diff(&pen)
173			_, _ = buf.WriteString(seq)
174			pen = c.Style
175		}
176
177		// Write the URL escape sequence
178		if c.Link != link && link.URL != "" {
179			_, _ = buf.WriteString(ansi.ResetHyperlink())
180			link = Link{}
181		}
182		if c.Link != link {
183			_, _ = buf.WriteString(ansi.SetHyperlink(c.Link.URL, c.Link.Params))
184			link = c.Link
185		}
186
187		_, _ = buf.WriteString(c.String())
188	}
189
190	if pending.Len() > 0 {
191		_, _ = buf.WriteString(pending.String())
192	}
193	if link.URL != "" {
194		_, _ = buf.WriteString(ansi.ResetHyperlink())
195	}
196	if !pen.IsZero() {
197		_, _ = buf.WriteString(ansi.ResetStyle)
198	}
199}
200
201// Lines represents a slice of lines.
202type Lines []Line
203
204// Height returns the height of the lines.
205func (ls Lines) Height() int {
206	return len(ls)
207}
208
209// Width returns the width of the widest line.
210func (ls Lines) Width() int {
211	maxWidth := 0
212	for _, l := range ls {
213		maxWidth = max(maxWidth, len(l))
214	}
215	return maxWidth
216}
217
218// String returns the string representation of the lines.
219func (ls Lines) String() string {
220	var buf strings.Builder
221	for i, l := range ls {
222		buf.WriteString(l.String())
223		if i < len(ls)-1 {
224			_ = buf.WriteByte('\n')
225		}
226	}
227	return buf.String()
228}
229
230// Render renders the lines to a styled string with all the required attributes
231// and styles.
232func (ls Lines) Render() string {
233	var buf strings.Builder
234	for i, l := range ls {
235		renderLine(&buf, l)
236		if i < len(ls)-1 {
237			_ = buf.WriteByte('\n')
238		}
239	}
240	return buf.String()
241}
242
243// Buffer represents a cell buffer that contains the contents of a screen.
244type Buffer struct {
245	// Lines is a slice of lines that make up the cells of the buffer.
246	Lines []Line
247}
248
249var _ Drawable = (*Buffer)(nil)
250
251// NewBuffer creates a new buffer with the given width and height.
252// This is a convenience function that initializes a new buffer and resizes it.
253func NewBuffer(width int, height int) *Buffer {
254	b := new(Buffer)
255	b.Lines = make([]Line, height)
256	for i := range b.Lines {
257		b.Lines[i] = make(Line, width)
258		for j := range b.Lines[i] {
259			b.Lines[i][j] = EmptyCell
260		}
261	}
262	b.Resize(width, height)
263	return b
264}
265
266// String returns the string representation of the buffer.
267func (b *Buffer) String() string {
268	return Lines(b.Lines).String()
269}
270
271// Render renders the buffer to a styled string with all the required
272// attributes and styles.
273func (b *Buffer) Render() string {
274	return Lines(b.Lines).Render()
275}
276
277// Line returns a pointer to the line at the given y position.
278// If the line does not exist, it returns nil.
279func (b *Buffer) Line(y int) Line {
280	if y < 0 || y >= len(b.Lines) {
281		return nil
282	}
283	return b.Lines[y]
284}
285
286// CellAt returns the cell at the given position. It returns nil if the
287// position is out of bounds.
288func (b *Buffer) CellAt(x int, y int) *Cell {
289	if y < 0 || y >= len(b.Lines) {
290		return nil
291	}
292	return b.Lines[y].At(x)
293}
294
295// SetCell sets the cell at the given x, y position.
296func (b *Buffer) SetCell(x, y int, c *Cell) {
297	if y < 0 || y >= len(b.Lines) {
298		return
299	}
300
301	b.Lines[y].Set(x, c)
302}
303
304// Height implements Screen.
305func (b *Buffer) Height() int {
306	return len(b.Lines)
307}
308
309// Width implements Screen.
310func (b *Buffer) Width() int {
311	if len(b.Lines) == 0 {
312		return 0
313	}
314	return len(b.Lines[0])
315}
316
317// Bounds returns the bounds of the buffer.
318// The origin is always at (0, 0) and the maximum coordinates are determined by
319// the width and height of the buffer.
320func (b *Buffer) Bounds() Rectangle {
321	return Rect(0, 0, b.Width(), b.Height())
322}
323
324// Resize resizes the buffer to the given width and height.
325func (b *Buffer) Resize(width int, height int) {
326	curWidth, curHeight := b.Width(), b.Height()
327	if curWidth == width && curHeight == height {
328		// No need to resize if the dimensions are the same.
329		return
330	}
331
332	if width > curWidth {
333		line := make(Line, width-curWidth)
334		for i := range line {
335			line[i] = EmptyCell
336		}
337		for i := range b.Lines {
338			b.Lines[i] = append(b.Lines[i], line...)
339		}
340	} else if width < curWidth {
341		for i := range b.Lines {
342			b.Lines[i] = b.Lines[i][:width]
343		}
344	}
345
346	if height > len(b.Lines) {
347		for i := len(b.Lines); i < height; i++ {
348			line := make(Line, width)
349			for j := range line {
350				line[j] = EmptyCell
351			}
352			b.Lines = append(b.Lines, line)
353		}
354	} else if height < len(b.Lines) {
355		b.Lines = b.Lines[:height]
356	}
357}
358
359// Fill fills the buffer with the given cell and rectangle.
360func (b *Buffer) Fill(c *Cell) {
361	b.FillArea(c, b.Bounds())
362}
363
364// FillArea fills the buffer with the given cell and rectangle.
365func (b *Buffer) FillArea(c *Cell, area Rectangle) {
366	cellWidth := 1
367	if c != nil && c.Width > 1 {
368		cellWidth = c.Width
369	}
370	for y := area.Min.Y; y < area.Max.Y; y++ {
371		for x := area.Min.X; x < area.Max.X; x += cellWidth {
372			b.SetCell(x, y, c)
373		}
374	}
375}
376
377// Clear clears the buffer with space cells and rectangle.
378func (b *Buffer) Clear() {
379	area := b.Bounds()
380	for y := area.Min.Y; y < area.Max.Y; y++ {
381		for x := area.Min.X; x < area.Max.X; x++ {
382			b.Lines[y][x] = EmptyCell
383		}
384	}
385}
386
387// ClearArea clears the buffer with space cells within the specified
388// rectangles. Only cells within the rectangle's bounds are affected.
389func (b *Buffer) ClearArea(area Rectangle) {
390	b.FillArea(nil, area)
391}
392
393// CloneArea clones the area of the buffer within the specified rectangle. If
394// the area is out of bounds, it returns nil.
395func (b *Buffer) CloneArea(area Rectangle) *Buffer {
396	bounds := b.Bounds()
397	if !area.In(bounds) {
398		return nil
399	}
400	n := NewBuffer(area.Dx(), area.Dy())
401	for y := area.Min.Y; y < area.Max.Y; y++ {
402		for x := area.Min.X; x < area.Max.X; {
403			c := b.CellAt(x, y)
404			if c == nil || c.IsZero() {
405				x++
406				continue
407			}
408			n.SetCell(x-area.Min.X, y-area.Min.Y, c)
409			x += max(c.Width, 1)
410		}
411	}
412	return n
413}
414
415// Clone clones the entire buffer into a new buffer.
416func (b *Buffer) Clone() *Buffer {
417	return b.CloneArea(b.Bounds())
418}
419
420// Draw draws the buffer to the given screen at the specified area.
421// It implements the [Drawable] interface.
422func (b *Buffer) Draw(scr Screen, area Rectangle) {
423	if area.Empty() {
424		return
425	}
426
427	// Ensure the area is within the bounds of the screen.
428	bounds := scr.Bounds()
429	if !area.Overlaps(bounds) {
430		return
431	}
432
433	for y := area.Min.Y; y < area.Max.Y; y++ {
434		for x := area.Min.X; x < area.Max.X; {
435			c := b.CellAt(x-area.Min.X, y-area.Min.Y)
436			if c == nil || c.IsZero() {
437				x++
438				continue
439			}
440			scr.SetCell(x, y, c)
441			width := c.Width
442			if width <= 0 {
443				width = 1
444			}
445			x += width
446		}
447	}
448}
449
450// InsertLine inserts n lines at the given line position, with the given
451// optional cell, within the specified rectangles. If no rectangles are
452// specified, it inserts lines in the entire buffer. Only cells within the
453// rectangle's horizontal bounds are affected. Lines are pushed out of the
454// rectangle bounds and lost. This follows terminal [ansi.IL] behavior.
455// It returns the pushed out lines.
456func (b *Buffer) InsertLine(y, n int, c *Cell) {
457	b.InsertLineArea(y, n, c, b.Bounds())
458}
459
460// InsertLineArea inserts new lines at the given line position, with the
461// given optional cell, within the rectangle bounds. Only cells within the
462// rectangle's horizontal bounds are affected. Lines are pushed out of the
463// rectangle bounds and lost. This follows terminal [ansi.IL] behavior.
464func (b *Buffer) InsertLineArea(y, n int, c *Cell, area Rectangle) {
465	if n <= 0 || y < area.Min.Y || y >= area.Max.Y || y >= b.Height() {
466		return
467	}
468
469	// Limit number of lines to insert to available space
470	if y+n > area.Max.Y {
471		n = area.Max.Y - y
472	}
473
474	// Move existing lines down within the bounds
475	for i := area.Max.Y - 1; i >= y+n; i-- {
476		for x := area.Min.X; x < area.Max.X; x++ {
477			// We don't need to clone c here because we're just moving lines down.
478			b.Lines[i][x] = b.Lines[i-n][x]
479		}
480	}
481
482	// Clear the newly inserted lines within bounds
483	for i := y; i < y+n; i++ {
484		for x := area.Min.X; x < area.Max.X; x++ {
485			b.SetCell(x, i, c)
486		}
487	}
488}
489
490// DeleteLineArea deletes lines at the given line position, with the given
491// optional cell, within the rectangle bounds. Only cells within the
492// rectangle's bounds are affected. Lines are shifted up within the bounds and
493// new blank lines are created at the bottom. This follows terminal [ansi.DL]
494// behavior.
495func (b *Buffer) DeleteLineArea(y, n int, c *Cell, area Rectangle) {
496	if n <= 0 || y < area.Min.Y || y >= area.Max.Y || y >= b.Height() {
497		return
498	}
499
500	// Limit deletion count to available space in scroll region
501	if n > area.Max.Y-y {
502		n = area.Max.Y - y
503	}
504
505	// Shift cells up within the bounds
506	for dst := y; dst < area.Max.Y-n; dst++ {
507		src := dst + n
508		for x := area.Min.X; x < area.Max.X; x++ {
509			// We don't need to clone c here because we're just moving cells up.
510			b.Lines[dst][x] = b.Lines[src][x]
511		}
512	}
513
514	// Fill the bottom n lines with blank cells
515	for i := area.Max.Y - n; i < area.Max.Y; i++ {
516		for x := area.Min.X; x < area.Max.X; x++ {
517			b.SetCell(x, i, c)
518		}
519	}
520}
521
522// DeleteLine deletes n lines at the given line position, with the given
523// optional cell, within the specified rectangles. If no rectangles are
524// specified, it deletes lines in the entire buffer.
525func (b *Buffer) DeleteLine(y, n int, c *Cell) {
526	b.DeleteLineArea(y, n, c, b.Bounds())
527}
528
529// InsertCell inserts new cells at the given position, with the given optional
530// cell, within the specified rectangles. If no rectangles are specified, it
531// inserts cells in the entire buffer. This follows terminal [ansi.ICH]
532// behavior.
533func (b *Buffer) InsertCell(x, y, n int, c *Cell) {
534	b.InsertCellArea(x, y, n, c, b.Bounds())
535}
536
537// InsertCellArea inserts new cells at the given position, with the given
538// optional cell, within the rectangle bounds. Only cells within the
539// rectangle's bounds are affected, following terminal [ansi.ICH] behavior.
540func (b *Buffer) InsertCellArea(x, y, n int, c *Cell, area Rectangle) {
541	if n <= 0 || y < area.Min.Y || y >= area.Max.Y || y >= b.Height() ||
542		x < area.Min.X || x >= area.Max.X || x >= b.Width() {
543		return
544	}
545
546	// Limit number of cells to insert to available space
547	if x+n > area.Max.X {
548		n = area.Max.X - x
549	}
550
551	// Move existing cells within rectangle bounds to the right
552	for i := area.Max.X - 1; i >= x+n && i-n >= area.Min.X; i-- {
553		// We don't need to clone c here because we're just moving cells to the
554		// right.
555		b.Lines[y][i] = b.Lines[y][i-n]
556	}
557
558	// Clear the newly inserted cells within rectangle bounds
559	for i := x; i < x+n && i < area.Max.X; i++ {
560		b.SetCell(i, y, c)
561	}
562}
563
564// DeleteCell deletes cells at the given position, with the given optional
565// cell, within the specified rectangles. If no rectangles are specified, it
566// deletes cells in the entire buffer. This follows terminal [ansi.DCH]
567// behavior.
568func (b *Buffer) DeleteCell(x, y, n int, c *Cell) {
569	b.DeleteCellArea(x, y, n, c, b.Bounds())
570}
571
572// DeleteCellArea deletes cells at the given position, with the given
573// optional cell, within the rectangle bounds. Only cells within the
574// rectangle's bounds are affected, following terminal [ansi.DCH] behavior.
575func (b *Buffer) DeleteCellArea(x, y, n int, c *Cell, area Rectangle) {
576	if n <= 0 || y < area.Min.Y || y >= area.Max.Y || y >= b.Height() ||
577		x < area.Min.X || x >= area.Max.X || x >= b.Width() {
578		return
579	}
580
581	// Calculate how many positions we can actually delete
582	remainingCells := area.Max.X - x
583	if n > remainingCells {
584		n = remainingCells
585	}
586
587	// Shift the remaining cells to the left
588	for i := x; i < area.Max.X-n; i++ {
589		if i+n < area.Max.X {
590			// We need to use SetCell here to ensure we blank out any wide
591			// cells we encounter.
592			b.SetCell(i, y, b.CellAt(i+n, y))
593		}
594	}
595
596	// Fill the vacated positions with the given cell
597	for i := area.Max.X - n; i < area.Max.X; i++ {
598		b.SetCell(i, y, c)
599	}
600}
601
602// ScreenBuffer is a buffer that can be used as a [Screen].
603type ScreenBuffer struct {
604	*RenderBuffer
605	Method ansi.Method
606}
607
608var (
609	_ Screen   = ScreenBuffer{}
610	_ Drawable = ScreenBuffer{}
611)
612
613// NewScreenBuffer creates a new ScreenBuffer with the given width and height.
614func NewScreenBuffer(width, height int) ScreenBuffer {
615	return ScreenBuffer{
616		RenderBuffer: NewRenderBuffer(width, height),
617		Method:       ansi.WcWidth,
618	}
619}
620
621// WidthMethod returns the width method used by the screen.
622// It defaults to [ansi.WcWidth].
623func (s ScreenBuffer) WidthMethod() WidthMethod {
624	return s.Method
625}
626
627// TrimSpace trims trailing spaces from the end of each line in the given
628// string.
629func TrimSpace(s string) string {
630	lines := strings.Split(s, "\n")
631	for i, line := range lines {
632		// Check if we have a trailing '\r' and preserve it
633		hasCR := strings.HasSuffix(line, "\r")
634		if hasCR {
635			line = strings.TrimSuffix(line, "\r")
636		}
637		line = strings.TrimRight(line, " ")
638		if hasCR {
639			line = line + "\r"
640		}
641		lines[i] = line
642	}
643	return strings.Join(lines, "\n")
644}
645
646// RenderBuffer represents a buffer that keeps track of the current and new
647// state of the screen, allowing for efficient rendering by only updating the
648// parts of the screen that have changed.
649type RenderBuffer struct {
650	*Buffer
651
652	// Touched records which lines changed since the last render. It holds at
653	// least one entry per screen row, and a nil entry means that row is
654	// unchanged. Shortening it is not meaningful, since the rows still exist, so
655	// the renderer restores the length rather than checking it at every read.
656	Touched []*LineData
657}
658
659// NewRenderBuffer creates a new [RenderBuffer] with the given width and height.
660func NewRenderBuffer(width, height int) *RenderBuffer {
661	return &RenderBuffer{
662		Buffer:  NewBuffer(width, height),
663		Touched: make([]*LineData, height),
664	}
665}
666
667// TouchLine marks a line n times starting at the given x position as touched.
668func (b *RenderBuffer) TouchLine(x, y, n int) {
669	if y < 0 || y >= len(b.Lines) {
670		return
671	}
672
673	b.growTouched()
674
675	ch := b.Touched[y]
676	if ch == nil {
677		ch = &LineData{FirstCell: x, LastCell: x + n}
678	} else {
679		ch.FirstCell = min(ch.FirstCell, x)
680		ch.LastCell = max(ch.LastCell, x+n)
681	}
682	b.Touched[y] = ch
683}
684
685// Touch marks the cell at the given x, y position as touched.
686func (b *RenderBuffer) Touch(x, y int) {
687	b.TouchLine(x, y, 0)
688}
689
690// TouchedLines returns the number of touched lines in the buffer.
691func (b *RenderBuffer) TouchedLines() int {
692	count := 0
693	for _, t := range b.Touched {
694		if t != nil {
695			count++
696		}
697	}
698	return count
699}
700
701// SetCell sets the cell at the given x, y position and marks the line as
702// touched.
703func (b *RenderBuffer) SetCell(x, y int, c *Cell) {
704	if p := b.CellAt(x, y); !cellEqual(p, c) {
705		width := 1
706		if c != nil && c.Width > 0 {
707			width = c.Width
708		}
709		if p != nil && p.Width > 0 {
710			width = max(width, p.Width)
711		}
712		b.TouchLine(x, y, width)
713	}
714	b.Buffer.SetCell(x, y, c)
715}
716
717// InsertLine inserts n lines at the given line position, with the given
718// optional cell, within the specified rectangles. If no rectangles are
719// specified, it inserts lines in the entire buffer. Only cells within the
720// rectangle's horizontal bounds are affected. Lines are pushed out of the
721// rectangle bounds and lost. This follows terminal [ansi.IL] behavior.
722func (b *RenderBuffer) InsertLine(y, n int, c *Cell) {
723	b.InsertLineArea(y, n, c, b.Bounds())
724}
725
726// InsertLineArea inserts new lines at the given line position, with the given
727// optional cell, within the rectangle bounds. Only cells within the
728// rectangle's horizontal bounds are affected. Lines are pushed out of the
729// rectangle bounds and lost. This follows terminal [ansi.IL] behavior.
730func (b *RenderBuffer) InsertLineArea(y, n int, c *Cell, area Rectangle) {
731	b.Buffer.InsertLineArea(y, n, c, area)
732	for i := area.Min.Y; i < area.Max.Y; i++ {
733		b.TouchLine(area.Min.X, i, area.Max.X-area.Min.X)
734		b.TouchLine(area.Min.X, i-n, area.Max.X-area.Min.X)
735	}
736}
737
738// DeleteLine deletes n lines at the given line position, with the given
739// optional cell, within the specified rectangles. If no rectangles are
740// specified, it deletes lines in the entire buffer.
741func (b *RenderBuffer) DeleteLine(y, n int, c *Cell) {
742	b.DeleteLineArea(y, n, c, b.Bounds())
743}
744
745// DeleteLineArea deletes lines at the given line position, with the given
746// optional cell, within the rectangle bounds. Only cells within the
747// rectangle's bounds are affected. Lines are shifted up within the bounds and
748// new blank lines are created at the bottom. This follows terminal [ansi.DL]
749// behavior.
750func (b *RenderBuffer) DeleteLineArea(y, n int, c *Cell, area Rectangle) {
751	b.Buffer.DeleteLineArea(y, n, c, area)
752	for i := area.Min.Y; i < area.Max.Y; i++ {
753		b.TouchLine(area.Min.X, i, area.Max.X-area.Min.X)
754		b.TouchLine(area.Min.X, i+n, area.Max.X-area.Min.X)
755	}
756}
757
758// InsertCell inserts new cells at the given position, with the given optional
759// cell, within the specified rectangles. If no rectangles are specified, it
760// inserts cells in the entire buffer. This follows terminal [ansi.ICH]
761// behavior.
762func (b *RenderBuffer) InsertCell(x, y, n int, c *Cell) {
763	b.InsertCellArea(x, y, n, c, b.Bounds())
764}
765
766// InsertCellArea inserts new cells at the given position, with the given
767// optional cell, within the rectangle bounds. Only cells within the
768// rectangle's bounds are affected, following terminal [ansi.ICH] behavior.
769func (b *RenderBuffer) InsertCellArea(x, y, n int, c *Cell, area Rectangle) {
770	b.Buffer.InsertCellArea(x, y, n, c, area)
771	if x+n > area.Max.X {
772		n = area.Max.X - x
773	}
774	b.TouchLine(x, y, n)
775}
776
777// DeleteCell deletes cells at the given position, with the given optional
778// cell, within the specified rectangles. If no rectangles are specified, it
779// deletes cells in the entire buffer. This follows terminal [ansi.DCH]
780// behavior.
781func (b *RenderBuffer) DeleteCell(x, y, n int, c *Cell) {
782	b.DeleteCellArea(x, y, n, c, b.Bounds())
783}
784
785// DeleteCellArea deletes cells at the given position, with the given
786// optional cell, within the rectangle bounds. Only cells within the
787// rectangle's bounds are affected, following terminal [ansi.DCH] behavior.
788func (b *RenderBuffer) DeleteCellArea(x, y, n int, c *Cell, area Rectangle) {
789	b.Buffer.DeleteCellArea(x, y, n, c, area)
790	remainingCells := area.Max.X - x
791	if n > remainingCells {
792		n = remainingCells
793	}
794	b.TouchLine(x, y, n)
795}
796
797// Resize resizes the buffer and touches every cell it creates, since the
798// screen there still shows whatever the last render left.
799func (b *RenderBuffer) Resize(width, height int) {
800	curWidth, curHeight := b.Width(), b.Height()
801	b.Buffer.Resize(width, height)
802
803	if width > curWidth {
804		for y := range min(curHeight, height) {
805			b.TouchLine(curWidth, y, width-curWidth)
806		}
807	}
808	for y := curHeight; y < height; y++ {
809		b.TouchLine(0, y, width)
810	}
811}
812
813// Clear clears the buffer with space cells and marks all lines as touched.
814func (b *RenderBuffer) Clear() {
815	b.Buffer.Clear()
816	w := b.Width()
817	for y := range b.Lines {
818		b.TouchLine(0, y, w)
819	}
820}
821
822// ClearArea clears the buffer with space cells within the specified rectangle
823// and marks the affected lines as touched.
824func (b *RenderBuffer) ClearArea(area Rectangle) {
825	b.Buffer.ClearArea(area)
826	w := area.Max.X - area.Min.X
827	for y := area.Min.Y; y < area.Max.Y; y++ {
828		b.TouchLine(area.Min.X, y, w)
829	}
830}
831
832// Fill fills the buffer with the given cell and marks all lines as touched.
833func (b *RenderBuffer) Fill(c *Cell) {
834	b.FillArea(c, b.Bounds())
835}
836
837// FillArea fills the buffer with the given cell within the specified rectangle
838// and marks the affected lines as touched.
839func (b *RenderBuffer) FillArea(c *Cell, area Rectangle) {
840	b.Buffer.FillArea(c, area)
841	w := area.Max.X - area.Min.X
842	for y := area.Min.Y; y < area.Max.Y; y++ {
843		b.TouchLine(area.Min.X, y, w)
844	}
845}
846
847// growTouched restores the one-entry-per-row invariant, which an application is
848// free to break by shortening or dropping the list.
849//
850// Only ever grows. Truncating to the screen would be the tidier rule, but
851// [RenderBuffer.TouchedLines] counts non-nil records rather than touched ones,
852// so discarding the entries of a frame that collapsed makes it look untouched
853// and skips the erase that clears the rows it gave up. Growing is enough: every
854// reader indexes by screen row, and a longer list has an entry for each.
855func (b *RenderBuffer) growTouched() {
856	if len(b.Touched) >= len(b.Lines) {
857		return
858	}
859	touched := make([]*LineData, len(b.Lines))
860	copy(touched, b.Touched)
861	b.Touched = touched
862}