1
1
mirror of https://github.com/walles/moar.git synced 2024-10-26 13:00:40 +03:00

Compare commits

...

5 Commits

Author SHA1 Message Date
Johan Walles
f69e58d9b3 Handle reading underline color 2024-08-12 20:57:44 +02:00
Johan Walles
065b95a653 Add a colored underline test case 2024-08-12 20:50:57 +02:00
Johan Walles
d9b3e16e00 Render underline color changes 2024-08-12 20:44:40 +02:00
Johan Walles
ae0ae42241 Support parsing underline color escape sequences 2024-08-12 20:43:10 +02:00
Johan Walles
2c9f16fa2a Add underline color support to Style 2024-08-12 20:39:48 +02:00
4 changed files with 66 additions and 28 deletions

View File

@ -511,6 +511,18 @@ func rawUpdateStyle(style twin.Style, escapeSequenceWithoutHeader string, number
case 49:
style = style.WithBackground(twin.ColorDefault)
case 58:
var err error
var color *twin.Color
index, color, err = consumeCompositeColor(numbersBuffer, index-1)
if err != nil {
return style, numbersBuffer, fmt.Errorf("Underline: %w", err)
}
style = style.WithUnderlineColor(*color)
case 59:
style = style.WithUnderlineColor(twin.ColorDefault)
// Bright foreground colors: see https://pkg.go.dev/github.com/gdamore/Color
//
// After testing vs less and cat on iTerm2 3.3.9 / macOS Catalina
@ -573,9 +585,9 @@ func joinUints(ints []uint) string {
// * A color value that can be applied to a style
func consumeCompositeColor(numbers []uint, index int) (int, *twin.Color, error) {
baseIndex := index
if numbers[index] != 38 && numbers[index] != 48 {
if numbers[index] != 38 && numbers[index] != 48 && numbers[index] != 58 {
err := fmt.Errorf(
"unknown start of color sequence <%d>, expected 38 (foreground) or 48 (background): <CSI %sm>",
"unknown start of color sequence <%d>, expected 38 (foreground), 48 (background) or 58 (underline): <CSI %sm>",
numbers[index],
joinUints(numbers[baseIndex:]))
return -1, nil, err

View File

@ -216,7 +216,7 @@ func TestConsumeCompositeColorBadPrefix(t *testing.T) {
// 8 bit color
// Example from: https://github.com/walles/moar/issues/14
_, color, err := consumeCompositeColor([]uint{29}, 0)
assert.Equal(t, err.Error(), "unknown start of color sequence <29>, expected 38 (foreground) or 48 (background): <CSI 29m>")
assert.Equal(t, err.Error(), "unknown start of color sequence <29>, expected 38 (foreground), 48 (background) or 58 (underline): <CSI 29m>")
assert.Assert(t, color == nil)
}

View File

@ -0,0 +1 @@
[58:5:196mRed underline Default colored underline

View File

@ -19,9 +19,10 @@ const (
)
type Style struct {
fg Color
bg Color
attrs AttrMask
fg Color
bg Color
underlineColor Color
attrs AttrMask
// This hyperlinkURL is a URL for in-terminal hyperlinks.
//
@ -37,8 +38,13 @@ type Style struct {
var StyleDefault Style
func (style Style) String() string {
undelineSuffix := ""
if style.underlineColor != ColorDefault {
undelineSuffix = fmt.Sprintf(" underlined with %v", style.underlineColor)
}
if style.attrs == AttrNone {
return fmt.Sprint(style.fg, " on ", style.bg)
return fmt.Sprint(style.fg, " on ", style.bg, undelineSuffix)
}
attrNames := make([]string, 0)
@ -67,15 +73,16 @@ func (style Style) String() string {
attrNames = append(attrNames, "\""+*style.hyperlinkURL+"\"")
}
return fmt.Sprint(strings.Join(attrNames, " "), " ", style.fg, " on ", style.bg)
return fmt.Sprint(strings.Join(attrNames, " "), " ", style.fg, " on ", style.bg, undelineSuffix)
}
func (style Style) WithAttr(attr AttrMask) Style {
result := Style{
fg: style.fg,
bg: style.bg,
attrs: style.attrs | attr,
hyperlinkURL: style.hyperlinkURL,
fg: style.fg,
bg: style.bg,
underlineColor: style.underlineColor,
attrs: style.attrs | attr,
hyperlinkURL: style.hyperlinkURL,
}
// Bold and dim are mutually exclusive
@ -97,19 +104,21 @@ func (style Style) WithHyperlink(hyperlinkURL *string) Style {
}
return Style{
fg: style.fg,
bg: style.bg,
attrs: style.attrs,
hyperlinkURL: hyperlinkURL,
fg: style.fg,
bg: style.bg,
underlineColor: style.underlineColor,
attrs: style.attrs,
hyperlinkURL: hyperlinkURL,
}
}
func (style Style) WithoutAttr(attr AttrMask) Style {
return Style{
fg: style.fg,
bg: style.bg,
attrs: style.attrs & ^attr,
hyperlinkURL: style.hyperlinkURL,
fg: style.fg,
bg: style.bg,
underlineColor: style.underlineColor,
attrs: style.attrs & ^attr,
hyperlinkURL: style.hyperlinkURL,
}
}
@ -119,19 +128,31 @@ func (attr AttrMask) has(attrs AttrMask) bool {
func (style Style) WithBackground(color Color) Style {
return Style{
fg: style.fg,
bg: color,
attrs: style.attrs,
hyperlinkURL: style.hyperlinkURL,
fg: style.fg,
bg: color,
underlineColor: style.underlineColor,
attrs: style.attrs,
hyperlinkURL: style.hyperlinkURL,
}
}
func (style Style) WithForeground(color Color) Style {
return Style{
fg: color,
bg: style.bg,
attrs: style.attrs,
hyperlinkURL: style.hyperlinkURL,
fg: color,
bg: style.bg,
underlineColor: style.underlineColor,
attrs: style.attrs,
hyperlinkURL: style.hyperlinkURL,
}
}
func (style Style) WithUnderlineColor(color Color) Style {
return Style{
fg: style.fg,
bg: style.bg,
underlineColor: color,
attrs: style.attrs,
hyperlinkURL: style.hyperlinkURL,
}
}
@ -159,6 +180,10 @@ func (current Style) RenderUpdateFrom(previous Style, terminalColorCount ColorCo
builder.WriteString(current.bg.ansiString(colorTypeBackground, terminalColorCount))
}
if current.underlineColor != previous.underlineColor {
builder.WriteString(current.underlineColor.ansiString(colorTypeUnderline, terminalColorCount))
}
// Handle AttrDim / AttrBold changes
previousBoldDim := previous.attrs & (AttrBold | AttrDim)
currentBoldDim := current.attrs & (AttrBold | AttrDim)