/
styling.go
209 lines (182 loc) · 6.53 KB
/
styling.go
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
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 (
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 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 (t setForeground) transform(s *Style) { s.Foreground = t.c }
func (t setBackground) transform(s *Style) { s.Background = 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
}