details.go

  1package logs
  2
  3import (
  4	"fmt"
  5	"strings"
  6	"time"
  7
  8	"github.com/charmbracelet/bubbles/key"
  9	"github.com/charmbracelet/bubbles/viewport"
 10	tea "github.com/charmbracelet/bubbletea"
 11	"github.com/charmbracelet/lipgloss"
 12	"github.com/kujtimiihoxha/opencode/internal/logging"
 13	"github.com/kujtimiihoxha/opencode/internal/tui/layout"
 14	"github.com/kujtimiihoxha/opencode/internal/tui/styles"
 15)
 16
 17type DetailComponent interface {
 18	tea.Model
 19	layout.Sizeable
 20	layout.Bindings
 21}
 22
 23type detailCmp struct {
 24	width, height int
 25	focused       bool
 26	currentLog    logging.LogMessage
 27	viewport      viewport.Model
 28}
 29
 30func (i *detailCmp) Init() tea.Cmd {
 31	messages := logging.List()
 32	if len(messages) == 0 {
 33		return nil
 34	}
 35	i.currentLog = messages[0]
 36	return nil
 37}
 38
 39func (i *detailCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 40	var (
 41		cmd  tea.Cmd
 42		cmds []tea.Cmd
 43	)
 44
 45	switch msg := msg.(type) {
 46	case selectedLogMsg:
 47		if msg.ID != i.currentLog.ID {
 48			i.currentLog = logging.LogMessage(msg)
 49			i.updateContent()
 50		}
 51	}
 52
 53	if i.focused {
 54		i.viewport, cmd = i.viewport.Update(msg)
 55		cmds = append(cmds, cmd)
 56	}
 57
 58	return i, tea.Batch(cmds...)
 59}
 60
 61func (i *detailCmp) updateContent() {
 62	var content strings.Builder
 63
 64	// Format the header with timestamp and level
 65	timeStyle := lipgloss.NewStyle().Foreground(styles.SubText0)
 66	levelStyle := getLevelStyle(i.currentLog.Level)
 67
 68	header := lipgloss.JoinHorizontal(
 69		lipgloss.Center,
 70		timeStyle.Render(i.currentLog.Time.Format(time.RFC3339)),
 71		"  ",
 72		levelStyle.Render(i.currentLog.Level),
 73	)
 74
 75	content.WriteString(lipgloss.NewStyle().Bold(true).Render(header))
 76	content.WriteString("\n\n")
 77
 78	// Message with styling
 79	messageStyle := lipgloss.NewStyle().Bold(true).Foreground(styles.Text)
 80	content.WriteString(messageStyle.Render("Message:"))
 81	content.WriteString("\n")
 82	content.WriteString(lipgloss.NewStyle().Padding(0, 2).Render(i.currentLog.Message))
 83	content.WriteString("\n\n")
 84
 85	// Attributes section
 86	if len(i.currentLog.Attributes) > 0 {
 87		attrHeaderStyle := lipgloss.NewStyle().Bold(true).Foreground(styles.Text)
 88		content.WriteString(attrHeaderStyle.Render("Attributes:"))
 89		content.WriteString("\n")
 90
 91		// Create a table-like display for attributes
 92		keyStyle := lipgloss.NewStyle().Foreground(styles.Primary).Bold(true)
 93		valueStyle := lipgloss.NewStyle().Foreground(styles.Text)
 94
 95		for _, attr := range i.currentLog.Attributes {
 96			attrLine := fmt.Sprintf("%s: %s",
 97				keyStyle.Render(attr.Key),
 98				valueStyle.Render(attr.Value),
 99			)
100			content.WriteString(lipgloss.NewStyle().Padding(0, 2).Render(attrLine))
101			content.WriteString("\n")
102		}
103	}
104
105	i.viewport.SetContent(content.String())
106}
107
108func getLevelStyle(level string) lipgloss.Style {
109	style := lipgloss.NewStyle().Bold(true)
110
111	switch strings.ToLower(level) {
112	case "info":
113		return style.Foreground(styles.Blue)
114	case "warn", "warning":
115		return style.Foreground(styles.Warning)
116	case "error", "err":
117		return style.Foreground(styles.Error)
118	case "debug":
119		return style.Foreground(styles.Green)
120	default:
121		return style.Foreground(styles.Text)
122	}
123}
124
125func (i *detailCmp) View() string {
126	return i.viewport.View()
127}
128
129func (i *detailCmp) Blur() tea.Cmd {
130	i.focused = false
131	return nil
132}
133
134func (i *detailCmp) Focus() tea.Cmd {
135	i.focused = true
136	return nil
137}
138
139func (i *detailCmp) IsFocused() bool {
140	return i.focused
141}
142
143func (i *detailCmp) GetSize() (int, int) {
144	return i.width, i.height
145}
146
147func (i *detailCmp) SetSize(width int, height int) {
148	i.width = width
149	i.height = height
150	i.viewport.Width = i.width
151	i.viewport.Height = i.height
152	i.updateContent()
153}
154
155func (i *detailCmp) BindingKeys() []key.Binding {
156	return []key.Binding{
157		i.viewport.KeyMap.PageDown,
158		i.viewport.KeyMap.PageUp,
159		i.viewport.KeyMap.HalfPageDown,
160		i.viewport.KeyMap.HalfPageUp,
161	}
162}
163
164func (i *detailCmp) BorderText() map[layout.BorderPosition]string {
165	return map[layout.BorderPosition]string{
166		layout.TopLeftBorder: "Log Details",
167	}
168}
169
170func NewLogsDetails() DetailComponent {
171	return &detailCmp{
172		viewport: viewport.New(0, 0),
173	}
174}