Add Table test and benchmark, store TableCell as []byte instead of string

This commit is contained in:
Trevor Slocum 2020-10-06 17:17:43 -07:00
parent 4cbfd55a8e
commit 96b2dd5523
5 changed files with 146 additions and 18 deletions

View File

@ -1,6 +1,7 @@
v1.5.1 (WIP)
- Store TextView buffer as [][]byte instead of []string
- Add TextView.SetBytes and TextView.GetBytes
- Add TableCell.SetBytes, TableCell.GetBytes and TableCell.GetText
- Allow modification of scroll bar render text
- Optimize TextView (writing is 90% faster, drawing is 50% faster)

View File

@ -1,8 +1,8 @@
package main
import (
"bytes"
"fmt"
"strings"
"github.com/gdamore/tcell/v2"
"gitlab.com/tslocum/cview"
@ -251,8 +251,8 @@ const tableSelectCell = `[green]func[white] [yellow]main[white]() {
func Table(nextSlide func()) (title string, content cview.Primitive) {
table := cview.NewTable().
SetFixed(1, 1)
for row, line := range strings.Split(tableData, "\n") {
for column, cell := range strings.Split(line, "|") {
for row, line := range bytes.Split([]byte(tableData), []byte("\n")) {
for column, cell := range bytes.Split(line, []byte("|")) {
color := tcell.ColorWhite.TrueColor()
if row == 0 {
color = tcell.ColorYellow.TrueColor()

View File

@ -2,17 +2,19 @@
package main
import (
"strings"
"bytes"
"github.com/gdamore/tcell/v2"
"gitlab.com/tslocum/cview"
)
const loremIpsumText = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
func main() {
app := cview.NewApplication()
table := cview.NewTable().
SetBorders(true)
lorem := strings.Split("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.", " ")
lorem := bytes.Split([]byte(loremIpsumText), []byte(" "))
cols, rows := 10, 40
word := 0
for r := 0; r < rows; r++ {

View File

@ -1,6 +1,7 @@
package cview
import (
"bytes"
"sort"
"sync"
@ -16,7 +17,7 @@ type TableCell struct {
Reference interface{}
// The text to be displayed in the table cell.
Text string
Text []byte
// The alignment of the cell text. One of AlignLeft (default), AlignCenter,
// or AlignRight.
@ -52,7 +53,7 @@ type TableCell struct {
// NewTableCell returns a new table cell with sensible defaults. That is, left
// aligned text with the primary text color (see Styles) and a transparent
// background (using the background of the Table).
func NewTableCell(text string) *TableCell {
func NewTableCell(text []byte) *TableCell {
return &TableCell{
Text: text,
Align: AlignLeft,
@ -61,8 +62,8 @@ func NewTableCell(text string) *TableCell {
}
}
// SetText sets the cell's text.
func (c *TableCell) SetText(text string) *TableCell {
// SetBytes sets the cell's text.
func (c *TableCell) SetBytes(text []byte) *TableCell {
c.Lock()
defer c.Unlock()
@ -70,6 +71,24 @@ func (c *TableCell) SetText(text string) *TableCell {
return c
}
// SetText sets the cell's text.
func (c *TableCell) SetText(text string) *TableCell {
return c.SetBytes([]byte(text))
}
// GetBytes returns the cell's text.
func (c *TableCell) GetBytes() []byte {
c.RLock()
defer c.RUnlock()
return c.Text
}
// GetText returns the cell's text.
func (c *TableCell) GetText() string {
return string(c.GetBytes())
}
// SetAlign sets the cell's text alignment, one of AlignLeft, AlignCenter, or
// AlignRight.
func (c *TableCell) SetAlign(align int) *TableCell {
@ -285,7 +304,7 @@ type Table struct {
// If set to true, the table's last row will always be visible.
trackEnd bool
// The sort function of the table. Defaults to a case-sensitive string comparison.
// The sort function of the table. Defaults to a case-sensitive comparison.
sortFunc func(column, i, j int) bool
// Whether or not the table should be sorted when a fixed row is clicked.
@ -591,7 +610,7 @@ func (t *Table) SetCell(row, column int, cell *TableCell) *Table {
}
// SetCellSimple calls SetCell() with the given text, left-aligned, in white.
func (t *Table) SetCellSimple(row, column int, text string) *Table {
func (t *Table) SetCellSimple(row, column int, text []byte) *Table {
return t.SetCell(row, column, NewTableCell(text))
}
@ -797,7 +816,7 @@ func (t *Table) Sort(column int, descending bool) *Table {
if t.sortFunc == nil {
t.sortFunc = func(column, i, j int) bool {
return t.cells[i][column].Text < t.cells[j][column].Text
return bytes.Compare(t.cells[i][column].Text, t.cells[j][column].Text) == -1
}
}
@ -995,7 +1014,7 @@ ColumnLoop:
}
for _, row := range evaluationRows {
if cell := getCell(row, column); cell != nil {
_, _, _, _, _, _, cellWidth := decomposeText([]byte(cell.Text), true, false)
_, _, _, _, _, _, cellWidth := decomposeText(cell.Text, true, false)
if cell.MaxWidth > 0 && cell.MaxWidth < cellWidth {
cellWidth = cell.MaxWidth
}
@ -1089,8 +1108,8 @@ ColumnLoop:
finalWidth = width - columnX - 1
}
cell.x, cell.y, cell.width = x+columnX+1, y+rowY, finalWidth
_, printed := printWithStyle(screen, []byte(cell.Text), x+columnX+1, y+rowY, finalWidth, cell.Align, SetAttributes(tcell.StyleDefault.Foreground(cell.Color), cell.Attributes))
if TaggedStringWidth(cell.Text)-printed > 0 && printed > 0 {
_, printed := printWithStyle(screen, cell.Text, x+columnX+1, y+rowY, finalWidth, cell.Align, SetAttributes(tcell.StyleDefault.Foreground(cell.Color), cell.Attributes))
if TaggedTextWidth(cell.Text)-printed > 0 && printed > 0 {
_, _, style, _ := screen.GetContent(x+columnX+finalWidth, y+rowY)
printWithStyle(screen, []byte(string(SemigraphicsHorizontalEllipsis)), x+columnX+finalWidth, y+rowY, 1, AlignLeft, style)
}
@ -1201,7 +1220,7 @@ ColumnLoop:
// the drawing of a cell by background color, selected cells last.
type cellInfo struct {
x, y, w, h int
text tcell.Color
color tcell.Color
selected bool
}
cellsByBackgroundColor := make(map[tcell.Color][]*cellInfo)
@ -1229,7 +1248,7 @@ ColumnLoop:
y: by,
w: bw,
h: bh,
text: cell.Color,
color: cell.Color,
selected: cellSelected,
})
if !ok {
@ -1256,7 +1275,7 @@ ColumnLoop:
if t.selectedStyle != tcell.StyleDefault {
defer colorBackground(cell.x, cell.y, cell.w, cell.h, selBg, selFg, selAttr, false)
} else {
defer colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.text, 0, true)
defer colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.color, 0, true)
}
} else {
colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, tcell.ColorDefault, 0, false)

106
table_test.go Normal file
View File

@ -0,0 +1,106 @@
package cview
import (
"fmt"
"testing"
)
var tableTestCases = generateTableTestCases()
type tableTestCase struct {
rows int
columns int
fixedRows int
fixedColumns int
}
func (c *tableTestCase) String() string {
return fmt.Sprintf("Rows=%d/Cols=%d/FixedRows=%d/FixedCols=%d", c.rows, c.columns, c.fixedRows, c.fixedColumns)
}
func TestTable(t *testing.T) {
t.Parallel()
for _, c := range tableTestCases {
c := c // Capture
t.Run(c.String(), func(t *testing.T) {
t.Parallel()
table := tc(c)
app, err := newTestApp(table)
if err != nil {
t.Errorf("failed to initialize Application: %s", err)
}
for row := 0; row < c.rows; row++ {
for column := 0; column < c.columns; column++ {
contents := table.GetCell(row, column).GetText()
expected := fmt.Sprintf("%d,%d", column, row)
if contents != expected {
t.Errorf("failed to either get or set TableCell text: expected %s, got %s", expected, contents)
}
}
}
table.Draw(app.screen)
table.Clear()
})
}
}
func BenchmarkTableDraw(b *testing.B) {
for _, c := range tableTestCases {
c := c // Capture
b.Run(c.String(), func(b *testing.B) {
table := tc(c)
app, err := newTestApp(table)
if err != nil {
b.Errorf("failed to initialize Application: %s", err)
}
table.Draw(app.screen)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
table.Draw(app.screen)
}
})
}
}
func generateTableTestCases() []*tableTestCase {
var cases []*tableTestCase
for i := 1; i < 3; i++ {
rows := i * 5
for i := 1; i < 3; i++ {
columns := i * 7
for fixedRows := 0; fixedRows < 3; fixedRows++ {
for fixedColumns := 0; fixedColumns < 3; fixedColumns++ {
cases = append(cases, &tableTestCase{rows, columns, fixedRows, fixedColumns})
}
}
}
}
return cases
}
func tc(c *tableTestCase) *Table {
table := NewTable()
for row := 0; row < c.rows; row++ {
for column := 0; column < c.columns; column++ {
table.SetCellSimple(row, column, []byte(fmt.Sprintf("%d,%d", column, row)))
}
}
table.SetFixed(c.fixedRows, c.fixedColumns)
return table
}