From a91b713dcec4d17f76155f0cd6b26903c59b9c19 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Mon, 14 Sep 2026 22:31:12 +0200 Subject: plan 10: stderr messages cannot be split by quoted newlines; config paths escaped; krino new refuses control characters; independent terminal oracle --- cmd/krino/display_test.go | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) (limited to 'cmd/krino/display_test.go') diff --git a/cmd/krino/display_test.go b/cmd/krino/display_test.go index 0f4cf90..a1b199c 100644 --- a/cmd/krino/display_test.go +++ b/cmd/krino/display_test.go @@ -6,15 +6,24 @@ import ( "fmt" "strings" "testing" - "unicode" "unicode/utf8" ) -// 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. +// terminalRunes is the test's own, hand-written list of code points a +// terminal acts on or that reorder what it shows: C0 controls, DEL, C1 +// controls, the Unicode bidirectional marks, embeddings, overrides and +// isolates (UAX #9), and the line and paragraph separators. It is written +// out, not derived from Go's tables or from display, so it can catch a +// class display misses (re-review term F3). +var terminalRunes = [][2]rune{ + {0x00, 0x1f}, {0x7f, 0x9f}, + {0x061c, 0x061c}, {0x200e, 0x200f}, {0x202a, 0x202e}, {0x2066, 0x2069}, + {0x2028, 0x2029}, +} + +// firstUnsafe names the first thing in s from terminalRunes (a newline +// allowed when allowNewline), or invalid UTF-8, or returns "" when there is +// none. func firstUnsafe(s string, allowNewline bool) string { if !utf8.ValidString(s) { return "invalid UTF-8" @@ -23,8 +32,10 @@ func firstUnsafe(s string, allowNewline bool) string { if r == '\n' && allowNewline { continue } - if unicode.IsControl(r) || unicode.Is(unicode.Bidi_Control, r) || unicode.In(r, unicode.Zl, unicode.Zp) { - return fmt.Sprintf("%U", r) + for _, span := range terminalRunes { + if r >= span[0] && r <= span[1] { + return fmt.Sprintf("%U", r) + } } } return "" @@ -92,8 +103,8 @@ func TestReviewEscapesHostileNames(t *testing.T) { } // 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). +// through display, so a quoted file name cannot act on the terminal; each +// Write keeps its final newline (review M5). func TestSafeWriterEscapesButKeepsNewlines(t *testing.T) { var b strings.Builder in := "a\x1b[2J\nb\u202e\n" @@ -101,7 +112,9 @@ func TestSafeWriterEscapesButKeepsNewlines(t *testing.T) { 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 { + // Only the Write's final newline ends a line; one inside it came from + // quoted text and could fake a line of krino's own (re-review term F1). + if got, want := b.String(), "a\\x1b[2J\\x0ab\\u202e\n"; got != want { t.Errorf("wrote %q, want %q", got, want) } } -- cgit v1.3