diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index 8f79c468..5dd00e49 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -291,6 +291,9 @@ from the replacement string. To preserve the whitespace, use the \fBs\fR flag. Also, \fB{q}\fR is replaced to the current query string. Note that you can escape a placeholder pattern by prepending a backslash. + +Preview window will be updated even when there is no match for the current +query if any of the placeholder expressions evaluates to a non-empty string. .RE .TP .BI "--preview-window=" "[POSITION][:SIZE[%]][:wrap][:hidden]" diff --git a/src/terminal.go b/src/terminal.go index 2daad273..664b7d47 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -224,6 +224,7 @@ const ( type placeholderFlags struct { plus bool preserveSpace bool + query bool } func toActions(types ...actionType) []action { @@ -1152,6 +1153,8 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) { case 's': flags.preserveSpace = true skipChars++ + case 'q': + flags.query = true default: break } @@ -1162,14 +1165,17 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) { return false, matchWithoutFlags, flags } -func hasPlusFlag(template string) bool { +func hasPreviewFlags(template string) (plus bool, query bool) { for _, match := range placeholder.FindAllString(template, -1) { _, _, flags := parsePlaceholder(match) if flags.plus { - return true + plus = true + } + if flags.query { + query = true } } - return false + return } func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, forcePlus bool, query string, allItems []*Item) string { @@ -1288,13 +1294,28 @@ func (t *Terminal) currentItem() *Item { func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) { current := t.currentItem() - if !forcePlus && !hasPlusFlag(template) || len(t.selected) == 0 { + plus, query := hasPreviewFlags(template) + if !(query && len(t.input) > 0 || (forcePlus || plus) && len(t.selected) > 0) { return current != nil, []*Item{current, current} } - sels := make([]*Item, len(t.selected)+1) - sels[0] = current - for i, sel := range t.sortSelected() { - sels[i+1] = sel.item + + // We would still want to update preview window even if there is no match if + // 1. command template contains {q} and the query string is not empty + // 2. or it contains {+} and we have more than one item already selected. + // To do so, we pass an empty Item instead of nil to trigger an update. + if current == nil { + current = &Item{} + } + + var sels []*Item + if len(t.selected) == 0 { + sels = []*Item{current, current} + } else { + sels = make([]*Item, len(t.selected)+1) + sels[0] = current + for i, sel := range t.sortSelected() { + sels[i+1] = sel.item + } } return true, sels } @@ -1861,6 +1882,12 @@ func (t *Terminal) Loop() { t.mutex.Unlock() // Must be unlocked before touching reqBox if changed { + if t.isPreviewEnabled() { + _, q := hasPreviewFlags(t.preview.command) + if q { + t.version++ + } + } t.eventBox.Set(EvtSearchNew, t.sort) } for _, event := range events { diff --git a/test/test_go.rb b/test/test_go.rb index 3c487b4b..4f4f94e1 100644 --- a/test/test_go.rb +++ b/test/test_go.rb @@ -1355,6 +1355,35 @@ class TestGoFZF < TestBase tmux.until { |_| %w[1 2 3] == File.readlines(tempname).map(&:chomp) } end + def test_preview_flags + tmux.send_keys %(seq 10 | sed 's/^/:: /; s/$/ /' | + #{FZF} --multi --preview 'echo {{2}/{s2}/{+2}/{+s2}/{q}}'), :Enter + tmux.until { |lines| lines[1].include?('{1/1 /1/1 /}') } + tmux.send_keys '123' + tmux.until { |lines| lines[1].include?('{////123}') } + tmux.send_keys 'C-u', '1' + tmux.until { |lines| lines.match_count == 2 } + tmux.until { |lines| lines[1].include?('{1/1 /1/1 /1}') } + tmux.send_keys :BTab + tmux.until { |lines| lines[1].include?('{10/10 /1/1 /1}') } + tmux.send_keys :BTab + tmux.until { |lines| lines[1].include?('{10/10 /1 10/1 10 /1}') } + tmux.send_keys '2' + tmux.until { |lines| lines[1].include?('{//1 10/1 10 /12}') } + tmux.send_keys '3' + tmux.until { |lines| lines[1].include?('{//1 10/1 10 /123}') } + end + + def test_preview_q_no_match + tmux.send_keys %(: | #{FZF} --preview 'echo foo {q}'), :Enter + tmux.until { |lines| lines.match_count == 0 } + tmux.until { |lines| !lines[1].include?('foo') } + tmux.send_keys 'bar' + tmux.until { |lines| lines[1].include?('foo bar') } + tmux.send_keys 'C-u' + tmux.until { |lines| !lines[1].include?('foo') } + end + def test_no_clear tmux.send_keys "seq 10 | fzf --no-clear --inline-info --height 5 > #{tempname}", :Enter prompt = '> < 10/10'