diff options
Diffstat (limited to 'cmd/krino/display.go')
| -rw-r--r-- | cmd/krino/display.go | 54 |
1 files changed, 54 insertions, 0 deletions
diff --git a/cmd/krino/display.go b/cmd/krino/display.go new file mode 100644 index 0000000..52e8385 --- /dev/null +++ b/cmd/krino/display.go @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package main + +import ( + "fmt" + "strings" + "unicode/utf8" +) + +// display makes s safe to print to a terminal (spec ยง15.1). File names, +// paths, reasons and tool messages come from outside krino: a control +// character in one could move the cursor, recolour or erase what krino +// prints, and a bidirectional control could reorder it, so a name could +// hide a step or fake one. Every C0 control, DEL, C1 control, Unicode +// bidirectional embedding, override or isolate, and every byte that is not +// valid UTF-8 is shown as an escape instead - \x1b for a byte, \u202e for +// a rune. Everything else, backslash included, is printed as it is. Text +// that is plain printable ASCII is returned without copying. +func display(s string) string { + plain := true + for i := 0; i < len(s); i++ { + if c := s[i]; c < 0x20 || c >= 0x7f { + plain = false + break + } + } + if plain { + return s + } + var b strings.Builder + for i := 0; i < len(s); { + r, size := utf8.DecodeRuneInString(s[i:]) + switch { + case r == utf8.RuneError && size == 1: + fmt.Fprintf(&b, `\x%02x`, s[i]) + case r < 0x20 || r == 0x7f: + fmt.Fprintf(&b, `\x%02x`, r) + case controlRune(r): + fmt.Fprintf(&b, `\u%04x`, r) + default: + b.WriteString(s[i : i+size]) + } + i += size + } + 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. +func controlRune(r rune) bool { + return (r >= 0x80 && r <= 0x9f) || (r >= 0x202a && r <= 0x202e) || (r >= 0x2066 && r <= 0x2069) +} |
