table.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. package logs
  2. import (
  3. "slices"
  4. "github.com/charmbracelet/bubbles/key"
  5. "github.com/charmbracelet/bubbles/table"
  6. tea "github.com/charmbracelet/bubbletea"
  7. "github.com/opencode-ai/opencode/internal/logging"
  8. "github.com/opencode-ai/opencode/internal/pubsub"
  9. "github.com/opencode-ai/opencode/internal/tui/layout"
  10. // "github.com/opencode-ai/opencode/internal/tui/styles"
  11. "github.com/opencode-ai/opencode/internal/tui/theme"
  12. "github.com/opencode-ai/opencode/internal/tui/util"
  13. )
  14. type TableComponent interface {
  15. tea.Model
  16. layout.Sizeable
  17. layout.Bindings
  18. }
  19. type tableCmp struct {
  20. table table.Model
  21. }
  22. type selectedLogMsg logging.LogMessage
  23. func (i *tableCmp) Init() tea.Cmd {
  24. i.setRows()
  25. return nil
  26. }
  27. func (i *tableCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  28. var cmds []tea.Cmd
  29. switch msg.(type) {
  30. case pubsub.Event[logging.LogMessage]:
  31. i.setRows()
  32. return i, nil
  33. }
  34. prevSelectedRow := i.table.SelectedRow()
  35. t, cmd := i.table.Update(msg)
  36. cmds = append(cmds, cmd)
  37. i.table = t
  38. selectedRow := i.table.SelectedRow()
  39. if selectedRow != nil {
  40. if prevSelectedRow == nil || selectedRow[0] == prevSelectedRow[0] {
  41. var log logging.LogMessage
  42. for _, row := range logging.List() {
  43. if row.ID == selectedRow[0] {
  44. log = row
  45. break
  46. }
  47. }
  48. if log.ID != "" {
  49. cmds = append(cmds, util.CmdHandler(selectedLogMsg(log)))
  50. }
  51. }
  52. }
  53. return i, tea.Batch(cmds...)
  54. }
  55. func (i *tableCmp) View() string {
  56. t := theme.CurrentTheme()
  57. defaultStyles := table.DefaultStyles()
  58. defaultStyles.Selected = defaultStyles.Selected.Foreground(t.Primary())
  59. i.table.SetStyles(defaultStyles)
  60. return i.table.View()
  61. }
  62. func (i *tableCmp) GetSize() (int, int) {
  63. return i.table.Width(), i.table.Height()
  64. }
  65. func (i *tableCmp) SetSize(width int, height int) tea.Cmd {
  66. i.table.SetWidth(width)
  67. i.table.SetHeight(height)
  68. columns := i.table.Columns()
  69. // Calculate widths for visible columns
  70. timeWidth := 8 // Fixed width for Time column
  71. levelWidth := 7 // Fixed width for Level column
  72. // Message column gets the remaining space
  73. messageWidth := width - timeWidth - levelWidth - 5 // 5 for padding and borders
  74. // Set column widths
  75. columns[0].Width = 0 // ID column (hidden)
  76. columns[1].Width = timeWidth
  77. columns[2].Width = levelWidth
  78. columns[3].Width = messageWidth
  79. i.table.SetColumns(columns)
  80. return nil
  81. }
  82. func (i *tableCmp) BindingKeys() []key.Binding {
  83. return layout.KeyMapToSlice(i.table.KeyMap)
  84. }
  85. func (i *tableCmp) setRows() {
  86. rows := []table.Row{}
  87. logs := logging.List()
  88. slices.SortFunc(logs, func(a, b logging.LogMessage) int {
  89. if a.Time.Before(b.Time) {
  90. return 1
  91. }
  92. if a.Time.After(b.Time) {
  93. return -1
  94. }
  95. return 0
  96. })
  97. for _, log := range logs {
  98. // Include ID as hidden first column for selection
  99. row := table.Row{
  100. log.ID,
  101. log.Time.Format("15:04:05"),
  102. log.Level,
  103. log.Message,
  104. }
  105. rows = append(rows, row)
  106. }
  107. i.table.SetRows(rows)
  108. }
  109. func NewLogsTable() TableComponent {
  110. columns := []table.Column{
  111. {Title: "ID", Width: 0}, // ID column with zero width
  112. {Title: "Time", Width: 8},
  113. {Title: "Level", Width: 7},
  114. {Title: "Message", Width: 30},
  115. }
  116. tableModel := table.New(
  117. table.WithColumns(columns),
  118. )
  119. tableModel.Focus()
  120. return &tableCmp{
  121. table: tableModel,
  122. }
  123. }