Skip to content

Adding initial support for running interactive commands #1736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,19 @@ For more advanced examples, see [Key bindings for git with fzf][fzf-git]

[fzf-git]: https://junegunn.kr/2016/07/fzf-git/

### Using fzf in interactive mode

You can also use fzf in "interactive command" mode. This mode changes the "default"
way how fzf retrieves input for filtering. Instead of reading from stdin, fzf
executes command specified by `--cmd` option. Every change to the filter query
results in the command being reevaulated and the result is fed back to fzf.
The query is passed to the command through `{}`.

```bash
# Run grep continuosly in current directory
fzf -c 'ag -i {} *'
```

Tips
----

Expand Down
4 changes: 4 additions & 0 deletions src/chunklist.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ func NewChunkList(trans ItemBuilder) *ChunkList {
trans: trans}
}

func (cs *ChunkList) clear() {
cs.chunks = nil
}

func (c *Chunk) push(trans ItemBuilder, data []byte) bool {
if trans(&c.items[c.count], data) {
c.count++
Expand Down
21 changes: 20 additions & 1 deletion src/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ package fzf
import (
"fmt"
"os"
"strings"
"time"

"github.com/junegunn/fzf/src/util"
Expand Down Expand Up @@ -135,11 +136,18 @@ func Run(opts *Options, revision string) {

// Reader
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
interactiveCommand := opts.Command != ""

if !streamingFilter {
reader := NewReader(func(data []byte) bool {
return chunkList.Push(data)
}, eventBox, opts.ReadZero)
go reader.ReadSource()
if interactiveCommand {
var command = strings.ReplaceAll(opts.Command, "{}", opts.Query)
go reader.ReadSourceCustom(command)
} else {
go reader.ReadSource()
}
}

// Matcher
Expand Down Expand Up @@ -248,6 +256,17 @@ func Run(opts *Options, revision string) {
case bool:
sort = val
}

if interactiveCommand {
chunkList.clear()

reader := NewReader(func(data []byte) bool {
return chunkList.Push(data)
}, eventBox, opts.ReadZero)
var command = strings.ReplaceAll(opts.Command, "{}", string(terminal.Input()))
go reader.ReadSourceCustom(command)
}

snapshot, _ := chunkList.Snapshot()
matcher.Reset(snapshot, terminal.Input(), true, !reading, sort)
delay = false
Expand Down
7 changes: 6 additions & 1 deletion src/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ const usage = `usage: fzf [options]
--tac Reverse the order of the input
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
when the scores are tied [length|begin|end|index]
(default: length)
(default: length)
-c, --cmd=CMD Interactive mode command

Interface
-m, --multi Enable multi-select with tab/shift-tab
Expand Down Expand Up @@ -155,6 +156,7 @@ type Options struct {
FuzzyAlgo algo.Algo
Extended bool
Case Case
Command string
Normalize bool
Nth []Range
WithNth []Range
Expand Down Expand Up @@ -208,6 +210,7 @@ func defaultOptions() *Options {
FuzzyAlgo: algo.FuzzyMatchV2,
Extended: true,
Case: CaseSmart,
Command: "",
Normalize: true,
Nth: make([]Range, 0),
WithNth: make([]Range, 0),
Expand Down Expand Up @@ -998,6 +1001,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Fuzzy = true
case "-q", "--query":
opts.Query = nextString(allArgs, &i, "query string required")
case "-c", "--cmd":
opts.Command = nextString(allArgs, &i, "command string required")
case "-f", "--filter":
filter := nextString(allArgs, &i, "query string required")
opts.Filter = &filter
Expand Down
8 changes: 8 additions & 0 deletions src/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ func (r *Reader) ReadSource() {
r.fin(success)
}

// ReadSourceCustom reads data from the default command or from standard input
func (r *Reader) ReadSourceCustom(cmd string) {
r.startEventPoller()
var success bool
success = r.readFromCommand("sh", cmd)
r.fin(success)
}

func (r *Reader) feed(src io.Reader) {
delim := byte('\n')
if r.delimNil {
Expand Down