doing 2.0.20 → 2.0.21

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 (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,884 @@
1
+ package algo
2
+
3
+ /*
4
+
5
+ Algorithm
6
+ ---------
7
+
8
+ FuzzyMatchV1 finds the first "fuzzy" occurrence of the pattern within the given
9
+ text in O(n) time where n is the length of the text. Once the position of the
10
+ last character is located, it traverses backwards to see if there's a shorter
11
+ substring that matches the pattern.
12
+
13
+ a_____b___abc__ To find "abc"
14
+ *-----*-----*> 1. Forward scan
15
+ <*** 2. Backward scan
16
+
17
+ The algorithm is simple and fast, but as it only sees the first occurrence,
18
+ it is not guaranteed to find the occurrence with the highest score.
19
+
20
+ a_____b__c__abc
21
+ *-----*--* ***
22
+
23
+ FuzzyMatchV2 implements a modified version of Smith-Waterman algorithm to find
24
+ the optimal solution (highest score) according to the scoring criteria. Unlike
25
+ the original algorithm, omission or mismatch of a character in the pattern is
26
+ not allowed.
27
+
28
+ Performance
29
+ -----------
30
+
31
+ The new V2 algorithm is slower than V1 as it examines all occurrences of the
32
+ pattern instead of stopping immediately after finding the first one. The time
33
+ complexity of the algorithm is O(nm) if a match is found and O(n) otherwise
34
+ where n is the length of the item and m is the length of the pattern. Thus, the
35
+ performance overhead may not be noticeable for a query with high selectivity.
36
+ However, if the performance is more important than the quality of the result,
37
+ you can still choose v1 algorithm with --algo=v1.
38
+
39
+ Scoring criteria
40
+ ----------------
41
+
42
+ - We prefer matches at special positions, such as the start of a word, or
43
+ uppercase character in camelCase words.
44
+
45
+ - That is, we prefer an occurrence of the pattern with more characters
46
+ matching at special positions, even if the total match length is longer.
47
+ e.g. "fuzzyfinder" vs. "fuzzy-finder" on "ff"
48
+ ````````````
49
+ - Also, if the first character in the pattern appears at one of the special
50
+ positions, the bonus point for the position is multiplied by a constant
51
+ as it is extremely likely that the first character in the typed pattern
52
+ has more significance than the rest.
53
+ e.g. "fo-bar" vs. "foob-r" on "br"
54
+ ``````
55
+ - But since fzf is still a fuzzy finder, not an acronym finder, we should also
56
+ consider the total length of the matched substring. This is why we have the
57
+ gap penalty. The gap penalty increases as the length of the gap (distance
58
+ between the matching characters) increases, so the effect of the bonus is
59
+ eventually cancelled at some point.
60
+ e.g. "fuzzyfinder" vs. "fuzzy-blurry-finder" on "ff"
61
+ ```````````
62
+ - Consequently, it is crucial to find the right balance between the bonus
63
+ and the gap penalty. The parameters were chosen that the bonus is cancelled
64
+ when the gap size increases beyond 8 characters.
65
+
66
+ - The bonus mechanism can have the undesirable side effect where consecutive
67
+ matches are ranked lower than the ones with gaps.
68
+ e.g. "foobar" vs. "foo-bar" on "foob"
69
+ ```````
70
+ - To correct this anomaly, we also give extra bonus point to each character
71
+ in a consecutive matching chunk.
72
+ e.g. "foobar" vs. "foo-bar" on "foob"
73
+ ``````
74
+ - The amount of consecutive bonus is primarily determined by the bonus of the
75
+ first character in the chunk.
76
+ e.g. "foobar" vs. "out-of-bound" on "oob"
77
+ ````````````
78
+ */
79
+
80
+ import (
81
+ "bytes"
82
+ "fmt"
83
+ "strings"
84
+ "unicode"
85
+ "unicode/utf8"
86
+
87
+ "github.com/junegunn/fzf/src/util"
88
+ )
89
+
90
+ var DEBUG bool
91
+
92
+ func indexAt(index int, max int, forward bool) int {
93
+ if forward {
94
+ return index
95
+ }
96
+ return max - index - 1
97
+ }
98
+
99
+ // Result contains the results of running a match function.
100
+ type Result struct {
101
+ // TODO int32 should suffice
102
+ Start int
103
+ End int
104
+ Score int
105
+ }
106
+
107
+ const (
108
+ scoreMatch = 16
109
+ scoreGapStart = -3
110
+ scoreGapExtension = -1
111
+
112
+ // We prefer matches at the beginning of a word, but the bonus should not be
113
+ // too great to prevent the longer acronym matches from always winning over
114
+ // shorter fuzzy matches. The bonus point here was specifically chosen that
115
+ // the bonus is cancelled when the gap between the acronyms grows over
116
+ // 8 characters, which is approximately the average length of the words found
117
+ // in web2 dictionary and my file system.
118
+ bonusBoundary = scoreMatch / 2
119
+
120
+ // Although bonus point for non-word characters is non-contextual, we need it
121
+ // for computing bonus points for consecutive chunks starting with a non-word
122
+ // character.
123
+ bonusNonWord = scoreMatch / 2
124
+
125
+ // Edge-triggered bonus for matches in camelCase words.
126
+ // Compared to word-boundary case, they don't accompany single-character gaps
127
+ // (e.g. FooBar vs. foo-bar), so we deduct bonus point accordingly.
128
+ bonusCamel123 = bonusBoundary + scoreGapExtension
129
+
130
+ // Minimum bonus point given to characters in consecutive chunks.
131
+ // Note that bonus points for consecutive matches shouldn't have needed if we
132
+ // used fixed match score as in the original algorithm.
133
+ bonusConsecutive = -(scoreGapStart + scoreGapExtension)
134
+
135
+ // The first character in the typed pattern usually has more significance
136
+ // than the rest so it's important that it appears at special positions where
137
+ // bonus points are given, e.g. "to-go" vs. "ongoing" on "og" or on "ogo".
138
+ // The amount of the extra bonus should be limited so that the gap penalty is
139
+ // still respected.
140
+ bonusFirstCharMultiplier = 2
141
+ )
142
+
143
+ type charClass int
144
+
145
+ const (
146
+ charNonWord charClass = iota
147
+ charLower
148
+ charUpper
149
+ charLetter
150
+ charNumber
151
+ )
152
+
153
+ func posArray(withPos bool, len int) *[]int {
154
+ if withPos {
155
+ pos := make([]int, 0, len)
156
+ return &pos
157
+ }
158
+ return nil
159
+ }
160
+
161
+ func alloc16(offset int, slab *util.Slab, size int) (int, []int16) {
162
+ if slab != nil && cap(slab.I16) > offset+size {
163
+ slice := slab.I16[offset : offset+size]
164
+ return offset + size, slice
165
+ }
166
+ return offset, make([]int16, size)
167
+ }
168
+
169
+ func alloc32(offset int, slab *util.Slab, size int) (int, []int32) {
170
+ if slab != nil && cap(slab.I32) > offset+size {
171
+ slice := slab.I32[offset : offset+size]
172
+ return offset + size, slice
173
+ }
174
+ return offset, make([]int32, size)
175
+ }
176
+
177
+ func charClassOfAscii(char rune) charClass {
178
+ if char >= 'a' && char <= 'z' {
179
+ return charLower
180
+ } else if char >= 'A' && char <= 'Z' {
181
+ return charUpper
182
+ } else if char >= '0' && char <= '9' {
183
+ return charNumber
184
+ }
185
+ return charNonWord
186
+ }
187
+
188
+ func charClassOfNonAscii(char rune) charClass {
189
+ if unicode.IsLower(char) {
190
+ return charLower
191
+ } else if unicode.IsUpper(char) {
192
+ return charUpper
193
+ } else if unicode.IsNumber(char) {
194
+ return charNumber
195
+ } else if unicode.IsLetter(char) {
196
+ return charLetter
197
+ }
198
+ return charNonWord
199
+ }
200
+
201
+ func charClassOf(char rune) charClass {
202
+ if char <= unicode.MaxASCII {
203
+ return charClassOfAscii(char)
204
+ }
205
+ return charClassOfNonAscii(char)
206
+ }
207
+
208
+ func bonusFor(prevClass charClass, class charClass) int16 {
209
+ if prevClass == charNonWord && class != charNonWord {
210
+ // Word boundary
211
+ return bonusBoundary
212
+ } else if prevClass == charLower && class == charUpper ||
213
+ prevClass != charNumber && class == charNumber {
214
+ // camelCase letter123
215
+ return bonusCamel123
216
+ } else if class == charNonWord {
217
+ return bonusNonWord
218
+ }
219
+ return 0
220
+ }
221
+
222
+ func bonusAt(input *util.Chars, idx int) int16 {
223
+ if idx == 0 {
224
+ return bonusBoundary
225
+ }
226
+ return bonusFor(charClassOf(input.Get(idx-1)), charClassOf(input.Get(idx)))
227
+ }
228
+
229
+ func normalizeRune(r rune) rune {
230
+ if r < 0x00C0 || r > 0x2184 {
231
+ return r
232
+ }
233
+
234
+ n := normalized[r]
235
+ if n > 0 {
236
+ return n
237
+ }
238
+ return r
239
+ }
240
+
241
+ // Algo functions make two assumptions
242
+ // 1. "pattern" is given in lowercase if "caseSensitive" is false
243
+ // 2. "pattern" is already normalized if "normalize" is true
244
+ type Algo func(caseSensitive bool, normalize bool, forward bool, input *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int)
245
+
246
+ func trySkip(input *util.Chars, caseSensitive bool, b byte, from int) int {
247
+ byteArray := input.Bytes()[from:]
248
+ idx := bytes.IndexByte(byteArray, b)
249
+ if idx == 0 {
250
+ // Can't skip any further
251
+ return from
252
+ }
253
+ // We may need to search for the uppercase letter again. We don't have to
254
+ // consider normalization as we can be sure that this is an ASCII string.
255
+ if !caseSensitive && b >= 'a' && b <= 'z' {
256
+ if idx > 0 {
257
+ byteArray = byteArray[:idx]
258
+ }
259
+ uidx := bytes.IndexByte(byteArray, b-32)
260
+ if uidx >= 0 {
261
+ idx = uidx
262
+ }
263
+ }
264
+ if idx < 0 {
265
+ return -1
266
+ }
267
+ return from + idx
268
+ }
269
+
270
+ func isAscii(runes []rune) bool {
271
+ for _, r := range runes {
272
+ if r >= utf8.RuneSelf {
273
+ return false
274
+ }
275
+ }
276
+ return true
277
+ }
278
+
279
+ func asciiFuzzyIndex(input *util.Chars, pattern []rune, caseSensitive bool) int {
280
+ // Can't determine
281
+ if !input.IsBytes() {
282
+ return 0
283
+ }
284
+
285
+ // Not possible
286
+ if !isAscii(pattern) {
287
+ return -1
288
+ }
289
+
290
+ firstIdx, idx := 0, 0
291
+ for pidx := 0; pidx < len(pattern); pidx++ {
292
+ idx = trySkip(input, caseSensitive, byte(pattern[pidx]), idx)
293
+ if idx < 0 {
294
+ return -1
295
+ }
296
+ if pidx == 0 && idx > 0 {
297
+ // Step back to find the right bonus point
298
+ firstIdx = idx - 1
299
+ }
300
+ idx++
301
+ }
302
+ return firstIdx
303
+ }
304
+
305
+ func debugV2(T []rune, pattern []rune, F []int32, lastIdx int, H []int16, C []int16) {
306
+ width := lastIdx - int(F[0]) + 1
307
+
308
+ for i, f := range F {
309
+ I := i * width
310
+ if i == 0 {
311
+ fmt.Print(" ")
312
+ for j := int(f); j <= lastIdx; j++ {
313
+ fmt.Printf(" " + string(T[j]) + " ")
314
+ }
315
+ fmt.Println()
316
+ }
317
+ fmt.Print(string(pattern[i]) + " ")
318
+ for idx := int(F[0]); idx < int(f); idx++ {
319
+ fmt.Print(" 0 ")
320
+ }
321
+ for idx := int(f); idx <= lastIdx; idx++ {
322
+ fmt.Printf("%2d ", H[i*width+idx-int(F[0])])
323
+ }
324
+ fmt.Println()
325
+
326
+ fmt.Print(" ")
327
+ for idx, p := range C[I : I+width] {
328
+ if idx+int(F[0]) < int(F[i]) {
329
+ p = 0
330
+ }
331
+ if p > 0 {
332
+ fmt.Printf("%2d ", p)
333
+ } else {
334
+ fmt.Print(" ")
335
+ }
336
+ }
337
+ fmt.Println()
338
+ }
339
+ }
340
+
341
+ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
342
+ // Assume that pattern is given in lowercase if case-insensitive.
343
+ // First check if there's a match and calculate bonus for each position.
344
+ // If the input string is too long, consider finding the matching chars in
345
+ // this phase as well (non-optimal alignment).
346
+ M := len(pattern)
347
+ if M == 0 {
348
+ return Result{0, 0, 0}, posArray(withPos, M)
349
+ }
350
+ N := input.Length()
351
+
352
+ // Since O(nm) algorithm can be prohibitively expensive for large input,
353
+ // we fall back to the greedy algorithm.
354
+ if slab != nil && N*M > cap(slab.I16) {
355
+ return FuzzyMatchV1(caseSensitive, normalize, forward, input, pattern, withPos, slab)
356
+ }
357
+
358
+ // Phase 1. Optimized search for ASCII string
359
+ idx := asciiFuzzyIndex(input, pattern, caseSensitive)
360
+ if idx < 0 {
361
+ return Result{-1, -1, 0}, nil
362
+ }
363
+
364
+ // Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages
365
+ offset16 := 0
366
+ offset32 := 0
367
+ offset16, H0 := alloc16(offset16, slab, N)
368
+ offset16, C0 := alloc16(offset16, slab, N)
369
+ // Bonus point for each position
370
+ offset16, B := alloc16(offset16, slab, N)
371
+ // The first occurrence of each character in the pattern
372
+ offset32, F := alloc32(offset32, slab, M)
373
+ // Rune array
374
+ _, T := alloc32(offset32, slab, N)
375
+ input.CopyRunes(T)
376
+
377
+ // Phase 2. Calculate bonus for each point
378
+ maxScore, maxScorePos := int16(0), 0
379
+ pidx, lastIdx := 0, 0
380
+ pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), charNonWord, false
381
+ Tsub := T[idx:]
382
+ H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)]
383
+ for off, char := range Tsub {
384
+ var class charClass
385
+ if char <= unicode.MaxASCII {
386
+ class = charClassOfAscii(char)
387
+ if !caseSensitive && class == charUpper {
388
+ char += 32
389
+ }
390
+ } else {
391
+ class = charClassOfNonAscii(char)
392
+ if !caseSensitive && class == charUpper {
393
+ char = unicode.To(unicode.LowerCase, char)
394
+ }
395
+ if normalize {
396
+ char = normalizeRune(char)
397
+ }
398
+ }
399
+
400
+ Tsub[off] = char
401
+ bonus := bonusFor(prevClass, class)
402
+ Bsub[off] = bonus
403
+ prevClass = class
404
+
405
+ if char == pchar {
406
+ if pidx < M {
407
+ F[pidx] = int32(idx + off)
408
+ pidx++
409
+ pchar = pattern[util.Min(pidx, M-1)]
410
+ }
411
+ lastIdx = idx + off
412
+ }
413
+
414
+ if char == pchar0 {
415
+ score := scoreMatch + bonus*bonusFirstCharMultiplier
416
+ H0sub[off] = score
417
+ C0sub[off] = 1
418
+ if M == 1 && (forward && score > maxScore || !forward && score >= maxScore) {
419
+ maxScore, maxScorePos = score, idx+off
420
+ if forward && bonus == bonusBoundary {
421
+ break
422
+ }
423
+ }
424
+ inGap = false
425
+ } else {
426
+ if inGap {
427
+ H0sub[off] = util.Max16(prevH0+scoreGapExtension, 0)
428
+ } else {
429
+ H0sub[off] = util.Max16(prevH0+scoreGapStart, 0)
430
+ }
431
+ C0sub[off] = 0
432
+ inGap = true
433
+ }
434
+ prevH0 = H0sub[off]
435
+ }
436
+ if pidx != M {
437
+ return Result{-1, -1, 0}, nil
438
+ }
439
+ if M == 1 {
440
+ result := Result{maxScorePos, maxScorePos + 1, int(maxScore)}
441
+ if !withPos {
442
+ return result, nil
443
+ }
444
+ pos := []int{maxScorePos}
445
+ return result, &pos
446
+ }
447
+
448
+ // Phase 3. Fill in score matrix (H)
449
+ // Unlike the original algorithm, we do not allow omission.
450
+ f0 := int(F[0])
451
+ width := lastIdx - f0 + 1
452
+ offset16, H := alloc16(offset16, slab, width*M)
453
+ copy(H, H0[f0:lastIdx+1])
454
+
455
+ // Possible length of consecutive chunk at each position.
456
+ _, C := alloc16(offset16, slab, width*M)
457
+ copy(C, C0[f0:lastIdx+1])
458
+
459
+ Fsub := F[1:]
460
+ Psub := pattern[1:][:len(Fsub)]
461
+ for off, f := range Fsub {
462
+ f := int(f)
463
+ pchar := Psub[off]
464
+ pidx := off + 1
465
+ row := pidx * width
466
+ inGap := false
467
+ Tsub := T[f : lastIdx+1]
468
+ Bsub := B[f:][:len(Tsub)]
469
+ Csub := C[row+f-f0:][:len(Tsub)]
470
+ Cdiag := C[row+f-f0-1-width:][:len(Tsub)]
471
+ Hsub := H[row+f-f0:][:len(Tsub)]
472
+ Hdiag := H[row+f-f0-1-width:][:len(Tsub)]
473
+ Hleft := H[row+f-f0-1:][:len(Tsub)]
474
+ Hleft[0] = 0
475
+ for off, char := range Tsub {
476
+ col := off + f
477
+ var s1, s2, consecutive int16
478
+
479
+ if inGap {
480
+ s2 = Hleft[off] + scoreGapExtension
481
+ } else {
482
+ s2 = Hleft[off] + scoreGapStart
483
+ }
484
+
485
+ if pchar == char {
486
+ s1 = Hdiag[off] + scoreMatch
487
+ b := Bsub[off]
488
+ consecutive = Cdiag[off] + 1
489
+ // Break consecutive chunk
490
+ if b == bonusBoundary {
491
+ consecutive = 1
492
+ } else if consecutive > 1 {
493
+ b = util.Max16(b, util.Max16(bonusConsecutive, B[col-int(consecutive)+1]))
494
+ }
495
+ if s1+b < s2 {
496
+ s1 += Bsub[off]
497
+ consecutive = 0
498
+ } else {
499
+ s1 += b
500
+ }
501
+ }
502
+ Csub[off] = consecutive
503
+
504
+ inGap = s1 < s2
505
+ score := util.Max16(util.Max16(s1, s2), 0)
506
+ if pidx == M-1 && (forward && score > maxScore || !forward && score >= maxScore) {
507
+ maxScore, maxScorePos = score, col
508
+ }
509
+ Hsub[off] = score
510
+ }
511
+ }
512
+
513
+ if DEBUG {
514
+ debugV2(T, pattern, F, lastIdx, H, C)
515
+ }
516
+
517
+ // Phase 4. (Optional) Backtrace to find character positions
518
+ pos := posArray(withPos, M)
519
+ j := f0
520
+ if withPos {
521
+ i := M - 1
522
+ j = maxScorePos
523
+ preferMatch := true
524
+ for {
525
+ I := i * width
526
+ j0 := j - f0
527
+ s := H[I+j0]
528
+
529
+ var s1, s2 int16
530
+ if i > 0 && j >= int(F[i]) {
531
+ s1 = H[I-width+j0-1]
532
+ }
533
+ if j > int(F[i]) {
534
+ s2 = H[I+j0-1]
535
+ }
536
+
537
+ if s > s1 && (s > s2 || s == s2 && preferMatch) {
538
+ *pos = append(*pos, j)
539
+ if i == 0 {
540
+ break
541
+ }
542
+ i--
543
+ }
544
+ preferMatch = C[I+j0] > 1 || I+width+j0+1 < len(C) && C[I+width+j0+1] > 0
545
+ j--
546
+ }
547
+ }
548
+ // Start offset we return here is only relevant when begin tiebreak is used.
549
+ // However finding the accurate offset requires backtracking, and we don't
550
+ // want to pay extra cost for the option that has lost its importance.
551
+ return Result{j, maxScorePos + 1, int(maxScore)}, pos
552
+ }
553
+
554
+ // Implement the same sorting criteria as V2
555
+ func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, pattern []rune, sidx int, eidx int, withPos bool) (int, *[]int) {
556
+ pidx, score, inGap, consecutive, firstBonus := 0, 0, false, 0, int16(0)
557
+ pos := posArray(withPos, len(pattern))
558
+ prevClass := charNonWord
559
+ if sidx > 0 {
560
+ prevClass = charClassOf(text.Get(sidx - 1))
561
+ }
562
+ for idx := sidx; idx < eidx; idx++ {
563
+ char := text.Get(idx)
564
+ class := charClassOf(char)
565
+ if !caseSensitive {
566
+ if char >= 'A' && char <= 'Z' {
567
+ char += 32
568
+ } else if char > unicode.MaxASCII {
569
+ char = unicode.To(unicode.LowerCase, char)
570
+ }
571
+ }
572
+ // pattern is already normalized
573
+ if normalize {
574
+ char = normalizeRune(char)
575
+ }
576
+ if char == pattern[pidx] {
577
+ if withPos {
578
+ *pos = append(*pos, idx)
579
+ }
580
+ score += scoreMatch
581
+ bonus := bonusFor(prevClass, class)
582
+ if consecutive == 0 {
583
+ firstBonus = bonus
584
+ } else {
585
+ // Break consecutive chunk
586
+ if bonus == bonusBoundary {
587
+ firstBonus = bonus
588
+ }
589
+ bonus = util.Max16(util.Max16(bonus, firstBonus), bonusConsecutive)
590
+ }
591
+ if pidx == 0 {
592
+ score += int(bonus * bonusFirstCharMultiplier)
593
+ } else {
594
+ score += int(bonus)
595
+ }
596
+ inGap = false
597
+ consecutive++
598
+ pidx++
599
+ } else {
600
+ if inGap {
601
+ score += scoreGapExtension
602
+ } else {
603
+ score += scoreGapStart
604
+ }
605
+ inGap = true
606
+ consecutive = 0
607
+ firstBonus = 0
608
+ }
609
+ prevClass = class
610
+ }
611
+ return score, pos
612
+ }
613
+
614
+ // FuzzyMatchV1 performs fuzzy-match
615
+ func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
616
+ if len(pattern) == 0 {
617
+ return Result{0, 0, 0}, nil
618
+ }
619
+ if asciiFuzzyIndex(text, pattern, caseSensitive) < 0 {
620
+ return Result{-1, -1, 0}, nil
621
+ }
622
+
623
+ pidx := 0
624
+ sidx := -1
625
+ eidx := -1
626
+
627
+ lenRunes := text.Length()
628
+ lenPattern := len(pattern)
629
+
630
+ for index := 0; index < lenRunes; index++ {
631
+ char := text.Get(indexAt(index, lenRunes, forward))
632
+ // This is considerably faster than blindly applying strings.ToLower to the
633
+ // whole string
634
+ if !caseSensitive {
635
+ // Partially inlining `unicode.ToLower`. Ugly, but makes a noticeable
636
+ // difference in CPU cost. (Measured on Go 1.4.1. Also note that the Go
637
+ // compiler as of now does not inline non-leaf functions.)
638
+ if char >= 'A' && char <= 'Z' {
639
+ char += 32
640
+ } else if char > unicode.MaxASCII {
641
+ char = unicode.To(unicode.LowerCase, char)
642
+ }
643
+ }
644
+ if normalize {
645
+ char = normalizeRune(char)
646
+ }
647
+ pchar := pattern[indexAt(pidx, lenPattern, forward)]
648
+ if char == pchar {
649
+ if sidx < 0 {
650
+ sidx = index
651
+ }
652
+ if pidx++; pidx == lenPattern {
653
+ eidx = index + 1
654
+ break
655
+ }
656
+ }
657
+ }
658
+
659
+ if sidx >= 0 && eidx >= 0 {
660
+ pidx--
661
+ for index := eidx - 1; index >= sidx; index-- {
662
+ tidx := indexAt(index, lenRunes, forward)
663
+ char := text.Get(tidx)
664
+ if !caseSensitive {
665
+ if char >= 'A' && char <= 'Z' {
666
+ char += 32
667
+ } else if char > unicode.MaxASCII {
668
+ char = unicode.To(unicode.LowerCase, char)
669
+ }
670
+ }
671
+
672
+ pidx_ := indexAt(pidx, lenPattern, forward)
673
+ pchar := pattern[pidx_]
674
+ if char == pchar {
675
+ if pidx--; pidx < 0 {
676
+ sidx = index
677
+ break
678
+ }
679
+ }
680
+ }
681
+
682
+ if !forward {
683
+ sidx, eidx = lenRunes-eidx, lenRunes-sidx
684
+ }
685
+
686
+ score, pos := calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, withPos)
687
+ return Result{sidx, eidx, score}, pos
688
+ }
689
+ return Result{-1, -1, 0}, nil
690
+ }
691
+
692
+ // ExactMatchNaive is a basic string searching algorithm that handles case
693
+ // sensitivity. Although naive, it still performs better than the combination
694
+ // of strings.ToLower + strings.Index for typical fzf use cases where input
695
+ // strings and patterns are not very long.
696
+ //
697
+ // Since 0.15.0, this function searches for the match with the highest
698
+ // bonus point, instead of stopping immediately after finding the first match.
699
+ // The solution is much cheaper since there is only one possible alignment of
700
+ // the pattern.
701
+ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
702
+ if len(pattern) == 0 {
703
+ return Result{0, 0, 0}, nil
704
+ }
705
+
706
+ lenRunes := text.Length()
707
+ lenPattern := len(pattern)
708
+
709
+ if lenRunes < lenPattern {
710
+ return Result{-1, -1, 0}, nil
711
+ }
712
+
713
+ if asciiFuzzyIndex(text, pattern, caseSensitive) < 0 {
714
+ return Result{-1, -1, 0}, nil
715
+ }
716
+
717
+ // For simplicity, only look at the bonus at the first character position
718
+ pidx := 0
719
+ bestPos, bonus, bestBonus := -1, int16(0), int16(-1)
720
+ for index := 0; index < lenRunes; index++ {
721
+ index_ := indexAt(index, lenRunes, forward)
722
+ char := text.Get(index_)
723
+ if !caseSensitive {
724
+ if char >= 'A' && char <= 'Z' {
725
+ char += 32
726
+ } else if char > unicode.MaxASCII {
727
+ char = unicode.To(unicode.LowerCase, char)
728
+ }
729
+ }
730
+ if normalize {
731
+ char = normalizeRune(char)
732
+ }
733
+ pidx_ := indexAt(pidx, lenPattern, forward)
734
+ pchar := pattern[pidx_]
735
+ if pchar == char {
736
+ if pidx_ == 0 {
737
+ bonus = bonusAt(text, index_)
738
+ }
739
+ pidx++
740
+ if pidx == lenPattern {
741
+ if bonus > bestBonus {
742
+ bestPos, bestBonus = index, bonus
743
+ }
744
+ if bonus == bonusBoundary {
745
+ break
746
+ }
747
+ index -= pidx - 1
748
+ pidx, bonus = 0, 0
749
+ }
750
+ } else {
751
+ index -= pidx
752
+ pidx, bonus = 0, 0
753
+ }
754
+ }
755
+ if bestPos >= 0 {
756
+ var sidx, eidx int
757
+ if forward {
758
+ sidx = bestPos - lenPattern + 1
759
+ eidx = bestPos + 1
760
+ } else {
761
+ sidx = lenRunes - (bestPos + 1)
762
+ eidx = lenRunes - (bestPos - lenPattern + 1)
763
+ }
764
+ score, _ := calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false)
765
+ return Result{sidx, eidx, score}, nil
766
+ }
767
+ return Result{-1, -1, 0}, nil
768
+ }
769
+
770
+ // PrefixMatch performs prefix-match
771
+ func PrefixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
772
+ if len(pattern) == 0 {
773
+ return Result{0, 0, 0}, nil
774
+ }
775
+
776
+ trimmedLen := 0
777
+ if !unicode.IsSpace(pattern[0]) {
778
+ trimmedLen = text.LeadingWhitespaces()
779
+ }
780
+
781
+ if text.Length()-trimmedLen < len(pattern) {
782
+ return Result{-1, -1, 0}, nil
783
+ }
784
+
785
+ for index, r := range pattern {
786
+ char := text.Get(trimmedLen + index)
787
+ if !caseSensitive {
788
+ char = unicode.ToLower(char)
789
+ }
790
+ if normalize {
791
+ char = normalizeRune(char)
792
+ }
793
+ if char != r {
794
+ return Result{-1, -1, 0}, nil
795
+ }
796
+ }
797
+ lenPattern := len(pattern)
798
+ score, _ := calculateScore(caseSensitive, normalize, text, pattern, trimmedLen, trimmedLen+lenPattern, false)
799
+ return Result{trimmedLen, trimmedLen + lenPattern, score}, nil
800
+ }
801
+
802
+ // SuffixMatch performs suffix-match
803
+ func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
804
+ lenRunes := text.Length()
805
+ trimmedLen := lenRunes
806
+ if len(pattern) == 0 || !unicode.IsSpace(pattern[len(pattern)-1]) {
807
+ trimmedLen -= text.TrailingWhitespaces()
808
+ }
809
+ if len(pattern) == 0 {
810
+ return Result{trimmedLen, trimmedLen, 0}, nil
811
+ }
812
+ diff := trimmedLen - len(pattern)
813
+ if diff < 0 {
814
+ return Result{-1, -1, 0}, nil
815
+ }
816
+
817
+ for index, r := range pattern {
818
+ char := text.Get(index + diff)
819
+ if !caseSensitive {
820
+ char = unicode.ToLower(char)
821
+ }
822
+ if normalize {
823
+ char = normalizeRune(char)
824
+ }
825
+ if char != r {
826
+ return Result{-1, -1, 0}, nil
827
+ }
828
+ }
829
+ lenPattern := len(pattern)
830
+ sidx := trimmedLen - lenPattern
831
+ eidx := trimmedLen
832
+ score, _ := calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false)
833
+ return Result{sidx, eidx, score}, nil
834
+ }
835
+
836
+ // EqualMatch performs equal-match
837
+ func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
838
+ lenPattern := len(pattern)
839
+ if lenPattern == 0 {
840
+ return Result{-1, -1, 0}, nil
841
+ }
842
+
843
+ // Strip leading whitespaces
844
+ trimmedLen := 0
845
+ if !unicode.IsSpace(pattern[0]) {
846
+ trimmedLen = text.LeadingWhitespaces()
847
+ }
848
+
849
+ // Strip trailing whitespaces
850
+ trimmedEndLen := 0
851
+ if !unicode.IsSpace(pattern[lenPattern-1]) {
852
+ trimmedEndLen = text.TrailingWhitespaces()
853
+ }
854
+
855
+ if text.Length()-trimmedLen-trimmedEndLen != lenPattern {
856
+ return Result{-1, -1, 0}, nil
857
+ }
858
+ match := true
859
+ if normalize {
860
+ runes := text.ToRunes()
861
+ for idx, pchar := range pattern {
862
+ char := runes[trimmedLen+idx]
863
+ if !caseSensitive {
864
+ char = unicode.To(unicode.LowerCase, char)
865
+ }
866
+ if normalizeRune(pchar) != normalizeRune(char) {
867
+ match = false
868
+ break
869
+ }
870
+ }
871
+ } else {
872
+ runes := text.ToRunes()
873
+ runesStr := string(runes[trimmedLen : len(runes)-trimmedEndLen])
874
+ if !caseSensitive {
875
+ runesStr = strings.ToLower(runesStr)
876
+ }
877
+ match = runesStr == string(pattern)
878
+ }
879
+ if match {
880
+ return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+bonusBoundary)*lenPattern +
881
+ (bonusFirstCharMultiplier-1)*bonusBoundary}, nil
882
+ }
883
+ return Result{-1, -1, 0}, nil
884
+ }