File: styling.go

package info (click to toggle)
elvish 0.21.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 6,372 kB
  • sloc: javascript: 236; sh: 130; python: 104; makefile: 88; xml: 9
file content (213 lines) | stat: -rw-r--r-- 6,775 bytes parent folder | download | duplicates (2)
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
}