forked from tslocum/cview
parent
4de432d7aa
commit
710303491e
79
textview.go
79
textview.go
|
@ -3,7 +3,6 @@ package cview
|
|||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
|
@ -26,7 +25,7 @@ var (
|
|||
// view.
|
||||
type textViewIndex struct {
|
||||
Line int // The index into the "buffer" variable.
|
||||
Pos int // The index into the "buffer" string (byte position).
|
||||
Pos int // The index into the "buffer" line ([]byte position).
|
||||
NextPos int // The (byte) index of the next character in this buffer line.
|
||||
Width int // The screen width of this line.
|
||||
ForegroundColor string // The starting foreground color ("" = don't change, "-" = reset).
|
||||
|
@ -100,7 +99,7 @@ type TextView struct {
|
|||
*Box
|
||||
|
||||
// The text buffer.
|
||||
buffer []string
|
||||
buffer [][]byte
|
||||
|
||||
// The last bytes that have been received but are not part of the buffer yet.
|
||||
recentBytes []byte
|
||||
|
@ -317,38 +316,33 @@ func (t *TextView) SetText(text string) *TextView {
|
|||
|
||||
// GetText returns the current text of this text view. If "stripTags" is set
|
||||
// to true, any region/color tags are stripped from the text.
|
||||
func (t *TextView) GetText(stripTags bool) string {
|
||||
func (t *TextView) GetText(stripTags bool) []byte {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
|
||||
// Get the buffer.
|
||||
buffer := t.buffer
|
||||
if !stripTags {
|
||||
buffer = append(buffer, string(t.recentBytes))
|
||||
if len(t.recentBytes) > 0 {
|
||||
return bytes.Join(append(t.buffer, t.recentBytes), []byte("\n"))
|
||||
}
|
||||
return bytes.Join(t.buffer, []byte("\n"))
|
||||
}
|
||||
|
||||
// Add newlines again.
|
||||
text := strings.Join(buffer, "\n")
|
||||
|
||||
// Strip from tags if required.
|
||||
if stripTags {
|
||||
if t.regions {
|
||||
text = regionPattern.ReplaceAllString(text, "")
|
||||
}
|
||||
if t.dynamicColors {
|
||||
text = colorPattern.ReplaceAllStringFunc(text, func(match string) string {
|
||||
if len(match) > 2 {
|
||||
return ""
|
||||
}
|
||||
return match
|
||||
})
|
||||
}
|
||||
if t.regions || t.dynamicColors {
|
||||
text = escapePattern.ReplaceAllString(text, `[$1$2]`)
|
||||
}
|
||||
buffer := bytes.Join(t.buffer, []byte("\n"))
|
||||
if t.regions {
|
||||
buffer = regionPattern.ReplaceAll(buffer, nil)
|
||||
}
|
||||
|
||||
return text
|
||||
if t.dynamicColors {
|
||||
buffer = colorPattern.ReplaceAllFunc(buffer, func(match []byte) []byte {
|
||||
if len(match) > 2 {
|
||||
return nil
|
||||
}
|
||||
return match
|
||||
})
|
||||
}
|
||||
if t.regions || t.dynamicColors {
|
||||
buffer = escapePattern.ReplaceAll(buffer, []byte(`[$1$2]`))
|
||||
}
|
||||
return buffer
|
||||
}
|
||||
|
||||
// SetDynamicColors sets the flag that allows the text color to be changed
|
||||
|
@ -636,7 +630,7 @@ func (t *TextView) GetRegionText(regionID string) string {
|
|||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
|
||||
if !t.regions || regionID == "" {
|
||||
if !t.regions || len(regionID) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
|
@ -649,17 +643,17 @@ func (t *TextView) GetRegionText(regionID string) string {
|
|||
// Find all color tags in this line.
|
||||
var colorTagIndices [][]int
|
||||
if t.dynamicColors {
|
||||
colorTagIndices = colorPattern.FindAllStringIndex(str, -1)
|
||||
colorTagIndices = colorPattern.FindAllIndex(str, -1)
|
||||
}
|
||||
|
||||
// Find all regions in this line.
|
||||
var (
|
||||
regionIndices [][]int
|
||||
regions [][]string
|
||||
regions [][][]byte
|
||||
)
|
||||
if t.regions {
|
||||
regionIndices = regionPattern.FindAllStringIndex(str, -1)
|
||||
regions = regionPattern.FindAllStringSubmatch(str, -1)
|
||||
regionIndices = regionPattern.FindAllIndex(str, -1)
|
||||
regions = regionPattern.FindAllSubmatch(str, -1)
|
||||
}
|
||||
|
||||
// Analyze this line.
|
||||
|
@ -682,7 +676,7 @@ func (t *TextView) GetRegionText(regionID string) string {
|
|||
// This is the end of the requested region. We're done.
|
||||
return buffer.String()
|
||||
}
|
||||
currentRegionID = regions[currentRegion][1]
|
||||
currentRegionID = string(regions[currentRegion][1])
|
||||
currentRegion++
|
||||
}
|
||||
continue
|
||||
|
@ -690,7 +684,7 @@ func (t *TextView) GetRegionText(regionID string) string {
|
|||
|
||||
// Add this rune.
|
||||
if currentRegionID == regionID {
|
||||
buffer.WriteRune(ch)
|
||||
buffer.WriteByte(ch)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -768,12 +762,12 @@ func (t *TextView) write(p []byte) (n int, err error) {
|
|||
|
||||
// Transform the new bytes into strings.
|
||||
newBytes = bytes.Replace(newBytes, []byte{'\t'}, bytes.Repeat([]byte{' '}, TabSize), -1)
|
||||
for index, line := range newLineRegex.Split(string(newBytes), -1) {
|
||||
for index, line := range bytes.Split(newBytes, []byte("\n")) {
|
||||
if index == 0 {
|
||||
if len(t.buffer) == 0 {
|
||||
t.buffer = []string{line}
|
||||
t.buffer = [][]byte{line}
|
||||
} else {
|
||||
t.buffer[len(t.buffer)-1] += line
|
||||
t.buffer[len(t.buffer)-1] = append(t.buffer[len(t.buffer)-1], line...)
|
||||
}
|
||||
} else {
|
||||
t.buffer = append(t.buffer, line)
|
||||
|
@ -831,7 +825,8 @@ func (t *TextView) reindexBuffer(width int) {
|
|||
)
|
||||
|
||||
// Go through each line in the buffer.
|
||||
for bufferIndex, str := range t.buffer {
|
||||
for bufferIndex, buf := range t.buffer {
|
||||
str := string(buf)
|
||||
colorTagIndices, colorTags, regionIndices, regions, escapeIndices, strippedStr, _ := decomposeString(str, t.dynamicColors, t.regions)
|
||||
|
||||
// Split the line if required.
|
||||
|
@ -963,11 +958,11 @@ func (t *TextView) reindexBuffer(width int) {
|
|||
if t.wrap && t.wordWrap {
|
||||
for _, line := range t.index {
|
||||
str := t.buffer[line.Line][line.Pos:line.NextPos]
|
||||
spaces := spacePattern.FindAllStringIndex(str, -1)
|
||||
spaces := spacePattern.FindAllIndex(str, -1)
|
||||
if spaces != nil && spaces[len(spaces)-1][1] == len(str) {
|
||||
oldNextPos := line.NextPos
|
||||
line.NextPos -= spaces[len(spaces)-1][1] - spaces[len(spaces)-1][0]
|
||||
line.Width -= stringWidth(t.buffer[line.Line][line.NextPos:oldNextPos])
|
||||
line.Width -= stringWidth(string(t.buffer[line.Line][line.NextPos:oldNextPos]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1109,7 +1104,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||
}
|
||||
|
||||
// Process tags.
|
||||
colorTagIndices, colorTags, regionIndices, regions, escapeIndices, strippedText, _ := decomposeString(text, t.dynamicColors, t.regions)
|
||||
colorTagIndices, colorTags, regionIndices, regions, escapeIndices, strippedText, _ := decomposeString(string(text), t.dynamicColors, t.regions)
|
||||
|
||||
// Calculate the position of the line.
|
||||
var skip, posX int
|
||||
|
|
|
@ -3,7 +3,6 @@ package cview
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -68,9 +67,6 @@ func TestTextViewWrite(t *testing.T) {
|
|||
}
|
||||
|
||||
contents := tv.GetText(false)
|
||||
if len(contents) > 0 {
|
||||
contents = contents[0 : len(contents)-1] // Remove extra newline
|
||||
}
|
||||
if len(contents) != len(expectedData) {
|
||||
t.Errorf("failed to write: incorrect contents: expected %d bytes, got %d", len(contents), len(expectedData))
|
||||
} else if !bytes.Equal([]byte(contents), expectedData) {
|
||||
|
@ -155,6 +151,76 @@ func BenchmarkTextViewIndex(b *testing.B) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTextViewGetText(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tv := NewTextView()
|
||||
tv.SetDynamicColors(true)
|
||||
tv.SetRegions(true)
|
||||
|
||||
n, err := tv.Write(randomData)
|
||||
if err != nil {
|
||||
t.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
|
||||
} else if n != randomDataSize {
|
||||
t.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
|
||||
}
|
||||
|
||||
suffix := []byte(`["start"]outer[b]inner[-]outer[""]`)
|
||||
suffixStripped := []byte("outerinnerouter")
|
||||
|
||||
n, err = tv.Write(suffix)
|
||||
if err != nil {
|
||||
t.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(tv.GetText(false), append(randomData, suffix...)) {
|
||||
t.Error("failed to get un-stripped text: unexpected suffix")
|
||||
}
|
||||
|
||||
if !bytes.Equal(tv.GetText(true), append(randomData, suffixStripped...)) {
|
||||
t.Error("failed to get text stripped text: unexpected suffix")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTextViewGetText(b *testing.B) {
|
||||
for _, c := range textViewTestCases {
|
||||
c := c // Capture
|
||||
|
||||
if c.app {
|
||||
continue // Skip for this benchmark
|
||||
}
|
||||
|
||||
b.Run(c.String(), func(b *testing.B) {
|
||||
var (
|
||||
tv = tvc(NewTextView(), c)
|
||||
n int
|
||||
err error
|
||||
v []byte
|
||||
)
|
||||
|
||||
_, err = prepareAppendTextView(tv)
|
||||
if err != nil {
|
||||
b.Errorf("failed to prepare append TextView: %s", err)
|
||||
}
|
||||
|
||||
n, err = tv.Write(randomData)
|
||||
if err != nil {
|
||||
b.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
|
||||
} else if n != randomDataSize {
|
||||
b.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
v = tv.GetText(true)
|
||||
_ = v
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextViewDraw(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -242,7 +308,7 @@ func TestTextViewMaxLines(t *testing.T) {
|
|||
}
|
||||
|
||||
// retrieve the total text and see we have the 100 lines:
|
||||
count := strings.Count(tv.GetText(true), "\n")
|
||||
count := bytes.Count(tv.GetText(true), []byte("\n"))
|
||||
if count != 100 {
|
||||
t.Errorf("expected 100 lines, got %d", count)
|
||||
}
|
||||
|
@ -250,7 +316,7 @@ func TestTextViewMaxLines(t *testing.T) {
|
|||
// now set the maximum lines to 20, this should clip the buffer:
|
||||
tv.SetMaxLines(20)
|
||||
// verify buffer was clipped:
|
||||
count = len(strings.Split(tv.GetText(true), "\n"))
|
||||
count = len(bytes.Split(tv.GetText(true), []byte("\n")))
|
||||
if count != 20 {
|
||||
t.Errorf("expected 20 lines, got %d", count)
|
||||
}
|
||||
|
@ -265,14 +331,14 @@ func TestTextViewMaxLines(t *testing.T) {
|
|||
|
||||
// Sice max lines is set to 20, we should still get 20 lines:
|
||||
txt := tv.GetText(true)
|
||||
lines := strings.Split(txt, "\n")
|
||||
lines := bytes.Split(txt, []byte("\n"))
|
||||
count = len(lines)
|
||||
if count != 20 {
|
||||
t.Errorf("expected 20 lines, got %d", count)
|
||||
}
|
||||
|
||||
// and those 20 lines should be the last ones:
|
||||
if lines[0] != "L181" {
|
||||
if !bytes.Equal(lines[0], []byte("L181")) {
|
||||
t.Errorf("expected to get L181, got %s", lines[0])
|
||||
}
|
||||
|
||||
|
@ -335,9 +401,9 @@ func tvc(tv *TextView, c *textViewTestCase) *TextView {
|
|||
|
||||
func cl(v bool) rune {
|
||||
if v {
|
||||
return 'T'
|
||||
return 'Y'
|
||||
}
|
||||
return 'F'
|
||||
return 'N'
|
||||
}
|
||||
|
||||
func prepareAppendTextView(t *TextView) ([]byte, error) {
|
||||
|
|
Loading…
Reference in New Issue