doing 2.0.19 → 2.0.23
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/Gemfile.lock +15 -5
- data/README.md +1 -1
- data/bin/doing +2 -18
- data/doing.gemspec +5 -4
- data/doing.rdoc +2 -2
- data/generate_completions.sh +3 -3
- data/lib/doing/cli_status.rb +6 -2
- data/lib/doing/completion/bash_completion.rb +185 -0
- data/lib/doing/completion/fish_completion.rb +175 -0
- data/lib/doing/completion/string.rb +17 -0
- data/lib/doing/completion/zsh_completion.rb +140 -0
- data/lib/doing/completion.rb +39 -0
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +19 -5
- data/lib/doing.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
- data/scripts/generate_bash_completions.rb +6 -12
- data/scripts/generate_fish_completions.rb +7 -16
- data/scripts/generate_zsh_completions.rb +6 -15
- 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
|
+
}
|