aboutsummaryrefslogtreecommitdiff
path: root/cmd/krino/display.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/krino/display.go')
-rw-r--r--cmd/krino/display.go61
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
}