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.go54
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)
+}