table.go 3.0 KB

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