Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion get.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,19 @@ func (s Style) GetItalic() bool {
// GetUnderline returns the style's underline value. If no value is set false is
// returned.
func (s Style) GetUnderline() bool {
return s.getAsBool(underlineKey, false)
return s.ul != UnderlineNone
}

// GetUnderlineStyle returns the style's underline style. If no value is set
// UnderlineNone is returned.
func (s Style) GetUnderlineStyle() Underline {
return s.ul
}

// GetUnderlineColor returns the style's underline color. If no value is set
// NoColor{} is returned.
func (s Style) GetUnderlineColor() color.Color {
return s.getAsColor(underlineColorKey)
}

// GetStrikethrough returns the style's strikethrough value. If no value is set false
Expand Down
34 changes: 33 additions & 1 deletion set.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ func (s *Style) set(key propKey, value any) {
s.fgColor = colorOrNil(value)
case backgroundKey:
s.bgColor = colorOrNil(value)
case underlineColorKey:
s.ulColor = colorOrNil(value)
case underlineKey:
s.ul = value.(Underline)
case widthKey:
s.width = max(0, value.(int))
case heightKey:
Expand Down Expand Up @@ -103,6 +107,10 @@ func (s *Style) setFrom(key propKey, i Style) {
s.set(foregroundKey, i.fgColor)
case backgroundKey:
s.set(backgroundKey, i.bgColor)
case underlineColorKey:
s.set(underlineColorKey, i.ulColor)
case underlineKey:
s.set(underlineKey, i.ul)
case widthKey:
s.set(widthKey, i.width)
case heightKey:
Expand Down Expand Up @@ -193,7 +201,31 @@ func (s Style) Italic(v bool) Style {
// whitespace like margins and padding. To change this behavior set
// [Style.UnderlineSpaces].
func (s Style) Underline(v bool) Style {
s.set(underlineKey, v)
if v {
return s.UnderlineStyle(UnderlineSingle)
}
return s.UnderlineStyle(UnderlineNone)
}

// UnderlineStyle sets the underline style. This can be used to set the underline
// to be a single, double, curly, dotted, or dashed line.
//
// Note that not all terminal emulators support underline styles. If a style is
// not supported, it will typically fall back to a single underline but this is
// not guaranteed. This depends on the terminal emulator being used.
func (s Style) UnderlineStyle(u Underline) Style {
s.set(underlineKey, u)
return s
}

// UnderlineColor sets the color of the underline. By default, the underline
// will be the same color as the foreground.
//
// Note that not all terminal emulators support colored underlines. If color is
// not supported, it might produce unexpected results. This depends on the
// terminal emulator being used.
func (s Style) UnderlineColor(c color.Color) Style {
s.set(underlineColorKey, c)
return s
}

Expand Down
44 changes: 41 additions & 3 deletions style.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ const (
// Boolean props come first.
boldKey propKey = 1 << iota
italicKey
underlineKey
strikethroughKey
reverseKey
blinkKey
Expand All @@ -33,8 +32,10 @@ const (
colorWhitespaceKey

// Non-boolean props.
underlineKey
foregroundKey
backgroundKey
underlineColorKey
widthKey
heightKey
alignHorizontalKey
Expand Down Expand Up @@ -104,6 +105,29 @@ func (p props) has(k propKey) bool {
return p&props(k) != 0
}

// Underline is the style of the underline.
//
// Caveats:
// - Not all terminals support all underline styles.
// - Some terminals may render unsupported styles as standard underlines.
// - Terminal themes may affect the visibility of different underline styles.
type Underline uint8

const (
// UnderlineNone is no underline.
UnderlineNone = Underline(ansi.NoUnderlineStyle)
// UnderlineSingle is a single underline. This is the default when underline is enabled.
UnderlineSingle = Underline(ansi.SingleUnderlineStyle)
// UnderlineDouble is a double underline.
UnderlineDouble = Underline(ansi.DoubleUnderlineStyle)
// UnderlineCurly is a curly underline.
UnderlineCurly = Underline(ansi.CurlyUnderlineStyle)
// UnderlineDotted is a dotted underline.
UnderlineDotted = Underline(ansi.DottedUnderlineStyle)
// UnderlineDashed is a dashed underline.
UnderlineDashed = Underline(ansi.DashedUnderlineStyle)
)

// NewStyle returns a new, empty Style. While it's syntactic sugar for the
// [Style]{} primitive, it's recommended to use this function for creating styles
// in case the underlying implementation changes.
Expand All @@ -122,6 +146,9 @@ type Style struct {
// props that have values
fgColor color.Color
bgColor color.Color
ulColor color.Color

ul Underline

width int
height int
Expand Down Expand Up @@ -246,15 +273,16 @@ func (s Style) Render(strs ...string) string {

bold = s.getAsBool(boldKey, false)
italic = s.getAsBool(italicKey, false)
underline = s.getAsBool(underlineKey, false)
strikethrough = s.getAsBool(strikethroughKey, false)
reverse = s.getAsBool(reverseKey, false)
blink = s.getAsBool(blinkKey, false)
faint = s.getAsBool(faintKey, false)

fg = s.getAsColor(foregroundKey)
bg = s.getAsColor(backgroundKey)
ul = s.getAsColor(underlineColorKey)

underline = s.ul != UnderlineNone
width = s.getAsInt(widthKey)
height = s.getAsInt(heightKey)
horizontalAlign = s.getAsPosition(alignHorizontalKey)
Expand Down Expand Up @@ -334,8 +362,18 @@ func (s Style) Render(strs ...string) string {
}
}

if ul != noColor {
te = te.UnderlineColor(ul)
if colorWhitespace {
teWhitespace = teWhitespace.UnderlineColor(ul)
}
if useSpaceStyler {
teSpace = teSpace.UnderlineColor(ul)
}
}

if underline {
te = te.Underline()
te = te.UnderlineStyle(ansi.UnderlineStyle(s.ul))
}
if strikethrough {
te = te.Strikethrough()
Expand Down
4 changes: 4 additions & 0 deletions style_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ func TestUnderline(t *testing.T) {
NewStyle().UnderlineSpaces(true),
"ab\x1b[4m \x1b[mc",
},
{
NewStyle().UnderlineStyle(UnderlineCurly),
"\x1b[4;4:3ma\x1b[m\x1b[4;4:3mb\x1b[m\x1b[4m \x1b[m\x1b[4;4:3mc\x1b[m",
},
}

for i, tc := range tt {
Expand Down
3 changes: 1 addition & 2 deletions unset.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ func (s Style) UnsetItalic() Style {

// UnsetUnderline removes the underline style rule, if set.
func (s Style) UnsetUnderline() Style {
s.unset(underlineKey)
return s
return s.Underline(false)
}

// UnsetStrikethrough removes the strikethrough style rule, if set.
Expand Down
Loading