mirror of
https://github.com/walles/moar.git
synced 2024-11-11 00:27:04 +03:00
Merge branch 'walles/moar-colors'
Adds eight and twenty four bits color support.
This commit is contained in:
commit
1ed285d13b
@ -1,8 +1,10 @@
|
||||
package m
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
@ -171,9 +173,13 @@ func _StyledStringsFromString(logger *log.Logger, s string) []_StyledString {
|
||||
|
||||
// _UpdateStyle parses a string of the form "ESC[33m" into changes to style
|
||||
func _UpdateStyle(logger *log.Logger, style tcell.Style, escapeSequence string) tcell.Style {
|
||||
for _, number := range strings.Split(escapeSequence[2:len(escapeSequence)-1], ";") {
|
||||
switch number {
|
||||
case "", "0", "00":
|
||||
numbers := strings.Split(escapeSequence[2:len(escapeSequence)-1], ";")
|
||||
index := 0
|
||||
for index < len(numbers) {
|
||||
number := numbers[index]
|
||||
index++
|
||||
switch strings.TrimLeft(number, "0") {
|
||||
case "":
|
||||
style = tcell.StyleDefault
|
||||
|
||||
case "1":
|
||||
@ -205,6 +211,15 @@ func _UpdateStyle(logger *log.Logger, style tcell.Style, escapeSequence string)
|
||||
style = style.Foreground(6)
|
||||
case "37":
|
||||
style = style.Foreground(7)
|
||||
case "38":
|
||||
var err error = nil
|
||||
var color *tcell.Color
|
||||
index, color, err = consumeCompositeColor(numbers, index-1)
|
||||
if err != nil {
|
||||
logger.Printf("Foreground: %s", err.Error())
|
||||
return style
|
||||
}
|
||||
style = style.Foreground(*color)
|
||||
case "39":
|
||||
style = style.Foreground(tcell.ColorDefault)
|
||||
|
||||
@ -225,6 +240,15 @@ func _UpdateStyle(logger *log.Logger, style tcell.Style, escapeSequence string)
|
||||
style = style.Background(6)
|
||||
case "47":
|
||||
style = style.Background(7)
|
||||
case "48":
|
||||
var err error = nil
|
||||
var color *tcell.Color
|
||||
index, color, err = consumeCompositeColor(numbers, index-1)
|
||||
if err != nil {
|
||||
logger.Printf("Background: %s", err.Error())
|
||||
return style
|
||||
}
|
||||
style = style.Background(*color)
|
||||
case "49":
|
||||
style = style.Background(tcell.ColorDefault)
|
||||
|
||||
@ -235,3 +259,87 @@ func _UpdateStyle(logger *log.Logger, style tcell.Style, escapeSequence string)
|
||||
|
||||
return style
|
||||
}
|
||||
|
||||
// numbers is a list of numbers from a ANSI SGR string
|
||||
// index points to either 38 or 48 in that string
|
||||
//
|
||||
// This method will return:
|
||||
// * The first index in the string that this function did not consume
|
||||
// * A color value that can be applied to a style
|
||||
func consumeCompositeColor(numbers []string, index int) (int, *tcell.Color, error) {
|
||||
baseIndex := index
|
||||
if numbers[index] != "38" && numbers[index] != "48" {
|
||||
err := fmt.Errorf(
|
||||
"Unknown start of color sequence <%s>, expected 38 (foreground) or 48 (background): <CSI %sm>",
|
||||
numbers[index],
|
||||
strings.Join(numbers[baseIndex:], ";"))
|
||||
return -1, nil, err
|
||||
}
|
||||
|
||||
index++
|
||||
if index >= len(numbers) {
|
||||
err := fmt.Errorf(
|
||||
"Incomplete color sequence: <CSI %sm>",
|
||||
strings.Join(numbers[baseIndex:], ";"))
|
||||
return -1, nil, err
|
||||
}
|
||||
|
||||
if numbers[index] == "5" {
|
||||
// Handle 8 bit color
|
||||
index++
|
||||
if index >= len(numbers) {
|
||||
err := fmt.Errorf(
|
||||
"Incomplete 8 bit color sequence: <CSI %sm>",
|
||||
strings.Join(numbers[baseIndex:], ";"))
|
||||
return -1, nil, err
|
||||
}
|
||||
|
||||
colorNumber, err := strconv.Atoi(numbers[index])
|
||||
if err != nil {
|
||||
return -1, nil, err
|
||||
}
|
||||
|
||||
colorValue := tcell.Color(colorNumber)
|
||||
return index + 1, &colorValue, nil
|
||||
}
|
||||
|
||||
if numbers[index] == "2" {
|
||||
// Handle 24 bit color
|
||||
rIndex := index + 1
|
||||
gIndex := index + 2
|
||||
bIndex := index + 3
|
||||
if bIndex >= len(numbers) {
|
||||
err := fmt.Errorf(
|
||||
"Incomplete 24 bit color sequence, expected N8;2;R;G;Bm: <CSI %sm>",
|
||||
strings.Join(numbers[baseIndex:], ";"))
|
||||
return -1, nil, err
|
||||
}
|
||||
|
||||
rValueX, err := strconv.ParseInt(numbers[rIndex], 10, 32)
|
||||
if err != nil {
|
||||
return -1, nil, err
|
||||
}
|
||||
rValue := int32(rValueX)
|
||||
|
||||
gValueX, err := strconv.Atoi(numbers[gIndex])
|
||||
if err != nil {
|
||||
return -1, nil, err
|
||||
}
|
||||
gValue := int32(gValueX)
|
||||
|
||||
bValueX, err := strconv.Atoi(numbers[bIndex])
|
||||
if err != nil {
|
||||
return -1, nil, err
|
||||
}
|
||||
bValue := int32(bValueX)
|
||||
|
||||
colorValue := tcell.NewRGBColor(rValue, gValue, bValue)
|
||||
return bIndex + 1, &colorValue, nil
|
||||
}
|
||||
|
||||
err := fmt.Errorf(
|
||||
"Unknown color type <%s>, expected 5 (8 bit color) or 2 (24 bit color): <CSI %sm>",
|
||||
numbers[index],
|
||||
strings.Join(numbers[baseIndex:], ";"))
|
||||
return -1, nil, err
|
||||
}
|
||||
|
@ -7,6 +7,10 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
|
||||
"gotest.tools/assert"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
// Verify that we can tokenize all lines in ../sample-files/*
|
||||
@ -44,3 +48,91 @@ func TestTokenize(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsumeCompositeColorHappy(t *testing.T) {
|
||||
// 8 bit color
|
||||
// Example from: https://github.com/walles/moar/issues/14
|
||||
newIndex, color, err := consumeCompositeColor([]string{"38", "5", "74"}, 0)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, newIndex, 3)
|
||||
assert.Equal(t, *color, tcell.Color74)
|
||||
|
||||
// 24 bit color
|
||||
newIndex, color, err = consumeCompositeColor([]string{"38", "2", "10", "20", "30"}, 0)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, newIndex, 5)
|
||||
assert.Equal(t, *color, tcell.NewRGBColor(10, 20, 30))
|
||||
}
|
||||
|
||||
func TestConsumeCompositeColorHappyMidSequence(t *testing.T) {
|
||||
// 8 bit color
|
||||
// Example from: https://github.com/walles/moar/issues/14
|
||||
newIndex, color, err := consumeCompositeColor([]string{"whatever", "38", "5", "74"}, 1)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, newIndex, 4)
|
||||
assert.Equal(t, *color, tcell.Color74)
|
||||
|
||||
// 24 bit color
|
||||
newIndex, color, err = consumeCompositeColor([]string{"whatever", "38", "2", "10", "20", "30"}, 1)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, newIndex, 6)
|
||||
assert.Equal(t, *color, tcell.NewRGBColor(10, 20, 30))
|
||||
}
|
||||
|
||||
func TestConsumeCompositeColorBadPrefix(t *testing.T) {
|
||||
// 8 bit color
|
||||
// Example from: https://github.com/walles/moar/issues/14
|
||||
_, color, err := consumeCompositeColor([]string{"29"}, 0)
|
||||
assert.Equal(t, err.Error(), "Unknown start of color sequence <29>, expected 38 (foreground) or 48 (background): <CSI 29m>")
|
||||
assert.Assert(t, color == nil)
|
||||
|
||||
// Same test but mid-sequence, with initial index > 0
|
||||
_, color, err = consumeCompositeColor([]string{"whatever", "29"}, 1)
|
||||
assert.Equal(t, err.Error(), "Unknown start of color sequence <29>, expected 38 (foreground) or 48 (background): <CSI 29m>")
|
||||
assert.Assert(t, color == nil)
|
||||
}
|
||||
|
||||
func TestConsumeCompositeColorBadType(t *testing.T) {
|
||||
_, color, err := consumeCompositeColor([]string{"38", "4"}, 0)
|
||||
// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
||||
assert.Equal(t, err.Error(), "Unknown color type <4>, expected 5 (8 bit color) or 2 (24 bit color): <CSI 38;4m>")
|
||||
assert.Assert(t, color == nil)
|
||||
|
||||
// Same test but mid-sequence, with initial index > 0
|
||||
_, color, err = consumeCompositeColor([]string{"whatever", "38", "4"}, 1)
|
||||
assert.Equal(t, err.Error(), "Unknown color type <4>, expected 5 (8 bit color) or 2 (24 bit color): <CSI 38;4m>")
|
||||
assert.Assert(t, color == nil)
|
||||
}
|
||||
|
||||
func TestConsumeCompositeColorIncomplete(t *testing.T) {
|
||||
_, color, err := consumeCompositeColor([]string{"38"}, 0)
|
||||
assert.Equal(t, err.Error(), "Incomplete color sequence: <CSI 38m>")
|
||||
assert.Assert(t, color == nil)
|
||||
|
||||
// Same test, mid-sequence
|
||||
_, color, err = consumeCompositeColor([]string{"whatever", "38"}, 1)
|
||||
assert.Equal(t, err.Error(), "Incomplete color sequence: <CSI 38m>")
|
||||
assert.Assert(t, color == nil)
|
||||
}
|
||||
|
||||
func TestConsumeCompositeColorIncomplete8Bit(t *testing.T) {
|
||||
_, color, err := consumeCompositeColor([]string{"38", "5"}, 0)
|
||||
assert.Equal(t, err.Error(), "Incomplete 8 bit color sequence: <CSI 38;5m>")
|
||||
assert.Assert(t, color == nil)
|
||||
|
||||
// Same test, mid-sequence
|
||||
_, color, err = consumeCompositeColor([]string{"whatever", "38", "5"}, 1)
|
||||
assert.Equal(t, err.Error(), "Incomplete 8 bit color sequence: <CSI 38;5m>")
|
||||
assert.Assert(t, color == nil)
|
||||
}
|
||||
|
||||
func TestConsumeCompositeColorIncomplete24Bit(t *testing.T) {
|
||||
_, color, err := consumeCompositeColor([]string{"38", "2", "10", "20"}, 0)
|
||||
assert.Equal(t, err.Error(), "Incomplete 24 bit color sequence, expected N8;2;R;G;Bm: <CSI 38;2;10;20m>")
|
||||
assert.Assert(t, color == nil)
|
||||
|
||||
// Same test, mid-sequence
|
||||
_, color, err = consumeCompositeColor([]string{"whatever", "38", "2", "10", "20"}, 1)
|
||||
assert.Equal(t, err.Error(), "Incomplete 24 bit color sequence, expected N8;2;R;G;Bm: <CSI 38;2;10;20m>")
|
||||
assert.Assert(t, color == nil)
|
||||
}
|
||||
|
7
sample-files/8-bit-color.txt
Normal file
7
sample-files/8-bit-color.txt
Normal file
@ -0,0 +1,7 @@
|
||||
[01;31mLESS_TERMCAP_mb
|
||||
[01;38;5;74mLESS_TERMCAP_md
|
||||
[0mLESS_TERMCAP_me
|
||||
[0mLESS_TERMCAP_se
|
||||
[38;5;204mLESS_TERMCAP_so
|
||||
[0mLESS_TERMCAP_ue
|
||||
[01;32mLESS_TERMCAP_us
|
Loading…
Reference in New Issue
Block a user