mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-12 13:23:48 -05:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0076ec2e8d | ||
|
|
82c9671f79 | ||
|
|
d364a1122e | ||
|
|
fb570e94e7 | ||
|
|
6e3c830cd2 | ||
|
|
d7db7fc132 | ||
|
|
ff1550bb38 | ||
|
|
976001e474 | ||
|
|
531dd6fb4f | ||
|
|
ba035f2a76 | ||
|
|
d34675d3c9 | ||
|
|
ce95adc66c |
@@ -1,2 +1,2 @@
|
|||||||
golang 1.20.13
|
golang 1.20.14
|
||||||
ruby 3.4.1
|
ruby 3.4.1
|
||||||
|
|||||||
18
CHANGELOG.md
18
CHANGELOG.md
@@ -1,6 +1,24 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.64.0
|
||||||
|
------
|
||||||
|
- Added `multi` event that is triggered when the multi-selection has changed.
|
||||||
|
```sh
|
||||||
|
fzf --multi \
|
||||||
|
--bind 'ctrl-a:select-all,ctrl-d:deselect-all' \
|
||||||
|
--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
|
||||||
|
```
|
||||||
|
- Renamed `clear-selection` action to `clear-multi` for consistency.
|
||||||
|
- `clear-selection` remains supported as an alias for backward compatibility.
|
||||||
|
- Bug fixes
|
||||||
|
- Fixed a bug that could cause fzf to abort due to incorrect update ordering.
|
||||||
|
- Fixed a bug where some multi-selections were lost when using `exclude` or `change-nth`.
|
||||||
|
|
||||||
0.63.0
|
0.63.0
|
||||||
------
|
------
|
||||||
_Release highlights: https://junegunn.github.io/fzf/releases/0.63.0/_
|
_Release highlights: https://junegunn.github.io/fzf/releases/0.63.0/_
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2024 Junegunn Choi
|
Copyright (c) 2013-2025 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -493,4 +493,4 @@ autocmd FileType fzf set laststatus=0 noshowmode noruler
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2024 Junegunn Choi
|
Copyright (c) 2013-2025 Junegunn Choi
|
||||||
|
|||||||
@@ -503,7 +503,7 @@ LICENSE *fzf-license*
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2024 Junegunn Choi
|
Copyright (c) 2013-2025 Junegunn Choi
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:
|
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:
|
||||||
|
|||||||
2
install
2
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.63.0
|
version=0.64.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$version="0.63.0"
|
$version="0.64.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "0.63"
|
var version = "0.64"
|
||||||
var revision = "devel"
|
var revision = "devel"
|
||||||
|
|
||||||
//go:embed shell/key-bindings.bash
|
//go:embed shell/key-bindings.bash
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2024 Junegunn Choi
|
Copyright (c) 2013-2025 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf\-tmux 1 "Jun 2025" "fzf 0.63.0" "fzf\-tmux - open fzf in tmux split pane"
|
.TH fzf\-tmux 1 "Jul 2025" "fzf 0.64.0" "fzf\-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf\-tmux - open fzf in tmux split pane
|
fzf\-tmux - open fzf in tmux split pane
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2024 Junegunn Choi
|
Copyright (c) 2013-2025 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
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
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -1609,6 +1609,10 @@ e.g.
|
|||||||
# Beware not to introduce an infinite loop
|
# Beware not to introduce an infinite loop
|
||||||
seq 10 | fzf \-\-bind 'focus:up' \-\-cycle\fR
|
seq 10 | fzf \-\-bind 'focus:up' \-\-cycle\fR
|
||||||
.RE
|
.RE
|
||||||
|
\fImulti\fR
|
||||||
|
.RS
|
||||||
|
Triggered when the multi\-selection has changed.
|
||||||
|
.RE
|
||||||
|
|
||||||
\fIone\fR
|
\fIone\fR
|
||||||
.RS
|
.RS
|
||||||
@@ -1711,13 +1715,13 @@ 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\-prompt(...)\fR (change prompt to the given string)
|
||||||
\fBchange\-query(...)\fR (change query string to the given string)
|
\fBchange\-query(...)\fR (change query string to the given string)
|
||||||
\fBclear\-screen\fR \fIctrl\-l\fR
|
\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)
|
\fBclose\fR (close preview window if open, abort fzf otherwise)
|
||||||
\fBclear\-query\fR (clear query string)
|
\fBclear\-query\fR (clear query string)
|
||||||
\fBdelete\-char\fR \fIdel\fR
|
\fBdelete\-char\fR \fIdel\fR
|
||||||
\fBdelete\-char/eof\fR \fIctrl\-d\fR (same as \fBdelete\-char\fR except aborts fzf if query is empty)
|
\fBdelete\-char/eof\fR \fIctrl\-d\fR (same as \fBdelete\-char\fR except aborts fzf if query is empty)
|
||||||
\fBdeselect\fR
|
\fBdeselect\fR
|
||||||
\fBdeselect\-all\fR (deselect all matches)
|
\fBdeselect\-all\fR (deselect all matches; to also clear non-matched selections, use \fBclear\-multi\fR)
|
||||||
\fBdisable\-search\fR (disable search functionality)
|
\fBdisable\-search\fR (disable search functionality)
|
||||||
\fBdown\fR \fIctrl\-j ctrl\-n down\fR
|
\fBdown\fR \fIctrl\-j ctrl\-n down\fR
|
||||||
\fBenable\-search\fR (enable search functionality)
|
\fBenable\-search\fR (enable search functionality)
|
||||||
@@ -1938,6 +1942,17 @@ e.g.
|
|||||||
echo "change\-header:Invalid selection"'
|
echo "change\-header:Invalid selection"'
|
||||||
\fR
|
\fR
|
||||||
|
|
||||||
|
A common mistake when writing a \fBtransform\fR action is not escaping
|
||||||
|
placeholder expressions when passing them back to fzf. In the following
|
||||||
|
example, if you don't escape \fB{}\fR, fzf will immediately replace it with the
|
||||||
|
single-quoted string of the current item. This causes single quotes to appear
|
||||||
|
in the header and footer, and the script will break if any item contains
|
||||||
|
double-quote characters.
|
||||||
|
|
||||||
|
\fBfzf \-\-bind 'focus:transform:[[ $FZF_ACTION =~ up ]] &&
|
||||||
|
echo "change\-header()+transform\-footer:echo \\{}" ||
|
||||||
|
echo "change\-footer()+transform\-header:echo \\{}"'\fR
|
||||||
|
|
||||||
.SS TRANSFORM IN THE BACKGROUND
|
.SS TRANSFORM IN THE BACKGROUND
|
||||||
|
|
||||||
Transform actions are synchronous, meaning fzf becomes unresponsive while the
|
Transform actions are synchronous, meaning fzf becomes unresponsive while the
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
" Copyright (c) 2013-2024 Junegunn Choi
|
" Copyright (c) 2013-2025 Junegunn Choi
|
||||||
"
|
"
|
||||||
" MIT License
|
" MIT License
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2024 Junegunn Choi
|
Copyright (c) 2013-2025 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ func bonusAt(input *util.Chars, idx int) int16 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func normalizeRune(r rune) rune {
|
func normalizeRune(r rune) rune {
|
||||||
if r < 0x00C0 || r > 0x2184 {
|
if r < 0x00C0 || r > 0xFF61 {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -473,6 +473,103 @@ var normalized = map[rune]rune{
|
|||||||
'ử': 'u',
|
'ử': 'u',
|
||||||
'ữ': 'u',
|
'ữ': 'u',
|
||||||
'ự': 'u',
|
'ự': 'u',
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)
|
||||||
|
0xFF01: '!', // Fullwidth exclamation
|
||||||
|
0xFF02: '"', // Fullwidth quotation mark
|
||||||
|
0xFF03: '#', // Fullwidth number sign
|
||||||
|
0xFF04: '$', // Fullwidth dollar sign
|
||||||
|
0xFF05: '%', // Fullwidth percent
|
||||||
|
0xFF06: '&', // Fullwidth ampersand
|
||||||
|
0xFF07: '\'', // Fullwidth apostrophe
|
||||||
|
0xFF08: '(', // Fullwidth left parenthesis
|
||||||
|
0xFF09: ')', // Fullwidth right parenthesis
|
||||||
|
0xFF0A: '*', // Fullwidth asterisk
|
||||||
|
0xFF0B: '+', // Fullwidth plus
|
||||||
|
0xFF0C: ',', // Fullwidth comma
|
||||||
|
0xFF0D: '-', // Fullwidth hyphen-minus
|
||||||
|
0xFF0E: '.', // Fullwidth period
|
||||||
|
0xFF0F: '/', // Fullwidth slash
|
||||||
|
0xFF10: '0',
|
||||||
|
0xFF11: '1',
|
||||||
|
0xFF12: '2',
|
||||||
|
0xFF13: '3',
|
||||||
|
0xFF14: '4',
|
||||||
|
0xFF15: '5',
|
||||||
|
0xFF16: '6',
|
||||||
|
0xFF17: '7',
|
||||||
|
0xFF18: '8',
|
||||||
|
0xFF19: '9',
|
||||||
|
0xFF1A: ':', // Fullwidth colon
|
||||||
|
0xFF1B: ';', // Fullwidth semicolon
|
||||||
|
0xFF1C: '<', // Fullwidth less-than
|
||||||
|
0xFF1D: '=', // Fullwidth equal
|
||||||
|
0xFF1E: '>', // Fullwidth greater-than
|
||||||
|
0xFF1F: '?', // Fullwidth question mark
|
||||||
|
0xFF20: '@', // Fullwidth at sign
|
||||||
|
0xFF21: 'A',
|
||||||
|
0xFF22: 'B',
|
||||||
|
0xFF23: 'C',
|
||||||
|
0xFF24: 'D',
|
||||||
|
0xFF25: 'E',
|
||||||
|
0xFF26: 'F',
|
||||||
|
0xFF27: 'G',
|
||||||
|
0xFF28: 'H',
|
||||||
|
0xFF29: 'I',
|
||||||
|
0xFF2A: 'J',
|
||||||
|
0xFF2B: 'K',
|
||||||
|
0xFF2C: 'L',
|
||||||
|
0xFF2D: 'M',
|
||||||
|
0xFF2E: 'N',
|
||||||
|
0xFF2F: 'O',
|
||||||
|
0xFF30: 'P',
|
||||||
|
0xFF31: 'Q',
|
||||||
|
0xFF32: 'R',
|
||||||
|
0xFF33: 'S',
|
||||||
|
0xFF34: 'T',
|
||||||
|
0xFF35: 'U',
|
||||||
|
0xFF36: 'V',
|
||||||
|
0xFF37: 'W',
|
||||||
|
0xFF38: 'X',
|
||||||
|
0xFF39: 'Y',
|
||||||
|
0xFF3A: 'Z',
|
||||||
|
0xFF3B: '[', // Fullwidth left bracket
|
||||||
|
0xFF3C: '\\', // Fullwidth backslash
|
||||||
|
0xFF3D: ']', // Fullwidth right bracket
|
||||||
|
0xFF3E: '^', // Fullwidth circumflex
|
||||||
|
0xFF3F: '_', // Fullwidth underscore
|
||||||
|
0xFF40: '`', // Fullwidth grave accent
|
||||||
|
0xFF41: 'a',
|
||||||
|
0xFF42: 'b',
|
||||||
|
0xFF43: 'c',
|
||||||
|
0xFF44: 'd',
|
||||||
|
0xFF45: 'e',
|
||||||
|
0xFF46: 'f',
|
||||||
|
0xFF47: 'g',
|
||||||
|
0xFF48: 'h',
|
||||||
|
0xFF49: 'i',
|
||||||
|
0xFF4A: 'j',
|
||||||
|
0xFF4B: 'k',
|
||||||
|
0xFF4C: 'l',
|
||||||
|
0xFF4D: 'm',
|
||||||
|
0xFF4E: 'n',
|
||||||
|
0xFF4F: 'o',
|
||||||
|
0xFF50: 'p',
|
||||||
|
0xFF51: 'q',
|
||||||
|
0xFF52: 'r',
|
||||||
|
0xFF53: 's',
|
||||||
|
0xFF54: 't',
|
||||||
|
0xFF55: 'u',
|
||||||
|
0xFF56: 'v',
|
||||||
|
0xFF57: 'w',
|
||||||
|
0xFF58: 'x',
|
||||||
|
0xFF59: 'y',
|
||||||
|
0xFF5A: 'z',
|
||||||
|
0xFF5B: '{', // Fullwidth left brace
|
||||||
|
0xFF5C: '|', // Fullwidth vertical bar
|
||||||
|
0xFF5D: '}', // Fullwidth right brace
|
||||||
|
0xFF5E: '~', // Fullwidth tilde
|
||||||
|
0xFF61: '.', // Halfwidth ideographic full stop
|
||||||
}
|
}
|
||||||
|
|
||||||
// NormalizeRunes normalizes latin script letters
|
// NormalizeRunes normalizes latin script letters
|
||||||
@@ -480,7 +577,7 @@ func NormalizeRunes(runes []rune) []rune {
|
|||||||
ret := make([]rune, len(runes))
|
ret := make([]rune, len(runes))
|
||||||
copy(ret, runes)
|
copy(ret, runes)
|
||||||
for idx, r := range runes {
|
for idx, r := range runes {
|
||||||
if r < 0x00C0 || r > 0x2184 {
|
if r < 0x00C0 || r > 0xFF61 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
n := normalized[r]
|
n := normalized[r]
|
||||||
|
|||||||
@@ -41,6 +41,13 @@ func (c *Chunk) IsFull() bool {
|
|||||||
return c.count == chunkSize
|
return c.count == chunkSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Chunk) lastIndex(minValue int32) int32 {
|
||||||
|
if c.count == 0 {
|
||||||
|
return minValue
|
||||||
|
}
|
||||||
|
return c.items[c.count-1].Index() + 1 // Exclusive
|
||||||
|
}
|
||||||
|
|
||||||
func (cl *ChunkList) lastChunk() *Chunk {
|
func (cl *ChunkList) lastChunk() *Chunk {
|
||||||
return cl.chunks[len(cl.chunks)-1]
|
return cl.chunks[len(cl.chunks)-1]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
minIndex := request.chunks[0].items[0].Index()
|
minIndex := request.chunks[0].items[0].Index()
|
||||||
|
maxIndex := request.chunks[numChunks-1].lastIndex(minIndex)
|
||||||
cancelled := util.NewAtomicBool(false)
|
cancelled := util.NewAtomicBool(false)
|
||||||
|
|
||||||
slices := m.sliceChunks(request.chunks)
|
slices := m.sliceChunks(request.chunks)
|
||||||
@@ -236,7 +237,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
partialResult := <-resultChan
|
partialResult := <-resultChan
|
||||||
partialResults[partialResult.index] = partialResult.matches
|
partialResults[partialResult.index] = partialResult.matches
|
||||||
}
|
}
|
||||||
return NewMerger(pattern, partialResults, m.sort && request.pattern.sortable, m.tac, request.revision, minIndex), false
|
return NewMerger(pattern, partialResults, m.sort && request.pattern.sortable, m.tac, request.revision, minIndex, maxIndex), false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset is called to interrupt/signal the ongoing search
|
// Reset is called to interrupt/signal the ongoing search
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import "fmt"
|
|||||||
|
|
||||||
// EmptyMerger is a Merger with no data
|
// EmptyMerger is a Merger with no data
|
||||||
func EmptyMerger(revision revision) *Merger {
|
func EmptyMerger(revision revision) *Merger {
|
||||||
return NewMerger(nil, [][]Result{}, false, false, revision, 0)
|
return NewMerger(nil, [][]Result{}, false, false, revision, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merger holds a set of locally sorted lists of items and provides the view of
|
// Merger holds a set of locally sorted lists of items and provides the view of
|
||||||
@@ -22,14 +22,16 @@ type Merger struct {
|
|||||||
pass bool
|
pass bool
|
||||||
revision revision
|
revision revision
|
||||||
minIndex int32
|
minIndex int32
|
||||||
|
maxIndex int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// PassMerger returns a new Merger that simply returns the items in the
|
// PassMerger returns a new Merger that simply returns the items in the
|
||||||
// original order
|
// original order
|
||||||
func PassMerger(chunks *[]*Chunk, tac bool, revision revision) *Merger {
|
func PassMerger(chunks *[]*Chunk, tac bool, revision revision) *Merger {
|
||||||
var minIndex int32
|
var minIndex, maxIndex int32
|
||||||
if len(*chunks) > 0 {
|
if len(*chunks) > 0 {
|
||||||
minIndex = (*chunks)[0].items[0].Index()
|
minIndex = (*chunks)[0].items[0].Index()
|
||||||
|
maxIndex = (*chunks)[len(*chunks)-1].lastIndex(minIndex)
|
||||||
}
|
}
|
||||||
mg := Merger{
|
mg := Merger{
|
||||||
pattern: nil,
|
pattern: nil,
|
||||||
@@ -38,7 +40,8 @@ func PassMerger(chunks *[]*Chunk, tac bool, revision revision) *Merger {
|
|||||||
count: 0,
|
count: 0,
|
||||||
pass: true,
|
pass: true,
|
||||||
revision: revision,
|
revision: revision,
|
||||||
minIndex: minIndex}
|
minIndex: minIndex,
|
||||||
|
maxIndex: maxIndex}
|
||||||
|
|
||||||
for _, chunk := range *mg.chunks {
|
for _, chunk := range *mg.chunks {
|
||||||
mg.count += chunk.count
|
mg.count += chunk.count
|
||||||
@@ -47,7 +50,7 @@ func PassMerger(chunks *[]*Chunk, tac bool, revision revision) *Merger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewMerger returns a new Merger
|
// NewMerger returns a new Merger
|
||||||
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revision revision, minIndex int32) *Merger {
|
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revision revision, minIndex int32, maxIndex int32) *Merger {
|
||||||
mg := Merger{
|
mg := Merger{
|
||||||
pattern: pattern,
|
pattern: pattern,
|
||||||
lists: lists,
|
lists: lists,
|
||||||
@@ -59,7 +62,8 @@ func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revisi
|
|||||||
final: false,
|
final: false,
|
||||||
count: 0,
|
count: 0,
|
||||||
revision: revision,
|
revision: revision,
|
||||||
minIndex: minIndex}
|
minIndex: minIndex,
|
||||||
|
maxIndex: maxIndex}
|
||||||
|
|
||||||
for _, list := range mg.lists {
|
for _, list := range mg.lists {
|
||||||
mg.count += len(list)
|
mg.count += len(list)
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func TestMergerUnsorted(t *testing.T) {
|
|||||||
cnt := len(items)
|
cnt := len(items)
|
||||||
|
|
||||||
// Not sorted: same order
|
// Not sorted: same order
|
||||||
mg := NewMerger(nil, lists, false, false, revision{}, 0)
|
mg := NewMerger(nil, lists, false, false, revision{}, 0, 0)
|
||||||
assert(t, cnt == mg.Length(), "Invalid Length")
|
assert(t, cnt == mg.Length(), "Invalid Length")
|
||||||
for i := 0; i < cnt; i++ {
|
for i := 0; i < cnt; i++ {
|
||||||
assert(t, items[i] == mg.Get(i), "Invalid Get")
|
assert(t, items[i] == mg.Get(i), "Invalid Get")
|
||||||
@@ -70,7 +70,7 @@ func TestMergerSorted(t *testing.T) {
|
|||||||
cnt := len(items)
|
cnt := len(items)
|
||||||
|
|
||||||
// Sorted sorted order
|
// Sorted sorted order
|
||||||
mg := NewMerger(nil, lists, true, false, revision{}, 0)
|
mg := NewMerger(nil, lists, true, false, revision{}, 0, 0)
|
||||||
assert(t, cnt == mg.Length(), "Invalid Length")
|
assert(t, cnt == mg.Length(), "Invalid Length")
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
for i := 0; i < cnt; i++ {
|
for i := 0; i < cnt; i++ {
|
||||||
@@ -80,7 +80,7 @@ func TestMergerSorted(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Inverse order
|
// Inverse order
|
||||||
mg2 := NewMerger(nil, lists, true, false, revision{}, 0)
|
mg2 := NewMerger(nil, lists, true, false, revision{}, 0, 0)
|
||||||
for i := cnt - 1; i >= 0; i-- {
|
for i := cnt - 1; i >= 0; i-- {
|
||||||
if items[i] != mg2.Get(i) {
|
if items[i] != mg2.Get(i) {
|
||||||
t.Error("Not sorted", items[i], mg2.Get(i))
|
t.Error("Not sorted", items[i], mg2.Get(i))
|
||||||
|
|||||||
@@ -1008,6 +1008,8 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
|
|||||||
add(tui.JumpCancel)
|
add(tui.JumpCancel)
|
||||||
case "click-header":
|
case "click-header":
|
||||||
add(tui.ClickHeader)
|
add(tui.ClickHeader)
|
||||||
|
case "multi":
|
||||||
|
add(tui.Multi)
|
||||||
case "alt-enter", "alt-return":
|
case "alt-enter", "alt-return":
|
||||||
chords[tui.CtrlAltKey('m')] = key
|
chords[tui.CtrlAltKey('m')] = key
|
||||||
case "alt-space":
|
case "alt-space":
|
||||||
@@ -1561,7 +1563,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
appendAction(actCancel)
|
appendAction(actCancel)
|
||||||
case "clear-query":
|
case "clear-query":
|
||||||
appendAction(actClearQuery)
|
appendAction(actClearQuery)
|
||||||
case "clear-selection":
|
case "clear-multi", "clear-selection":
|
||||||
appendAction(actClearSelection)
|
appendAction(actClearSelection)
|
||||||
case "forward-char":
|
case "forward-char":
|
||||||
appendAction(actForwardChar)
|
appendAction(actForwardChar)
|
||||||
|
|||||||
@@ -450,31 +450,35 @@ func (a byTimeOrder) Less(i, j int) bool {
|
|||||||
return a[i].at.Before(a[j].at)
|
return a[i].at.Before(a[j].at)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EventTypes are listed in the order of their priority.
|
||||||
const (
|
const (
|
||||||
reqPrompt util.EventType = iota
|
reqResize util.EventType = iota
|
||||||
|
reqReinit
|
||||||
|
reqFullRedraw
|
||||||
|
reqRedraw
|
||||||
|
|
||||||
|
reqJump
|
||||||
|
reqPrompt
|
||||||
reqInfo
|
reqInfo
|
||||||
reqHeader
|
reqHeader
|
||||||
reqFooter
|
reqFooter
|
||||||
reqList
|
reqList
|
||||||
reqJump
|
|
||||||
reqActivate
|
|
||||||
reqReinit
|
|
||||||
reqFullRedraw
|
|
||||||
reqResize
|
|
||||||
reqRedraw
|
|
||||||
reqRedrawInputLabel
|
reqRedrawInputLabel
|
||||||
reqRedrawHeaderLabel
|
reqRedrawHeaderLabel
|
||||||
reqRedrawFooterLabel
|
reqRedrawFooterLabel
|
||||||
reqRedrawListLabel
|
reqRedrawListLabel
|
||||||
reqRedrawBorderLabel
|
reqRedrawBorderLabel
|
||||||
reqRedrawPreviewLabel
|
reqRedrawPreviewLabel
|
||||||
reqClose
|
|
||||||
reqPrintQuery
|
|
||||||
reqPreviewReady
|
reqPreviewReady
|
||||||
reqPreviewEnqueue
|
reqPreviewEnqueue
|
||||||
reqPreviewDisplay
|
reqPreviewDisplay
|
||||||
reqPreviewRefresh
|
reqPreviewRefresh
|
||||||
reqPreviewDelayed
|
reqPreviewDelayed
|
||||||
|
|
||||||
|
reqActivate
|
||||||
|
reqClose
|
||||||
|
reqPrintQuery
|
||||||
reqBecome
|
reqBecome
|
||||||
reqQuit
|
reqQuit
|
||||||
reqFatal
|
reqFatal
|
||||||
@@ -1624,14 +1628,12 @@ func (t *Terminal) changeHeader(header string) bool {
|
|||||||
return needFullRedraw
|
return needFullRedraw
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) changeFooter(footer string) bool {
|
func (t *Terminal) changeFooter(footer string) {
|
||||||
var lines []string
|
var lines []string
|
||||||
if len(footer) > 0 {
|
if len(footer) > 0 {
|
||||||
lines = strings.Split(strings.TrimSuffix(footer, "\n"), "\n")
|
lines = strings.Split(strings.TrimSuffix(footer, "\n"), "\n")
|
||||||
}
|
}
|
||||||
needFullRedraw := len(t.footer) != len(lines)
|
|
||||||
t.footer = lines
|
t.footer = lines
|
||||||
return needFullRedraw
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateHeader updates the header
|
// UpdateHeader updates the header
|
||||||
@@ -1678,12 +1680,12 @@ func (t *Terminal) UpdateList(merger *Merger) {
|
|||||||
// Trimmed by --tail: filter selection by index
|
// Trimmed by --tail: filter selection by index
|
||||||
filtered := make(map[int32]selectedItem)
|
filtered := make(map[int32]selectedItem)
|
||||||
minIndex := merger.minIndex
|
minIndex := merger.minIndex
|
||||||
maxIndex := minIndex + int32(merger.Length())
|
maxIndex := merger.maxIndex
|
||||||
for k, v := range t.selected {
|
for k, v := range t.selected {
|
||||||
var included bool
|
var included bool
|
||||||
if maxIndex > minIndex {
|
if maxIndex > minIndex {
|
||||||
included = k >= minIndex && k < maxIndex
|
included = k >= minIndex && k < maxIndex
|
||||||
} else { // int32 overflow [==> <==]
|
} else if maxIndex < minIndex { // int32 overflow [==> <==]
|
||||||
included = k >= minIndex || k < maxIndex
|
included = k >= minIndex || k < maxIndex
|
||||||
}
|
}
|
||||||
if included {
|
if included {
|
||||||
@@ -2889,19 +2891,29 @@ func (t *Terminal) resizeIfNeeded() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check footer window
|
||||||
|
if len(t.footer) > 0 && (t.footerWindow == nil || t.footerWindow.Height() != len(t.footer)) ||
|
||||||
|
len(t.footer) == 0 && t.footerWindow != nil {
|
||||||
|
t.printAll()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the header borders are used and header has changed
|
// Check if the header borders are used and header has changed
|
||||||
allHeaderLines := t.visibleHeaderLines()
|
allHeaderLines := t.visibleHeaderLines()
|
||||||
primaryHeaderLines := allHeaderLines
|
primaryHeaderLines := allHeaderLines
|
||||||
if t.hasHeaderLinesWindow() {
|
needHeaderWindow := t.hasHeaderWindow()
|
||||||
|
needHeaderLinesWindow := t.hasHeaderLinesWindow()
|
||||||
|
if needHeaderLinesWindow {
|
||||||
primaryHeaderLines -= t.headerLines
|
primaryHeaderLines -= t.headerLines
|
||||||
}
|
}
|
||||||
// FIXME: Full redraw is triggered if there are too many lines in the header
|
// FIXME: Full redraw is triggered if there are too many lines in the header
|
||||||
// so that the header window cannot display all of them.
|
// so that the header window cannot display all of them.
|
||||||
needHeaderLinesWindow := t.hasHeaderLinesWindow()
|
if (needHeaderWindow && t.headerWindow == nil) ||
|
||||||
if (t.headerBorderShape.Visible() || needHeaderLinesWindow) &&
|
(!needHeaderWindow && t.headerWindow != nil) ||
|
||||||
(t.headerWindow == nil && primaryHeaderLines > 0 || t.headerWindow != nil && primaryHeaderLines != t.headerWindow.Height()) ||
|
(needHeaderWindow && t.headerWindow != nil && primaryHeaderLines != t.headerWindow.Height()) ||
|
||||||
needHeaderLinesWindow && (t.headerLinesWindow == nil || t.headerLinesWindow != nil && t.headerLines != t.headerLinesWindow.Height()) ||
|
(needHeaderLinesWindow && t.headerLinesWindow == nil) ||
|
||||||
!needHeaderLinesWindow && t.headerLinesWindow != nil {
|
(!needHeaderLinesWindow && t.headerLinesWindow != nil) ||
|
||||||
|
(needHeaderLinesWindow && t.headerLinesWindow != nil && t.headerLines != t.headerLinesWindow.Height()) {
|
||||||
t.printAll()
|
t.printAll()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -5116,6 +5128,8 @@ func (t *Terminal) Loop() error {
|
|||||||
t.uiMutex.Lock()
|
t.uiMutex.Lock()
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
info := false
|
info := false
|
||||||
|
header := false
|
||||||
|
footer := false
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
req := util.EventType(key)
|
req := util.EventType(key)
|
||||||
value := (*events)[req]
|
value := (*events)[req]
|
||||||
@@ -5153,13 +5167,9 @@ func (t *Terminal) Loop() error {
|
|||||||
}
|
}
|
||||||
t.printList()
|
t.printList()
|
||||||
case reqHeader:
|
case reqHeader:
|
||||||
if !t.resizeIfNeeded() {
|
header = true
|
||||||
t.printHeader()
|
|
||||||
}
|
|
||||||
case reqFooter:
|
case reqFooter:
|
||||||
if !t.resizeIfNeeded() {
|
footer = true
|
||||||
t.printFooter()
|
|
||||||
}
|
|
||||||
case reqActivate:
|
case reqActivate:
|
||||||
t.suppress = false
|
t.suppress = false
|
||||||
if t.hasPreviewer() {
|
if t.hasPreviewer() {
|
||||||
@@ -5177,10 +5187,10 @@ func (t *Terminal) Loop() error {
|
|||||||
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, true)
|
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, true)
|
||||||
case reqRedrawPreviewLabel:
|
case reqRedrawPreviewLabel:
|
||||||
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.activePreviewOpts.Border(), true)
|
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.activePreviewOpts.Border(), true)
|
||||||
case reqReinit:
|
case reqReinit, reqResize, reqFullRedraw, reqRedraw:
|
||||||
|
if req == reqReinit {
|
||||||
t.tui.Resume(t.fullscreen, true)
|
t.tui.Resume(t.fullscreen, true)
|
||||||
t.fullRedraw()
|
}
|
||||||
case reqResize, reqFullRedraw, reqRedraw:
|
|
||||||
if req == reqResize {
|
if req == reqResize {
|
||||||
t.termSize = t.tui.Size()
|
t.termSize = t.tui.Size()
|
||||||
}
|
}
|
||||||
@@ -5243,9 +5253,17 @@ func (t *Terminal) Loop() error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if info && !t.resizeIfNeeded() {
|
if (info || header || footer) && !t.resizeIfNeeded() {
|
||||||
|
if info {
|
||||||
t.printInfo()
|
t.printInfo()
|
||||||
}
|
}
|
||||||
|
if header {
|
||||||
|
t.printHeader()
|
||||||
|
}
|
||||||
|
if footer {
|
||||||
|
t.printFooter()
|
||||||
|
}
|
||||||
|
}
|
||||||
t.flush()
|
t.flush()
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
t.uiMutex.Unlock()
|
t.uiMutex.Unlock()
|
||||||
@@ -5384,6 +5402,7 @@ func (t *Terminal) Loop() error {
|
|||||||
}
|
}
|
||||||
previousInput := t.input
|
previousInput := t.input
|
||||||
previousCx := t.cx
|
previousCx := t.cx
|
||||||
|
previousVersion := t.version
|
||||||
t.lastKey = event.KeyName()
|
t.lastKey = event.KeyName()
|
||||||
updatePreviewWindow := func(forcePreview bool) {
|
updatePreviewWindow := func(forcePreview bool) {
|
||||||
t.resizeWindows(forcePreview, false)
|
t.resizeWindows(forcePreview, false)
|
||||||
@@ -5670,24 +5689,17 @@ func (t *Terminal) Loop() error {
|
|||||||
t.cx = len(t.input)
|
t.cx = len(t.input)
|
||||||
case actChangeHeader, actTransformHeader, actBgTransformHeader:
|
case actChangeHeader, actTransformHeader, actBgTransformHeader:
|
||||||
capture(false, func(header string) {
|
capture(false, func(header string) {
|
||||||
|
// When a dedicated header window is not used, we may need to
|
||||||
|
// update other elements as well.
|
||||||
if t.changeHeader(header) {
|
if t.changeHeader(header) {
|
||||||
if t.headerWindow != nil {
|
req(reqList, reqPrompt, reqInfo)
|
||||||
// Need to resize header window
|
|
||||||
req(reqRedraw)
|
|
||||||
} else {
|
|
||||||
req(reqHeader, reqList, reqPrompt, reqInfo)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
req(reqHeader)
|
req(reqHeader)
|
||||||
}
|
|
||||||
})
|
})
|
||||||
case actChangeFooter, actTransformFooter, actBgTransformFooter:
|
case actChangeFooter, actTransformFooter, actBgTransformFooter:
|
||||||
capture(false, func(footer string) {
|
capture(false, func(footer string) {
|
||||||
if t.changeFooter(footer) {
|
t.changeFooter(footer)
|
||||||
req(reqRedraw)
|
|
||||||
} else {
|
|
||||||
req(reqFooter)
|
req(reqFooter)
|
||||||
}
|
|
||||||
})
|
})
|
||||||
case actChangeHeaderLabel, actTransformHeaderLabel, actBgTransformHeaderLabel:
|
case actChangeHeaderLabel, actTransformHeaderLabel, actBgTransformHeaderLabel:
|
||||||
capture(true, func(label string) {
|
capture(true, func(label string) {
|
||||||
@@ -6648,6 +6660,9 @@ func (t *Terminal) Loop() error {
|
|||||||
if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs && !doActions(onEOFs) {
|
if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs && !doActions(onEOFs) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if onMultis, prs := t.keymap[tui.Multi.AsEvent()]; t.version != previousVersion && prs && !doActions(onMultis) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
jumpEvent := tui.JumpCancel
|
jumpEvent := tui.JumpCancel
|
||||||
if event.Type == tui.Rune {
|
if event.Type == tui.Rune {
|
||||||
|
|||||||
@@ -110,11 +110,12 @@ func _() {
|
|||||||
_ = x[Jump-99]
|
_ = x[Jump-99]
|
||||||
_ = x[JumpCancel-100]
|
_ = x[JumpCancel-100]
|
||||||
_ = x[ClickHeader-101]
|
_ = x[ClickHeader-101]
|
||||||
|
_ = x[Multi-102]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidFatalBracketedPasteBeginBracketedPasteEndMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeader"
|
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidFatalBracketedPasteBeginBracketedPasteEndMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeaderMulti"
|
||||||
|
|
||||||
var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 466, 483, 488, 499, 508, 518, 528, 539, 547, 557, 566, 577, 592, 609, 615, 621, 632, 637, 641, 646, 649, 653, 659, 663, 673, 684}
|
var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 466, 483, 488, 499, 508, 518, 528, 539, 547, 557, 566, 577, 592, 609, 615, 621, 632, 637, 641, 646, 649, 653, 659, 663, 673, 684, 689}
|
||||||
|
|
||||||
func (i EventType) String() string {
|
func (i EventType) String() string {
|
||||||
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ const (
|
|||||||
Jump
|
Jump
|
||||||
JumpCancel
|
JumpCancel
|
||||||
ClickHeader
|
ClickHeader
|
||||||
|
Multi
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t EventType) AsEvent() Event {
|
func (t EventType) AsEvent() Event {
|
||||||
|
|||||||
@@ -1930,7 +1930,10 @@ class TestCore < TestInteractive
|
|||||||
|
|
||||||
def test_change_header_on_header_window
|
def test_change_header_on_header_window
|
||||||
tmux.send_keys %(seq 100 | #{FZF} --list-border --input-border --bind 'start:change-header(foo),space:change-header(bar)'), :Enter
|
tmux.send_keys %(seq 100 | #{FZF} --list-border --input-border --bind 'start:change-header(foo),space:change-header(bar)'), :Enter
|
||||||
tmux.until { |lines| assert lines.any_include?('foo') }
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('100/100')
|
||||||
|
assert lines.any_include?('foo')
|
||||||
|
end
|
||||||
tmux.send_keys :Space
|
tmux.send_keys :Space
|
||||||
tmux.until { |lines| assert lines.any_include?('bar') }
|
tmux.until { |lines| assert lines.any_include?('bar') }
|
||||||
end
|
end
|
||||||
@@ -1981,4 +1984,55 @@ class TestCore < TestInteractive
|
|||||||
refute lines.any_include?('[1]')
|
refute lines.any_include?('[1]')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_render_order
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --bind='focus:preview(echo boom)+change-footer(bam)'), :Enter
|
||||||
|
tmux.until { assert_equal 100, it.match_count }
|
||||||
|
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
|
||||||
|
|
||||||
|
def test_preserve_selection_on_revision_bump
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --multi --sync --query "'1" --bind 'a:select-all+change-header(pressed a),b:change-header(pressed b)+change-nth(1),c:exclude'), :Enter
|
||||||
|
tmux.until do
|
||||||
|
assert_equal 20, it.match_count
|
||||||
|
assert_equal 0, it.select_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :a
|
||||||
|
tmux.until do
|
||||||
|
assert_equal 20, it.match_count
|
||||||
|
assert_equal 20, it.select_count
|
||||||
|
assert it.any_include?('pressed a')
|
||||||
|
end
|
||||||
|
tmux.send_keys :b
|
||||||
|
tmux.until do
|
||||||
|
assert_equal 20, it.match_count
|
||||||
|
assert_equal 20, it.select_count
|
||||||
|
refute it.any_include?('pressed a')
|
||||||
|
assert it.any_include?('pressed b')
|
||||||
|
end
|
||||||
|
tmux.send_keys :a
|
||||||
|
tmux.until do
|
||||||
|
assert_equal 20, it.match_count
|
||||||
|
assert_equal 20, it.select_count
|
||||||
|
assert it.any_include?('pressed a')
|
||||||
|
refute it.any_include?('pressed b')
|
||||||
|
end
|
||||||
|
tmux.send_keys :c
|
||||||
|
tmux.until do
|
||||||
|
assert_equal 19, it.match_count
|
||||||
|
assert_equal 19, it.select_count
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user