Skip to content

Commit

Permalink
feat: auto height
Browse files Browse the repository at this point in the history
  • Loading branch information
maaslalani committed Aug 29, 2023
1 parent be9ffd1 commit 4858cf6
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 69 deletions.
10 changes: 5 additions & 5 deletions examples/table/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ const (
var (
// HeaderStyle is the lipgloss style used for the table headers.
HeaderStyle = lipgloss.NewStyle().Foreground(purple).Bold(true).Align(lipgloss.Center)
// RowStyle is the base lipgloss style used for the table rows.
RowStyle = lipgloss.NewStyle().Padding(1)
// CellStyle is the base lipgloss style used for the table rows.
CellStyle = lipgloss.NewStyle().Padding(0, 1).Width(14)
// OddRowStyle is the lipgloss style used for odd-numbered table rows.
OddRowStyle = RowStyle.Copy().Foreground(gray)
OddRowStyle = CellStyle.Copy().Foreground(gray)
// EvenRowStyle is the lipgloss style used for even-numbered table rows.
EvenRowStyle = RowStyle.Copy().Foreground(lightGray)
EvenRowStyle = CellStyle.Copy().Foreground(lightGray).Padding(1)
)

func main() {
Expand All @@ -38,7 +38,7 @@ func main() {
}
}).
Headers("LANGUAGE", "FORMAL", "INFORMAL").
Row("Chinese", "Nǐn hǎo", "Nǐ hǎo").
Row("Chinese Chinese Chinese", "Nǐn hǎo", "Nǐ hǎo").
Row("French", "Bonjour", "Salut").
Row("Japanese", "こんにけは", "やあ").
Row("Russian", "Zdravstvuyte", "Privet").
Expand Down
117 changes: 56 additions & 61 deletions table.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ type Table struct {
borderStyle Style
headers []any
rows [][]any

// widths tracks the width of each column.
widths []int

// heights tracks the height of each row.
heights []int
}

// NewTable returns a new Table that can be modified through different
Expand Down Expand Up @@ -62,60 +66,29 @@ func (t *Table) StyleFunc(style TableStyleFunc) *Table {
return t
}

// style returns the style for a cell based on it's position (row, column).
func (t *Table) style(row, col int) Style {
if t.styleFunc == nil {
return NewStyle()
}
return t.styleFunc(row, col)
}

// Rows sets the table rows.
func (t *Table) Rows(rows ...[]any) *Table {
t.rows = rows

if len(rows) == 0 {
return t
}

row := rows[0]

// Update the widths.
if t.widths == nil {
t.widths = make([]int, len(row))
}

// Update the widths.
for _, row := range rows {
for j, cell := range row {
t.widths[j] = max(t.widths[j], Width(fmt.Sprint(cell)))
}
}

return t
}

// Row appends a row of data to the table.
func (t *Table) Row(row ...any) *Table {
t.rows = append(t.rows, row)

// Update the widths.
if t.widths == nil {
t.widths = make([]int, len(row))
}

for i, cell := range row {
t.widths[i] = max(t.widths[i], Width(fmt.Sprint(cell)))
}

return t
}

// Headers sets the table headers.
func (t *Table) Headers(headers ...any) *Table {
t.headers = headers

// Update the widths.
if t.widths == nil {
t.widths = make([]int, len(headers))
}

for i, cell := range headers {
t.widths[i] = max(t.widths[i], Width(fmt.Sprint(cell)))
}

return t
}

