1package text
2
3import (
4 "strings"
5 "unicode/utf8"
6)
7
8// EscapeItem hold the description of terminal escapes in a line.
9// 'item' is the actual escape command
10// 'pos' is the index in the rune array where the 'item' shall be inserted back.
11// For example, the escape item in "F\x1b33mox" is {"\x1b33m", 1}.
12type EscapeItem struct {
13 Item string
14 Pos int
15}
16
17// ExtractTermEscapes extract terminal escapes out of a line and returns a new
18// line without terminal escapes and a slice of escape items. The terminal escapes
19// can be inserted back into the new line at rune index 'item.pos' to recover the
20// original line.
21//
22// Required: The line shall not contain "\n"
23func ExtractTermEscapes(line string) (string, []EscapeItem) {
24 var termEscapes []EscapeItem
25 var line1 strings.Builder
26
27 pos := 0
28 item := ""
29 occupiedRuneCount := 0
30 inEscape := false
31 for i, r := range []rune(line) {
32 if r == '\x1b' {
33 pos = i
34 item = string(r)
35 inEscape = true
36 continue
37 }
38 if inEscape {
39 item += string(r)
40 if r == 'm' {
41 termEscapes = append(termEscapes, EscapeItem{item, pos - occupiedRuneCount})
42 occupiedRuneCount += utf8.RuneCountInString(item)
43 inEscape = false
44 }
45 continue
46 }
47 line1.WriteRune(r)
48 }
49
50 return line1.String(), termEscapes
51}
52
53// ApplyTermEscapes apply the extracted terminal escapes to the edited line.
54// Escape sequences need to be ordered by their position.
55// If the position is < 0, the escape is applied at the beginning of the line.
56// If the position is > len(line), the escape is applied at the end of the line.
57func ApplyTermEscapes(line string, escapes []EscapeItem) string {
58 if len(escapes) == 0 {
59 return line
60 }
61
62 var out strings.Builder
63
64 currPos := 0
65 currItem := 0
66 for _, r := range line {
67 for currItem < len(escapes) && currPos >= escapes[currItem].Pos {
68 out.WriteString(escapes[currItem].Item)
69 currItem++
70 }
71 out.WriteRune(r)
72 currPos++
73 }
74
75 // Don't forget the trailing escapes, if any.
76 for currItem < len(escapes) {
77 out.WriteString(escapes[currItem].Item)
78 currItem++
79 }
80
81 return out.String()
82}
83
84// OffsetEscapes is a utility function to offset the position of a
85// collection of EscapeItem.
86func OffsetEscapes(escapes []EscapeItem, offset int) []EscapeItem {
87 result := make([]EscapeItem, len(escapes))
88 for i, e := range escapes {
89 result[i] = EscapeItem{
90 Item: e.Item,
91 Pos: e.Pos + offset,
92 }
93 }
94 return result
95}