v0.33.0 tui tap for live view and console enhancements
This commit is contained in:
1 parent
a1e9d21d06
commit
87f5576ccf
41 files changed
+4606
-449
No files matched your search
@@ -1,19 +1,17 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/lixenwraith/logwisp/internal/config"
|
||||
"github.com/lixenwraith/logwisp/internal/core"
|
||||
"github.com/lixenwraith/logwisp/internal/logline"
|
||||
"github.com/lixenwraith/logwisp/internal/plugin"
|
||||
"github.com/lixenwraith/logwisp/internal/session"
|
||||
"github.com/lixenwraith/logwisp/internal/sink"
|
||||
@@ -82,10 +80,7 @@ func NewConsoleSinkPlugin(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output := os.Stdout
|
||||
if opts.Target == "stderr" {
|
||||
output = os.Stderr
|
||||
}
|
||||
output := Stream(opts.Target)
|
||||
|
||||
// inline decides auto: a terminal, NO_COLOR unset, TERM not dumb. Paint
|
||||
// returns its text unchanged exactly when color is off.
|
||||
@@ -134,6 +129,14 @@ func NewConsoleSinkPlugin(
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
// Stream is the stream a console sink with target writes
|
||||
func Stream(target string) *os.File {
|
||||
if target == "stderr" {
|
||||
return os.Stderr
|
||||
}
|
||||
return os.Stdout
|
||||
}
|
||||
|
||||
// Capabilities returns supported capabilities
|
||||
func (cs *ConsoleSink) Capabilities() []core.Capability {
|
||||
return []core.Capability{
|
||||
@@ -228,7 +231,7 @@ func (cs *ConsoleSink) drain() {
|
||||
func (cs *ConsoleSink) write(event core.TransportEvent) {
|
||||
payload := event.Payload
|
||||
if cs.escape {
|
||||
payload = escapeControls(payload)
|
||||
payload = logline.EscapeControls(payload)
|
||||
}
|
||||
if cs.painter != nil {
|
||||
payload = paintLevel(cs.painter, payload, event.Entry.Level)
|
||||
@@ -260,52 +263,3 @@ func paintLevel(p *inline.Printer, payload []byte, level string) []byte {
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
// escapeControls writes what a terminal would act on or reorder (C0 and C1
|
||||
// controls but tab, DEL, bidi controls, line separators, invalid UTF-8) as
|
||||
// <hex>, keeping the final newline: a log line cannot drive the terminal.
|
||||
// Other invisible characters stay: emoji and Indic scripts need ZWJ.
|
||||
func escapeControls(p []byte) []byte {
|
||||
body, newline := bytes.CutSuffix(p, []byte{'\n'})
|
||||
if printableASCII(body) {
|
||||
return p
|
||||
}
|
||||
out := make([]byte, 0, len(p)+16)
|
||||
for len(body) > 0 {
|
||||
r, n := utf8.DecodeRune(body)
|
||||
if !terminalControl(r, n) {
|
||||
out = append(out, body[:n]...)
|
||||
} else {
|
||||
out = append(out, '<')
|
||||
out = hex.AppendEncode(out, body[:n])
|
||||
out = append(out, '>')
|
||||
}
|
||||
body = body[n:]
|
||||
}
|
||||
if newline {
|
||||
out = append(out, '\n')
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func terminalControl(r rune, size int) bool {
|
||||
switch {
|
||||
case r == utf8.RuneError && size == 1:
|
||||
return true
|
||||
case r < 0x20:
|
||||
return r != '\t'
|
||||
case r >= 0x7f && r <= 0x9f, r == 0x061c, r == 0x200e, r == 0x200f,
|
||||
r >= 0x2028 && r <= 0x202e, r >= 0x2066 && r <= 0x2069:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func printableASCII(b []byte) bool {
|
||||
for _, c := range b {
|
||||
if (c < 0x20 || c > 0x7e) && c != '\t' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -7,28 +7,6 @@ import (
|
||||
"github.com/lixenwraith/terminal/inline"
|
||||
)
|
||||
|
||||
// Everything a terminal would act on is escaped; tabs and the final newline,
|
||||
// which it only lays out, are kept.
|
||||
func TestEscapeControlsKeepsOnlyLayout(t *testing.T) {
|
||||
for in, want := range map[string]string{
|
||||
"plain\ttext\n": "plain\ttext\n",
|
||||
"a\x1b[2Jb\n": "a<1b>[2Jb\n",
|
||||
"bell\x07 del\x7f\n": "bell<07> del<7f>\n",
|
||||
"inner\nnewline\n": "inner<0a>newline\n",
|
||||
"c1 \u009b31m\n": "c1 <c29b>31m\n",
|
||||
"bidi \u202eevil\n": "bidi <e280ae>evil\n",
|
||||
"isolate \u2066x\u2069\n": "isolate <e281a6>x<e281a9>\n",
|
||||
"line\u2028sep\n": "line<e280a8>sep\n",
|
||||
"zwj \U0001f468\u200d\U0001f469\n": "zwj \U0001f468\u200d\U0001f469\n",
|
||||
"bad \xff utf8": "bad <ff> utf8",
|
||||
"ünïcødé and \ufffd kept\n": "ünïcødé and \ufffd kept\n",
|
||||
} {
|
||||
if got := string(escapeControls([]byte(in))); got != want {
|
||||
t.Errorf("%q: got %q, want %q", in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The entry's level, under any of its names, is painted where its line first
|
||||
// names it as a whole word, in any case; other words and levels stay plain.
|
||||
func TestPaintLevelColorsTheFirstWholeName(t *testing.T) {
|
||||
|
||||
@@ -82,6 +82,7 @@ func NewNullSinkPlugin(
|
||||
func (ns *NullSink) Capabilities() []core.Capability {
|
||||
return []core.Capability{
|
||||
core.CapSessionAware,
|
||||
core.CapBackpressure, // it discards at once: a wait loses nothing, a drop would count
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user