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