forked from toolshed/abra
		
	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.
		
			
				
	
	
		
			509 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			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
 | |
| }
 |