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
8 changes: 5 additions & 3 deletions examples/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module examples

go 1.19
go 1.23.0

toolchain go1.24.1

replace github.com/charmbracelet/lipgloss => ../

Expand All @@ -21,7 +23,7 @@ require (
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/bubbletea v0.25.0 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/colorprofile v0.3.0 // indirect
github.com/charmbracelet/keygen v0.5.0 // indirect
github.com/charmbracelet/log v0.4.0 // indirect
github.com/charmbracelet/x/ansi v0.8.0 // indirect
Expand All @@ -45,6 +47,6 @@ require (
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.22.0 // indirect
)
16 changes: 11 additions & 5 deletions examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/colorprofile v0.3.0 h1:KtLh9uuu1RCt+Hml4s6Hz+kB1PfV3wi++1h5ia65yKQ=
github.com/charmbracelet/colorprofile v0.3.0/go.mod h1:oHJ340RS2nmG1zRGPmhJKJ/jf4FPNNk0P39/wBPA1G0=
github.com/charmbracelet/keygen v0.5.0 h1:XY0fsoYiCSM9axkrU+2ziE6u6YjJulo/b9Dghnw6MZc=
github.com/charmbracelet/keygen v0.5.0/go.mod h1:DfvCgLHxZ9rJxdK0DGw3C/LkV4SgdGbnliHcObV3L+8=
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
Expand All @@ -21,7 +22,8 @@ github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/errors v0.0.0-20240117030013-d31dba354651 h1:3RXpZWGWTOeVXCTv0Dnzxdv/MhNUkBfEcbaTY0zrTQI=
github.com/charmbracelet/x/errors v0.0.0-20240117030013-d31dba354651/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
github.com/charmbracelet/x/exp/golden v0.0.0-20250416155516-c8c095191e0d h1:CcFJvHqALQYa7qFm8Y5S3yWSwIQEPKD9E0e/iz5O2yw=
github.com/charmbracelet/x/exp/golden v0.0.0-20250416155516-c8c095191e0d/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd h1:HqBjkSFXXfW4IgX3TMKipWoPEN08T3Pi4SA/3DLss/U=
github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd/go.mod h1:6GZ13FjIP6eOCqWU4lqgveGnYxQo9c3qBzHPeFu4HBE=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
Expand All @@ -31,6 +33,7 @@ github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:Yyn
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
Expand Down Expand Up @@ -59,11 +62,13 @@ github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKt
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
Expand All @@ -78,11 +83,12 @@ golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
10 changes: 6 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,26 @@ retract v0.7.0 // v0.7.0 introduces a bug that causes some apps to freeze.

retract v0.11.1 // v0.11.1 uses a broken version of x/ansi StringWidth that causes some lines to wrap incorrectly.

go 1.18
go 1.23.0

toolchain go1.24.1

require (
github.com/aymanbagabas/go-udiff v0.2.0
github.com/charmbracelet/x/ansi v0.8.0
github.com/charmbracelet/x/cellbuf v0.0.13
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a
github.com/charmbracelet/x/exp/golden v0.0.0-20250416155516-c8c095191e0d
github.com/muesli/termenv v0.16.0
github.com/rivo/uniseg v0.4.7
)

require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/colorprofile v0.3.0 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/sys v0.32.0 // indirect
)
13 changes: 7 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/colorprofile v0.3.0 h1:KtLh9uuu1RCt+Hml4s6Hz+kB1PfV3wi++1h5ia65yKQ=
github.com/charmbracelet/colorprofile v0.3.0/go.mod h1:oHJ340RS2nmG1zRGPmhJKJ/jf4FPNNk0P39/wBPA1G0=
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/exp/golden v0.0.0-20250416155516-c8c095191e0d h1:CcFJvHqALQYa7qFm8Y5S3yWSwIQEPKD9E0e/iz5O2yw=
github.com/charmbracelet/x/exp/golden v0.0.0-20250416155516-c8c095191e0d/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
Expand All @@ -26,6 +26,7 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
135 changes: 104 additions & 31 deletions table/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,24 @@
package table

