Implemented RenderedSequence.Buffer
This commit is contained in:
		
							parent
							
								
									a267dd583e
								
							
						
					
					
						commit
						6a11cf3efb
					
				@ -8,7 +8,7 @@ import (
 | 
				
			|||||||
// TextRender adds common methods for rendering a text on screeen.
 | 
					// TextRender adds common methods for rendering a text on screeen.
 | 
				
			||||||
type TextRender interface {
 | 
					type TextRender interface {
 | 
				
			||||||
	NormalizedText(text string) string
 | 
						NormalizedText(text string) string
 | 
				
			||||||
	RenderSequence(text string, lastColor, background Attribute) RenderedSequence
 | 
						RenderSequence(start, end int, lastColor, background Attribute) RenderedSequence
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// MarkdownRegex is used by MarkdownTextRenderer to determine how to format the
 | 
					// MarkdownRegex is used by MarkdownTextRenderer to determine how to format the
 | 
				
			||||||
@ -22,12 +22,30 @@ var markdownPattern = regexp.MustCompile(MarkdownRegex)
 | 
				
			|||||||
// MarkdownTextRenderer is used for rendering the text with colors using
 | 
					// MarkdownTextRenderer is used for rendering the text with colors using
 | 
				
			||||||
// markdown-like syntax.
 | 
					// markdown-like syntax.
 | 
				
			||||||
// See: https://github.com/gizak/termui/issues/4#issuecomment-87270635
 | 
					// See: https://github.com/gizak/termui/issues/4#issuecomment-87270635
 | 
				
			||||||
type MarkdownTextRenderer struct{}
 | 
					type MarkdownTextRenderer struct {
 | 
				
			||||||
 | 
						Text string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NormalizedText returns the text the user will see (without colors).
 | 
					// NormalizedText returns the text the user will see (without colors).
 | 
				
			||||||
// It strips out all formatting option and only preserves plain text.
 | 
					// It strips out all formatting option and only preserves plain text.
 | 
				
			||||||
func (r MarkdownTextRenderer) NormalizedText(text string) string {
 | 
					func (r MarkdownTextRenderer) NormalizedText() string {
 | 
				
			||||||
	return r.RenderSequence(text, 0, 0).NormalizedText
 | 
						return r.normalizeText(r.Text)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r MarkdownTextRenderer) normalizeText(text string) string {
 | 
				
			||||||
 | 
						lText := strings.ToLower(text)
 | 
				
			||||||
 | 
						indexes := markdownPattern.FindAllStringSubmatchIndex(lText, -1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Interate through indexes in reverse order.
 | 
				
			||||||
 | 
						for i := len(indexes) - 1; i >= 0; i-- {
 | 
				
			||||||
 | 
							theIndex := indexes[i]
 | 
				
			||||||
 | 
							start, end := theIndex[0], theIndex[1]
 | 
				
			||||||
 | 
							contentStart, contentEnd := theIndex[2], theIndex[3]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							text = text[:start] + text[contentStart:contentEnd] + text[end:]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return text
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
@ -42,14 +60,21 @@ For all available combinations, colors, and attribute, see: `StringToAttribute`.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
This method returns a RenderedSequence
 | 
					This method returns a RenderedSequence
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
func (r MarkdownTextRenderer) RenderSequence(text string, lastColor, background Attribute) RenderedSequence {
 | 
					func (r MarkdownTextRenderer) RenderSequence(start, end int, lastColor, background Attribute) RenderedSequence {
 | 
				
			||||||
 | 
						text := r.Text
 | 
				
			||||||
 | 
						if end == -1 {
 | 
				
			||||||
 | 
							end = len(r.NormalizedText())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	getMatch := func(s string) []int {
 | 
						getMatch := func(s string) []int {
 | 
				
			||||||
		return markdownPattern.FindStringSubmatchIndex(strings.ToLower(s))
 | 
							return markdownPattern.FindStringSubmatchIndex(strings.ToLower(s))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var sequences []ColorSubsequence
 | 
						var sequences []ColorSubsequence
 | 
				
			||||||
	for match := getMatch(text); match != nil; match = getMatch(text) {
 | 
						for match := getMatch(text); match != nil; match = getMatch(text) {
 | 
				
			||||||
		start, end := match[0], match[1]
 | 
							// Check if match is in the start/end range.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							matchStart, matchEnd := match[0], match[1]
 | 
				
			||||||
		colorStart, colorEnd := match[4], match[5]
 | 
							colorStart, colorEnd := match[4], match[5]
 | 
				
			||||||
		contentStart, contentEnd := match[2], match[3]
 | 
							contentStart, contentEnd := match[2], match[3]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -57,11 +82,30 @@ func (r MarkdownTextRenderer) RenderSequence(text string, lastColor, background
 | 
				
			|||||||
		content := text[contentStart:contentEnd]
 | 
							content := text[contentStart:contentEnd]
 | 
				
			||||||
		theSequence := ColorSubsequence{color, contentStart - 1, contentEnd - 1}
 | 
							theSequence := ColorSubsequence{color, contentStart - 1, contentEnd - 1}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		sequences = append(sequences, theSequence)
 | 
							if start < theSequence.End && end > theSequence.Start {
 | 
				
			||||||
		text = text[:start] + content + text[end:]
 | 
								// Make the sequence relative and append.
 | 
				
			||||||
 | 
								theSequence.Start -= start
 | 
				
			||||||
 | 
								if theSequence.Start < 0 {
 | 
				
			||||||
 | 
									theSequence.Start = 0
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								theSequence.End -= start
 | 
				
			||||||
 | 
								if theSequence.End < 0 {
 | 
				
			||||||
 | 
									theSequence.End = 0
 | 
				
			||||||
 | 
								} else if theSequence.End > end-start {
 | 
				
			||||||
 | 
									theSequence.End = end - start
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								sequences = append(sequences, theSequence)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							text = text[:matchStart] + content + text[matchEnd:]
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return RenderedSequence{text, lastColor, background, sequences}
 | 
						if end == -1 {
 | 
				
			||||||
 | 
							end = len(text)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return RenderedSequence{text[start:end], lastColor, background, sequences}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RenderedSequence is a string sequence that is capable of returning the
 | 
					// RenderedSequence is a string sequence that is capable of returning the
 | 
				
			||||||
@ -80,8 +124,35 @@ type ColorSubsequence struct {
 | 
				
			|||||||
	End   int
 | 
						End   int
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ColorSubsequencesToMap creates a map with all colors that from the
 | 
				
			||||||
 | 
					// subsequences.
 | 
				
			||||||
 | 
					func ColorSubsequencesToMap(sequences []ColorSubsequence) map[int]Attribute {
 | 
				
			||||||
 | 
						result := make(map[int]Attribute)
 | 
				
			||||||
 | 
						for _, theSequence := range sequences {
 | 
				
			||||||
 | 
							for i := theSequence.Start; i < theSequence.End; i++ {
 | 
				
			||||||
 | 
								result[i] = theSequence.Color
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Buffer returns the colorful formatted buffer and the last color that was
 | 
					// Buffer returns the colorful formatted buffer and the last color that was
 | 
				
			||||||
// used.
 | 
					// used.
 | 
				
			||||||
func (s *RenderedSequence) Buffer(x, y int) ([]Point, Attribute) {
 | 
					func (s *RenderedSequence) Buffer(x, y int) ([]Point, Attribute) {
 | 
				
			||||||
	return nil, s.LastColor
 | 
						buffer := make([]Point, 0, len(s.NormalizedText)) // This is just an assumtion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						colors := ColorSubsequencesToMap(s.Sequences)
 | 
				
			||||||
 | 
						for i, r := range []rune(s.NormalizedText) {
 | 
				
			||||||
 | 
							color, ok := colors[i]
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								color = s.LastColor
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							p := Point{r, s.BackgroundColor, color, x, y}
 | 
				
			||||||
 | 
							buffer = append(buffer, p)
 | 
				
			||||||
 | 
							x += charWidth(r)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return buffer, s.LastColor
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,36 +1,41 @@
 | 
				
			|||||||
package termui
 | 
					package termui
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/davecgh/go-spew/spew"
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getMDRenderer() MarkdownTextRenderer {
 | 
					func TestMarkdownTextRenderer_normalizeText(t *testing.T) {
 | 
				
			||||||
	return MarkdownTextRenderer{}
 | 
						renderer := MarkdownTextRenderer{}
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestMarkdownTextRenderer_NormalizedText(t *testing.T) {
 | 
						got := renderer.normalizeText("[ERROR](red,bold) Something went wrong")
 | 
				
			||||||
	renderer := getMDRenderer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	got := renderer.NormalizedText("[ERROR](red,bold) Something went wrong")
 | 
					 | 
				
			||||||
	assert.Equal(t, got, "ERROR Something went wrong")
 | 
						assert.Equal(t, got, "ERROR Something went wrong")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	got = renderer.NormalizedText("[foo](red) hello [bar](green) world")
 | 
						got = renderer.normalizeText("[foo](red) hello [bar](green) world")
 | 
				
			||||||
	assert.Equal(t, got, "foo hello bar world")
 | 
						assert.Equal(t, got, "foo hello bar world")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	got = renderer.NormalizedText("[foo](g) hello [bar]green (world)")
 | 
						got = renderer.normalizeText("[foo](g) hello [bar]green (world)")
 | 
				
			||||||
	assert.Equal(t, got, "foo hello [bar]green (world)")
 | 
						assert.Equal(t, got, "foo hello [bar]green (world)")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// FIXME: [[ERROR]](red,bold) test should normalize to:
 | 
						// FIXME: [[ERROR]](red,bold) test should normalize to:
 | 
				
			||||||
	// [ERROR] test
 | 
						// [ERROR] test
 | 
				
			||||||
 | 
						// FIXME: Support unicode inside the error message.
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func assertRenderSequence(t *testing.T, sequence RenderedSequence, last, background Attribute, text string, lenSequences int) {
 | 
					func TestMarkdownTextRenderer_NormalizedText(t *testing.T) {
 | 
				
			||||||
	assert.Equal(t, last, sequence.LastColor)
 | 
						renderer := MarkdownTextRenderer{"[ERROR](red,bold) Something went wrong"}
 | 
				
			||||||
	assert.Equal(t, background, sequence.BackgroundColor)
 | 
						assert.Equal(t, renderer.NormalizedText(), "ERROR Something went wrong")
 | 
				
			||||||
	assert.Equal(t, text, sequence.NormalizedText)
 | 
					}
 | 
				
			||||||
	assert.Equal(t, lenSequences, len(sequence.Sequences))
 | 
					
 | 
				
			||||||
 | 
					func assertRenderSequence(t *testing.T, sequence RenderedSequence, last, background Attribute, text string, lenSequences int) bool {
 | 
				
			||||||
 | 
						msg := fmt.Sprintf("seq: %v", spew.Sdump(sequence))
 | 
				
			||||||
 | 
						assert.Equal(t, last, sequence.LastColor, msg)
 | 
				
			||||||
 | 
						assert.Equal(t, background, sequence.BackgroundColor, msg)
 | 
				
			||||||
 | 
						assert.Equal(t, text, sequence.NormalizedText, msg)
 | 
				
			||||||
 | 
						return assert.Equal(t, lenSequences, len(sequence.Sequences), msg)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func assertColorSubsequence(t *testing.T, s ColorSubsequence, color string, start, end int) {
 | 
					func assertColorSubsequence(t *testing.T, s ColorSubsequence, color string, start, end int) {
 | 
				
			||||||
@ -38,15 +43,102 @@ func assertColorSubsequence(t *testing.T, s ColorSubsequence, color string, star
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestMarkdownTextRenderer_RenderSequence(t *testing.T) {
 | 
					func TestMarkdownTextRenderer_RenderSequence(t *testing.T) {
 | 
				
			||||||
	renderer := getMDRenderer()
 | 
						// Simple test.
 | 
				
			||||||
 | 
						renderer := MarkdownTextRenderer{"[ERROR](red,bold) something went wrong"}
 | 
				
			||||||
 | 
						got := renderer.RenderSequence(0, -1, 3, 5)
 | 
				
			||||||
 | 
						if assertRenderSequence(t, got, 3, 5, "ERROR something went wrong", 1) {
 | 
				
			||||||
 | 
							assertColorSubsequence(t, got.Sequences[0], "RED,BOLD", 0, 5)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	got := renderer.RenderSequence("[ERROR](red,bold) something went wrong", 3, 5)
 | 
						got = renderer.RenderSequence(3, 8, 3, 5)
 | 
				
			||||||
	assertRenderSequence(t, got, 3, 5, "ERROR something went wrong", 1)
 | 
						if assertRenderSequence(t, got, 3, 5, "OR so", 1) {
 | 
				
			||||||
	assertColorSubsequence(t, got.Sequences[0], "RED,BOLD", 0, 5)
 | 
							assertColorSubsequence(t, got.Sequences[0], "RED,BOLD", 0, 2)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	got = renderer.RenderSequence("[foo](red) hello [bar](green) world", 7, 2)
 | 
						// Test for mutiple colors.
 | 
				
			||||||
	assertRenderSequence(t, got, 3, 2, "foo hello bar world", 2)
 | 
						renderer = MarkdownTextRenderer{"[foo](red) hello [bar](blue) world"}
 | 
				
			||||||
 | 
						got = renderer.RenderSequence(0, -1, 7, 2)
 | 
				
			||||||
 | 
						if assertRenderSequence(t, got, 7, 2, "foo hello bar world", 2) {
 | 
				
			||||||
 | 
							assertColorSubsequence(t, got.Sequences[0], "RED", 0, 3)
 | 
				
			||||||
 | 
							assertColorSubsequence(t, got.Sequences[1], "BLUE", 10, 13)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assertColorSubsequence(t, got.Sequences[0], "RED", 0, 3)
 | 
						// Test that out-of-bound color sequences are not added.
 | 
				
			||||||
	assertColorSubsequence(t, got.Sequences[1], "GREEN", 10, 13)
 | 
						got = renderer.RenderSequence(4, 6, 8, 1)
 | 
				
			||||||
 | 
						assertRenderSequence(t, got, 8, 1, "he", 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test Half-rendered text
 | 
				
			||||||
 | 
						got = renderer.RenderSequence(1, 12, 0, 0)
 | 
				
			||||||
 | 
						if assertRenderSequence(t, got, 0, 0, "oo hello ba", 2) {
 | 
				
			||||||
 | 
							assertColorSubsequence(t, got.Sequences[0], "RED", 0, 2)
 | 
				
			||||||
 | 
							assertColorSubsequence(t, got.Sequences[1], "BLUE", 9, 11)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test Half-rendered text (edges)
 | 
				
			||||||
 | 
						got = renderer.RenderSequence(2, 11, 0, 0)
 | 
				
			||||||
 | 
						if assertRenderSequence(t, got, 0, 0, "o hello b", 2) {
 | 
				
			||||||
 | 
							assertColorSubsequence(t, got.Sequences[0], "RED", 0, 1)
 | 
				
			||||||
 | 
							assertColorSubsequence(t, got.Sequences[1], "BLUE", 8, 9)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test half-rendered text (unicode)
 | 
				
			||||||
 | 
						// FIXME: Add
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test inside
 | 
				
			||||||
 | 
						renderer = MarkdownTextRenderer{"foo [foobar](red) bar"}
 | 
				
			||||||
 | 
						got = renderer.RenderSequence(4, 10, 0, 0)
 | 
				
			||||||
 | 
						if assertRenderSequence(t, got, 0, 0, "foobar", 1) {
 | 
				
			||||||
 | 
							assertColorSubsequence(t, got.Sequences[0], "RED", 0, 6)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestColorSubsequencesToMap(t *testing.T) {
 | 
				
			||||||
 | 
						colorSubsequences := []ColorSubsequence{
 | 
				
			||||||
 | 
							{ColorRed, 1, 4},
 | 
				
			||||||
 | 
							{ColorBlue | AttrBold, 9, 10},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						expected := make(map[int]Attribute)
 | 
				
			||||||
 | 
						expected[1] = ColorRed
 | 
				
			||||||
 | 
						expected[2] = ColorRed
 | 
				
			||||||
 | 
						expected[3] = ColorRed
 | 
				
			||||||
 | 
						expected[9] = ColorBlue | AttrBold
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Equal(t, expected, ColorSubsequencesToMap(colorSubsequences))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestRenderedSequence_Buffer(t *testing.T) {
 | 
				
			||||||
 | 
						cs := []ColorSubsequence{
 | 
				
			||||||
 | 
							{ColorRed, 3, 5},
 | 
				
			||||||
 | 
							{ColorBlue | AttrBold, 9, 10},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sequence := RenderedSequence{"Hello world", ColorWhite, ColorBlack, cs}
 | 
				
			||||||
 | 
						newPoint := func(char string, x, y int, colorA ...Attribute) Point {
 | 
				
			||||||
 | 
							var color Attribute
 | 
				
			||||||
 | 
							if colorA != nil && len(colorA) == 1 {
 | 
				
			||||||
 | 
								color = colorA[0]
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								color = ColorWhite
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return Point{[]rune(char)[0], ColorBlack, color, x, y}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						expected := []Point{
 | 
				
			||||||
 | 
							newPoint("H", 5, 7),
 | 
				
			||||||
 | 
							newPoint("e", 6, 7),
 | 
				
			||||||
 | 
							newPoint("l", 7, 7),
 | 
				
			||||||
 | 
							newPoint("l", 7, 7, ColorRed),
 | 
				
			||||||
 | 
							newPoint("o", 8, 7, ColorRed),
 | 
				
			||||||
 | 
							newPoint(" ", 9, 7),
 | 
				
			||||||
 | 
							newPoint("w", 10, 7),
 | 
				
			||||||
 | 
							newPoint("o", 11, 7),
 | 
				
			||||||
 | 
							newPoint("r", 12, 7),
 | 
				
			||||||
 | 
							newPoint("l", 13, 7, ColorBlue|AttrBold),
 | 
				
			||||||
 | 
							newPoint("d", 14, 7),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						buffer, lastColor := sequence.Buffer(5, 7)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Equal(t, expected[:3], buffer[:3])
 | 
				
			||||||
 | 
						assert.Equal(t, ColorWhite, lastColor)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user