Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions table/resize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package table

func (m Model) cellFrameWidth() int {
return max(
m.styles.Header.GetHorizontalFrameSize(),
m.styles.Cell.GetHorizontalFrameSize(),
)
}

func (m Model) effectiveColumnWidths() []int {
widths := make([]int, len(m.cols))
for i, col := range m.cols {
widths[i] = col.Width
}
if m.tableWidth <= 0 {
return widths
}

return resizeColumnWidths(widths, m.tableWidth, m.cellFrameWidth())
}

func resizeColumnWidths(widths []int, target, frame int) []int {
out := append([]int(nil), widths...)

var indices []int
for i, w := range out {
if w > 0 {
indices = append(indices, i)
}
}

n := len(indices)
if n == 0 {
return out
}

sum := 0
for _, i := range indices {
sum += out[i] + frame
}
if sum == target {
return out
}

contentTarget := target - n*frame
if contentTarget < n {
contentTarget = n
}

contentSum := 0
for _, i := range indices {
contentSum += out[i]
}

switch {
case contentSum < contentTarget:
extra := contentTarget - contentSum
for _, i := range indices {
add := extra * out[i] / contentSum
out[i] += add
extra -= add
}
for j := 0; extra > 0; j++ {
out[indices[j%len(indices)]]++
extra--
}
case contentSum > contentTarget:
remaining := contentTarget
for k, i := range indices {
if k == len(indices)-1 {
out[i] = max(1, remaining)
break
}
w := max(1, out[i]*contentTarget/contentSum)
out[i] = w
remaining -= w
}
}

for {
sum = 0
for _, i := range indices {
sum += out[i] + frame
}
if sum == target {
break
}
if sum < target {
out[indices[0]]++
continue
}
shrunk := false
for _, i := range indices {
if out[i] > 1 {
out[i]--
shrunk = true
break
}
}
if !shrunk {
break
}
}

return out
}
44 changes: 44 additions & 0 deletions table/resize_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package table

import "testing"

func TestResizeColumnWidthsExpand(t *testing.T) {
got := resizeColumnWidths([]int{25, 16, 12}, 80, 2)
sum := 0
for _, w := range got {
sum += w + 2
}
if sum != 80 {
t.Fatalf("total width %d, want 80, widths %v", sum, got)
}
}

func TestResizeColumnWidthsShrink(t *testing.T) {
got := resizeColumnWidths([]int{25, 16, 12}, 30, 2)
sum := 0
for _, w := range got {
sum += w + 2
}
if sum != 30 {
t.Fatalf("total width %d, want 30, widths %v", sum, got)
}
}

func TestModelViewWidthMatchesTableWidth(t *testing.T) {
m := New(
WithWidth(80),
WithColumns([]Column{
{Title: "Name", Width: 25},
{Title: "Country", Width: 16},
{Title: "Dunk", Width: 12},
}),
WithRows([]Row{{"foo", "UK", "Yes"}}),
)

if got := m.naturalWidth(); got != 59 {
t.Fatalf("naturalWidth %d want 59", got)
}
if w := m.Width(); w != 80 {
t.Fatalf("Width() %d want 80", w)
}
}
42 changes: 32 additions & 10 deletions table/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ type Model struct {
viewport viewport.Model
start int
end int

tableWidth int
}

// Row represents one line in the table.
Expand Down Expand Up @@ -173,7 +175,7 @@ func WithHeight(h int) Option {
// WithWidth sets the width of the table.
func WithWidth(w int) Option {
return func(m *Model) {
m.viewport.SetWidth(w)
m.SetWidth(w)
}
}

Expand Down Expand Up @@ -319,12 +321,28 @@ func (m *Model) SetColumns(c []Column) {
m.UpdateViewport()
}

// SetWidth sets the width of the viewport of the table.
// SetWidth sets the total rendered width of the table.
func (m *Model) SetWidth(w int) {
m.viewport.SetWidth(w)
m.tableWidth = w
if w > 0 {
m.viewport.SetWidth(w)
} else {
m.viewport.SetWidth(m.naturalWidth())
}
m.UpdateViewport()
}

func (m Model) naturalWidth() int {
frame := m.cellFrameWidth()
total := 0
for _, col := range m.cols {
if col.Width > 0 {
total += col.Width + frame
}
}
return total
}

// SetHeight sets the height of the viewport of the table.
func (m *Model) SetHeight(h int) {
m.viewport.SetHeight(h - lipgloss.Height(m.headersView()))
Expand Down Expand Up @@ -416,26 +434,30 @@ func (m *Model) FromValues(value, separator string) {
}

func (m Model) headersView() string {
widths := m.effectiveColumnWidths()
s := make([]string, 0, len(m.cols))
for _, col := range m.cols {
if col.Width <= 0 {
for i, col := range m.cols {
w := widths[i]
if w <= 0 {
continue
}
style := lipgloss.NewStyle().Width(col.Width).MaxWidth(col.Width).Inline(true)
renderedCell := style.Render(ansi.Truncate(col.Title, col.Width, "…"))
style := lipgloss.NewStyle().Width(w).MaxWidth(w).Inline(true)
renderedCell := style.Render(ansi.Truncate(col.Title, w, "…"))
s = append(s, m.styles.Header.Render(renderedCell))
}
return lipgloss.JoinHorizontal(lipgloss.Top, s...)
}

func (m *Model) renderRow(r int) string {
widths := m.effectiveColumnWidths()
s := make([]string, 0, len(m.cols))
for i, value := range m.rows[r] {
if m.cols[i].Width <= 0 {
w := widths[i]
if w <= 0 {
continue
}
style := lipgloss.NewStyle().Width(m.cols[i].Width).MaxWidth(m.cols[i].Width).Inline(true)
renderedCell := m.styles.Cell.Render(style.Render(ansi.Truncate(value, m.cols[i].Width, "…")))
style := lipgloss.NewStyle().Width(w).MaxWidth(w).Inline(true)
renderedCell := m.styles.Cell.Render(style.Render(ansi.Truncate(value, w, "…")))
s = append(s, renderedCell)
}

Expand Down
2 changes: 1 addition & 1 deletion table/table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ func TestNew(t *testing.T) {
viewport.WithWidth(10),
viewport.WithHeight(20),
),
tableWidth: 10,
},
},
"WithFocused": {
Expand Down Expand Up @@ -737,7 +738,6 @@ func TestModel_View(t *testing.T) {
}),
)
},
skip: true,
},
"Modified viewport height": {
modelFunc: func() Model {
Expand Down
14 changes: 7 additions & 7 deletions table/testdata/TestModel_View/Extra_padding.golden
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@


Name Country of Orig… Dunk-able




Chocolate Digestives UK Yes
Name Country of Or… Dunk-able




Tim Tams Australia No
Chocolate Digestives UK Yes




Tim Tams Australia No

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Name Country of Orig… Dunk-able
Chocolate Digestives UK Yes
Tim Tams Australia No
Hobnobs UK Yes
Name Country of Origin Dunk-able
Chocolate Digestives UK Yes
Tim Tams Australia No
Hobnobs UK Yes



Expand Down
14 changes: 4 additions & 10 deletions table/testdata/TestModel_View/Width_less_than_columns.golden
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Name Country of Origin Dunk-able
Chocolate Digestives UK Yes
Tim Tams Australia No
Hobnobs UK Yes
Name Countr… Dunk-
Chocolate UK Yes
Tim Tams Austra… No
Hobnobs UK Yes



Expand All @@ -12,10 +12,4 @@