From 62c7f59b94ed5f3d8770a735c107233c6518acfb Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Sat, 31 Dec 2022 09:27:11 +0900 Subject: [PATCH] Add transform-prompt(...) action --- CHANGELOG.md | 7 +++++++ man/man1/fzf.1 | 1 + src/options.go | 4 +++- src/terminal.go | 21 ++++++++++++++------- test/test_go.rb | 9 +++++++++ 5 files changed, 34 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c556b0..e612645 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,13 @@ CHANGELOG ```sh curl localhost:6266 -d "change-query:$(date)" ``` + - Added `transform-prompt(...)` action for transforming the prompt string + using an external command + ```sh + # Press space to change the prompt string using an external command + # (only the first line of the output is taken) + fzf --bind 'space:reload(ls),load:transform-prompt(printf "%s> " "$(date)")' + ``` - Added `transform-query(...)` action for transforming the query string using an external command ```sh diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index 2efde05..e981a13 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -1034,6 +1034,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-prompt(...)\fR (transform prompt string using an external command) \fBtransform-query(...)\fR (transform query string using an external command) \fBunbind(...)\fR (unbind bindings) \fBunix-line-discard\fR \fIctrl-u\fR diff --git a/src/options.go b/src/options.go index 79a4a0a..0ee7db3 100644 --- a/src/options.go +++ b/src/options.go @@ -892,7 +892,7 @@ const ( func init() { executeRegexp = regexp.MustCompile( - `(?si)[:+](execute(?:-multi|-silent)?|reload(?:-sync)?|preview|change-query|change-prompt|change-preview-window|change-preview|(?:re|un)bind|pos|put|transform-query)`) + `(?si)[:+](execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt)|change-preview-window|change-preview|(?:re|un)bind|pos|put)`) splitRegexp = regexp.MustCompile("[,:]+") actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+") } @@ -1211,6 +1211,8 @@ func isExecuteAction(str string) actionType { return actExecuteMulti case "put": return actPut + case "transform-prompt": + return actTransformPrompt case "transform-query": return actTransformQuery } diff --git a/src/terminal.go b/src/terminal.go index 72b2e00..a89b6ac 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -311,6 +311,7 @@ const ( actToggleSort actTogglePreview actTogglePreviewWrap + actTransformPrompt actTransformQuery actPreview actChangePreview @@ -2034,8 +2035,10 @@ func (t *Terminal) redraw(clear bool) { func (t *Terminal) executeCommand(template string, forcePlus bool, background bool, captureFirstLine bool) string { line := "" - valid, list := t.buildPlusList(template, forcePlus) - if !valid { + valid, list := t.buildPlusList(template, forcePlus, false) + // captureFirstLine is used for transform-{prompt,query} and we don't want to + // return an empty string in those cases + if !valid && !captureFirstLine { return line } command := t.replacePlaceholder(template, forcePlus, string(t.input), list) @@ -2093,10 +2096,10 @@ func (t *Terminal) currentItem() *Item { return nil } -func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) { +func (t *Terminal) buildPlusList(template string, forcePlus bool, forceEvaluation bool) (bool, []*Item) { current := t.currentItem() slot, plus, query := hasPreviewFlags(template) - if !(!slot || query || (forcePlus || plus) && len(t.selected) > 0) { + if !forceEvaluation && !(!slot || query || (forcePlus || plus) && len(t.selected) > 0) { return current != nil, []*Item{current, current} } @@ -2421,7 +2424,7 @@ func (t *Terminal) Loop() { refreshPreview := func(command string) { if len(command) > 0 && t.isPreviewEnabled() { - _, list := t.buildPlusList(command, false) + _, list := t.buildPlusList(command, false, false) t.cancelPreview() t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, t.evaluateScrollOffset(), list}) } @@ -2649,7 +2652,7 @@ func (t *Terminal) Loop() { if t.hasPreviewer() { togglePreview(!t.previewer.enabled) if t.previewer.enabled { - valid, list := t.buildPlusList(t.previewOpts.command, false) + valid, list := t.buildPlusList(t.previewOpts.command, false, false) if valid { t.cancelPreview() t.previewBox.Set(reqPreviewEnqueue, @@ -2664,6 +2667,10 @@ func (t *Terminal) Loop() { t.previewed.version = 0 req(reqPreviewRefresh) } + case actTransformPrompt: + prompt := t.executeCommand(a.a, false, true, true) + t.prompt, t.promptLen = t.parsePrompt(prompt) + req(reqPrompt) case actTransformQuery: query := t.executeCommand(a.a, false, true, true) t.input = []rune(query) @@ -3036,7 +3043,7 @@ func (t *Terminal) Loop() { case actReload, actReloadSync: t.failed = nil - valid, list := t.buildPlusList(a.a, false) + valid, list := t.buildPlusList(a.a, false, false) if !valid { // We run the command even when there's no match // 1. If the template doesn't have any slots diff --git a/test/test_go.rb b/test/test_go.rb index c7d62c7..45c661a 100755 --- a/test/test_go.rb +++ b/test/test_go.rb @@ -1806,6 +1806,15 @@ class TestGoFZF < TestBase tmux.until { |lines| assert_equal '> RAB', lines[-1] } end + def test_transform_prompt + 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 }