-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstrings.go
144 lines (131 loc) · 3.53 KB
/
strings.go
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package golib
import (
"bytes"
"sort"
"github.com/lunixbochs/vtclean"
"golang.org/x/text/width"
)
// StringLength returns the number of normalized utf8-runes within the cleaned string.
// Clean means the string is stripped of terminal escape characters and color codes.
func StringLength(str string) (strlen int) {
str = vtclean.Clean(str, false)
for str != "" {
_, rest, runeWidth := ReadRune(str)
str = rest
strlen += runeWidth
}
// Alternative:
// strlen = utf8.RuneCountInString(str)
// Alternative:
// var ia norm.Iter
// ia.InitString(norm.NFKD, str)
return
}
// ReadRune reads one utf8 rune from the input string and provides information
// about the character width of the read rune.
func ReadRune(input string) (theRune string, rest string, runeWidth int) {
prop, size := width.LookupString(input)
rest = input[size:]
theRune = input[:size]
switch prop.Kind() {
case width.EastAsianFullwidth, width.EastAsianHalfwidth, width.EastAsianWide:
runeWidth = 2
default:
runeWidth = 1
}
return
}
// Substring returns a substring of the given input string, but the indices
// iFrom and iTo point to normalized utf8-runes within the cleaned string.
// Clean means the string is stripped of terminal escape characters and color codes.
// The total number of normalized utf8 runes in the clean string can be obtained from
// the StringLength() function.
// All runes and special codes will be preserved in the output string.
func Substring(str string, iFrom int, iTo int) string {
// Find the start in the input string
buf := bytes.NewBuffer(make([]byte, 0, len(str)))
textWidth := 0
cleanedLen := 0
for str != "" && textWidth < iFrom {
runeStr, rest, runeWidth := ReadRune(str)
buf.Write([]byte(runeStr))
cleaned := vtclean.Clean(buf.String(), false)
if len(cleaned) > cleanedLen {
// A visible rune was added
cleanedLen = len(cleaned)
textWidth += runeWidth
}
if textWidth >= iFrom {
break
}
str = rest
}
iLen := iTo - iFrom
possibleColor := false
// Find the end in the input string
to := 0
suffix := []byte(str)
buf.Reset()
textWidth = 0
cleanedLen = 0
for str != "" && textWidth < iLen {
runeStr, rest, runeWidth := ReadRune(str)
buf.Write([]byte(runeStr))
cleaned := vtclean.Clean(buf.String(), false)
if len(cleaned) > cleanedLen {
// A visible rune was added
cleanedLen = len(cleaned)
textWidth += runeWidth
}
if len(cleaned) != buf.Len() {
// Contains invisible escape characters, possibly color codes
possibleColor = true
}
if textWidth > iLen {
if runeWidth == 2 {
// Splitting wide character in the end, pad with a space
suffix[to] = ' '
to++
}
break
}
str = rest
to += len(runeStr)
}
result := string(suffix[:to])
if possibleColor {
// Might contain color codes, make sure to disable colors at the end
result += "\033[0m"
}
return result
}
func EqualStrings(a, b []string) bool {
switch {
case len(a) != len(b):
return false
case len(a) == 0:
return true
case &a[0] == &b[0]:
// Compare the address of the array backing the slices
return true
}
// Last resort: compare every string pair
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
// RemoveDuplicates sorts the given string slice and returns a copy with all duplicate
// strings removed.
func RemoveDuplicates(strings []string) []string {
sort.Strings(strings)
result := make([]string, 0, len(strings))
for _, str := range strings {
if len(result) == 0 || str != result[len(result)-1] {
result = append(result, str)
}
}
return result
}