doing 2.0.18 → 2.0.22
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -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 +18 -6
- 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,85 @@
|
|
1
|
+
package fzf
|
2
|
+
|
3
|
+
import (
|
4
|
+
"math"
|
5
|
+
"os"
|
6
|
+
"time"
|
7
|
+
|
8
|
+
"github.com/junegunn/fzf/src/util"
|
9
|
+
)
|
10
|
+
|
11
|
+
const (
|
12
|
+
// Core
|
13
|
+
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
14
|
+
coordinatorDelayStep time.Duration = 10 * time.Millisecond
|
15
|
+
|
16
|
+
// Reader
|
17
|
+
readerBufferSize = 64 * 1024
|
18
|
+
readerPollIntervalMin = 10 * time.Millisecond
|
19
|
+
readerPollIntervalStep = 5 * time.Millisecond
|
20
|
+
readerPollIntervalMax = 50 * time.Millisecond
|
21
|
+
|
22
|
+
// Terminal
|
23
|
+
initialDelay = 20 * time.Millisecond
|
24
|
+
initialDelayTac = 100 * time.Millisecond
|
25
|
+
spinnerDuration = 100 * time.Millisecond
|
26
|
+
previewCancelWait = 500 * time.Millisecond
|
27
|
+
previewChunkDelay = 100 * time.Millisecond
|
28
|
+
previewDelayed = 500 * time.Millisecond
|
29
|
+
maxPatternLength = 300
|
30
|
+
maxMulti = math.MaxInt32
|
31
|
+
|
32
|
+
// Matcher
|
33
|
+
numPartitionsMultiplier = 8
|
34
|
+
maxPartitions = 32
|
35
|
+
progressMinDuration = 200 * time.Millisecond
|
36
|
+
|
37
|
+
// Capacity of each chunk
|
38
|
+
chunkSize int = 100
|
39
|
+
|
40
|
+
// Pre-allocated memory slices to minimize GC
|
41
|
+
slab16Size int = 100 * 1024 // 200KB * 32 = 12.8MB
|
42
|
+
slab32Size int = 2048 // 8KB * 32 = 256KB
|
43
|
+
|
44
|
+
// Do not cache results of low selectivity queries
|
45
|
+
queryCacheMax int = chunkSize / 5
|
46
|
+
|
47
|
+
// Not to cache mergers with large lists
|
48
|
+
mergerCacheMax int = 100000
|
49
|
+
|
50
|
+
// History
|
51
|
+
defaultHistoryMax int = 1000
|
52
|
+
|
53
|
+
// Jump labels
|
54
|
+
defaultJumpLabels string = "asdfghjklqwertyuiopzxcvbnm1234567890ASDFGHJKLQWERTYUIOPZXCVBNM`~;:,<.>/?'\"!@#$%^&*()[{]}-_=+"
|
55
|
+
)
|
56
|
+
|
57
|
+
var defaultCommand string
|
58
|
+
|
59
|
+
func init() {
|
60
|
+
if !util.IsWindows() {
|
61
|
+
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
|
62
|
+
} else if os.Getenv("TERM") == "cygwin" {
|
63
|
+
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
// fzf events
|
68
|
+
const (
|
69
|
+
EvtReadNew util.EventType = iota
|
70
|
+
EvtReadFin
|
71
|
+
EvtSearchNew
|
72
|
+
EvtSearchProgress
|
73
|
+
EvtSearchFin
|
74
|
+
EvtHeader
|
75
|
+
EvtReady
|
76
|
+
EvtQuit
|
77
|
+
)
|
78
|
+
|
79
|
+
const (
|
80
|
+
exitCancel = -1
|
81
|
+
exitOk = 0
|
82
|
+
exitNoMatch = 1
|
83
|
+
exitError = 2
|
84
|
+
exitInterrupt = 130
|
85
|
+
)
|
@@ -0,0 +1,351 @@
|
|
1
|
+
/*
|
2
|
+
Package fzf implements fzf, a command-line fuzzy finder.
|
3
|
+
|
4
|
+
The MIT License (MIT)
|
5
|
+
|
6
|
+
Copyright (c) 2013-2021 Junegunn Choi
|
7
|
+
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
10
|
+
in the Software without restriction, including without limitation the rights
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
13
|
+
furnished to do so, subject to the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be included in
|
16
|
+
all copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
24
|
+
THE SOFTWARE.
|
25
|
+
*/
|
26
|
+
package fzf
|
27
|
+
|
28
|
+
import (
|
29
|
+
"fmt"
|
30
|
+
"os"
|
31
|
+
"time"
|
32
|
+
|
33
|
+
"github.com/junegunn/fzf/src/util"
|
34
|
+
)
|
35
|
+
|
36
|
+
/*
|
37
|
+
Reader -> EvtReadFin
|
38
|
+
Reader -> EvtReadNew -> Matcher (restart)
|
39
|
+
Terminal -> EvtSearchNew:bool -> Matcher (restart)
|
40
|
+
Matcher -> EvtSearchProgress -> Terminal (update info)
|
41
|
+
Matcher -> EvtSearchFin -> Terminal (update list)
|
42
|
+
Matcher -> EvtHeader -> Terminal (update header)
|
43
|
+
*/
|
44
|
+
|
45
|
+
// Run starts fzf
|
46
|
+
func Run(opts *Options, version string, revision string) {
|
47
|
+
sort := opts.Sort > 0
|
48
|
+
sortCriteria = opts.Criteria
|
49
|
+
|
50
|
+
if opts.Version {
|
51
|
+
if len(revision) > 0 {
|
52
|
+
fmt.Printf("%s (%s)\n", version, revision)
|
53
|
+
} else {
|
54
|
+
fmt.Println(version)
|
55
|
+
}
|
56
|
+
os.Exit(exitOk)
|
57
|
+
}
|
58
|
+
|
59
|
+
// Event channel
|
60
|
+
eventBox := util.NewEventBox()
|
61
|
+
|
62
|
+
// ANSI code processor
|
63
|
+
ansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) {
|
64
|
+
return util.ToChars(data), nil
|
65
|
+
}
|
66
|
+
|
67
|
+
var lineAnsiState, prevLineAnsiState *ansiState
|
68
|
+
if opts.Ansi {
|
69
|
+
if opts.Theme.Colored {
|
70
|
+
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
71
|
+
prevLineAnsiState = lineAnsiState
|
72
|
+
trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
|
73
|
+
lineAnsiState = newState
|
74
|
+
return util.ToChars([]byte(trimmed)), offsets
|
75
|
+
}
|
76
|
+
} else {
|
77
|
+
// When color is disabled but ansi option is given,
|
78
|
+
// we simply strip out ANSI codes from the input
|
79
|
+
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
80
|
+
trimmed, _, _ := extractColor(string(data), nil, nil)
|
81
|
+
return util.ToChars([]byte(trimmed)), nil
|
82
|
+
}
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
// Chunk list
|
87
|
+
var chunkList *ChunkList
|
88
|
+
var itemIndex int32
|
89
|
+
header := make([]string, 0, opts.HeaderLines)
|
90
|
+
if len(opts.WithNth) == 0 {
|
91
|
+
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
92
|
+
if len(header) < opts.HeaderLines {
|
93
|
+
header = append(header, string(data))
|
94
|
+
eventBox.Set(EvtHeader, header)
|
95
|
+
return false
|
96
|
+
}
|
97
|
+
item.text, item.colors = ansiProcessor(data)
|
98
|
+
item.text.Index = itemIndex
|
99
|
+
itemIndex++
|
100
|
+
return true
|
101
|
+
})
|
102
|
+
} else {
|
103
|
+
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
104
|
+
tokens := Tokenize(string(data), opts.Delimiter)
|
105
|
+
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
|
106
|
+
var ansiState *ansiState
|
107
|
+
if prevLineAnsiState != nil {
|
108
|
+
ansiStateDup := *prevLineAnsiState
|
109
|
+
ansiState = &ansiStateDup
|
110
|
+
}
|
111
|
+
for _, token := range tokens {
|
112
|
+
prevAnsiState := ansiState
|
113
|
+
_, _, ansiState = extractColor(token.text.ToString(), ansiState, nil)
|
114
|
+
if prevAnsiState != nil {
|
115
|
+
token.text.Prepend("\x1b[m" + prevAnsiState.ToString())
|
116
|
+
} else {
|
117
|
+
token.text.Prepend("\x1b[m")
|
118
|
+
}
|
119
|
+
}
|
120
|
+
}
|
121
|
+
trans := Transform(tokens, opts.WithNth)
|
122
|
+
transformed := joinTokens(trans)
|
123
|
+
if len(header) < opts.HeaderLines {
|
124
|
+
header = append(header, transformed)
|
125
|
+
eventBox.Set(EvtHeader, header)
|
126
|
+
return false
|
127
|
+
}
|
128
|
+
item.text, item.colors = ansiProcessor([]byte(transformed))
|
129
|
+
item.text.TrimTrailingWhitespaces()
|
130
|
+
item.text.Index = itemIndex
|
131
|
+
item.origText = &data
|
132
|
+
itemIndex++
|
133
|
+
return true
|
134
|
+
})
|
135
|
+
}
|
136
|
+
|
137
|
+
// Reader
|
138
|
+
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
139
|
+
var reader *Reader
|
140
|
+
if !streamingFilter {
|
141
|
+
reader = NewReader(func(data []byte) bool {
|
142
|
+
return chunkList.Push(data)
|
143
|
+
}, eventBox, opts.ReadZero, opts.Filter == nil)
|
144
|
+
go reader.ReadSource()
|
145
|
+
}
|
146
|
+
|
147
|
+
// Matcher
|
148
|
+
forward := true
|
149
|
+
for _, cri := range opts.Criteria[1:] {
|
150
|
+
if cri == byEnd {
|
151
|
+
forward = false
|
152
|
+
break
|
153
|
+
}
|
154
|
+
if cri == byBegin {
|
155
|
+
break
|
156
|
+
}
|
157
|
+
}
|
158
|
+
patternBuilder := func(runes []rune) *Pattern {
|
159
|
+
return BuildPattern(
|
160
|
+
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward,
|
161
|
+
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
|
162
|
+
}
|
163
|
+
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)
|
164
|
+
|
165
|
+
// Filtering mode
|
166
|
+
if opts.Filter != nil {
|
167
|
+
if opts.PrintQuery {
|
168
|
+
opts.Printer(*opts.Filter)
|
169
|
+
}
|
170
|
+
|
171
|
+
pattern := patternBuilder([]rune(*opts.Filter))
|
172
|
+
matcher.sort = pattern.sortable
|
173
|
+
|
174
|
+
found := false
|
175
|
+
if streamingFilter {
|
176
|
+
slab := util.MakeSlab(slab16Size, slab32Size)
|
177
|
+
reader := NewReader(
|
178
|
+
func(runes []byte) bool {
|
179
|
+
item := Item{}
|
180
|
+
if chunkList.trans(&item, runes) {
|
181
|
+
if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
|
182
|
+
opts.Printer(item.text.ToString())
|
183
|
+
found = true
|
184
|
+
}
|
185
|
+
}
|
186
|
+
return false
|
187
|
+
}, eventBox, opts.ReadZero, false)
|
188
|
+
reader.ReadSource()
|
189
|
+
} else {
|
190
|
+
eventBox.Unwatch(EvtReadNew)
|
191
|
+
eventBox.WaitFor(EvtReadFin)
|
192
|
+
|
193
|
+
snapshot, _ := chunkList.Snapshot()
|
194
|
+
merger, _ := matcher.scan(MatchRequest{
|
195
|
+
chunks: snapshot,
|
196
|
+
pattern: pattern})
|
197
|
+
for i := 0; i < merger.Length(); i++ {
|
198
|
+
opts.Printer(merger.Get(i).item.AsString(opts.Ansi))
|
199
|
+
found = true
|
200
|
+
}
|
201
|
+
}
|
202
|
+
if found {
|
203
|
+
os.Exit(exitOk)
|
204
|
+
}
|
205
|
+
os.Exit(exitNoMatch)
|
206
|
+
}
|
207
|
+
|
208
|
+
// Synchronous search
|
209
|
+
if opts.Sync {
|
210
|
+
eventBox.Unwatch(EvtReadNew)
|
211
|
+
eventBox.WaitFor(EvtReadFin)
|
212
|
+
}
|
213
|
+
|
214
|
+
// Go interactive
|
215
|
+
go matcher.Loop()
|
216
|
+
|
217
|
+
// Terminal I/O
|
218
|
+
terminal := NewTerminal(opts, eventBox)
|
219
|
+
deferred := opts.Select1 || opts.Exit0
|
220
|
+
go terminal.Loop()
|
221
|
+
if !deferred {
|
222
|
+
terminal.startChan <- true
|
223
|
+
}
|
224
|
+
|
225
|
+
// Event coordination
|
226
|
+
reading := true
|
227
|
+
clearCache := util.Once(false)
|
228
|
+
clearSelection := util.Once(false)
|
229
|
+
ticks := 0
|
230
|
+
var nextCommand *string
|
231
|
+
restart := func(command string) {
|
232
|
+
reading = true
|
233
|
+
clearCache = util.Once(true)
|
234
|
+
clearSelection = util.Once(true)
|
235
|
+
chunkList.Clear()
|
236
|
+
itemIndex = 0
|
237
|
+
header = make([]string, 0, opts.HeaderLines)
|
238
|
+
go reader.restart(command)
|
239
|
+
}
|
240
|
+
eventBox.Watch(EvtReadNew)
|
241
|
+
query := []rune{}
|
242
|
+
for {
|
243
|
+
delay := true
|
244
|
+
ticks++
|
245
|
+
input := func() []rune {
|
246
|
+
paused, input := terminal.Input()
|
247
|
+
if !paused {
|
248
|
+
query = input
|
249
|
+
}
|
250
|
+
return query
|
251
|
+
}
|
252
|
+
eventBox.Wait(func(events *util.Events) {
|
253
|
+
if _, fin := (*events)[EvtReadFin]; fin {
|
254
|
+
delete(*events, EvtReadNew)
|
255
|
+
}
|
256
|
+
for evt, value := range *events {
|
257
|
+
switch evt {
|
258
|
+
case EvtQuit:
|
259
|
+
if reading {
|
260
|
+
reader.terminate()
|
261
|
+
}
|
262
|
+
os.Exit(value.(int))
|
263
|
+
case EvtReadNew, EvtReadFin:
|
264
|
+
if evt == EvtReadFin && nextCommand != nil {
|
265
|
+
restart(*nextCommand)
|
266
|
+
nextCommand = nil
|
267
|
+
break
|
268
|
+
} else {
|
269
|
+
reading = reading && evt == EvtReadNew
|
270
|
+
}
|
271
|
+
snapshot, count := chunkList.Snapshot()
|
272
|
+
terminal.UpdateCount(count, !reading, value.(*string))
|
273
|
+
if opts.Sync {
|
274
|
+
opts.Sync = false
|
275
|
+
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
|
276
|
+
}
|
277
|
+
matcher.Reset(snapshot, input(), false, !reading, sort, clearCache())
|
278
|
+
|
279
|
+
case EvtSearchNew:
|
280
|
+
var command *string
|
281
|
+
switch val := value.(type) {
|
282
|
+
case searchRequest:
|
283
|
+
sort = val.sort
|
284
|
+
command = val.command
|
285
|
+
}
|
286
|
+
if command != nil {
|
287
|
+
if reading {
|
288
|
+
reader.terminate()
|
289
|
+
nextCommand = command
|
290
|
+
} else {
|
291
|
+
restart(*command)
|
292
|
+
}
|
293
|
+
break
|
294
|
+
}
|
295
|
+
snapshot, _ := chunkList.Snapshot()
|
296
|
+
matcher.Reset(snapshot, input(), true, !reading, sort, clearCache())
|
297
|
+
delay = false
|
298
|
+
|
299
|
+
case EvtSearchProgress:
|
300
|
+
switch val := value.(type) {
|
301
|
+
case float32:
|
302
|
+
terminal.UpdateProgress(val)
|
303
|
+
}
|
304
|
+
|
305
|
+
case EvtHeader:
|
306
|
+
headerPadded := make([]string, opts.HeaderLines)
|
307
|
+
copy(headerPadded, value.([]string))
|
308
|
+
terminal.UpdateHeader(headerPadded)
|
309
|
+
|
310
|
+
case EvtSearchFin:
|
311
|
+
switch val := value.(type) {
|
312
|
+
case *Merger:
|
313
|
+
if deferred {
|
314
|
+
count := val.Length()
|
315
|
+
if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
|
316
|
+
deferred = false
|
317
|
+
terminal.startChan <- true
|
318
|
+
} else if val.final {
|
319
|
+
if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
|
320
|
+
if opts.PrintQuery {
|
321
|
+
opts.Printer(opts.Query)
|
322
|
+
}
|
323
|
+
if len(opts.Expect) > 0 {
|
324
|
+
opts.Printer("")
|
325
|
+
}
|
326
|
+
for i := 0; i < count; i++ {
|
327
|
+
opts.Printer(val.Get(i).item.AsString(opts.Ansi))
|
328
|
+
}
|
329
|
+
if count > 0 {
|
330
|
+
os.Exit(exitOk)
|
331
|
+
}
|
332
|
+
os.Exit(exitNoMatch)
|
333
|
+
}
|
334
|
+
deferred = false
|
335
|
+
terminal.startChan <- true
|
336
|
+
}
|
337
|
+
}
|
338
|
+
terminal.UpdateList(val, clearSelection())
|
339
|
+
}
|
340
|
+
}
|
341
|
+
}
|
342
|
+
events.Clear()
|
343
|
+
})
|
344
|
+
if delay && reading {
|
345
|
+
dur := util.DurWithin(
|
346
|
+
time.Duration(ticks)*coordinatorDelayStep,
|
347
|
+
0, coordinatorDelayMax)
|
348
|
+
time.Sleep(dur)
|
349
|
+
}
|
350
|
+
}
|
351
|
+
}
|
@@ -0,0 +1,96 @@
|
|
1
|
+
package fzf
|
2
|
+
|
3
|
+
import (
|
4
|
+
"errors"
|
5
|
+
"io/ioutil"
|
6
|
+
"os"
|
7
|
+
"strings"
|
8
|
+
)
|
9
|
+
|
10
|
+
// History struct represents input history
|
11
|
+
type History struct {
|
12
|
+
path string
|
13
|
+
lines []string
|
14
|
+
modified map[int]string
|
15
|
+
maxSize int
|
16
|
+
cursor int
|
17
|
+
}
|
18
|
+
|
19
|
+
// NewHistory returns the pointer to a new History struct
|
20
|
+
func NewHistory(path string, maxSize int) (*History, error) {
|
21
|
+
fmtError := func(e error) error {
|
22
|
+
if os.IsPermission(e) {
|
23
|
+
return errors.New("permission denied: " + path)
|
24
|
+
}
|
25
|
+
return errors.New("invalid history file: " + e.Error())
|
26
|
+
}
|
27
|
+
|
28
|
+
// Read history file
|
29
|
+
data, err := ioutil.ReadFile(path)
|
30
|
+
if err != nil {
|
31
|
+
// If it doesn't exist, check if we can create a file with the name
|
32
|
+
if os.IsNotExist(err) {
|
33
|
+
data = []byte{}
|
34
|
+
if err := ioutil.WriteFile(path, data, 0600); err != nil {
|
35
|
+
return nil, fmtError(err)
|
36
|
+
}
|
37
|
+
} else {
|
38
|
+
return nil, fmtError(err)
|
39
|
+
}
|
40
|
+
}
|
41
|
+
// Split lines and limit the maximum number of lines
|
42
|
+
lines := strings.Split(strings.Trim(string(data), "\n"), "\n")
|
43
|
+
if len(lines[len(lines)-1]) > 0 {
|
44
|
+
lines = append(lines, "")
|
45
|
+
}
|
46
|
+
return &History{
|
47
|
+
path: path,
|
48
|
+
maxSize: maxSize,
|
49
|
+
lines: lines,
|
50
|
+
modified: make(map[int]string),
|
51
|
+
cursor: len(lines) - 1}, nil
|
52
|
+
}
|
53
|
+
|
54
|
+
func (h *History) append(line string) error {
|
55
|
+
// We don't append empty lines
|
56
|
+
if len(line) == 0 {
|
57
|
+
return nil
|
58
|
+
}
|
59
|
+
|
60
|
+
lines := append(h.lines[:len(h.lines)-1], line)
|
61
|
+
if len(lines) > h.maxSize {
|
62
|
+
lines = lines[len(lines)-h.maxSize:]
|
63
|
+
}
|
64
|
+
h.lines = append(lines, "")
|
65
|
+
return ioutil.WriteFile(h.path, []byte(strings.Join(h.lines, "\n")), 0600)
|
66
|
+
}
|
67
|
+
|
68
|
+
func (h *History) override(str string) {
|
69
|
+
// You can update the history but they're not written to the file
|
70
|
+
if h.cursor == len(h.lines)-1 {
|
71
|
+
h.lines[h.cursor] = str
|
72
|
+
} else if h.cursor < len(h.lines)-1 {
|
73
|
+
h.modified[h.cursor] = str
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
func (h *History) current() string {
|
78
|
+
if str, prs := h.modified[h.cursor]; prs {
|
79
|
+
return str
|
80
|
+
}
|
81
|
+
return h.lines[h.cursor]
|
82
|
+
}
|
83
|
+
|
84
|
+
func (h *History) previous() string {
|
85
|
+
if h.cursor > 0 {
|
86
|
+
h.cursor--
|
87
|
+
}
|
88
|
+
return h.current()
|
89
|
+
}
|
90
|
+
|
91
|
+
func (h *History) next() string {
|
92
|
+
if h.cursor < len(h.lines)-1 {
|
93
|
+
h.cursor++
|
94
|
+
}
|
95
|
+
return h.current()
|
96
|
+
}
|
@@ -0,0 +1,68 @@
|
|
1
|
+
package fzf
|
2
|
+
|
3
|
+
import (
|
4
|
+
"io/ioutil"
|
5
|
+
"os"
|
6
|
+
"runtime"
|
7
|
+
"testing"
|
8
|
+
)
|
9
|
+
|
10
|
+
func TestHistory(t *testing.T) {
|
11
|
+
maxHistory := 50
|
12
|
+
|
13
|
+
// Invalid arguments
|
14
|
+
var paths []string
|
15
|
+
if runtime.GOOS == "windows" {
|
16
|
+
// GOPATH should exist, so we shouldn't be able to override it
|
17
|
+
paths = []string{os.Getenv("GOPATH")}
|
18
|
+
} else {
|
19
|
+
paths = []string{"/etc", "/proc"}
|
20
|
+
}
|
21
|
+
|
22
|
+
for _, path := range paths {
|
23
|
+
if _, e := NewHistory(path, maxHistory); e == nil {
|
24
|
+
t.Error("Error expected for: " + path)
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
f, _ := ioutil.TempFile("", "fzf-history")
|
29
|
+
f.Close()
|
30
|
+
|
31
|
+
{ // Append lines
|
32
|
+
h, _ := NewHistory(f.Name(), maxHistory)
|
33
|
+
for i := 0; i < maxHistory+10; i++ {
|
34
|
+
h.append("foobar")
|
35
|
+
}
|
36
|
+
}
|
37
|
+
{ // Read lines
|
38
|
+
h, _ := NewHistory(f.Name(), maxHistory)
|
39
|
+
if len(h.lines) != maxHistory+1 {
|
40
|
+
t.Errorf("Expected: %d, actual: %d\n", maxHistory+1, len(h.lines))
|
41
|
+
}
|
42
|
+
for i := 0; i < maxHistory; i++ {
|
43
|
+
if h.lines[i] != "foobar" {
|
44
|
+
t.Error("Expected: foobar, actual: " + h.lines[i])
|
45
|
+
}
|
46
|
+
}
|
47
|
+
}
|
48
|
+
{ // Append lines
|
49
|
+
h, _ := NewHistory(f.Name(), maxHistory)
|
50
|
+
h.append("barfoo")
|
51
|
+
h.append("")
|
52
|
+
h.append("foobarbaz")
|
53
|
+
}
|
54
|
+
{ // Read lines again
|
55
|
+
h, _ := NewHistory(f.Name(), maxHistory)
|
56
|
+
if len(h.lines) != maxHistory+1 {
|
57
|
+
t.Errorf("Expected: %d, actual: %d\n", maxHistory+1, len(h.lines))
|
58
|
+
}
|
59
|
+
compare := func(idx int, exp string) {
|
60
|
+
if h.lines[idx] != exp {
|
61
|
+
t.Errorf("Expected: %s, actual: %s\n", exp, h.lines[idx])
|
62
|
+
}
|
63
|
+
}
|
64
|
+
compare(maxHistory-3, "foobar")
|
65
|
+
compare(maxHistory-2, "barfoo")
|
66
|
+
compare(maxHistory-1, "foobarbaz")
|
67
|
+
}
|
68
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
package fzf
|
2
|
+
|
3
|
+
import (
|
4
|
+
"github.com/junegunn/fzf/src/util"
|
5
|
+
)
|
6
|
+
|
7
|
+
// Item represents each input line. 56 bytes.
|
8
|
+
type Item struct {
|
9
|
+
text util.Chars // 32 = 24 + 1 + 1 + 2 + 4
|
10
|
+
transformed *[]Token // 8
|
11
|
+
origText *[]byte // 8
|
12
|
+
colors *[]ansiOffset // 8
|
13
|
+
}
|
14
|
+
|
15
|
+
// Index returns ordinal index of the Item
|
16
|
+
func (item *Item) Index() int32 {
|
17
|
+
return item.text.Index
|
18
|
+
}
|
19
|
+
|
20
|
+
var minItem = Item{text: util.Chars{Index: -1}}
|
21
|
+
|
22
|
+
func (item *Item) TrimLength() uint16 {
|
23
|
+
return item.text.TrimLength()
|
24
|
+
}
|
25
|
+
|
26
|
+
// Colors returns ansiOffsets of the Item
|
27
|
+
func (item *Item) Colors() []ansiOffset {
|
28
|
+
if item.colors == nil {
|
29
|
+
return []ansiOffset{}
|
30
|
+
}
|
31
|
+
return *item.colors
|
32
|
+
}
|
33
|
+
|
34
|
+
// AsString returns the original string
|
35
|
+
func (item *Item) AsString(stripAnsi bool) string {
|
36
|
+
if item.origText != nil {
|
37
|
+
if stripAnsi {
|
38
|
+
trimmed, _, _ := extractColor(string(*item.origText), nil, nil)
|
39
|
+
return trimmed
|
40
|
+
}
|
41
|
+
return string(*item.origText)
|
42
|
+
}
|
43
|
+
return item.text.ToString()
|
44
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
package fzf
|
2
|
+
|
3
|
+
import (
|
4
|
+
"testing"
|
5
|
+
|
6
|
+
"github.com/junegunn/fzf/src/util"
|
7
|
+
)
|
8
|
+
|
9
|
+
func TestStringPtr(t *testing.T) {
|
10
|
+
orig := []byte("\x1b[34mfoo")
|
11
|
+
text := []byte("\x1b[34mbar")
|
12
|
+
item := Item{origText: &orig, text: util.ToChars(text)}
|
13
|
+
if item.AsString(true) != "foo" || item.AsString(false) != string(orig) {
|
14
|
+
t.Fail()
|
15
|
+
}
|
16
|
+
if item.AsString(true) != "foo" {
|
17
|
+
t.Fail()
|
18
|
+
}
|
19
|
+
item.origText = nil
|
20
|
+
if item.AsString(true) != string(text) || item.AsString(false) != string(text) {
|
21
|
+
t.Fail()
|
22
|
+
}
|
23
|
+
}
|