summaryrefslogtreecommitdiff
path: root/src/terminal.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/terminal.go')
-rw-r--r--src/terminal.go154
1 files changed, 84 insertions, 70 deletions
diff --git a/src/terminal.go b/src/terminal.go
index c296e443..8a8e1658 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -23,6 +23,26 @@ import (
// import "github.com/pkg/profile"
+/*
+ Placeholder regex is used to extract placeholders from fzf's template
+ strings. Acts as input validation for parsePlaceholder function.
+ Describes the syntax, but it is fairly lenient.
+
+ The following pseudo regex has been reverse engineered from the
+ implementation. It is overly strict, but better describes whats possible.
+ As such it is not useful for validation, but rather to generate test
+ cases for example.
+
+ \\?(?: # escaped type
+ {\+?s?f?RANGE(?:,RANGE)*} # token type
+ |{q} # query type
+ |{\+?n?f?} # item type (notice no mandatory element inside brackets)
+ )
+ RANGE = (?:
+ (?:-?[0-9]+)?\.\.(?:-?[0-9]+)? # ellipsis syntax for token range (x..y)
+ |-?[0-9]+ # shorthand syntax (x..x)
+ )
+*/
var placeholder *regexp.Regexp
var whiteSuffix *regexp.Regexp
var offsetComponentRegex *regexp.Regexp
@@ -1520,22 +1540,6 @@ func keyMatch(key tui.Event, event tui.Event) bool {
key.Type == tui.DoubleClick && event.Type == tui.Mouse && event.MouseEvent.Double
}
-func quoteEntryCmd(entry string) string {
- escaped := strings.Replace(entry, `\`, `\\`, -1)
- escaped = `"` + strings.Replace(escaped, `"`, `\"`, -1) + `"`
- r, _ := regexp.Compile(`[&|<>()@^%!"]`)
- return r.ReplaceAllStringFunc(escaped, func(match string) string {
- return "^" + match
- })
-}
-
-func quoteEntry(entry string) string {
- if util.IsWindows() {
- return quoteEntryCmd(entry)
- }
- return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
-}
-
func parsePlaceholder(match string) (bool, string, placeholderFlags) {
flags := placeholderFlags{}
@@ -1561,6 +1565,7 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
skipChars++
case 'q':
flags.query = true
+ // query flag is not skipped
default:
break
}
@@ -1648,77 +1653,86 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, pr
if selected[0] == nil {
selected = []*Item{}
}
+
+ // replace placeholders one by one
return placeholder.ReplaceAllStringFunc(template, func(match string) string {
escaped, match, flags := parsePlaceholder(match)
- if escaped {
- return match
- }
+ // this function implements the effects a placeholder has on items
+ var replace func(*Item) string
- // Current query
- if match == "{q}" {
+ // placeholder types (escaped, query type, item type, token type)
+ switch {
+ case escaped:
+ return match
+ case match == "{q}":
return quoteEntry(query)
- }
-
- items := current
- if flags.plus || forcePlus {
- items = selected
- }
-
- replacements := make([]string, len(items))
-
- if match == "{}" {
- for idx, item := range items {
- if flags.number {
+ case match == "{}":
+ replace = func(item *Item) string {
+ switch {
+ case flags.number:
n := int(item.text.Index)
if n < 0 {
- replacements[idx] = ""
- } else {
- replacements[idx] = strconv.Itoa(n)
+ return ""
}
- } else if flags.file {
- replacements[idx] = item.AsString(stripAnsi)
- } else {
- replacements[idx] = quoteEntry(item.AsString(stripAnsi))
+ return strconv.Itoa(n)
+ case flags.file:
+ return item.AsString(stripAnsi)
+ default:
+ return quoteEntry(item.AsString(stripAnsi))
}
}
- if flags.file {
- return writeTemporaryFile(replacements, printsep)
+ default:
+ // token type and also failover (below)
+ rangeExpressions := strings.Split(match[1:len(match)-1], ",")
+ ranges := make([]Range, len(rangeExpressions))
+ for idx, s := range rangeExpressions {
+ r, ok := ParseRange(&s) // ellipsis (x..y) and shorthand (x..x) range syntax
+ if !ok {
+ // Invalid expression, just return the original string in the template
+ return match
+ }
+ ranges[idx] = r
}
- return strings.Join(replacements, " ")
- }
- tokens := strings.Split(match[1:len(match)-1], ",")
- ranges := make([]Range, len(tokens))
- for idx, s := range tokens {
- r, ok := ParseRange(&s)
- if !ok {
- // Invalid expression, just return the original string in the template
- return match
+ replace = func(item *Item) string {
+ tokens := Tokenize(item.AsString(stripAnsi), delimiter)
+ trans := Transform(tokens, ranges)
+ str := joinTokens(trans)
+
+ // trim the last delimiter
+ if delimiter.str != nil {
+ str = strings.TrimSuffix(str, *delimiter.str)
+ } else if delimiter.regex != nil {
+ delims := delimiter.regex.FindAllStringIndex(str, -1)
+ // make sure the delimiter is at the very end of the string
+ if len(delims) > 0 && delims[len(delims)-1][1] == len(str) {
+ str = str[:delims[len(delims)-1][0]]
+ }
+ }
+
+ if !flags.preserveSpace {
+ str = strings.TrimSpace(str)
+ }
+ if !flags.file {
+ str = quoteEntry(str)
+ }
+ return str
}
- ranges[idx] = r
}
+ // apply 'replace' function over proper set of items and return result
+
+ items := current
+ if flags.plus || forcePlus {
+ items = selected
+ }
+ replacements := make([]string, len(items))
+
for idx, item := range items {
- tokens := Tokenize(item.AsString(stripAnsi), delimiter)
- trans := Transform(tokens, ranges)
- str := joinTokens(trans)
- if delimiter.str != nil {
- str = strings.TrimSuffix(str, *delimiter.str)
- } else if delimiter.regex != nil {
- delims := delimiter.regex.FindAllStringIndex(str, -1)
- if len(delims) > 0 && delims[len(delims)-1][1] == len(str) {
- str = str[:delims[len(delims)-1][0]]
- }
- }
- if !flags.preserveSpace {
- str = strings.TrimSpace(str)
- }
- if !flags.file {
- str = quoteEntry(str)
- }
- replacements[idx] = str
+ replacements[idx] = replace(item)
}
+
if flags.file {
return writeTemporaryFile(replacements, printsep)
}