summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/terminal.go87
-rw-r--r--src/terminal_test.go69
2 files changed, 86 insertions, 70 deletions
diff --git a/src/terminal.go b/src/terminal.go
index 85a51112..231375ba 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -66,7 +66,7 @@ const maxFocusEvents = 10000
const blockDuration = 1 * time.Second
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*$`)
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
@@ -692,6 +692,7 @@ func processExecution(action actionType) bool {
type placeholderFlags struct {
plus bool
+ asterisk bool
preserveSpace bool
number bool
forceUpdate bool
@@ -713,7 +714,7 @@ type searchRequest struct {
type previewRequest struct {
template string
scrollOffset int
- list []*Item
+ list [3][]*Item // current, select, and all matched items
env []string
query string
}
@@ -4099,6 +4100,8 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
trimmed := ""
for _, char := range match[1:] {
switch char {
+ case '*':
+ flags.asterisk = true
case '+':
flags.plus = true
case 's':
@@ -4122,19 +4125,16 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
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) {
escaped, _, flags := parsePlaceholder(match)
if escaped {
continue
}
- if flags.plus {
- plus = true
- }
- if flags.forceUpdate {
- forceUpdate = true
- }
slot = true
+ plus = plus || flags.plus
+ asterisk = asterisk || flags.asterisk
+ forceUpdate = forceUpdate || flags.forceUpdate
}
return
}
@@ -4146,17 +4146,17 @@ type replacePlaceholderParams struct {
printsep string
forcePlus bool
query string
- allItems []*Item
+ allItems [3][]*Item // current, select, and all matched items
lastAction actionType
prompt string
executor *util.Executor
}
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{
template: template,
stripAnsi: t.ansi,
@@ -4177,7 +4177,11 @@ func (t *Terminal) evaluateScrollOffset() int {
}
// 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)
offsetExpr := offsetTrimCharsRegex.ReplaceAllString(replaced, "")
@@ -4209,14 +4213,9 @@ func (t *Terminal) evaluateScrollOffset() int {
func replacePlaceholder(params replacePlaceholderParams) (string, []string) {
tempFiles := []string{}
- current := params.allItems[:1]
- selected := params.allItems[1:]
- if current[0] == nil {
- current = []*Item{}
- }
- if selected[0] == nil {
- selected = []*Item{}
- }
+ current := params.allItems[0]
+ selected := params.allItems[1]
+ matched := params.allItems[2]
// replace placeholders one by one
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
items := current
- if flags.plus || params.forcePlus {
+ if flags.asterisk {
+ items = matched
+ } else if flags.plus || params.forcePlus {
items = selected
}
replacements := make([]string, len(items))
@@ -4546,11 +4547,15 @@ func (t *Terminal) currentItem() *Item {
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()
- slot, plus, forceUpdate := hasPreviewFlags(template)
- if !(!slot || forceUpdate || (forcePlus || plus) && len(t.selected) > 0) {
- return current != nil, []*Item{current, current}
+ slot, plus, asterisk, forceUpdate := hasPreviewFlags(template)
+ if !(!slot || forceUpdate || asterisk || (forcePlus || plus) && len(t.selected) > 0) {
+ 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
@@ -4561,17 +4566,25 @@ func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item
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
if len(t.selected) == 0 {
- sels = []*Item{current, current}
- } else {
- sels = make([]*Item, len(t.selected)+1)
- sels[0] = current
+ sels = []*Item{current}
+ } else if len(t.selected) > 0 {
+ sels = make([]*Item, len(t.selected))
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 {
@@ -4831,7 +4844,8 @@ func (t *Terminal) Loop() error {
stop := false
t.previewBox.WaitFor(reqPreviewReady)
for {
- var items []*Item
+ requested := false
+ var items [3][]*Item
var commandTemplate string
var env []string
var query string
@@ -4849,6 +4863,7 @@ func (t *Terminal) Loop() error {
items = request.list
env = request.env
query = request.query
+ requested = true
}
}
events.Clear()
@@ -4856,7 +4871,7 @@ func (t *Terminal) Loop() error {
if stop {
break
}
- if items == nil {
+ if !requested {
continue
}
version++
@@ -6396,7 +6411,7 @@ func (t *Terminal) Loop() error {
// We run the command even when there's no match
// 1. If the template doesn't have any slots
// 2. If the template has {q}
- slot, _, forceUpdate := hasPreviewFlags(a.a)
+ slot, _, _, forceUpdate := hasPreviewFlags(a.a)
valid = !slot || forceUpdate
}
if valid {
@@ -6585,7 +6600,7 @@ func (t *Terminal) Loop() error {
}
if queryChanged && t.canPreview() && len(t.previewOpts.command) > 0 {
- _, _, forceUpdate := hasPreviewFlags(t.previewOpts.command)
+ _, _, _, forceUpdate := hasPreviewFlags(t.previewOpts.command)
if forceUpdate {
t.version++
}
diff --git a/src/terminal_test.go b/src/terminal_test.go
index 380e40d1..d6a49138 100644
--- a/src/terminal_test.go
+++ b/src/terminal_test.go
@@ -12,7 +12,7 @@ import (
"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{
template: template,
stripAnsi: stripAnsi,
@@ -30,11 +30,11 @@ func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter
func TestReplacePlaceholder(t *testing.T) {
item1 := newItem(" foo'bar \x1b[31mbaz\x1b[m")
- items1 := []*Item{item1, item1}
- items2 := []*Item{
- newItem("foo'bar \x1b[31mbaz\x1b[m"),
- newItem("foo'bar \x1b[31mbaz\x1b[m"),
- newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
+ items1 := [3][]*Item{{item1}, {item1}, nil}
+ items2 := [3][]*Item{
+ {newItem("foo'bar \x1b[31mbaz\x1b[m")},
+ {newItem("foo'bar \x1b[31mbaz\x1b[m"),
+ newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}, nil}
delim := "'"
var regex *regexp.Regexp
@@ -145,11 +145,11 @@ func TestReplacePlaceholder(t *testing.T) {
checkFormat("echo {{.O}} {{.O}}")
// 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 /")
// 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}}")
// String delimiter
@@ -166,17 +166,18 @@ func TestReplacePlaceholder(t *testing.T) {
Test single placeholders, but focus on the placeholders' parameters (e.g. flags).
see: TestParsePlaceholder
*/
- items3 := []*Item{
+ items3 := [3][]*Item{
// single line
- newItem("1a 1b 1c 1d 1e 1f"),
+ {newItem("1a 1b 1c 1d 1e 1f")},
// multi line
- newItem("1a 1b 1c 1d 1e 1f"),
- newItem("2a 2b 2c 2d 2e 2f"),
- newItem("3a 3b 3c 3d 3e 3f"),
- newItem("4a 4b 4c 4d 4e 4f"),
- newItem("5a 5b 5c 5d 5e 5f"),
- newItem("6a 6b 6c 6d 6e 6f"),
- newItem("7a 7b 7c 7d 7e 7f"),
+ {newItem("1a 1b 1c 1d 1e 1f"),
+ newItem("2a 2b 2c 2d 2e 2f"),
+ newItem("3a 3b 3c 3d 3e 3f"),
+ newItem("4a 4b 4c 4d 4e 4f"),
+ newItem("5a 5b 5c 5d 5e 5f"),
+ newItem("6a 6b 6c 6d 6e 6f"),
+ newItem("7a 7b 7c 7d 7e 7f")},
+ nil,
}
stripAnsi := false
forcePlus := false
@@ -557,14 +558,14 @@ func newItem(str string) *Item {
return &Item{origText: &bytes, text: util.ToChars([]byte(trimmed))}
}
-// Functions tested in this file require array of items (allItems). The array needs
-// to consist of at least two nils. This is helper function.
-func newItems(str ...string) []*Item {
- result := make([]*Item, util.Max(len(str), 2))
+// Functions tested in this file require array of items (allItems).
+// This is helper function.
+func newItems(str ...string) [3][]*Item {
+ result := make([]*Item, len(str))
for i, s := range str {
result[i] = newItem(s)
}
- return result
+ return [3][]*Item{result, nil, nil}
}
// (for logging purposes)
@@ -588,7 +589,7 @@ func templateToString(format string, data any) string {
type give struct {
template string
query string
- allItems []*Item
+ allItems [3][]*Item
}
type want struct {
/*
@@ -626,25 +627,25 @@ func testCommands(t *testing.T, tests []testCase) {
// evaluate the test cases
for idx, test := range tests {
gotOutput := replacePlaceholderTest(
- test.give.template, stripAnsi, delimiter, printsep, forcePlus,
- test.give.query,
- test.give.allItems)
+ test.template, stripAnsi, delimiter, printsep, forcePlus,
+ test.query,
+ test.allItems)
switch {
- case test.want.output != "":
- if gotOutput != test.want.output {
+ case test.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'",
idx,
- test.give.template, test.give.query, test.give.allItems,
- gotOutput, test.want.output)
+ test.template, test.query, test.allItems,
+ gotOutput, test.output)
}
- case test.want.match != "":
- wantMatch := strings.ReplaceAll(test.want.match, `\`, `\\`)
+ case test.match != "":
+ wantMatch := strings.ReplaceAll(test.match, `\`, `\\`)
wantRegex := regexp.MustCompile(wantMatch)
if !wantRegex.MatchString(gotOutput) {
t.Errorf("tests[%v]:\ngave{\n\ttemplate: '%s',\n\tquery: '%s',\n\tallItems: %s}\nand got '%s',\nbut want '%s'",
idx,
- test.give.template, test.give.query, test.give.allItems,
- gotOutput, test.want.match)
+ test.template, test.query, test.allItems,
+ gotOutput, test.match)
}
default:
t.Errorf("tests[%v]: test case does not describe 'want' property", idx)