diff --git a/internal/dynamo-browse/models/itemrender/coll.go b/internal/dynamo-browse/models/itemrender/coll.go index 2277e5b..3a015e1 100644 --- a/internal/dynamo-browse/models/itemrender/coll.go +++ b/internal/dynamo-browse/models/itemrender/coll.go @@ -13,6 +13,10 @@ func (sr *ListRenderer) TypeName() string { } func (sr *ListRenderer) StringValue() string { + return "" +} + +func (sr *ListRenderer) MetaInfo() string { if len(sr.Value) == 1 { return fmt.Sprintf("(1 item)") } @@ -34,6 +38,10 @@ func (sr *MapRenderer) TypeName() string { } func (sr *MapRenderer) StringValue() string { + return "" +} + +func (sr *MapRenderer) MetaInfo() string { if len(sr.Value) == 1 { return fmt.Sprintf("(1 item)") } diff --git a/internal/dynamo-browse/models/itemrender/itemdisp.go b/internal/dynamo-browse/models/itemrender/itemdisp.go index 47043c5..dd1f17f 100644 --- a/internal/dynamo-browse/models/itemrender/itemdisp.go +++ b/internal/dynamo-browse/models/itemrender/itemdisp.go @@ -5,6 +5,7 @@ import "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" type Renderer interface { TypeName() string StringValue() string + MetaInfo() string SubItems() []SubItem } diff --git a/internal/dynamo-browse/models/itemrender/nils.go b/internal/dynamo-browse/models/itemrender/nils.go index 27a4388..9171f31 100644 --- a/internal/dynamo-browse/models/itemrender/nils.go +++ b/internal/dynamo-browse/models/itemrender/nils.go @@ -3,11 +3,15 @@ package itemrender type OtherRenderer struct{} func (u OtherRenderer) TypeName() string { - return "(other)" + return "??" } -func (u OtherRenderer) StringValue() string { - return "(other)" +func (sr OtherRenderer) StringValue() string { + return "" +} + +func (u OtherRenderer) MetaInfo() string { + return "(unrecognised)" } func (u OtherRenderer) SubItems() []SubItem { diff --git a/internal/dynamo-browse/models/itemrender/scalars.go b/internal/dynamo-browse/models/itemrender/scalars.go index f9c9b10..9f67fe5 100644 --- a/internal/dynamo-browse/models/itemrender/scalars.go +++ b/internal/dynamo-browse/models/itemrender/scalars.go @@ -1,7 +1,6 @@ package itemrender import ( - "fmt" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) @@ -15,6 +14,10 @@ func (sr *StringRenderer) StringValue() string { return sr.Value } +func (sr *StringRenderer) MetaInfo() string { + return "" +} + func (sr *StringRenderer) SubItems() []SubItem { return nil } @@ -29,6 +32,10 @@ func (sr *NumberRenderer) StringValue() string { return sr.Value } +func (sr *NumberRenderer) MetaInfo() string { + return "" +} + func (sr *NumberRenderer) SubItems() []SubItem { return nil } @@ -46,6 +53,10 @@ func (sr *BoolRenderer) StringValue() string { return "False" } +func (sr *BoolRenderer) MetaInfo() string { + return "" +} + func (sr *BoolRenderer) SubItems() []SubItem { return nil } @@ -57,10 +68,11 @@ func (sr *BinaryRenderer) TypeName() string { } func (sr *BinaryRenderer) StringValue() string { - if len(sr.Value) == 1 { - return fmt.Sprintf("(1 byte)") - } - return fmt.Sprintf("(%d bytes)", len(sr.Value)) + return "" +} + +func (sr *BinaryRenderer) MetaInfo() string { + return cardinality(len(sr.Value), "byte", "bytes") } func (sr *BinaryRenderer) SubItems() []SubItem { @@ -73,6 +85,10 @@ func (sr *NullRenderer) TypeName() string { return "NULL" } +func (sr *NullRenderer) MetaInfo() string { + return "" +} + func (sr *NullRenderer) StringValue() string { return "null" } diff --git a/internal/dynamo-browse/models/itemrender/sets.go b/internal/dynamo-browse/models/itemrender/sets.go index 4885a32..209b1fa 100644 --- a/internal/dynamo-browse/models/itemrender/sets.go +++ b/internal/dynamo-browse/models/itemrender/sets.go @@ -15,6 +15,10 @@ func (sr *GenericRenderer) TypeName() string { } func (sr *GenericRenderer) StringValue() string { + return "" +} + +func (sr *GenericRenderer) MetaInfo() string { return cardinality(len(sr.subitemValue), "item", "items") } diff --git a/internal/dynamo-browse/ui/teamodels/dynamoitemview/model.go b/internal/dynamo-browse/ui/teamodels/dynamoitemview/model.go index 4109f7b..dd4a5c6 100644 --- a/internal/dynamo-browse/ui/teamodels/dynamoitemview/model.go +++ b/internal/dynamo-browse/ui/teamodels/dynamoitemview/model.go @@ -17,9 +17,14 @@ import ( var ( activeHeaderStyle = lipgloss.NewStyle(). - Bold(true). - Foreground(lipgloss.Color("#ffffff")). - Background(lipgloss.Color("#4479ff")) + Bold(true). + Foreground(lipgloss.Color("#ffffff")). + Background(lipgloss.Color("#4479ff")) + + fieldTypeStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("#31e131")) + metaInfoStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("#888888")) ) type Model struct { @@ -96,7 +101,8 @@ func (m *Model) updateViewportToSelectedMessage() { } func (m *Model) renderItem(w io.Writer, prefix string, name string, r itemrender.Renderer) { - fmt.Fprintf(w, "%s%v\t%s\t%s\n", prefix, name, r.TypeName(), r.StringValue()) + fmt.Fprintf(w, "%s%v\t%s\t%s%s\n", + prefix, name, fieldTypeStyle.Render(r.TypeName()), r.StringValue(), metaInfoStyle.Render(r.MetaInfo())) if subitems := r.SubItems(); len(subitems) > 0 { for _, si := range subitems { m.renderItem(w, prefix+" ", si.Key, si.Value) diff --git a/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go b/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go index c12bd7a..6e51a22 100644 --- a/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go +++ b/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go @@ -24,6 +24,7 @@ type Model struct { w, h int // model state + colOffset int rows []table.Row resultSet *models.ResultSet } @@ -60,6 +61,12 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "k", "down": m.table.GoDown() return m, m.postSelectedItemChanged + case "j": + m.setLeftmostDisplayedColumn(m.colOffset - 1) + return m, nil + case "l": + m.setLeftmostDisplayedColumn(m.colOffset + 1) + return m, nil case "I", "pgup": m.table.GoPageUp() return m, m.postSelectedItemChanged @@ -72,6 +79,17 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } +func (m *Model) setLeftmostDisplayedColumn(newCol int) { + if newCol < 0 { + m.colOffset = 0 + } else if newCol >= len(m.resultSet.Columns) { + m.colOffset = len(m.resultSet.Columns) - 1 + } else { + m.colOffset = newCol + } + m.rebuildTable() +} + func (m *Model) View() string { return lipgloss.JoinVertical(lipgloss.Top, m.frameTitle.View(), m.table.View()) } @@ -85,23 +103,39 @@ func (m *Model) Resize(w, h int) layout.ResizingModel { } func (m *Model) updateTable() { + m.colOffset = 0 + + m.frameTitle.SetTitle("Table: " + m.resultSet.TableInfo.Name) + m.rebuildTable() +} + +func (m *Model) rebuildTable() { resultSet := m.resultSet - m.frameTitle.SetTitle("Table: " + resultSet.TableInfo.Name) - - newTbl := table.New(resultSet.Columns, m.w, m.h-m.frameTitle.HeaderHeight()) + newTbl := table.New(resultSet.Columns[m.colOffset:], m.w, m.h-m.frameTitle.HeaderHeight()) newRows := make([]table.Row, 0) for i, r := range resultSet.Items() { if resultSet.Hidden(i) { continue } - newRows = append(newRows, itemTableRow{resultSet: resultSet, itemIndex: i, item: r}) + newRows = append(newRows, itemTableRow{ + resultSet: resultSet, + itemIndex: i, + colOffset: m.colOffset, + item: r, + }) } m.rows = newRows newTbl.SetRows(newRows) - + for newTbl.Cursor() != m.table.Cursor() { + if newTbl.Cursor() < m.table.Cursor() { + newTbl.GoDown() + } else if newTbl.Cursor() > m.table.Cursor() { + newTbl.GoUp() + } + } m.table = newTbl } diff --git a/internal/dynamo-browse/ui/teamodels/dynamotableview/tblmodel.go b/internal/dynamo-browse/ui/teamodels/dynamotableview/tblmodel.go index e3a726e..4ef82f4 100644 --- a/internal/dynamo-browse/ui/teamodels/dynamotableview/tblmodel.go +++ b/internal/dynamo-browse/ui/teamodels/dynamotableview/tblmodel.go @@ -17,11 +17,15 @@ var ( Foreground(lipgloss.Color("#e13131")) newRowStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#31e131")) + + metaInfoStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("#888888")) ) type itemTableRow struct { resultSet *models.ResultSet itemIndex int + colOffset int item models.Item } @@ -30,17 +34,6 @@ func (mtr itemTableRow) Render(w io.Writer, model table.Model, index int) { isDirty := mtr.resultSet.IsDirty(mtr.itemIndex) isNew := mtr.resultSet.IsNew(mtr.itemIndex) - sb := strings.Builder{} - for i, colName := range mtr.resultSet.Columns { - if i > 0 { - sb.WriteString("\t") - } - - if r := mtr.item.Renderer(colName); r != nil { - sb.WriteString(r.StringValue()) - } - } - var style lipgloss.Style if index == model.Cursor() { @@ -54,6 +47,21 @@ func (mtr itemTableRow) Render(w io.Writer, model table.Model, index int) { } else if isDirty { style = style.Copy().Inherit(dirtyRowStyle) } + metaInfoStyle := style.Copy().Inherit(metaInfoStyle) - fmt.Fprintln(w, style.Render(sb.String())) + sb := strings.Builder{} + for i, colName := range mtr.resultSet.Columns[mtr.colOffset:] { + if i > 0 { + sb.WriteString(style.Render("\t")) + } + + if r := mtr.item.Renderer(colName); r != nil { + sb.WriteString(style.Render(r.StringValue())) + if mi := r.MetaInfo(); mi != "" { + sb.WriteString(metaInfoStyle.Render(mi)) + } + } + } + + fmt.Fprintln(w, sb.String()) }