cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

cat/module-path
012 / internal / ui / theme / fill.go
fill.go 2.6K
1package theme23import (4 "image/color"5 "strings"67 "charm.land/lipgloss/v2"8 "github.com/charmbracelet/x/ansi"9)1011// Fill draws a rendered line on a band's colors out to width columns:12// every cell without a background of its own gets the band's background,13// and every cell without a text color its foreground. Styled parts (key14// chips, the mode indicator) keep their colors. A band with no colors15// returns the line unchanged.16//17// It works on the escape sequences, so it costs one pass over the line:18// the band's colors are set at the start and again after every SGR that19// resets them.20func Fill(line string, width int, band lipgloss.Style) string {21 bg, fg := band.GetBackground(), band.GetForeground()22 hasBg, hasFg := !isNoColor(bg), !isNoColor(fg)23 if !hasBg && !hasFg {24 return line25 }26 set := func(needBg, needFg bool) string {27 st := ansi.NewStyle()28 if needBg && hasBg {29 st = st.BackgroundColor(bg)30 }31 if needFg && hasFg {32 st = st.ForegroundColor(fg)33 }34 if len(st) == 0 {35 return ""36 }37 return st.String()38 }39 var b strings.Builder40 b.Grow(len(line) + 64)41 b.WriteString(set(true, true))42 p := ansi.NewParser()43 var state byte44 w := 045 for s := line; len(s) > 0; {46 seq, sw, n, newState := ansi.DecodeSequence(s, state, p)47 state, s = newState, s[n:]48 b.WriteString(seq)49 w += sw50 if sw == 0 && isSGR(seq, p) {51 resetBg, resetFg := sgrResets(p.Params())52 b.WriteString(set(resetBg, resetFg))53 }54 }55 if w < width {56 b.WriteString("\x1b[m" + set(true, true) + strings.Repeat(" ", width-w))57 }58 b.WriteString("\x1b[m")59 return b.String()60}6162// isSGR reports whether seq, just decoded by p, is Select Graphic63// Rendition (ESC [ ... m).64func isSGR(seq string, p *ansi.Parser) bool {65 if !ansi.HasCsiPrefix(seq) {66 return false67 }68 c := ansi.Cmd(p.Command())69 return c.Final() == 'm' && c.Prefix() == 0 && c.Intermediate() == 070}7172// sgrResets reports whether an SGR's parameters leave the background and73// the foreground at the terminal's default, so the band's must be set74// again.75func sgrResets(params ansi.Params) (bg, fg bool) {76 if len(params) == 0 {77 return true, true78 }79 for i := 0; i < len(params); i++ {80 switch v := params[i].Param(0); {81 case v == 0:82 bg, fg = true, true83 case v == 39:84 fg = true85 case v == 49:86 bg = true87 case v >= 30 && v <= 37, v >= 90 && v <= 97:88 fg = false89 case v >= 40 && v <= 47, v >= 100 && v <= 107:90 bg = false91 case v == 38 || v == 48 || v == 58:92 var c color.Color93 n := ansi.ReadStyleColor(params[i:], &c)94 if v == 38 {95 fg = false96 } else if v == 48 {97 bg = false98 }99 i += max(n, 1) - 1100 }101 }102 return bg, fg103}