main
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}