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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/doing.rdoc +1 -1
- data/lib/doing/version.rb +1 -1
- data/lib/helpers/fzf/.goreleaser.yml +119 -0
- data/lib/helpers/fzf/.rubocop.yml +28 -0
- data/lib/helpers/fzf/ADVANCED.md +565 -0
- data/lib/helpers/fzf/BUILD.md +49 -0
- data/lib/helpers/fzf/CHANGELOG.md +1193 -0
- data/lib/helpers/fzf/Dockerfile +11 -0
- data/lib/helpers/fzf/LICENSE +21 -0
- data/lib/helpers/fzf/Makefile +166 -0
- data/lib/helpers/fzf/README-VIM.md +486 -0
- data/lib/helpers/fzf/README.md +712 -0
- data/lib/helpers/fzf/bin/fzf-tmux +233 -0
- data/lib/helpers/fzf/doc/fzf.txt +512 -0
- data/lib/helpers/fzf/go.mod +17 -0
- data/lib/helpers/fzf/go.sum +31 -0
- data/lib/helpers/fzf/install +382 -0
- data/lib/helpers/fzf/install.ps1 +65 -0
- data/lib/helpers/fzf/main.go +14 -0
- data/lib/helpers/fzf/man/man1/fzf-tmux.1 +68 -0
- data/lib/helpers/fzf/man/man1/fzf.1 +1001 -0
- data/lib/helpers/fzf/plugin/fzf.vim +1048 -0
- data/lib/helpers/fzf/shell/completion.bash +381 -0
- data/lib/helpers/fzf/shell/completion.zsh +329 -0
- data/lib/helpers/fzf/shell/key-bindings.bash +96 -0
- data/lib/helpers/fzf/shell/key-bindings.fish +172 -0
- data/lib/helpers/fzf/shell/key-bindings.zsh +114 -0
- data/lib/helpers/fzf/src/LICENSE +21 -0
- data/lib/helpers/fzf/src/algo/algo.go +884 -0
- data/lib/helpers/fzf/src/algo/algo_test.go +197 -0
- data/lib/helpers/fzf/src/algo/normalize.go +492 -0
- data/lib/helpers/fzf/src/ansi.go +409 -0
- data/lib/helpers/fzf/src/ansi_test.go +427 -0
- data/lib/helpers/fzf/src/cache.go +81 -0
- data/lib/helpers/fzf/src/cache_test.go +39 -0
- data/lib/helpers/fzf/src/chunklist.go +89 -0
- data/lib/helpers/fzf/src/chunklist_test.go +80 -0
- data/lib/helpers/fzf/src/constants.go +85 -0
- data/lib/helpers/fzf/src/core.go +351 -0
- data/lib/helpers/fzf/src/history.go +96 -0
- data/lib/helpers/fzf/src/history_test.go +68 -0
- data/lib/helpers/fzf/src/item.go +44 -0
- data/lib/helpers/fzf/src/item_test.go +23 -0
- data/lib/helpers/fzf/src/matcher.go +235 -0
- data/lib/helpers/fzf/src/merger.go +120 -0
- data/lib/helpers/fzf/src/merger_test.go +88 -0
- data/lib/helpers/fzf/src/options.go +1691 -0
- data/lib/helpers/fzf/src/options_test.go +457 -0
- data/lib/helpers/fzf/src/pattern.go +425 -0
- data/lib/helpers/fzf/src/pattern_test.go +209 -0
- data/lib/helpers/fzf/src/protector/protector.go +8 -0
- data/lib/helpers/fzf/src/protector/protector_openbsd.go +10 -0
- data/lib/helpers/fzf/src/reader.go +201 -0
- data/lib/helpers/fzf/src/reader_test.go +63 -0
- data/lib/helpers/fzf/src/result.go +243 -0
- data/lib/helpers/fzf/src/result_others.go +16 -0
- data/lib/helpers/fzf/src/result_test.go +159 -0
- data/lib/helpers/fzf/src/result_x86.go +16 -0
- data/lib/helpers/fzf/src/terminal.go +2832 -0
- data/lib/helpers/fzf/src/terminal_test.go +638 -0
- data/lib/helpers/fzf/src/terminal_unix.go +26 -0
- data/lib/helpers/fzf/src/terminal_windows.go +45 -0
- data/lib/helpers/fzf/src/tokenizer.go +253 -0
- data/lib/helpers/fzf/src/tokenizer_test.go +112 -0
- data/lib/helpers/fzf/src/tui/dummy.go +46 -0
- data/lib/helpers/fzf/src/tui/light.go +987 -0
- data/lib/helpers/fzf/src/tui/light_unix.go +110 -0
- data/lib/helpers/fzf/src/tui/light_windows.go +145 -0
- data/lib/helpers/fzf/src/tui/tcell.go +721 -0
- data/lib/helpers/fzf/src/tui/tcell_test.go +392 -0
- data/lib/helpers/fzf/src/tui/ttyname_unix.go +47 -0
- data/lib/helpers/fzf/src/tui/ttyname_windows.go +14 -0
- data/lib/helpers/fzf/src/tui/tui.go +625 -0
- data/lib/helpers/fzf/src/tui/tui_test.go +20 -0
- data/lib/helpers/fzf/src/util/atomicbool.go +34 -0
- data/lib/helpers/fzf/src/util/atomicbool_test.go +17 -0
- data/lib/helpers/fzf/src/util/chars.go +198 -0
- data/lib/helpers/fzf/src/util/chars_test.go +46 -0
- data/lib/helpers/fzf/src/util/eventbox.go +96 -0
- data/lib/helpers/fzf/src/util/eventbox_test.go +61 -0
- data/lib/helpers/fzf/src/util/slab.go +12 -0
- data/lib/helpers/fzf/src/util/util.go +138 -0
- data/lib/helpers/fzf/src/util/util_test.go +40 -0
- data/lib/helpers/fzf/src/util/util_unix.go +47 -0
- data/lib/helpers/fzf/src/util/util_windows.go +83 -0
- data/lib/helpers/fzf/test/fzf.vader +175 -0
- data/lib/helpers/fzf/test/test_go.rb +2626 -0
- data/lib/helpers/fzf/uninstall +117 -0
- metadata +87 -1
|
@@ -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
|
+
}
|