0
0
forked from toolshed/abra
decentral1se 1723025fbf
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.
2025-03-16 12:31:45 +01:00

509 lines
10 KiB
Go

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
}