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:
2025-03-16 12:04:32 +01:00
parent a2b678caf6
commit 1723025fbf
822 changed files with 25433 additions and 197407 deletions

21
vendor/github.com/charmbracelet/x/cellbuf/LICENSE generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

131
vendor/github.com/charmbracelet/x/cellbuf/sequence.go generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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()
}
}