131 lines
3.7 KiB
Go
131 lines
3.7 KiB
Go
package mujica
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"mujicaui/theme"
|
|
)
|
|
|
|
// LogLevel is a log line's severity.
|
|
type LogLevel int
|
|
|
|
const (
|
|
LogDebug LogLevel = iota
|
|
LogInfo
|
|
LogWarn
|
|
LogError
|
|
)
|
|
|
|
// LogLine is one entry of a LogViewer.
|
|
type LogLine struct {
|
|
Time time.Time
|
|
Level LogLevel
|
|
Text string
|
|
}
|
|
|
|
// LogViewerState is a LogViewer's filters, highlight and place.
|
|
type LogViewerState struct {
|
|
// Hidden levels are filtered out.
|
|
Hidden [4]bool
|
|
// Query is highlighted in the shown lines.
|
|
Query string
|
|
// Follow keeps the newest line in view; it starts on.
|
|
Follow bool
|
|
rows ui.ListState
|
|
shown []int
|
|
scanned int
|
|
hidden [4]bool
|
|
started bool
|
|
}
|
|
|
|
// LogViewerOptions configure a LogViewer.
|
|
type LogViewerOptions struct {
|
|
Disabled bool
|
|
}
|
|
|
|
// LogViewer shows filtered, highlighted log lines, virtualized.
|
|
func LogViewer(c *ui.Context, lines []LogLine, s *LogViewerState, opts LogViewerOptions) ChoiceResult {
|
|
if s == nil {
|
|
panic("mujica: LogViewer needs a state")
|
|
}
|
|
if !s.started {
|
|
s.started, s.Follow = true, true
|
|
}
|
|
var res ChoiceResult
|
|
res.Element = codePanel(c).Disabled(opts.Disabled)
|
|
res.Element.Children(func() {
|
|
codeHeader(c, func() {
|
|
for lv := LogDebug; lv <= LogError; lv++ {
|
|
on := !s.Hidden[lv]
|
|
if Toggle(c, &on, logViewerName(c, lv), ToggleOptions{Disabled: opts.Disabled}).Changed() {
|
|
s.Hidden[lv] = !on
|
|
res.changed = true
|
|
slog.Debug("mujica: log_viewer level", "level", lv, "shown", on)
|
|
}
|
|
}
|
|
SearchInput(c, &s.Query, SearchInputOptions{Disabled: opts.Disabled, Placeholder: msg(c, "log_viewer.highlight")}).Element.Grow(1).MinWidth(0)
|
|
was := s.Follow
|
|
if Toggle(c, &s.Follow, msg(c, "log_viewer.follow"), ToggleOptions{Disabled: opts.Disabled}).Changed() {
|
|
res.changed = true
|
|
slog.Debug("mujica: log_viewer follow", "on", s.Follow)
|
|
if s.Follow && !was {
|
|
s.rows.ScrollToEnd()
|
|
}
|
|
}
|
|
})
|
|
logViewerFilter(lines, s)
|
|
if len(s.shown) == 0 {
|
|
codeCaption(c, msg(c, "log_viewer.empty")).Padding(Density(c).Unit() * 4)
|
|
return
|
|
}
|
|
s.rows.FollowEnd = s.Follow
|
|
s.rows.Key = func(i int) any { return s.shown[i] }
|
|
s.rows.Label = func(i int) string { return lines[s.shown[i]].Text }
|
|
d := Density(c)
|
|
ui.List(c, &s.rows, len(s.shown), func(i int) {
|
|
logViewerRow(c, lines[s.shown[i]], s.Query)
|
|
}).Grow(1).MinHeight(d.ControlHeight()*3).Padding(d.Unit(), 0)
|
|
})
|
|
return res
|
|
}
|
|
|
|
// logViewerFilter extends or redoes the shown lines.
|
|
func logViewerFilter(lines []LogLine, s *LogViewerState) {
|
|
if s.hidden != s.Hidden || s.scanned > len(lines) {
|
|
s.hidden, s.scanned, s.shown = s.Hidden, 0, s.shown[:0]
|
|
}
|
|
for i := s.scanned; i < len(lines); i++ {
|
|
lv := lines[i].Level
|
|
if lv < LogDebug || lv > LogError {
|
|
panic("mujica: LogLine level out of range")
|
|
}
|
|
if !s.Hidden[lv] {
|
|
s.shown = append(s.shown, i)
|
|
}
|
|
}
|
|
s.scanned = len(lines)
|
|
}
|
|
|
|
func logViewerName(c *ui.Context, lv LogLevel) string {
|
|
return msg(c, [...]string{"log_viewer.debug", "log_viewer.info", "log_viewer.warn", "log_viewer.error"}[lv])
|
|
}
|
|
|
|
func logViewerRow(c *ui.Context, l LogLine, query string) {
|
|
k := Tokens(c)
|
|
d := Density(c)
|
|
col := [...]ui.Color{k.TextMuted, k.Info, k.Warning, k.Danger}[l.Level]
|
|
ui.Row(c).MinHeight(d.ControlHeight()).Padding(0, d.Unit()*3).Gap(d.Unit() * 3).AlignItems(ui.Center).Children(func() {
|
|
if !l.Time.IsZero() {
|
|
codeMono(c, l.Time.Format("15:04:05.000")).TextColor(k.TextMuted).Shrink(0)
|
|
}
|
|
ui.Text(c, fmt.Sprintf("%-5s", logViewerName(c, l.Level))).Font(theme.MonoFont).FontSize(FontSize(c, codeSize)).
|
|
FontWeight(700).TextColor(col).SingleLine().Shrink(0)
|
|
ui.RichText(c, codeMatchSpans(c, l.Text, query)...).Font(theme.MonoFont).FontSize(FontSize(c, codeSize)).
|
|
SingleLine().Grow(1).MinWidth(0)
|
|
})
|
|
}
|