diff options
Diffstat (limited to 'src/terminal.go')
| -rw-r--r-- | src/terminal.go | 154 |
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) } |
