doing 2.0.20 → 2.0.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +1 -1
  5. data/doing.rdoc +1 -1
  6. data/lib/doing/version.rb +1 -1
  7. data/lib/helpers/fzf/.goreleaser.yml +119 -0
  8. data/lib/helpers/fzf/.rubocop.yml +28 -0
  9. data/lib/helpers/fzf/ADVANCED.md +565 -0
  10. data/lib/helpers/fzf/BUILD.md +49 -0
  11. data/lib/helpers/fzf/CHANGELOG.md +1193 -0
  12. data/lib/helpers/fzf/Dockerfile +11 -0
  13. data/lib/helpers/fzf/LICENSE +21 -0
  14. data/lib/helpers/fzf/Makefile +166 -0
  15. data/lib/helpers/fzf/README-VIM.md +486 -0
  16. data/lib/helpers/fzf/README.md +712 -0
  17. data/lib/helpers/fzf/bin/fzf-tmux +233 -0
  18. data/lib/helpers/fzf/doc/fzf.txt +512 -0
  19. data/lib/helpers/fzf/go.mod +17 -0
  20. data/lib/helpers/fzf/go.sum +31 -0
  21. data/lib/helpers/fzf/install +382 -0
  22. data/lib/helpers/fzf/install.ps1 +65 -0
  23. data/lib/helpers/fzf/main.go +14 -0
  24. data/lib/helpers/fzf/man/man1/fzf-tmux.1 +68 -0
  25. data/lib/helpers/fzf/man/man1/fzf.1 +1001 -0
  26. data/lib/helpers/fzf/plugin/fzf.vim +1048 -0
  27. data/lib/helpers/fzf/shell/completion.bash +381 -0
  28. data/lib/helpers/fzf/shell/completion.zsh +329 -0
  29. data/lib/helpers/fzf/shell/key-bindings.bash +96 -0
  30. data/lib/helpers/fzf/shell/key-bindings.fish +172 -0
  31. data/lib/helpers/fzf/shell/key-bindings.zsh +114 -0
  32. data/lib/helpers/fzf/src/LICENSE +21 -0
  33. data/lib/helpers/fzf/src/algo/algo.go +884 -0
  34. data/lib/helpers/fzf/src/algo/algo_test.go +197 -0
  35. data/lib/helpers/fzf/src/algo/normalize.go +492 -0
  36. data/lib/helpers/fzf/src/ansi.go +409 -0
  37. data/lib/helpers/fzf/src/ansi_test.go +427 -0
  38. data/lib/helpers/fzf/src/cache.go +81 -0
  39. data/lib/helpers/fzf/src/cache_test.go +39 -0
  40. data/lib/helpers/fzf/src/chunklist.go +89 -0
  41. data/lib/helpers/fzf/src/chunklist_test.go +80 -0
  42. data/lib/helpers/fzf/src/constants.go +85 -0
  43. data/lib/helpers/fzf/src/core.go +351 -0
  44. data/lib/helpers/fzf/src/history.go +96 -0
  45. data/lib/helpers/fzf/src/history_test.go +68 -0
  46. data/lib/helpers/fzf/src/item.go +44 -0
  47. data/lib/helpers/fzf/src/item_test.go +23 -0
  48. data/lib/helpers/fzf/src/matcher.go +235 -0
  49. data/lib/helpers/fzf/src/merger.go +120 -0
  50. data/lib/helpers/fzf/src/merger_test.go +88 -0
  51. data/lib/helpers/fzf/src/options.go +1691 -0
  52. data/lib/helpers/fzf/src/options_test.go +457 -0
  53. data/lib/helpers/fzf/src/pattern.go +425 -0
  54. data/lib/helpers/fzf/src/pattern_test.go +209 -0
  55. data/lib/helpers/fzf/src/protector/protector.go +8 -0
  56. data/lib/helpers/fzf/src/protector/protector_openbsd.go +10 -0
  57. data/lib/helpers/fzf/src/reader.go +201 -0
  58. data/lib/helpers/fzf/src/reader_test.go +63 -0
  59. data/lib/helpers/fzf/src/result.go +243 -0
  60. data/lib/helpers/fzf/src/result_others.go +16 -0
  61. data/lib/helpers/fzf/src/result_test.go +159 -0
  62. data/lib/helpers/fzf/src/result_x86.go +16 -0
  63. data/lib/helpers/fzf/src/terminal.go +2832 -0
  64. data/lib/helpers/fzf/src/terminal_test.go +638 -0
  65. data/lib/helpers/fzf/src/terminal_unix.go +26 -0
  66. data/lib/helpers/fzf/src/terminal_windows.go +45 -0
  67. data/lib/helpers/fzf/src/tokenizer.go +253 -0
  68. data/lib/helpers/fzf/src/tokenizer_test.go +112 -0
  69. data/lib/helpers/fzf/src/tui/dummy.go +46 -0
  70. data/lib/helpers/fzf/src/tui/light.go +987 -0
  71. data/lib/helpers/fzf/src/tui/light_unix.go +110 -0
  72. data/lib/helpers/fzf/src/tui/light_windows.go +145 -0
  73. data/lib/helpers/fzf/src/tui/tcell.go +721 -0
  74. data/lib/helpers/fzf/src/tui/tcell_test.go +392 -0
  75. data/lib/helpers/fzf/src/tui/ttyname_unix.go +47 -0
  76. data/lib/helpers/fzf/src/tui/ttyname_windows.go +14 -0
  77. data/lib/helpers/fzf/src/tui/tui.go +625 -0
  78. data/lib/helpers/fzf/src/tui/tui_test.go +20 -0
  79. data/lib/helpers/fzf/src/util/atomicbool.go +34 -0
  80. data/lib/helpers/fzf/src/util/atomicbool_test.go +17 -0
  81. data/lib/helpers/fzf/src/util/chars.go +198 -0
  82. data/lib/helpers/fzf/src/util/chars_test.go +46 -0
  83. data/lib/helpers/fzf/src/util/eventbox.go +96 -0
  84. data/lib/helpers/fzf/src/util/eventbox_test.go +61 -0
  85. data/lib/helpers/fzf/src/util/slab.go +12 -0
  86. data/lib/helpers/fzf/src/util/util.go +138 -0
  87. data/lib/helpers/fzf/src/util/util_test.go +40 -0
  88. data/lib/helpers/fzf/src/util/util_unix.go +47 -0
  89. data/lib/helpers/fzf/src/util/util_windows.go +83 -0
  90. data/lib/helpers/fzf/test/fzf.vader +175 -0
  91. data/lib/helpers/fzf/test/test_go.rb +2626 -0
  92. data/lib/helpers/fzf/uninstall +117 -0
  93. metadata +87 -1
@@ -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
+ }