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	currentLog    logging.LogMessage
 26	viewport      viewport.Model
 27}
 28
 29func (i *detailCmp) Init() tea.Cmd {
 30	messages := logging.List()
 31	if len(messages) == 0 {
 32		return nil
 33	}
 34	i.currentLog = messages[0]
 35	return nil
 36}
 37
 38func (i *detailCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 39	switch msg := msg.(type) {
 40	case selectedLogMsg:
 41		if msg.ID != i.currentLog.ID {
 42			i.currentLog = logging.LogMessage(msg)
 43			i.updateContent()
 44		}
 45	}
 46
 47	return i, nil
 48}
 49
 50func (i *detailCmp) updateContent() {
 51	var content strings.Builder
 52
 53	// Format the header with timestamp and level
 54	timeStyle := lipgloss.NewStyle().Foreground(styles.SubText0)
 55	levelStyle := getLevelStyle(i.currentLog.Level)
 56
 57	header := lipgloss.JoinHorizontal(
 58		lipgloss.Center,
 59		timeStyle.Render(i.currentLog.Time.Format(time.RFC3339)),
 60		"  ",
 61		levelStyle.Render(i.currentLog.Level),
 62	)
 63
 64	content.WriteString(lipgloss.NewStyle().Bold(true).Render(header))
 65	content.WriteString("\n\n")
 66
 67	// Message with styling
 68	messageStyle := lipgloss.NewStyle().Bold(true).Foreground(styles.Text)
 69	content.WriteString(messageStyle.Render("Message:"))
 70	content.WriteString("\n")
 71	content.WriteString(lipgloss.NewStyle().Padding(0, 2).Render(i.currentLog.Message))
 72	content.WriteString("\n\n")
 73
 74	// Attributes section
 75	if len(i.currentLog.Attributes) > 0 {
 76		attrHeaderStyle := lipgloss.NewStyle().Bold(true).Foreground(styles.Text)
 77		content.WriteString(attrHeaderStyle.Render("Attributes:"))
 78		content.WriteString("\n")
 79
 80		// Create a table-like display for attributes
 81		keyStyle := lipgloss.NewStyle().Foreground(styles.Primary).Bold(true)
 82		valueStyle := lipgloss.NewStyle().Foreground(styles.Text)
 83
 84		for _, attr := range i.currentLog.Attributes {
 85			attrLine := fmt.Sprintf("%s: %s",
 86				keyStyle.Render(attr.Key),
 87				valueStyle.Render(attr.Value),
 88			)
 89			content.WriteString(lipgloss.NewStyle().Padding(0, 2).Render(attrLine))
 90			content.WriteString("\n")
 91		}
 92	}
 93
 94	i.viewport.SetContent(content.String())
 95}
 96
 97func getLevelStyle(level string) lipgloss.Style {
 98	style := lipgloss.NewStyle().Bold(true)
 99
100	switch strings.ToLower(level) {
101	case "info":
102		return style.Foreground(styles.Blue)
103	case "warn", "warning":
104		return style.Foreground(styles.Warning)
105	case "error", "err":
106		return style.Foreground(styles.Error)
107	case "debug":
108		return style.Foreground(styles.Green)
109	default:
110		return style.Foreground(styles.Text)
111	}
112}
113
114func (i *detailCmp) View() string {
115	return styles.ForceReplaceBackgroundWithLipgloss(i.viewport.View(), styles.Background)
116}
117
118func (i *detailCmp) GetSize() (int, int) {
119	return i.width, i.height
120}
121
122func (i *detailCmp) SetSize(width int, height int) {
123	i.width = width
124	i.height = height
125	i.viewport.Width = i.width
126	i.viewport.Height = i.height
127	i.updateContent()
128}
129
130func (i *detailCmp) BindingKeys() []key.Binding {
131	return []key.Binding{
132		i.viewport.KeyMap.PageDown,
133		i.viewport.KeyMap.PageUp,
134		i.viewport.KeyMap.HalfPageDown,
135		i.viewport.KeyMap.HalfPageUp,
136	}
137}
138
139func (i *detailCmp) BorderText() map[layout.BorderPosition]string {
140	return map[layout.BorderPosition]string{
141		layout.TopLeftBorder: "Log Details",
142	}
143}
144
145func NewLogsDetails() DetailComponent {
146	return &detailCmp{
147		viewport: viewport.New(0, 0),
148	}
149}