forked from toolshed/abra
build: go 1.24
We were running behind and there were quite some deprecations to update. This was mostly in the upstream copy/pasta package but seems quite minimal.
This commit is contained in:
21
vendor/github.com/charmbracelet/x/cellbuf/LICENSE
generated
vendored
Normal file
21
vendor/github.com/charmbracelet/x/cellbuf/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Charmbracelet, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
473
vendor/github.com/charmbracelet/x/cellbuf/buffer.go
generated
vendored
Normal file
473
vendor/github.com/charmbracelet/x/cellbuf/buffer.go
generated
vendored
Normal file
@ -0,0 +1,473 @@
|
||||
package cellbuf
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
// NewCell returns a new cell. This is a convenience function that initializes a
|
||||
// new cell with the given content. The cell's width is determined by the
|
||||
// content using [runewidth.RuneWidth].
|
||||
// This will only account for the first combined rune in the content. If the
|
||||
// content is empty, it will return an empty cell with a width of 0.
|
||||
func NewCell(r rune, comb ...rune) (c *Cell) {
|
||||
c = new(Cell)
|
||||
c.Rune = r
|
||||
c.Width = runewidth.RuneWidth(r)
|
||||
for _, r := range comb {
|
||||
if runewidth.RuneWidth(r) > 0 {
|
||||
break
|
||||
}
|
||||
c.Comb = append(c.Comb, r)
|
||||
}
|
||||
c.Comb = comb
|
||||
c.Width = runewidth.StringWidth(string(append([]rune{r}, comb...)))
|
||||
return
|
||||
}
|
||||
|
||||
// NewCellString returns a new cell with the given string content. This is a
|
||||
// convenience function that initializes a new cell with the given content. The
|
||||
// cell's width is determined by the content using [runewidth.StringWidth].
|
||||
// This will only use the first combined rune in the string. If the string is
|
||||
// empty, it will return an empty cell with a width of 0.
|
||||
func NewCellString(s string) (c *Cell) {
|
||||
c = new(Cell)
|
||||
for i, r := range s {
|
||||
if i == 0 {
|
||||
c.Rune = r
|
||||
// We only care about the first rune's width
|
||||
c.Width = runewidth.RuneWidth(r)
|
||||
} else {
|
||||
if runewidth.RuneWidth(r) > 0 {
|
||||
break
|
||||
}
|
||||
c.Comb = append(c.Comb, r)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NewGraphemeCell returns a new cell. This is a convenience function that
|
||||
// initializes a new cell with the given content. The cell's width is determined
|
||||
// by the content using [uniseg.FirstGraphemeClusterInString].
|
||||
// This is used when the content is a grapheme cluster i.e. a sequence of runes
|
||||
// that form a single visual unit.
|
||||
// This will only return the first grapheme cluster in the string. If the
|
||||
// string is empty, it will return an empty cell with a width of 0.
|
||||
func NewGraphemeCell(s string) (c *Cell) {
|
||||
g, _, w, _ := uniseg.FirstGraphemeClusterInString(s, -1)
|
||||
return newGraphemeCell(g, w)
|
||||
}
|
||||
|
||||
func newGraphemeCell(s string, w int) (c *Cell) {
|
||||
c = new(Cell)
|
||||
c.Width = w
|
||||
for i, r := range s {
|
||||
if i == 0 {
|
||||
c.Rune = r
|
||||
} else {
|
||||
c.Comb = append(c.Comb, r)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Line represents a line in the terminal.
|
||||
// A nil cell represents an blank cell, a cell with a space character and a
|
||||
// width of 1.
|
||||
// If a cell has no content and a width of 0, it is a placeholder for a wide
|
||||
// cell.
|
||||
type Line []*Cell
|
||||
|
||||
// Width returns the width of the line.
|
||||
func (l Line) Width() int {
|
||||
return len(l)
|
||||
}
|
||||
|
||||
// Len returns the length of the line.
|
||||
func (l Line) Len() int {
|
||||
return len(l)
|
||||
}
|
||||
|
||||
// String returns the string representation of the line. Any trailing spaces
|
||||
// are removed.
|
||||
func (l Line) String() (s string) {
|
||||
for _, c := range l {
|
||||
if c == nil {
|
||||
s += " "
|
||||
} else if c.Empty() {
|
||||
continue
|
||||
} else {
|
||||
s += c.String()
|
||||
}
|
||||
}
|
||||
s = strings.TrimRight(s, " ")
|
||||
return
|
||||
}
|
||||
|
||||
// At returns the cell at the given x position.
|
||||
// If the cell does not exist, it returns nil.
|
||||
func (l Line) At(x int) *Cell {
|
||||
if x < 0 || x >= len(l) {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := l[x]
|
||||
if c == nil {
|
||||
newCell := BlankCell
|
||||
return &newCell
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Set sets the cell at the given x position. If a wide cell is given, it will
|
||||
// set the cell and the following cells to [EmptyCell]. It returns true if the
|
||||
// cell was set.
|
||||
func (l Line) Set(x int, c *Cell) bool {
|
||||
return l.set(x, c, true)
|
||||
}
|
||||
|
||||
func (l Line) set(x int, c *Cell, clone bool) bool {
|
||||
width := l.Width()
|
||||
if x < 0 || x >= width {
|
||||
return false
|
||||
}
|
||||
|
||||
// When a wide cell is partially overwritten, we need
|
||||
// to fill the rest of the cell with space cells to
|
||||
// avoid rendering issues.
|
||||
prev := l.At(x)
|
||||
if prev != nil && prev.Width > 1 {
|
||||
// Writing to the first wide cell
|
||||
for j := 0; j < prev.Width && x+j < l.Width(); j++ {
|
||||
l[x+j] = prev.Clone().Blank()
|
||||
}
|
||||
} else if prev != nil && prev.Width == 0 {
|
||||
// Writing to wide cell placeholders
|
||||
for j := 1; j < maxCellWidth && x-j >= 0; j++ {
|
||||
wide := l.At(x - j)
|
||||
if wide != nil && wide.Width > 1 && j < wide.Width {
|
||||
for k := 0; k < wide.Width; k++ {
|
||||
l[x-j+k] = wide.Clone().Blank()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if clone && c != nil {
|
||||
// Clone the cell if not nil.
|
||||
c = c.Clone()
|
||||
}
|
||||
|
||||
if c != nil && x+c.Width > width {
|
||||
// If the cell is too wide, we write blanks with the same style.
|
||||
for i := 0; i < c.Width && x+i < width; i++ {
|
||||
l[x+i] = c.Clone().Blank()
|
||||
}
|
||||
} else {
|
||||
l[x] = c
|
||||
|
||||
// Mark wide cells with an empty cell zero width
|
||||
// We set the wide cell down below
|
||||
if c != nil && c.Width > 1 {
|
||||
for j := 1; j < c.Width && x+j < l.Width(); j++ {
|
||||
var wide Cell
|
||||
l[x+j] = &wide
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Buffer is a 2D grid of cells representing a screen or terminal.
|
||||
type Buffer struct {
|
||||
// Lines holds the lines of the buffer.
|
||||
Lines []Line
|
||||
}
|
||||
|
||||
// NewBuffer creates a new buffer with the given width and height.
|
||||
// This is a convenience function that initializes a new buffer and resizes it.
|
||||
func NewBuffer(width int, height int) *Buffer {
|
||||
b := new(Buffer)
|
||||
b.Resize(width, height)
|
||||
return b
|
||||
}
|
||||
|
||||
// String returns the string representation of the buffer.
|
||||
func (b *Buffer) String() (s string) {
|
||||
for i, l := range b.Lines {
|
||||
s += l.String()
|
||||
if i < len(b.Lines)-1 {
|
||||
s += "\r\n"
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Line returns a pointer to the line at the given y position.
|
||||
// If the line does not exist, it returns nil.
|
||||
func (b *Buffer) Line(y int) Line {
|
||||
if y < 0 || y >= len(b.Lines) {
|
||||
return nil
|
||||
}
|
||||
return b.Lines[y]
|
||||
}
|
||||
|
||||
// Cell implements Screen.
|
||||
func (b *Buffer) Cell(x int, y int) *Cell {
|
||||
if y < 0 || y >= len(b.Lines) {
|
||||
return nil
|
||||
}
|
||||
return b.Lines[y].At(x)
|
||||
}
|
||||
|
||||
// maxCellWidth is the maximum width a terminal cell can get.
|
||||
const maxCellWidth = 4
|
||||
|
||||
// SetCell sets the cell at the given x, y position.
|
||||
func (b *Buffer) SetCell(x, y int, c *Cell) bool {
|
||||
return b.setCell(x, y, c, true)
|
||||
}
|
||||
|
||||
// setCell sets the cell at the given x, y position. This will always clone and
|
||||
// allocates a new cell if c is not nil.
|
||||
func (b *Buffer) setCell(x, y int, c *Cell, clone bool) bool {
|
||||
if y < 0 || y >= len(b.Lines) {
|
||||
return false
|
||||
}
|
||||
return b.Lines[y].set(x, c, clone)
|
||||
}
|
||||
|
||||
// Height implements Screen.
|
||||
func (b *Buffer) Height() int {
|
||||
return len(b.Lines)
|
||||
}
|
||||
|
||||
// Width implements Screen.
|
||||
func (b *Buffer) Width() int {
|
||||
if len(b.Lines) == 0 {
|
||||
return 0
|
||||
}
|
||||
return b.Lines[0].Width()
|
||||
}
|
||||
|
||||
// Bounds returns the bounds of the buffer.
|
||||
func (b *Buffer) Bounds() Rectangle {
|
||||
return Rect(0, 0, b.Width(), b.Height())
|
||||
}
|
||||
|
||||
// Resize resizes the buffer to the given width and height.
|
||||
func (b *Buffer) Resize(width int, height int) {
|
||||
if width == 0 || height == 0 {
|
||||
b.Lines = nil
|
||||
return
|
||||
}
|
||||
|
||||
if width > b.Width() {
|
||||
line := make(Line, width-b.Width())
|
||||
for i := range b.Lines {
|
||||
b.Lines[i] = append(b.Lines[i], line...)
|
||||
}
|
||||
} else if width < b.Width() {
|
||||
for i := range b.Lines {
|
||||
b.Lines[i] = b.Lines[i][:width]
|
||||
}
|
||||
}
|
||||
|
||||
if height > len(b.Lines) {
|
||||
for i := len(b.Lines); i < height; i++ {
|
||||
b.Lines = append(b.Lines, make(Line, width))
|
||||
}
|
||||
} else if height < len(b.Lines) {
|
||||
b.Lines = b.Lines[:height]
|
||||
}
|
||||
}
|
||||
|
||||
// FillRect fills the buffer with the given cell and rectangle.
|
||||
func (b *Buffer) FillRect(c *Cell, rect Rectangle) {
|
||||
cellWidth := 1
|
||||
if c != nil && c.Width > 1 {
|
||||
cellWidth = c.Width
|
||||
}
|
||||
for y := rect.Min.Y; y < rect.Max.Y; y++ {
|
||||
for x := rect.Min.X; x < rect.Max.X; x += cellWidth {
|
||||
b.setCell(x, y, c, false) //nolint:errcheck
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill fills the buffer with the given cell and rectangle.
|
||||
func (b *Buffer) Fill(c *Cell) {
|
||||
b.FillRect(c, b.Bounds())
|
||||
}
|
||||
|
||||
// Clear clears the buffer with space cells and rectangle.
|
||||
func (b *Buffer) Clear() {
|
||||
b.ClearRect(b.Bounds())
|
||||
}
|
||||
|
||||
// ClearRect clears the buffer with space cells within the specified
|
||||
// rectangles. Only cells within the rectangle's bounds are affected.
|
||||
func (b *Buffer) ClearRect(rect Rectangle) {
|
||||
b.FillRect(nil, rect)
|
||||
}
|
||||
|
||||
// InsertLine inserts n lines at the given line position, with the given
|
||||
// optional cell, within the specified rectangles. If no rectangles are
|
||||
// specified, it inserts lines in the entire buffer. Only cells within the
|
||||
// rectangle's horizontal bounds are affected. Lines are pushed out of the
|
||||
// rectangle bounds and lost. This follows terminal [ansi.IL] behavior.
|
||||
// It returns the pushed out lines.
|
||||
func (b *Buffer) InsertLine(y, n int, c *Cell) {
|
||||
b.InsertLineRect(y, n, c, b.Bounds())
|
||||
}
|
||||
|
||||
// InsertLineRect inserts new lines at the given line position, with the
|
||||
// given optional cell, within the rectangle bounds. Only cells within the
|
||||
// rectangle's horizontal bounds are affected. Lines are pushed out of the
|
||||
// rectangle bounds and lost. This follows terminal [ansi.IL] behavior.
|
||||
func (b *Buffer) InsertLineRect(y, n int, c *Cell, rect Rectangle) {
|
||||
if n <= 0 || y < rect.Min.Y || y >= rect.Max.Y || y >= b.Height() {
|
||||
return
|
||||
}
|
||||
|
||||
// Limit number of lines to insert to available space
|
||||
if y+n > rect.Max.Y {
|
||||
n = rect.Max.Y - y
|
||||
}
|
||||
|
||||
// Move existing lines down within the bounds
|
||||
for i := rect.Max.Y - 1; i >= y+n; i-- {
|
||||
for x := rect.Min.X; x < rect.Max.X; x++ {
|
||||
// We don't need to clone c here because we're just moving lines down.
|
||||
b.setCell(x, i, b.Lines[i-n][x], false)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the newly inserted lines within bounds
|
||||
for i := y; i < y+n; i++ {
|
||||
for x := rect.Min.X; x < rect.Max.X; x++ {
|
||||
b.setCell(x, i, c, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteLineRect deletes lines at the given line position, with the given
|
||||
// optional cell, within the rectangle bounds. Only cells within the
|
||||
// rectangle's bounds are affected. Lines are shifted up within the bounds and
|
||||
// new blank lines are created at the bottom. This follows terminal [ansi.DL]
|
||||
// behavior.
|
||||
func (b *Buffer) DeleteLineRect(y, n int, c *Cell, rect Rectangle) {
|
||||
if n <= 0 || y < rect.Min.Y || y >= rect.Max.Y || y >= b.Height() {
|
||||
return
|
||||
}
|
||||
|
||||
// Limit deletion count to available space in scroll region
|
||||
if n > rect.Max.Y-y {
|
||||
n = rect.Max.Y - y
|
||||
}
|
||||
|
||||
// Shift cells up within the bounds
|
||||
for dst := y; dst < rect.Max.Y-n; dst++ {
|
||||
src := dst + n
|
||||
for x := rect.Min.X; x < rect.Max.X; x++ {
|
||||
// We don't need to clone c here because we're just moving cells up.
|
||||
// b.lines[dst][x] = b.lines[src][x]
|
||||
b.setCell(x, dst, b.Lines[src][x], false)
|
||||
}
|
||||
}
|
||||
|
||||
// Fill the bottom n lines with blank cells
|
||||
for i := rect.Max.Y - n; i < rect.Max.Y; i++ {
|
||||
for x := rect.Min.X; x < rect.Max.X; x++ {
|
||||
b.setCell(x, i, c, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteLine deletes n lines at the given line position, with the given
|
||||
// optional cell, within the specified rectangles. If no rectangles are
|
||||
// specified, it deletes lines in the entire buffer.
|
||||
func (b *Buffer) DeleteLine(y, n int, c *Cell) {
|
||||
b.DeleteLineRect(y, n, c, b.Bounds())
|
||||
}
|
||||
|
||||
// InsertCell inserts new cells at the given position, with the given optional
|
||||
// cell, within the specified rectangles. If no rectangles are specified, it
|
||||
// inserts cells in the entire buffer. This follows terminal [ansi.ICH]
|
||||
// behavior.
|
||||
func (b *Buffer) InsertCell(x, y, n int, c *Cell) {
|
||||
b.InsertCellRect(x, y, n, c, b.Bounds())
|
||||
}
|
||||
|
||||
// InsertCellRect inserts new cells at the given position, with the given
|
||||
// optional cell, within the rectangle bounds. Only cells within the
|
||||
// rectangle's bounds are affected, following terminal [ansi.ICH] behavior.
|
||||
func (b *Buffer) InsertCellRect(x, y, n int, c *Cell, rect Rectangle) {
|
||||
if n <= 0 || y < rect.Min.Y || y >= rect.Max.Y || y >= b.Height() ||
|
||||
x < rect.Min.X || x >= rect.Max.X || x >= b.Width() {
|
||||
return
|
||||
}
|
||||
|
||||
// Limit number of cells to insert to available space
|
||||
if x+n > rect.Max.X {
|
||||
n = rect.Max.X - x
|
||||
}
|
||||
|
||||
// Move existing cells within rectangle bounds to the right
|
||||
for i := rect.Max.X - 1; i >= x+n && i-n >= rect.Min.X; i-- {
|
||||
// We don't need to clone c here because we're just moving cells to the
|
||||
// right.
|
||||
// b.lines[y][i] = b.lines[y][i-n]
|
||||
b.setCell(i, y, b.Lines[y][i-n], false)
|
||||
}
|
||||
|
||||
// Clear the newly inserted cells within rectangle bounds
|
||||
for i := x; i < x+n && i < rect.Max.X; i++ {
|
||||
b.setCell(i, y, c, true)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteCell deletes cells at the given position, with the given optional
|
||||
// cell, within the specified rectangles. If no rectangles are specified, it
|
||||
// deletes cells in the entire buffer. This follows terminal [ansi.DCH]
|
||||
// behavior.
|
||||
func (b *Buffer) DeleteCell(x, y, n int, c *Cell) {
|
||||
b.DeleteCellRect(x, y, n, c, b.Bounds())
|
||||
}
|
||||
|
||||
// DeleteCellRect deletes cells at the given position, with the given
|
||||
// optional cell, within the rectangle bounds. Only cells within the
|
||||
// rectangle's bounds are affected, following terminal [ansi.DCH] behavior.
|
||||
func (b *Buffer) DeleteCellRect(x, y, n int, c *Cell, rect Rectangle) {
|
||||
if n <= 0 || y < rect.Min.Y || y >= rect.Max.Y || y >= b.Height() ||
|
||||
x < rect.Min.X || x >= rect.Max.X || x >= b.Width() {
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate how many positions we can actually delete
|
||||
remainingCells := rect.Max.X - x
|
||||
if n > remainingCells {
|
||||
n = remainingCells
|
||||
}
|
||||
|
||||
// Shift the remaining cells to the left
|
||||
for i := x; i < rect.Max.X-n; i++ {
|
||||
if i+n < rect.Max.X {
|
||||
// We don't need to clone c here because we're just moving cells to
|
||||
// the left.
|
||||
// b.lines[y][i] = b.lines[y][i+n]
|
||||
b.setCell(i, y, b.Lines[y][i+n], false)
|
||||
}
|
||||
}
|
||||
|
||||
// Fill the vacated positions with the given cell
|
||||
for i := rect.Max.X - n; i < rect.Max.X; i++ {
|
||||
b.setCell(i, y, c, true)
|
||||
}
|
||||
}
|
508
vendor/github.com/charmbracelet/x/cellbuf/cell.go
generated
vendored
Normal file
508
vendor/github.com/charmbracelet/x/cellbuf/cell.go
generated
vendored
Normal file
@ -0,0 +1,508 @@
|
||||
package cellbuf
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/x/ansi"
|
||||
)
|
||||
|
||||
var (
|
||||
// BlankCell is a cell with a single space, width of 1, and no style or link.
|
||||
BlankCell = Cell{Rune: ' ', Width: 1}
|
||||
|
||||
// EmptyCell is just an empty cell used for comparisons and as a placeholder
|
||||
// for wide cells.
|
||||
EmptyCell = Cell{}
|
||||
)
|
||||
|
||||
// Cell represents a single cell in the terminal screen.
|
||||
type Cell struct {
|
||||
// The style of the cell. Nil style means no style. Zero value prints a
|
||||
// reset sequence.
|
||||
Style Style
|
||||
|
||||
// Link is the hyperlink of the cell.
|
||||
Link Link
|
||||
|
||||
// Comb is the combining runes of the cell. This is nil if the cell is a
|
||||
// single rune or if it's a zero width cell that is part of a wider cell.
|
||||
Comb []rune
|
||||
|
||||
// Width is the mono-space width of the grapheme cluster.
|
||||
Width int
|
||||
|
||||
// Rune is the main rune of the cell. This is zero if the cell is part of a
|
||||
// wider cell.
|
||||
Rune rune
|
||||
}
|
||||
|
||||
// Append appends runes to the cell without changing the width. This is useful
|
||||
// when we want to use the cell to store escape sequences or other runes that
|
||||
// don't affect the width of the cell.
|
||||
func (c *Cell) Append(r ...rune) {
|
||||
for i, r := range r {
|
||||
if i == 0 && c.Rune == 0 {
|
||||
c.Rune = r
|
||||
continue
|
||||
}
|
||||
c.Comb = append(c.Comb, r)
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the string content of the cell excluding any styles, links,
|
||||
// and escape sequences.
|
||||
func (c Cell) String() string {
|
||||
if c.Rune == 0 {
|
||||
return ""
|
||||
}
|
||||
if len(c.Comb) == 0 {
|
||||
return string(c.Rune)
|
||||
}
|
||||
return string(append([]rune{c.Rune}, c.Comb...))
|
||||
}
|
||||
|
||||
// Equal returns whether the cell is equal to the other cell.
|
||||
func (c *Cell) Equal(o *Cell) bool {
|
||||
return o != nil &&
|
||||
c.Width == o.Width &&
|
||||
c.Rune == o.Rune &&
|
||||
runesEqual(c.Comb, o.Comb) &&
|
||||
c.Style.Equal(&o.Style) &&
|
||||
c.Link.Equal(&o.Link)
|
||||
}
|
||||
|
||||
// Empty returns whether the cell is an empty cell. An empty cell is a cell
|
||||
// with a width of 0, a rune of 0, and no combining runes.
|
||||
func (c Cell) Empty() bool {
|
||||
return c.Width == 0 &&
|
||||
c.Rune == 0 &&
|
||||
len(c.Comb) == 0
|
||||
}
|
||||
|
||||
// Reset resets the cell to the default state zero value.
|
||||
func (c *Cell) Reset() {
|
||||
c.Rune = 0
|
||||
c.Comb = nil
|
||||
c.Width = 0
|
||||
c.Style.Reset()
|
||||
c.Link.Reset()
|
||||
}
|
||||
|
||||
// Clear returns whether the cell consists of only attributes that don't
|
||||
// affect appearance of a space character.
|
||||
func (c *Cell) Clear() bool {
|
||||
return c.Rune == ' ' && len(c.Comb) == 0 && c.Width == 1 && c.Style.Clear() && c.Link.Empty()
|
||||
}
|
||||
|
||||
// Clone returns a copy of the cell.
|
||||
func (c *Cell) Clone() (n *Cell) {
|
||||
n = new(Cell)
|
||||
*n = *c
|
||||
return
|
||||
}
|
||||
|
||||
// Blank makes the cell a blank cell by setting the rune to a space, comb to
|
||||
// nil, and the width to 1.
|
||||
func (c *Cell) Blank() *Cell {
|
||||
c.Rune = ' '
|
||||
c.Comb = nil
|
||||
c.Width = 1
|
||||
return c
|
||||
}
|
||||
|
||||
// Link represents a hyperlink in the terminal screen.
|
||||
type Link struct {
|
||||
URL string
|
||||
Params string
|
||||
}
|
||||
|
||||
// String returns a string representation of the hyperlink.
|
||||
func (h Link) String() string {
|
||||
return h.URL
|
||||
}
|
||||
|
||||
// Reset resets the hyperlink to the default state zero value.
|
||||
func (h *Link) Reset() {
|
||||
h.URL = ""
|
||||
h.Params = ""
|
||||
}
|
||||
|
||||
// Equal returns whether the hyperlink is equal to the other hyperlink.
|
||||
func (h *Link) Equal(o *Link) bool {
|
||||
return o != nil && h.URL == o.URL && h.Params == o.Params
|
||||
}
|
||||
|
||||
// Empty returns whether the hyperlink is empty.
|
||||
func (h Link) Empty() bool {
|
||||
return h.URL == "" && h.Params == ""
|
||||
}
|
||||
|
||||
// AttrMask is a bitmask for text attributes that can change the look of text.
|
||||
// These attributes can be combined to create different styles.
|
||||
type AttrMask uint8
|
||||
|
||||
// These are the available text attributes that can be combined to create
|
||||
// different styles.
|
||||
const (
|
||||
BoldAttr AttrMask = 1 << iota
|
||||
FaintAttr
|
||||
ItalicAttr
|
||||
SlowBlinkAttr
|
||||
RapidBlinkAttr
|
||||
ReverseAttr
|
||||
ConcealAttr
|
||||
StrikethroughAttr
|
||||
|
||||
ResetAttr AttrMask = 0
|
||||
)
|
||||
|
||||
// Contains returns whether the attribute mask contains the attribute.
|
||||
func (a AttrMask) Contains(attr AttrMask) bool {
|
||||
return a&attr == attr
|
||||
}
|
||||
|
||||
// UnderlineStyle is the style of underline to use for text.
|
||||
type UnderlineStyle = ansi.UnderlineStyle
|
||||
|
||||
// These are the available underline styles.
|
||||
const (
|
||||
NoUnderline = ansi.NoUnderlineStyle
|
||||
SingleUnderline = ansi.SingleUnderlineStyle
|
||||
DoubleUnderline = ansi.DoubleUnderlineStyle
|
||||
CurlyUnderline = ansi.CurlyUnderlineStyle
|
||||
DottedUnderline = ansi.DottedUnderlineStyle
|
||||
DashedUnderline = ansi.DashedUnderlineStyle
|
||||
)
|
||||
|
||||
// Style represents the Style of a cell.
|
||||
type Style struct {
|
||||
Fg ansi.Color
|
||||
Bg ansi.Color
|
||||
Ul ansi.Color
|
||||
Attrs AttrMask
|
||||
UlStyle UnderlineStyle
|
||||
}
|
||||
|
||||
// Sequence returns the ANSI sequence that sets the style.
|
||||
func (s Style) Sequence() string {
|
||||
if s.Empty() {
|
||||
return ansi.ResetStyle
|
||||
}
|
||||
|
||||
var b ansi.Style
|
||||
|
||||
if s.Attrs != 0 {
|
||||
if s.Attrs&BoldAttr != 0 {
|
||||
b = b.Bold()
|
||||
}
|
||||
if s.Attrs&FaintAttr != 0 {
|
||||
b = b.Faint()
|
||||
}
|
||||
if s.Attrs&ItalicAttr != 0 {
|
||||
b = b.Italic()
|
||||
}
|
||||
if s.Attrs&SlowBlinkAttr != 0 {
|
||||
b = b.SlowBlink()
|
||||
}
|
||||
if s.Attrs&RapidBlinkAttr != 0 {
|
||||
b = b.RapidBlink()
|
||||
}
|
||||
if s.Attrs&ReverseAttr != 0 {
|
||||
b = b.Reverse()
|
||||
}
|
||||
if s.Attrs&ConcealAttr != 0 {
|
||||
b = b.Conceal()
|
||||
}
|
||||
if s.Attrs&StrikethroughAttr != 0 {
|
||||
b = b.Strikethrough()
|
||||
}
|
||||
}
|
||||
if s.UlStyle != NoUnderline {
|
||||
switch s.UlStyle {
|
||||
case SingleUnderline:
|
||||
b = b.Underline()
|
||||
case DoubleUnderline:
|
||||
b = b.DoubleUnderline()
|
||||
case CurlyUnderline:
|
||||
b = b.CurlyUnderline()
|
||||
case DottedUnderline:
|
||||
b = b.DottedUnderline()
|
||||
case DashedUnderline:
|
||||
b = b.DashedUnderline()
|
||||
}
|
||||
}
|
||||
if s.Fg != nil {
|
||||
b = b.ForegroundColor(s.Fg)
|
||||
}
|
||||
if s.Bg != nil {
|
||||
b = b.BackgroundColor(s.Bg)
|
||||
}
|
||||
if s.Ul != nil {
|
||||
b = b.UnderlineColor(s.Ul)
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// DiffSequence returns the ANSI sequence that sets the style as a diff from
|
||||
// another style.
|
||||
func (s Style) DiffSequence(o Style) string {
|
||||
if o.Empty() {
|
||||
return s.Sequence()
|
||||
}
|
||||
|
||||
var b ansi.Style
|
||||
|
||||
if !colorEqual(s.Fg, o.Fg) {
|
||||
b = b.ForegroundColor(s.Fg)
|
||||
}
|
||||
|
||||
if !colorEqual(s.Bg, o.Bg) {
|
||||
b = b.BackgroundColor(s.Bg)
|
||||
}
|
||||
|
||||
if !colorEqual(s.Ul, o.Ul) {
|
||||
b = b.UnderlineColor(s.Ul)
|
||||
}
|
||||
|
||||
var (
|
||||
noBlink bool
|
||||
isNormal bool
|
||||
)
|
||||
|
||||
if s.Attrs != o.Attrs {
|
||||
if s.Attrs&BoldAttr != o.Attrs&BoldAttr {
|
||||
if s.Attrs&BoldAttr != 0 {
|
||||
b = b.Bold()
|
||||
} else if !isNormal {
|
||||
isNormal = true
|
||||
b = b.NormalIntensity()
|
||||
}
|
||||
}
|
||||
if s.Attrs&FaintAttr != o.Attrs&FaintAttr {
|
||||
if s.Attrs&FaintAttr != 0 {
|
||||
b = b.Faint()
|
||||
} else if !isNormal {
|
||||
b = b.NormalIntensity()
|
||||
}
|
||||
}
|
||||
if s.Attrs&ItalicAttr != o.Attrs&ItalicAttr {
|
||||
if s.Attrs&ItalicAttr != 0 {
|
||||
b = b.Italic()
|
||||
} else {
|
||||
b = b.NoItalic()
|
||||
}
|
||||
}
|
||||
if s.Attrs&SlowBlinkAttr != o.Attrs&SlowBlinkAttr {
|
||||
if s.Attrs&SlowBlinkAttr != 0 {
|
||||
b = b.SlowBlink()
|
||||
} else if !noBlink {
|
||||
noBlink = true
|
||||
b = b.NoBlink()
|
||||
}
|
||||
}
|
||||
if s.Attrs&RapidBlinkAttr != o.Attrs&RapidBlinkAttr {
|
||||
if s.Attrs&RapidBlinkAttr != 0 {
|
||||
b = b.RapidBlink()
|
||||
} else if !noBlink {
|
||||
b = b.NoBlink()
|
||||
}
|
||||
}
|
||||
if s.Attrs&ReverseAttr != o.Attrs&ReverseAttr {
|
||||
if s.Attrs&ReverseAttr != 0 {
|
||||
b = b.Reverse()
|
||||
} else {
|
||||
b = b.NoReverse()
|
||||
}
|
||||
}
|
||||
if s.Attrs&ConcealAttr != o.Attrs&ConcealAttr {
|
||||
if s.Attrs&ConcealAttr != 0 {
|
||||
b = b.Conceal()
|
||||
} else {
|
||||
b = b.NoConceal()
|
||||
}
|
||||
}
|
||||
if s.Attrs&StrikethroughAttr != o.Attrs&StrikethroughAttr {
|
||||
if s.Attrs&StrikethroughAttr != 0 {
|
||||
b = b.Strikethrough()
|
||||
} else {
|
||||
b = b.NoStrikethrough()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s.UlStyle != o.UlStyle {
|
||||
b = b.UnderlineStyle(s.UlStyle)
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Equal returns true if the style is equal to the other style.
|
||||
func (s *Style) Equal(o *Style) bool {
|
||||
return s.Attrs == o.Attrs &&
|
||||
s.UlStyle == o.UlStyle &&
|
||||
colorEqual(s.Fg, o.Fg) &&
|
||||
colorEqual(s.Bg, o.Bg) &&
|
||||
colorEqual(s.Ul, o.Ul)
|
||||
}
|
||||
|
||||
func colorEqual(c, o ansi.Color) bool {
|
||||
if c == nil && o == nil {
|
||||
return true
|
||||
}
|
||||
if c == nil || o == nil {
|
||||
return false
|
||||
}
|
||||
cr, cg, cb, ca := c.RGBA()
|
||||
or, og, ob, oa := o.RGBA()
|
||||
return cr == or && cg == og && cb == ob && ca == oa
|
||||
}
|
||||
|
||||
// Bold sets the bold attribute.
|
||||
func (s *Style) Bold(v bool) *Style {
|
||||
if v {
|
||||
s.Attrs |= BoldAttr
|
||||
} else {
|
||||
s.Attrs &^= BoldAttr
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Faint sets the faint attribute.
|
||||
func (s *Style) Faint(v bool) *Style {
|
||||
if v {
|
||||
s.Attrs |= FaintAttr
|
||||
} else {
|
||||
s.Attrs &^= FaintAttr
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Italic sets the italic attribute.
|
||||
func (s *Style) Italic(v bool) *Style {
|
||||
if v {
|
||||
s.Attrs |= ItalicAttr
|
||||
} else {
|
||||
s.Attrs &^= ItalicAttr
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// SlowBlink sets the slow blink attribute.
|
||||
func (s *Style) SlowBlink(v bool) *Style {
|
||||
if v {
|
||||
s.Attrs |= SlowBlinkAttr
|
||||
} else {
|
||||
s.Attrs &^= SlowBlinkAttr
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// RapidBlink sets the rapid blink attribute.
|
||||
func (s *Style) RapidBlink(v bool) *Style {
|
||||
if v {
|
||||
s.Attrs |= RapidBlinkAttr
|
||||
} else {
|
||||
s.Attrs &^= RapidBlinkAttr
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Reverse sets the reverse attribute.
|
||||
func (s *Style) Reverse(v bool) *Style {
|
||||
if v {
|
||||
s.Attrs |= ReverseAttr
|
||||
} else {
|
||||
s.Attrs &^= ReverseAttr
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Conceal sets the conceal attribute.
|
||||
func (s *Style) Conceal(v bool) *Style {
|
||||
if v {
|
||||
s.Attrs |= ConcealAttr
|
||||
} else {
|
||||
s.Attrs &^= ConcealAttr
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Strikethrough sets the strikethrough attribute.
|
||||
func (s *Style) Strikethrough(v bool) *Style {
|
||||
if v {
|
||||
s.Attrs |= StrikethroughAttr
|
||||
} else {
|
||||
s.Attrs &^= StrikethroughAttr
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// UnderlineStyle sets the underline style.
|
||||
func (s *Style) UnderlineStyle(style UnderlineStyle) *Style {
|
||||
s.UlStyle = style
|
||||
return s
|
||||
}
|
||||
|
||||
// Underline sets the underline attribute.
|
||||
// This is a syntactic sugar for [UnderlineStyle].
|
||||
func (s *Style) Underline(v bool) *Style {
|
||||
if v {
|
||||
return s.UnderlineStyle(SingleUnderline)
|
||||
}
|
||||
return s.UnderlineStyle(NoUnderline)
|
||||
}
|
||||
|
||||
// Foreground sets the foreground color.
|
||||
func (s *Style) Foreground(c ansi.Color) *Style {
|
||||
s.Fg = c
|
||||
return s
|
||||
}
|
||||
|
||||
// Background sets the background color.
|
||||
func (s *Style) Background(c ansi.Color) *Style {
|
||||
s.Bg = c
|
||||
return s
|
||||
}
|
||||
|
||||
// UnderlineColor sets the underline color.
|
||||
func (s *Style) UnderlineColor(c ansi.Color) *Style {
|
||||
s.Ul = c
|
||||
return s
|
||||
}
|
||||
|
||||
// Reset resets the style to default.
|
||||
func (s *Style) Reset() *Style {
|
||||
s.Fg = nil
|
||||
s.Bg = nil
|
||||
s.Ul = nil
|
||||
s.Attrs = ResetAttr
|
||||
s.UlStyle = NoUnderline
|
||||
return s
|
||||
}
|
||||
|
||||
// Empty returns true if the style is empty.
|
||||
func (s *Style) Empty() bool {
|
||||
return s.Fg == nil && s.Bg == nil && s.Ul == nil && s.Attrs == ResetAttr && s.UlStyle == NoUnderline
|
||||
}
|
||||
|
||||
// Clear returns whether the style consists of only attributes that don't
|
||||
// affect appearance of a space character.
|
||||
func (s *Style) Clear() bool {
|
||||
return s.UlStyle == NoUnderline &&
|
||||
s.Attrs&^(BoldAttr|FaintAttr|ItalicAttr|SlowBlinkAttr|RapidBlinkAttr) == 0 &&
|
||||
s.Fg == nil &&
|
||||
s.Bg == nil &&
|
||||
s.Ul == nil
|
||||
}
|
||||
|
||||
func runesEqual(a, b []rune) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i, r := range a {
|
||||
if r != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
6
vendor/github.com/charmbracelet/x/cellbuf/errors.go
generated
vendored
Normal file
6
vendor/github.com/charmbracelet/x/cellbuf/errors.go
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
package cellbuf
|
||||
|
||||
import "errors"
|
||||
|
||||
// ErrOutOfBounds is returned when the given x, y position is out of bounds.
|
||||
var ErrOutOfBounds = errors.New("out of bounds")
|
21
vendor/github.com/charmbracelet/x/cellbuf/geom.go
generated
vendored
Normal file
21
vendor/github.com/charmbracelet/x/cellbuf/geom.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
package cellbuf
|
||||
|
||||
import (
|
||||
"image"
|
||||
)
|
||||
|
||||
// Position represents an x, y position.
|
||||
type Position = image.Point
|
||||
|
||||
// Pos is a shorthand for Position{X: x, Y: y}.
|
||||
func Pos(x, y int) Position {
|
||||
return image.Pt(x, y)
|
||||
}
|
||||
|
||||
// Rectange represents a rectangle.
|
||||
type Rectangle = image.Rectangle
|
||||
|
||||
// Rect is a shorthand for Rectangle.
|
||||
func Rect(x, y, w, h int) Rectangle {
|
||||
return image.Rect(x, y, x+w, y+h)
|
||||
}
|
272
vendor/github.com/charmbracelet/x/cellbuf/hardscroll.go
generated
vendored
Normal file
272
vendor/github.com/charmbracelet/x/cellbuf/hardscroll.go
generated
vendored
Normal file
@ -0,0 +1,272 @@
|
||||
package cellbuf
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/x/ansi"
|
||||
)
|
||||
|
||||
// scrollOptimize optimizes the screen to transform the old buffer into the new
|
||||
// buffer.
|
||||
func (s *Screen) scrollOptimize() {
|
||||
height := s.newbuf.Height()
|
||||
if s.oldnum == nil || len(s.oldnum) < height {
|
||||
s.oldnum = make([]int, height)
|
||||
}
|
||||
|
||||
// Calculate the indices
|
||||
s.updateHashmap()
|
||||
if len(s.hashtab) < height {
|
||||
return
|
||||
}
|
||||
|
||||
// Pass 1 - from top to bottom scrolling up
|
||||
for i := 0; i < height; {
|
||||
for i < height && (s.oldnum[i] == newIndex || s.oldnum[i] <= i) {
|
||||
i++
|
||||
}
|
||||
if i >= height {
|
||||
break
|
||||
}
|
||||
|
||||
shift := s.oldnum[i] - i // shift > 0
|
||||
start := i
|
||||
|
||||
i++
|
||||
for i < height && s.oldnum[i] != newIndex && s.oldnum[i]-i == shift {
|
||||
i++
|
||||
}
|
||||
end := i - 1 + shift
|
||||
|
||||
if !s.scrolln(shift, start, end, height-1) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2 - from bottom to top scrolling down
|
||||
for i := height - 1; i >= 0; {
|
||||
for i >= 0 && (s.oldnum[i] == newIndex || s.oldnum[i] >= i) {
|
||||
i--
|
||||
}
|
||||
if i < 0 {
|
||||
break
|
||||
}
|
||||
|
||||
shift := s.oldnum[i] - i // shift < 0
|
||||
end := i
|
||||
|
||||
i--
|
||||
for i >= 0 && s.oldnum[i] != newIndex && s.oldnum[i]-i == shift {
|
||||
i--
|
||||
}
|
||||
|
||||
start := i + 1 - (-shift)
|
||||
if !s.scrolln(shift, start, end, height-1) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scrolln scrolls the screen up by n lines.
|
||||
func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
|
||||
const (
|
||||
nonDestScrollRegion = false
|
||||
memoryBelow = false
|
||||
)
|
||||
|
||||
blank := s.clearBlank()
|
||||
if n > 0 {
|
||||
// Scroll up (forward)
|
||||
v = s.scrollUp(n, top, bot, 0, maxY, blank)
|
||||
if !v {
|
||||
s.buf.WriteString(ansi.SetTopBottomMargins(top+1, bot+1))
|
||||
|
||||
// XXX: How should we handle this in inline mode when not using alternate screen?
|
||||
s.cur.X, s.cur.Y = -1, -1
|
||||
v = s.scrollUp(n, top, bot, top, bot, blank)
|
||||
s.buf.WriteString(ansi.SetTopBottomMargins(1, maxY+1))
|
||||
s.cur.X, s.cur.Y = -1, -1
|
||||
}
|
||||
|
||||
if !v {
|
||||
v = s.scrollIdl(n, top, bot-n+1, blank)
|
||||
}
|
||||
|
||||
// Clear newly shifted-in lines.
|
||||
if v &&
|
||||
(nonDestScrollRegion || (memoryBelow && bot == maxY)) {
|
||||
if bot == maxY {
|
||||
s.move(0, bot-n+1)
|
||||
s.clearToBottom(nil)
|
||||
} else {
|
||||
for i := 0; i < n; i++ {
|
||||
s.move(0, bot-i)
|
||||
s.clearToEnd(nil, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if n < 0 {
|
||||
// Scroll down (backward)
|
||||
v = s.scrollDown(-n, top, bot, 0, maxY, blank)
|
||||
if !v {
|
||||
s.buf.WriteString(ansi.SetTopBottomMargins(top+1, bot+1))
|
||||
|
||||
// XXX: How should we handle this in inline mode when not using alternate screen?
|
||||
s.cur.X, s.cur.Y = -1, -1
|
||||
v = s.scrollDown(-n, top, bot, top, bot, blank)
|
||||
s.buf.WriteString(ansi.SetTopBottomMargins(1, maxY+1))
|
||||
s.cur.X, s.cur.Y = -1, -1
|
||||
|
||||
if !v {
|
||||
v = s.scrollIdl(-n, bot+n+1, top, blank)
|
||||
}
|
||||
|
||||
// Clear newly shifted-in lines.
|
||||
if v &&
|
||||
(nonDestScrollRegion || (memoryBelow && top == 0)) {
|
||||
for i := 0; i < -n; i++ {
|
||||
s.move(0, top+i)
|
||||
s.clearToEnd(nil, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !v {
|
||||
return
|
||||
}
|
||||
|
||||
s.scrollBuffer(s.curbuf, n, top, bot, blank)
|
||||
|
||||
// shift hash values too, they can be reused
|
||||
s.scrollOldhash(n, top, bot)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// scrollBuffer scrolls the buffer by n lines.
|
||||
func (s *Screen) scrollBuffer(b *Buffer, n, top, bot int, blank *Cell) {
|
||||
if top < 0 || bot < top || bot >= b.Height() {
|
||||
// Nothing to scroll
|
||||
return
|
||||
}
|
||||
|
||||
if n < 0 {
|
||||
// shift n lines downwards
|
||||
limit := top - n
|
||||
for line := bot; line >= limit && line >= 0 && line >= top; line-- {
|
||||
copy(b.Lines[line], b.Lines[line+n])
|
||||
}
|
||||
for line := top; line < limit && line <= b.Height()-1 && line <= bot; line++ {
|
||||
b.FillRect(blank, Rect(0, line, b.Width(), 1))
|
||||
}
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
// shift n lines upwards
|
||||
limit := bot - n
|
||||
for line := top; line <= limit && line <= b.Height()-1 && line <= bot; line++ {
|
||||
copy(b.Lines[line], b.Lines[line+n])
|
||||
}
|
||||
for line := bot; line > limit && line >= 0 && line >= top; line-- {
|
||||
b.FillRect(blank, Rect(0, line, b.Width(), 1))
|
||||
}
|
||||
}
|
||||
|
||||
s.touchLine(b.Width(), b.Height(), top, bot-top+1, true)
|
||||
}
|
||||
|
||||
// touchLine marks the line as touched.
|
||||
func (s *Screen) touchLine(width, height, y, n int, changed bool) {
|
||||
if n < 0 || y < 0 || y >= height {
|
||||
return // Nothing to touch
|
||||
}
|
||||
|
||||
for i := y; i < y+n && i < height; i++ {
|
||||
if changed {
|
||||
s.touch[i] = lineData{firstCell: 0, lastCell: width - 1}
|
||||
} else {
|
||||
delete(s.touch, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scrollUp scrolls the screen up by n lines.
|
||||
func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
|
||||
if n == 1 && top == minY && bot == maxY {
|
||||
s.move(0, bot)
|
||||
s.updatePen(blank)
|
||||
s.buf.WriteByte('\n')
|
||||
} else if n == 1 && bot == maxY {
|
||||
s.move(0, top)
|
||||
s.updatePen(blank)
|
||||
s.buf.WriteString(ansi.DeleteLine(1))
|
||||
} else if top == minY && bot == maxY {
|
||||
if s.xtermLike {
|
||||
s.move(0, bot)
|
||||
} else {
|
||||
s.move(0, top)
|
||||
}
|
||||
s.updatePen(blank)
|
||||
if s.xtermLike {
|
||||
s.buf.WriteString(ansi.ScrollUp(n))
|
||||
} else {
|
||||
s.buf.WriteString(strings.Repeat("\n", n))
|
||||
}
|
||||
} else if bot == maxY {
|
||||
s.move(0, top)
|
||||
s.updatePen(blank)
|
||||
s.buf.WriteString(ansi.DeleteLine(n))
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// scrollDown scrolls the screen down by n lines.
|
||||
func (s *Screen) scrollDown(n, top, bot, minY, maxY int, blank *Cell) bool {
|
||||
if n == 1 && top == minY && bot == maxY {
|
||||
s.move(0, top)
|
||||
s.updatePen(blank)
|
||||
s.buf.WriteString(ansi.ReverseIndex)
|
||||
} else if n == 1 && bot == maxY {
|
||||
s.move(0, top)
|
||||
s.updatePen(blank)
|
||||
s.buf.WriteString(ansi.InsertLine(1))
|
||||
} else if top == minY && bot == maxY {
|
||||
s.move(0, top)
|
||||
s.updatePen(blank)
|
||||
if s.xtermLike {
|
||||
s.buf.WriteString(ansi.ScrollDown(n))
|
||||
} else {
|
||||
s.buf.WriteString(strings.Repeat(ansi.ReverseIndex, n))
|
||||
}
|
||||
} else if bot == maxY {
|
||||
s.move(0, top)
|
||||
s.updatePen(blank)
|
||||
s.buf.WriteString(ansi.InsertLine(n))
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// scrollIdl scrolls the screen n lines by using [ansi.DL] at del and using
|
||||
// [ansi.IL] at ins.
|
||||
func (s *Screen) scrollIdl(n, del, ins int, blank *Cell) bool {
|
||||
if n < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Delete lines
|
||||
s.move(0, del)
|
||||
s.updatePen(blank)
|
||||
s.buf.WriteString(ansi.DeleteLine(n))
|
||||
|
||||
// Insert lines
|
||||
s.move(0, ins)
|
||||
s.updatePen(blank)
|
||||
s.buf.WriteString(ansi.InsertLine(n))
|
||||
|
||||
return true
|
||||
}
|
301
vendor/github.com/charmbracelet/x/cellbuf/hashmap.go
generated
vendored
Normal file
301
vendor/github.com/charmbracelet/x/cellbuf/hashmap.go
generated
vendored
Normal file
@ -0,0 +1,301 @@
|
||||
package cellbuf
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/x/ansi"
|
||||
)
|
||||
|
||||
// hash returns the hash value of a [Line].
|
||||
func hash(l Line) (h uint64) {
|
||||
for _, c := range l {
|
||||
var r rune
|
||||
if c == nil {
|
||||
r = ansi.SP
|
||||
} else {
|
||||
r = c.Rune
|
||||
}
|
||||
h += (h << 5) + uint64(r)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// hashmap represents a single [Line] hash.
|
||||
type hashmap struct {
|
||||
value uint64
|
||||
oldcount, newcount int
|
||||
oldindex, newindex int
|
||||
}
|
||||
|
||||
// The value used to indicate lines created by insertions and scrolls.
|
||||
const newIndex = -1
|
||||
|
||||
// updateHashmap updates the hashmap with the new hash value.
|
||||
func (s *Screen) updateHashmap() {
|
||||
height := s.newbuf.Height()
|
||||
if len(s.oldhash) >= height && len(s.newhash) >= height {
|
||||
// rehash changed lines
|
||||
for i := 0; i < height; i++ {
|
||||
_, ok := s.touch[i]
|
||||
if ok {
|
||||
s.oldhash[i] = hash(s.curbuf.Line(i))
|
||||
s.newhash[i] = hash(s.newbuf.Line(i))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// rehash all
|
||||
if len(s.oldhash) != height {
|
||||
s.oldhash = make([]uint64, height)
|
||||
}
|
||||
if len(s.newhash) != height {
|
||||
s.newhash = make([]uint64, height)
|
||||
}
|
||||
for i := 0; i < height; i++ {
|
||||
s.oldhash[i] = hash(s.curbuf.Line(i))
|
||||
s.newhash[i] = hash(s.newbuf.Line(i))
|
||||
}
|
||||
}
|
||||
|
||||
s.hashtab = make([]hashmap, height*2)
|
||||
for i := 0; i < height; i++ {
|
||||
hashval := s.oldhash[i]
|
||||
|
||||
// Find matching hash or empty slot
|
||||
idx := 0
|
||||
for idx < len(s.hashtab) && s.hashtab[idx].value != 0 {
|
||||
if s.hashtab[idx].value == hashval {
|
||||
break
|
||||
}
|
||||
idx++
|
||||
}
|
||||
|
||||
s.hashtab[idx].value = hashval // in case this is a new hash
|
||||
s.hashtab[idx].oldcount++
|
||||
s.hashtab[idx].oldindex = i
|
||||
}
|
||||
for i := 0; i < height; i++ {
|
||||
hashval := s.newhash[i]
|
||||
|
||||
// Find matching hash or empty slot
|
||||
idx := 0
|
||||
for idx < len(s.hashtab) && s.hashtab[idx].value != 0 {
|
||||
if s.hashtab[idx].value == hashval {
|
||||
break
|
||||
}
|
||||
idx++
|
||||
}
|
||||
|
||||
s.hashtab[idx].value = hashval // in case this is a new hash
|
||||
s.hashtab[idx].newcount++
|
||||
s.hashtab[idx].newindex = i
|
||||
|
||||
s.oldnum[i] = newIndex // init old indices slice
|
||||
}
|
||||
|
||||
// Mark line pair corresponding to unique hash pairs.
|
||||
for i := 0; i < len(s.hashtab) && s.hashtab[i].value != 0; i++ {
|
||||
hsp := &s.hashtab[i]
|
||||
if hsp.oldcount == 1 && hsp.newcount == 1 && hsp.oldindex != hsp.newindex {
|
||||
s.oldnum[hsp.newindex] = hsp.oldindex
|
||||
}
|
||||
}
|
||||
|
||||
s.growHunks()
|
||||
|
||||
// Eliminate bad or impossible shifts. This includes removing those hunks
|
||||
// which could not grow because of conflicts, as well those which are to be
|
||||
// moved too far, they are likely to destroy more than carry.
|
||||
for i := 0; i < height; {
|
||||
var start, shift, size int
|
||||
for i < height && s.oldnum[i] == newIndex {
|
||||
i++
|
||||
}
|
||||
if i >= height {
|
||||
break
|
||||
}
|
||||
start = i
|
||||
shift = s.oldnum[i] - i
|
||||
i++
|
||||
for i < height && s.oldnum[i] != newIndex && s.oldnum[i]-i == shift {
|
||||
i++
|
||||
}
|
||||
size = i - start
|
||||
if size < 3 || size+min(size/8, 2) < abs(shift) {
|
||||
for start < i {
|
||||
s.oldnum[start] = newIndex
|
||||
start++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// After clearing invalid hunks, try grow the rest.
|
||||
s.growHunks()
|
||||
}
|
||||
|
||||
// scrollOldhash
|
||||
func (s *Screen) scrollOldhash(n, top, bot int) {
|
||||
if len(s.oldhash) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
size := bot - top + 1 - abs(n)
|
||||
if n > 0 {
|
||||
// Move existing hashes up
|
||||
copy(s.oldhash[top:], s.oldhash[top+n:top+n+size])
|
||||
// Recalculate hashes for newly shifted-in lines
|
||||
for i := bot; i > bot-n; i-- {
|
||||
s.oldhash[i] = hash(s.curbuf.Line(i))
|
||||
}
|
||||
} else {
|
||||
// Move existing hashes down
|
||||
copy(s.oldhash[top-n:], s.oldhash[top:top+size])
|
||||
// Recalculate hashes for newly shifted-in lines
|
||||
for i := top; i < top-n; i++ {
|
||||
s.oldhash[i] = hash(s.curbuf.Line(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Screen) growHunks() {
|
||||
var (
|
||||
backLimit int // limits for cells to fill
|
||||
backRefLimit int // limit for references
|
||||
i int
|
||||
nextHunk int
|
||||
)
|
||||
|
||||
height := s.newbuf.Height()
|
||||
for i < height && s.oldnum[i] == newIndex {
|
||||
i++
|
||||
}
|
||||
for ; i < height; i = nextHunk {
|
||||
var (
|
||||
forwardLimit int
|
||||
forwardRefLimit int
|
||||
end int
|
||||
start = i
|
||||
shift = s.oldnum[i] - i
|
||||
)
|
||||
|
||||
// get forward limit
|
||||
i = start + 1
|
||||
for i < height &&
|
||||
s.oldnum[i] != newIndex &&
|
||||
s.oldnum[i]-i == shift {
|
||||
i++
|
||||
}
|
||||
|
||||
end = i
|
||||
for i < height && s.oldnum[i] == newIndex {
|
||||
i++
|
||||
}
|
||||
|
||||
nextHunk = i
|
||||
forwardLimit = i
|
||||
if i >= height || s.oldnum[i] >= i {
|
||||
forwardRefLimit = i
|
||||
} else {
|
||||
forwardRefLimit = s.oldnum[i]
|
||||
}
|
||||
|
||||
i = start - 1
|
||||
|
||||
// grow back
|
||||
if shift < 0 {
|
||||
backLimit = backRefLimit + (-shift)
|
||||
}
|
||||
for i >= backLimit {
|
||||
if s.newhash[i] == s.oldhash[i+shift] ||
|
||||
s.costEffective(i+shift, i, shift < 0) {
|
||||
s.oldnum[i] = i + shift
|
||||
} else {
|
||||
break
|
||||
}
|
||||
i--
|
||||
}
|
||||
|
||||
i = end
|
||||
// grow forward
|
||||
if shift > 0 {
|
||||
forwardLimit = forwardRefLimit - shift
|
||||
}
|
||||
for i < forwardLimit {
|
||||
if s.newhash[i] == s.oldhash[i+shift] ||
|
||||
s.costEffective(i+shift, i, shift > 0) {
|
||||
s.oldnum[i] = i + shift
|
||||
} else {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
backLimit = i
|
||||
backRefLimit = backLimit
|
||||
if shift > 0 {
|
||||
backRefLimit += shift
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// costEffective returns true if the cost of moving line 'from' to line 'to' seems to be
|
||||
// cost effective. 'blank' indicates whether the line 'to' would become blank.
|
||||
func (s *Screen) costEffective(from, to int, blank bool) bool {
|
||||
if from == to {
|
||||
return false
|
||||
}
|
||||
|
||||
newFrom := s.oldnum[from]
|
||||
if newFrom == newIndex {
|
||||
newFrom = from
|
||||
}
|
||||
|
||||
// On the left side of >= is the cost before moving. On the right side --
|
||||
// cost after moving.
|
||||
|
||||
// Calculate costs before moving.
|
||||
var costBeforeMove int
|
||||
if blank {
|
||||
// Cost of updating blank line at destination.
|
||||
costBeforeMove = s.updateCostBlank(s.newbuf.Line(to))
|
||||
} else {
|
||||
// Cost of updating exiting line at destination.
|
||||
costBeforeMove = s.updateCost(s.curbuf.Line(to), s.newbuf.Line(to))
|
||||
}
|
||||
|
||||
// Add cost of updating source line
|
||||
costBeforeMove += s.updateCost(s.curbuf.Line(newFrom), s.newbuf.Line(from))
|
||||
|
||||
// Calculate costs after moving.
|
||||
var costAfterMove int
|
||||
if newFrom == from {
|
||||
// Source becomes blank after move
|
||||
costAfterMove = s.updateCostBlank(s.newbuf.Line(from))
|
||||
} else {
|
||||
// Source gets updated from another line
|
||||
costAfterMove = s.updateCost(s.curbuf.Line(newFrom), s.newbuf.Line(from))
|
||||
}
|
||||
|
||||
// Add cost of moving source line to destination
|
||||
costAfterMove += s.updateCost(s.curbuf.Line(from), s.newbuf.Line(to))
|
||||
|
||||
// Return true if moving is cost effective (costs less or equal)
|
||||
return costBeforeMove >= costAfterMove
|
||||
}
|
||||
|
||||
func (s *Screen) updateCost(from, to Line) (cost int) {
|
||||
var fidx, tidx int
|
||||
for i := s.newbuf.Width() - 1; i > 0; i, fidx, tidx = i-1, fidx+1, tidx+1 {
|
||||
if !cellEqual(from.At(fidx), to.At(tidx)) {
|
||||
cost++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Screen) updateCostBlank(to Line) (cost int) {
|
||||
var tidx int
|
||||
for i := s.newbuf.Width() - 1; i > 0; i, tidx = i-1, tidx+1 {
|
||||
if !cellEqual(nil, to.At(tidx)) {
|
||||
cost++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
14
vendor/github.com/charmbracelet/x/cellbuf/link.go
generated
vendored
Normal file
14
vendor/github.com/charmbracelet/x/cellbuf/link.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
package cellbuf
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/colorprofile"
|
||||
)
|
||||
|
||||
// Convert converts a hyperlink to respect the given color profile.
|
||||
func ConvertLink(h Link, p colorprofile.Profile) Link {
|
||||
if p == colorprofile.NoTTY {
|
||||
return Link{}
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
1457
vendor/github.com/charmbracelet/x/cellbuf/screen.go
generated
vendored
Normal file
1457
vendor/github.com/charmbracelet/x/cellbuf/screen.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
131
vendor/github.com/charmbracelet/x/cellbuf/sequence.go
generated
vendored
Normal file
131
vendor/github.com/charmbracelet/x/cellbuf/sequence.go
generated
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
package cellbuf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image/color"
|
||||
|
||||
"github.com/charmbracelet/x/ansi"
|
||||
)
|
||||
|
||||
// ReadStyle reads a Select Graphic Rendition (SGR) escape sequences from a
|
||||
// list of parameters.
|
||||
func ReadStyle(params ansi.Params, pen *Style) {
|
||||
if len(params) == 0 {
|
||||
pen.Reset()
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < len(params); i++ {
|
||||
param, hasMore, _ := params.Param(i, 0)
|
||||
switch param {
|
||||
case 0: // Reset
|
||||
pen.Reset()
|
||||
case 1: // Bold
|
||||
pen.Bold(true)
|
||||
case 2: // Dim/Faint
|
||||
pen.Faint(true)
|
||||
case 3: // Italic
|
||||
pen.Italic(true)
|
||||
case 4: // Underline
|
||||
nextParam, _, ok := params.Param(i+1, 0)
|
||||
if hasMore && ok { // Only accept subparameters i.e. separated by ":"
|
||||
switch nextParam {
|
||||
case 0, 1, 2, 3, 4, 5:
|
||||
i++
|
||||
switch nextParam {
|
||||
case 0: // No Underline
|
||||
pen.UnderlineStyle(NoUnderline)
|
||||
case 1: // Single Underline
|
||||
pen.UnderlineStyle(SingleUnderline)
|
||||
case 2: // Double Underline
|
||||
pen.UnderlineStyle(DoubleUnderline)
|
||||
case 3: // Curly Underline
|
||||
pen.UnderlineStyle(CurlyUnderline)
|
||||
case 4: // Dotted Underline
|
||||
pen.UnderlineStyle(DottedUnderline)
|
||||
case 5: // Dashed Underline
|
||||
pen.UnderlineStyle(DashedUnderline)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Single Underline
|
||||
pen.Underline(true)
|
||||
}
|
||||
case 5: // Slow Blink
|
||||
pen.SlowBlink(true)
|
||||
case 6: // Rapid Blink
|
||||
pen.RapidBlink(true)
|
||||
case 7: // Reverse
|
||||
pen.Reverse(true)
|
||||
case 8: // Conceal
|
||||
pen.Conceal(true)
|
||||
case 9: // Crossed-out/Strikethrough
|
||||
pen.Strikethrough(true)
|
||||
case 22: // Normal Intensity (not bold or faint)
|
||||
pen.Bold(false).Faint(false)
|
||||
case 23: // Not italic, not Fraktur
|
||||
pen.Italic(false)
|
||||
case 24: // Not underlined
|
||||
pen.Underline(false)
|
||||
case 25: // Blink off
|
||||
pen.SlowBlink(false).RapidBlink(false)
|
||||
case 27: // Positive (not reverse)
|
||||
pen.Reverse(false)
|
||||
case 28: // Reveal
|
||||
pen.Conceal(false)
|
||||
case 29: // Not crossed out
|
||||
pen.Strikethrough(false)
|
||||
case 30, 31, 32, 33, 34, 35, 36, 37: // Set foreground
|
||||
pen.Foreground(ansi.Black + ansi.BasicColor(param-30)) //nolint:gosec
|
||||
case 38: // Set foreground 256 or truecolor
|
||||
var c color.Color
|
||||
n := ReadStyleColor(params[i:], &c)
|
||||
if n > 0 {
|
||||
pen.Foreground(c)
|
||||
i += n - 1
|
||||
}
|
||||
case 39: // Default foreground
|
||||
pen.Foreground(nil)
|
||||
case 40, 41, 42, 43, 44, 45, 46, 47: // Set background
|
||||
pen.Background(ansi.Black + ansi.BasicColor(param-40)) //nolint:gosec
|
||||
case 48: // Set background 256 or truecolor
|
||||
var c color.Color
|
||||
n := ReadStyleColor(params[i:], &c)
|
||||
if n > 0 {
|
||||
pen.Background(c)
|
||||
i += n - 1
|
||||
}
|
||||
case 49: // Default Background
|
||||
pen.Background(nil)
|
||||
case 58: // Set underline color
|
||||
var c color.Color
|
||||
n := ReadStyleColor(params[i:], &c)
|
||||
if n > 0 {
|
||||
pen.UnderlineColor(c)
|
||||
i += n - 1
|
||||
}
|
||||
case 59: // Default underline color
|
||||
pen.UnderlineColor(nil)
|
||||
case 90, 91, 92, 93, 94, 95, 96, 97: // Set bright foreground
|
||||
pen.Foreground(ansi.BrightBlack + ansi.BasicColor(param-90)) //nolint:gosec
|
||||
case 100, 101, 102, 103, 104, 105, 106, 107: // Set bright background
|
||||
pen.Background(ansi.BrightBlack + ansi.BasicColor(param-100)) //nolint:gosec
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ReadLink reads a hyperlink escape sequence from a data buffer.
|
||||
func ReadLink(p []byte, link *Link) {
|
||||
params := bytes.Split(p, []byte{';'})
|
||||
if len(params) != 3 {
|
||||
return
|
||||
}
|
||||
link.Params = string(params[1])
|
||||
link.URL = string(params[2])
|
||||
}
|
||||
|
||||
// ReadStyleColor reads a color from a list of parameters.
|
||||
// See [ansi.ReadStyleColor] for more information.
|
||||
func ReadStyleColor(params ansi.Params, c *color.Color) int {
|
||||
return ansi.ReadStyleColor(params, c)
|
||||
}
|
31
vendor/github.com/charmbracelet/x/cellbuf/style.go
generated
vendored
Normal file
31
vendor/github.com/charmbracelet/x/cellbuf/style.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
package cellbuf
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/colorprofile"
|
||||
)
|
||||
|
||||
// Convert converts a style to respect the given color profile.
|
||||
func ConvertStyle(s Style, p colorprofile.Profile) Style {
|
||||
switch p {
|
||||
case colorprofile.TrueColor:
|
||||
return s
|
||||
case colorprofile.Ascii:
|
||||
s.Fg = nil
|
||||
s.Bg = nil
|
||||
s.Ul = nil
|
||||
case colorprofile.NoTTY:
|
||||
return Style{}
|
||||
}
|
||||
|
||||
if s.Fg != nil {
|
||||
s.Fg = p.Convert(s.Fg)
|
||||
}
|
||||
if s.Bg != nil {
|
||||
s.Bg = p.Convert(s.Bg)
|
||||
}
|
||||
if s.Ul != nil {
|
||||
s.Ul = p.Convert(s.Ul)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
137
vendor/github.com/charmbracelet/x/cellbuf/tabstop.go
generated
vendored
Normal file
137
vendor/github.com/charmbracelet/x/cellbuf/tabstop.go
generated
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
package cellbuf
|
||||
|
||||
// DefaultTabInterval is the default tab interval.
|
||||
const DefaultTabInterval = 8
|
||||
|
||||
// TabStops represents horizontal line tab stops.
|
||||
type TabStops struct {
|
||||
stops []int
|
||||
interval int
|
||||
width int
|
||||
}
|
||||
|
||||
// NewTabStops creates a new set of tab stops from a number of columns and an
|
||||
// interval.
|
||||
func NewTabStops(width, interval int) *TabStops {
|
||||
ts := new(TabStops)
|
||||
ts.interval = interval
|
||||
ts.width = width
|
||||
ts.stops = make([]int, (width+(interval-1))/interval)
|
||||
ts.init(0, width)
|
||||
return ts
|
||||
}
|
||||
|
||||
// DefaultTabStops creates a new set of tab stops with the default interval.
|
||||
func DefaultTabStops(cols int) *TabStops {
|
||||
return NewTabStops(cols, DefaultTabInterval)
|
||||
}
|
||||
|
||||
// Resize resizes the tab stops to the given width.
|
||||
func (ts *TabStops) Resize(width int) {
|
||||
if width == ts.width {
|
||||
return
|
||||
}
|
||||
|
||||
if width < ts.width {
|
||||
size := (width + (ts.interval - 1)) / ts.interval
|
||||
ts.stops = ts.stops[:size]
|
||||
} else {
|
||||
size := (width - ts.width + (ts.interval - 1)) / ts.interval
|
||||
ts.stops = append(ts.stops, make([]int, size)...)
|
||||
}
|
||||
|
||||
ts.init(ts.width, width)
|
||||
ts.width = width
|
||||
}
|
||||
|
||||
// IsStop returns true if the given column is a tab stop.
|
||||
func (ts TabStops) IsStop(col int) bool {
|
||||
mask := ts.mask(col)
|
||||
i := col >> 3
|
||||
if i < 0 || i >= len(ts.stops) {
|
||||
return false
|
||||
}
|
||||
return ts.stops[i]&mask != 0
|
||||
}
|
||||
|
||||
// Next returns the next tab stop after the given column.
|
||||
func (ts TabStops) Next(col int) int {
|
||||
return ts.Find(col, 1)
|
||||
}
|
||||
|
||||
// Prev returns the previous tab stop before the given column.
|
||||
func (ts TabStops) Prev(col int) int {
|
||||
return ts.Find(col, -1)
|
||||
}
|
||||
|
||||
// Find returns the prev/next tab stop before/after the given column and delta.
|
||||
// If delta is positive, it returns the next tab stop after the given column.
|
||||
// If delta is negative, it returns the previous tab stop before the given column.
|
||||
// If delta is zero, it returns the given column.
|
||||
func (ts TabStops) Find(col, delta int) int {
|
||||
if delta == 0 {
|
||||
return col
|
||||
}
|
||||
|
||||
var prev bool
|
||||
count := delta
|
||||
if count < 0 {
|
||||
count = -count
|
||||
prev = true
|
||||
}
|
||||
|
||||
for count > 0 {
|
||||
if !prev {
|
||||
if col >= ts.width-1 {
|
||||
return col
|
||||
}
|
||||
|
||||
col++
|
||||
} else {
|
||||
if col < 1 {
|
||||
return col
|
||||
}
|
||||
|
||||
col--
|
||||
}
|
||||
|
||||
if ts.IsStop(col) {
|
||||
count--
|
||||
}
|
||||
}
|
||||
|
||||
return col
|
||||
}
|
||||
|
||||
// Set adds a tab stop at the given column.
|
||||
func (ts *TabStops) Set(col int) {
|
||||
mask := ts.mask(col)
|
||||
ts.stops[col>>3] |= mask
|
||||
}
|
||||
|
||||
// Reset removes the tab stop at the given column.
|
||||
func (ts *TabStops) Reset(col int) {
|
||||
mask := ts.mask(col)
|
||||
ts.stops[col>>3] &= ^mask
|
||||
}
|
||||
|
||||
// Clear removes all tab stops.
|
||||
func (ts *TabStops) Clear() {
|
||||
ts.stops = make([]int, len(ts.stops))
|
||||
}
|
||||
|
||||
// mask returns the mask for the given column.
|
||||
func (ts *TabStops) mask(col int) int {
|
||||
return 1 << (col & (ts.interval - 1))
|
||||
}
|
||||
|
||||
// init initializes the tab stops starting from col until width.
|
||||
func (ts *TabStops) init(col, width int) {
|
||||
for x := col; x < width; x++ {
|
||||
if x%ts.interval == 0 {
|
||||
ts.Set(x)
|
||||
} else {
|
||||
ts.Reset(x)
|
||||
}
|
||||
}
|
||||
}
|
38
vendor/github.com/charmbracelet/x/cellbuf/utils.go
generated
vendored
Normal file
38
vendor/github.com/charmbracelet/x/cellbuf/utils.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
package cellbuf
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Height returns the height of a string.
|
||||
func Height(s string) int {
|
||||
return strings.Count(s, "\n") + 1
|
||||
}
|
||||
|
||||
func min(a, b int) int { //nolint:predeclared
|
||||
if a > b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func max(a, b int) int { //nolint:predeclared
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func clamp(v, low, high int) int {
|
||||
if high < low {
|
||||
low, high = high, low
|
||||
}
|
||||
return min(high, max(low, v))
|
||||
}
|
||||
|
||||
func abs(a int) int {
|
||||
if a < 0 {
|
||||
return -a
|
||||
}
|
||||
return a
|
||||
}
|
185
vendor/github.com/charmbracelet/x/cellbuf/wrap.go
generated
vendored
Normal file
185
vendor/github.com/charmbracelet/x/cellbuf/wrap.go
generated
vendored
Normal file
@ -0,0 +1,185 @@
|
||||
package cellbuf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/charmbracelet/x/ansi"
|
||||
)
|
||||
|
||||
const nbsp = '\u00a0'
|
||||
|
||||
// Wrap returns a string that is wrapped to the specified limit applying any
|
||||
// ANSI escape sequences in the string. It tries to wrap the string at word
|
||||
// boundaries, but will break words if necessary.
|
||||
//
|
||||
// The breakpoints string is a list of characters that are considered
|
||||
// breakpoints for word wrapping. A hyphen (-) is always considered a
|
||||
// breakpoint.
|
||||
//
|
||||
// Note: breakpoints must be a string of 1-cell wide rune characters.
|
||||
func Wrap(s string, limit int, breakpoints string) string {
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if limit < 1 {
|
||||
return s
|
||||
}
|
||||
|
||||
p := ansi.GetParser()
|
||||
defer ansi.PutParser(p)
|
||||
|
||||
var (
|
||||
buf bytes.Buffer
|
||||
word bytes.Buffer
|
||||
space bytes.Buffer
|
||||
style, curStyle Style
|
||||
link, curLink Link
|
||||
curWidth int
|
||||
wordLen int
|
||||
)
|
||||
|
||||
hasBlankStyle := func() bool {
|
||||
// Only follow reverse attribute, bg color and underline style
|
||||
return !style.Attrs.Contains(ReverseAttr) && style.Bg == nil && style.UlStyle == NoUnderline
|
||||
}
|
||||
|
||||
addSpace := func() {
|
||||
curWidth += space.Len()
|
||||
buf.Write(space.Bytes())
|
||||
space.Reset()
|
||||
}
|
||||
|
||||
addWord := func() {
|
||||
if word.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
curLink = link
|
||||
curStyle = style
|
||||
|
||||
addSpace()
|
||||
curWidth += wordLen
|
||||
buf.Write(word.Bytes())
|
||||
word.Reset()
|
||||
wordLen = 0
|
||||
}
|
||||
|
||||
addNewline := func() {
|
||||
if !curStyle.Empty() {
|
||||
buf.WriteString(ansi.ResetStyle)
|
||||
}
|
||||
if !curLink.Empty() {
|
||||
buf.WriteString(ansi.ResetHyperlink())
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
if !curLink.Empty() {
|
||||
buf.WriteString(ansi.SetHyperlink(curLink.URL, curLink.Params))
|
||||
}
|
||||
if !curStyle.Empty() {
|
||||
buf.WriteString(curStyle.Sequence())
|
||||
}
|
||||
curWidth = 0
|
||||
space.Reset()
|
||||
}
|
||||
|
||||
var state byte
|
||||
for len(s) > 0 {
|
||||
seq, width, n, newState := ansi.DecodeSequence(s, state, p)
|
||||
switch width {
|
||||
case 0:
|
||||
if ansi.Equal(seq, "\t") {
|
||||
addWord()
|
||||
space.WriteString(seq)
|
||||
break
|
||||
} else if ansi.Equal(seq, "\n") {
|
||||
if wordLen == 0 {
|
||||
if curWidth+space.Len() > limit {
|
||||
curWidth = 0
|
||||
} else {
|
||||
// preserve whitespaces
|
||||
buf.Write(space.Bytes())
|
||||
}
|
||||
space.Reset()
|
||||
}
|
||||
|
||||
addWord()
|
||||
addNewline()
|
||||
break
|
||||
} else if ansi.HasCsiPrefix(seq) && p.Command() == 'm' {
|
||||
// SGR style sequence [ansi.SGR]
|
||||
ReadStyle(p.Params(), &style)
|
||||
} else if ansi.HasOscPrefix(seq) && p.Command() == 8 {
|
||||
// Hyperlink sequence [ansi.SetHyperlink]
|
||||
ReadLink(p.Data(), &link)
|
||||
}
|
||||
|
||||
word.WriteString(seq)
|
||||
default:
|
||||
if len(seq) == 1 {
|
||||
// ASCII
|
||||
r, _ := utf8.DecodeRuneInString(seq)
|
||||
if r != nbsp && unicode.IsSpace(r) && hasBlankStyle() {
|
||||
addWord()
|
||||
space.WriteRune(r)
|
||||
break
|
||||
} else if r == '-' || runeContainsAny(r, breakpoints) {
|
||||
addSpace()
|
||||
if curWidth+wordLen+width <= limit {
|
||||
addWord()
|
||||
buf.WriteString(seq)
|
||||
curWidth += width
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if wordLen+width > limit {
|
||||
// Hardwrap the word if it's too long
|
||||
addWord()
|
||||
}
|
||||
|
||||
word.WriteString(seq)
|
||||
wordLen += width
|
||||
|
||||
if curWidth+wordLen+space.Len() > limit {
|
||||
addNewline()
|
||||
}
|
||||
}
|
||||
|
||||
s = s[n:]
|
||||
state = newState
|
||||
}
|
||||
|
||||
if wordLen == 0 {
|
||||
if curWidth+space.Len() > limit {
|
||||
curWidth = 0
|
||||
} else {
|
||||
// preserve whitespaces
|
||||
buf.Write(space.Bytes())
|
||||
}
|
||||
space.Reset()
|
||||
}
|
||||
|
||||
addWord()
|
||||
|
||||
if !curLink.Empty() {
|
||||
buf.WriteString(ansi.ResetHyperlink())
|
||||
}
|
||||
if !curStyle.Empty() {
|
||||
buf.WriteString(ansi.ResetStyle)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func runeContainsAny[T string | []rune](r rune, s T) bool {
|
||||
for _, c := range []rune(s) {
|
||||
if c == r {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
339
vendor/github.com/charmbracelet/x/cellbuf/writer.go
generated
vendored
Normal file
339
vendor/github.com/charmbracelet/x/cellbuf/writer.go
generated
vendored
Normal file
@ -0,0 +1,339 @@
|
||||
package cellbuf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/x/ansi"
|
||||
)
|
||||
|
||||
// CellBuffer is a cell buffer that represents a set of cells in a screen or a
|
||||
// grid.
|
||||
type CellBuffer interface {
|
||||
// Cell returns the cell at the given position.
|
||||
Cell(x, y int) *Cell
|
||||
// SetCell sets the cell at the given position to the given cell. It
|
||||
// returns whether the cell was set successfully.
|
||||
SetCell(x, y int, c *Cell) bool
|
||||
// Bounds returns the bounds of the cell buffer.
|
||||
Bounds() Rectangle
|
||||
}
|
||||
|
||||
// FillRect fills the rectangle within the cell buffer with the given cell.
|
||||
// This will not fill cells outside the bounds of the cell buffer.
|
||||
func FillRect(s CellBuffer, c *Cell, rect Rectangle) {
|
||||
for y := rect.Min.Y; y < rect.Max.Y; y++ {
|
||||
for x := rect.Min.X; x < rect.Max.X; x++ {
|
||||
s.SetCell(x, y, c) //nolint:errcheck
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill fills the cell buffer with the given cell.
|
||||
func Fill(s CellBuffer, c *Cell) {
|
||||
FillRect(s, c, s.Bounds())
|
||||
}
|
||||
|
||||
// ClearRect clears the rectangle within the cell buffer with blank cells.
|
||||
func ClearRect(s CellBuffer, rect Rectangle) {
|
||||
FillRect(s, nil, rect)
|
||||
}
|
||||
|
||||
// Clear clears the cell buffer with blank cells.
|
||||
func Clear(s CellBuffer) {
|
||||
Fill(s, nil)
|
||||
}
|
||||
|
||||
// SetContentRect clears the rectangle within the cell buffer with blank cells,
|
||||
// and sets the given string as its content. If the height or width of the
|
||||
// string exceeds the height or width of the cell buffer, it will be truncated.
|
||||
func SetContentRect(s CellBuffer, str string, rect Rectangle) {
|
||||
// Replace all "\n" with "\r\n" to ensure the cursor is reset to the start
|
||||
// of the line. Make sure we don't replace "\r\n" with "\r\r\n".
|
||||
str = strings.ReplaceAll(str, "\r\n", "\n")
|
||||
str = strings.ReplaceAll(str, "\n", "\r\n")
|
||||
ClearRect(s, rect)
|
||||
printString(s, ansi.GraphemeWidth, rect.Min.X, rect.Min.Y, rect, str, true, "")
|
||||
}
|
||||
|
||||
// SetContent clears the cell buffer with blank cells, and sets the given string
|
||||
// as its content. If the height or width of the string exceeds the height or
|
||||
// width of the cell buffer, it will be truncated.
|
||||
func SetContent(s CellBuffer, str string) {
|
||||
SetContentRect(s, str, s.Bounds())
|
||||
}
|
||||
|
||||
// Render returns a string representation of the grid with ANSI escape sequences.
|
||||
func Render(d CellBuffer) string {
|
||||
var buf bytes.Buffer
|
||||
height := d.Bounds().Dy()
|
||||
for y := 0; y < height; y++ {
|
||||
_, line := RenderLine(d, y)
|
||||
buf.WriteString(line)
|
||||
if y < height-1 {
|
||||
buf.WriteString("\r\n")
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// RenderLine returns a string representation of the yth line of the grid along
|
||||
// with the width of the line.
|
||||
func RenderLine(d CellBuffer, n int) (w int, line string) {
|
||||
var pen Style
|
||||
var link Link
|
||||
var buf bytes.Buffer
|
||||
var pendingLine string
|
||||
var pendingWidth int // this ignores space cells until we hit a non-space cell
|
||||
|
||||
writePending := func() {
|
||||
// If there's no pending line, we don't need to do anything.
|
||||
if len(pendingLine) == 0 {
|
||||
return
|
||||
}
|
||||
buf.WriteString(pendingLine)
|
||||
w += pendingWidth
|
||||
pendingWidth = 0
|
||||
pendingLine = ""
|
||||
}
|
||||
|
||||
for x := 0; x < d.Bounds().Dx(); x++ {
|
||||
if cell := d.Cell(x, n); cell != nil && cell.Width > 0 {
|
||||
// Convert the cell's style and link to the given color profile.
|
||||
cellStyle := cell.Style
|
||||
cellLink := cell.Link
|
||||
if cellStyle.Empty() && !pen.Empty() {
|
||||
writePending()
|
||||
buf.WriteString(ansi.ResetStyle) //nolint:errcheck
|
||||
pen.Reset()
|
||||
}
|
||||
if !cellStyle.Equal(&pen) {
|
||||
writePending()
|
||||
seq := cellStyle.DiffSequence(pen)
|
||||
buf.WriteString(seq) // nolint:errcheck
|
||||
pen = cellStyle
|
||||
}
|
||||
|
||||
// Write the URL escape sequence
|
||||
if cellLink != link && link.URL != "" {
|
||||
writePending()
|
||||
buf.WriteString(ansi.ResetHyperlink()) //nolint:errcheck
|
||||
link.Reset()
|
||||
}
|
||||
if cellLink != link {
|
||||
writePending()
|
||||
buf.WriteString(ansi.SetHyperlink(cellLink.URL, cellLink.Params)) //nolint:errcheck
|
||||
link = cellLink
|
||||
}
|
||||
|
||||
// We only write the cell content if it's not empty. If it is, we
|
||||
// append it to the pending line and width to be evaluated later.
|
||||
if cell.Equal(&BlankCell) {
|
||||
pendingLine += cell.String()
|
||||
pendingWidth += cell.Width
|
||||
} else {
|
||||
writePending()
|
||||
buf.WriteString(cell.String())
|
||||
w += cell.Width
|
||||
}
|
||||
}
|
||||
}
|
||||
if link.URL != "" {
|
||||
buf.WriteString(ansi.ResetHyperlink()) //nolint:errcheck
|
||||
}
|
||||
if !pen.Empty() {
|
||||
buf.WriteString(ansi.ResetStyle) //nolint:errcheck
|
||||
}
|
||||
return w, strings.TrimRight(buf.String(), " ") // Trim trailing spaces
|
||||
}
|
||||
|
||||
// ScreenWriter represents a writer that writes to a [Screen] parsing ANSI
|
||||
// escape sequences and Unicode characters and converting them into cells that
|
||||
// can be written to a cell [Buffer].
|
||||
type ScreenWriter struct {
|
||||
*Screen
|
||||
}
|
||||
|
||||
// NewScreenWriter creates a new ScreenWriter that writes to the given Screen.
|
||||
// This is a convenience function for creating a ScreenWriter.
|
||||
func NewScreenWriter(s *Screen) *ScreenWriter {
|
||||
return &ScreenWriter{s}
|
||||
}
|
||||
|
||||
// Write writes the given bytes to the screen.
|
||||
// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
|
||||
// sequences.
|
||||
func (s *ScreenWriter) Write(p []byte) (n int, err error) {
|
||||
printString(s.Screen, s.method,
|
||||
s.cur.X, s.cur.Y, s.Bounds(),
|
||||
p, false, "")
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// SetContent clears the screen with blank cells, and sets the given string as
|
||||
// its content. If the height or width of the string exceeds the height or
|
||||
// width of the screen, it will be truncated.
|
||||
//
|
||||
// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape sequences.
|
||||
func (s *ScreenWriter) SetContent(str string) {
|
||||
s.SetContentRect(str, s.Bounds())
|
||||
}
|
||||
|
||||
// SetContentRect clears the rectangle within the screen with blank cells, and
|
||||
// sets the given string as its content. If the height or width of the string
|
||||
// exceeds the height or width of the screen, it will be truncated.
|
||||
//
|
||||
// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
|
||||
// sequences.
|
||||
func (s *ScreenWriter) SetContentRect(str string, rect Rectangle) {
|
||||
// Replace all "\n" with "\r\n" to ensure the cursor is reset to the start
|
||||
// of the line. Make sure we don't replace "\r\n" with "\r\r\n".
|
||||
str = strings.ReplaceAll(str, "\r\n", "\n")
|
||||
str = strings.ReplaceAll(str, "\n", "\r\n")
|
||||
s.ClearRect(rect)
|
||||
printString(s.Screen, s.method,
|
||||
rect.Min.X, rect.Min.Y, rect,
|
||||
str, true, "")
|
||||
}
|
||||
|
||||
// Print prints the string at the current cursor position. It will wrap the
|
||||
// string to the width of the screen if it exceeds the width of the screen.
|
||||
// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
|
||||
// sequences.
|
||||
func (s *ScreenWriter) Print(str string, v ...interface{}) {
|
||||
if len(v) > 0 {
|
||||
str = fmt.Sprintf(str, v...)
|
||||
}
|
||||
printString(s.Screen, s.method,
|
||||
s.cur.X, s.cur.Y, s.Bounds(),
|
||||
str, false, "")
|
||||
}
|
||||
|
||||
// PrintAt prints the string at the given position. It will wrap the string to
|
||||
// the width of the screen if it exceeds the width of the screen.
|
||||
// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
|
||||
// sequences.
|
||||
func (s *ScreenWriter) PrintAt(x, y int, str string, v ...interface{}) {
|
||||
if len(v) > 0 {
|
||||
str = fmt.Sprintf(str, v...)
|
||||
}
|
||||
printString(s.Screen, s.method,
|
||||
x, y, s.Bounds(),
|
||||
str, false, "")
|
||||
}
|
||||
|
||||
// PrintCrop prints the string at the current cursor position and truncates the
|
||||
// text if it exceeds the width of the screen. Use tail to specify a string to
|
||||
// append if the string is truncated.
|
||||
// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
|
||||
// sequences.
|
||||
func (s *ScreenWriter) PrintCrop(str string, tail string) {
|
||||
printString(s.Screen, s.method,
|
||||
s.cur.X, s.cur.Y, s.Bounds(),
|
||||
str, true, tail)
|
||||
}
|
||||
|
||||
// PrintCropAt prints the string at the given position and truncates the text
|
||||
// if it exceeds the width of the screen. Use tail to specify a string to append
|
||||
// if the string is truncated.
|
||||
// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
|
||||
// sequences.
|
||||
func (s *ScreenWriter) PrintCropAt(x, y int, str string, tail string) {
|
||||
printString(s.Screen, s.method,
|
||||
x, y, s.Bounds(),
|
||||
str, true, tail)
|
||||
}
|
||||
|
||||
// printString draws a string starting at the given position.
|
||||
func printString[T []byte | string](
|
||||
s CellBuffer,
|
||||
m ansi.Method,
|
||||
x, y int,
|
||||
bounds Rectangle, str T,
|
||||
truncate bool, tail string,
|
||||
) {
|
||||
p := ansi.GetParser()
|
||||
defer ansi.PutParser(p)
|
||||
|
||||
var tailc Cell
|
||||
if truncate && len(tail) > 0 {
|
||||
if m == ansi.WcWidth {
|
||||
tailc = *NewCellString(tail)
|
||||
} else {
|
||||
tailc = *NewGraphemeCell(tail)
|
||||
}
|
||||
}
|
||||
|
||||
decoder := ansi.DecodeSequenceWc[T]
|
||||
if m == ansi.GraphemeWidth {
|
||||
decoder = ansi.DecodeSequence[T]
|
||||
}
|
||||
|
||||
var cell Cell
|
||||
var style Style
|
||||
var link Link
|
||||
var state byte
|
||||
for len(str) > 0 {
|
||||
seq, width, n, newState := decoder(str, state, p)
|
||||
|
||||
switch width {
|
||||
case 1, 2, 3, 4: // wide cells can go up to 4 cells wide
|
||||
cell.Width += width
|
||||
cell.Append([]rune(string(seq))...)
|
||||
|
||||
if !truncate && x+cell.Width > bounds.Max.X && y+1 < bounds.Max.Y {
|
||||
// Wrap the string to the width of the window
|
||||
x = bounds.Min.X
|
||||
y++
|
||||
}
|
||||
if Pos(x, y).In(bounds) {
|
||||
if truncate && tailc.Width > 0 && x+cell.Width > bounds.Max.X-tailc.Width {
|
||||
// Truncate the string and append the tail if any.
|
||||
cell := tailc
|
||||
cell.Style = style
|
||||
cell.Link = link
|
||||
s.SetCell(x, y, &cell)
|
||||
x += tailc.Width
|
||||
} else {
|
||||
// Print the cell to the screen
|
||||
cell.Style = style
|
||||
cell.Link = link
|
||||
s.SetCell(x, y, &cell) //nolint:errcheck
|
||||
x += width
|
||||
}
|
||||
}
|
||||
|
||||
// String is too long for the line, truncate it.
|
||||
// Make sure we reset the cell for the next iteration.
|
||||
cell.Reset()
|
||||
default:
|
||||
// Valid sequences always have a non-zero Cmd.
|
||||
// TODO: Handle cursor movement and other sequences
|
||||
switch {
|
||||
case ansi.HasCsiPrefix(seq) && p.Command() == 'm':
|
||||
// SGR - Select Graphic Rendition
|
||||
ReadStyle(p.Params(), &style)
|
||||
case ansi.HasOscPrefix(seq) && p.Command() == 8:
|
||||
// Hyperlinks
|
||||
ReadLink(p.Data(), &link)
|
||||
case ansi.Equal(seq, T("\n")):
|
||||
y++
|
||||
case ansi.Equal(seq, T("\r")):
|
||||
x = bounds.Min.X
|
||||
default:
|
||||
cell.Append([]rune(string(seq))...)
|
||||
}
|
||||
}
|
||||
|
||||
// Advance the state and data
|
||||
state = newState
|
||||
str = str[n:]
|
||||
}
|
||||
|
||||
// Make sure to set the last cell if it's not empty.
|
||||
if !cell.Empty() {
|
||||
s.SetCell(x, y, &cell) //nolint:errcheck
|
||||
cell.Reset()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user