import (
"slices"
"strings"

"github.com/charmbracelet/lipgloss"
"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
const (
// HeaderRow denotes the header's row index used when rendering headers. Use
// this value when looking to customize header styles in StyleFunc.
HeaderRow int = -1
// FirstCol stores the index of the first column in a table.
FirstCol int = 0
// NoBorder is used to fill a gap with repeated spaces instead of drawing a border.
NoBorder string = " "
// EmptyCell is used to clear a cell value.
EmptyCell string = ""
)

// StyleFunc is the style function that determines the style of a Cell.
//
Expand Down Expand Up @@ -46,13 +55,14 @@ type Table struct {
styleFunc StyleFunc
border lipgloss.Border

borderTop bool
borderBottom bool
borderLeft bool
borderRight bool
borderHeader bool
borderColumn bool
borderRow bool
borderTop bool
borderBottom bool
borderLeft bool
borderRight bool
borderHeader bool
borderColumn bool
borderRow bool
borderMergeColumns []int

borderStyle lipgloss.Style
headers []string
Expand Down Expand Up @@ -190,6 +200,16 @@ func (t *Table) BorderRow(v bool) *Table {
return t
}

// BorderColumnMerge sets the columns where adjacent vertical cells that contain
// the same data get merged into a single cell.
// Prerequesite: BorderRow and BorderColumn must be set to true.
func (t *Table) BorderMergeColumns(cols ...int) *Table {
if t.borderRow && t.borderColumn {
t.borderMergeColumns = cols
}
return t
}

// BorderStyle sets the style for the table border.
func (t *Table) BorderStyle(style lipgloss.Style) *Table {
t.borderStyle = style
Expand Down Expand Up @@ -426,14 +446,11 @@ func (t *Table) constructRows(availableLines int) string {
return sb.String()
}

// constructRow constructs the row for the table given an index and row data
// based on the current configuration. If isOverflow is true, the row is
// rendered as an overflow row (using ellipsis).
func (t *Table) constructRow(index int, isOverflow bool) string {
func (t *Table) constructRow(row int, isOverflow bool) string {
var s strings.Builder

hasHeaders := len(t.headers) > 0
height := t.heights[index+btoi(hasHeaders)]
height := t.heights[row+btoi(hasHeaders)]
if isOverflow {
height = 1
}
Expand All @@ -444,25 +461,33 @@ func (t *Table) constructRow(index int, isOverflow bool) string {
cells = append(cells, left)
}

for c := 0; c < t.data.Columns(); c++ {
verticalCellsEqual := func(row, col int) bool { return t.data.At(row-1, col) == t.data.At(row, col) }
modifyCell := func(col int, cell string) string {
if row > 0 && slices.Contains(t.borderMergeColumns, col) && verticalCellsEqual(row, col) {
return EmptyCell // if merging column, clear cells with identical data excluding the first occurrence
}
return cell
}

for col := 0; col < t.data.Columns(); col++ {
cell := "…"
if !isOverflow {
cell = t.data.At(index, c)
cell = t.data.At(row, col)
}

cellStyle := t.style(index, c)
cellStyle := t.style(row, col)
if !t.wrap {
cell = t.truncateCell(cell, index, c)
cell = t.truncateCell(cell, row, col)
}
cells = append(cells, cellStyle.
// Account for the margins in the cell sizing.
Height(height-cellStyle.GetVerticalMargins()).
MaxHeight(height).
Width(t.widths[c]-cellStyle.GetHorizontalMargins()).
MaxWidth(t.widths[c]).
Render(cell))
Width(t.widths[col]-cellStyle.GetHorizontalMargins()).
MaxWidth(t.widths[col]).
Render(modifyCell(col, cell)))

if c < t.data.Columns()-1 && t.borderColumn {
if col < t.data.Columns()-1 && t.borderColumn {
cells = append(cells, left)
}
}
Expand All @@ -478,18 +503,66 @@ func (t *Table) constructRow(index int, isOverflow bool) string {

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

if t.borderRow && index < t.data.Rows()-1 && !isOverflow {
t.drawRowBorders(&s, row, isOverflow)

return s.String()
}

// Draws the borders separating rows for singular row.
func (t *Table) drawRowBorders(s *strings.Builder, row int, isOverflow bool) {
if t.borderRow && row < t.data.Rows()-1 && !isOverflow {
t.drawLeftmostBorder(s, row)
t.drawMiddleBorders(s, row)
t.drawRightmostBorders(s, row)
}
}

// Draws the leftmost border intersection for a singular row.
func (t *Table) drawLeftmostBorder(s *strings.Builder, row int) {
if slices.Contains(t.borderMergeColumns, FirstCol) && t.verticalCellsEqual(row, FirstCol) {
s.WriteString(t.borderStyle.Render(t.border.Left))
} else {
s.WriteString(t.borderStyle.Render(t.border.MiddleLeft))
for i := 0; i < len(t.widths); i++ {
s.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Bottom, t.widths[i])))
if i < len(t.widths)-1 && t.borderColumn {
s.WriteString(t.borderStyle.Render(t.border.Middle))
}
}
}

// Draws all the horizontal and intersection borders for a singular row, exluding the last column.
func (t *Table) drawMiddleBorders(s *strings.Builder, row int) {
for col := 0; col < len(t.widths)-1; col++ {
if !t.borderColumn {
s.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Bottom, t.widths[col])))
} else if slices.Contains(t.borderMergeColumns, col) && slices.Contains(t.borderMergeColumns, col+1) &&
t.verticalCellsEqual(row, col) && t.verticalCellsEqual(row, col+1) {
s.WriteString(t.borderStyle.Render(strings.Repeat(NoBorder, t.widths[col])))
s.WriteString(t.borderStyle.Render(t.border.Left)) // |
} else if slices.Contains(t.borderMergeColumns, col) && t.verticalCellsEqual(row, col) {
s.WriteString(t.borderStyle.Render(strings.Repeat(NoBorder, t.widths[col])))
s.WriteString(t.borderStyle.Render(t.border.MiddleLeft)) // |-
} else if slices.Contains(t.borderMergeColumns, col+1) && t.verticalCellsEqual(row, col+1) {
s.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Bottom, t.widths[col])))
s.WriteString(t.borderStyle.Render(t.border.MiddleRight)) // -|
} else {
s.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Bottom, t.widths[col])))
s.WriteString(t.borderStyle.Render(t.border.Middle)) // -|-
}
s.WriteString(t.borderStyle.Render(t.border.MiddleRight) + "\n")
}
}

return s.String()
// Draws the rightmost column's horizontal and intersection border for a singular row.
func (t *Table) drawRightmostBorders(s *strings.Builder, row int) {
lastCol := len(t.widths) - 1
if slices.Contains(t.borderMergeColumns, lastCol) && t.verticalCellsEqual(row, lastCol) {
s.WriteString(t.borderStyle.Render(strings.Repeat(NoBorder, t.widths[lastCol])))
s.WriteString(t.borderStyle.Render(t.border.Right) + "\n") // |
} else {
s.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Bottom, t.widths[lastCol])))
s.WriteString(t.borderStyle.Render(t.border.MiddleRight) + "\n") // -|
}
}

// Returns true if the cell at (row, col) and the cell below it contain the same data.
func (t *Table) verticalCellsEqual(row, col int) bool {
return t.data.At(row, col) == t.data.At(row+1, col)
}

func (t *Table) truncateCell(cell string, rowIndex, colIndex int) string {
Expand Down
Loading