diff --git a/cmd/syft/internal/ui/ui.go b/cmd/syft/internal/ui/ui.go index 193768c51..4673a2917 100644 --- a/cmd/syft/internal/ui/ui.go +++ b/cmd/syft/internal/ui/ui.go @@ -1,6 +1,7 @@ package ui import ( + "bytes" "fmt" "io" "os" @@ -8,6 +9,7 @@ import ( "time" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" "github.com/wagoodman/go-partybus" "github.com/anchore/bubbly" @@ -52,7 +54,7 @@ func New(out io.Writer, quiet bool, handlers ...bubbly.EventHandler) *UI { func (m *UI) Setup(subscription partybus.Unsubscribable) error { // we still want to collect log messages, however, we also the logger shouldn't write to the screen directly if logWrapper, ok := log.Get().(logger.Controller); ok { - logWrapper.SetOutput(m.frame.(*frame.Frame).Footer()) + logWrapper.SetOutput(&logFooter{w: m.frame.(*frame.Frame).Footer()}) } m.subscription = subscription @@ -187,6 +189,48 @@ func (m UI) View() string { return m.frame.View() } +var ( + logTitleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("8")) // dark grey + // the first log line gets a branch pointing at it from the title, the rest are indented to match + logBranch = logTitleStyle.Render("└─▶") + " " + logIndent = " " +) + +// logFooter decorates log entries drawn below the TUI with a one-time title (written when the first entry arrives) +// and a branch from the title to the first log line. Writes are serialized by the logger. +type logFooter struct { + w io.Writer + started bool +} + +func (l *logFooter) Write(p []byte) (int, error) { + var buf bytes.Buffer + first := !l.started + if first { + buf.WriteString(logTitleStyle.Render("Logs (non-fatal, see -v for more):") + "\n") + l.started = true + } + for i, line := range bytes.SplitAfter(p, []byte("\n")) { + if len(line) == 0 { + continue + } + if first && i == 0 { + buf.WriteString(logBranch) + } else { + buf.WriteString(logIndent) + } + buf.Write(line) + } + if _, err := l.w.Write(buf.Bytes()); err != nil { + return 0, err + } + return len(p), nil +} + +// IsTerminal marks the footer as terminal-bound (it is drawn by the TUI) so the logger keeps colors. NO_COLOR is +// still honored by the formatter. +func (*logFooter) IsTerminal() bool { return true } + func runWithTimeout(timeout time.Duration, fn func() error) (err error) { c := make(chan struct{}, 1) go func() { diff --git a/go.mod b/go.mod index 8c49b84ff..327deee1d 100644 --- a/go.mod +++ b/go.mod @@ -11,12 +11,12 @@ require ( github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/acobaugh/osrelease v0.1.0 github.com/adrg/xdg v0.5.3 - github.com/anchore/bubbly v0.2.1 + github.com/anchore/bubbly v0.2.2 github.com/anchore/clio v0.1.1 github.com/anchore/fangs v0.1.1 github.com/anchore/go-collections v0.1.1 github.com/anchore/go-homedir v0.1.1 - github.com/anchore/go-logger v0.1.1 + github.com/anchore/go-logger v0.2.0 github.com/anchore/go-macholibre v0.1.1 github.com/anchore/go-rpmdb v0.2.0 github.com/anchore/go-sync v0.1.2 diff --git a/go.sum b/go.sum index 99c006c5d..6e9b0a686 100644 --- a/go.sum +++ b/go.sum @@ -120,8 +120,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/anchore/bubbly v0.2.1 h1:b3ZNBefY8ks5DI4SQ7BXSbBIsN6eoaTD6s1su7NuCBo= -github.com/anchore/bubbly v0.2.1/go.mod h1:o6a9aH3/fCn+mb5g1T1tBbQZ4hGR0yO5/uD1d2Vtago= +github.com/anchore/bubbly v0.2.2 h1:53JHRlpaNZjMcUYdz7n1U7+kOhy0zsJv9gpGEXvTcz8= +github.com/anchore/bubbly v0.2.2/go.mod h1:M4twoFkczlfuNdU8QV7oQjVJwPbxdUWRMfKydcLba0U= github.com/anchore/clio v0.1.1 h1:9bkYnaFbpT0J3G0qSHyQqa/lpK5zV0C9Gtgh82H/jj0= github.com/anchore/clio v0.1.1/go.mod h1:CFPIopJlakJbBpKtAL/EPpj4W9hgBO7PfFc8N2FAtlE= github.com/anchore/fangs v0.1.1 h1:NBTHocmWM2/0gNhebpoE/wEB0Z0nxh2lESww2o/Uf5M= @@ -130,8 +130,8 @@ github.com/anchore/go-collections v0.1.1 h1:kGlmSehs+ctTBZr+98F2+taHeolHy+XDx1Nc github.com/anchore/go-collections v0.1.1/go.mod h1:zgDbPSNDpfU47tVo7aW+2suG7+vXB1Sq1qtDhRJknQ0= github.com/anchore/go-homedir v0.1.1 h1:KpaO5BPrVT7cVMcEZ9KfFfEi0iuo3DtagPGl0C744X0= github.com/anchore/go-homedir v0.1.1/go.mod h1:9B0DGhbmMAJVGGEbrlb+PkORM5eDFCEOtZ3xQ22qaLA= -github.com/anchore/go-logger v0.1.1 h1:HPBNmQVBdYliwaFVUL7oJ3UvSlaoXAMj/ZagAIYaCpA= -github.com/anchore/go-logger v0.1.1/go.mod h1:ekWuh5BkZVwyXnyEd4fwL29N4pNLssgRsqh2+kFN/b8= +github.com/anchore/go-logger v0.2.0 h1:V8Gazz+F8xwWEWz+whqt8/5163IkO4XrAie+1qbK10k= +github.com/anchore/go-logger v0.2.0/go.mod h1:omqLWBwT/B5/aTwIH6pl5WBylBzeH3BGHUTS6OFeFvY= github.com/anchore/go-lzo v0.1.1 h1:IwL/fvkdtlIrYIXck6WxZ3nb8WjjHziYYmGxlooyOnM= github.com/anchore/go-lzo v0.1.1/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk= github.com/anchore/go-macholibre v0.1.1 h1:sTBHi3S1WVIOCamc6NyT8Sn9mBmD30DCTfjfVm4k6/M=