diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f1213f8..4727de6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,18 @@ CHANGELOG ========= -0.63.1 +0.64.0 ------ +- Added `multi` event that is triggered when the multi-selection has changed. + ```sh + fzf --multi --bind 'multi:transform-footer:(( FZF_SELECT_COUNT )) && echo "Selected: $FZF_SELECT_COUNT item(s)"' + ``` - [Halfwidth and fullwidth alphanumeric and punctuation characters](https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)) are now internally normalized to their ASCII equivalents to allow matching with ASCII queries. ```sh echo ABC| fzf -q abc ``` - Fixed a bug which caused fzf to abort due to incorrect update ordering. +- Renamed `clear-selection` action to `clear-multi` for consistency. 0.63.0 ------ diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index 0802127e..fdbca13a 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. .. -.TH fzf 1 "Jun 2025" "fzf 0.63.0" "fzf - a command-line fuzzy finder" +.TH fzf 1 "Jul 2025" "fzf 0.64.0" "fzf - a command-line fuzzy finder" .SH NAME fzf - a command-line fuzzy finder @@ -1609,6 +1609,10 @@ e.g. # Beware not to introduce an infinite loop seq 10 | fzf \-\-bind 'focus:up' \-\-cycle\fR .RE +\fImulti\fR +.RS +Triggered when the multi\-selection has changed. +.RE \fIone\fR .RS @@ -1711,7 +1715,7 @@ A key or an event can be bound to one or more of the following actions. \fBchange\-prompt(...)\fR (change prompt to the given string) \fBchange\-query(...)\fR (change query string to the given string) \fBclear\-screen\fR \fIctrl\-l\fR - \fBclear\-selection\fR (clear multi\-selection) + \fBclear\-multi\fR (clear multi\-selection) \fBclose\fR (close preview window if open, abort fzf otherwise) \fBclear\-query\fR (clear query string) \fBdelete\-char\fR \fIdel\fR diff --git a/src/options.go b/src/options.go index c724f2eb..5cb9f14a 100644 --- a/src/options.go +++ b/src/options.go @@ -1008,6 +1008,8 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error add(tui.JumpCancel) case "click-header": add(tui.ClickHeader) + case "multi": + add(tui.Multi) case "alt-enter", "alt-return": chords[tui.CtrlAltKey('m')] = key case "alt-space": @@ -1561,7 +1563,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA appendAction(actCancel) case "clear-query": appendAction(actClearQuery) - case "clear-selection": + case "clear-multi", "clear-selection": appendAction(actClearSelection) case "forward-char": appendAction(actForwardChar) diff --git a/src/terminal.go b/src/terminal.go index 8b5529fe..634d7859 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -5399,6 +5399,7 @@ func (t *Terminal) Loop() error { } previousInput := t.input previousCx := t.cx + previousVersion := t.version t.lastKey = event.KeyName() updatePreviewWindow := func(forcePreview bool) { t.resizeWindows(forcePreview, false) @@ -6656,6 +6657,9 @@ func (t *Terminal) Loop() error { if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs && !doActions(onEOFs) { continue } + if onMultis, prs := t.keymap[tui.Multi.AsEvent()]; t.version != previousVersion && prs && !doActions(onMultis) { + continue + } } else { jumpEvent := tui.JumpCancel if event.Type == tui.Rune { diff --git a/src/tui/tui.go b/src/tui/tui.go index c899ee78..02b9b093 100644 --- a/src/tui/tui.go +++ b/src/tui/tui.go @@ -132,6 +132,7 @@ const ( Jump JumpCancel ClickHeader + Multi ) func (t EventType) AsEvent() Event { diff --git a/test/test_core.rb b/test/test_core.rb index b19b1dc5..08d6c05b 100644 --- a/test/test_core.rb +++ b/test/test_core.rb @@ -1988,4 +1988,15 @@ class TestCore < TestInteractive tmux.until { assert it.any_include?('boom') } tmux.until { assert it.any_include?('bam') } end + + def test_multi_event + tmux.send_keys %(seq 100 | #{FZF} --multi --bind 'multi:transform-footer:(( FZF_SELECT_COUNT )) && echo "Selected $FZF_SELECT_COUNT item(s)"'), :Enter + tmux.until { assert_equal 100, it.match_count } + tmux.send_keys :Tab + tmux.until { assert_equal 1, it.select_count } + tmux.until { assert it.any_include?('Selected 1 item(s)') } + tmux.send_keys :Tab + tmux.until { assert_equal 0, it.select_count } + tmux.until { refute it.any_include?('Selected') } + end end