aboutsummaryrefslogtreecommitdiff
path: root/cmd/krino/display.go
blob: 52e83859c0b877b52f1af24a67b5771e33b110a3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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)
}