diff options
Diffstat (limited to 'cmd/krino/display.go')
| -rw-r--r-- | cmd/krino/display.go | 61 |
1 files changed, 57 insertions, 4 deletions
diff --git a/cmd/krino/display.go b/cmd/krino/display.go index 52e8385..193d8b8 100644 --- a/cmd/krino/display.go +++ b/cmd/krino/display.go @@ -4,8 +4,12 @@ package main import ( "fmt" + "io" "strings" + "unicode" "unicode/utf8" + + "krino/internal/cond" ) // display makes s safe to print to a terminal (spec ยง15.1). File names, @@ -46,9 +50,58 @@ func display(s string) string { return b.String() } -// controlRune reports whether r is a C1 control or a Unicode bidirectional -// embedding, override or isolate: printable-looking code points a terminal -// acts on. +// controlRune reports whether r is a C1 control, a Unicode bidirectional +// control (embeddings, overrides, isolates, and the marks U+061C, U+200E and +// U+200F), or a line or paragraph separator: code points a terminal acts on, +// or that reorder or break the lines krino prints. func controlRune(r rune) bool { - return (r >= 0x80 && r <= 0x9f) || (r >= 0x202a && r <= 0x202e) || (r >= 0x2066 && r <= 0x2069) + return (r >= 0x80 && r <= 0x9f) || unicode.Is(unicode.Bidi_Control, r) || unicode.In(r, unicode.Zl, unicode.Zp) +} + +// safeWriter writes through display line by line, keeping the newlines: +// every error and warning krino writes to stderr may quote a file name or a +// tool's message, and nothing krino itself writes there is styled (review +// M5). +type safeWriter struct{ w io.Writer } + +func (s safeWriter) Write(p []byte) (int, error) { + lines := strings.Split(string(p), "\n") + for i, l := range lines { + lines[i] = display(l) + } + if _, err := io.WriteString(s.w, strings.Join(lines, "\n")); err != nil { + return 0, err + } + return len(p), nil +} + +// jsonSafe escapes, in an encoded JSON document, the code points +// encoding/json leaves raw that a terminal acts on - DEL, C1 controls, +// bidirectional controls - as \uXXXX. They can only occur inside strings, +// where the escape is the same value (review terminal F4). +func jsonSafe(b []byte) []byte { + s := string(b) + var out strings.Builder + out.Grow(len(s)) + for _, r := range s { + if r == 0x7f || controlRune(r) { + fmt.Fprintf(&out, `\u%04x`, r) + continue + } + out.WriteRune(r) + } + return []byte(out.String()) +} + +// displayTrace returns a copy of t with every label and error passed through +// display, so a newline or escape in a file name or a tool's message cannot +// forge or disturb an explain trace line (review M5). +func displayTrace(t *cond.Trace) *cond.Trace { + c := *t + c.Label, c.Err = display(t.Label), display(t.Err) + c.Children = make([]*cond.Trace, len(t.Children)) + for i, ch := range t.Children { + c.Children[i] = displayTrace(ch) + } + return &c } |
