440 lines
9.9 KiB
Go
440 lines
9.9 KiB
Go
package table
|
|
|
|
import "github.com/charmbracelet/lipgloss"
|
|
|
|
// Border defines the borders in and around the table.
|
|
type Border struct {
|
|
Top string
|
|
Left string
|
|
Right string
|
|
Bottom string
|
|
TopRight string
|
|
TopLeft string
|
|
BottomRight string
|
|
BottomLeft string
|
|
|
|
TopJunction string
|
|
LeftJunction string
|
|
RightJunction string
|
|
BottomJunction string
|
|
|
|
InnerJunction string
|
|
|
|
InnerDivider string
|
|
|
|
// Styles for 2x2 tables and larger
|
|
styleMultiTopLeft lipgloss.Style
|
|
styleMultiTop lipgloss.Style
|
|
styleMultiTopRight lipgloss.Style
|
|
styleMultiRight lipgloss.Style
|
|
styleMultiBottomRight lipgloss.Style
|
|
styleMultiBottom lipgloss.Style
|
|
styleMultiBottomLeft lipgloss.Style
|
|
styleMultiLeft lipgloss.Style
|
|
styleMultiInner lipgloss.Style
|
|
|
|
// Styles for a single column table
|
|
styleSingleColumnTop lipgloss.Style
|
|
styleSingleColumnInner lipgloss.Style
|
|
styleSingleColumnBottom lipgloss.Style
|
|
|
|
// Styles for a single row table
|
|
styleSingleRowLeft lipgloss.Style
|
|
styleSingleRowInner lipgloss.Style
|
|
styleSingleRowRight lipgloss.Style
|
|
|
|
// Style for a table with only one cell
|
|
styleSingleCell lipgloss.Style
|
|
|
|
// Style for the footer
|
|
styleFooter lipgloss.Style
|
|
}
|
|
|
|
var (
|
|
// https://www.w3.org/TR/xml-entity-names/025.html
|
|
|
|
borderDefault = Border{
|
|
Top: "━",
|
|
Left: "┃",
|
|
Right: "┃",
|
|
Bottom: "━",
|
|
|
|
TopRight: "┓",
|
|
TopLeft: "┏",
|
|
BottomRight: "┛",
|
|
BottomLeft: "┗",
|
|
|
|
TopJunction: "┳",
|
|
LeftJunction: "┣",
|
|
RightJunction: "┫",
|
|
BottomJunction: "┻",
|
|
InnerJunction: "╋",
|
|
|
|
InnerDivider: "┃",
|
|
}
|
|
|
|
borderRounded = Border{
|
|
Top: "─",
|
|
Left: "│",
|
|
Right: "│",
|
|
Bottom: "─",
|
|
|
|
TopRight: "╮",
|
|
TopLeft: "╭",
|
|
BottomRight: "╯",
|
|
BottomLeft: "╰",
|
|
|
|
TopJunction: "┬",
|
|
LeftJunction: "├",
|
|
RightJunction: "┤",
|
|
BottomJunction: "┴",
|
|
InnerJunction: "┼",
|
|
|
|
InnerDivider: "│",
|
|
}
|
|
)
|
|
|
|
func init() {
|
|
borderDefault.generateStyles()
|
|
borderRounded.generateStyles()
|
|
}
|
|
|
|
func (b *Border) generateStyles() {
|
|
b.generateMultiStyles()
|
|
b.generateSingleColumnStyles()
|
|
b.generateSingleRowStyles()
|
|
b.generateSingleCellStyle()
|
|
|
|
// The footer is a single cell with the top taken off... usually. We can
|
|
// re-enable the top if needed this way for certain format configurations.
|
|
b.styleFooter = b.styleSingleCell.Copy().
|
|
Align(lipgloss.Right).
|
|
BorderBottom(true).
|
|
BorderRight(true).
|
|
BorderLeft(true)
|
|
}
|
|
|
|
func (b *Border) styleLeftWithFooter(original lipgloss.Style) lipgloss.Style {
|
|
border := original.GetBorderStyle()
|
|
|
|
border.BottomLeft = b.LeftJunction
|
|
|
|
return original.Copy().BorderStyle(border)
|
|
}
|
|
|
|
func (b *Border) styleRightWithFooter(original lipgloss.Style) lipgloss.Style {
|
|
border := original.GetBorderStyle()
|
|
|
|
border.BottomRight = b.RightJunction
|
|
|
|
return original.Copy().BorderStyle(border)
|
|
}
|
|
|
|
func (b *Border) styleBothWithFooter(original lipgloss.Style) lipgloss.Style {
|
|
border := original.GetBorderStyle()
|
|
|
|
border.BottomLeft = b.LeftJunction
|
|
border.BottomRight = b.RightJunction
|
|
|
|
return original.Copy().BorderStyle(border)
|
|
}
|
|
|
|
// This function is long, but it's just repetitive...
|
|
//
|
|
//nolint:funlen
|
|
func (b *Border) generateMultiStyles() {
|
|
b.styleMultiTopLeft = lipgloss.NewStyle().BorderStyle(
|
|
lipgloss.Border{
|
|
TopLeft: b.TopLeft,
|
|
Top: b.Top,
|
|
TopRight: b.TopJunction,
|
|
Right: b.InnerDivider,
|
|
BottomRight: b.InnerJunction,
|
|
Bottom: b.Bottom,
|
|
BottomLeft: b.LeftJunction,
|
|
Left: b.Left,
|
|
},
|
|
)
|
|
|
|
b.styleMultiTop = lipgloss.NewStyle().BorderStyle(
|
|
lipgloss.Border{
|
|
Top: b.Top,
|
|
Right: b.InnerDivider,
|
|
Bottom: b.Bottom,
|
|
|
|
TopRight: b.TopJunction,
|
|
BottomRight: b.InnerJunction,
|
|
},
|
|
).BorderTop(true).BorderBottom(true).BorderRight(true)
|
|
|
|
b.styleMultiTopRight = lipgloss.NewStyle().BorderStyle(
|
|
lipgloss.Border{
|
|
Top: b.Top,
|
|
Right: b.Right,
|
|
Bottom: b.Bottom,
|
|
|
|
TopRight: b.TopRight,
|
|
BottomRight: b.RightJunction,
|
|
},
|
|
).BorderTop(true).BorderBottom(true).BorderRight(true)
|
|
|
|
b.styleMultiLeft = lipgloss.NewStyle().BorderStyle(
|
|
lipgloss.Border{
|
|
Left: b.Left,
|
|
Right: b.InnerDivider,
|
|
},
|
|
).BorderRight(true).BorderLeft(true)
|
|
|
|
b.styleMultiRight = lipgloss.NewStyle().BorderStyle(
|
|
lipgloss.Border{
|
|
Right: b.Right,
|
|
},
|
|
).BorderRight(true)
|
|
|
|
b.styleMultiInner = lipgloss.NewStyle().BorderStyle(
|
|
lipgloss.Border{
|
|
Right: b.InnerDivider,
|
|
},
|
|
).BorderRight(true)
|
|
|
|
b.styleMultiBottomLeft = lipgloss.NewStyle().BorderStyle(
|
|
lipgloss.Border{
|
|
Left: b.Left,
|
|
Right: b.InnerDivider,
|
|
Bottom: b.Bottom,
|
|
|
|
BottomLeft: b.BottomLeft,
|
|
BottomRight: b.BottomJunction,
|
|
},
|
|
).BorderLeft(true).BorderBottom(true).BorderRight(true)
|
|
|
|
b.styleMultiBottom = lipgloss.NewStyle().BorderStyle(
|
|
lipgloss.Border{
|
|
Right: b.InnerDivider,
|
|
Bottom: b.Bottom,
|
|
|
|
BottomRight: b.BottomJunction,
|
|
},
|
|
).BorderBottom(true).BorderRight(true)
|
|
|
|
b.styleMultiBottomRight = lipgloss.NewStyle().BorderStyle(
|
|
lipgloss.Border{
|
|
Right: b.Right,
|
|
Bottom: b.Bottom,
|
|
|
|
BottomRight: b.BottomRight,
|
|
},
|
|
).BorderBottom(true).BorderRight(true)
|
|
}
|
|
|
|
func (b *Border) generateSingleColumnStyles() {
|
|
b.styleSingleColumnTop = lipgloss.NewStyle().BorderStyle(
|
|
lipgloss.Border{
|
|
Top: b.Top,
|
|
Left: b.Left,
|
|
Right: b.Right,
|
|
Bottom: b.Bottom,
|
|
|
|
TopLeft: b.TopLeft,
|
|
TopRight: b.TopRight,
|
|
BottomLeft: b.LeftJunction,
|
|
BottomRight: b.RightJunction,
|
|
},
|
|
)
|
|
|
|
b.styleSingleColumnInner = lipgloss.NewStyle().BorderStyle(
|
|
lipgloss.Border{
|
|
Left: b.Left,
|
|
Right: b.Right,
|
|
},
|
|
).BorderRight(true).BorderLeft(true)
|
|
|
|
b.styleSingleColumnBottom = lipgloss.NewStyle().BorderStyle(
|
|
lipgloss.Border{
|
|
Left: b.Left,
|
|
Right: b.Right,
|
|
Bottom: b.Bottom,
|
|
|
|
BottomLeft: b.BottomLeft,
|
|
BottomRight: b.BottomRight,
|
|
},
|
|
).BorderRight(true).BorderLeft(true).BorderBottom(true)
|
|
}
|
|
|
|
func (b *Border) generateSingleRowStyles() {
|
|
b.styleSingleRowLeft = lipgloss.NewStyle().BorderStyle(
|
|
lipgloss.Border{
|
|
Top: b.Top,
|
|
Left: b.Left,
|
|
Right: b.InnerDivider,
|
|
Bottom: b.Bottom,
|
|
|
|
BottomLeft: b.BottomLeft,
|
|
BottomRight: b.BottomJunction,
|
|
TopRight: b.TopJunction,
|
|
TopLeft: b.TopLeft,
|
|
},
|
|
)
|
|
|
|
b.styleSingleRowInner = lipgloss.NewStyle().BorderStyle(
|
|
lipgloss.Border{
|
|
Top: b.Top,
|
|
Right: b.InnerDivider,
|
|
Bottom: b.Bottom,
|
|
|
|
BottomRight: b.BottomJunction,
|
|
TopRight: b.TopJunction,
|
|
},
|
|
).BorderTop(true).BorderBottom(true).BorderRight(true)
|
|
|
|
b.styleSingleRowRight = lipgloss.NewStyle().BorderStyle(
|
|
lipgloss.Border{
|
|
Top: b.Top,
|
|
Right: b.Right,
|
|
Bottom: b.Bottom,
|
|
|
|
BottomRight: b.BottomRight,
|
|
TopRight: b.TopRight,
|
|
},
|
|
).BorderTop(true).BorderBottom(true).BorderRight(true)
|
|
}
|
|
|
|
func (b *Border) generateSingleCellStyle() {
|
|
b.styleSingleCell = lipgloss.NewStyle().BorderStyle(
|
|
lipgloss.Border{
|
|
Top: b.Top,
|
|
Left: b.Left,
|
|
Right: b.Right,
|
|
Bottom: b.Bottom,
|
|
|
|
BottomLeft: b.BottomLeft,
|
|
BottomRight: b.BottomRight,
|
|
TopRight: b.TopRight,
|
|
TopLeft: b.TopLeft,
|
|
},
|
|
)
|
|
}
|
|
|
|
// BorderDefault uses the basic square border, useful to reset the border if
|
|
// it was changed somehow.
|
|
func (m Model) BorderDefault() Model {
|
|
// Already generated styles
|
|
m.border = borderDefault
|
|
|
|
return m
|
|
}
|
|
|
|
// BorderRounded uses a thin, rounded border.
|
|
func (m Model) BorderRounded() Model {
|
|
// Already generated styles
|
|
m.border = borderRounded
|
|
|
|
return m
|
|
}
|
|
|
|
// Border uses the given border components to render the table.
|
|
func (m Model) Border(border Border) Model {
|
|
border.generateStyles()
|
|
|
|
m.border = border
|
|
|
|
return m
|
|
}
|
|
|
|
type borderStyleRow struct {
|
|
left lipgloss.Style
|
|
inner lipgloss.Style
|
|
right lipgloss.Style
|
|
}
|
|
|
|
func (b *borderStyleRow) inherit(s lipgloss.Style) {
|
|
b.left = b.left.Copy().Inherit(s)
|
|
b.inner = b.inner.Copy().Inherit(s)
|
|
b.right = b.right.Copy().Inherit(s)
|
|
}
|
|
|
|
// There's a lot of branches here, but splitting it up further would make it
|
|
// harder to follow. So just be careful with comments and make sure it's tested!
|
|
//
|
|
//nolint:nestif
|
|
func (m Model) styleHeaders() borderStyleRow {
|
|
hasRows := len(m.GetVisibleRows()) > 0 || m.calculatePadding(0) > 0
|
|
singleColumn := len(m.columns) == 1
|
|
styles := borderStyleRow{}
|
|
|
|
// Possible configurations:
|
|
// - Single cell
|
|
// - Single row
|
|
// - Single column
|
|
// - Multi
|
|
|
|
if singleColumn {
|
|
if hasRows {
|
|
// Single column
|
|
styles.left = m.border.styleSingleColumnTop
|
|
styles.inner = styles.left
|
|
styles.right = styles.left
|
|
} else {
|
|
// Single cell
|
|
styles.left = m.border.styleSingleCell
|
|
styles.inner = styles.left
|
|
styles.right = styles.left
|
|
|
|
if m.hasFooter() {
|
|
styles.left = m.border.styleBothWithFooter(styles.left)
|
|
}
|
|
}
|
|
} else if !hasRows {
|
|
// Single row
|
|
styles.left = m.border.styleSingleRowLeft
|
|
styles.inner = m.border.styleSingleRowInner
|
|
styles.right = m.border.styleSingleRowRight
|
|
|
|
if m.hasFooter() {
|
|
styles.left = m.border.styleLeftWithFooter(styles.left)
|
|
styles.right = m.border.styleRightWithFooter(styles.right)
|
|
}
|
|
} else {
|
|
// Multi
|
|
styles.left = m.border.styleMultiTopLeft
|
|
styles.inner = m.border.styleMultiTop
|
|
styles.right = m.border.styleMultiTopRight
|
|
}
|
|
|
|
styles.inherit(m.headerStyle)
|
|
|
|
return styles
|
|
}
|
|
|
|
func (m Model) styleRows() (inner borderStyleRow, last borderStyleRow) {
|
|
if len(m.columns) == 1 {
|
|
inner.left = m.border.styleSingleColumnInner
|
|
inner.inner = inner.left
|
|
inner.right = inner.left
|
|
|
|
last.left = m.border.styleSingleColumnBottom
|
|
|
|
if m.hasFooter() {
|
|
last.left = m.border.styleBothWithFooter(last.left)
|
|
}
|
|
|
|
last.inner = last.left
|
|
last.right = last.left
|
|
} else {
|
|
inner.left = m.border.styleMultiLeft
|
|
inner.inner = m.border.styleMultiInner
|
|
inner.right = m.border.styleMultiRight
|
|
|
|
last.left = m.border.styleMultiBottomLeft
|
|
last.inner = m.border.styleMultiBottom
|
|
last.right = m.border.styleMultiBottomRight
|
|
|
|
if m.hasFooter() {
|
|
last.left = m.border.styleLeftWithFooter(last.left)
|
|
last.right = m.border.styleRightWithFooter(last.right)
|
|
}
|
|
}
|
|
|
|
return inner, last
|
|
}
|