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// Contains returns whether the attribute mask contains the attribute.
158func (a AttrMask) Contains(attr AttrMask) bool {
159 return a&attr == attr
160}
161
162// UnderlineStyle is the style of underline to use for text.
163type UnderlineStyle = ansi.UnderlineStyle
164
165// These are the available underline styles.
166const (
167 NoUnderline = ansi.NoUnderlineStyle
168 SingleUnderline = ansi.SingleUnderlineStyle
169 DoubleUnderline = ansi.DoubleUnderlineStyle
170 CurlyUnderline = ansi.CurlyUnderlineStyle
171 DottedUnderline = ansi.DottedUnderlineStyle
172 DashedUnderline = ansi.DashedUnderlineStyle
173)
174
175// Style represents the Style of a cell.
176type Style struct {
177 Fg ansi.Color
178 Bg ansi.Color
179 Ul ansi.Color
180 Attrs AttrMask
181 UlStyle UnderlineStyle
182}
183
184// Sequence returns the ANSI sequence that sets the style.
185func (s Style) Sequence() string {
186 if s.Empty() {
187 return ansi.ResetStyle
188 }
189
190 var b ansi.Style
191
192 if s.Attrs != 0 {
193 if s.Attrs&BoldAttr != 0 {
194 b = b.Bold()
195 }
196 if s.Attrs&FaintAttr != 0 {
197 b = b.Faint()
198 }
199 if s.Attrs&ItalicAttr != 0 {
200 b = b.Italic()
201 }
202 if s.Attrs&SlowBlinkAttr != 0 {
203 b = b.SlowBlink()
204 }
205 if s.Attrs&RapidBlinkAttr != 0 {
206 b = b.RapidBlink()
207 }
208 if s.Attrs&ReverseAttr != 0 {
209 b = b.Reverse()
210 }
211 if s.Attrs&ConcealAttr != 0 {
212 b = b.Conceal()
213 }
214 if s.Attrs&StrikethroughAttr != 0 {
215 b = b.Strikethrough()
216 }
217 }
218 if s.UlStyle != NoUnderline {
219 switch s.UlStyle {
220 case SingleUnderline:
221 b = b.Underline()
222 case DoubleUnderline:
223 b = b.DoubleUnderline()
224 case CurlyUnderline:
225 b = b.CurlyUnderline()
226 case DottedUnderline:
227 b = b.DottedUnderline()
228 case DashedUnderline:
229 b = b.DashedUnderline()
230 }
231 }
232 if s.Fg != nil {
233 b = b.ForegroundColor(s.Fg)
234 }
235 if s.Bg != nil {
236 b = b.BackgroundColor(s.Bg)
237 }
238 if s.Ul != nil {
239 b = b.UnderlineColor(s.Ul)
240 }
241
242 return b.String()
243}
244
245// DiffSequence returns the ANSI sequence that sets the style as a diff from
246// another style.
247func (s Style) DiffSequence(o Style) string {
248 if o.Empty() {
249 return s.Sequence()
250 }
251
252 var b ansi.Style
253
254 if !colorEqual(s.Fg, o.Fg) {
255 b = b.ForegroundColor(s.Fg)
256 }
257
258 if !colorEqual(s.Bg, o.Bg) {
259 b = b.BackgroundColor(s.Bg)
260 }
261
262 if !colorEqual(s.Ul, o.Ul) {
263 b = b.UnderlineColor(s.Ul)
264 }
265
266 var (
267 noBlink bool
268 isNormal bool
269 )
270
271 if s.Attrs != o.Attrs {
272 if s.Attrs&BoldAttr != o.Attrs&BoldAttr {
273 if s.Attrs&BoldAttr != 0 {
274 b = b.Bold()
275 } else if !isNormal {
276 isNormal = true
277 b = b.NormalIntensity()
278 }
279 }
280 if s.Attrs&FaintAttr != o.Attrs&FaintAttr {
281 if s.Attrs&FaintAttr != 0 {
282 b = b.Faint()
283 } else if !isNormal {
284 b = b.NormalIntensity()
285 }
286 }
287 if s.Attrs&ItalicAttr != o.Attrs&ItalicAttr {
288 if s.Attrs&ItalicAttr != 0 {
289 b = b.Italic()
290 } else {
291 b = b.NoItalic()
292 }
293 }
294 if s.Attrs&SlowBlinkAttr != o.Attrs&SlowBlinkAttr {
295 if s.Attrs&SlowBlinkAttr != 0 {
296 b = b.SlowBlink()
297 } else if !noBlink {
298 noBlink = true
299 b = b.NoBlink()
300 }
301 }
302 if s.Attrs&RapidBlinkAttr != o.Attrs&RapidBlinkAttr {
303 if s.Attrs&RapidBlinkAttr != 0 {
304 b = b.RapidBlink()
305 } else if !noBlink {
306 b = b.NoBlink()
307 }
308 }
309 if s.Attrs&ReverseAttr != o.Attrs&ReverseAttr {
310 if s.Attrs&ReverseAttr != 0 {
311 b = b.Reverse()
312 } else {
313 b = b.NoReverse()
314 }
315 }
316 if s.Attrs&ConcealAttr != o.Attrs&ConcealAttr {
317 if s.Attrs&ConcealAttr != 0 {
318 b = b.Conceal()
319 } else {
320 b = b.NoConceal()
321 }
322 }
323 if s.Attrs&StrikethroughAttr != o.Attrs&StrikethroughAttr {
324 if s.Attrs&StrikethroughAttr != 0 {
325 b = b.Strikethrough()
326 } else {
327 b = b.NoStrikethrough()
328 }
329 }
330 }
331
332 if s.UlStyle != o.UlStyle {
333 b = b.UnderlineStyle(s.UlStyle)
334 }
335
336 return b.String()
337}
338
339// Equal returns true if the style is equal to the other style.
340func (s *Style) Equal(o *Style) bool {
341 return s.Attrs == o.Attrs &&
342 s.UlStyle == o.UlStyle &&
343 colorEqual(s.Fg, o.Fg) &&
344 colorEqual(s.Bg, o.Bg) &&
345 colorEqual(s.Ul, o.Ul)
346}
347
348func colorEqual(c, o ansi.Color) bool {
349 if c == nil && o == nil {
350 return true
351 }
352 if c == nil || o == nil {
353 return false
354 }
355 cr, cg, cb, ca := c.RGBA()
356 or, og, ob, oa := o.RGBA()
357 return cr == or && cg == og && cb == ob && ca == oa
358}
359
360// Bold sets the bold attribute.
361func (s *Style) Bold(v bool) *Style {
362 if v {
363 s.Attrs |= BoldAttr
364 } else {
365 s.Attrs &^= BoldAttr
366 }
367 return s
368}
369
370// Faint sets the faint attribute.
371func (s *Style) Faint(v bool) *Style {
372 if v {
373 s.Attrs |= FaintAttr
374 } else {
375 s.Attrs &^= FaintAttr
376 }
377 return s
378}
379
380// Italic sets the italic attribute.
381func (s *Style) Italic(v bool) *Style {
382 if v {
383 s.Attrs |= ItalicAttr
384 } else {
385 s.Attrs &^= ItalicAttr
386 }
387 return s
388}
389
390// SlowBlink sets the slow blink attribute.
391func (s *Style) SlowBlink(v bool) *Style {
392 if v {
393 s.Attrs |= SlowBlinkAttr
394 } else {
395 s.Attrs &^= SlowBlinkAttr
396 }
397 return s
398}
399
400// RapidBlink sets the rapid blink attribute.
401func (s *Style) RapidBlink(v bool) *Style {
402 if v {
403 s.Attrs |= RapidBlinkAttr
404 } else {
405 s.Attrs &^= RapidBlinkAttr
406 }
407 return s
408}
409
410// Reverse sets the reverse attribute.
411func (s *Style) Reverse(v bool) *Style {
412 if v {
413 s.Attrs |= ReverseAttr
414 } else {
415 s.Attrs &^= ReverseAttr
416 }
417 return s
418}
419
420// Conceal sets the conceal attribute.
421func (s *Style) Conceal(v bool) *Style {
422 if v {
423 s.Attrs |= ConcealAttr
424 } else {
425 s.Attrs &^= ConcealAttr
426 }
427 return s
428}
429
430// Strikethrough sets the strikethrough attribute.
431func (s *Style) Strikethrough(v bool) *Style {
432 if v {
433 s.Attrs |= StrikethroughAttr
434 } else {
435 s.Attrs &^= StrikethroughAttr
436 }
437 return s
438}
439
440// UnderlineStyle sets the underline style.
441func (s *Style) UnderlineStyle(style UnderlineStyle) *Style {
442 s.UlStyle = style
443 return s
444}
445
446// Underline sets the underline attribute.
447// This is a syntactic sugar for [UnderlineStyle].
448func (s *Style) Underline(v bool) *Style {
449 if v {
450 return s.UnderlineStyle(SingleUnderline)
451 }
452 return s.UnderlineStyle(NoUnderline)
453}
454
455// Foreground sets the foreground color.
456func (s *Style) Foreground(c ansi.Color) *Style {
457 s.Fg = c
458 return s
459}
460
461// Background sets the background color.
462func (s *Style) Background(c ansi.Color) *Style {
463 s.Bg = c
464 return s
465}
466
467// UnderlineColor sets the underline color.
468func (s *Style) UnderlineColor(c ansi.Color) *Style {
469 s.Ul = c
470 return s
471}
472
473// Reset resets the style to default.
474func (s *Style) Reset() *Style {
475 s.Fg = nil
476 s.Bg = nil
477 s.Ul = nil
478 s.Attrs = ResetAttr
479 s.UlStyle = NoUnderline
480 return s
481}
482
483// Empty returns true if the style is empty.
484func (s *Style) Empty() bool {
485 return s.Fg == nil && s.Bg == nil && s.Ul == nil && s.Attrs == ResetAttr && s.UlStyle == NoUnderline
486}
487
488// Clear returns whether the style consists of only attributes that don't
489// affect appearance of a space character.
490func (s *Style) Clear() bool {
491 return s.UlStyle == NoUnderline &&
492 s.Attrs&^(BoldAttr|FaintAttr|ItalicAttr|SlowBlinkAttr|RapidBlinkAttr) == 0 &&
493 s.Fg == nil &&
494 s.Bg == nil &&
495 s.Ul == nil
496}
497
498func runesEqual(a, b []rune) bool {
499 if len(a) != len(b) {
500 return false
501 }
502 for i, r := range a {
503 if r != b[i] {
504 return false
505 }
506 }
507 return true
508}