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/nsf/termbox-go"
 | 
			
		||||
 | 
			
		||||
func commonList() *termui.List {
 | 
			
		||||
func markdownList() *termui.List {
 | 
			
		||||
	strs := []string{
 | 
			
		||||
		"[0] github.com/gizak/termui",
 | 
			
		||||
		"[1] 笀耔 [澉 灊灅甗](RED) 郔镺 笀耔 澉 [灊灅甗](yellow) 郔镺",
 | 
			
		||||
@ -26,18 +26,40 @@ func commonList() *termui.List {
 | 
			
		||||
	return list
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func listHidden() *termui.List {
 | 
			
		||||
	list := commonList()
 | 
			
		||||
func hideList(list *termui.List) *termui.List {
 | 
			
		||||
	list.Border.Label = "List - Hidden"
 | 
			
		||||
	list.Overflow = "hidden"
 | 
			
		||||
 | 
			
		||||
	return list
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func listWrap() *termui.List {
 | 
			
		||||
	list := commonList()
 | 
			
		||||
func wrapList(list *termui.List) *termui.List {
 | 
			
		||||
	list.Border.Label = "List - Wrapped"
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
@ -49,11 +71,20 @@ func main() {
 | 
			
		||||
	}
 | 
			
		||||
	defer termui.Close()
 | 
			
		||||
 | 
			
		||||
	hiddenList := listHidden()
 | 
			
		||||
	wrappedList := listWrap()
 | 
			
		||||
	wrappedList.X = 30
 | 
			
		||||
	hiddenMarkdownList := hideList(markdownList())
 | 
			
		||||
	wrappedMarkdownList := wrapList(markdownList())
 | 
			
		||||
 | 
			
		||||
	hiddenEscapeList := hideList(escapeList())
 | 
			
		||||
	wrappedEscapeList := wrapList(escapeList())
 | 
			
		||||
 | 
			
		||||
	lists := []termui.Bufferer{
 | 
			
		||||
		hiddenEscapeList,
 | 
			
		||||
		hiddenMarkdownList,
 | 
			
		||||
		wrappedMarkdownList,
 | 
			
		||||
		wrappedEscapeList,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	termui.UseTheme("helloworld")
 | 
			
		||||
	termui.Render(hiddenList, wrappedList)
 | 
			
		||||
	termui.Render(lists...)
 | 
			
		||||
	termbox.PollEvent()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										209
									
								
								textRender.go
									
									
									
									
									
								
							
							
						
						
									
										209
									
								
								textRender.go
									
									
									
									
									
								
							@ -1,7 +1,9 @@
 | 
			
		||||
package termui
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -243,3 +245,210 @@ type PlainRendererFactory struct{}
 | 
			
		||||
func (f PlainRendererFactory) TextRenderer(text string) TextRenderer {
 | 
			
		||||
	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
 | 
			
		||||
 | 
			
		||||
	assert.Implements(t, inter, new(MarkdownTextRenderer))
 | 
			
		||||
	assert.Implements(t, inter, new(EscapeCodeRenderer))
 | 
			
		||||
	assert.Implements(t, inter, new(PlainRenderer))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -20,6 +21,7 @@ func TestTextRendererFactory_TestInterface(t *testing.T) {
 | 
			
		||||
	var inter *TextRendererFactory
 | 
			
		||||
 | 
			
		||||
	assert.Implements(t, inter, new(MarkdownTextRendererFactory))
 | 
			
		||||
	assert.Implements(t, inter, new(EscapeCodeRendererFactory))
 | 
			
		||||
	assert.Implements(t, inter, new(PlainRendererFactory))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -270,3 +272,183 @@ func TestPosUnicode(t *testing.T) {
 | 
			
		||||
	require.Equal(t, "你好", 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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user