aboutsummaryrefslogtreecommitdiff
path: root/cmd/krino/display_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/krino/display_test.go')
-rw-r--r--cmd/krino/display_test.go42
1 files changed, 37 insertions, 5 deletions
diff --git a/cmd/krino/display_test.go b/cmd/krino/display_test.go
index 4c1cefe..0f4cf90 100644
--- a/cmd/krino/display_test.go
+++ b/cmd/krino/display_test.go
@@ -6,13 +6,15 @@ import (
"fmt"
"strings"
"testing"
+ "unicode"
"unicode/utf8"
)
-// firstUnsafe names the first thing in s a terminal could act on - a C0
-// control (a newline too, unless allowNewline), DEL, a C1 control, a
-// bidirectional control, or invalid UTF-8 - or returns "" when there is
-// none.
+// firstUnsafe names the first thing in s a terminal could act on - a
+// control character (a newline too, unless allowNewline), a Unicode
+// bidirectional control, a line or paragraph separator, or invalid UTF-8 -
+// or returns "" when there is none. It is built from Go's Unicode tables,
+// not from display's own ranges, so it can catch a class display misses.
func firstUnsafe(s string, allowNewline bool) string {
if !utf8.ValidString(s) {
return "invalid UTF-8"
@@ -21,7 +23,7 @@ func firstUnsafe(s string, allowNewline bool) string {
if r == '\n' && allowNewline {
continue
}
- if r < 0x20 || r == 0x7f || (r >= 0x80 && r <= 0x9f) || (r >= 0x202a && r <= 0x202e) || (r >= 0x2066 && r <= 0x2069) {
+ if unicode.IsControl(r) || unicode.Is(unicode.Bidi_Control, r) || unicode.In(r, unicode.Zl, unicode.Zp) {
return fmt.Sprintf("%U", r)
}
}
@@ -46,6 +48,10 @@ func TestDisplayEscapesTerminalControls(t *testing.T) {
"isolate\u2066x\u2069.pdf": `isolate\u2066x\u2069.pdf`,
"bad\xffbyte.pdf": `bad\xffbyte.pdf`,
`back\slash.pdf`: `back\slash.pdf`,
+ "alm\u061c.pdf": `alm\u061c.pdf`,
+ "lrm\u200e.pdf": `lrm\u200e.pdf`,
+ "rlm\u200f.pdf": `rlm\u200f.pdf`,
+ "ls\u2028ps\u2029.pdf": `ls\u2028ps\u2029.pdf`,
"replacement\ufffd.pdf": "replacement\ufffd.pdf",
} {
if got := display(in); got != want {
@@ -84,3 +90,29 @@ func TestReviewEscapesHostileNames(t *testing.T) {
t.Errorf("undo review printed %s:\n%q", bad, undoOut)
}
}
+
+// TestSafeWriterEscapesButKeepsNewlines: everything written to stderr goes
+// through display line by line, so a quoted file name cannot act on the
+// terminal while messages keep their lines (review M5).
+func TestSafeWriterEscapesButKeepsNewlines(t *testing.T) {
+ var b strings.Builder
+ in := "a\x1b[2J\nb\u202e\n"
+ n, err := safeWriter{&b}.Write([]byte(in))
+ if err != nil || n != len(in) {
+ t.Fatalf("Write = %d, %v", n, err)
+ }
+ if got, want := b.String(), "a\\x1b[2J\nb\\u202e\n"; got != want {
+ t.Errorf("wrote %q, want %q", got, want)
+ }
+}
+
+// TestJSONSafeEscapesTerminalRunes: in an encoded JSON document, DEL, C1 and
+// bidirectional controls come out as \uXXXX escapes - the same string
+// values - and everything else is untouched (review terminal F4).
+func TestJSONSafeEscapesTerminalRunes(t *testing.T) {
+ in := "{\"rel\": \"c1\u009bx\u202ey\u007fz ż\"}"
+ want := `{"rel": "c1\u009bx\u202ey\u007fz ż"}`
+ if got := string(jsonSafe([]byte(in))); got != want {
+ t.Errorf("jsonSafe = %q, want %q", got, want)
+ }
+}