From 8785c218a591dadc61f371c03dcdc835cf5e31a3 Mon Sep 17 00:00:00 2001 From: Brandon Fulljames Date: Mon, 14 Feb 2022 01:18:20 +0900 Subject: [PATCH] Rename header to column for clarity (#9) Also includes some basic documentation. --- examples/dimensions/main.go | 6 ++--- examples/features/main.go | 12 ++++----- table/border.go | 3 +++ table/{header.go => column.go} | 8 +++--- table/row.go | 22 +++++++++------- table/table.go | 48 ++++++++++++++++++++++------------ table/table_test.go | 10 +++---- 7 files changed, 66 insertions(+), 43 deletions(-) rename table/{header.go => column.go} (51%) diff --git a/examples/dimensions/main.go b/examples/dimensions/main.go index edbf432..3c7c182 100644 --- a/examples/dimensions/main.go +++ b/examples/dimensions/main.go @@ -19,11 +19,11 @@ type Model struct { } func genTable(columnCount int, rowCount int) table.Model { - headers := []table.Header{} + columns := []table.Column{} for column := 0; column < columnCount; column++ { columnStr := fmt.Sprintf("%d", column+1) - headers = append(headers, table.NewHeader(columnStr, columnStr, 4)) + columns = append(columns, table.NewColumn(columnStr, columnStr, 4)) } rows := []table.Row{} @@ -39,7 +39,7 @@ func genTable(columnCount int, rowCount int) table.Model { rows = append(rows, table.NewRow(rowData)) } - return table.New(headers).WithRows(rows).HeaderStyle(lipgloss.NewStyle().Bold(true)) + return table.New(columns).WithRows(rows).HeaderStyle(lipgloss.NewStyle().Bold(true)) } func NewModel() Model { diff --git a/examples/features/main.go b/examples/features/main.go index 4a6997a..99ea84c 100644 --- a/examples/features/main.go +++ b/examples/features/main.go @@ -46,11 +46,11 @@ type Model struct { } func NewModel() Model { - headers := []table.Header{ - table.NewHeader(columnKeyID, "ID", 5), - table.NewHeader(columnKeyName, "Name", 10), - table.NewHeader(columnKeyDescription, "Description", 30), - table.NewHeader(columnKeyCount, "#", 5), + columns := []table.Column{ + table.NewColumn(columnKeyID, "ID", 5), + table.NewColumn(columnKeyName, "Name", 10), + table.NewColumn(columnKeyDescription, "Description", 30), + table.NewColumn(columnKeyCount, "#", 5), } rows := []table.Row{ @@ -75,7 +75,7 @@ func NewModel() Model { } return Model{ - tableModel: table.New(headers). + tableModel: table.New(columns). WithRows(rows). HeaderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("10")).Bold(true)). SelectableRows(true). diff --git a/table/border.go b/table/border.go index c0a8388..572246a 100644 --- a/table/border.go +++ b/table/border.go @@ -242,6 +242,8 @@ func (b *Border) generateStyles() { ) } +// 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 @@ -249,6 +251,7 @@ func (m Model) BorderDefault() Model { return m } +// Border uses the given border components to render the table func (m Model) Border(border Border) Model { border.generateStyles() diff --git a/table/header.go b/table/column.go similarity index 51% rename from table/header.go rename to table/column.go index 286e64c..3ce254b 100644 --- a/table/header.go +++ b/table/column.go @@ -4,7 +4,8 @@ import ( "fmt" ) -type Header struct { +// Column is a column in the table +type Column struct { Title string Key string Width int @@ -12,8 +13,9 @@ type Header struct { fmtString string } -func NewHeader(key, title string, width int) Header { - return Header{ +// NewColumn creates a new column with the given information +func NewColumn(key, title string, width int) Column { + return Column{ Key: key, Title: title, Width: width, diff --git a/table/row.go b/table/row.go index 0e10558..13c1bcd 100644 --- a/table/row.go +++ b/table/row.go @@ -8,6 +8,8 @@ import ( type RowData map[string]interface{} +// Row represents a row in the table with some data keyed to the table columns> +// Can have a style applied to it such as color/bold. Create using NewRow() type Row struct { Style lipgloss.Style Data RowData @@ -15,6 +17,7 @@ type Row struct { selected bool } +// NewRow creates a new row and copies the given row data func NewRow(data RowData) Row { d := Row{ Data: make(map[string]interface{}), @@ -28,14 +31,15 @@ func NewRow(data RowData) Row { return d } +// WithStyle uses the given style for the text in the row func (r Row) WithStyle(style lipgloss.Style) Row { - r.Style = style + r.Style = style.Copy() return r } func (m Model) renderRow(i int) string { - numHeaders := len(m.headers) + numColumns := len(m.columns) row := m.rows[i] last := i == len(m.rows)-1 highlighted := i == m.rowCursorIndex @@ -58,7 +62,7 @@ func (m Model) renderRow(i int) string { rowLastStyleRight lipgloss.Style ) - if numHeaders == 1 { + if numColumns == 1 { rowStyleLeft = m.border.styleSingleColumnInner rowLastStyleLeft = m.border.styleSingleColumnBottom @@ -72,16 +76,16 @@ func (m Model) renderRow(i int) string { rowLastStyleRight = m.border.styleMultiBottomRight } - for i, header := range m.headers { + for i, column := range m.columns { var str string - if header.Key == columnKeySelect { + if column.Key == columnKeySelect { if row.selected { str = "[x]" } else { str = "[ ]" } - } else if entry, exists := row.Data[header.Key]; exists { + } else if entry, exists := row.Data[column.Key]; exists { str = fmt.Sprintf("%v", entry) } @@ -90,7 +94,7 @@ func (m Model) renderRow(i int) string { if !last { if i == 0 { cellStyle = cellStyle.Inherit(rowStyleLeft) - } else if i < numHeaders-1 { + } else if i < numColumns-1 { cellStyle = cellStyle.Inherit(rowStyleInner) } else { cellStyle = cellStyle.Inherit(rowStyleRight) @@ -98,14 +102,14 @@ func (m Model) renderRow(i int) string { } else { if i == 0 { cellStyle = cellStyle.Inherit(rowLastStyleLeft) - } else if i < numHeaders-1 { + } else if i < numColumns-1 { cellStyle = cellStyle.Inherit(rowLastStyleInner) } else { cellStyle = cellStyle.Inherit(rowLastStyleRight) } } - cellStr := cellStyle.Render(fmt.Sprintf(header.fmtString, limitStr(str, header.Width))) + cellStr := cellStyle.Render(fmt.Sprintf(column.fmtString, limitStr(str, column.Width))) columnStrings = append(columnStrings, cellStr) } diff --git a/table/table.go b/table/table.go index 6de10be..c5251d9 100644 --- a/table/table.go +++ b/table/table.go @@ -8,8 +8,6 @@ import ( "github.com/charmbracelet/lipgloss" ) -type MovementMode int - const ( columnKeySelect = "___select___" ) @@ -18,8 +16,9 @@ var ( defaultHighlightStyle = lipgloss.NewStyle().Background(lipgloss.Color("#334")) ) +// Model is the main table model. Create using New() type Model struct { - headers []Header + columns []Column headerStyle lipgloss.Style rows []Row @@ -37,47 +36,53 @@ type Model struct { border Border } -func New(headers []Header) Model { +// New creates a new table ready for further modifications +func New(columns []Column) Model { m := Model{ - headers: make([]Header, len(headers)), + columns: make([]Column, len(columns)), highlightStyle: defaultHighlightStyle.Copy(), border: borderDefault, } // Do a full deep copy to avoid unexpected edits - copy(m.headers, headers) + copy(m.columns, columns) return m } +// HeaderStyle sets the style to apply to the header text, such as color or bold func (m Model) HeaderStyle(style lipgloss.Style) Model { m.headerStyle = style.Copy() return m } +// WithRows sets the rows to show as data in the table func (m Model) WithRows(rows []Row) Model { m.rows = rows return m } +// SelectableRows sets whether or not rows are selectable. If set, adds a column +// in the front that acts as a checkbox and responds to controls if Focused func (m Model) SelectableRows(selectable bool) Model { m.selectableRows = selectable - hasSelectHeader := m.headers[0].Key == columnKeySelect + hasSelectColumn := m.columns[0].Key == columnKeySelect - if hasSelectHeader != selectable { + if hasSelectColumn != selectable { if selectable { - m.headers = append([]Header{ - NewHeader(columnKeySelect, "[x]", 3), - }, m.headers...) + m.columns = append([]Column{ + NewColumn(columnKeySelect, "[x]", 3), + }, m.columns...) } else { - m.headers = m.headers[1:] + m.columns = m.columns[1:] } } return m } +// HighlightedRow returns the full Row that's currently highlighted by the user func (m Model) HighlightedRow() Row { if len(m.rows) > 0 { return m.rows[m.rowCursorIndex] @@ -87,24 +92,31 @@ func (m Model) HighlightedRow() Row { return Row{} } +// SelectedRows returns all rows that have been set as selected by the user func (m Model) SelectedRows() []Row { return m.selectedRows } +// HighlightStyle sets a custom style to use when the row is being highlighted +// by the cursor func (m Model) HighlightStyle(style lipgloss.Style) Model { m.highlightStyle = style return m } +// Focused allows the table to show highlighted rows and take in controls of +// up/down/space/etc to let the user navigate the table and interact with it func (m Model) Focused(focused bool) Model { m.focused = focused return m } +// Init initializes the table per the Bubble Tea architecture func (m Model) Init() tea.Cmd { return nil } +// Update responds to input from the user or other messages from Bubble Tea func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { if !m.focused { return m, nil @@ -153,12 +165,14 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { return m, nil } +// View renders the table. It does not end in a newline, so that it can be +// composed with other elements more consistently. func (m Model) View() string { - numHeaders := len(m.headers) + numColumns := len(m.columns) hasRows := len(m.rows) > 0 // Safety valve for empty tables - if numHeaders == 0 { + if numColumns == 0 { return "" } @@ -172,7 +186,7 @@ func (m Model) View() string { headerStyleRight lipgloss.Style ) - if numHeaders == 1 { + if numColumns == 1 { if hasRows { headerStyleLeft = m.border.styleSingleColumnTop } else { @@ -196,13 +210,13 @@ func (m Model) View() string { headerStyleRight = headerStyleRight.Copy().Inherit(m.headerStyle) } - for i, header := range m.headers { + for i, header := range m.columns { headerSection := fmt.Sprintf(header.fmtString, header.Title) var borderStyle lipgloss.Style if i == 0 { borderStyle = headerStyleLeft - } else if i < len(m.headers)-1 { + } else if i < len(m.columns)-1 { borderStyle = headerStyleInner } else { borderStyle = headerStyleRight diff --git a/table/table_test.go b/table/table_test.go index c82ec07..f03bb12 100644 --- a/table/table_test.go +++ b/table/table_test.go @@ -18,12 +18,12 @@ func TestBasicTableShowsAllHeaders(t *testing.T) { secondWidth = 20 ) - headers := []Header{ - NewHeader(firstKey, firstTitle, firstWidth), - NewHeader(secondKey, secondTitle, secondWidth), + columns := []Column{ + NewColumn(firstKey, firstTitle, firstWidth), + NewColumn(secondKey, secondTitle, secondWidth), } - table := New(headers) + table := New(columns) rendered := table.View() @@ -33,7 +33,7 @@ func TestBasicTableShowsAllHeaders(t *testing.T) { assert.False(t, strings.HasSuffix(rendered, "\n"), "Should not end in newline") } -func TestNilHeadersSafelyReturnsEmptyView(t *testing.T) { +func TestNilColumnsSafelyReturnsEmptyView(t *testing.T) { table := New(nil) assert.Equal(t, "", table.View())