Expand Down Expand Up @@ -169,30 +142,37 @@ func (t *Table) String() string {

var s strings.Builder

hasHeaders := len(t.headers) > 0
headers := t.headers
if !hasHeaders {
headers = t.rows[0]
}

// Initialize the widths.
t.widths = make([]int, len(headers))
t.heights = make([]int, boolToInt(hasHeaders)+len(t.rows))

// It's possible that the styling affects the width of the table or rows.
//
// It's also possible that the styling function was set after the headers
// and rows.
//
// So let's update the widths one last time.
for i, cell := range t.headers {
t.widths[i] = max(t.widths[i], Width(t.styleFunc(0, i).Render(fmt.Sprint(cell))))
t.widths[i] = max(t.widths[i], Width(t.style(0, i).Render(fmt.Sprint(cell))))
t.heights[0] = max(t.heights[0], Height(t.style(0, i).Render(fmt.Sprint(cell))))
}
for r, row := range t.rows {
for i, cell := range row {
t.widths[i] = max(t.widths[i], Width(t.styleFunc(r+1, i).Render(fmt.Sprint(cell))))
rendered := t.style(r+1, i).Render(fmt.Sprint(cell))
t.heights[r+boolToInt(hasHeaders)] = max(t.heights[r+boolToInt(hasHeaders)], Height(rendered))
t.widths[i] = max(t.widths[i], Width(rendered))
}
}

hasHeaders := len(t.headers) > 0
headers := t.headers

// Write the top border.
if t.borderTop {
s.WriteString(t.borderStyle.Render(t.border.TopLeft))
if !hasHeaders {
headers = t.rows[0]
}
for i := 0; i < len(headers); i++ {
s.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Top, t.widths[i])))
if i < len(headers)-1 {
Expand All @@ -208,11 +188,7 @@ func (t *Table) String() string {
s.WriteString(t.borderStyle.Render(t.border.Left))
}
for i, header := range t.headers {
s.WriteString(t.styleFunc(0, i).
UnsetPaddingTop().
UnsetPaddingBottom().
UnsetMarginTop().
UnsetMarginBottom().
s.WriteString(t.style(0, i).
Width(t.widths[i]).
MaxWidth(t.widths[i]).
Render(fmt.Sprint(header)))
Expand Down Expand Up @@ -250,26 +226,38 @@ func (t *Table) String() string {

// Write the data.
for r, row := range t.rows {
height := t.heights[r+boolToInt(hasHeaders)]

left := strings.Repeat(t.borderStyle.Render(t.border.Left)+"\n", height)
right := strings.Repeat(t.borderStyle.Render(t.border.Right)+"\n", height)

var cells []string
if t.borderLeft {
s.WriteString(t.borderStyle.Render(t.border.Left))
cells = append(cells, left)
}

for c, cell := range row {
s.WriteString(t.styleFunc(r+1, c).
UnsetPaddingTop().
UnsetPaddingBottom().
UnsetMarginTop().
UnsetMarginBottom().
cells = append(cells, t.style(r+1, c).
Height(height).
MaxHeight(height).
Width(t.widths[c]).
MaxWidth(t.widths[c]).
Render(fmt.Sprint(cell)))

if c < len(row)-1 {
s.WriteString(t.borderStyle.Render(t.border.Left))
cells = append(cells, left)
}
}

if t.borderRight {
s.WriteString(t.borderStyle.Render(t.border.Right))
cells = append(cells, right)
}
s.WriteString("\n")

for i, cell := range cells {
cells[i] = strings.TrimRight(cell, "\n")
}

s.WriteString(JoinHorizontal(Top, cells...) + "\n")
}

// Write the bottom border.
Expand All @@ -291,3 +279,10 @@ func (t *Table) String() string {
func (t *Table) Render() string {
return t.String()
}

func boolToInt(b bool) int {
if b {
return 1
}
return 0
}
60 changes: 57 additions & 3 deletions table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import (
var TableStyle = func(row, col int) Style {
switch {
case row == 0:
return NewStyle().Padding(1).Align(Center)
return NewStyle().Padding(0, 1).Align(Center)
case row%2 == 0:
return NewStyle().Padding(1)
return NewStyle().Padding(0, 1)
default:
return NewStyle().Padding(1)
return NewStyle().Padding(0, 1)
}
}

Expand Down Expand Up @@ -224,3 +224,57 @@ func TestTableUnsetHeaderSeparatorWithBorder(t *testing.T) {
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
}
}

func TestTableHeights(t *testing.T) {
styleFunc := func(row, col int) Style {
if row == 0 {
return NewStyle().Bold(true).Padding(0, 1)
}
if col == 0 {
return NewStyle().Width(18).Padding(1)
}
return NewStyle().Width(25).Padding(1, 2)
}

table := NewTable().
Border(NormalBorder()).
StyleFunc(styleFunc).
Headers("EXPRESSION", "MEANING").
Row("Chutar o balde", `Literally translates to "kick the bucket." It's used when someone gives up or loses patience.`).
Row("Engolir sapos", `Literally means "to swallow frogs." It's used to describe someone who has to tolerate or endure unpleasant situations.`).
Row("Arroz de festa", `Literally means "party rice." ItΒ΄s used to refer to someone who shows up everywhere.`)

expected := strings.TrimSpace(`
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ EXPRESSION β”‚ MEANING β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚ β”‚
β”‚ Chutar o balde β”‚ Literally translates β”‚
β”‚ β”‚ to "kick the bucket." β”‚
β”‚ β”‚ It's used when β”‚
β”‚ β”‚ someone gives up or β”‚
β”‚ β”‚ loses patience. β”‚
β”‚ β”‚ β”‚
β”‚ β”‚ β”‚
β”‚ Engolir sapos β”‚ Literally means "to β”‚
β”‚ β”‚ swallow frogs." It's β”‚
β”‚ β”‚ used to describe β”‚
β”‚ β”‚ someone who has to β”‚
β”‚ β”‚ tolerate or endure β”‚
β”‚ β”‚ unpleasant β”‚
β”‚ β”‚ situations. β”‚
β”‚ β”‚ β”‚
β”‚ β”‚ β”‚
β”‚ Arroz de festa β”‚ Literally means β”‚
β”‚ β”‚ "party rice." ItΒ΄s β”‚
β”‚ β”‚ used to refer to β”‚
β”‚ β”‚ someone who shows up β”‚
β”‚ β”‚ everywhere. β”‚
β”‚ β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
`)

if table.String() != expected {
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String())
}
}

0 comments on commit 4858cf6

Please sign in to comment.