mirror of
https://codeberg.org/scip/tablizer.git
synced 2025-12-17 12:31:06 +01:00
Add interactive filter table (#62)
This commit is contained in:
2
go.mod
2
go.mod
@@ -10,7 +10,7 @@ require (
|
|||||||
github.com/charmbracelet/bubbles v0.21.0
|
github.com/charmbracelet/bubbles v0.21.0
|
||||||
github.com/charmbracelet/bubbletea v1.3.4
|
github.com/charmbracelet/bubbletea v1.3.4
|
||||||
github.com/charmbracelet/lipgloss v1.1.0
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
github.com/evertras/bubble-table v0.17.2
|
github.com/evertras/bubble-table v0.19.0
|
||||||
github.com/gookit/color v1.5.4
|
github.com/gookit/color v1.5.4
|
||||||
github.com/hashicorp/hcl/v2 v2.24.0
|
github.com/hashicorp/hcl/v2 v2.24.0
|
||||||
github.com/lithammer/fuzzysearch v1.1.8
|
github.com/lithammer/fuzzysearch v1.1.8
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -32,6 +32,8 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6
|
|||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||||
github.com/evertras/bubble-table v0.17.2 h1:4MtLO888s2xb94OG3KqJCIEav6gE3V4ob56hmOammf0=
|
github.com/evertras/bubble-table v0.17.2 h1:4MtLO888s2xb94OG3KqJCIEav6gE3V4ob56hmOammf0=
|
||||||
github.com/evertras/bubble-table v0.17.2/go.mod h1:ifHujS1YxwnYSOgcR2+m3GnJ84f7CVU/4kUOxUCjEbQ=
|
github.com/evertras/bubble-table v0.17.2/go.mod h1:ifHujS1YxwnYSOgcR2+m3GnJ84f7CVU/4kUOxUCjEbQ=
|
||||||
|
github.com/evertras/bubble-table v0.19.0 h1:+JlXRUjNuBN1JI7XU1PapmW1wglbcqZUKkiPnVKPgrc=
|
||||||
|
github.com/evertras/bubble-table v0.19.0/go.mod h1:ifHujS1YxwnYSOgcR2+m3GnJ84f7CVU/4kUOxUCjEbQ=
|
||||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||||
|
|||||||
@@ -28,8 +28,14 @@ import (
|
|||||||
"github.com/tlinden/tablizer/cfg"
|
"github.com/tlinden/tablizer/cfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FilterTable struct {
|
// The context exists outside of the bubble loop, and is being used as
|
||||||
Table table.Model
|
// pointer reciever. That way we can use it as our primary storage
|
||||||
|
// container.
|
||||||
|
type Context struct {
|
||||||
|
selectedColumn int
|
||||||
|
showHelp bool
|
||||||
|
descending bool
|
||||||
|
data *Tabdata
|
||||||
|
|
||||||
// Window dimensions
|
// Window dimensions
|
||||||
totalWidth int
|
totalWidth int
|
||||||
@@ -38,23 +44,64 @@ type FilterTable struct {
|
|||||||
// Table dimensions
|
// Table dimensions
|
||||||
horizontalMargin int
|
horizontalMargin int
|
||||||
verticalMargin int
|
verticalMargin int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute tablizer sort function, feed it with fresh config, we do
|
||||||
|
// NOT use the existing runtime config, because sorting is
|
||||||
|
// configurable in the UI separately.
|
||||||
|
func (ctx *Context) Sort(mode string) {
|
||||||
|
conf := cfg.Config{
|
||||||
|
SortMode: mode,
|
||||||
|
SortDescending: ctx.descending,
|
||||||
|
UseSortByColumn: []int{ctx.selectedColumn + 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.descending = !ctx.descending
|
||||||
|
|
||||||
|
sortTable(conf, ctx.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The actual table model, holds the context pointer, a copy of the
|
||||||
|
// pre-processed data and some flags
|
||||||
|
type FilterTable struct {
|
||||||
|
Table table.Model
|
||||||
|
|
||||||
Rows int
|
Rows int
|
||||||
|
|
||||||
quitting bool
|
quitting bool
|
||||||
unchanged bool
|
unchanged bool
|
||||||
|
|
||||||
|
maxColumns int
|
||||||
|
headerIdx map[string]int
|
||||||
|
|
||||||
|
ctx *Context
|
||||||
|
|
||||||
|
columns []table.Column
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Add a fixed margin to account for description & instructions
|
// header+footer
|
||||||
fixedVerticalMargin = 0
|
ExtraRows = 5
|
||||||
|
|
||||||
ExtraRows = 8
|
HelpFooter = "?:help | "
|
||||||
|
|
||||||
HELP = "/:filter esc:clear-filter q:commit c-c:abort space:select a:select-all | "
|
// number of lines taken by help below, adjust accordingly!
|
||||||
|
HelpRows = 7
|
||||||
|
|
||||||
|
// shown when the user presses ?
|
||||||
|
LongHelp = `
|
||||||
|
Key bindings usable in interactive filter table:
|
||||||
|
q commit and quit s sort alphanumerically
|
||||||
|
ctrl-c discard and quit d sort by duration
|
||||||
|
up|down navigate rows t sort by time
|
||||||
|
TAB navigage columns f fuzzy filter
|
||||||
|
space [de]select row ESC finish filter input
|
||||||
|
a [de]select all rows ? show help buffer
|
||||||
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// we use our own custom border style
|
||||||
customBorder = table.Border{
|
customBorder = table.Border{
|
||||||
Top: "─",
|
Top: "─",
|
||||||
Left: "│",
|
Left: "│",
|
||||||
@@ -74,16 +121,27 @@ var (
|
|||||||
|
|
||||||
InnerDivider: "│",
|
InnerDivider: "│",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cells in selected columns will be highlighted
|
||||||
|
StyleSelected = lipgloss.NewStyle().
|
||||||
|
Background(lipgloss.Color("#696969")).
|
||||||
|
Foreground(lipgloss.Color("#ffffff")).
|
||||||
|
Align(lipgloss.Left)
|
||||||
|
|
||||||
|
// the default style
|
||||||
|
NoStyle = lipgloss.NewStyle().Align(lipgloss.Left)
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewModel(data *Tabdata) FilterTable {
|
// initializes the table model
|
||||||
|
func NewModel(data *Tabdata, ctx *Context) FilterTable {
|
||||||
columns := make([]table.Column, len(data.headers))
|
columns := make([]table.Column, len(data.headers))
|
||||||
rows := make([]table.Row, len(data.entries))
|
|
||||||
lengths := make([]int, len(data.headers))
|
lengths := make([]int, len(data.headers))
|
||||||
|
hidx := make(map[string]int, len(data.headers))
|
||||||
|
|
||||||
// give columns at least the header width
|
// give columns at least the header width
|
||||||
for idx, header := range data.headers {
|
for idx, header := range data.headers {
|
||||||
lengths[idx] = len(header)
|
lengths[idx] = len(header)
|
||||||
|
hidx[strings.ToLower(header)] = idx
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine max width per column
|
// determine max width per column
|
||||||
@@ -95,45 +153,48 @@ func NewModel(data *Tabdata) FilterTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup column data
|
// determine flexFactor with base 10, used by flexColumns
|
||||||
|
for i, len := range lengths {
|
||||||
|
if len <= 10 {
|
||||||
|
lengths[i] = 1
|
||||||
|
} else {
|
||||||
|
lengths[i] = len / 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup column data with flexColumns
|
||||||
for idx, header := range data.headers {
|
for idx, header := range data.headers {
|
||||||
columns[idx] = table.NewColumn(strings.ToLower(header), header, lengths[idx]+2).
|
columns[idx] = table.NewFlexColumn(strings.ToLower(header),
|
||||||
WithFiltered(true)
|
header, lengths[idx]).WithFiltered(true).WithStyle(NoStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup table data
|
// separate variable so we can share the row filling code
|
||||||
for idx, entry := range data.entries {
|
filtertbl := FilterTable{
|
||||||
rowdata := make(table.RowData, len(entry))
|
maxColumns: len(data.headers),
|
||||||
|
|
||||||
for i, cell := range entry {
|
|
||||||
rowdata[strings.ToLower(data.headers[i])] = cell + " "
|
|
||||||
}
|
|
||||||
|
|
||||||
rows[idx] = table.NewRow(rowdata)
|
|
||||||
}
|
|
||||||
|
|
||||||
keys := table.DefaultKeyMap()
|
|
||||||
keys.RowDown.SetKeys("j", "down", "s")
|
|
||||||
keys.RowUp.SetKeys("k", "up", "w")
|
|
||||||
|
|
||||||
// our final interactive table filled with our prepared data
|
|
||||||
return FilterTable{
|
|
||||||
Table: table.New(columns).
|
|
||||||
WithRows(rows).
|
|
||||||
WithKeyMap(keys).
|
|
||||||
Filtered(true).
|
|
||||||
Focused(true).
|
|
||||||
SelectableRows(true).
|
|
||||||
WithSelectedText(" ", "✓").
|
|
||||||
WithFooterVisibility(true).
|
|
||||||
WithHeaderVisibility(true).
|
|
||||||
Border(customBorder),
|
|
||||||
horizontalMargin: 10,
|
|
||||||
Rows: len(data.entries),
|
Rows: len(data.entries),
|
||||||
|
headerIdx: hidx,
|
||||||
|
ctx: ctx,
|
||||||
|
columns: columns,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filtertbl.Table = table.New(columns)
|
||||||
|
filtertbl.fillRows()
|
||||||
|
|
||||||
|
return filtertbl
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m FilterTable) ToggleSelected() {
|
// Applied to every cell on every change (TAB,up,down key, resize
|
||||||
|
// event etc)
|
||||||
|
func CellController(input table.StyledCellFuncInput, m FilterTable) lipgloss.Style {
|
||||||
|
if m.headerIdx[input.Column.Key()] == m.ctx.selectedColumn {
|
||||||
|
return StyleSelected
|
||||||
|
}
|
||||||
|
|
||||||
|
return NoStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selects or deselects ALL rows
|
||||||
|
func (m *FilterTable) ToggleAllSelected() {
|
||||||
rows := m.Table.GetVisibleRows()
|
rows := m.Table.GetVisibleRows()
|
||||||
selected := m.Table.SelectedRows()
|
selected := m.Table.SelectedRows()
|
||||||
|
|
||||||
@@ -150,10 +211,55 @@ func (m FilterTable) ToggleSelected() {
|
|||||||
m.Table.WithRows(rows)
|
m.Table.WithRows(rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ? pressed, display help message
|
||||||
|
func (m FilterTable) ToggleHelp() {
|
||||||
|
m.ctx.showHelp = !m.ctx.showHelp
|
||||||
|
}
|
||||||
|
|
||||||
func (m FilterTable) Init() tea.Cmd {
|
func (m FilterTable) Init() tea.Cmd {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Forward call to context sort
|
||||||
|
func (m *FilterTable) Sort(mode string) {
|
||||||
|
m.ctx.Sort(mode)
|
||||||
|
m.fillRows()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fills the table rows with our data. Called once on startup and
|
||||||
|
// repeatedly if the user changes the sort order in some way
|
||||||
|
func (m *FilterTable) fillRows() {
|
||||||
|
// required to be able to feed the model to the controller
|
||||||
|
controllerWrapper := func(input table.StyledCellFuncInput) lipgloss.Style {
|
||||||
|
return CellController(input, *m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill the rows with style
|
||||||
|
rows := make([]table.Row, len(m.ctx.data.entries))
|
||||||
|
for idx, entry := range m.ctx.data.entries {
|
||||||
|
rowdata := make(table.RowData, len(entry))
|
||||||
|
|
||||||
|
for i, cell := range entry {
|
||||||
|
rowdata[strings.ToLower(m.ctx.data.headers[i])] =
|
||||||
|
table.NewStyledCellWithStyleFunc(cell+" ", controllerWrapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows[idx] = table.NewRow(rowdata)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Table = m.Table.
|
||||||
|
WithRows(rows).
|
||||||
|
Filtered(true).
|
||||||
|
WithFuzzyFilter().
|
||||||
|
Focused(true).
|
||||||
|
SelectableRows(true).
|
||||||
|
WithSelectedText(" ", "✓").
|
||||||
|
WithFooterVisibility(true).
|
||||||
|
WithHeaderVisibility(true).
|
||||||
|
Border(customBorder)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Part of the bubbletea event loop, called every tick
|
||||||
func (m FilterTable) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m FilterTable) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
var (
|
var (
|
||||||
cmd tea.Cmd
|
cmd tea.Cmd
|
||||||
@@ -163,6 +269,8 @@ func (m FilterTable) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.Table, cmd = m.Table.Update(msg)
|
m.Table, cmd = m.Table.Update(msg)
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
|
// If the user is about to enter filter text, do NOT respond to
|
||||||
|
// key bindings, as they might be part of the filter!
|
||||||
if !m.Table.GetIsFilterInputFocused() {
|
if !m.Table.GetIsFilterInputFocused() {
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
@@ -178,23 +286,48 @@ func (m FilterTable) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
cmds = append(cmds, tea.Quit)
|
cmds = append(cmds, tea.Quit)
|
||||||
|
|
||||||
case "a":
|
case "a":
|
||||||
m.ToggleSelected()
|
m.ToggleAllSelected()
|
||||||
|
|
||||||
|
case "tab":
|
||||||
|
m.SelectNextColumn()
|
||||||
|
|
||||||
|
case "?":
|
||||||
|
m.ToggleHelp()
|
||||||
|
m.recalculateTable()
|
||||||
|
|
||||||
|
case "s":
|
||||||
|
m.Sort("alphanumeric")
|
||||||
|
|
||||||
|
case "n":
|
||||||
|
m.Sort("numeric")
|
||||||
|
|
||||||
|
case "d":
|
||||||
|
m.Sort("duration")
|
||||||
|
|
||||||
|
case "t":
|
||||||
|
m.Sort("time")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Happens when the terminal window has been resized
|
||||||
|
switch msg := msg.(type) {
|
||||||
case tea.WindowSizeMsg:
|
case tea.WindowSizeMsg:
|
||||||
m.totalWidth = msg.Width
|
m.ctx.totalWidth = msg.Width
|
||||||
m.totalHeight = msg.Height
|
m.ctx.totalHeight = msg.Height
|
||||||
|
|
||||||
m.recalculateTable()
|
m.recalculateTable()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
m.updateFooter()
|
m.updateFooter()
|
||||||
|
|
||||||
return m, tea.Batch(cmds...)
|
return m, tea.Batch(cmds...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add some info to the footer
|
||||||
func (m *FilterTable) updateFooter() {
|
func (m *FilterTable) updateFooter() {
|
||||||
selected := m.Table.SelectedRows()
|
selected := m.Table.SelectedRows()
|
||||||
footer := fmt.Sprintf("selected: %d", len(selected))
|
footer := fmt.Sprintf("selected: %d ", len(selected))
|
||||||
|
|
||||||
if m.Table.GetIsFilterInputFocused() {
|
if m.Table.GetIsFilterInputFocused() {
|
||||||
footer = fmt.Sprintf("/%s %s", m.Table.GetCurrentFilter(), footer)
|
footer = fmt.Sprintf("/%s %s", m.Table.GetCurrentFilter(), footer)
|
||||||
@@ -202,9 +335,10 @@ func (m *FilterTable) updateFooter() {
|
|||||||
footer = fmt.Sprintf("Filter: %s %s", m.Table.GetCurrentFilter(), footer)
|
footer = fmt.Sprintf("Filter: %s %s", m.Table.GetCurrentFilter(), footer)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Table = m.Table.WithStaticFooter(HELP + footer)
|
m.Table = m.Table.WithStaticFooter(HelpFooter + footer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called on resize event (or if help has been toggled)
|
||||||
func (m *FilterTable) recalculateTable() {
|
func (m *FilterTable) recalculateTable() {
|
||||||
m.Table = m.Table.
|
m.Table = m.Table.
|
||||||
WithTargetWidth(m.calculateWidth()).
|
WithTargetWidth(m.calculateWidth()).
|
||||||
@@ -212,36 +346,68 @@ func (m *FilterTable) recalculateTable() {
|
|||||||
WithPageSize(m.calculateHeight() - ExtraRows)
|
WithPageSize(m.calculateHeight() - ExtraRows)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m FilterTable) calculateWidth() int {
|
func (m *FilterTable) calculateWidth() int {
|
||||||
return m.totalWidth - m.horizontalMargin
|
return m.ctx.totalWidth - m.ctx.horizontalMargin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m FilterTable) calculateHeight() int {
|
// Take help height into account, if enabled
|
||||||
if m.Rows+ExtraRows < m.totalHeight {
|
func (m *FilterTable) calculateHeight() int {
|
||||||
// FIXME: avoid full screen somehow
|
height := m.Rows + ExtraRows
|
||||||
return m.Rows + ExtraRows
|
|
||||||
|
if height >= m.ctx.totalHeight {
|
||||||
|
height = m.ctx.totalHeight - m.ctx.verticalMargin
|
||||||
|
} else {
|
||||||
|
height = m.ctx.totalHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.totalHeight - m.verticalMargin - fixedVerticalMargin
|
if m.ctx.showHelp {
|
||||||
|
height = height - HelpRows
|
||||||
|
}
|
||||||
|
|
||||||
|
return height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Part of the bubbletable event view, called every tick
|
||||||
func (m FilterTable) View() string {
|
func (m FilterTable) View() string {
|
||||||
body := strings.Builder{}
|
body := strings.Builder{}
|
||||||
|
|
||||||
if !m.quitting {
|
if !m.quitting {
|
||||||
body.WriteString(m.Table.View())
|
body.WriteString(m.Table.View())
|
||||||
|
|
||||||
|
if m.ctx.showHelp {
|
||||||
|
body.WriteString(LongHelp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return body.String()
|
return body.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// User hit the TAB key
|
||||||
|
func (m *FilterTable) SelectNextColumn() {
|
||||||
|
if m.ctx.selectedColumn == m.maxColumns-1 {
|
||||||
|
m.ctx.selectedColumn = 0
|
||||||
|
} else {
|
||||||
|
m.ctx.selectedColumn++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// entry point from outside tablizer into table editor
|
||||||
func tableEditor(conf *cfg.Config, data *Tabdata) (*Tabdata, error) {
|
func tableEditor(conf *cfg.Config, data *Tabdata) (*Tabdata, error) {
|
||||||
// we render to STDERR to avoid dead lock when the user redirects STDOUT
|
// we render to STDERR to avoid dead lock when the user redirects STDOUT
|
||||||
// see https://github.com/charmbracelet/bubbletea/issues/860
|
// see https://github.com/charmbracelet/bubbletea/issues/860
|
||||||
|
//
|
||||||
|
// TODO: doesn't work with libgloss v2 anymore!
|
||||||
lipgloss.SetDefaultRenderer(lipgloss.NewRenderer(os.Stderr))
|
lipgloss.SetDefaultRenderer(lipgloss.NewRenderer(os.Stderr))
|
||||||
|
|
||||||
|
ctx := &Context{data: data}
|
||||||
|
|
||||||
|
// Output to STDERR because there's a known bubbletea/lipgloss
|
||||||
|
// issue: if a program with a tui is expected to write something
|
||||||
|
// to STDOUT when the tui is finished, then the styles do not
|
||||||
|
// work. So we write to STDERR (which works) and tablizer can
|
||||||
|
// still be used inside pipes.
|
||||||
program := tea.NewProgram(
|
program := tea.NewProgram(
|
||||||
NewModel(data),
|
NewModel(data, ctx),
|
||||||
tea.WithOutput(os.Stderr),
|
tea.WithOutput(os.Stderr),
|
||||||
tea.WithAltScreen())
|
tea.WithAltScreen())
|
||||||
|
|
||||||
@@ -255,13 +421,21 @@ func tableEditor(conf *cfg.Config, data *Tabdata) (*Tabdata, error) {
|
|||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
|
|
||||||
table := m.(FilterTable).Table
|
// Data has been modified. Extract it, put it back into our own
|
||||||
data.entries = make([][]string, len(table.SelectedRows()))
|
// structure and give control back to cmdline tablizer.
|
||||||
|
filteredtable := m.(FilterTable)
|
||||||
|
|
||||||
|
data.entries = make([][]string, len(filteredtable.Table.SelectedRows()))
|
||||||
for pos, row := range m.(FilterTable).Table.SelectedRows() {
|
for pos, row := range m.(FilterTable).Table.SelectedRows() {
|
||||||
entry := make([]string, len(data.headers))
|
entry := make([]string, len(data.headers))
|
||||||
for idx, field := range data.headers {
|
for idx, field := range data.headers {
|
||||||
entry[idx] = row.Data[strings.ToLower(field)].(string)
|
cell := row.Data[strings.ToLower(field)]
|
||||||
|
switch value := cell.(type) {
|
||||||
|
case string:
|
||||||
|
entry[idx] = value
|
||||||
|
case table.StyledCell:
|
||||||
|
entry[idx] = value.Data.(string)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.entries[pos] = entry
|
data.entries[pos] = entry
|
||||||
|
|||||||
Reference in New Issue
Block a user