Allow bare put action in transform output

transform/bg-transform now permit bare `put`, inserting the key that
triggered the action (`a:transform:echo put` puts `a`).
This commit is contained in:
Junegunn Choi
2026-06-02 20:21:04 +09:00
parent 7d647c70c2
commit ae78a5c56d
6 changed files with 33 additions and 9 deletions
+3 -5
View File
@@ -10,7 +10,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode"
"github.com/junegunn/fzf/src/algo" "github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/tui" "github.com/junegunn/fzf/src/tui"
@@ -1734,10 +1733,10 @@ Loop:
return masked return masked
} }
func parseSingleActionList(str string) ([]*action, error) { func parseSingleActionList(str string, putAllowed bool) ([]*action, error) {
// We prepend a colon to satisfy argActionRegexp and remove it later // We prepend a colon to satisfy argActionRegexp and remove it later
masked := maskActionContents(":" + str)[1:] masked := maskActionContents(":" + str)[1:]
return parseActionList(masked, str, []*action{}, false) return parseActionList(masked, str, []*action{}, putAllowed)
} }
func parseActionList(masked string, original string, prevActions []*action, putAllowed bool) ([]*action, error) { func parseActionList(masked string, original string, prevActions []*action, putAllowed bool) ([]*action, error) {
@@ -2043,8 +2042,7 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) error {
} }
key = firstKey(keys) key = firstKey(keys)
} }
putAllowed := key.Type == tui.Rune && unicode.IsGraphic(key.Char) keymap[key], err = parseActionList(pair[1], origPairStr[len(pair[0])+1:], keymap[key], key.Printable())
keymap[key], err = parseActionList(pair[1], origPairStr[len(pair[0])+1:], keymap[key], putAllowed)
if err != nil { if err != nil {
return err return err
} }
+2 -2
View File
@@ -572,7 +572,7 @@ func TestValidateSign(t *testing.T) {
} }
func TestParseSingleActionList(t *testing.T) { func TestParseSingleActionList(t *testing.T) {
actions, _ := parseSingleActionList("Execute@foo+bar,baz@+up+up+reload:down+down") actions, _ := parseSingleActionList("Execute@foo+bar,baz@+up+up+reload:down+down", false)
if len(actions) != 4 { if len(actions) != 4 {
t.Errorf("Invalid number of actions parsed:%d", len(actions)) t.Errorf("Invalid number of actions parsed:%d", len(actions))
} }
@@ -588,7 +588,7 @@ func TestParseSingleActionList(t *testing.T) {
} }
func TestParseSingleActionListError(t *testing.T) { func TestParseSingleActionListError(t *testing.T) {
_, err := parseSingleActionList("change-query(foobar)baz") _, err := parseSingleActionList("change-query(foobar)baz", false)
if err == nil { if err == nil {
t.Errorf("Failed to detect error") t.Errorf("Failed to detect error")
} }
+1 -1
View File
@@ -240,7 +240,7 @@ Loop:
} }
body = body[:contentLength] body = body[:contentLength]
actions, err := parseSingleActionList(strings.Trim(string(body), "\r\n")) actions, err := parseSingleActionList(strings.Trim(string(body), "\r\n"), false)
if err != nil { if err != nil {
return bad(err.Error()) return bad(err.Error())
} }
+2 -1
View File
@@ -6973,7 +6973,8 @@ func (t *Terminal) Loop() error {
}) })
case actTransform, actBgTransform: case actTransform, actBgTransform:
capture(false, func(body string) { capture(false, func(body string) {
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil { // Allow 'put' if the triggering key is a printable character
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n"), event.Printable()); err == nil {
// NOTE: We're not properly passing the return value here // NOTE: We're not properly passing the return value here
doActions(actions) doActions(actions)
} }
+7
View File
@@ -4,6 +4,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
"github.com/rivo/uniseg" "github.com/rivo/uniseg"
@@ -252,6 +253,12 @@ func (e Event) Comparable() Event {
return Event{e.Type, e.Char, nil} return Event{e.Type, e.Char, nil}
} }
// Printable returns true if the event is a printable character that can be
// inserted into the query (e.g. via the 'put' action).
func (e Event) Printable() bool {
return e.Type == Rune && unicode.IsGraphic(e.Char)
}
func (e Event) KeyName() string { func (e Event) KeyName() string {
if me := e.MouseEvent; me != nil { if me := e.MouseEvent; me != nil {
return me.Name() return me.Name()
+18
View File
@@ -971,6 +971,24 @@ class TestCore < TestInteractive
tmux.until { |lines| assert_includes lines[1], ' aabravo/aabravo' } tmux.until { |lines| assert_includes lines[1], ' aabravo/aabravo' }
end end
def test_transform_put
tmux.send_keys %(seq 1000 | #{FZF} --bind 'a:transform:echo put'), :Enter
tmux.until { |lines| assert_equal 1000, lines.match_count }
tmux.send_keys :a
tmux.until { |lines| assert_equal '> a', lines.last }
tmux.send_keys :b
tmux.until { |lines| assert_equal '> ab', lines.last }
end
# The async callback runs in a later iteration, but 'put' must still insert
# the key that triggered the bg-transform (snapshot of the scheduling event).
def test_bg_transform_put
tmux.send_keys %(seq 1000 | #{FZF} --bind 'a:bg-transform:sleep 0.5; echo put'), :Enter
tmux.until { |lines| assert_equal 1000, lines.match_count }
tmux.send_keys 'ab'
tmux.until { |lines| assert_equal '> ba', lines.last }
end
def test_accept_non_empty def test_accept_non_empty
tmux.send_keys %(seq 1000 | #{fzf('--print-query --bind enter:accept-non-empty')}), :Enter tmux.send_keys %(seq 1000 | #{fzf('--print-query --bind enter:accept-non-empty')}), :Enter
tmux.until { |lines| assert_equal 1000, lines.match_count } tmux.until { |lines| assert_equal 1000, lines.match_count }