From 36d2bb332b87900e8178f2ec54d2cfda07f075db Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Wed, 28 Dec 2022 00:05:31 +0900 Subject: [PATCH] Add transform-query(...) action Test case authored by @SpicyLemon Close #1930 Close #2465 Close #2559 Close #2509 (e.g. fzf --bind 'space:transform-query:printf %s%s {q} {}') --- CHANGELOG.md | 18 +++++++++++++++++- man/man1/fzf.1 | 1 + src/options.go | 4 +++- src/terminal.go | 26 +++++++++++++++++++++----- test/test_go.rb | 9 +++++++++ 5 files changed, 51 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ae8edaa..3608f4e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,23 @@ CHANGELOG # Both actions respect --layout option seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected --layout reverse ``` -- Added `change-query(...)` action +- Added `change-query(...)` action that simply changes the query string to the + given static string. This can be useful when used with `--listen`. + ```sh + curl localhost:6266 -d "change-query:$(date)" + ``` +- Added `transform-query(...)` action for transforming the query string using + an external command + ```sh + # Press space to convert the query to uppercase letters + fzf --bind 'space:transform-query(tr [:lower:] [:upper:] <<< {q})' + + # Bind it to 'change' event for automatic conversion + fzf --bind 'change:transform-query(tr [:lower:] [:upper:] <<< {q})' + + # Can only type numbers + fzf --bind 'change:transform-query(sed 's/[^0-9]//g' <<< {q})' + ``` - `put` action can optionally take an argument string ```sh # a will put 'alpha' on the prompt, ctrl-b will put 'bravo' diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index b6488bdb..f5e01b6e 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -1023,6 +1023,7 @@ A key or an event can be bound to one or more of the following actions. \fBtoggle-search\fR (toggle search functionality) \fBtoggle-sort\fR \fBtoggle+up\fR \fIbtab (shift-tab)\fR + \fBtransform-query(...)\fR (transform query string using an external command) \fBunbind(...)\fR (unbind bindings) \fBunix-line-discard\fR \fIctrl-u\fR \fBunix-word-rubout\fR \fIctrl-w\fR diff --git a/src/options.go b/src/options.go index f25b6e73..1288fbe4 100644 --- a/src/options.go +++ b/src/options.go @@ -890,7 +890,7 @@ const ( func init() { executeRegexp = regexp.MustCompile( - `(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-query|change-prompt|change-preview-window|change-preview|(?:re|un)bind|pos|put)`) + `(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-query|change-prompt|change-preview-window|change-preview|(?:re|un)bind|pos|put|transform-query)`) splitRegexp = regexp.MustCompile("[,:]+") actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+") } @@ -1207,6 +1207,8 @@ func isExecuteAction(str string) actionType { return actExecuteMulti case "put": return actPut + case "transform-query": + return actTransformQuery } return actIgnore } diff --git a/src/terminal.go b/src/terminal.go index 68a099cf..2d18f791 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -308,6 +308,7 @@ const ( actToggleSort actTogglePreview actTogglePreviewWrap + actTransformQuery actPreview actChangePreview actChangePreviewWindow @@ -2014,10 +2015,11 @@ func (t *Terminal) redraw(clear bool) { t.printAll() } -func (t *Terminal) executeCommand(template string, forcePlus bool, background bool) { +func (t *Terminal) executeCommand(template string, forcePlus bool, background bool, captureFirstLine bool) string { + line := "" valid, list := t.buildPlusList(template, forcePlus) if !valid { - return + return line } command := t.replacePlaceholder(template, forcePlus, string(t.input), list) cmd := util.ExecCommand(command, false) @@ -2033,11 +2035,21 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo t.refresh() } else { t.tui.Pause(false) - cmd.Run() + if captureFirstLine { + out, _ := cmd.StdoutPipe() + reader := bufio.NewReader(out) + cmd.Start() + line, _ = reader.ReadString('\n') + line = strings.TrimRight(line, "\r\n") + cmd.Wait() + } else { + cmd.Run() + } t.tui.Resume(false, false) } t.executing.Set(false) cleanTemporaryFiles() + return line } func (t *Terminal) hasPreviewer() bool { @@ -2610,9 +2622,9 @@ func (t *Terminal) Loop() { switch a.t { case actIgnore: case actExecute, actExecuteSilent: - t.executeCommand(a.a, false, a.t == actExecuteSilent) + t.executeCommand(a.a, false, a.t == actExecuteSilent, false) case actExecuteMulti: - t.executeCommand(a.a, true, false) + t.executeCommand(a.a, true, false, false) case actInvalid: t.mutex.Unlock() return false @@ -2635,6 +2647,10 @@ func (t *Terminal) Loop() { t.previewed.version = 0 req(reqPreviewRefresh) } + case actTransformQuery: + query := t.executeCommand(a.a, false, true, true) + t.input = []rune(query) + t.cx = len(t.input) case actToggleSort: t.sort = !t.sort changed = true diff --git a/test/test_go.rb b/test/test_go.rb index 4b2856eb..a4d2e2a8 100755 --- a/test/test_go.rb +++ b/test/test_go.rb @@ -1797,6 +1797,15 @@ class TestGoFZF < TestBase tmux.until { |lines| assert_equal '> foobarbaz', lines.last } end + def test_transform_query + tmux.send_keys %{#{FZF} --bind 'ctrl-r:transform-query(rev <<< {q}),ctrl-u:transform-query: tr "[:lower:]" "[:upper:]" <<< {q}' --query bar}, :Enter + tmux.until { |lines| assert_equal '> bar', lines[-1] } + tmux.send_keys 'C-r' + tmux.until { |lines| assert_equal '> rab', lines[-1] } + tmux.send_keys 'C-u' + tmux.until { |lines| assert_equal '> RAB', lines[-1] } + end + def test_clear_selection tmux.send_keys %(seq 100 | #{FZF} --multi --bind space:clear-selection), :Enter tmux.until { |lines| assert_equal 100, lines.match_count }