main
Raw Download raw file
  1package cellbuf
  2
  3import (
  4	"github.com/charmbracelet/x/ansi"
  5)
  6
  7var (
  8	// BlankCell is a cell with a single space, width of 1, and no style or link.
  9	BlankCell = Cell{Rune: ' ', Width: 1}
 10
 11	// EmptyCell is just an empty cell used for comparisons and as a placeholder
 12	// for wide cells.
 13	EmptyCell = Cell{}
 14)
 15
 16// Cell represents a single cell in the terminal screen.
 17type Cell struct {
 18	// The style of the cell. Nil style means no style. Zero value prints a
 19	// reset sequence.
 20	Style Style
 21
 22	// Link is the hyperlink of the cell.
 23	Link Link
 24
 25	// Comb is the combining runes of the cell. This is nil if the cell is a
 26	// single rune or if it's a zero width cell that is part of a wider cell.
 27	Comb []rune
 28
 29	// Width is the mono-space width of the grapheme cluster.
 30	Width int
 31
 32	// Rune is the main rune of the cell. This is zero if the cell is part of a
 33	// wider cell.
 34	Rune rune
 35}
 36
 37// Append appends runes to the cell without changing the width. This is useful
 38// when we want to use the cell to store escape sequences or other runes that
 39// don't affect the width of the cell.
 40func (c *Cell) Append(r ...rune) {
 41	for i, r := range r {
 42		if i == 0 && c.Rune == 0 {
 43			c.Rune = r
 44			continue
 45		}
 46		c.Comb = append(c.Comb, r)
 47	}
 48}
 49
 50// String returns the string content of the cell excluding any styles, links,
 51// and escape sequences.
 52func (c Cell) String() string {
 53	if c.Rune == 0 {
 54		return ""
 55	}
 56	if len(c.Comb) == 0 {
 57		return string(c.Rune)
 58	}
 59	return string(append([]rune{c.Rune}, c.Comb...))
 60}
 61
 62// Equal returns whether the cell is equal to the other cell.
 63func (c *Cell) Equal(o *Cell) bool {
 64	return o != nil &&
 65		c.Width == o.Width &&
 66		c.Rune == o.Rune &&
 67		runesEqual(c.Comb, o.Comb) &&
 68		c.Style.Equal(&o.Style) &&
 69		c.Link.Equal(&o.Link)
 70}
 71
 72// Empty returns whether the cell is an empty cell. An empty cell is a cell
 73// with a width of 0, a rune of 0, and no combining runes.
 74func (c Cell) Empty() bool {
 75	return c.Width == 0 &&
 76		c.Rune == 0 &&
 77		len(c.Comb) == 0
 78}
 79
 80// Reset resets the cell to the default state zero value.
 81func (c *Cell) Reset() {
 82	c.Rune = 0
 83	c.Comb = nil
 84	c.Width = 0
 85	c.Style.Reset()
 86	c.Link.Reset()
 87}
 88
 89// Clear returns whether the cell consists of only attributes that don't
 90// affect appearance of a space character.
 91func (c *Cell) Clear() bool {
 92	return c.Rune == ' ' && len(c.Comb) == 0 && c.Width == 1 && c.Style.Clear() && c.Link.Empty()
 93}
 94
 95// Clone returns a copy of the cell.
 96func (c *Cell) Clone() (n *Cell) {
 97	n = new(Cell)
 98	*n = *c
 99	return
100}
101
102// Blank makes the cell a blank cell by setting the rune to a space, comb to
103// nil, and the width to 1.
104func (c *Cell) Blank() *Cell {
105	c.Rune = ' '
106	c.Comb = nil
107	c.Width = 1
108	return c
109}
110
111// Link represents a hyperlink in the terminal screen.
112type Link struct {
113	URL    string
114	Params string
115}
116
117// String returns a string representation of the hyperlink.
118func (h Link) String() string {
119	return h.URL
120}
121
122// Reset resets the hyperlink to the default state zero value.
123func (h *Link) Reset() {
124	h.URL = ""
125	h.Params = ""
126}
127
128// Equal returns whether the hyperlink is equal to the other hyperlink.
129func (h *Link) Equal(o *Link) bool {
130	return o != nil && h.URL == o.URL && h.Params == o.Params
131}
132
133// Empty returns whether the hyperlink is empty.
134func (h Link) Empty() bool {
135	return h.URL == "" && h.Params == ""
136}
137
138// AttrMask is a bitmask for text attributes that can change the look of text.
139// These attributes can be combined to create different styles.
140type AttrMask uint8
141
142// These are the available text attributes that can be combined to create
143// different styles.
144const (
145	BoldAttr AttrMask = 1 << iota
146	FaintAttr
147	ItalicAttr
148	SlowBlinkAttr
149	RapidBlinkAttr
150	ReverseAttr
151	ConcealAttr
152	StrikethroughAttr
153
154	ResetAttr AttrMask = 0
155)
156
157// UnderlineStyle is the style of underline to use for text.
158type UnderlineStyle = ansi.UnderlineStyle
159
160// These are the available underline styles.
161const (
162	NoUnderline     = ansi.NoUnderlineStyle
163	SingleUnderline = ansi.SingleUnderlineStyle
164	DoubleUnderline = ansi.DoubleUnderlineStyle
165	CurlyUnderline  = ansi.CurlyUnderlineStyle
166	DottedUnderline = ansi.DottedUnderlineStyle
167	DashedUnderline = ansi.DashedUnderlineStyle
168)
169
170// Style represents the Style of a cell.
171type Style struct {
172	Fg      ansi.Color
173	Bg      ansi.Color
174	Ul      ansi.Color
175	Attrs   AttrMask
176	UlStyle UnderlineStyle
177}
178
179// Sequence returns the ANSI sequence that sets the style.
180func (s Style) Sequence() string {
181	if s.Empty() {
182		return ansi.ResetStyle
183	}
184
185	var b ansi.Style
186
187	if s.Attrs != 0 {
188		if s.Attrs&BoldAttr != 0 {
189			b = b.Bold()
190		}
191		if s.Attrs&FaintAttr != 0 {
192			b = b.Faint()
193		}
194		if s.Attrs&ItalicAttr != 0 {
195			b = b.Italic()
196		}
197		if s.Attrs&SlowBlinkAttr != 0 {
198			b = b.SlowBlink()
199		}
200		if s.Attrs&RapidBlinkAttr != 0 {
201			b = b.RapidBlink()
202		}
203		if s.Attrs&ReverseAttr != 0 {
204			b = b.Reverse()
205		}
206		if s.Attrs&ConcealAttr != 0 {
207			b = b.Conceal()
208		}
209		if s.Attrs&StrikethroughAttr != 0 {
210			b = b.Strikethrough()
211		}
212	}
213	if s.UlStyle != NoUnderline {
214		switch s.UlStyle {
215		case SingleUnderline:
216			b = b.Underline()
217		case DoubleUnderline:
218			b = b.DoubleUnderline()
219		case CurlyUnderline:
220			b = b.CurlyUnderline()
221		case DottedUnderline:
222			b = b.DottedUnderline()
223		case DashedUnderline:
224			b = b.DashedUnderline()
225		}
226	}
227	if s.Fg != nil {
228		b = b.ForegroundColor(s.Fg)
229	}
230	if s.Bg != nil {
231		b = b.BackgroundColor(s.Bg)
232	}
233	if s.Ul != nil {
234		b = b.UnderlineColor(s.Ul)
235	}
236
237	return b.String()
238}
239
240// DiffSequence returns the ANSI sequence that sets the style as a diff from
241// another style.
242func (s Style) DiffSequence(o Style) string {
243	if o.Empty() {
244		return s.Sequence()
245	}
246
247	var b ansi.Style
248
249	if !colorEqual(s.Fg, o.Fg) {
250		b = b.ForegroundColor(s.Fg)
251	}
252
253	if !colorEqual(s.Bg, o.Bg) {
254		b = b.BackgroundColor(s.Bg)
255	}
256
257	if !colorEqual(s.Ul, o.Ul) {
258		b = b.UnderlineColor(s.Ul)
259	}
260
261	var (
262		noBlink  bool
263		isNormal bool
264	)
265
266	if s.Attrs != o.Attrs {
267		if s.Attrs&BoldAttr != o.Attrs&BoldAttr {
268			if s.Attrs&BoldAttr != 0 {
269				b = b.Bold()
270			} else if !isNormal {
271				isNormal = true
272				b = b.NormalIntensity()
273			}
274		}
275		if s.Attrs&FaintAttr != o.Attrs&FaintAttr {
276			if s.Attrs&FaintAttr != 0 {
277				b = b.Faint()
278			} else if !isNormal {
279				b = b.NormalIntensity()
280			}
281		}
282		if s.Attrs&ItalicAttr != o.Attrs&ItalicAttr {
283			if s.Attrs&ItalicAttr != 0 {
284				b = b.Italic()
285			} else {
286				b = b.NoItalic()
287			}
288		}
289		if s.Attrs&SlowBlinkAttr != o.Attrs&SlowBlinkAttr {
290			if s.Attrs&SlowBlinkAttr != 0 {
291				b = b.SlowBlink()
292			} else if !noBlink {
293				noBlink = true
294				b = b.NoBlink()
295			}
296		}
297		if s.Attrs&RapidBlinkAttr != o.Attrs&RapidBlinkAttr {
298			if s.Attrs&RapidBlinkAttr != 0 {
299				b = b.RapidBlink()
300			} else if !noBlink {
301				b = b.NoBlink()
302			}
303		}
304		if s.Attrs&ReverseAttr != o.Attrs&ReverseAttr {
305			if s.Attrs&ReverseAttr != 0 {
306				b = b.Reverse()
307			} else {
308				b = b.NoReverse()
309			}
310		}
311		if s.Attrs&ConcealAttr != o.Attrs&ConcealAttr {
312			if s.Attrs&ConcealAttr != 0 {
313				b = b.Conceal()
314			} else {
315				b = b.NoConceal()
316			}
317		}
318		if s.Attrs&StrikethroughAttr != o.Attrs&StrikethroughAttr {
319			if s.Attrs&StrikethroughAttr != 0 {
320				b = b.Strikethrough()
321			} else {
322				b = b.NoStrikethrough()
323			}
324		}
325	}
326
327	if s.UlStyle != o.UlStyle {
328		b = b.UnderlineStyle(s.UlStyle)
329	}
330
331	return b.String()
332}
333
334// Equal returns true if the style is equal to the other style.
335func (s *Style) Equal(o *Style) bool {
336	return s.Attrs == o.Attrs &&
337		s.UlStyle == o.UlStyle &&
338		colorEqual(s.Fg, o.Fg) &&
339		colorEqual(s.Bg, o.Bg) &&
340		colorEqual(s.Ul, o.Ul)
341}
342
343func colorEqual(c, o ansi.Color) bool {
344	if c == nil && o == nil {
345		return true
346	}
347	if c == nil || o == nil {
348		return false
349	}
350	cr, cg, cb, ca := c.RGBA()
351	or, og, ob, oa := o.RGBA()
352	return cr == or && cg == og && cb == ob && ca == oa
353}
354
355// Bold sets the bold attribute.
356func (s *Style) Bold(v bool) *Style {
357	if v {
358		s.Attrs |= BoldAttr
359	} else {
360		s.Attrs &^= BoldAttr
361	}
362	return s
363}
364
365// Faint sets the faint attribute.
366func (s *Style) Faint(v bool) *Style {
367	if v {
368		s.Attrs |= FaintAttr
369	} else {
370		s.Attrs &^= FaintAttr
371	}
372	return s
373}
374
375// Italic sets the italic attribute.
376func (s *Style) Italic(v bool) *Style {
377	if v {
378		s.Attrs |= ItalicAttr
379	} else {
380		s.Attrs &^= ItalicAttr
381	}
382	return s
383}
384
385// SlowBlink sets the slow blink attribute.
386func (s *Style) SlowBlink(v bool) *Style {
387	if v {
388		s.Attrs |= SlowBlinkAttr
389	} else {
390		s.Attrs &^= SlowBlinkAttr
391	}
392	return s
393}
394
395// RapidBlink sets the rapid blink attribute.
396func (s *Style) RapidBlink(v bool) *Style {
397	if v {
398		s.Attrs |= RapidBlinkAttr
399	} else {
400		s.Attrs &^= RapidBlinkAttr
401	}
402	return s
403}
404
405// Reverse sets the reverse attribute.
406func (s *Style) Reverse(v bool) *Style {
407	if v {
408		s.Attrs |= ReverseAttr
409	} else {
410		s.Attrs &^= ReverseAttr
411	}
412	return s
413}
414
415// Conceal sets the conceal attribute.
416func (s *Style) Conceal(v bool) *Style {
417	if v {
418		s.Attrs |= ConcealAttr
419	} else {
420		s.Attrs &^= ConcealAttr
421	}
422	return s
423}
424
425// Strikethrough sets the strikethrough attribute.
426func (s *Style) Strikethrough(v bool) *Style {
427	if v {
428		s.Attrs |= StrikethroughAttr
429	} else {
430		s.Attrs &^= StrikethroughAttr
431	}
432	return s
433}
434
435// UnderlineStyle sets the underline style.
436func (s *Style) UnderlineStyle(style UnderlineStyle) *Style {
437	s.UlStyle = style
438	return s
439}
440
441// Underline sets the underline attribute.
442// This is a syntactic sugar for [UnderlineStyle].
443func (s *Style) Underline(v bool) *Style {
444	if v {
445		return s.UnderlineStyle(SingleUnderline)
446	}
447	return s.UnderlineStyle(NoUnderline)
448}
449
450// Foreground sets the foreground color.
451func (s *Style) Foreground(c ansi.Color) *Style {
452	s.Fg = c
453	return s
454}
455
456// Background sets the background color.
457func (s *Style) Background(c ansi.Color) *Style {
458	s.Bg = c
459	return s
460}
461
462// UnderlineColor sets the underline color.
463func (s *Style) UnderlineColor(c ansi.Color) *Style {
464	s.Ul = c
465	return s
466}
467
468// Reset resets the style to default.
469func (s *Style) Reset() *Style {
470	s.Fg = nil
471	s.Bg = nil
472	s.Ul = nil
473	s.Attrs = ResetAttr
474	s.UlStyle = NoUnderline
475	return s
476}
477
478// Empty returns true if the style is empty.
479func (s *Style) Empty() bool {
480	return s.Fg == nil && s.Bg == nil && s.Ul == nil && s.Attrs == ResetAttr && s.UlStyle == NoUnderline
481}
482
483// Clear returns whether the style consists of only attributes that don't
484// affect appearance of a space character.
485func (s *Style) Clear() bool {
486	return s.UlStyle == NoUnderline &&
487		s.Attrs&^(BoldAttr|FaintAttr|ItalicAttr|SlowBlinkAttr|RapidBlinkAttr) == 0 &&
488		s.Fg == nil &&
489		s.Bg == nil &&
490		s.Ul == nil
491}
492
493func runesEqual(a, b []rune) bool {
494	if len(a) != len(b) {
495		return false
496	}
497	for i, r := range a {
498		if r != b[i] {
499			return false
500		}
501	}
502	return true
503}