okami.mom/ultraviolet
git clone https://git.nkp.pet/okami.mom/ultraviolet
8786532
main
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 { 17return 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 { 25return 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 { 33l := make (Line ,width ) 34for i := range l { 35l [i ]= EmptyCell 36 } 37return l 38} 39 40// Set sets the cell at the given x position. 41func (l Line )Set (x int ,c * Cell ) { 42lineWidth := len (l ) 43if x < 0 || x >= lineWidth { 44return 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. 50var prev * Cell 51if prev = l .At (x );prev != nil {//nolint:nestif 52if pw := prev .Width ;pw > 1 { 53// Writing to the first wide cell 54for j := 0 ;j < pw && x + j < lineWidth ;j ++ { 55l [x + j ]= * prev 56l [x + j ].Empty () 57 } 58 }else if pw == 0 { 59// Writing to wide cell placeholders 60for j := 1 ;x - j >= 0 ;j ++ { 61if wide := l .At (x - j );wide != nil { 62if ww := wide .Width ;ww > 1 && j < ww { 63for k := range ww { 64l [x - j + k ]= * wide 65l [x - j + k ].Empty () 66 } 67break 68 } 69 } 70 } 71 } 72 } 73 74if c == nil { 75// Nil cells are treated as blank empty cells. 76l [x ]= EmptyCell 77return 78 } 79 80l [x ]= * c 81cw := c .Width 82if x + cw > lineWidth { 83// If the cell is too wide, we write blanks with the same style. 84for i := 0 ;i < cw && x + i < lineWidth ;i ++ { 85l [x + i ]= * c 86l [x + i ].Empty () 87 } 88return 89 } 90 91if cw > 1 { 92// Mark wide cells with zero-width placeholder cells. 93for j := 1 ;j < cw && x + j < lineWidth ;j ++ { 94l [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 { 102if x < 0 || x >= len (l ) { 103return nil 104 } 105 106return & l [x ] 107} 108 109// String returns the string representation of the line. Any trailing spaces 110// are removed. 111func (l Line )String ()string { 112var buf strings.Builder 113var pending bytes.Buffer 114for _ ,c := range l { 115if c .IsZero () { 116continue 117 } 118if c .Equal (& EmptyCell ) { 119pending .WriteByte (' ' ) 120continue 121 } 122if pending .Len ()> 0 { 123buf .WriteString (pending .String ()) 124pending .Reset () 125 } 126buf .WriteString (c .String ()) 127 } 128 129return 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 { 135var buf strings.Builder 136renderLine (& buf ,l ) 137return buf .String () 138} 139 140func renderLine (buf io.StringWriter ,l Line ) { 141var pen Style 142var link Link 143var pending bytes.Buffer 144 145for _ ,c := range l { 146if c .IsZero () { 147continue 148 } 149if c .Equal (& EmptyCell ) { 150if ! pen .IsZero () { 151_ ,_ = buf .WriteString (ansi .ResetStyle ) 152pen = Style {} 153 } 154if ! link .IsZero () { 155_ ,_ = buf .WriteString (ansi .ResetHyperlink ()) 156link = Link {} 157 } 158pending .WriteByte (' ' ) 159continue 160 } 161 162if pending .Len ()> 0 { 163_ ,_ = buf .WriteString (pending .String ()) 164pending .Reset () 165 } 166 167if c .Style .IsZero ()&& ! pen .IsZero () { 168_ ,_ = buf .WriteString (ansi .ResetStyle ) 169pen = Style {} 170 } 171if ! c .Style .Equal (& pen ) { 172seq := c .Style .Diff (& pen ) 173_ ,_ = buf .WriteString (seq ) 174pen = c .Style 175 } 176 177// Write the URL escape sequence 178if c .Link != link && link .URL != "" { 179_ ,_ = buf .WriteString (ansi .ResetHyperlink ()) 180link = Link {} 181 } 182if c .Link != link { 183_ ,_ = buf .WriteString (ansi .SetHyperlink (c .Link .URL ,c .Link .Params )) 184link = c .Link 185 } 186 187_ ,_ = buf .WriteString (c .String ()) 188 } 189 190if pending .Len ()> 0 { 191_ ,_ = buf .WriteString (pending .String ()) 192 } 193if link .URL != "" { 194_ ,_ = buf .WriteString (ansi .ResetHyperlink ()) 195 } 196if ! 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 { 206return len (ls ) 207} 208 209// Width returns the width of the widest line. 210func (ls Lines )Width ()int { 211maxWidth := 0 212for _ ,l := range ls { 213maxWidth = max (maxWidth ,len (l )) 214 } 215return maxWidth 216} 217 218// String returns the string representation of the lines. 219func (ls Lines )String ()string { 220var buf strings.Builder 221for i ,l := range ls { 222buf .WriteString (l .String ()) 223if i < len (ls )- 1 { 224_ = buf .WriteByte ('\n' ) 225 } 226 } 227return 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 { 233var buf strings.Builder 234for i ,l := range ls { 235renderLine (& buf ,l ) 236if i < len (ls )- 1 { 237_ = buf .WriteByte ('\n' ) 238 } 239 } 240return 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. 246Lines []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 { 254b := new (Buffer ) 255b .Lines = make ([]Line ,height ) 256for i := range b .Lines { 257b .Lines [i ]= make (Line ,width ) 258for j := range b .Lines [i ] { 259b .Lines [i ][j ]= EmptyCell 260 } 261 } 262b .Resize (width ,height ) 263return b 264} 265 266// String returns the string representation of the buffer. 267func (b * Buffer )String ()string { 268return 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 { 274return 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 { 280if y < 0 || y >= len (b .Lines ) { 281return nil 282 } 283return 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 { 289if y < 0 || y >= len (b .Lines ) { 290return nil 291 } 292return 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 ) { 297if y < 0 || y >= len (b .Lines ) { 298return 299 } 300 301b .Lines [y ].Set (x ,c ) 302} 303 304// Height implements Screen. 305func (b * Buffer )Height ()int { 306return len (b .Lines ) 307} 308 309// Width implements Screen. 310func (b * Buffer )Width ()int { 311if len (b .Lines )== 0 { 312return 0 313 } 314return 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 { 321return 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 ) { 326curWidth ,curHeight := b .Width (),b .Height () 327if curWidth == width && curHeight == height { 328// No need to resize if the dimensions are the same. 329return 330 } 331 332if width > curWidth { 333line := make (Line ,width - curWidth ) 334for i := range line { 335line [i ]= EmptyCell 336 } 337for i := range b .Lines { 338b .Lines [i ]= append (b .Lines [i ],line ... ) 339 } 340 }else if width < curWidth { 341for i := range b .Lines { 342b .Lines [i ]= b .Lines [i ][:width ] 343 } 344 } 345 346if height > len (b .Lines ) { 347for i := len (b .Lines );i < height ;i ++ { 348line := make (Line ,width ) 349for j := range line { 350line [j ]= EmptyCell 351 } 352b .Lines = append (b .Lines ,line ) 353 } 354 }else if height < len (b .Lines ) { 355b .Lines = b .Lines [:height ] 356 } 357} 358 359// Fill fills the buffer with the given cell and rectangle. 360func (b * Buffer )Fill (c * Cell ) { 361b .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 ) { 366cellWidth := 1 367if c != nil && c .Width > 1 { 368cellWidth = c .Width 369 } 370for y := area .Min .Y ;y < area .Max .Y ;y ++ { 371for x := area .Min .X ;x < area .Max .X ;x += cellWidth { 372b .SetCell (x ,y ,c ) 373 } 374 } 375} 376 377// Clear clears the buffer with space cells and rectangle. 378func (b * Buffer )Clear () { 379area := b .Bounds () 380for y := area .Min .Y ;y < area .Max .Y ;y ++ { 381for x := area .Min .X ;x < area .Max .X ;x ++ { 382b .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 ) { 390b .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 { 396bounds := b .Bounds () 397if ! area .In (bounds ) { 398return nil 399 } 400n := NewBuffer (area .Dx (),area .Dy ()) 401for y := area .Min .Y ;y < area .Max .Y ;y ++ { 402for x := area .Min .X ;x < area .Max .X ; { 403c := b .CellAt (x ,y ) 404if c == nil || c .IsZero () { 405x ++ 406continue 407 } 408n .SetCell (x - area .Min .X ,y - area .Min .Y ,c ) 409x += max (c .Width ,1 ) 410 } 411 } 412return n 413} 414 415// Clone clones the entire buffer into a new buffer. 416func (b * Buffer )Clone ()* Buffer { 417return 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 ) { 423if area .Empty () { 424return 425 } 426 427// Ensure the area is within the bounds of the screen. 428bounds := scr .Bounds () 429if ! area .Overlaps (bounds ) { 430return 431 } 432 433for y := area .Min .Y ;y < area .Max .Y ;y ++ { 434for x := area .Min .X ;x < area .Max .X ; { 435c := b .CellAt (x - area .Min .X ,y - area .Min .Y ) 436if c == nil || c .IsZero () { 437x ++ 438continue 439 } 440scr .SetCell (x ,y ,c ) 441width := c .Width 442if width <= 0 { 443width = 1 444 } 445x += 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 ) { 457b .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 ) { 465if n <= 0 || y < area .Min .Y || y >= area .Max .Y || y >= b .Height () { 466return 467 } 468 469// Limit number of lines to insert to available space 470if y + n > area .Max .Y { 471n = area .Max .Y - y 472 } 473 474// Move existing lines down within the bounds 475for i := area .Max .Y - 1 ;i >= y + n ;i -- { 476for 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. 478b .Lines [i ][x ]= b .Lines [i - n ][x ] 479 } 480 } 481 482// Clear the newly inserted lines within bounds 483for i := y ;i < y + n ;i ++ { 484for x := area .Min .X ;x < area .Max .X ;x ++ { 485b .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 ) { 496if n <= 0 || y < area .Min .Y || y >= area .Max .Y || y >= b .Height () { 497return 498 } 499 500// Limit deletion count to available space in scroll region 501if n > area .Max .Y - y { 502n = area .Max .Y - y 503 } 504 505// Shift cells up within the bounds 506for dst := y ;dst < area .Max .Y - n ;dst ++ { 507src := dst + n 508for 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. 510b .Lines [dst ][x ]= b .Lines [src ][x ] 511 } 512 } 513 514// Fill the bottom n lines with blank cells 515for i := area .Max .Y - n ;i < area .Max .Y ;i ++ { 516for x := area .Min .X ;x < area .Max .X ;x ++ { 517b .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 ) { 526b .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 ) { 534b .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 ) { 541if n <= 0 || y < area .Min .Y || y >= area .Max .Y || y >= b .Height ()|| 542x < area .Min .X || x >= area .Max .X || x >= b .Width () { 543return 544 } 545 546// Limit number of cells to insert to available space 547if x + n > area .Max .X { 548n = area .Max .X - x 549 } 550 551// Move existing cells within rectangle bounds to the right 552for 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. 555b .Lines [y ][i ]= b .Lines [y ][i - n ] 556 } 557 558// Clear the newly inserted cells within rectangle bounds 559for i := x ;i < x + n && i < area .Max .X ;i ++ { 560b .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 ) { 569b .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 ) { 576if n <= 0 || y < area .Min .Y || y >= area .Max .Y || y >= b .Height ()|| 577x < area .Min .X || x >= area .Max .X || x >= b .Width () { 578return 579 } 580 581// Calculate how many positions we can actually delete 582remainingCells := area .Max .X - x 583if n > remainingCells { 584n = remainingCells 585 } 586 587// Shift the remaining cells to the left 588for i := x ;i < area .Max .X - n ;i ++ { 589if i + n < area .Max .X { 590// We need to use SetCell here to ensure we blank out any wide 591// cells we encounter. 592b .SetCell (i ,y ,b .CellAt (i + n ,y )) 593 } 594 } 595 596// Fill the vacated positions with the given cell 597for i := area .Max .X - n ;i < area .Max .X ;i ++ { 598b .SetCell (i ,y ,c ) 599 } 600} 601 602// ScreenBuffer is a buffer that can be used as a [Screen]. 603type ScreenBuffer struct { 604* RenderBuffer 605Method 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 { 615return ScreenBuffer { 616RenderBuffer :NewRenderBuffer (width ,height ), 617Method :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 { 624return 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 { 630lines := strings .Split (s ,"\n" ) 631for i ,line := range lines { 632// Check if we have a trailing '\r' and preserve it 633hasCR := strings .HasSuffix (line ,"\r" ) 634if hasCR { 635line = strings .TrimSuffix (line ,"\r" ) 636 } 637line = strings .TrimRight (line ," " ) 638if hasCR { 639line = line + "\r" 640 } 641lines [i ]= line 642 } 643return 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. 656Touched []* LineData 657} 658 659// NewRenderBuffer creates a new [RenderBuffer] with the given width and height. 660func NewRenderBuffer (width ,height int )* RenderBuffer { 661return & RenderBuffer { 662Buffer :NewBuffer (width ,height ), 663Touched :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 ) { 669if y < 0 || y >= len (b .Lines ) { 670return 671 } 672 673b .growTouched () 674 675ch := b .Touched [y ] 676if ch == nil { 677ch = & LineData {FirstCell :x ,LastCell :x + n } 678 }else { 679ch .FirstCell = min (ch .FirstCell ,x ) 680ch .LastCell = max (ch .LastCell ,x + n ) 681 } 682b .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 ) { 687b .TouchLine (x ,y ,0 ) 688} 689 690// TouchedLines returns the number of touched lines in the buffer. 691func (b * RenderBuffer )TouchedLines ()int { 692count := 0 693for _ ,t := range b .Touched { 694if t != nil { 695count ++ 696 } 697 } 698return 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 ) { 704if p := b .CellAt (x ,y );! cellEqual (p ,c ) { 705width := 1 706if c != nil && c .Width > 0 { 707width = c .Width 708 } 709if p != nil && p .Width > 0 { 710width = max (width ,p .Width ) 711 } 712b .TouchLine (x ,y ,width ) 713 } 714b .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 ) { 723b .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 ) { 731b .Buffer .InsertLineArea (y ,n ,c ,area ) 732for i := area .Min .Y ;i < area .Max .Y ;i ++ { 733b .TouchLine (area .Min .X ,i ,area .Max .X - area .Min .X ) 734b .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 ) { 742b .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 ) { 751b .Buffer .DeleteLineArea (y ,n ,c ,area ) 752for i := area .Min .Y ;i < area .Max .Y ;i ++ { 753b .TouchLine (area .Min .X ,i ,area .Max .X - area .Min .X ) 754b .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 ) { 763b .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 ) { 770b .Buffer .InsertCellArea (x ,y ,n ,c ,area ) 771if x + n > area .Max .X { 772n = area .Max .X - x 773 } 774b .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 ) { 782b .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 ) { 789b .Buffer .DeleteCellArea (x ,y ,n ,c ,area ) 790remainingCells := area .Max .X - x 791if n > remainingCells { 792n = remainingCells 793 } 794b .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 ) { 800curWidth ,curHeight := b .Width (),b .Height () 801b .Buffer .Resize (width ,height ) 802 803if width > curWidth { 804for y := range min (curHeight ,height ) { 805b .TouchLine (curWidth ,y ,width - curWidth ) 806 } 807 } 808for y := curHeight ;y < height ;y ++ { 809b .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 () { 815b .Buffer .Clear () 816w := b .Width () 817for y := range b .Lines { 818b .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 ) { 825b .Buffer .ClearArea (area ) 826w := area .Max .X - area .Min .X 827for y := area .Min .Y ;y < area .Max .Y ;y ++ { 828b .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 ) { 834b .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 ) { 840b .Buffer .FillArea (c ,area ) 841w := area .Max .X - area .Min .X 842for y := area .Min .Y ;y < area .Max .Y ;y ++ { 843b .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 () { 856if len (b .Touched )>= len (b .Lines ) { 857return 858 } 859touched := make ([]* LineData ,len (b .Lines )) 860copy (touched ,b .Touched ) 861b .Touched = touched 862}