fix: shared indices for first row of data and headers (StyleFunc bug) (#377)

* test(table): show differing styles between first row and headers

* fix(table): remove overlapping indices for styling rows and headers

* test(table): remove TestTableShrink; it's fixed on another branch

* docs(table): add godoc for HeaderRow const

* fix(test): use HeaderRow for table header styling
This commit is contained in:
bashbunni 2024-10-03 10:12:42 -07:00 committed by GitHub
parent b08e7e4762
commit f8dd5072db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 52 additions and 11 deletions

View File

@ -7,6 +7,10 @@ import (
"github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/ansi"
) )
// HeaderRow denotes the header's row index used when rendering headers. Use
// this value when looking to customize header styles in StyleFunc.
const HeaderRow int = -1
// StyleFunc is the style function that determines the style of a Cell. // StyleFunc is the style function that determines the style of a Cell.
// //
// It takes the row and column of the cell as an input and determines the // It takes the row and column of the cell as an input and determines the
@ -235,15 +239,15 @@ func (t *Table) String() string {
// the StyleFunc after the headers and rows. Update the widths for a final // the StyleFunc after the headers and rows. Update the widths for a final
// time. // time.
for i, cell := range t.headers { for i, cell := range t.headers {
t.widths[i] = max(t.widths[i], lipgloss.Width(t.style(0, i).Render(cell))) t.widths[i] = max(t.widths[i], lipgloss.Width(t.style(HeaderRow, i).Render(cell)))
t.heights[0] = max(t.heights[0], lipgloss.Height(t.style(0, i).Render(cell))) t.heights[0] = max(t.heights[0], lipgloss.Height(t.style(HeaderRow, i).Render(cell)))
} }
for r := 0; r < t.data.Rows(); r++ { for r := 0; r < t.data.Rows(); r++ {
for i := 0; i < t.data.Columns(); i++ { for i := 0; i < t.data.Columns(); i++ {
cell := t.data.At(r, i) cell := t.data.At(r, i)
rendered := t.style(r+1, i).Render(cell) rendered := t.style(r, i).Render(cell)
t.heights[r+btoi(hasHeaders)] = max(t.heights[r+btoi(hasHeaders)], lipgloss.Height(rendered)) t.heights[r+btoi(hasHeaders)] = max(t.heights[r+btoi(hasHeaders)], lipgloss.Height(rendered))
t.widths[i] = max(t.widths[i], lipgloss.Width(rendered)) t.widths[i] = max(t.widths[i], lipgloss.Width(rendered))
} }
@ -452,7 +456,7 @@ func (t *Table) constructHeaders() string {
s.WriteString(t.borderStyle.Render(t.border.Left)) s.WriteString(t.borderStyle.Render(t.border.Left))
} }
for i, header := range t.headers { for i, header := range t.headers {
s.WriteString(t.style(0, i). s.WriteString(t.style(HeaderRow, i).
MaxHeight(1). MaxHeight(1).
Width(t.widths[i]). Width(t.widths[i]).
MaxWidth(t.widths[i]). MaxWidth(t.widths[i]).
@ -537,7 +541,7 @@ func (t *Table) constructRow(index int, isOverflow bool) string {
cell = t.data.At(index, c) cell = t.data.At(index, c)
} }
cells = append(cells, t.style(index+1, c). cells = append(cells, t.style(index, c).
Height(height). Height(height).
MaxHeight(height). MaxHeight(height).
Width(t.widths[c]). Width(t.widths[c]).

View File

@ -7,11 +7,12 @@ import (
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/ansi"
"github.com/charmbracelet/x/exp/golden"
) )
var TableStyle = func(row, col int) lipgloss.Style { var TableStyle = func(row, col int) lipgloss.Style {
switch { switch {
case row == 0: case row == HeaderRow:
return lipgloss.NewStyle().Padding(0, 1).Align(lipgloss.Center) return lipgloss.NewStyle().Padding(0, 1).Align(lipgloss.Center)
case row%2 == 0: case row%2 == 0:
return lipgloss.NewStyle().Padding(0, 1) return lipgloss.NewStyle().Padding(0, 1)
@ -65,7 +66,7 @@ func TestTableExample(t *testing.T) {
BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("99"))). BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("99"))).
StyleFunc(func(row, col int) lipgloss.Style { StyleFunc(func(row, col int) lipgloss.Style {
switch { switch {
case row == 0: case row == HeaderRow:
return HeaderStyle return HeaderStyle
case row%2 == 0: case row%2 == 0:
return EvenRowStyle return EvenRowStyle
@ -91,8 +92,8 @@ func TestTableExample(t *testing.T) {
`) `)
if table.String() != expected { if got := ansi.Strip(table.String()); got != expected {
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, got)
} }
} }
@ -526,7 +527,7 @@ func TestTableRowSeparators(t *testing.T) {
func TestTableHeights(t *testing.T) { func TestTableHeights(t *testing.T) {
styleFunc := func(row, col int) lipgloss.Style { styleFunc := func(row, col int) lipgloss.Style {
if row == 0 { if row == HeaderRow {
return lipgloss.NewStyle().Padding(0, 1) return lipgloss.NewStyle().Padding(0, 1)
} }
if col == 0 { if col == 0 {
@ -584,7 +585,7 @@ func TestTableHeights(t *testing.T) {
func TestTableMultiLineRowSeparator(t *testing.T) { func TestTableMultiLineRowSeparator(t *testing.T) {
styleFunc := func(row, col int) lipgloss.Style { styleFunc := func(row, col int) lipgloss.Style {
if row == 0 { if row == HeaderRow {
return lipgloss.NewStyle().Padding(0, 1) return lipgloss.NewStyle().Padding(0, 1)
} }
if col == 0 { if col == 0 {
@ -1140,6 +1141,33 @@ func TestTableHeightWithOffset(t *testing.T) {
} }
} }
func TestStyleFunc(t *testing.T) {
TestStyle := func(row, col int) lipgloss.Style {
switch {
// this is the header
case row == HeaderRow:
return lipgloss.NewStyle().Align(lipgloss.Center)
// this is the first row of data
case row == 0:
return lipgloss.NewStyle().Padding(0, 1).Align(lipgloss.Right)
default:
return lipgloss.NewStyle().Padding(0, 1)
}
}
table := New().
Border(lipgloss.NormalBorder()).
StyleFunc(TestStyle).
Headers("LANGUAGE", "FORMAL", "INFORMAL").
Row("Chinese", "Nǐn hǎo", "Nǐ hǎo").
Row("French", "Bonjour", "Salut").
Row("Japanese", "こんにちは", "やあ").
Row("Russian", "Zdravstvuyte", "Privet").
Row("Spanish", "Hola", "¿Qué tal?")
golden.RequireEqual(t, []byte(table.String()))
}
func TestClearRows(t *testing.T) { func TestClearRows(t *testing.T) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {

9
table/testdata/TestStyleFunc.golden vendored Normal file
View File

@ -0,0 +1,9 @@
┌──────────┬──────────────┬───────────┐
│ LANGUAGE │ FORMAL │ INFORMAL │
├──────────┼──────────────┼───────────┤
│ Chinese │ Nǐn hǎo │ Nǐ hǎo │
│ French │ Bonjour │ Salut │
│ Japanese │ こんにちは │ やあ │
│ Russian │ Zdravstvuyte │ Privet │
│ Spanish │ Hola │ ¿Qué tal? │
└──────────┴──────────────┴───────────┘