Browse Source

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

tablepad
Trevor Slocum 2 years ago
parent
commit
96b2dd5523
  1. 1
      CHANGELOG
  2. 6
      demos/presentation/table.go
  3. 6
      demos/table/main.go
  4. 45
      table.go
  5. 106
      table_test.go

1
CHANGELOG

@ -1,6 +1,7 @@ @@ -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)

6
demos/presentation/table.go

@ -1,8 +1,8 @@ @@ -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]() { @@ -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()

6
demos/table/main.go

@ -2,17 +2,19 @@ @@ -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++ {

45
table.go

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
package cview
import (
"bytes"
"sort"
"sync"
@ -16,7 +17,7 @@ type TableCell struct { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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: @@ -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: @@ -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: @@ -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: @@ -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: @@ -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

@ -0,0 +1,106 @@ @@ -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
}
Loading…
Cancel
Save