Added EscapeCodeRenderer
- Added `EscapeCode`-type - Implemented EscapeCode.String() - Implemented EscapeCode.Raw() - Implemented EscapeCode.MakeSafe() - Implemented EscapeCode.IsValid() - Added `EscapeCodeRenderer` - Implemented EscapeCodeRenderer.RenderSequence() - Implemented EscapeCodeRenderer.Render() - Implemented `EscapeCodeRenderer.NormalizedText`. - Added EscapeCodeRendererFactory - Implemented EscapeCodeRendererFactory.TextRenderer() - Added escape code examples to examples/coloredList.go
This commit is contained in:
parent
ac747cb49f
commit
a3f1384a3b
@ -5,7 +5,7 @@ package main
|
|||||||
import "github.com/gizak/termui"
|
import "github.com/gizak/termui"
|
||||||
import "github.com/nsf/termbox-go"
|
import "github.com/nsf/termbox-go"
|
||||||
|
|
||||||
func commonList() *termui.List {
|
func markdownList() *termui.List {
|
||||||
strs := []string{
|
strs := []string{
|
||||||
"[0] github.com/gizak/termui",
|
"[0] github.com/gizak/termui",
|
||||||
"[1] 笀耔 [澉 灊灅甗](RED) 郔镺 笀耔 澉 [灊灅甗](yellow) 郔镺",
|
"[1] 笀耔 [澉 灊灅甗](RED) 郔镺 笀耔 澉 [灊灅甗](yellow) 郔镺",
|
||||||
@ -26,18 +26,40 @@ func commonList() *termui.List {
|
|||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
func listHidden() *termui.List {
|
func hideList(list *termui.List) *termui.List {
|
||||||
list := commonList()
|
|
||||||
list.Border.Label = "List - Hidden"
|
list.Border.Label = "List - Hidden"
|
||||||
list.Overflow = "hidden"
|
list.Overflow = "hidden"
|
||||||
|
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
func listWrap() *termui.List {
|
func wrapList(list *termui.List) *termui.List {
|
||||||
list := commonList()
|
|
||||||
list.Border.Label = "List - Wrapped"
|
list.Border.Label = "List - Wrapped"
|
||||||
list.Overflow = "wrap"
|
list.Overflow = "wrap"
|
||||||
|
list.X = 30
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func escapeList() *termui.List {
|
||||||
|
strs := []string{
|
||||||
|
"[0] github.com/gizak/termui",
|
||||||
|
"[1] 笀耔 \033[31m澉 灊灅甗 \033[0m郔镺 笀耔 澉 \033[33m灊灅甗 郔镺",
|
||||||
|
"[2] こんにちは世界",
|
||||||
|
"[3] keyboard.go",
|
||||||
|
"[4] \033[31moutput\033[0m.go",
|
||||||
|
"[5] random_out.go",
|
||||||
|
"[6] \033[1mdashboard\033[0m.go",
|
||||||
|
"[7] nsf/termbox-go",
|
||||||
|
"[8] OVERFLOW!!!!!!!\033[31;1m!!!!!!!!!!!!\033[0m!!!",
|
||||||
|
}
|
||||||
|
|
||||||
|
list := termui.NewList()
|
||||||
|
list.RendererFactory = termui.EscapeCodeRendererFactory{}
|
||||||
|
list.Items = strs
|
||||||
|
list.Height = 15
|
||||||
|
list.Width = 26
|
||||||
|
list.Y = 15
|
||||||
|
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
@ -49,11 +71,20 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer termui.Close()
|
defer termui.Close()
|
||||||
|
|
||||||
hiddenList := listHidden()
|
hiddenMarkdownList := hideList(markdownList())
|
||||||
wrappedList := listWrap()
|
wrappedMarkdownList := wrapList(markdownList())
|
||||||
wrappedList.X = 30
|
|
||||||
|
hiddenEscapeList := hideList(escapeList())
|
||||||
|
wrappedEscapeList := wrapList(escapeList())
|
||||||
|
|
||||||
|
lists := []termui.Bufferer{
|
||||||
|
hiddenEscapeList,
|
||||||
|
hiddenMarkdownList,
|
||||||
|
wrappedMarkdownList,
|
||||||
|
wrappedEscapeList,
|
||||||
|
}
|
||||||
|
|
||||||
termui.UseTheme("helloworld")
|
termui.UseTheme("helloworld")
|
||||||
termui.Render(hiddenList, wrappedList)
|
termui.Render(lists...)
|
||||||
termbox.PollEvent()
|
termbox.PollEvent()
|
||||||
}
|
}
|
||||||
|
209
textRender.go
209
textRender.go
@ -1,7 +1,9 @@
|
|||||||
package termui
|
package termui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -243,3 +245,210 @@ type PlainRendererFactory struct{}
|
|||||||
func (f PlainRendererFactory) TextRenderer(text string) TextRenderer {
|
func (f PlainRendererFactory) TextRenderer(text string) TextRenderer {
|
||||||
return PlainRenderer{text}
|
return PlainRenderer{text}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We can't use a raw string here because \033 must not be escaped.
|
||||||
|
// I'd like to append (?<=m; i.e. lookbehind), but unfortunately,
|
||||||
|
// it is not supported. So we will need to do that manually.
|
||||||
|
var escapeRegex = "\033\\[(([0-9]{1,2}[;m])+)"
|
||||||
|
var colorEscapeCodeRegex = regexp.MustCompile(escapeRegex)
|
||||||
|
var colorEscapeCodeRegexMatchAll = regexp.MustCompile("^" + escapeRegex + "$")
|
||||||
|
|
||||||
|
// An EscapeCode is a unix ASCII Escape code.
|
||||||
|
type EscapeCode string
|
||||||
|
|
||||||
|
func (e EscapeCode) escapeNumberToColor(colorID int) (Attribute, error) {
|
||||||
|
var color Attribute
|
||||||
|
switch colorID {
|
||||||
|
case 0:
|
||||||
|
color = ColorDefault
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
color = AttrBold
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
color = AttrUnderline
|
||||||
|
|
||||||
|
case 30:
|
||||||
|
color = ColorBlack
|
||||||
|
|
||||||
|
case 31:
|
||||||
|
color = ColorRed
|
||||||
|
|
||||||
|
case 32:
|
||||||
|
color = ColorGreen
|
||||||
|
|
||||||
|
case 33:
|
||||||
|
color = ColorYellow
|
||||||
|
|
||||||
|
case 34:
|
||||||
|
color = ColorBlue
|
||||||
|
|
||||||
|
case 35:
|
||||||
|
color = ColorMagenta
|
||||||
|
|
||||||
|
case 36:
|
||||||
|
color = ColorCyan
|
||||||
|
|
||||||
|
case 37:
|
||||||
|
color = ColorWhite
|
||||||
|
|
||||||
|
default:
|
||||||
|
safeCode := e.MakeSafe()
|
||||||
|
return 0, fmt.Errorf("Unkown/unsupported escape code: '%v'", safeCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return color, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color converts the escape code to an `Attribute` (color).
|
||||||
|
// The EscapeCode must be formatted like this:
|
||||||
|
// - ASCII-Escape chacter (\033) + [ + Number + (;Number...) + m
|
||||||
|
// The second number is optimal. The semicolon (;) is used
|
||||||
|
// to seperate the colors.
|
||||||
|
// For example: `\033[1;31m` means: the following text is red and bold.
|
||||||
|
func (e EscapeCode) Color() (Attribute, error) {
|
||||||
|
escapeCode := string(e)
|
||||||
|
matches := colorEscapeCodeRegexMatchAll.FindStringSubmatch(escapeCode)
|
||||||
|
invalidEscapeCode := func() error {
|
||||||
|
safeCode := e.MakeSafe()
|
||||||
|
return fmt.Errorf("%v is not a valid ASCII escape code", safeCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches == nil || escapeCode[len(escapeCode)-1] != 'm' {
|
||||||
|
return 0, invalidEscapeCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
color := Attribute(0)
|
||||||
|
for _, id := range strings.Split(matches[1][:len(matches[1])-1], ";") {
|
||||||
|
colorID, err := strconv.Atoi(id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, invalidEscapeCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
newColor, err := e.escapeNumberToColor(colorID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
color |= newColor
|
||||||
|
}
|
||||||
|
|
||||||
|
return color, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeSafe replace the invisible escape code chacacter (\0333)
|
||||||
|
// with \\0333 so that it will not mess up the terminal when an error
|
||||||
|
// is shown.
|
||||||
|
func (e EscapeCode) MakeSafe() string {
|
||||||
|
return strings.Replace(string(e), "\033", "\\033", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alias to `EscapeCode.MakeSafe()`
|
||||||
|
func (e EscapeCode) String() string {
|
||||||
|
return e.MakeSafe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raw returns the raw value of the escape code.
|
||||||
|
// Alias to string(EscapeCode)
|
||||||
|
func (e EscapeCode) Raw() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid returns whether or not the syntax of the escape code is
|
||||||
|
// valid and the code is supported.
|
||||||
|
func (e EscapeCode) IsValid() bool {
|
||||||
|
_, err := e.Color()
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A EscapeCodeRenderer does not render the text at all.
|
||||||
|
type EscapeCodeRenderer struct {
|
||||||
|
Text string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NormalizedText strips all escape code outs (even the unkown/unsupported)
|
||||||
|
// ones.
|
||||||
|
func (r EscapeCodeRenderer) NormalizedText() string {
|
||||||
|
matches := colorEscapeCodeRegex.FindAllStringIndex(r.Text, -1)
|
||||||
|
text := []byte(r.Text)
|
||||||
|
|
||||||
|
// Iterate through matches in reverse order
|
||||||
|
for i := len(matches) - 1; i >= 0; i-- {
|
||||||
|
start, end := matches[i][0], matches[i][1]
|
||||||
|
if EscapeCode(text[start:end]).IsValid() {
|
||||||
|
text = append(text[:start], text[end:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderSequence renders the text just like Render but the start and end may
|
||||||
|
// be set. If end is -1, the end of the string will be used.
|
||||||
|
func (r EscapeCodeRenderer) RenderSequence(start, end int, lastColor, background Attribute) RenderedSequence {
|
||||||
|
normalizedRunes := []rune(r.NormalizedText())
|
||||||
|
if end < 0 {
|
||||||
|
end = len(normalizedRunes)
|
||||||
|
}
|
||||||
|
|
||||||
|
text := []byte(r.Text)
|
||||||
|
matches := colorEscapeCodeRegex.FindAllSubmatchIndex(text, -1)
|
||||||
|
removed := 0
|
||||||
|
var sequences []ColorSubsequence
|
||||||
|
runeLength := func(length int) int {
|
||||||
|
return len([]rune(string(text[:length])))
|
||||||
|
}
|
||||||
|
|
||||||
|
runes := []rune(r.Text)
|
||||||
|
for _, theMatch := range matches {
|
||||||
|
// Escapde code start, escape code end
|
||||||
|
eStart := runeLength(theMatch[0]) - removed
|
||||||
|
eEnd := runeLength(theMatch[1]) - removed
|
||||||
|
escapeCode := EscapeCode(runes[eStart:eEnd])
|
||||||
|
|
||||||
|
// If an error occurs (e.g. unkown escape code), we will just ignore it :)
|
||||||
|
color, err := escapeCode.Color()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch old color sequence
|
||||||
|
if len(sequences) > 0 {
|
||||||
|
last := &sequences[len(sequences)-1]
|
||||||
|
last.End = eStart - start
|
||||||
|
}
|
||||||
|
|
||||||
|
// eEnd < 0 means the the sequence is withing the range.
|
||||||
|
if eEnd-start >= 0 {
|
||||||
|
// The sequence starts when the escape code ends and ends when the text
|
||||||
|
// end. If there is another escape code, this will be patched in the
|
||||||
|
// previous line.
|
||||||
|
colorSeq := ColorSubsequence{color, eStart - start, end - start}
|
||||||
|
if colorSeq.Start < 0 {
|
||||||
|
colorSeq.Start = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
sequences = append(sequences, colorSeq)
|
||||||
|
}
|
||||||
|
|
||||||
|
runes = append(runes[:eStart], runes[eEnd:]...)
|
||||||
|
removed += eEnd - eStart
|
||||||
|
}
|
||||||
|
|
||||||
|
runes = runes[start:end]
|
||||||
|
return RenderedSequence{string(runes), lastColor, background, sequences, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render just like RenderSequence
|
||||||
|
func (r EscapeCodeRenderer) Render(lastColor, background Attribute) RenderedSequence {
|
||||||
|
return r.RenderSequence(0, -1, lastColor, background)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EscapeCodeRendererFactory is a TextRendererFactory for
|
||||||
|
// the EscapeCodeRenderer.
|
||||||
|
type EscapeCodeRendererFactory struct{}
|
||||||
|
|
||||||
|
// TextRenderer returns a EscapeCodeRenderer instance.
|
||||||
|
func (f EscapeCodeRendererFactory) TextRenderer(text string) TextRenderer {
|
||||||
|
return EscapeCodeRenderer{text}
|
||||||
|
}
|
||||||
|
@ -13,6 +13,7 @@ func TestTextRender_TestInterface(t *testing.T) {
|
|||||||
var inter *TextRenderer
|
var inter *TextRenderer
|
||||||
|
|
||||||
assert.Implements(t, inter, new(MarkdownTextRenderer))
|
assert.Implements(t, inter, new(MarkdownTextRenderer))
|
||||||
|
assert.Implements(t, inter, new(EscapeCodeRenderer))
|
||||||
assert.Implements(t, inter, new(PlainRenderer))
|
assert.Implements(t, inter, new(PlainRenderer))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ func TestTextRendererFactory_TestInterface(t *testing.T) {
|
|||||||
var inter *TextRendererFactory
|
var inter *TextRendererFactory
|
||||||
|
|
||||||
assert.Implements(t, inter, new(MarkdownTextRendererFactory))
|
assert.Implements(t, inter, new(MarkdownTextRendererFactory))
|
||||||
|
assert.Implements(t, inter, new(EscapeCodeRendererFactory))
|
||||||
assert.Implements(t, inter, new(PlainRendererFactory))
|
assert.Implements(t, inter, new(PlainRendererFactory))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,3 +272,183 @@ func TestPosUnicode(t *testing.T) {
|
|||||||
require.Equal(t, "你好", text[:6])
|
require.Equal(t, "你好", text[:6])
|
||||||
assert.Equal(t, 2, posUnicode(text, 6))
|
assert.Equal(t, 2, posUnicode(text, 6))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make `escapeCode` safe (i.e. replace \033 by \\033) so that it is not
|
||||||
|
// formatted.
|
||||||
|
// func makeEscapeCodeSafe(escapeCode string) string {
|
||||||
|
// return strings.Replace(escapeCode, "\033", "\\033", -1)
|
||||||
|
// }
|
||||||
|
|
||||||
|
func TestEscapeCode_Color(t *testing.T) {
|
||||||
|
codes := map[EscapeCode]Attribute{
|
||||||
|
"\033[30m": ColorBlack,
|
||||||
|
"\033[31m": ColorRed,
|
||||||
|
"\033[32m": ColorGreen,
|
||||||
|
"\033[33m": ColorYellow,
|
||||||
|
"\033[34m": ColorBlue,
|
||||||
|
"\033[35m": ColorMagenta,
|
||||||
|
"\033[36m": ColorCyan,
|
||||||
|
"\033[37m": ColorWhite,
|
||||||
|
"\033[1;31m": ColorRed | AttrBold,
|
||||||
|
"\033[1;4;31m": ColorRed | AttrBold | AttrUnderline,
|
||||||
|
"\033[0m": ColorDefault,
|
||||||
|
}
|
||||||
|
|
||||||
|
for code, color := range codes {
|
||||||
|
got, err := code.Color()
|
||||||
|
msg := fmt.Sprintf("Escape code: '%v'", code.MakeSafe())
|
||||||
|
if assert.NoError(t, err, msg) {
|
||||||
|
assert.Equal(t, color, got, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidEscapeCodes := []EscapeCode{
|
||||||
|
"\03354m",
|
||||||
|
"[54m",
|
||||||
|
"\033[34",
|
||||||
|
"\033[34;m",
|
||||||
|
"\033[34m;",
|
||||||
|
"\033[34;",
|
||||||
|
"\033[5432m",
|
||||||
|
"t\033[30m",
|
||||||
|
"t\033[30ms",
|
||||||
|
"\033[30ms",
|
||||||
|
}
|
||||||
|
|
||||||
|
errMsg := "%v is not a valid ASCII escape code"
|
||||||
|
for _, invalidEscapeCode := range invalidEscapeCodes {
|
||||||
|
color, err := invalidEscapeCode.Color()
|
||||||
|
safeEscapeCode := invalidEscapeCode.MakeSafe()
|
||||||
|
expectedErr := fmt.Sprintf(errMsg, safeEscapeCode)
|
||||||
|
if assert.EqualError(t, err, expectedErr, "Expected: "+expectedErr) {
|
||||||
|
assert.Equal(t, color, Attribute(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outOfRangeCodes := []EscapeCode{
|
||||||
|
"\033[2m",
|
||||||
|
"\033[3m",
|
||||||
|
"\033[3m",
|
||||||
|
"\033[5m",
|
||||||
|
"\033[6m",
|
||||||
|
"\033[7m",
|
||||||
|
"\033[8m",
|
||||||
|
"\033[38m",
|
||||||
|
"\033[39m",
|
||||||
|
"\033[40m",
|
||||||
|
"\033[41m",
|
||||||
|
"\033[43m",
|
||||||
|
"\033[45m",
|
||||||
|
"\033[46m",
|
||||||
|
"\033[48m",
|
||||||
|
"\033[49m",
|
||||||
|
"\033[50m",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, code := range outOfRangeCodes {
|
||||||
|
color, err := code.Color()
|
||||||
|
safeCode := code.MakeSafe()
|
||||||
|
errMsg := fmt.Sprintf("Unkown/unsupported escape code: '%v'", safeCode)
|
||||||
|
if assert.EqualError(t, err, errMsg) {
|
||||||
|
assert.Equal(t, color, Attribute(0), "Escape Code: "+safeCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case: check for out of slice panic on empty string
|
||||||
|
_, err := EscapeCode("").Color()
|
||||||
|
assert.EqualError(t, err, " is not a valid ASCII escape code")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEscapeCode_String(t *testing.T) {
|
||||||
|
e := EscapeCode("\033[32m")
|
||||||
|
assert.Equal(t, "\\033[32m", e.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEscapeCode_Raw(t *testing.T) {
|
||||||
|
e := EscapeCode("\033[32m")
|
||||||
|
assert.Equal(t, "\033[32m", e.Raw())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEscapeCodeRenderer_NormalizedText(t *testing.T) {
|
||||||
|
renderer := EscapeCodeRenderer{"\033[33mtest \033[35mfoo \033[33;1mbar"}
|
||||||
|
assert.Equal(t, "test foo bar", renderer.NormalizedText())
|
||||||
|
|
||||||
|
renderer = EscapeCodeRenderer{"hello \033[38mtest"}
|
||||||
|
assert.Equal(t, "hello \033[38mtest", renderer.NormalizedText())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEscapeCodeRenderer_RenderSequence(t *testing.T) {
|
||||||
|
black, white := ColorWhite, ColorBlack
|
||||||
|
renderer := EscapeCodeRenderer{"test \033[33mfoo \033[31mbar"}
|
||||||
|
sequence := renderer.RenderSequence(0, -1, black, white)
|
||||||
|
if assertRenderSequence(t, sequence, black, white, "test foo bar", 2) {
|
||||||
|
assertColorSubsequence(t, sequence.Sequences[0], "YELLOW", 5, 9)
|
||||||
|
assertColorSubsequence(t, sequence.Sequences[1], "RED", 9, 12)
|
||||||
|
getPoint := func(n int) Point {
|
||||||
|
point, width := sequence.PointAt(n, 10+n, 30)
|
||||||
|
assert.Equal(t, 1, width)
|
||||||
|
|
||||||
|
return point
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also test the points at to make sure that
|
||||||
|
// I didn't make a counting mistake...
|
||||||
|
AssertPoint(t, getPoint(0), 't', 10, 30)
|
||||||
|
AssertPoint(t, getPoint(1), 'e', 11, 30)
|
||||||
|
AssertPoint(t, getPoint(2), 's', 12, 30)
|
||||||
|
AssertPoint(t, getPoint(3), 't', 13, 30)
|
||||||
|
AssertPoint(t, getPoint(4), ' ', 14, 30)
|
||||||
|
AssertPoint(t, getPoint(5), 'f', 15, 30, ColorYellow)
|
||||||
|
AssertPoint(t, getPoint(6), 'o', 16, 30, ColorYellow)
|
||||||
|
AssertPoint(t, getPoint(7), 'o', 17, 30, ColorYellow)
|
||||||
|
AssertPoint(t, getPoint(8), ' ', 18, 30, ColorYellow)
|
||||||
|
AssertPoint(t, getPoint(9), 'b', 19, 30, ColorRed)
|
||||||
|
AssertPoint(t, getPoint(10), 'a', 20, 30, ColorRed)
|
||||||
|
AssertPoint(t, getPoint(11), 'r', 21, 30, ColorRed)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer = EscapeCodeRenderer{"甗 郔\033[33m镺 笀耔 澉 灊\033[31m灅甗"}
|
||||||
|
sequence = renderer.RenderSequence(2, -1, black, white)
|
||||||
|
if assertRenderSequence(t, sequence, black, white, "郔镺 笀耔 澉 灊灅甗", 2) {
|
||||||
|
assertColorSubsequence(t, sequence.Sequences[0], "YELLOW", 1, 9)
|
||||||
|
assertColorSubsequence(t, sequence.Sequences[1], "RED", 9, 11)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer = EscapeCodeRenderer{"\033[33mHell\033[31mo world"}
|
||||||
|
sequence = renderer.RenderSequence(2, -1, black, white)
|
||||||
|
if assertRenderSequence(t, sequence, black, white, "llo world", 2) {
|
||||||
|
assertColorSubsequence(t, sequence.Sequences[0], "YELLOW", 0, 2)
|
||||||
|
assertColorSubsequence(t, sequence.Sequences[1], "RED", 2, 9)
|
||||||
|
}
|
||||||
|
|
||||||
|
sequence = renderer.RenderSequence(1, 7, black, white)
|
||||||
|
if assertRenderSequence(t, sequence, black, white, "ello w", 2) {
|
||||||
|
assertColorSubsequence(t, sequence.Sequences[0], "YELLOW", 0, 3)
|
||||||
|
assertColorSubsequence(t, sequence.Sequences[1], "RED", 3, 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
sequence = renderer.RenderSequence(6, 10, black, white)
|
||||||
|
if assertRenderSequence(t, sequence, black, white, "worl", 1) {
|
||||||
|
assertColorSubsequence(t, sequence.Sequences[0], "RED", 0, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with out-of-range escape code
|
||||||
|
renderer = EscapeCodeRenderer{"hello \033[38mtest"}
|
||||||
|
sequence = renderer.RenderSequence(0, -1, black, white)
|
||||||
|
assertRenderSequence(t, sequence, black, white, "hello \033[38mtest", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEscapeCodeRenderer_Render(t *testing.T) {
|
||||||
|
renderer := EscapeCodeRenderer{"test \033[33mfoo \033[31mbar"}
|
||||||
|
sequence := renderer.Render(4, 6)
|
||||||
|
if assertRenderSequence(t, sequence, 4, 6, "test foo bar", 2) {
|
||||||
|
assertColorSubsequence(t, sequence.Sequences[0], "YELLOW", 5, 9)
|
||||||
|
assertColorSubsequence(t, sequence.Sequences[1], "RED", 9, 12)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEscapeCodeRendererFactory_TextRenderer(t *testing.T) {
|
||||||
|
factory := EscapeCodeRendererFactory{}
|
||||||
|
assert.Equal(t, EscapeCodeRenderer{"foo"}, factory.TextRenderer("foo"))
|
||||||
|
assert.Equal(t, EscapeCodeRenderer{"bar"}, factory.TextRenderer("bar"))
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user