doing 2.0.18 → 2.0.22
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 +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
|
+
}
|