Skip to content

Commit 687d00d

Browse files
committed
feat: add underline style and color
This adds support for setting the underline style and color. The underline style can be set to single, double, curly, dotted, or dashed. The color of the underline can be set to any color. See https://sw.kovidgoyal.net/kitty/underlines/
1 parent 0870fbb commit 687d00d

File tree

5 files changed

+67
-7
lines changed

5 files changed

+67
-7
lines changed

get.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func (s Style) GetItalic() bool {
2121
// GetUnderline returns the style's underline value. If no value is set false is
2222
// returned.
2323
func (s Style) GetUnderline() bool {
24-
return s.getAsBool(underlineKey, false)
24+
return s.ul != NoUnderline
2525
}
2626

2727
// GetStrikethrough returns the style's strikethrough value. If no value is set false

set.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ func (s *Style) set(key propKey, value interface{}) {
1313
s.fgColor = colorOrNil(value)
1414
case backgroundKey:
1515
s.bgColor = colorOrNil(value)
16+
case underlineColorKey:
17+
s.ulColor = colorOrNil(value)
18+
case underlineKey:
19+
s.ul = value.(Underline)
1620
case widthKey:
1721
s.width = max(0, value.(int))
1822
case heightKey:
@@ -95,6 +99,10 @@ func (s *Style) setFrom(key propKey, i Style) {
9599
s.set(foregroundKey, i.fgColor)
96100
case backgroundKey:
97101
s.set(backgroundKey, i.bgColor)
102+
case underlineColorKey:
103+
s.set(underlineColorKey, i.ulColor)
104+
case underlineKey:
105+
s.set(underlineKey, i.ul)
98106
case widthKey:
99107
s.set(widthKey, i.width)
100108
case heightKey:
@@ -177,7 +185,23 @@ func (s Style) Italic(v bool) Style {
177185
// whitespace like margins and padding. To change this behavior set
178186
// [Style.UnderlineSpaces].
179187
func (s Style) Underline(v bool) Style {
180-
s.set(underlineKey, v)
188+
if v {
189+
return s.UnderlineStyle(SingleUnderline)
190+
}
191+
return s.UnderlineStyle(NoUnderline)
192+
}
193+
194+
// UnderlineStyle sets the underline style. This can be used to set the underline
195+
// to be a single, double, curly, dotted, or dashed line.
196+
func (s Style) UnderlineStyle(u Underline) Style {
197+
s.set(underlineKey, u)
198+
return s
199+
}
200+
201+
// UnderlineColor sets the color of the underline. By default, the underline
202+
// will be the same color as the foreground.
203+
func (s Style) UnderlineColor(c color.Color) Style {
204+
s.set(underlineColorKey, c)
181205
return s
182206
}
183207

style.go

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ const (
1919
// Boolean props come first.
2020
boldKey propKey = 1 << iota
2121
italicKey
22-
underlineKey
2322
strikethroughKey
2423
reverseKey
2524
blinkKey
@@ -29,8 +28,10 @@ const (
2928
colorWhitespaceKey
3029

3130
// Non-boolean props.
31+
underlineKey
3232
foregroundKey
3333
backgroundKey
34+
underlineColorKey
3435
widthKey
3536
heightKey
3637
alignHorizontalKey
@@ -96,6 +97,24 @@ func (p props) has(k propKey) bool {
9697
return p&props(k) != 0
9798
}
9899

100+
// Underline is the style of the underline.
101+
type Underline uint8
102+
103+
const (
104+
// NoUnderline is no underline.
105+
NoUnderline = Underline(ansi.NoUnderlineStyle)
106+
// SingleUnderline is a single underline. This is the default when underline is enabled.
107+
SingleUnderline = Underline(ansi.SingleUnderlineStyle)
108+
// DoubleUnderline is a double underline.
109+
DoubleUnderline = Underline(ansi.DoubleUnderlineStyle)
110+
// CurlyUnderline is a curly underline.
111+
CurlyUnderline = Underline(ansi.CurlyUnderlineStyle)
112+
// DottedUnderline is a dotted underline.
113+
DottedUnderline = Underline(ansi.DottedUnderlineStyle)
114+
// DashedUnderline is a dashed underline.
115+
DashedUnderline = Underline(ansi.DashedUnderlineStyle)
116+
)
117+
99118
// NewStyle returns a new, empty Style. While it's syntactic sugar for the
100119
// [Style]{} primitive, it's recommended to use this function for creating styles
101120
// in case the underlying implementation changes.
@@ -114,6 +133,9 @@ type Style struct {
114133
// props that have values
115134
fgColor color.Color
116135
bgColor color.Color
136+
ulColor color.Color
137+
138+
ul Underline
117139

118140
width int
119141
height int
@@ -234,15 +256,16 @@ func (s Style) Render(strs ...string) string {
234256

235257
bold = s.getAsBool(boldKey, false)
236258
italic = s.getAsBool(italicKey, false)
237-
underline = s.getAsBool(underlineKey, false)
238259
strikethrough = s.getAsBool(strikethroughKey, false)
239260
reverse = s.getAsBool(reverseKey, false)
240261
blink = s.getAsBool(blinkKey, false)
241262
faint = s.getAsBool(faintKey, false)
242263

243264
fg = s.getAsColor(foregroundKey)
244265
bg = s.getAsColor(backgroundKey)
266+
ul = s.getAsColor(underlineColorKey)
245267

268+
underline = s.ul != NoUnderline
246269
width = s.getAsInt(widthKey)
247270
height = s.getAsInt(heightKey)
248271
horizontalAlign = s.getAsPosition(alignHorizontalKey)
@@ -322,8 +345,18 @@ func (s Style) Render(strs ...string) string {
322345
}
323346
}
324347

348+
if ul != noColor {
349+
te = te.UnderlineColor(ul)
350+
if colorWhitespace {
351+
teWhitespace = teWhitespace.UnderlineColor(ul)
352+
}
353+
if useSpaceStyler {
354+
teSpace = teSpace.UnderlineColor(ul)
355+
}
356+
}
357+
325358
if underline {
326-
te = te.Underline()
359+
te = te.UnderlineStyle(ansi.UnderlineStyle(s.ul))
327360
}
328361
if strikethrough {
329362
te = te.Strikethrough()

style_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ func TestUnderline(t *testing.T) {
3030
NewStyle().UnderlineSpaces(true),
3131
"ab\x1b[4m \x1b[mc",
3232
},
33+
{
34+
NewStyle().UnderlineStyle(CurlyUnderline),
35+
"\x1b[4;4:3ma\x1b[m\x1b[4;4:3mb\x1b[m\x1b[4m \x1b[m\x1b[4;4:3mc\x1b[m",
36+
},
3337
}
3438

3539
for i, tc := range tt {

unset.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ func (s Style) UnsetItalic() Style {
1919

2020
// UnsetUnderline removes the underline style rule, if set.
2121
func (s Style) UnsetUnderline() Style {
22-
s.unset(underlineKey)
23-
return s
22+
return s.Underline(false)
2423
}
2524

2625
// UnsetStrikethrough removes the strikethrough style rule, if set.

0 commit comments

Comments
 (0)