diff --git a/.github/workflows/lint-soft.yml b/.github/workflows/lint-soft.yml index c739bcf..cd79f53 100644 --- a/.github/workflows/lint-soft.yml +++ b/.github/workflows/lint-soft.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v4 - name: golangci-lint - uses: golangci/golangci-lint-action@v4 + uses: golangci/golangci-lint-action@v5 with: # Optional: golangci-lint command line arguments. args: --config .golangci-soft.yml --issues-exit-code=0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 35158ce..7e45846 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v4 - name: golangci-lint - uses: golangci/golangci-lint-action@v4 + uses: golangci/golangci-lint-action@v5 with: # Optional: golangci-lint command line arguments. #args: diff --git a/.golangci-soft.yml b/.golangci-soft.yml index ef456e0..1b6824b 100644 --- a/.golangci-soft.yml +++ b/.golangci-soft.yml @@ -23,7 +23,6 @@ linters: - gomnd - gomoddirectives - goprintffuncname - - ifshort # - lll - misspell - nakedret diff --git a/README.md b/README.md index 80eb2aa..f6e4d21 100644 --- a/README.md +++ b/README.md @@ -217,6 +217,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { * [mritd/bubbles](https://github.com/mritd/bubbles): Some general-purpose bubbles. Inputs with validation, menu selection, a modified progressbar, and so on. +* [kevm/bubbleo](https://github.com/KevM/bubbleo): A set of bubbles with a + focus on navigation: navigation stacks, breakcrumbs, menus and so on. * [treilik/bubbleboxer](https://github.com/treilik/bubbleboxer): Layout multiple bubbles side-by-side in a layout-tree. * [treilik/bubblelister](https://github.com/treilik/bubblelister): An alternate diff --git a/table/table.go b/table/table.go index 7bff090..e355b23 100644 --- a/table/table.go +++ b/table/table.go @@ -14,11 +14,12 @@ import ( type Model struct { KeyMap KeyMap - cols []Column - rows []Row - cursor int - focus bool - styles Styles + cols []Column + rows []Row + cursor int + focus bool + styles Styles + styleFunc StyleFunc viewport viewport.Model start int @@ -188,6 +189,13 @@ func WithStyles(s Styles) Option { } } +// WithStyleFunc sets the table style func which can determine a cell style per column, row, and selected state. +func WithStyleFunc(f StyleFunc) Option { + return func(m *Model) { + m.styleFunc = f + } +} + // WithKeyMap sets the key map. func WithKeyMap(km KeyMap) Option { return func(m *Model) { @@ -397,6 +405,9 @@ func (m *Model) FromValues(value, separator string) { m.SetRows(rows) } +// StyleFunc is a function that can be used to customize the style of a table cell based on the row and column index. +type StyleFunc func(row, col int, value string) lipgloss.Style + func (m Model) headersView() string { var s = make([]string, 0, len(m.cols)) for _, col := range m.cols { @@ -410,20 +421,30 @@ func (m Model) headersView() string { return lipgloss.JoinHorizontal(lipgloss.Left, s...) } -func (m *Model) renderRow(rowID int) string { +func (m *Model) renderRow(r int) string { var s = make([]string, 0, len(m.cols)) - for i, value := range m.rows[rowID] { + for i, value := range m.rows[r] { if m.cols[i].Width <= 0 { continue } + var cellStyle lipgloss.Style + if m.styleFunc != nil { + cellStyle = m.styleFunc(r, i, value) + if r == m.cursor { + cellStyle.Inherit(m.styles.Selected) + } + } else { + cellStyle = m.styles.Cell + } + style := lipgloss.NewStyle().Width(m.cols[i].Width).MaxWidth(m.cols[i].Width).Inline(true) - renderedCell := m.styles.Cell.Render(style.Render(runewidth.Truncate(value, m.cols[i].Width, "…"))) + renderedCell := cellStyle.Render(style.Render(runewidth.Truncate(value, m.cols[i].Width, "…"))) s = append(s, renderedCell) } row := lipgloss.JoinHorizontal(lipgloss.Left, s...) - if rowID == m.cursor { + if r == m.cursor { return m.styles.Selected.Render(row) } diff --git a/table/table_test.go b/table/table_test.go index d927be0..24f075f 100644 --- a/table/table_test.go +++ b/table/table_test.go @@ -1,6 +1,7 @@ package table import ( + "strings" "testing" "github.com/charmbracelet/lipgloss" @@ -108,3 +109,33 @@ func TestRenderRow(t *testing.T) { }) } } +func TestRenderRowStyleFunc(t *testing.T) { + tests := []struct { + name string + table *Model + expected string + }{ + { + name: "simple row", + table: &Model{ + rows: []Row{{"Foooooo", "Baaaaar", "Baaaaaz"}}, + cols: cols, + styleFunc: func(row, col int, value string) lipgloss.Style { + if strings.HasSuffix(value, "z") { + return lipgloss.NewStyle().Transform(strings.ToLower) + } + return lipgloss.NewStyle().Transform(strings.ToUpper) + }, + }, + expected: "FOOOOOO BAAAAAR baaaaaz ", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + row := tc.table.renderRow(0) + if row != tc.expected { + t.Fatalf("\n\nWant: \n%s\n\nGot: \n%s\n", tc.expected, row) + } + }) + } +} diff --git a/textinput/textinput_test.go b/textinput/textinput_test.go new file mode 100644 index 0000000..27a7640 --- /dev/null +++ b/textinput/textinput_test.go @@ -0,0 +1,32 @@ +package textinput + +import ( + "testing" +) + +func Test_CurrentSuggestion(t *testing.T) { + textinput := New() + textinput.ShowSuggestions = true + + suggestion := textinput.CurrentSuggestion() + expected := "" + if suggestion != expected { + t.Fatalf("Error: expected no current suggestion but was %s", suggestion) + } + + textinput.SetSuggestions([]string{"test1", "test2", "test3"}) + suggestion = textinput.CurrentSuggestion() + expected = "" + if suggestion != expected { + t.Fatalf("Error: expected no current suggestion but was %s", suggestion) + } + + textinput.SetValue("test") + textinput.updateSuggestions() + textinput.nextSuggestion() + suggestion = textinput.CurrentSuggestion() + expected = "test2" + if suggestion != expected { + t.Fatalf("Error: expected first suggestion but was %s", suggestion) + } +}