Skip to content

Commit 78da928

Browse files
committed
Experimental implementation of "reload" action
# Reload input list with different sources seq 10 | fzf --bind 'ctrl-a:reload(seq 100),ctrl-b:reload(seq 1000)' # Reload as you type seq 10 | fzf --bind 'change:reload:seq {q}' --phony # Integration with ripgrep RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case " INITIAL_QUERY="" FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \ fzf --bind "change:reload:$RG_PREFIX {q} || true" \ --ansi --phony --query "$INITIAL_QUERY" Close #751 Close #965 Close #974 Close #1736 Related #1723
1 parent 11962da commit 78da928

File tree

7 files changed

+164
-58
lines changed

7 files changed

+164
-58
lines changed

src/chunklist.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ func (cl *ChunkList) Push(data []byte) bool {
6464
return ret
6565
}
6666

67+
// Clear clears the data
68+
func (cl *ChunkList) Clear() {
69+
cl.mutex.Lock()
70+
cl.chunks = nil
71+
cl.mutex.Unlock()
72+
}
73+
6774
// Snapshot returns immutable snapshot of the ChunkList
6875
func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
6976
cl.mutex.Lock()

src/core.go

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,9 @@ func Run(opts *Options, revision string) {
135135

136136
// Reader
137137
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
138+
var reader *Reader
138139
if !streamingFilter {
139-
reader := NewReader(func(data []byte) bool {
140+
reader = NewReader(func(data []byte) bool {
140141
return chunkList.Push(data)
141142
}, eventBox, opts.ReadZero)
142143
go reader.ReadSource()
@@ -223,6 +224,7 @@ func Run(opts *Options, revision string) {
223224
// Event coordination
224225
reading := true
225226
ticks := 0
227+
var nextCommand *string
226228
eventBox.Watch(EvtReadNew)
227229
for {
228230
delay := true
@@ -241,21 +243,41 @@ func Run(opts *Options, revision string) {
241243
switch evt {
242244

243245
case EvtReadNew, EvtReadFin:
244-
reading = reading && evt == EvtReadNew
246+
clearCache := false
247+
if evt == EvtReadFin && nextCommand != nil {
248+
chunkList.Clear()
249+
clearCache = true
250+
go reader.restart(*nextCommand)
251+
nextCommand = nil
252+
} else {
253+
reading = reading && evt == EvtReadNew
254+
}
245255
snapshot, count := chunkList.Snapshot()
246-
terminal.UpdateCount(count, !reading, value.(bool))
256+
terminal.UpdateCount(count, !reading, value.(*string))
247257
if opts.Sync {
248258
terminal.UpdateList(PassMerger(&snapshot, opts.Tac))
249259
}
250-
matcher.Reset(snapshot, input(), false, !reading, sort)
260+
matcher.Reset(snapshot, input(), false, !reading, sort, clearCache)
251261

252262
case EvtSearchNew:
263+
var command *string
253264
switch val := value.(type) {
254-
case bool:
255-
sort = val
265+
case searchRequest:
266+
sort = val.sort
267+
command = val.command
268+
}
269+
if command != nil {
270+
if reading {
271+
reader.terminate()
272+
nextCommand = command
273+
} else {
274+
reading = true
275+
chunkList.Clear()
276+
go reader.restart(*command)
277+
}
256278
}
257279
snapshot, _ := chunkList.Snapshot()
258-
matcher.Reset(snapshot, input(), true, !reading, sort)
280+
matcher.Reset(snapshot, input(), true, !reading, sort, command != nil)
259281
delay = false
260282

261283
case EvtSearchProgress:

src/matcher.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ import (
1212

1313
// MatchRequest represents a search request
1414
type MatchRequest struct {
15-
chunks []*Chunk
16-
pattern *Pattern
17-
final bool
18-
sort bool
15+
chunks []*Chunk
16+
pattern *Pattern
17+
final bool
18+
sort bool
19+
clearCache bool
1920
}
2021

2122
// Matcher is responsible for performing search
@@ -69,7 +70,7 @@ func (m *Matcher) Loop() {
6970
events.Clear()
7071
})
7172

72-
if request.sort != m.sort {
73+
if request.sort != m.sort || request.clearCache {
7374
m.sort = request.sort
7475
m.mergerCache = make(map[string]*Merger)
7576
clearChunkCache()
@@ -221,7 +222,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
221222
}
222223

223224
// Reset is called to interrupt/signal the ongoing search
224-
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool) {
225+
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, clearCache bool) {
225226
pattern := m.patternBuilder(patternRunes)
226227

227228
var event util.EventType
@@ -230,5 +231,5 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
230231
} else {
231232
event = reqRetry
232233
}
233-
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable})
234+
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, clearCache})
234235
}

src/options.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -631,13 +631,15 @@ func init() {
631631
// Backreferences are not supported.
632632
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
633633
executeRegexp = regexp.MustCompile(
634-
`(?si):(execute(?:-multi|-silent)?):.+|:(execute(?:-multi|-silent)?)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
634+
`(?si):(execute(?:-multi|-silent)?|reload):.+|:(execute(?:-multi|-silent)?|reload)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
635635
}
636636

637637
func parseKeymap(keymap map[int][]action, str string) {
638638
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
639639
prefix := ":execute"
640-
if src[len(prefix)] == '-' {
640+
if strings.HasPrefix(src, ":reload") {
641+
prefix = ":reload"
642+
} else if src[len(prefix)] == '-' {
641643
c := src[len(prefix)+1]
642644
if c == 's' || c == 'S' {
643645
prefix += "-silent"
@@ -790,6 +792,8 @@ func parseKeymap(keymap map[int][]action, str string) {
790792
} else {
791793
var offset int
792794
switch t {
795+
case actReload:
796+
offset = len("reload")
793797
case actExecuteSilent:
794798
offset = len("execute-silent")
795799
case actExecuteMulti:
@@ -825,6 +829,8 @@ func isExecuteAction(str string) actionType {
825829
prefix = matches[0][2]
826830
}
827831
switch prefix {
832+
case "reload":
833+
return actReload
828834
case "execute":
829835
return actExecute
830836
case "execute-silent":

src/reader.go

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"bufio"
55
"io"
66
"os"
7+
"os/exec"
8+
"sync"
79
"sync/atomic"
810
"time"
911

@@ -16,11 +18,16 @@ type Reader struct {
1618
eventBox *util.EventBox
1719
delimNil bool
1820
event int32
21+
finChan chan bool
22+
mutex sync.Mutex
23+
exec *exec.Cmd
24+
command *string
25+
killed bool
1926
}
2027

2128
// NewReader returns new Reader object
2229
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, delimNil bool) *Reader {
23-
return &Reader{pusher, eventBox, delimNil, int32(EvtReady)}
30+
return &Reader{pusher, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, false}
2431
}
2532

2633
func (r *Reader) startEventPoller() {
@@ -29,9 +36,10 @@ func (r *Reader) startEventPoller() {
2936
pollInterval := readerPollIntervalMin
3037
for {
3138
if atomic.CompareAndSwapInt32(ptr, int32(EvtReadNew), int32(EvtReady)) {
32-
r.eventBox.Set(EvtReadNew, true)
39+
r.eventBox.Set(EvtReadNew, (*string)(nil))
3340
pollInterval = readerPollIntervalMin
3441
} else if atomic.LoadInt32(ptr) == int32(EvtReadFin) {
42+
r.finChan <- true
3543
return
3644
} else {
3745
pollInterval += readerPollIntervalStep
@@ -46,20 +54,49 @@ func (r *Reader) startEventPoller() {
4654

4755
func (r *Reader) fin(success bool) {
4856
atomic.StoreInt32(&r.event, int32(EvtReadFin))
49-
r.eventBox.Set(EvtReadFin, success)
57+
<-r.finChan
58+
59+
r.mutex.Lock()
60+
ret := r.command
61+
if success || r.killed {
62+
ret = nil
63+
}
64+
r.mutex.Unlock()
65+
66+
r.eventBox.Set(EvtReadFin, ret)
67+
}
68+
69+
func (r *Reader) terminate() {
70+
r.mutex.Lock()
71+
defer func() { r.mutex.Unlock() }()
72+
73+
r.killed = true
74+
if r.exec != nil && r.exec.Process != nil {
75+
util.KillCommand(r.exec)
76+
} else {
77+
os.Stdin.Close()
78+
}
79+
}
80+
81+
func (r *Reader) restart(command string) {
82+
r.event = int32(EvtReady)
83+
r.startEventPoller()
84+
success := r.readFromCommand(nil, command)
85+
r.fin(success)
5086
}
5187

5288
// ReadSource reads data from the default command or from standard input
5389
func (r *Reader) ReadSource() {
5490
r.startEventPoller()
5591
var success bool
5692
if util.IsTty() {
93+
// The default command for *nix requires bash
94+
shell := "bash"
5795
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
5896
if len(cmd) == 0 {
59-
// The default command for *nix requires bash
60-
success = r.readFromCommand("bash", defaultCommand)
97+
success = r.readFromCommand(&shell, defaultCommand)
6198
} else {
62-
success = r.readFromCommand("sh", cmd)
99+
success = r.readFromCommand(nil, cmd)
63100
}
64101
} else {
65102
success = r.readFromStdin()
@@ -102,16 +139,25 @@ func (r *Reader) readFromStdin() bool {
102139
return true
103140
}
104141

105-
func (r *Reader) readFromCommand(shell string, cmd string) bool {
106-
listCommand := util.ExecCommandWith(shell, cmd, false)
107-
out, err := listCommand.StdoutPipe()
142+
func (r *Reader) readFromCommand(shell *string, command string) bool {
143+
r.mutex.Lock()
144+
r.killed = false
145+
r.command = &command
146+
if shell != nil {
147+
r.exec = util.ExecCommandWith(*shell, command, true)
148+
} else {
149+
r.exec = util.ExecCommand(command, true)
150+
}
151+
out, err := r.exec.StdoutPipe()
108152
if err != nil {
153+
r.mutex.Unlock()
109154
return false
110155
}
111-
err = listCommand.Start()
156+
err = r.exec.Start()
157+
r.mutex.Unlock()
112158
if err != nil {
113159
return false
114160
}
115161
r.feed(out)
116-
return listCommand.Wait() == nil
162+
return r.exec.Wait() == nil
117163
}

src/reader_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ func TestReadFromCommand(t *testing.T) {
1212
eb := util.NewEventBox()
1313
reader := Reader{
1414
pusher: func(s []byte) bool { strs = append(strs, string(s)); return true },
15+
finChan: make(chan bool, 1),
1516
eventBox: eb,
1617
event: int32(EvtReady)}
1718

@@ -23,7 +24,7 @@ func TestReadFromCommand(t *testing.T) {
2324
}
2425

2526
// Normal command
26-
reader.fin(reader.readFromCommand("sh", `echo abc && echo def`))
27+
reader.fin(reader.readFromCommand(nil, `echo abc && echo def`))
2728
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
2829
t.Errorf("%s", strs)
2930
}
@@ -48,7 +49,7 @@ func TestReadFromCommand(t *testing.T) {
4849
reader.startEventPoller()
4950

5051
// Failing command
51-
reader.fin(reader.readFromCommand("sh", `no-such-command`))
52+
reader.fin(reader.readFromCommand(nil, `no-such-command`))
5253
strs = []string{}
5354
if len(strs) > 0 {
5455
t.Errorf("%s", strs)

0 commit comments

Comments
 (0)