doing 2.0.19 → 2.0.23

Sign up to get free protection for your applications and to get access to all the features.
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,201 @@
1
+ package fzf
2
+
3
+ import (
4
+ "bufio"
5
+ "context"
6
+ "io"
7
+ "os"
8
+ "os/exec"
9
+ "path/filepath"
10
+ "sync"
11
+ "sync/atomic"
12
+ "time"
13
+
14
+ "github.com/junegunn/fzf/src/util"
15
+ "github.com/saracen/walker"
16
+ )
17
+
18
+ // Reader reads from command or standard input
19
+ type Reader struct {
20
+ pusher func([]byte) bool
21
+ eventBox *util.EventBox
22
+ delimNil bool
23
+ event int32
24
+ finChan chan bool
25
+ mutex sync.Mutex
26
+ exec *exec.Cmd
27
+ command *string
28
+ killed bool
29
+ wait bool
30
+ }
31
+
32
+ // NewReader returns new Reader object
33
+ func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, delimNil bool, wait bool) *Reader {
34
+ return &Reader{pusher, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, false, wait}
35
+ }
36
+
37
+ func (r *Reader) startEventPoller() {
38
+ go func() {
39
+ ptr := &r.event
40
+ pollInterval := readerPollIntervalMin
41
+ for {
42
+ if atomic.CompareAndSwapInt32(ptr, int32(EvtReadNew), int32(EvtReady)) {
43
+ r.eventBox.Set(EvtReadNew, (*string)(nil))
44
+ pollInterval = readerPollIntervalMin
45
+ } else if atomic.LoadInt32(ptr) == int32(EvtReadFin) {
46
+ if r.wait {
47
+ r.finChan <- true
48
+ }
49
+ return
50
+ } else {
51
+ pollInterval += readerPollIntervalStep
52
+ if pollInterval > readerPollIntervalMax {
53
+ pollInterval = readerPollIntervalMax
54
+ }
55
+ }
56
+ time.Sleep(pollInterval)
57
+ }
58
+ }()
59
+ }
60
+
61
+ func (r *Reader) fin(success bool) {
62
+ atomic.StoreInt32(&r.event, int32(EvtReadFin))
63
+ if r.wait {
64
+ <-r.finChan
65
+ }
66
+
67
+ r.mutex.Lock()
68
+ ret := r.command
69
+ if success || r.killed {
70
+ ret = nil
71
+ }
72
+ r.mutex.Unlock()
73
+
74
+ r.eventBox.Set(EvtReadFin, ret)
75
+ }
76
+
77
+ func (r *Reader) terminate() {
78
+ r.mutex.Lock()
79
+ defer func() { r.mutex.Unlock() }()
80
+
81
+ r.killed = true
82
+ if r.exec != nil && r.exec.Process != nil {
83
+ util.KillCommand(r.exec)
84
+ } else if defaultCommand != "" {
85
+ os.Stdin.Close()
86
+ }
87
+ }
88
+
89
+ func (r *Reader) restart(command string) {
90
+ r.event = int32(EvtReady)
91
+ r.startEventPoller()
92
+ success := r.readFromCommand(nil, command)
93
+ r.fin(success)
94
+ }
95
+
96
+ // ReadSource reads data from the default command or from standard input
97
+ func (r *Reader) ReadSource() {
98
+ r.startEventPoller()
99
+ var success bool
100
+ if util.IsTty() {
101
+ // The default command for *nix requires bash
102
+ shell := "bash"
103
+ cmd := os.Getenv("FZF_DEFAULT_COMMAND")
104
+ if len(cmd) == 0 {
105
+ if defaultCommand != "" {
106
+ success = r.readFromCommand(&shell, defaultCommand)
107
+ } else {
108
+ success = r.readFiles()
109
+ }
110
+ } else {
111
+ success = r.readFromCommand(nil, cmd)
112
+ }
113
+ } else {
114
+ success = r.readFromStdin()
115
+ }
116
+ r.fin(success)
117
+ }
118
+
119
+ func (r *Reader) feed(src io.Reader) {
120
+ delim := byte('\n')
121
+ if r.delimNil {
122
+ delim = '\000'
123
+ }
124
+ reader := bufio.NewReaderSize(src, readerBufferSize)
125
+ for {
126
+ // ReadBytes returns err != nil if and only if the returned data does not
127
+ // end in delim.
128
+ bytea, err := reader.ReadBytes(delim)
129
+ byteaLen := len(bytea)
130
+ if byteaLen > 0 {
131
+ if err == nil {
132
+ // get rid of carriage return if under Windows:
133
+ if util.IsWindows() && byteaLen >= 2 && bytea[byteaLen-2] == byte('\r') {
134
+ bytea = bytea[:byteaLen-2]
135
+ } else {
136
+ bytea = bytea[:byteaLen-1]
137
+ }
138
+ }
139
+ if r.pusher(bytea) {
140
+ atomic.StoreInt32(&r.event, int32(EvtReadNew))
141
+ }
142
+ }
143
+ if err != nil {
144
+ break
145
+ }
146
+ }
147
+ }
148
+
149
+ func (r *Reader) readFromStdin() bool {
150
+ r.feed(os.Stdin)
151
+ return true
152
+ }
153
+
154
+ func (r *Reader) readFiles() bool {
155
+ r.killed = false
156
+ fn := func(path string, mode os.FileInfo) error {
157
+ path = filepath.Clean(path)
158
+ if path != "." {
159
+ isDir := mode.Mode().IsDir()
160
+ if isDir && filepath.Base(path)[0] == '.' {
161
+ return filepath.SkipDir
162
+ }
163
+ if !isDir && r.pusher([]byte(path)) {
164
+ atomic.StoreInt32(&r.event, int32(EvtReadNew))
165
+ }
166
+ }
167
+ r.mutex.Lock()
168
+ defer r.mutex.Unlock()
169
+ if r.killed {
170
+ return context.Canceled
171
+ }
172
+ return nil
173
+ }
174
+ cb := walker.WithErrorCallback(func(pathname string, err error) error {
175
+ return nil
176
+ })
177
+ return walker.Walk(".", fn, cb) == nil
178
+ }
179
+
180
+ func (r *Reader) readFromCommand(shell *string, command string) bool {
181
+ r.mutex.Lock()
182
+ r.killed = false
183
+ r.command = &command
184
+ if shell != nil {
185
+ r.exec = util.ExecCommandWith(*shell, command, true)
186
+ } else {
187
+ r.exec = util.ExecCommand(command, true)
188
+ }
189
+ out, err := r.exec.StdoutPipe()
190
+ if err != nil {
191
+ r.mutex.Unlock()
192
+ return false
193
+ }
194
+ err = r.exec.Start()
195
+ r.mutex.Unlock()
196
+ if err != nil {
197
+ return false
198
+ }
199
+ r.feed(out)
200
+ return r.exec.Wait() == nil
201
+ }
@@ -0,0 +1,63 @@
1
+ package fzf
2
+
3
+ import (
4
+ "testing"
5
+ "time"
6
+
7
+ "github.com/junegunn/fzf/src/util"
8
+ )
9
+
10
+ func TestReadFromCommand(t *testing.T) {
11
+ strs := []string{}
12
+ eb := util.NewEventBox()
13
+ reader := NewReader(
14
+ func(s []byte) bool { strs = append(strs, string(s)); return true },
15
+ eb, false, true)
16
+
17
+ reader.startEventPoller()
18
+
19
+ // Check EventBox
20
+ if eb.Peek(EvtReadNew) {
21
+ t.Error("EvtReadNew should not be set yet")
22
+ }
23
+
24
+ // Normal command
25
+ reader.fin(reader.readFromCommand(nil, `echo abc&&echo def`))
26
+ if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
27
+ t.Errorf("%s", strs)
28
+ }
29
+
30
+ // Check EventBox again
31
+ eb.WaitFor(EvtReadFin)
32
+
33
+ // Wait should return immediately
34
+ eb.Wait(func(events *util.Events) {
35
+ events.Clear()
36
+ })
37
+
38
+ // EventBox is cleared
39
+ if eb.Peek(EvtReadNew) {
40
+ t.Error("EvtReadNew should not be set yet")
41
+ }
42
+
43
+ // Make sure that event poller is finished
44
+ time.Sleep(readerPollIntervalMax)
45
+
46
+ // Restart event poller
47
+ reader.startEventPoller()
48
+
49
+ // Failing command
50
+ reader.fin(reader.readFromCommand(nil, `no-such-command`))
51
+ strs = []string{}
52
+ if len(strs) > 0 {
53
+ t.Errorf("%s", strs)
54
+ }
55
+
56
+ // Check EventBox again
57
+ if eb.Peek(EvtReadNew) {
58
+ t.Error("Command failed. EvtReadNew should not be set")
59
+ }
60
+ if !eb.Peek(EvtReadFin) {
61
+ t.Error("EvtReadFin should be set")
62
+ }
63
+ }
@@ -0,0 +1,243 @@
1
+ package fzf
2
+
3
+ import (
4
+ "math"
5
+ "sort"
6
+ "unicode"
7
+
8
+ "github.com/junegunn/fzf/src/tui"
9
+ "github.com/junegunn/fzf/src/util"
10
+ )
11
+
12
+ // Offset holds two 32-bit integers denoting the offsets of a matched substring
13
+ type Offset [2]int32
14
+
15
+ type colorOffset struct {
16
+ offset [2]int32
17
+ color tui.ColorPair
18
+ }
19
+
20
+ type Result struct {
21
+ item *Item
22
+ points [4]uint16
23
+ }
24
+
25
+ func buildResult(item *Item, offsets []Offset, score int) Result {
26
+ if len(offsets) > 1 {
27
+ sort.Sort(ByOrder(offsets))
28
+ }
29
+
30
+ result := Result{item: item}
31
+ numChars := item.text.Length()
32
+ minBegin := math.MaxUint16
33
+ minEnd := math.MaxUint16
34
+ maxEnd := 0
35
+ validOffsetFound := false
36
+ for _, offset := range offsets {
37
+ b, e := int(offset[0]), int(offset[1])
38
+ if b < e {
39
+ minBegin = util.Min(b, minBegin)
40
+ minEnd = util.Min(e, minEnd)
41
+ maxEnd = util.Max(e, maxEnd)
42
+ validOffsetFound = true
43
+ }
44
+ }
45
+
46
+ for idx, criterion := range sortCriteria {
47
+ val := uint16(math.MaxUint16)
48
+ switch criterion {
49
+ case byScore:
50
+ // Higher is better
51
+ val = math.MaxUint16 - util.AsUint16(score)
52
+ case byLength:
53
+ val = item.TrimLength()
54
+ case byBegin, byEnd:
55
+ if validOffsetFound {
56
+ whitePrefixLen := 0
57
+ for idx := 0; idx < numChars; idx++ {
58
+ r := item.text.Get(idx)
59
+ whitePrefixLen = idx
60
+ if idx == minBegin || !unicode.IsSpace(r) {
61
+ break
62
+ }
63
+ }
64
+ if criterion == byBegin {
65
+ val = util.AsUint16(minEnd - whitePrefixLen)
66
+ } else {
67
+ val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/int(item.TrimLength()))
68
+ }
69
+ }
70
+ }
71
+ result.points[3-idx] = val
72
+ }
73
+
74
+ return result
75
+ }
76
+
77
+ // Sort criteria to use. Never changes once fzf is started.
78
+ var sortCriteria []criterion
79
+
80
+ // Index returns ordinal index of the Item
81
+ func (result *Result) Index() int32 {
82
+ return result.item.Index()
83
+ }
84
+
85
+ func minRank() Result {
86
+ return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
87
+ }
88
+
89
+ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, current bool) []colorOffset {
90
+ itemColors := result.item.Colors()
91
+
92
+ // No ANSI codes
93
+ if len(itemColors) == 0 {
94
+ var offsets []colorOffset
95
+ for _, off := range matchOffsets {
96
+ offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: colMatch})
97
+ }
98
+ return offsets
99
+ }
100
+
101
+ // Find max column
102
+ var maxCol int32
103
+ for _, off := range matchOffsets {
104
+ if off[1] > maxCol {
105
+ maxCol = off[1]
106
+ }
107
+ }
108
+ for _, ansi := range itemColors {
109
+ if ansi.offset[1] > maxCol {
110
+ maxCol = ansi.offset[1]
111
+ }
112
+ }
113
+
114
+ cols := make([]int, maxCol)
115
+ for colorIndex, ansi := range itemColors {
116
+ for i := ansi.offset[0]; i < ansi.offset[1]; i++ {
117
+ cols[i] = colorIndex + 1 // 1-based index of itemColors
118
+ }
119
+ }
120
+
121
+ for _, off := range matchOffsets {
122
+ for i := off[0]; i < off[1]; i++ {
123
+ // Negative of 1-based index of itemColors
124
+ // - The extra -1 means highlighted
125
+ cols[i] = cols[i]*-1 - 1
126
+ }
127
+ }
128
+
129
+ // sort.Sort(ByOrder(offsets))
130
+
131
+ // Merge offsets
132
+ // ------------ ---- -- ----
133
+ // ++++++++ ++++++++++
134
+ // --++++++++-- --++++++++++---
135
+ curr := 0
136
+ start := 0
137
+ ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair {
138
+ fg := ansi.color.fg
139
+ bg := ansi.color.bg
140
+ if fg == -1 {
141
+ if current {
142
+ fg = theme.Current.Color
143
+ } else {
144
+ fg = theme.Fg.Color
145
+ }
146
+ }
147
+ if bg == -1 {
148
+ if current {
149
+ bg = theme.DarkBg.Color
150
+ } else {
151
+ bg = theme.Bg.Color
152
+ }
153
+ }
154
+ return tui.NewColorPair(fg, bg, ansi.color.attr).MergeAttr(base)
155
+ }
156
+ var colors []colorOffset
157
+ add := func(idx int) {
158
+ if curr != 0 && idx > start {
159
+ if curr < 0 {
160
+ color := colMatch
161
+ if curr < -1 && theme.Colored {
162
+ origColor := ansiToColorPair(itemColors[-curr-2], colMatch)
163
+ // hl or hl+ only sets the foreground color, so colMatch is the
164
+ // combination of either [hl and bg] or [hl+ and bg+].
165
+ //
166
+ // If the original text already has background color, and the
167
+ // foreground color of colMatch is -1, we shouldn't only apply the
168
+ // background color of colMatch.
169
+ // e.g. echo -e "\x1b[32;7mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline
170
+ // echo -e "\x1b[42mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline
171
+ if color.Fg().IsDefault() && origColor.HasBg() {
172
+ color = origColor
173
+ } else {
174
+ color = origColor.MergeNonDefault(color)
175
+ }
176
+ }
177
+ colors = append(colors, colorOffset{
178
+ offset: [2]int32{int32(start), int32(idx)}, color: color})
179
+ } else {
180
+ ansi := itemColors[curr-1]
181
+ colors = append(colors, colorOffset{
182
+ offset: [2]int32{int32(start), int32(idx)},
183
+ color: ansiToColorPair(ansi, colBase)})
184
+ }
185
+ }
186
+ }
187
+ for idx, col := range cols {
188
+ if col != curr {
189
+ add(idx)
190
+ start = idx
191
+ curr = col
192
+ }
193
+ }
194
+ add(int(maxCol))
195
+ return colors
196
+ }
197
+
198
+ // ByOrder is for sorting substring offsets
199
+ type ByOrder []Offset
200
+
201
+ func (a ByOrder) Len() int {
202
+ return len(a)
203
+ }
204
+
205
+ func (a ByOrder) Swap(i, j int) {
206
+ a[i], a[j] = a[j], a[i]
207
+ }
208
+
209
+ func (a ByOrder) Less(i, j int) bool {
210
+ ioff := a[i]
211
+ joff := a[j]
212
+ return (ioff[0] < joff[0]) || (ioff[0] == joff[0]) && (ioff[1] <= joff[1])
213
+ }
214
+
215
+ // ByRelevance is for sorting Items
216
+ type ByRelevance []Result
217
+
218
+ func (a ByRelevance) Len() int {
219
+ return len(a)
220
+ }
221
+
222
+ func (a ByRelevance) Swap(i, j int) {
223
+ a[i], a[j] = a[j], a[i]
224
+ }
225
+
226
+ func (a ByRelevance) Less(i, j int) bool {
227
+ return compareRanks(a[i], a[j], false)
228
+ }
229
+
230
+ // ByRelevanceTac is for sorting Items
231
+ type ByRelevanceTac []Result
232
+
233
+ func (a ByRelevanceTac) Len() int {
234
+ return len(a)
235
+ }
236
+
237
+ func (a ByRelevanceTac) Swap(i, j int) {
238
+ a[i], a[j] = a[j], a[i]
239
+ }
240
+
241
+ func (a ByRelevanceTac) Less(i, j int) bool {
242
+ return compareRanks(a[i], a[j], true)
243
+ }
@@ -0,0 +1,16 @@
1
+ // +build !386,!amd64
2
+
3
+ package fzf
4
+
5
+ func compareRanks(irank Result, jrank Result, tac bool) bool {
6
+ for idx := 3; idx >= 0; idx-- {
7
+ left := irank.points[idx]
8
+ right := jrank.points[idx]
9
+ if left < right {
10
+ return true
11
+ } else if left > right {
12
+ return false
13
+ }
14
+ }
15
+ return (irank.item.Index() <= jrank.item.Index()) != tac
16
+ }
@@ -0,0 +1,159 @@
1
+ // +build !tcell
2
+
3
+ package fzf
4
+
5
+ import (
6
+ "math"
7
+ "sort"
8
+ "testing"
9
+
10
+ "github.com/junegunn/fzf/src/tui"
11
+ "github.com/junegunn/fzf/src/util"
12
+ )
13
+
14
+ func withIndex(i *Item, index int) *Item {
15
+ (*i).text.Index = int32(index)
16
+ return i
17
+ }
18
+
19
+ func TestOffsetSort(t *testing.T) {
20
+ offsets := []Offset{
21
+ {3, 5}, {2, 7},
22
+ {1, 3}, {2, 9}}
23
+ sort.Sort(ByOrder(offsets))
24
+
25
+ if offsets[0][0] != 1 || offsets[0][1] != 3 ||
26
+ offsets[1][0] != 2 || offsets[1][1] != 7 ||
27
+ offsets[2][0] != 2 || offsets[2][1] != 9 ||
28
+ offsets[3][0] != 3 || offsets[3][1] != 5 {
29
+ t.Error("Invalid order:", offsets)
30
+ }
31
+ }
32
+
33
+ func TestRankComparison(t *testing.T) {
34
+ rank := func(vals ...uint16) Result {
35
+ return Result{
36
+ points: [4]uint16{vals[0], vals[1], vals[2], vals[3]},
37
+ item: &Item{text: util.Chars{Index: int32(vals[4])}}}
38
+ }
39
+ if compareRanks(rank(3, 0, 0, 0, 5), rank(2, 0, 0, 0, 7), false) ||
40
+ !compareRanks(rank(3, 0, 0, 0, 5), rank(3, 0, 0, 0, 6), false) ||
41
+ !compareRanks(rank(1, 2, 0, 0, 3), rank(1, 3, 0, 0, 2), false) ||
42
+ !compareRanks(rank(0, 0, 0, 0, 0), rank(0, 0, 0, 0, 0), false) {
43
+ t.Error("Invalid order")
44
+ }
45
+
46
+ if compareRanks(rank(3, 0, 0, 0, 5), rank(2, 0, 0, 0, 7), true) ||
47
+ !compareRanks(rank(3, 0, 0, 0, 5), rank(3, 0, 0, 0, 6), false) ||
48
+ !compareRanks(rank(1, 2, 0, 0, 3), rank(1, 3, 0, 0, 2), true) ||
49
+ !compareRanks(rank(0, 0, 0, 0, 0), rank(0, 0, 0, 0, 0), false) {
50
+ t.Error("Invalid order (tac)")
51
+ }
52
+ }
53
+
54
+ // Match length, string length, index
55
+ func TestResultRank(t *testing.T) {
56
+ // FIXME global
57
+ sortCriteria = []criterion{byScore, byLength}
58
+
59
+ strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
60
+ item1 := buildResult(
61
+ withIndex(&Item{text: util.RunesToChars(strs[0])}, 1), []Offset{}, 2)
62
+ if item1.points[3] != math.MaxUint16-2 || // Bonus
63
+ item1.points[2] != 3 || // Length
64
+ item1.points[1] != 0 || // Unused
65
+ item1.points[0] != 0 || // Unused
66
+ item1.item.Index() != 1 {
67
+ t.Error(item1)
68
+ }
69
+ // Only differ in index
70
+ item2 := buildResult(&Item{text: util.RunesToChars(strs[0])}, []Offset{}, 2)
71
+
72
+ items := []Result{item1, item2}
73
+ sort.Sort(ByRelevance(items))
74
+ if items[0] != item2 || items[1] != item1 {
75
+ t.Error(items)
76
+ }
77
+
78
+ items = []Result{item2, item1, item1, item2}
79
+ sort.Sort(ByRelevance(items))
80
+ if items[0] != item2 || items[1] != item2 ||
81
+ items[2] != item1 || items[3] != item1 {
82
+ t.Error(items, item1, item1.item.Index(), item2, item2.item.Index())
83
+ }
84
+
85
+ // Sort by relevance
86
+ item3 := buildResult(
87
+ withIndex(&Item{}, 2), []Offset{{1, 3}, {5, 7}}, 3)
88
+ item4 := buildResult(
89
+ withIndex(&Item{}, 2), []Offset{{1, 2}, {6, 7}}, 4)
90
+ item5 := buildResult(
91
+ withIndex(&Item{}, 2), []Offset{{1, 3}, {5, 7}}, 5)
92
+ item6 := buildResult(
93
+ withIndex(&Item{}, 2), []Offset{{1, 2}, {6, 7}}, 6)
94
+ items = []Result{item1, item2, item3, item4, item5, item6}
95
+ sort.Sort(ByRelevance(items))
96
+ if !(items[0] == item6 && items[1] == item5 &&
97
+ items[2] == item4 && items[3] == item3 &&
98
+ items[4] == item2 && items[5] == item1) {
99
+ t.Error(items, item1, item2, item3, item4, item5, item6)
100
+ }
101
+ }
102
+
103
+ func TestColorOffset(t *testing.T) {
104
+ // ------------ 20 ---- -- ----
105
+ // ++++++++ ++++++++++
106
+ // --++++++++-- --++++++++++---
107
+
108
+ offsets := []Offset{{5, 15}, {25, 35}}
109
+ item := Result{
110
+ item: &Item{
111
+ colors: &[]ansiOffset{
112
+ {[2]int32{0, 20}, ansiState{1, 5, 0, -1}},
113
+ {[2]int32{22, 27}, ansiState{2, 6, tui.Bold, -1}},
114
+ {[2]int32{30, 32}, ansiState{3, 7, 0, -1}},
115
+ {[2]int32{33, 40}, ansiState{4, 8, tui.Bold, -1}}}}}
116
+
117
+ colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
118
+ colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
119
+ colors := item.colorOffsets(offsets, tui.Dark256, colBase, colMatch, true)
120
+ assert := func(idx int, b int32, e int32, c tui.ColorPair) {
121
+ o := colors[idx]
122
+ if o.offset[0] != b || o.offset[1] != e || o.color != c {
123
+ t.Error(o, b, e, c)
124
+ }
125
+ }
126
+ // [{[0 5] {1 5 0}} {[5 15] {99 199 0}} {[15 20] {1 5 0}}
127
+ // {[22 25] {2 6 1}} {[25 27] {99 199 1}} {[27 30] {99 199 0}}
128
+ // {[30 32] {99 199 0}} {[32 33] {99 199 0}} {[33 35] {99 199 1}}
129
+ // {[35 40] {4 8 1}}]
130
+ assert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))
131
+ assert(1, 5, 15, colMatch)
132
+ assert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined))
133
+ assert(3, 22, 25, tui.NewColorPair(2, 6, tui.Bold))
134
+ assert(4, 25, 27, colMatch.WithAttr(tui.Bold))
135
+ assert(5, 27, 30, colMatch)
136
+ assert(6, 30, 32, colMatch)
137
+ assert(7, 32, 33, colMatch) // TODO: Should we merge consecutive blocks?
138
+ assert(8, 33, 35, colMatch.WithAttr(tui.Bold))
139
+ assert(9, 35, 40, tui.NewColorPair(4, 8, tui.Bold))
140
+
141
+ colRegular := tui.NewColorPair(-1, -1, tui.AttrUndefined)
142
+ colUnderline := tui.NewColorPair(-1, -1, tui.Underline)
143
+ colors = item.colorOffsets(offsets, tui.Dark256, colRegular, colUnderline, true)
144
+
145
+ // [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}}
146
+ // {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}}
147
+ // {[30 32] {3 7 8}} {[32 33] {-1 -1 8}} {[33 35] {4 8 9}}
148
+ // {[35 40] {4 8 1}}]
149
+ assert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))
150
+ assert(1, 5, 15, tui.NewColorPair(1, 5, tui.Underline))
151
+ assert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined))
152
+ assert(3, 22, 25, tui.NewColorPair(2, 6, tui.Bold))
153
+ assert(4, 25, 27, tui.NewColorPair(2, 6, tui.Bold|tui.Underline))
154
+ assert(5, 27, 30, colUnderline)
155
+ assert(6, 30, 32, tui.NewColorPair(3, 7, tui.Underline))
156
+ assert(7, 32, 33, colUnderline)
157
+ assert(8, 33, 35, tui.NewColorPair(4, 8, tui.Bold|tui.Underline))
158
+ assert(9, 35, 40, tui.NewColorPair(4, 8, tui.Bold))
159
+ }
@@ -0,0 +1,16 @@
1
+ // +build 386 amd64
2
+
3
+ package fzf
4
+
5
+ import "unsafe"
6
+
7
+ func compareRanks(irank Result, jrank Result, tac bool) bool {
8
+ left := *(*uint64)(unsafe.Pointer(&irank.points[0]))
9
+ right := *(*uint64)(unsafe.Pointer(&jrank.points[0]))
10
+ if left < right {
11
+ return true
12
+ } else if left > right {
13
+ return false
14
+ }
15
+ return (irank.item.Index() <= jrank.item.Index()) != tac
16
+ }