1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
|
package ui
import (
"strings"
)
// Styling specifies how to change a Style. It can also be applied to a Segment
// or Text.
type Styling interface{ transform(*Style) }
// StyleText returns a new Text with the given Styling's applied. It does not
// modify the given Text.
func StyleText(t Text, ts ...Styling) Text {
newt := make(Text, len(t))
for i, seg := range t {
newt[i] = StyleSegment(seg, ts...)
}
return newt
}
// StyleSegment returns a new Segment with the given Styling's applied. It does
// not modify the given Segment.
func StyleSegment(seg *Segment, ts ...Styling) *Segment {
return &Segment{Text: seg.Text, Style: ApplyStyling(seg.Style, ts...)}
}
// ApplyStyling returns a new Style with the given Styling's applied.
func ApplyStyling(s Style, ts ...Styling) Style {
for _, t := range ts {
if t != nil {
t.transform(&s)
}
}
return s
}
// Stylings joins several transformers into one.
func Stylings(ts ...Styling) Styling { return jointStyling(ts) }
// Common stylings.
var (
Reset Styling = reset{}
FgDefault Styling = setForeground{nil}
FgBlack Styling = setForeground{Black}
FgRed Styling = setForeground{Red}
FgGreen Styling = setForeground{Green}
FgYellow Styling = setForeground{Yellow}
FgBlue Styling = setForeground{Blue}
FgMagenta Styling = setForeground{Magenta}
FgCyan Styling = setForeground{Cyan}
FgWhite Styling = setForeground{White}
FgBrightBlack Styling = setForeground{BrightBlack}
FgBrightRed Styling = setForeground{BrightRed}
FgBrightGreen Styling = setForeground{BrightGreen}
FgBrightYellow Styling = setForeground{BrightYellow}
FgBrightBlue Styling = setForeground{BrightBlue}
FgBrightMagenta Styling = setForeground{BrightMagenta}
FgBrightCyan Styling = setForeground{BrightCyan}
FgBrightWhite Styling = setForeground{BrightWhite}
BgDefault Styling = setBackground{nil}
BgBlack Styling = setBackground{Black}
BgRed Styling = setBackground{Red}
BgGreen Styling = setBackground{Green}
BgYellow Styling = setBackground{Yellow}
BgBlue Styling = setBackground{Blue}
BgMagenta Styling = setBackground{Magenta}
BgCyan Styling = setBackground{Cyan}
BgWhite Styling = setBackground{White}
BgBrightBlack Styling = setBackground{BrightBlack}
BgBrightRed Styling = setBackground{BrightRed}
BgBrightGreen Styling = setBackground{BrightGreen}
BgBrightYellow Styling = setBackground{BrightYellow}
BgBrightBlue Styling = setBackground{BrightBlue}
BgBrightMagenta Styling = setBackground{BrightMagenta}
BgBrightCyan Styling = setBackground{BrightCyan}
BgBrightWhite Styling = setBackground{BrightWhite}
Bold Styling = boolOn{boldField{}}
Dim Styling = boolOn{dimField{}}
Italic Styling = boolOn{italicField{}}
Underlined Styling = boolOn{underlinedField{}}
Blink Styling = boolOn{blinkField{}}
Inverse Styling = boolOn{inverseField{}}
NoBold Styling = boolOff{boldField{}}
NoDim Styling = boolOff{dimField{}}
NoItalic Styling = boolOff{italicField{}}
NoUnderlined Styling = boolOff{underlinedField{}}
NoBlink Styling = boolOff{blinkField{}}
NoInverse Styling = boolOff{inverseField{}}
ToggleBold Styling = boolToggle{boldField{}}
ToggleDim Styling = boolToggle{dimField{}}
ToggleItalic Styling = boolToggle{italicField{}}
ToggleUnderlined Styling = boolToggle{underlinedField{}}
ToggleBlink Styling = boolToggle{blinkField{}}
ToggleInverse Styling = boolToggle{inverseField{}}
)
// Fg returns a Styling that sets the foreground color.
func Fg(c Color) Styling { return setForeground{c} }
// Bg returns a Styling that sets the background color.
func Bg(c Color) Styling { return setBackground{c} }
type reset struct{}
type setForeground struct{ c Color }
type setBackground struct{ c Color }
type boolOn struct{ f boolField }
type boolOff struct{ f boolField }
type boolToggle struct{ f boolField }
func (reset) transform(s *Style) { *s = Style{} }
func (t setForeground) transform(s *Style) { s.Fg = t.c }
func (t setBackground) transform(s *Style) { s.Bg = t.c }
func (t boolOn) transform(s *Style) { *t.f.get(s) = true }
func (t boolOff) transform(s *Style) { *t.f.get(s) = false }
func (t boolToggle) transform(s *Style) { p := t.f.get(s); *p = !*p }
type boolField interface{ get(*Style) *bool }
type boldField struct{}
type dimField struct{}
type italicField struct{}
type underlinedField struct{}
type blinkField struct{}
type inverseField struct{}
func (boldField) get(s *Style) *bool { return &s.Bold }
func (dimField) get(s *Style) *bool { return &s.Dim }
func (italicField) get(s *Style) *bool { return &s.Italic }
func (underlinedField) get(s *Style) *bool { return &s.Underlined }
func (blinkField) get(s *Style) *bool { return &s.Blink }
func (inverseField) get(s *Style) *bool { return &s.Inverse }
type jointStyling []Styling
func (t jointStyling) transform(s *Style) {
for _, t := range t {
t.transform(s)
}
}
// ParseStyling parses a text representation of Styling, which are kebab
// case counterparts to the names of the builtin Styling's. For example,
// ToggleInverse is expressed as "toggle-inverse".
//
// Multiple stylings can be joined by spaces, which is equivalent to calling
// Stylings.
//
// If the given string is invalid, ParseStyling returns nil.
func ParseStyling(s string) Styling {
if !strings.ContainsRune(s, ' ') {
return parseOneStyling(s)
}
var joint jointStyling
for _, subs := range strings.Split(s, " ") {
parsed := parseOneStyling(subs)
if parsed == nil {
return nil
}
joint = append(joint, parseOneStyling(subs))
}
return joint
}
var boolFields = map[string]boolField{
"bold": boldField{},
"dim": dimField{},
"italic": italicField{},
"underlined": underlinedField{},
"blink": blinkField{},
"inverse": inverseField{},
}
func parseOneStyling(name string) Styling {
switch {
case name == "default" || name == "fg-default":
return FgDefault
case strings.HasPrefix(name, "fg-"):
if color := parseColor(name[len("fg-"):]); color != nil {
return setForeground{color}
}
case name == "bg-default":
return BgDefault
case strings.HasPrefix(name, "bg-"):
if color := parseColor(name[len("bg-"):]); color != nil {
return setBackground{color}
}
case strings.HasPrefix(name, "no-"):
if f, ok := boolFields[name[len("no-"):]]; ok {
return boolOff{f}
}
case strings.HasPrefix(name, "toggle-"):
if f, ok := boolFields[name[len("toggle-"):]]; ok {
return boolToggle{f}
}
default:
if f, ok := boolFields[name]; ok {
return boolOn{f}
}
if color := parseColor(name); color != nil {
return setForeground{color}
}
}
return nil
}
|