mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-15 04:05:48 -07:00
Add {*} placeholder flag
This commit is contained in:
@@ -3,6 +3,11 @@ CHANGELOG
|
|||||||
|
|
||||||
0.63.0
|
0.63.0
|
||||||
------
|
------
|
||||||
|
- Added `{*}` placeholder flag that evaluates to all matched items.
|
||||||
|
```bash
|
||||||
|
seq 10000 | fzf --preview "awk '{sum += \$1} END {print sum}' {*f}"
|
||||||
|
```
|
||||||
|
- Use this with caution, as it can make fzf sluggish for large lists.
|
||||||
- Added background variants of transform actions with `bg-` prefix that run asynchronously in the background
|
- Added background variants of transform actions with `bg-` prefix that run asynchronously in the background
|
||||||
```sh
|
```sh
|
||||||
GETTER='curl -s http://metaphorpsum.com/sentences/1'
|
GETTER='curl -s http://metaphorpsum.com/sentences/1'
|
||||||
|
@@ -789,13 +789,16 @@ fzf also exports \fB$FZF_PREVIEW_TOP\fR and \fB$FZF_PREVIEW_LEFT\fR so that
|
|||||||
the preview command can determine the position of the preview window.
|
the preview command can determine the position of the preview window.
|
||||||
|
|
||||||
A placeholder expression starting with \fB+\fR flag will be replaced to the
|
A placeholder expression starting with \fB+\fR flag will be replaced to the
|
||||||
space-separated list of the selected lines (or the current line if no selection
|
space-separated list of the selected items (or the current item if no selection
|
||||||
was made) individually quoted.
|
was made) individually quoted.
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
\fBfzf \-\-multi \-\-preview='head \-10 {+}'
|
\fBfzf \-\-multi \-\-preview='head \-10 {+}'
|
||||||
git log \-\-oneline | fzf \-\-multi \-\-preview 'git show {+1}'\fR
|
git log \-\-oneline | fzf \-\-multi \-\-preview 'git show {+1}'\fR
|
||||||
|
|
||||||
|
Similarly, a placeholder expression starting with \fB*\fR flag will be replaced
|
||||||
|
to the space-separated list of all matched items individually quoted.
|
||||||
|
|
||||||
Each expression expands to a quoted string, so that it's safe to pass it as an
|
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
|
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
|
the curly braces. But if you don't want this behavior, you can put
|
||||||
@@ -807,14 +810,13 @@ from the replacement string. To preserve the whitespace, use the \fBs\fR flag.
|
|||||||
|
|
||||||
A placeholder expression with \fBf\fR flag is replaced to the path of
|
A placeholder expression with \fBf\fR flag is replaced to the path of
|
||||||
a temporary file that holds the evaluated list. This is useful when you
|
a temporary file that holds the evaluated list. This is useful when you
|
||||||
multi-select a large number of items and the length of the evaluated string may
|
pass a large number of items and the length of the evaluated string may
|
||||||
exceed \fBARG_MAX\fR.
|
exceed \fBARG_MAX\fR.
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
\fB# Press CTRL\-A to select 100K items and see the sum of all the numbers.
|
\fB# See the sum of all the matched numbers
|
||||||
# This won't work properly without 'f' flag due to ARG_MAX limit.
|
# This won't work properly without 'f' flag due to ARG_MAX limit.
|
||||||
seq 100000 | fzf \-\-multi \-\-bind ctrl\-a:select\-all \\
|
seq 100000 | fzf \-\-preview "awk '{sum+=\\$1} END {print sum}' {*f}"\fR
|
||||||
\-\-preview "awk '{sum+=\\$1} END {print sum}' {+f}"\fR
|
|
||||||
|
|
||||||
Also,
|
Also,
|
||||||
|
|
||||||
|
@@ -66,7 +66,7 @@ const maxFocusEvents = 10000
|
|||||||
const blockDuration = 1 * time.Second
|
const blockDuration = 1 * time.Second
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
placeholder = regexp.MustCompile(`\\?(?:{[+sfr]*[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*$`)
|
whiteSuffix = regexp.MustCompile(`\s*$`)
|
||||||
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
|
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
|
||||||
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
|
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
|
||||||
@@ -692,6 +692,7 @@ func processExecution(action actionType) bool {
|
|||||||
|
|
||||||
type placeholderFlags struct {
|
type placeholderFlags struct {
|
||||||
plus bool
|
plus bool
|
||||||
|
asterisk bool
|
||||||
preserveSpace bool
|
preserveSpace bool
|
||||||
number bool
|
number bool
|
||||||
forceUpdate bool
|
forceUpdate bool
|
||||||
@@ -713,7 +714,7 @@ type searchRequest struct {
|
|||||||
type previewRequest struct {
|
type previewRequest struct {
|
||||||
template string
|
template string
|
||||||
scrollOffset int
|
scrollOffset int
|
||||||
list []*Item
|
list [3][]*Item // current, select, and all matched items
|
||||||
env []string
|
env []string
|
||||||
query string
|
query string
|
||||||
}
|
}
|
||||||
@@ -4099,6 +4100,8 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
|
|||||||
trimmed := ""
|
trimmed := ""
|
||||||
for _, char := range match[1:] {
|
for _, char := range match[1:] {
|
||||||
switch char {
|
switch char {
|
||||||
|
case '*':
|
||||||
|
flags.asterisk = true
|
||||||
case '+':
|
case '+':
|
||||||
flags.plus = true
|
flags.plus = true
|
||||||
case 's':
|
case 's':
|
||||||
@@ -4122,19 +4125,16 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
|
|||||||
return false, matchWithoutFlags, flags
|
return false, matchWithoutFlags, flags
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasPreviewFlags(template string) (slot bool, plus bool, forceUpdate bool) {
|
func hasPreviewFlags(template string) (slot bool, plus bool, asterisk bool, forceUpdate bool) {
|
||||||
for _, match := range placeholder.FindAllString(template, -1) {
|
for _, match := range placeholder.FindAllString(template, -1) {
|
||||||
escaped, _, flags := parsePlaceholder(match)
|
escaped, _, flags := parsePlaceholder(match)
|
||||||
if escaped {
|
if escaped {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if flags.plus {
|
|
||||||
plus = true
|
|
||||||
}
|
|
||||||
if flags.forceUpdate {
|
|
||||||
forceUpdate = true
|
|
||||||
}
|
|
||||||
slot = true
|
slot = true
|
||||||
|
plus = plus || flags.plus
|
||||||
|
asterisk = asterisk || flags.asterisk
|
||||||
|
forceUpdate = forceUpdate || flags.forceUpdate
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4146,17 +4146,17 @@ type replacePlaceholderParams struct {
|
|||||||
printsep string
|
printsep string
|
||||||
forcePlus bool
|
forcePlus bool
|
||||||
query string
|
query string
|
||||||
allItems []*Item
|
allItems [3][]*Item // current, select, and all matched items
|
||||||
lastAction actionType
|
lastAction actionType
|
||||||
prompt string
|
prompt string
|
||||||
executor *util.Executor
|
executor *util.Executor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) replacePlaceholderInInitialCommand(template string) (string, []string) {
|
func (t *Terminal) replacePlaceholderInInitialCommand(template string) (string, []string) {
|
||||||
return t.replacePlaceholder(template, false, string(t.input), []*Item{nil, nil})
|
return t.replacePlaceholder(template, false, string(t.input), [3][]*Item{nil, nil, nil})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input string, list []*Item) (string, []string) {
|
func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input string, list [3][]*Item) (string, []string) {
|
||||||
return replacePlaceholder(replacePlaceholderParams{
|
return replacePlaceholder(replacePlaceholderParams{
|
||||||
template: template,
|
template: template,
|
||||||
stripAnsi: t.ansi,
|
stripAnsi: t.ansi,
|
||||||
@@ -4177,7 +4177,11 @@ func (t *Terminal) evaluateScrollOffset() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We only need the current item to calculate the scroll offset
|
// We only need the current item to calculate the scroll offset
|
||||||
replaced, tempFiles := t.replacePlaceholder(t.activePreviewOpts.scroll, false, "", []*Item{t.currentItem(), nil})
|
current := []*Item{t.currentItem()}
|
||||||
|
if current[0] == nil {
|
||||||
|
current = nil
|
||||||
|
}
|
||||||
|
replaced, tempFiles := t.replacePlaceholder(t.activePreviewOpts.scroll, false, "", [3][]*Item{current, nil, nil})
|
||||||
removeFiles(tempFiles)
|
removeFiles(tempFiles)
|
||||||
offsetExpr := offsetTrimCharsRegex.ReplaceAllString(replaced, "")
|
offsetExpr := offsetTrimCharsRegex.ReplaceAllString(replaced, "")
|
||||||
|
|
||||||
@@ -4209,14 +4213,9 @@ func (t *Terminal) evaluateScrollOffset() int {
|
|||||||
|
|
||||||
func replacePlaceholder(params replacePlaceholderParams) (string, []string) {
|
func replacePlaceholder(params replacePlaceholderParams) (string, []string) {
|
||||||
tempFiles := []string{}
|
tempFiles := []string{}
|
||||||
current := params.allItems[:1]
|
current := params.allItems[0]
|
||||||
selected := params.allItems[1:]
|
selected := params.allItems[1]
|
||||||
if current[0] == nil {
|
matched := params.allItems[2]
|
||||||
current = []*Item{}
|
|
||||||
}
|
|
||||||
if selected[0] == nil {
|
|
||||||
selected = []*Item{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace placeholders one by one
|
// replace placeholders one by one
|
||||||
replaced := placeholder.ReplaceAllStringFunc(params.template, func(match string) string {
|
replaced := placeholder.ReplaceAllStringFunc(params.template, func(match string) string {
|
||||||
@@ -4312,7 +4311,9 @@ func replacePlaceholder(params replacePlaceholderParams) (string, []string) {
|
|||||||
// apply 'replace' function over proper set of items and return result
|
// apply 'replace' function over proper set of items and return result
|
||||||
|
|
||||||
items := current
|
items := current
|
||||||
if flags.plus || params.forcePlus {
|
if flags.asterisk {
|
||||||
|
items = matched
|
||||||
|
} else if flags.plus || params.forcePlus {
|
||||||
items = selected
|
items = selected
|
||||||
}
|
}
|
||||||
replacements := make([]string, len(items))
|
replacements := make([]string, len(items))
|
||||||
@@ -4546,11 +4547,15 @@ func (t *Terminal) currentItem() *Item {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) {
|
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, [3][]*Item) {
|
||||||
current := t.currentItem()
|
current := t.currentItem()
|
||||||
slot, plus, forceUpdate := hasPreviewFlags(template)
|
slot, plus, asterisk, forceUpdate := hasPreviewFlags(template)
|
||||||
if !(!slot || forceUpdate || (forcePlus || plus) && len(t.selected) > 0) {
|
if !(!slot || forceUpdate || asterisk || (forcePlus || plus) && len(t.selected) > 0) {
|
||||||
return current != nil, []*Item{current, current}
|
if current == nil {
|
||||||
|
// Invalid
|
||||||
|
return false, [3][]*Item{nil, nil, nil}
|
||||||
|
}
|
||||||
|
return true, [3][]*Item{{current}, {current}, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We would still want to update preview window even if there is no match if
|
// We would still want to update preview window even if there is no match if
|
||||||
@@ -4561,17 +4566,25 @@ func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item
|
|||||||
current = &minItem
|
current = &minItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var all []*Item
|
||||||
|
if asterisk {
|
||||||
|
cnt := t.merger.Length()
|
||||||
|
all = make([]*Item, cnt)
|
||||||
|
for i := 0; i < cnt; i++ {
|
||||||
|
all[i] = t.merger.Get(i).item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var sels []*Item
|
var sels []*Item
|
||||||
if len(t.selected) == 0 {
|
if len(t.selected) == 0 {
|
||||||
sels = []*Item{current, current}
|
sels = []*Item{current}
|
||||||
} else {
|
} else if len(t.selected) > 0 {
|
||||||
sels = make([]*Item, len(t.selected)+1)
|
sels = make([]*Item, len(t.selected))
|
||||||
sels[0] = current
|
|
||||||
for i, sel := range t.sortSelected() {
|
for i, sel := range t.sortSelected() {
|
||||||
sels[i+1] = sel.item
|
sels[i] = sel.item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true, sels
|
return true, [3][]*Item{{current}, sels, all}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) selectItem(item *Item) bool {
|
func (t *Terminal) selectItem(item *Item) bool {
|
||||||
@@ -4831,7 +4844,8 @@ func (t *Terminal) Loop() error {
|
|||||||
stop := false
|
stop := false
|
||||||
t.previewBox.WaitFor(reqPreviewReady)
|
t.previewBox.WaitFor(reqPreviewReady)
|
||||||
for {
|
for {
|
||||||
var items []*Item
|
requested := false
|
||||||
|
var items [3][]*Item
|
||||||
var commandTemplate string
|
var commandTemplate string
|
||||||
var env []string
|
var env []string
|
||||||
var query string
|
var query string
|
||||||
@@ -4849,6 +4863,7 @@ func (t *Terminal) Loop() error {
|
|||||||
items = request.list
|
items = request.list
|
||||||
env = request.env
|
env = request.env
|
||||||
query = request.query
|
query = request.query
|
||||||
|
requested = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events.Clear()
|
events.Clear()
|
||||||
@@ -4856,7 +4871,7 @@ func (t *Terminal) Loop() error {
|
|||||||
if stop {
|
if stop {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if items == nil {
|
if !requested {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
version++
|
version++
|
||||||
@@ -6396,7 +6411,7 @@ func (t *Terminal) Loop() error {
|
|||||||
// We run the command even when there's no match
|
// We run the command even when there's no match
|
||||||
// 1. If the template doesn't have any slots
|
// 1. If the template doesn't have any slots
|
||||||
// 2. If the template has {q}
|
// 2. If the template has {q}
|
||||||
slot, _, forceUpdate := hasPreviewFlags(a.a)
|
slot, _, _, forceUpdate := hasPreviewFlags(a.a)
|
||||||
valid = !slot || forceUpdate
|
valid = !slot || forceUpdate
|
||||||
}
|
}
|
||||||
if valid {
|
if valid {
|
||||||
@@ -6585,7 +6600,7 @@ func (t *Terminal) Loop() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if queryChanged && t.canPreview() && len(t.previewOpts.command) > 0 {
|
if queryChanged && t.canPreview() && len(t.previewOpts.command) > 0 {
|
||||||
_, _, forceUpdate := hasPreviewFlags(t.previewOpts.command)
|
_, _, _, forceUpdate := hasPreviewFlags(t.previewOpts.command)
|
||||||
if forceUpdate {
|
if forceUpdate {
|
||||||
t.version++
|
t.version++
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems [3][]*Item) string {
|
||||||
replaced, _ := replacePlaceholder(replacePlaceholderParams{
|
replaced, _ := replacePlaceholder(replacePlaceholderParams{
|
||||||
template: template,
|
template: template,
|
||||||
stripAnsi: stripAnsi,
|
stripAnsi: stripAnsi,
|
||||||
@@ -30,11 +30,11 @@ func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter
|
|||||||
|
|
||||||
func TestReplacePlaceholder(t *testing.T) {
|
func TestReplacePlaceholder(t *testing.T) {
|
||||||
item1 := newItem(" foo'bar \x1b[31mbaz\x1b[m")
|
item1 := newItem(" foo'bar \x1b[31mbaz\x1b[m")
|
||||||
items1 := []*Item{item1, item1}
|
items1 := [3][]*Item{{item1}, {item1}, nil}
|
||||||
items2 := []*Item{
|
items2 := [3][]*Item{
|
||||||
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"),
|
||||||
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
|
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}, nil}
|
||||||
|
|
||||||
delim := "'"
|
delim := "'"
|
||||||
var regex *regexp.Regexp
|
var regex *regexp.Regexp
|
||||||
@@ -145,11 +145,11 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
checkFormat("echo {{.O}} {{.O}}")
|
checkFormat("echo {{.O}} {{.O}}")
|
||||||
|
|
||||||
// No match
|
// No match
|
||||||
result = replacePlaceholderTest("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, nil})
|
result = replacePlaceholderTest("echo {}/{+}", true, Delimiter{}, printsep, false, "query", [3][]*Item{nil, nil, nil})
|
||||||
check("echo /")
|
check("echo /")
|
||||||
|
|
||||||
// No match, but with selections
|
// No match, but with selections
|
||||||
result = replacePlaceholderTest("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, item1})
|
result = replacePlaceholderTest("echo {}/{+}", true, Delimiter{}, printsep, false, "query", [3][]*Item{nil, {item1}, nil})
|
||||||
checkFormat("echo /{{.O}} foo{{.I}}bar baz{{.O}}")
|
checkFormat("echo /{{.O}} foo{{.I}}bar baz{{.O}}")
|
||||||
|
|
||||||
// String delimiter
|
// String delimiter
|
||||||
@@ -166,17 +166,18 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
Test single placeholders, but focus on the placeholders' parameters (e.g. flags).
|
Test single placeholders, but focus on the placeholders' parameters (e.g. flags).
|
||||||
see: TestParsePlaceholder
|
see: TestParsePlaceholder
|
||||||
*/
|
*/
|
||||||
items3 := []*Item{
|
items3 := [3][]*Item{
|
||||||
// single line
|
// single line
|
||||||
newItem("1a 1b 1c 1d 1e 1f"),
|
{newItem("1a 1b 1c 1d 1e 1f")},
|
||||||
// multi line
|
// multi line
|
||||||
newItem("1a 1b 1c 1d 1e 1f"),
|
{newItem("1a 1b 1c 1d 1e 1f"),
|
||||||
newItem("2a 2b 2c 2d 2e 2f"),
|
newItem("2a 2b 2c 2d 2e 2f"),
|
||||||
newItem("3a 3b 3c 3d 3e 3f"),
|
newItem("3a 3b 3c 3d 3e 3f"),
|
||||||
newItem("4a 4b 4c 4d 4e 4f"),
|
newItem("4a 4b 4c 4d 4e 4f"),
|
||||||
newItem("5a 5b 5c 5d 5e 5f"),
|
newItem("5a 5b 5c 5d 5e 5f"),
|
||||||
newItem("6a 6b 6c 6d 6e 6f"),
|
newItem("6a 6b 6c 6d 6e 6f"),
|
||||||
newItem("7a 7b 7c 7d 7e 7f"),
|
newItem("7a 7b 7c 7d 7e 7f")},
|
||||||
|
nil,
|
||||||
}
|
}
|
||||||
stripAnsi := false
|
stripAnsi := false
|
||||||
forcePlus := false
|
forcePlus := false
|
||||||
@@ -557,14 +558,14 @@ func newItem(str string) *Item {
|
|||||||
return &Item{origText: &bytes, text: util.ToChars([]byte(trimmed))}
|
return &Item{origText: &bytes, text: util.ToChars([]byte(trimmed))}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Functions tested in this file require array of items (allItems). The array needs
|
// Functions tested in this file require array of items (allItems).
|
||||||
// to consist of at least two nils. This is helper function.
|
// This is helper function.
|
||||||
func newItems(str ...string) []*Item {
|
func newItems(str ...string) [3][]*Item {
|
||||||
result := make([]*Item, util.Max(len(str), 2))
|
result := make([]*Item, len(str))
|
||||||
for i, s := range str {
|
for i, s := range str {
|
||||||
result[i] = newItem(s)
|
result[i] = newItem(s)
|
||||||
}
|
}
|
||||||
return result
|
return [3][]*Item{result, nil, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
// (for logging purposes)
|
// (for logging purposes)
|
||||||
@@ -588,7 +589,7 @@ func templateToString(format string, data any) string {
|
|||||||
type give struct {
|
type give struct {
|
||||||
template string
|
template string
|
||||||
query string
|
query string
|
||||||
allItems []*Item
|
allItems [3][]*Item
|
||||||
}
|
}
|
||||||
type want struct {
|
type want struct {
|
||||||
/*
|
/*
|
||||||
@@ -626,25 +627,25 @@ func testCommands(t *testing.T, tests []testCase) {
|
|||||||
// evaluate the test cases
|
// evaluate the test cases
|
||||||
for idx, test := range tests {
|
for idx, test := range tests {
|
||||||
gotOutput := replacePlaceholderTest(
|
gotOutput := replacePlaceholderTest(
|
||||||
test.give.template, stripAnsi, delimiter, printsep, forcePlus,
|
test.template, stripAnsi, delimiter, printsep, forcePlus,
|
||||||
test.give.query,
|
test.query,
|
||||||
test.give.allItems)
|
test.allItems)
|
||||||
switch {
|
switch {
|
||||||
case test.want.output != "":
|
case test.output != "":
|
||||||
if gotOutput != test.want.output {
|
if gotOutput != test.output {
|
||||||
t.Errorf("tests[%v]:\ngave{\n\ttemplate: '%s',\n\tquery: '%s',\n\tallItems: %s}\nand got '%s',\nbut want '%s'",
|
t.Errorf("tests[%v]:\ngave{\n\ttemplate: '%s',\n\tquery: '%s',\n\tallItems: %s}\nand got '%s',\nbut want '%s'",
|
||||||
idx,
|
idx,
|
||||||
test.give.template, test.give.query, test.give.allItems,
|
test.template, test.query, test.allItems,
|
||||||
gotOutput, test.want.output)
|
gotOutput, test.output)
|
||||||
}
|
}
|
||||||
case test.want.match != "":
|
case test.match != "":
|
||||||
wantMatch := strings.ReplaceAll(test.want.match, `\`, `\\`)
|
wantMatch := strings.ReplaceAll(test.match, `\`, `\\`)
|
||||||
wantRegex := regexp.MustCompile(wantMatch)
|
wantRegex := regexp.MustCompile(wantMatch)
|
||||||
if !wantRegex.MatchString(gotOutput) {
|
if !wantRegex.MatchString(gotOutput) {
|
||||||
t.Errorf("tests[%v]:\ngave{\n\ttemplate: '%s',\n\tquery: '%s',\n\tallItems: %s}\nand got '%s',\nbut want '%s'",
|
t.Errorf("tests[%v]:\ngave{\n\ttemplate: '%s',\n\tquery: '%s',\n\tallItems: %s}\nand got '%s',\nbut want '%s'",
|
||||||
idx,
|
idx,
|
||||||
test.give.template, test.give.query, test.give.allItems,
|
test.template, test.query, test.allItems,
|
||||||
gotOutput, test.want.match)
|
gotOutput, test.match)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
t.Errorf("tests[%v]: test case does not describe 'want' property", idx)
|
t.Errorf("tests[%v]: test case does not describe 'want' property", idx)
|
||||||
|
@@ -189,6 +189,20 @@ class TestPreview < TestInteractive
|
|||||||
tmux.until { |lines| assert_includes lines[1], ' {//1 10/1 10 /123//0 9} ' }
|
tmux.until { |lines| assert_includes lines[1], ' {//1 10/1 10 /123//0 9} ' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_preview_asterisk
|
||||||
|
tmux.send_keys %(seq 5 | #{FZF} --multi --preview 'echo [{} / {+} / {*}]' --preview-window '+{1}'), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 5, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' [1 / 1 / 1 2 3 4 5] ' }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' [2 / 1 / 1 2 3 4 5] ' }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' [3 / 1 2 / 1 2 3 4 5] ' }
|
||||||
|
tmux.send_keys '5'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' [5 / 1 2 / 5] ' }
|
||||||
|
tmux.send_keys '5'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' [ / 1 2 / ] ' }
|
||||||
|
end
|
||||||
|
|
||||||
def test_preview_file
|
def test_preview_file
|
||||||
tmux.send_keys %[(echo foo bar; echo bar foo) | #{FZF} --multi --preview 'cat {+f} {+f2} {+nf} {+fn}' --print0], :Enter
|
tmux.send_keys %[(echo foo bar; echo bar foo) | #{FZF} --multi --preview 'cat {+f} {+f2} {+nf} {+fn}' --print0], :Enter
|
||||||
tmux.until { |lines| assert_includes lines[1], ' foo barbar00 ' }
|
tmux.until { |lines| assert_includes lines[1], ' foo barbar00 ' }
|
||||||
|
Reference in New Issue
Block a user