Implement flag for preserving whitespace around field (#1242)

This commit is contained in:
ZDNoFYVe 2018-03-30 02:47:46 +00:00 committed by Junegunn Choi
parent 9ff33814ea
commit 43345fb642
3 changed files with 75 additions and 17 deletions

View File

@ -285,6 +285,9 @@ was made) individually quoted.
e.g. \fBfzf --multi --preview='head -10 {+}'\fR e.g. \fBfzf --multi --preview='head -10 {+}'\fR
\fBgit log --oneline | fzf --multi --preview 'git show {+1}'\fR \fBgit log --oneline | fzf --multi --preview 'git show {+1}'\fR
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.
Also, \fB{q}\fR is replaced to the current query string. Also, \fB{q}\fR is replaced to the current query string.
Note that you can escape a placeholder pattern by prepending a backslash. Note that you can escape a placeholder pattern by prepending a backslash.

View File

@ -24,7 +24,7 @@ import (
var placeholder *regexp.Regexp var placeholder *regexp.Regexp
func init() { func init() {
placeholder = regexp.MustCompile("\\\\?(?:{\\+?[0-9,-.]*}|{q})") placeholder = regexp.MustCompile("\\\\?(?:{[+s]*[0-9,-.]*}|{q})")
} }
type jumpMode int type jumpMode int
@ -221,6 +221,11 @@ const (
actTop actTop
) )
type placeholderFlags struct {
plus bool
preserveSpace bool
}
func toActions(types ...actionType) []action { func toActions(types ...actionType) []action {
actions := make([]action, len(types)) actions := make([]action, len(types))
for idx, t := range types { for idx, t := range types {
@ -1130,12 +1135,37 @@ func quoteEntry(entry string) string {
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'" return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
} }
func parsePlaceholder(match string) (bool, string, placeholderFlags) {
flags := placeholderFlags{}
if match[0] == '\\' {
// Escaped placeholder pattern
return true, match[1:], flags
}
skipChars := 1
for _, char := range match[1:] {
switch char {
case '+':
flags.plus = true
skipChars++
case 's':
flags.preserveSpace = true
skipChars++
default:
break
}
}
matchWithoutFlags := "{" + match[skipChars:]
return false, matchWithoutFlags, flags
}
func hasPlusFlag(template string) bool { func hasPlusFlag(template string) bool {
for _, match := range placeholder.FindAllString(template, -1) { for _, match := range placeholder.FindAllString(template, -1) {
if match[0] == '\\' { _, _, flags := parsePlaceholder(match)
continue if flags.plus {
}
if match[1] == '+' {
return true return true
} }
} }
@ -1152,9 +1182,10 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
selected = []*Item{} selected = []*Item{}
} }
return placeholder.ReplaceAllStringFunc(template, func(match string) string { return placeholder.ReplaceAllStringFunc(template, func(match string) string {
// Escaped pattern escaped, match, flags := parsePlaceholder(match)
if match[0] == '\\' {
return match[1:] if escaped {
return match
} }
// Current query // Current query
@ -1162,13 +1193,8 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
return quoteEntry(query) return quoteEntry(query)
} }
plusFlag := forcePlus
if match[1] == '+' {
match = "{" + match[2:]
plusFlag = true
}
items := current items := current
if plusFlag { if flags.plus || forcePlus {
items = selected items = selected
} }
@ -1204,7 +1230,9 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
str = str[:delims[len(delims)-1][0]] str = str[:delims[len(delims)-1][0]]
} }
} }
if !flags.preserveSpace {
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
}
replacements[idx] = quoteEntry(str) replacements[idx] = quoteEntry(str)
} }
return strings.Join(replacements, " ") return strings.Join(replacements, " ")

View File

@ -21,6 +21,9 @@ func TestReplacePlaceholder(t *testing.T) {
newItem("foo'bar \x1b[31mbaz\x1b[m"), newItem("foo'bar \x1b[31mbaz\x1b[m"),
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")} newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
delim := "'"
var regex *regexp.Regexp
var result string var result string
check := func(expected string) { check := func(expected string) {
if result != expected { if result != expected {
@ -72,6 +75,31 @@ func TestReplacePlaceholder(t *testing.T) {
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, true, "query", items2) result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, true, "query", items2)
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''") check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
// Whitespace preserving flag with "'" delimiter
result = replacePlaceholder("echo {s1}", true, Delimiter{str: &delim}, false, "query", items1)
check("echo ' foo'")
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, false, "query", items1)
check("echo 'bar baz'")
result = replacePlaceholder("echo {s}", true, Delimiter{str: &delim}, false, "query", items1)
check("echo ' foo'\\''bar baz'")
result = replacePlaceholder("echo {s..}", true, Delimiter{str: &delim}, false, "query", items1)
check("echo ' foo'\\''bar baz'")
// Whitespace preserving flag with regex delimiter
regex = regexp.MustCompile("\\w+")
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, false, "query", items1)
check("echo ' '")
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, false, "query", items1)
check("echo ''\\'''")
result = replacePlaceholder("echo {s3}", true, Delimiter{regex: regex}, false, "query", items1)
check("echo ' '")
// No match // No match
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, nil}) result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, nil})
check("echo /") check("echo /")
@ -81,12 +109,11 @@ func TestReplacePlaceholder(t *testing.T) {
check("echo /' foo'\\''bar baz'") check("echo /' foo'\\''bar baz'")
// String delimiter // String delimiter
delim := "'"
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, false, "query", items1) result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, false, "query", items1)
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'") check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
// Regex delimiter // Regex delimiter
regex := regexp.MustCompile("[oa]+") regex = regexp.MustCompile("[oa]+")
// foo'bar baz // foo'bar baz
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, false, "query", items1) result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, false, "query", items1)
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'") check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")