From 31fd207ba232cf419f55cbfede8a90490bd25c5d Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Sun, 30 Mar 2025 19:28:21 +0900 Subject: [PATCH] Add 'r' flag (raw) for unquoted output By default, placeholder expressions are automatically quoted to ensure they are safely passed as arguments to external programs. The r flag ({r}, {r1}, etc.) disables this behavior, outputting the evaluated value without quotes. For example, echo 'foo bar' | fzf --preview 'echo {} {r}' The preview command becomes: echo 'foo bar' foo bar Since `{r}` expands to unquoted "foo bar", 'foo' and 'bar' are passed as separate arguments. **Use with caution** Unquoted output can lead to broken commands. echo "let's go" | fzf --preview 'echo {r}' Close #4330 --- man/man1/fzf.1 | 6 ++++++ src/terminal.go | 11 +++++++---- src/terminal_test.go | 8 ++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index 4b2c5896..366cb3b2 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -768,6 +768,12 @@ e.g. \fBfzf \-\-multi \-\-preview='head \-10 {+}' git log \-\-oneline | fzf \-\-multi \-\-preview 'git show {+1}'\fR +Each expression expands to a quoted string, so that it's safe to pass it as an +argument to an external command. So you should not manually add quotes around +the curly braces. But if you don't want this behavior, you can put +\fBr\fR flag (raw) in the expression (e.g. \fB{r}\fR, \fB{r1}\fR, etc). +Use it with caution as unquoted output can lead to broken commands. + When using a field index expression, leading and trailing whitespace is stripped from the replacement string. To preserve the whitespace, use the \fBs\fR flag. diff --git a/src/terminal.go b/src/terminal.go index 8b4651c1..f3a7fe4d 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -38,7 +38,7 @@ As such it is not useful for validation, but rather to generate test cases for example. \\?(?: # escaped type - {\+?s?f?RANGE(?:,RANGE)*} # token type + {\+?s?f?r?RANGE(?:,RANGE)*} # token type {q[:s?RANGE]} # query type |{\+?n?f?} # item type (notice no mandatory element inside brackets) ) @@ -65,7 +65,7 @@ const maxFocusEvents = 10000 const blockDuration = 1 * time.Second func init() { - placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q(?::s?[0-9,-.]+)?}|{fzf:(?:query|action|prompt)}|{\+?f?nf?})`) + placeholder = regexp.MustCompile(`\\?(?:{[+sfr]*[0-9,-.]*}|{q(?::s?[0-9,-.]+)?}|{fzf:(?:query|action|prompt)}|{\+?f?nf?})`) whiteSuffix = regexp.MustCompile(`\s*$`) offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`) offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`) @@ -628,6 +628,7 @@ type placeholderFlags struct { number bool forceUpdate bool file bool + raw bool } type searchRequest struct { @@ -3782,6 +3783,8 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) { flags.number = true case 'f': flags.file = true + case 'r': + flags.raw = true case 'q': flags.forceUpdate = true trimmed += string(char) @@ -3933,7 +3936,7 @@ func replacePlaceholder(params replacePlaceholderParams) (string, []string) { return "''" } return strconv.Itoa(int(n)) - case flags.file: + case flags.file || flags.raw: return item.AsString(params.stripAnsi) default: return params.executor.QuoteEntry(item.AsString(params.stripAnsi)) @@ -3975,7 +3978,7 @@ func replacePlaceholder(params replacePlaceholderParams) (string, []string) { if !flags.preserveSpace { str = strings.TrimSpace(str) } - if !flags.file { + if !flags.file && !flags.raw { str = params.executor.QuoteEntry(str) } return str diff --git a/src/terminal_test.go b/src/terminal_test.go index 1d50767b..380e40d1 100644 --- a/src/terminal_test.go +++ b/src/terminal_test.go @@ -75,6 +75,14 @@ func TestReplacePlaceholder(t *testing.T) { result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items1) checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}") + // {r}, strip ansi + result = replacePlaceholderTest("echo {r}", true, Delimiter{}, printsep, false, "query", items1) + checkFormat("echo foo'bar baz") + + // {r..}, strip ansi + result = replacePlaceholderTest("echo {r..}", true, Delimiter{}, printsep, false, "query", items1) + checkFormat("echo foo'bar baz") + // {}, with multiple items result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items2) checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")