doing 2.0.19 → 2.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/Gemfile.lock +15 -5
- data/README.md +1 -1
- data/bin/doing +2 -18
- data/doing.gemspec +5 -4
- data/doing.rdoc +2 -2
- data/generate_completions.sh +3 -3
- data/lib/doing/cli_status.rb +6 -2
- data/lib/doing/completion/bash_completion.rb +185 -0
- data/lib/doing/completion/fish_completion.rb +175 -0
- data/lib/doing/completion/string.rb +17 -0
- data/lib/doing/completion/zsh_completion.rb +140 -0
- data/lib/doing/completion.rb +39 -0
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +19 -5
- data/lib/doing.rb +1 -1
- data/lib/helpers/fzf/.goreleaser.yml +119 -0
- data/lib/helpers/fzf/.rubocop.yml +28 -0
- data/lib/helpers/fzf/ADVANCED.md +565 -0
- data/lib/helpers/fzf/BUILD.md +49 -0
- data/lib/helpers/fzf/CHANGELOG.md +1193 -0
- data/lib/helpers/fzf/Dockerfile +11 -0
- data/lib/helpers/fzf/LICENSE +21 -0
- data/lib/helpers/fzf/Makefile +166 -0
- data/lib/helpers/fzf/README-VIM.md +486 -0
- data/lib/helpers/fzf/README.md +712 -0
- data/lib/helpers/fzf/bin/fzf-tmux +233 -0
- data/lib/helpers/fzf/doc/fzf.txt +512 -0
- data/lib/helpers/fzf/go.mod +17 -0
- data/lib/helpers/fzf/go.sum +31 -0
- data/lib/helpers/fzf/install +382 -0
- data/lib/helpers/fzf/install.ps1 +65 -0
- data/lib/helpers/fzf/main.go +14 -0
- data/lib/helpers/fzf/man/man1/fzf-tmux.1 +68 -0
- data/lib/helpers/fzf/man/man1/fzf.1 +1001 -0
- data/lib/helpers/fzf/plugin/fzf.vim +1048 -0
- data/lib/helpers/fzf/shell/completion.bash +381 -0
- data/lib/helpers/fzf/shell/completion.zsh +329 -0
- data/lib/helpers/fzf/shell/key-bindings.bash +96 -0
- data/lib/helpers/fzf/shell/key-bindings.fish +172 -0
- data/lib/helpers/fzf/shell/key-bindings.zsh +114 -0
- data/lib/helpers/fzf/src/LICENSE +21 -0
- data/lib/helpers/fzf/src/algo/algo.go +884 -0
- data/lib/helpers/fzf/src/algo/algo_test.go +197 -0
- data/lib/helpers/fzf/src/algo/normalize.go +492 -0
- data/lib/helpers/fzf/src/ansi.go +409 -0
- data/lib/helpers/fzf/src/ansi_test.go +427 -0
- data/lib/helpers/fzf/src/cache.go +81 -0
- data/lib/helpers/fzf/src/cache_test.go +39 -0
- data/lib/helpers/fzf/src/chunklist.go +89 -0
- data/lib/helpers/fzf/src/chunklist_test.go +80 -0
- data/lib/helpers/fzf/src/constants.go +85 -0
- data/lib/helpers/fzf/src/core.go +351 -0
- data/lib/helpers/fzf/src/history.go +96 -0
- data/lib/helpers/fzf/src/history_test.go +68 -0
- data/lib/helpers/fzf/src/item.go +44 -0
- data/lib/helpers/fzf/src/item_test.go +23 -0
- data/lib/helpers/fzf/src/matcher.go +235 -0
- data/lib/helpers/fzf/src/merger.go +120 -0
- data/lib/helpers/fzf/src/merger_test.go +88 -0
- data/lib/helpers/fzf/src/options.go +1691 -0
- data/lib/helpers/fzf/src/options_test.go +457 -0
- data/lib/helpers/fzf/src/pattern.go +425 -0
- data/lib/helpers/fzf/src/pattern_test.go +209 -0
- data/lib/helpers/fzf/src/protector/protector.go +8 -0
- data/lib/helpers/fzf/src/protector/protector_openbsd.go +10 -0
- data/lib/helpers/fzf/src/reader.go +201 -0
- data/lib/helpers/fzf/src/reader_test.go +63 -0
- data/lib/helpers/fzf/src/result.go +243 -0
- data/lib/helpers/fzf/src/result_others.go +16 -0
- data/lib/helpers/fzf/src/result_test.go +159 -0
- data/lib/helpers/fzf/src/result_x86.go +16 -0
- data/lib/helpers/fzf/src/terminal.go +2832 -0
- data/lib/helpers/fzf/src/terminal_test.go +638 -0
- data/lib/helpers/fzf/src/terminal_unix.go +26 -0
- data/lib/helpers/fzf/src/terminal_windows.go +45 -0
- data/lib/helpers/fzf/src/tokenizer.go +253 -0
- data/lib/helpers/fzf/src/tokenizer_test.go +112 -0
- data/lib/helpers/fzf/src/tui/dummy.go +46 -0
- data/lib/helpers/fzf/src/tui/light.go +987 -0
- data/lib/helpers/fzf/src/tui/light_unix.go +110 -0
- data/lib/helpers/fzf/src/tui/light_windows.go +145 -0
- data/lib/helpers/fzf/src/tui/tcell.go +721 -0
- data/lib/helpers/fzf/src/tui/tcell_test.go +392 -0
- data/lib/helpers/fzf/src/tui/ttyname_unix.go +47 -0
- data/lib/helpers/fzf/src/tui/ttyname_windows.go +14 -0
- data/lib/helpers/fzf/src/tui/tui.go +625 -0
- data/lib/helpers/fzf/src/tui/tui_test.go +20 -0
- data/lib/helpers/fzf/src/util/atomicbool.go +34 -0
- data/lib/helpers/fzf/src/util/atomicbool_test.go +17 -0
- data/lib/helpers/fzf/src/util/chars.go +198 -0
- data/lib/helpers/fzf/src/util/chars_test.go +46 -0
- data/lib/helpers/fzf/src/util/eventbox.go +96 -0
- data/lib/helpers/fzf/src/util/eventbox_test.go +61 -0
- data/lib/helpers/fzf/src/util/slab.go +12 -0
- data/lib/helpers/fzf/src/util/util.go +138 -0
- data/lib/helpers/fzf/src/util/util_test.go +40 -0
- data/lib/helpers/fzf/src/util/util_unix.go +47 -0
- data/lib/helpers/fzf/src/util/util_windows.go +83 -0
- data/lib/helpers/fzf/test/fzf.vader +175 -0
- data/lib/helpers/fzf/test/test_go.rb +2626 -0
- data/lib/helpers/fzf/uninstall +117 -0
- data/scripts/generate_bash_completions.rb +6 -12
- data/scripts/generate_fish_completions.rb +7 -16
- data/scripts/generate_zsh_completions.rb +6 -15
- metadata +144 -9
@@ -0,0 +1,427 @@
|
|
1
|
+
package fzf
|
2
|
+
|
3
|
+
import (
|
4
|
+
"math/rand"
|
5
|
+
"regexp"
|
6
|
+
"strings"
|
7
|
+
"testing"
|
8
|
+
"unicode/utf8"
|
9
|
+
|
10
|
+
"github.com/junegunn/fzf/src/tui"
|
11
|
+
)
|
12
|
+
|
13
|
+
// The following regular expression will include not all but most of the
|
14
|
+
// frequently used ANSI sequences. This regex is used as a reference for
|
15
|
+
// testing nextAnsiEscapeSequence().
|
16
|
+
//
|
17
|
+
// References:
|
18
|
+
// - https://github.com/gnachman/iTerm2
|
19
|
+
// - https://web.archive.org/web/20090204053813/http://ascii-table.com/ansi-escape-sequences.php
|
20
|
+
// (archived from http://ascii-table.com/ansi-escape-sequences.php)
|
21
|
+
// - https://web.archive.org/web/20090227051140/http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
22
|
+
// (archived from http://ascii-table.com/ansi-escape-sequences-vt-100.php)
|
23
|
+
// - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
24
|
+
// - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
25
|
+
var ansiRegexReference = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
|
26
|
+
|
27
|
+
func testParserReference(t testing.TB, str string) {
|
28
|
+
t.Helper()
|
29
|
+
|
30
|
+
toSlice := func(start, end int) []int {
|
31
|
+
if start == -1 {
|
32
|
+
return nil
|
33
|
+
}
|
34
|
+
return []int{start, end}
|
35
|
+
}
|
36
|
+
|
37
|
+
s := str
|
38
|
+
for i := 0; ; i++ {
|
39
|
+
got := toSlice(nextAnsiEscapeSequence(s))
|
40
|
+
exp := ansiRegexReference.FindStringIndex(s)
|
41
|
+
|
42
|
+
equal := len(got) == len(exp)
|
43
|
+
if equal {
|
44
|
+
for i := 0; i < len(got); i++ {
|
45
|
+
if got[i] != exp[i] {
|
46
|
+
equal = false
|
47
|
+
break
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
51
|
+
if !equal {
|
52
|
+
var exps, gots []rune
|
53
|
+
if len(got) == 2 {
|
54
|
+
gots = []rune(s[got[0]:got[1]])
|
55
|
+
}
|
56
|
+
if len(exp) == 2 {
|
57
|
+
exps = []rune(s[exp[0]:exp[1]])
|
58
|
+
}
|
59
|
+
t.Errorf("%d: %q: got: %v (%q) want: %v (%q)", i, s, got, gots, exp, exps)
|
60
|
+
return
|
61
|
+
}
|
62
|
+
if len(exp) == 0 {
|
63
|
+
return
|
64
|
+
}
|
65
|
+
s = s[exp[1]:]
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
func TestNextAnsiEscapeSequence(t *testing.T) {
|
70
|
+
testStrs := []string{
|
71
|
+
"\x1b[0mhello world",
|
72
|
+
"\x1b[1mhello world",
|
73
|
+
"椙\x1b[1m椙",
|
74
|
+
"椙\x1b[1椙m椙",
|
75
|
+
"\x1b[1mhello \x1b[mw\x1b7o\x1b8r\x1b(Bl\x1b[2@d",
|
76
|
+
"\x1b[1mhello \x1b[Kworld",
|
77
|
+
"hello \x1b[34;45;1mworld",
|
78
|
+
"hello \x1b[34;45;1mwor\x1b[34;45;1mld",
|
79
|
+
"hello \x1b[34;45;1mwor\x1b[0mld",
|
80
|
+
"hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md",
|
81
|
+
"hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md",
|
82
|
+
"hello \x1b[32;1mworld",
|
83
|
+
"hello world",
|
84
|
+
"hello \x1b[0;38;5;200;48;5;100mworld",
|
85
|
+
"\x1b椙",
|
86
|
+
"椙\x08",
|
87
|
+
"\n\x08",
|
88
|
+
"X\x08",
|
89
|
+
"",
|
90
|
+
"\x1b]4;3;rgb:aa/bb/cc\x07 ",
|
91
|
+
"\x1b]4;3;rgb:aa/bb/cc\x1b\\ ",
|
92
|
+
ansiBenchmarkString,
|
93
|
+
}
|
94
|
+
|
95
|
+
for _, s := range testStrs {
|
96
|
+
testParserReference(t, s)
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
func TestNextAnsiEscapeSequence_Fuzz_Modified(t *testing.T) {
|
101
|
+
t.Parallel()
|
102
|
+
if testing.Short() {
|
103
|
+
t.Skip("short test")
|
104
|
+
}
|
105
|
+
|
106
|
+
testStrs := []string{
|
107
|
+
"\x1b[0mhello world",
|
108
|
+
"\x1b[1mhello world",
|
109
|
+
"椙\x1b[1m椙",
|
110
|
+
"椙\x1b[1椙m椙",
|
111
|
+
"\x1b[1mhello \x1b[mw\x1b7o\x1b8r\x1b(Bl\x1b[2@d",
|
112
|
+
"\x1b[1mhello \x1b[Kworld",
|
113
|
+
"hello \x1b[34;45;1mworld",
|
114
|
+
"hello \x1b[34;45;1mwor\x1b[34;45;1mld",
|
115
|
+
"hello \x1b[34;45;1mwor\x1b[0mld",
|
116
|
+
"hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md",
|
117
|
+
"hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md",
|
118
|
+
"hello \x1b[32;1mworld",
|
119
|
+
"hello world",
|
120
|
+
"hello \x1b[0;38;5;200;48;5;100mworld",
|
121
|
+
ansiBenchmarkString,
|
122
|
+
}
|
123
|
+
|
124
|
+
replacementBytes := [...]rune{'\x0e', '\x0f', '\x1b', '\x08'}
|
125
|
+
|
126
|
+
modifyString := func(s string, rr *rand.Rand) string {
|
127
|
+
n := rr.Intn(len(s))
|
128
|
+
b := []rune(s)
|
129
|
+
for ; n >= 0 && len(b) != 0; n-- {
|
130
|
+
i := rr.Intn(len(b))
|
131
|
+
switch x := rr.Intn(4); x {
|
132
|
+
case 0:
|
133
|
+
b = append(b[:i], b[i+1:]...)
|
134
|
+
case 1:
|
135
|
+
j := rr.Intn(len(replacementBytes) - 1)
|
136
|
+
b[i] = replacementBytes[j]
|
137
|
+
case 2:
|
138
|
+
x := rune(rr.Intn(utf8.MaxRune))
|
139
|
+
for !utf8.ValidRune(x) {
|
140
|
+
x = rune(rr.Intn(utf8.MaxRune))
|
141
|
+
}
|
142
|
+
b[i] = x
|
143
|
+
case 3:
|
144
|
+
b[i] = rune(rr.Intn(utf8.MaxRune)) // potentially invalid
|
145
|
+
default:
|
146
|
+
t.Fatalf("unsupported value: %d", x)
|
147
|
+
}
|
148
|
+
}
|
149
|
+
return string(b)
|
150
|
+
}
|
151
|
+
|
152
|
+
rr := rand.New(rand.NewSource(1))
|
153
|
+
for _, s := range testStrs {
|
154
|
+
for i := 1_000; i >= 0; i-- {
|
155
|
+
testParserReference(t, modifyString(s, rr))
|
156
|
+
}
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
func TestNextAnsiEscapeSequence_Fuzz_Random(t *testing.T) {
|
161
|
+
t.Parallel()
|
162
|
+
|
163
|
+
if testing.Short() {
|
164
|
+
t.Skip("short test")
|
165
|
+
}
|
166
|
+
|
167
|
+
randomString := func(rr *rand.Rand) string {
|
168
|
+
numChars := rand.Intn(50)
|
169
|
+
codePoints := make([]rune, numChars)
|
170
|
+
for i := 0; i < len(codePoints); i++ {
|
171
|
+
var r rune
|
172
|
+
for n := 0; n < 1000; n++ {
|
173
|
+
r = rune(rr.Intn(utf8.MaxRune))
|
174
|
+
// Allow 10% of runes to be invalid
|
175
|
+
if utf8.ValidRune(r) || rr.Float64() < 0.10 {
|
176
|
+
break
|
177
|
+
}
|
178
|
+
}
|
179
|
+
codePoints[i] = r
|
180
|
+
}
|
181
|
+
return string(codePoints)
|
182
|
+
}
|
183
|
+
|
184
|
+
rr := rand.New(rand.NewSource(1))
|
185
|
+
for i := 0; i < 100_000; i++ {
|
186
|
+
testParserReference(t, randomString(rr))
|
187
|
+
}
|
188
|
+
}
|
189
|
+
|
190
|
+
func TestExtractColor(t *testing.T) {
|
191
|
+
assert := func(offset ansiOffset, b int32, e int32, fg tui.Color, bg tui.Color, bold bool) {
|
192
|
+
var attr tui.Attr
|
193
|
+
if bold {
|
194
|
+
attr = tui.Bold
|
195
|
+
}
|
196
|
+
if offset.offset[0] != b || offset.offset[1] != e ||
|
197
|
+
offset.color.fg != fg || offset.color.bg != bg || offset.color.attr != attr {
|
198
|
+
t.Error(offset, b, e, fg, bg, attr)
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
src := "hello world"
|
203
|
+
var state *ansiState
|
204
|
+
clean := "\x1b[0m"
|
205
|
+
check := func(assertion func(ansiOffsets *[]ansiOffset, state *ansiState)) {
|
206
|
+
output, ansiOffsets, newState := extractColor(src, state, nil)
|
207
|
+
state = newState
|
208
|
+
if output != "hello world" {
|
209
|
+
t.Errorf("Invalid output: %s %v", output, []rune(output))
|
210
|
+
}
|
211
|
+
t.Log(src, ansiOffsets, clean)
|
212
|
+
assertion(ansiOffsets, state)
|
213
|
+
}
|
214
|
+
|
215
|
+
check(func(offsets *[]ansiOffset, state *ansiState) {
|
216
|
+
if offsets != nil {
|
217
|
+
t.Fail()
|
218
|
+
}
|
219
|
+
})
|
220
|
+
|
221
|
+
state = nil
|
222
|
+
src = "\x1b[0mhello world"
|
223
|
+
check(func(offsets *[]ansiOffset, state *ansiState) {
|
224
|
+
if offsets != nil {
|
225
|
+
t.Fail()
|
226
|
+
}
|
227
|
+
})
|
228
|
+
|
229
|
+
state = nil
|
230
|
+
src = "\x1b[1mhello world"
|
231
|
+
check(func(offsets *[]ansiOffset, state *ansiState) {
|
232
|
+
if len(*offsets) != 1 {
|
233
|
+
t.Fail()
|
234
|
+
}
|
235
|
+
assert((*offsets)[0], 0, 11, -1, -1, true)
|
236
|
+
})
|
237
|
+
|
238
|
+
state = nil
|
239
|
+
src = "\x1b[1mhello \x1b[mw\x1b7o\x1b8r\x1b(Bl\x1b[2@d"
|
240
|
+
check(func(offsets *[]ansiOffset, state *ansiState) {
|
241
|
+
if len(*offsets) != 1 {
|
242
|
+
t.Fail()
|
243
|
+
}
|
244
|
+
assert((*offsets)[0], 0, 6, -1, -1, true)
|
245
|
+
})
|
246
|
+
|
247
|
+
state = nil
|
248
|
+
src = "\x1b[1mhello \x1b[Kworld"
|
249
|
+
check(func(offsets *[]ansiOffset, state *ansiState) {
|
250
|
+
if len(*offsets) != 1 {
|
251
|
+
t.Fail()
|
252
|
+
}
|
253
|
+
assert((*offsets)[0], 0, 11, -1, -1, true)
|
254
|
+
})
|
255
|
+
|
256
|
+
state = nil
|
257
|
+
src = "hello \x1b[34;45;1mworld"
|
258
|
+
check(func(offsets *[]ansiOffset, state *ansiState) {
|
259
|
+
if len(*offsets) != 1 {
|
260
|
+
t.Fail()
|
261
|
+
}
|
262
|
+
assert((*offsets)[0], 6, 11, 4, 5, true)
|
263
|
+
})
|
264
|
+
|
265
|
+
state = nil
|
266
|
+
src = "hello \x1b[34;45;1mwor\x1b[34;45;1mld"
|
267
|
+
check(func(offsets *[]ansiOffset, state *ansiState) {
|
268
|
+
if len(*offsets) != 1 {
|
269
|
+
t.Fail()
|
270
|
+
}
|
271
|
+
assert((*offsets)[0], 6, 11, 4, 5, true)
|
272
|
+
})
|
273
|
+
|
274
|
+
state = nil
|
275
|
+
src = "hello \x1b[34;45;1mwor\x1b[0mld"
|
276
|
+
check(func(offsets *[]ansiOffset, state *ansiState) {
|
277
|
+
if len(*offsets) != 1 {
|
278
|
+
t.Fail()
|
279
|
+
}
|
280
|
+
assert((*offsets)[0], 6, 9, 4, 5, true)
|
281
|
+
})
|
282
|
+
|
283
|
+
state = nil
|
284
|
+
src = "hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md"
|
285
|
+
check(func(offsets *[]ansiOffset, state *ansiState) {
|
286
|
+
if len(*offsets) != 3 {
|
287
|
+
t.Fail()
|
288
|
+
}
|
289
|
+
assert((*offsets)[0], 6, 8, 4, 233, true)
|
290
|
+
assert((*offsets)[1], 8, 9, 161, 233, true)
|
291
|
+
assert((*offsets)[2], 10, 11, 161, -1, false)
|
292
|
+
})
|
293
|
+
|
294
|
+
// {38,48};5;{38,48}
|
295
|
+
state = nil
|
296
|
+
src = "hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md"
|
297
|
+
check(func(offsets *[]ansiOffset, state *ansiState) {
|
298
|
+
if len(*offsets) != 2 {
|
299
|
+
t.Fail()
|
300
|
+
}
|
301
|
+
assert((*offsets)[0], 6, 9, 38, 48, true)
|
302
|
+
assert((*offsets)[1], 9, 10, 48, 38, true)
|
303
|
+
})
|
304
|
+
|
305
|
+
src = "hello \x1b[32;1mworld"
|
306
|
+
check(func(offsets *[]ansiOffset, state *ansiState) {
|
307
|
+
if len(*offsets) != 1 {
|
308
|
+
t.Fail()
|
309
|
+
}
|
310
|
+
if state.fg != 2 || state.bg != -1 || state.attr == 0 {
|
311
|
+
t.Fail()
|
312
|
+
}
|
313
|
+
assert((*offsets)[0], 6, 11, 2, -1, true)
|
314
|
+
})
|
315
|
+
|
316
|
+
src = "hello world"
|
317
|
+
check(func(offsets *[]ansiOffset, state *ansiState) {
|
318
|
+
if len(*offsets) != 1 {
|
319
|
+
t.Fail()
|
320
|
+
}
|
321
|
+
if state.fg != 2 || state.bg != -1 || state.attr == 0 {
|
322
|
+
t.Fail()
|
323
|
+
}
|
324
|
+
assert((*offsets)[0], 0, 11, 2, -1, true)
|
325
|
+
})
|
326
|
+
|
327
|
+
src = "hello \x1b[0;38;5;200;48;5;100mworld"
|
328
|
+
check(func(offsets *[]ansiOffset, state *ansiState) {
|
329
|
+
if len(*offsets) != 2 {
|
330
|
+
t.Fail()
|
331
|
+
}
|
332
|
+
if state.fg != 200 || state.bg != 100 || state.attr > 0 {
|
333
|
+
t.Fail()
|
334
|
+
}
|
335
|
+
assert((*offsets)[0], 0, 6, 2, -1, true)
|
336
|
+
assert((*offsets)[1], 6, 11, 200, 100, false)
|
337
|
+
})
|
338
|
+
}
|
339
|
+
|
340
|
+
func TestAnsiCodeStringConversion(t *testing.T) {
|
341
|
+
assert := func(code string, prevState *ansiState, expected string) {
|
342
|
+
state := interpretCode(code, prevState)
|
343
|
+
if expected != state.ToString() {
|
344
|
+
t.Errorf("expected: %s, actual: %s",
|
345
|
+
strings.Replace(expected, "\x1b[", "\\x1b[", -1),
|
346
|
+
strings.Replace(state.ToString(), "\x1b[", "\\x1b[", -1))
|
347
|
+
}
|
348
|
+
}
|
349
|
+
assert("\x1b[m", nil, "")
|
350
|
+
assert("\x1b[m", &ansiState{attr: tui.Blink, lbg: -1}, "")
|
351
|
+
|
352
|
+
assert("\x1b[31m", nil, "\x1b[31;49m")
|
353
|
+
assert("\x1b[41m", nil, "\x1b[39;41m")
|
354
|
+
|
355
|
+
assert("\x1b[92m", nil, "\x1b[92;49m")
|
356
|
+
assert("\x1b[102m", nil, "\x1b[39;102m")
|
357
|
+
|
358
|
+
assert("\x1b[31m", &ansiState{fg: 4, bg: 4, lbg: -1}, "\x1b[31;44m")
|
359
|
+
assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse, lbg: -1}, "\x1b[1;2;7;31;49m")
|
360
|
+
assert("\x1b[38;5;100;48;5;200m", nil, "\x1b[38;5;100;48;5;200m")
|
361
|
+
assert("\x1b[48;5;100;38;5;200m", nil, "\x1b[38;5;200;48;5;100m")
|
362
|
+
assert("\x1b[48;5;100;38;2;10;20;30;1m", nil, "\x1b[1;38;2;10;20;30;48;5;100m")
|
363
|
+
assert("\x1b[48;5;100;38;2;10;20;30;7m",
|
364
|
+
&ansiState{attr: tui.Dim | tui.Italic, fg: 1, bg: 1},
|
365
|
+
"\x1b[2;3;7;38;2;10;20;30;48;5;100m")
|
366
|
+
}
|
367
|
+
|
368
|
+
func TestParseAnsiCode(t *testing.T) {
|
369
|
+
tests := []struct {
|
370
|
+
In, Exp string
|
371
|
+
N int
|
372
|
+
}{
|
373
|
+
{"123", "", 123},
|
374
|
+
{"1a", "", -1},
|
375
|
+
{"1a;12", "12", -1},
|
376
|
+
{"12;a", "a", 12},
|
377
|
+
{"-2", "", -1},
|
378
|
+
}
|
379
|
+
for _, x := range tests {
|
380
|
+
n, s := parseAnsiCode(x.In)
|
381
|
+
if n != x.N || s != x.Exp {
|
382
|
+
t.Fatalf("%q: got: (%d %q) want: (%d %q)", x.In, n, s, x.N, x.Exp)
|
383
|
+
}
|
384
|
+
}
|
385
|
+
}
|
386
|
+
|
387
|
+
// kernel/bpf/preload/iterators/README
|
388
|
+
const ansiBenchmarkString = "\x1b[38;5;81m\x1b[01;31m\x1b[Kkernel/\x1b[0m\x1b[38;5;81mbpf/" +
|
389
|
+
"\x1b[0m\x1b[38;5;81mpreload/\x1b[0m\x1b[38;5;81miterators/" +
|
390
|
+
"\x1b[0m\x1b[38;5;149mMakefile\x1b[m\x1b[K\x1b[0m"
|
391
|
+
|
392
|
+
func BenchmarkNextAnsiEscapeSequence(b *testing.B) {
|
393
|
+
b.SetBytes(int64(len(ansiBenchmarkString)))
|
394
|
+
for i := 0; i < b.N; i++ {
|
395
|
+
s := ansiBenchmarkString
|
396
|
+
for {
|
397
|
+
_, o := nextAnsiEscapeSequence(s)
|
398
|
+
if o == -1 {
|
399
|
+
break
|
400
|
+
}
|
401
|
+
s = s[o:]
|
402
|
+
}
|
403
|
+
}
|
404
|
+
}
|
405
|
+
|
406
|
+
// Baseline test to compare the speed of nextAnsiEscapeSequence() to the
|
407
|
+
// previously used regex based implementation.
|
408
|
+
func BenchmarkNextAnsiEscapeSequence_Regex(b *testing.B) {
|
409
|
+
b.SetBytes(int64(len(ansiBenchmarkString)))
|
410
|
+
for i := 0; i < b.N; i++ {
|
411
|
+
s := ansiBenchmarkString
|
412
|
+
for {
|
413
|
+
a := ansiRegexReference.FindStringIndex(s)
|
414
|
+
if len(a) == 0 {
|
415
|
+
break
|
416
|
+
}
|
417
|
+
s = s[a[1]:]
|
418
|
+
}
|
419
|
+
}
|
420
|
+
}
|
421
|
+
|
422
|
+
func BenchmarkExtractColor(b *testing.B) {
|
423
|
+
b.SetBytes(int64(len(ansiBenchmarkString)))
|
424
|
+
for i := 0; i < b.N; i++ {
|
425
|
+
extractColor(ansiBenchmarkString, nil, nil)
|
426
|
+
}
|
427
|
+
}
|
@@ -0,0 +1,81 @@
|
|
1
|
+
package fzf
|
2
|
+
|
3
|
+
import "sync"
|
4
|
+
|
5
|
+
// queryCache associates strings to lists of items
|
6
|
+
type queryCache map[string][]Result
|
7
|
+
|
8
|
+
// ChunkCache associates Chunk and query string to lists of items
|
9
|
+
type ChunkCache struct {
|
10
|
+
mutex sync.Mutex
|
11
|
+
cache map[*Chunk]*queryCache
|
12
|
+
}
|
13
|
+
|
14
|
+
// NewChunkCache returns a new ChunkCache
|
15
|
+
func NewChunkCache() ChunkCache {
|
16
|
+
return ChunkCache{sync.Mutex{}, make(map[*Chunk]*queryCache)}
|
17
|
+
}
|
18
|
+
|
19
|
+
// Add adds the list to the cache
|
20
|
+
func (cc *ChunkCache) Add(chunk *Chunk, key string, list []Result) {
|
21
|
+
if len(key) == 0 || !chunk.IsFull() || len(list) > queryCacheMax {
|
22
|
+
return
|
23
|
+
}
|
24
|
+
|
25
|
+
cc.mutex.Lock()
|
26
|
+
defer cc.mutex.Unlock()
|
27
|
+
|
28
|
+
qc, ok := cc.cache[chunk]
|
29
|
+
if !ok {
|
30
|
+
cc.cache[chunk] = &queryCache{}
|
31
|
+
qc = cc.cache[chunk]
|
32
|
+
}
|
33
|
+
(*qc)[key] = list
|
34
|
+
}
|
35
|
+
|
36
|
+
// Lookup is called to lookup ChunkCache
|
37
|
+
func (cc *ChunkCache) Lookup(chunk *Chunk, key string) []Result {
|
38
|
+
if len(key) == 0 || !chunk.IsFull() {
|
39
|
+
return nil
|
40
|
+
}
|
41
|
+
|
42
|
+
cc.mutex.Lock()
|
43
|
+
defer cc.mutex.Unlock()
|
44
|
+
|
45
|
+
qc, ok := cc.cache[chunk]
|
46
|
+
if ok {
|
47
|
+
list, ok := (*qc)[key]
|
48
|
+
if ok {
|
49
|
+
return list
|
50
|
+
}
|
51
|
+
}
|
52
|
+
return nil
|
53
|
+
}
|
54
|
+
|
55
|
+
func (cc *ChunkCache) Search(chunk *Chunk, key string) []Result {
|
56
|
+
if len(key) == 0 || !chunk.IsFull() {
|
57
|
+
return nil
|
58
|
+
}
|
59
|
+
|
60
|
+
cc.mutex.Lock()
|
61
|
+
defer cc.mutex.Unlock()
|
62
|
+
|
63
|
+
qc, ok := cc.cache[chunk]
|
64
|
+
if !ok {
|
65
|
+
return nil
|
66
|
+
}
|
67
|
+
|
68
|
+
for idx := 1; idx < len(key); idx++ {
|
69
|
+
// [---------| ] | [ |---------]
|
70
|
+
// [--------| ] | [ |--------]
|
71
|
+
// [-------| ] | [ |-------]
|
72
|
+
prefix := key[:len(key)-idx]
|
73
|
+
suffix := key[idx:]
|
74
|
+
for _, substr := range [2]string{prefix, suffix} {
|
75
|
+
if cached, found := (*qc)[substr]; found {
|
76
|
+
return cached
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
return nil
|
81
|
+
}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
package fzf
|
2
|
+
|
3
|
+
import "testing"
|
4
|
+
|
5
|
+
func TestChunkCache(t *testing.T) {
|
6
|
+
cache := NewChunkCache()
|
7
|
+
chunk1p := &Chunk{}
|
8
|
+
chunk2p := &Chunk{count: chunkSize}
|
9
|
+
items1 := []Result{{}}
|
10
|
+
items2 := []Result{{}, {}}
|
11
|
+
cache.Add(chunk1p, "foo", items1)
|
12
|
+
cache.Add(chunk2p, "foo", items1)
|
13
|
+
cache.Add(chunk2p, "bar", items2)
|
14
|
+
|
15
|
+
{ // chunk1 is not full
|
16
|
+
cached := cache.Lookup(chunk1p, "foo")
|
17
|
+
if cached != nil {
|
18
|
+
t.Error("Cached disabled for non-empty chunks", cached)
|
19
|
+
}
|
20
|
+
}
|
21
|
+
{
|
22
|
+
cached := cache.Lookup(chunk2p, "foo")
|
23
|
+
if cached == nil || len(cached) != 1 {
|
24
|
+
t.Error("Expected 1 item cached", cached)
|
25
|
+
}
|
26
|
+
}
|
27
|
+
{
|
28
|
+
cached := cache.Lookup(chunk2p, "bar")
|
29
|
+
if cached == nil || len(cached) != 2 {
|
30
|
+
t.Error("Expected 2 items cached", cached)
|
31
|
+
}
|
32
|
+
}
|
33
|
+
{
|
34
|
+
cached := cache.Lookup(chunk1p, "foobar")
|
35
|
+
if cached != nil {
|
36
|
+
t.Error("Expected 0 item cached", cached)
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
@@ -0,0 +1,89 @@
|
|
1
|
+
package fzf
|
2
|
+
|
3
|
+
import "sync"
|
4
|
+
|
5
|
+
// Chunk is a list of Items whose size has the upper limit of chunkSize
|
6
|
+
type Chunk struct {
|
7
|
+
items [chunkSize]Item
|
8
|
+
count int
|
9
|
+
}
|
10
|
+
|
11
|
+
// ItemBuilder is a closure type that builds Item object from byte array
|
12
|
+
type ItemBuilder func(*Item, []byte) bool
|
13
|
+
|
14
|
+
// ChunkList is a list of Chunks
|
15
|
+
type ChunkList struct {
|
16
|
+
chunks []*Chunk
|
17
|
+
mutex sync.Mutex
|
18
|
+
trans ItemBuilder
|
19
|
+
}
|
20
|
+
|
21
|
+
// NewChunkList returns a new ChunkList
|
22
|
+
func NewChunkList(trans ItemBuilder) *ChunkList {
|
23
|
+
return &ChunkList{
|
24
|
+
chunks: []*Chunk{},
|
25
|
+
mutex: sync.Mutex{},
|
26
|
+
trans: trans}
|
27
|
+
}
|
28
|
+
|
29
|
+
func (c *Chunk) push(trans ItemBuilder, data []byte) bool {
|
30
|
+
if trans(&c.items[c.count], data) {
|
31
|
+
c.count++
|
32
|
+
return true
|
33
|
+
}
|
34
|
+
return false
|
35
|
+
}
|
36
|
+
|
37
|
+
// IsFull returns true if the Chunk is full
|
38
|
+
func (c *Chunk) IsFull() bool {
|
39
|
+
return c.count == chunkSize
|
40
|
+
}
|
41
|
+
|
42
|
+
func (cl *ChunkList) lastChunk() *Chunk {
|
43
|
+
return cl.chunks[len(cl.chunks)-1]
|
44
|
+
}
|
45
|
+
|
46
|
+
// CountItems returns the total number of Items
|
47
|
+
func CountItems(cs []*Chunk) int {
|
48
|
+
if len(cs) == 0 {
|
49
|
+
return 0
|
50
|
+
}
|
51
|
+
return chunkSize*(len(cs)-1) + cs[len(cs)-1].count
|
52
|
+
}
|
53
|
+
|
54
|
+
// Push adds the item to the list
|
55
|
+
func (cl *ChunkList) Push(data []byte) bool {
|
56
|
+
cl.mutex.Lock()
|
57
|
+
|
58
|
+
if len(cl.chunks) == 0 || cl.lastChunk().IsFull() {
|
59
|
+
cl.chunks = append(cl.chunks, &Chunk{})
|
60
|
+
}
|
61
|
+
|
62
|
+
ret := cl.lastChunk().push(cl.trans, data)
|
63
|
+
cl.mutex.Unlock()
|
64
|
+
return ret
|
65
|
+
}
|
66
|
+
|
67
|
+
// Clear clears the data
|
68
|
+
func (cl *ChunkList) Clear() {
|
69
|
+
cl.mutex.Lock()
|
70
|
+
cl.chunks = nil
|
71
|
+
cl.mutex.Unlock()
|
72
|
+
}
|
73
|
+
|
74
|
+
// Snapshot returns immutable snapshot of the ChunkList
|
75
|
+
func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
|
76
|
+
cl.mutex.Lock()
|
77
|
+
|
78
|
+
ret := make([]*Chunk, len(cl.chunks))
|
79
|
+
copy(ret, cl.chunks)
|
80
|
+
|
81
|
+
// Duplicate the last chunk
|
82
|
+
if cnt := len(ret); cnt > 0 {
|
83
|
+
newChunk := *ret[cnt-1]
|
84
|
+
ret[cnt-1] = &newChunk
|
85
|
+
}
|
86
|
+
|
87
|
+
cl.mutex.Unlock()
|
88
|
+
return ret, CountItems(ret)
|
89
|
+
}
|
@@ -0,0 +1,80 @@
|
|
1
|
+
package fzf
|
2
|
+
|
3
|
+
import (
|
4
|
+
"fmt"
|
5
|
+
"testing"
|
6
|
+
|
7
|
+
"github.com/junegunn/fzf/src/util"
|
8
|
+
)
|
9
|
+
|
10
|
+
func TestChunkList(t *testing.T) {
|
11
|
+
// FIXME global
|
12
|
+
sortCriteria = []criterion{byScore, byLength}
|
13
|
+
|
14
|
+
cl := NewChunkList(func(item *Item, s []byte) bool {
|
15
|
+
item.text = util.ToChars(s)
|
16
|
+
return true
|
17
|
+
})
|
18
|
+
|
19
|
+
// Snapshot
|
20
|
+
snapshot, count := cl.Snapshot()
|
21
|
+
if len(snapshot) > 0 || count > 0 {
|
22
|
+
t.Error("Snapshot should be empty now")
|
23
|
+
}
|
24
|
+
|
25
|
+
// Add some data
|
26
|
+
cl.Push([]byte("hello"))
|
27
|
+
cl.Push([]byte("world"))
|
28
|
+
|
29
|
+
// Previously created snapshot should remain the same
|
30
|
+
if len(snapshot) > 0 {
|
31
|
+
t.Error("Snapshot should not have changed")
|
32
|
+
}
|
33
|
+
|
34
|
+
// But the new snapshot should contain the added items
|
35
|
+
snapshot, count = cl.Snapshot()
|
36
|
+
if len(snapshot) != 1 && count != 2 {
|
37
|
+
t.Error("Snapshot should not be empty now")
|
38
|
+
}
|
39
|
+
|
40
|
+
// Check the content of the ChunkList
|
41
|
+
chunk1 := snapshot[0]
|
42
|
+
if chunk1.count != 2 {
|
43
|
+
t.Error("Snapshot should contain only two items")
|
44
|
+
}
|
45
|
+
if chunk1.items[0].text.ToString() != "hello" ||
|
46
|
+
chunk1.items[1].text.ToString() != "world" {
|
47
|
+
t.Error("Invalid data")
|
48
|
+
}
|
49
|
+
if chunk1.IsFull() {
|
50
|
+
t.Error("Chunk should not have been marked full yet")
|
51
|
+
}
|
52
|
+
|
53
|
+
// Add more data
|
54
|
+
for i := 0; i < chunkSize*2; i++ {
|
55
|
+
cl.Push([]byte(fmt.Sprintf("item %d", i)))
|
56
|
+
}
|
57
|
+
|
58
|
+
// Previous snapshot should remain the same
|
59
|
+
if len(snapshot) != 1 {
|
60
|
+
t.Error("Snapshot should stay the same")
|
61
|
+
}
|
62
|
+
|
63
|
+
// New snapshot
|
64
|
+
snapshot, count = cl.Snapshot()
|
65
|
+
if len(snapshot) != 3 || !snapshot[0].IsFull() ||
|
66
|
+
!snapshot[1].IsFull() || snapshot[2].IsFull() || count != chunkSize*2+2 {
|
67
|
+
t.Error("Expected two full chunks and one more chunk")
|
68
|
+
}
|
69
|
+
if snapshot[2].count != 2 {
|
70
|
+
t.Error("Unexpected number of items")
|
71
|
+
}
|
72
|
+
|
73
|
+
cl.Push([]byte("hello"))
|
74
|
+
cl.Push([]byte("world"))
|
75
|
+
|
76
|
+
lastChunkCount := snapshot[len(snapshot)-1].count
|
77
|
+
if lastChunkCount != 2 {
|
78
|
+
t.Error("Unexpected number of items:", lastChunkCount)
|
79
|
+
}
|
80
|
+
}
|