diff --git a/CHANGELOG.md b/CHANGELOG.md index 4232d688..0984986a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ CHANGELOG 0.40.0 ------ +- Added `zero` event that is triggered when there's no match + ```sh + # Reload the candidate list when there's no match + echo $RANDOM | fzf --bind 'zero:reload(echo $RANDOM)+clear-query' --height 3 + ``` - New actions - Added `track` action which makes fzf track the current item when the search result is updated. If the user manually moves the cursor, or the diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index 0d706751..14bfc262 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -1008,6 +1008,17 @@ e.g. \fB# Automatically select the only match seq 10 | fzf --bind one:accept\fR .RE +\fIzero\fR +.RS +Triggered when there's no match. \fBzero:abort\fR binding is comparable to +\fB--exit-0\fR option, but the difference is that \fB--exit-0\fR is only +effective before the interactive finder starts but \fBzero\fR event is +triggered by the interactive finder. + +e.g. + \fB# Reload the candidate list when there's no match + echo $RANDOM | fzf --bind 'zero:reload(echo $RANDOM)+clear-query' --height 3\fR +.RE \fIbackward-eof\fR .RS diff --git a/src/options.go b/src/options.go index e0a5caf6..6f6b25e8 100644 --- a/src/options.go +++ b/src/options.go @@ -632,6 +632,8 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E add(tui.Focus) case "one": add(tui.One) + case "zero": + add(tui.Zero) case "alt-enter", "alt-return": chords[tui.CtrlAltKey('m')] = key case "alt-space": diff --git a/src/terminal.go b/src/terminal.go index 47991a65..3dc2062f 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -956,10 +956,18 @@ func (t *Terminal) UpdateList(merger *Merger, reset bool) { t.cy = count - util.Min(count, t.maxItems()) + pos } } - if !t.reading && t.merger.Length() == 1 { - one := tui.One.AsEvent() - if _, prs := t.keymap[one]; prs { - t.eventChan <- one + if !t.reading { + switch t.merger.Length() { + case 0: + zero := tui.Zero.AsEvent() + if _, prs := t.keymap[zero]; prs { + t.eventChan <- zero + } + case 1: + one := tui.One.AsEvent() + if _, prs := t.keymap[one]; prs { + t.eventChan <- one + } } } t.mutex.Unlock() @@ -2854,7 +2862,7 @@ func (t *Terminal) Loop() { } select { case event = <-t.eventChan: - needBarrier = event != tui.Load.AsEvent() + needBarrier = !event.Is(tui.Load, tui.One, tui.Zero) case actions = <-t.serverChan: event = tui.Invalid.AsEvent() needBarrier = false diff --git a/src/tui/tui.go b/src/tui/tui.go index b8b7ae6a..a0e0b487 100644 --- a/src/tui/tui.go +++ b/src/tui/tui.go @@ -94,6 +94,7 @@ const ( Load Focus One + Zero AltBS @@ -283,6 +284,15 @@ type Event struct { MouseEvent *MouseEvent } +func (e Event) Is(types ...EventType) bool { + for _, t := range types { + if e.Type == t { + return true + } + } + return false +} + type MouseEvent struct { Y int X int diff --git a/test/test_go.rb b/test/test_go.rb index fc0b9e04..7cdacdef 100755 --- a/test/test_go.rb +++ b/test/test_go.rb @@ -2835,18 +2835,24 @@ class TestGoFZF < TestBase end end - def test_one - tmux.send_keys "seq 10 | #{FZF} --bind 'one:preview:echo {} is the only match'", :Enter + def test_one_and_zero + tmux.send_keys "seq 10 | #{FZF} --bind 'zero:preview(echo no match),one:preview(echo {} is the only match)'", :Enter tmux.send_keys '1' tmux.until do |lines| assert_equal 2, lines.match_count refute(lines.any? { _1.include?('only match') }) + refute(lines.any? { _1.include?('no match') }) end tmux.send_keys '0' tmux.until do |lines| assert_equal 1, lines.match_count assert(lines.any? { _1.include?('only match') }) end + tmux.send_keys '0' + tmux.until do |lines| + assert_equal 0, lines.match_count + assert(lines.any? { _1.include?('no match') }) + end end def test_height_range_with_exit_0