mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-01 20:52:06 -07:00
Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ecb6b234cc | ||
|
39dbc8acdb | ||
|
a56489bc7f | ||
|
99927c7071 | ||
|
3e28403978 | ||
|
37370f057f | ||
|
f4b46fad27 | ||
|
9d2c6a95f4 | ||
|
376a76d1d3 | ||
|
1fcc07e54e | ||
|
8db3345c2f | ||
|
69aa2fea68 | ||
|
298749bfcd | ||
|
f1f31baae1 | ||
|
e1c8f19e8f | ||
|
5e302c70e9 | ||
|
4c5a679066 | ||
|
41f0b2c354 | ||
|
a0a3c349c9 | ||
|
bc3983181d | ||
|
980b58ef5a | ||
|
a2604c0963 | ||
|
6dbc108da2 | ||
|
bd98f988f0 | ||
|
06301c7847 |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,6 +1,16 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.16.11
|
||||||
|
-------
|
||||||
|
- Performance optimization
|
||||||
|
- Fixed missing preview update
|
||||||
|
|
||||||
|
0.16.10
|
||||||
|
-------
|
||||||
|
- Fixed invalid handling of ANSI colors in preview window
|
||||||
|
- Further improved `--ansi` performance
|
||||||
|
|
||||||
0.16.9
|
0.16.9
|
||||||
------
|
------
|
||||||
- Memory and performance optimization
|
- Memory and performance optimization
|
||||||
|
105
README.md
105
README.md
@@ -3,15 +3,19 @@
|
|||||||
|
|
||||||
fzf is a general-purpose command-line fuzzy finder.
|
fzf is a general-purpose command-line fuzzy finder.
|
||||||
|
|
||||||

|
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-preview.png" width=640>
|
||||||
|
|
||||||
|
It's an interactive Unix filter for command-line that can be used with any
|
||||||
|
list; files, command history, processes, hostnames, bookmarks, git commits,
|
||||||
|
etc.
|
||||||
|
|
||||||
Pros
|
Pros
|
||||||
----
|
----
|
||||||
|
|
||||||
- No dependencies
|
- Portable, no dependencies
|
||||||
- Blazingly fast
|
- Blazingly fast
|
||||||
- The most comprehensive feature set
|
- The most comprehensive feature set
|
||||||
- Flexible layout using tmux panes
|
- Flexible layout
|
||||||
- Batteries included
|
- Batteries included
|
||||||
- Vim/Neovim plugin, key bindings and fuzzy auto-completion
|
- Vim/Neovim plugin, key bindings and fuzzy auto-completion
|
||||||
|
|
||||||
@@ -42,6 +46,10 @@ Table of Contents
|
|||||||
* [Settings](#settings)
|
* [Settings](#settings)
|
||||||
* [Supported commands](#supported-commands)
|
* [Supported commands](#supported-commands)
|
||||||
* [Vim plugin](#vim-plugin)
|
* [Vim plugin](#vim-plugin)
|
||||||
|
* [Advanced topics](#advanced-topics)
|
||||||
|
* [Performance](#performance)
|
||||||
|
* [Executing external programs](#executing-external-programs)
|
||||||
|
* [Preview window](#preview-window)
|
||||||
* [Tips](#tips)
|
* [Tips](#tips)
|
||||||
* [Respecting .gitignore, <code>.hgignore</code>, and <code>svn:ignore</code>](#respecting-gitignore-hgignore-and-svnignore)
|
* [Respecting .gitignore, <code>.hgignore</code>, and <code>svn:ignore</code>](#respecting-gitignore-hgignore-and-svnignore)
|
||||||
* [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal)
|
* [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal)
|
||||||
@@ -383,13 +391,94 @@ Vim plugin
|
|||||||
|
|
||||||
See [README-VIM.md](README-VIM.md).
|
See [README-VIM.md](README-VIM.md).
|
||||||
|
|
||||||
|
Advanced topics
|
||||||
|
---------------
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
fzf is fast, and is [getting even faster][perf]. Performance should not be
|
||||||
|
a problem in most use cases. However, you might want to be aware of the
|
||||||
|
options that affect the performance.
|
||||||
|
|
||||||
|
- `--ansi` tells fzf to extract and parse ANSI color codes in the input and it
|
||||||
|
makes the initial scanning slower. So it's not recommended that you add it
|
||||||
|
to your `$FZF_DEFAULT_OPTS`.
|
||||||
|
- `--nth` makes fzf slower as fzf has to tokenize each line.
|
||||||
|
- `--with-nth` makes fzf slower as fzf has to tokenize and reassemble each
|
||||||
|
line.
|
||||||
|
- If you absolutely need better performance, you can consider using
|
||||||
|
`--algo=v1` (the default being `v2`) to make fzf use faster greedy
|
||||||
|
algorithm. However, this algorithm is not guaranteed to find the optimal
|
||||||
|
ordering of the matches and is not recommended.
|
||||||
|
|
||||||
|
[perf]: https://junegunn.kr/images/fzf-0.16.10.png
|
||||||
|
|
||||||
|
### Executing external programs
|
||||||
|
|
||||||
|
You can set up key bindings for starting external processes without leaving
|
||||||
|
fzf (`execute`, `execute-silent`).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Press F1 to open the file with less without leaving fzf
|
||||||
|
# Press CTRL-Y to copy the line to clipboard and aborts fzf (requires pbcopy)
|
||||||
|
fzf --bind 'f1:execute(less -f {}),ctrl-y:execute-silent(echo {} | pbcopy)+abort'
|
||||||
|
```
|
||||||
|
|
||||||
|
See *KEY BINDINGS* section of the man page for details.
|
||||||
|
|
||||||
|
### Preview window
|
||||||
|
|
||||||
|
When `--preview` option is set, fzf automatically starts external process with
|
||||||
|
the current line as the argument and shows the result in the split window.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# {} is replaced to the single-quoted string of the focused line
|
||||||
|
fzf --preview 'cat {}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Since preview window is updated only after the process is complete, it's
|
||||||
|
important that the command finishes quickly.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use head instead of cat so that the command doesn't take too long to finish
|
||||||
|
fzf --preview 'head -100 {}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Preview window supports ANSI colors, so you can use programs that
|
||||||
|
syntax-highlights the content of a file.
|
||||||
|
|
||||||
|
- Highlight: http://www.andre-simon.de/doku/highlight/en/highlight.php
|
||||||
|
- CodeRay: http://coderay.rubychan.de/
|
||||||
|
- Rouge: https://github.com/jneen/rouge
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Try highlight, coderay, rougify in turn, then fall back to cat
|
||||||
|
fzf --preview '[[ $(file --mime {}) =~ binary ]] &&
|
||||||
|
echo {} is a binary file ||
|
||||||
|
(highlight -O ansi -l {} ||
|
||||||
|
coderay {} ||
|
||||||
|
rougify {} ||
|
||||||
|
cat {}) 2> /dev/null | head -500'
|
||||||
|
```
|
||||||
|
|
||||||
|
You can customize the size and position of the preview window using
|
||||||
|
`--preview-window` option. For example,
|
||||||
|
|
||||||
|
```bash
|
||||||
|
fzf --height 40% --reverse --preview 'file {}' --preview-window down:1
|
||||||
|
```
|
||||||
|
|
||||||
|
For more advanced examples, see [Key bindings for git with fzf][fzf-git].
|
||||||
|
|
||||||
|
[fzf-git]: https://junegunn.kr/2016/07/fzf-git/
|
||||||
|
|
||||||
Tips
|
Tips
|
||||||
----
|
----
|
||||||
|
|
||||||
#### Respecting `.gitignore`, `.hgignore`, and `svn:ignore`
|
#### Respecting `.gitignore`, `.hgignore`, and `svn:ignore`
|
||||||
|
|
||||||
[ag](https://github.com/ggreer/the_silver_searcher) or
|
[ag](https://github.com/ggreer/the_silver_searcher) or
|
||||||
[pt](https://github.com/monochromegane/the_platinum_searcher) will do the
|
[rg](https://github.com/BurntSushi/ripgrep) will do the
|
||||||
filtering:
|
filtering:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -426,10 +515,10 @@ export FZF_DEFAULT_COMMAND='
|
|||||||
|
|
||||||
#### Fish shell
|
#### Fish shell
|
||||||
|
|
||||||
It's [a known bug of fish](https://github.com/fish-shell/fish-shell/issues/1362)
|
Fish shell before version 2.6.0 [doesn't allow](https://github.com/fish-shell/fish-shell/issues/1362)
|
||||||
(will be fixed in 2.6.0) that it doesn't allow reading from STDIN in command
|
reading from STDIN in command substitution, which means simple `vim (fzf)`
|
||||||
substitution, which means simple `vim (fzf)` won't work as expected. The
|
doesn't work as expected. The workaround for fish 2.5.0 and earlier is to use
|
||||||
workaround is to use the `read` fish command:
|
the `read` fish command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
fzf | read -l result; and vim $result
|
fzf | read -l result; and vim $result
|
||||||
|
3
install
3
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.16.9
|
version=0.16.11
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -171,6 +171,7 @@ case "$archi" in
|
|||||||
OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64}.tgz ;;
|
OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64}.tgz ;;
|
||||||
OpenBSD\ *86) download fzf-$version-openbsd_${binary_arch:-386}.tgz ;;
|
OpenBSD\ *86) download fzf-$version-openbsd_${binary_arch:-386}.tgz ;;
|
||||||
CYGWIN*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
|
CYGWIN*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
|
||||||
|
MINGW*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
|
||||||
*) binary_available=0 binary_error=1 ;;
|
*) binary_available=0 binary_error=1 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
@@ -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 "Jul 2017" "fzf 0.16.9" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Aug 2017" "fzf 0.16.11" "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
|
||||||
|
@@ -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 "Jul 2017" "fzf 0.16.9" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Aug 2017" "fzf 0.16.11" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -111,6 +111,9 @@ Comma-separated list of sort criteria to apply when the scores are tied.
|
|||||||
.B "-m, --multi"
|
.B "-m, --multi"
|
||||||
Enable multi-select with tab/shift-tab
|
Enable multi-select with tab/shift-tab
|
||||||
.TP
|
.TP
|
||||||
|
.B "+m, --no-multi"
|
||||||
|
Disable multi-select
|
||||||
|
.TP
|
||||||
.B "--no-mouse"
|
.B "--no-mouse"
|
||||||
Disable mouse
|
Disable mouse
|
||||||
.TP
|
.TP
|
||||||
@@ -357,6 +360,9 @@ e.g. \fBfzf --multi | fzf --sync\fR
|
|||||||
.B "--version"
|
.B "--version"
|
||||||
Display version information and exit
|
Display version information and exit
|
||||||
|
|
||||||
|
.TP
|
||||||
|
Note that most options have the opposite versions with \fB--no-\fR prefix.
|
||||||
|
|
||||||
.SH ENVIRONMENT VARIABLES
|
.SH ENVIRONMENT VARIABLES
|
||||||
.TP
|
.TP
|
||||||
.B FZF_DEFAULT_COMMAND
|
.B FZF_DEFAULT_COMMAND
|
||||||
|
@@ -688,7 +688,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
lcd -
|
lcd -
|
||||||
endif
|
endif
|
||||||
endtry
|
endtry
|
||||||
setlocal nospell bufhidden=wipe nobuflisted
|
setlocal nospell bufhidden=wipe nobuflisted nonumber
|
||||||
setf fzf
|
setf fzf
|
||||||
startinsert
|
startinsert
|
||||||
return []
|
return []
|
||||||
@@ -756,7 +756,7 @@ let s:default_action = {
|
|||||||
function! s:shortpath()
|
function! s:shortpath()
|
||||||
let short = pathshorten(fnamemodify(getcwd(), ':~:.'))
|
let short = pathshorten(fnamemodify(getcwd(), ':~:.'))
|
||||||
let slash = (s:is_win && !&shellslash) ? '\' : '/'
|
let slash = (s:is_win && !&shellslash) ? '\' : '/'
|
||||||
return empty(short) ? '~'.slash : short . (short =~ slash.'$' ? '' : slash)
|
return empty(short) ? '~'.slash : short . (short =~ escape(slash, '\').'$' ? '' : slash)
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:cmd(bang, ...) abort
|
function! s:cmd(bang, ...) abort
|
||||||
|
@@ -78,9 +78,11 @@ Scoring criteria
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
@@ -251,19 +253,72 @@ func normalizeRune(r rune) rune {
|
|||||||
// 2. "pattern" is already normalized if "normalize" is true
|
// 2. "pattern" is already normalized if "normalize" is true
|
||||||
type Algo func(caseSensitive bool, normalize bool, forward bool, input util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int)
|
type Algo func(caseSensitive bool, normalize bool, forward bool, input util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int)
|
||||||
|
|
||||||
|
func trySkip(input *util.Chars, caseSensitive bool, b byte, from int) int {
|
||||||
|
byteArray := input.Bytes()[from:]
|
||||||
|
idx := bytes.IndexByte(byteArray, b)
|
||||||
|
if idx == 0 {
|
||||||
|
// Can't skip any further
|
||||||
|
return from
|
||||||
|
}
|
||||||
|
// We may need to search for the uppercase letter again. We don't have to
|
||||||
|
// consider normalization as we can be sure that this is an ASCII string.
|
||||||
|
if !caseSensitive && b >= 'a' && b <= 'z' {
|
||||||
|
uidx := bytes.IndexByte(byteArray, b-32)
|
||||||
|
if idx < 0 || uidx >= 0 && uidx < idx {
|
||||||
|
idx = uidx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if idx < 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return from + idx
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAscii(runes []rune) bool {
|
||||||
|
for _, r := range runes {
|
||||||
|
if r >= utf8.RuneSelf {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func asciiFuzzyIndex(input *util.Chars, pattern []rune, caseSensitive bool) int {
|
||||||
|
// Can't determine
|
||||||
|
if !input.IsBytes() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not possible
|
||||||
|
if !isAscii(pattern) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
firstIdx, idx := 0, 0
|
||||||
|
for pidx := 0; pidx < len(pattern); pidx++ {
|
||||||
|
idx = trySkip(input, caseSensitive, byte(pattern[pidx]), idx)
|
||||||
|
if idx < 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if pidx == 0 && idx > 0 {
|
||||||
|
// Step back to find the right bonus point
|
||||||
|
firstIdx = idx - 1
|
||||||
|
}
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
return firstIdx
|
||||||
|
}
|
||||||
|
|
||||||
func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
// Assume that pattern is given in lowercase if case-insensitive.
|
// Assume that pattern is given in lowercase if case-insensitive.
|
||||||
// First check if there's a match and calculate bonus for each position.
|
// First check if there's a match and calculate bonus for each position.
|
||||||
// If the input string is too long, consider finding the matching chars in
|
// If the input string is too long, consider finding the matching chars in
|
||||||
// this phase as well (non-optimal alignment).
|
// this phase as well (non-optimal alignment).
|
||||||
N := input.Length()
|
|
||||||
M := len(pattern)
|
M := len(pattern)
|
||||||
switch M {
|
if M == 0 {
|
||||||
case 0:
|
|
||||||
return Result{0, 0, 0}, posArray(withPos, M)
|
return Result{0, 0, 0}, posArray(withPos, M)
|
||||||
case 1:
|
|
||||||
return ExactMatchNaive(caseSensitive, normalize, forward, input, pattern[0:1], withPos, slab)
|
|
||||||
}
|
}
|
||||||
|
N := input.Length()
|
||||||
|
|
||||||
// Since O(nm) algorithm can be prohibitively expensive for large input,
|
// Since O(nm) algorithm can be prohibitively expensive for large input,
|
||||||
// we fall back to the greedy algorithm.
|
// we fall back to the greedy algorithm.
|
||||||
@@ -281,10 +336,16 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input util.C
|
|||||||
// Rune array
|
// Rune array
|
||||||
offset32, T := alloc32(offset32, slab, N, false)
|
offset32, T := alloc32(offset32, slab, N, false)
|
||||||
|
|
||||||
// Phase 1. Check if there's a match and calculate bonus for each point
|
// Phase 1. Optimized search for ASCII string
|
||||||
|
idx := asciiFuzzyIndex(&input, pattern, caseSensitive)
|
||||||
|
if idx < 0 {
|
||||||
|
return Result{-1, -1, 0}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 2. Calculate bonus for each point
|
||||||
pidx, lastIdx, prevClass := 0, 0, charNonWord
|
pidx, lastIdx, prevClass := 0, 0, charNonWord
|
||||||
input.CopyRunes(T)
|
input.CopyRunes(T)
|
||||||
for idx := 0; idx < N; idx++ {
|
for ; idx < N; idx++ {
|
||||||
char := T[idx]
|
char := T[idx]
|
||||||
var class charClass
|
var class charClass
|
||||||
if char <= unicode.MaxASCII {
|
if char <= unicode.MaxASCII {
|
||||||
@@ -324,8 +385,17 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input util.C
|
|||||||
if pidx != M {
|
if pidx != M {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
if M == 1 && B[F[0]] == bonusBoundary {
|
||||||
|
p := int(F[0])
|
||||||
|
result := Result{p, p + 1, scoreMatch + bonusBoundary*bonusFirstCharMultiplier}
|
||||||
|
if !withPos {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
pos := []int{p}
|
||||||
|
return result, &pos
|
||||||
|
}
|
||||||
|
|
||||||
// Phase 2. Fill in score matrix (H)
|
// Phase 3. Fill in score matrix (H)
|
||||||
// Unlike the original algorithm, we do not allow omission.
|
// Unlike the original algorithm, we do not allow omission.
|
||||||
width := lastIdx - int(F[0]) + 1
|
width := lastIdx - int(F[0]) + 1
|
||||||
offset16, H := alloc16(offset16, slab, width*M, false)
|
offset16, H := alloc16(offset16, slab, width*M, false)
|
||||||
@@ -414,7 +484,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input util.C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 3. (Optional) Backtrace to find character positions
|
// Phase 4. (Optional) Backtrace to find character positions
|
||||||
pos := posArray(withPos, M)
|
pos := posArray(withPos, M)
|
||||||
j := int(F[0])
|
j := int(F[0])
|
||||||
if withPos {
|
if withPos {
|
||||||
@@ -607,6 +677,10 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text util
|
|||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if asciiFuzzyIndex(&text, pattern, caseSensitive) < 0 {
|
||||||
|
return Result{-1, -1, 0}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// For simplicity, only look at the bonus at the first character position
|
// For simplicity, only look at the bonus at the first character position
|
||||||
pidx := 0
|
pidx := 0
|
||||||
bestPos, bonus, bestBonus := -1, int16(0), int16(-1)
|
bestPos, bonus, bestBonus := -1, int16(0), int16(-1)
|
||||||
|
@@ -17,7 +17,7 @@ func assertMatch2(t *testing.T, fun Algo, caseSensitive, normalize, forward bool
|
|||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
pattern = strings.ToLower(pattern)
|
pattern = strings.ToLower(pattern)
|
||||||
}
|
}
|
||||||
res, pos := fun(caseSensitive, normalize, forward, util.RunesToChars([]rune(input)), []rune(pattern), true, nil)
|
res, pos := fun(caseSensitive, normalize, forward, util.ToChars([]byte(input)), []rune(pattern), true, nil)
|
||||||
var start, end int
|
var start, end int
|
||||||
if pos == nil || len(*pos) == 0 {
|
if pos == nil || len(*pos) == 0 {
|
||||||
start = res.Start
|
start = res.Start
|
||||||
|
@@ -55,7 +55,6 @@ func CountItems(cs []*Chunk) int {
|
|||||||
// Push adds the item to the list
|
// Push adds the item to the list
|
||||||
func (cl *ChunkList) Push(data []byte) bool {
|
func (cl *ChunkList) Push(data []byte) bool {
|
||||||
cl.mutex.Lock()
|
cl.mutex.Lock()
|
||||||
defer cl.mutex.Unlock()
|
|
||||||
|
|
||||||
if len(cl.chunks) == 0 || cl.lastChunk().IsFull() {
|
if len(cl.chunks) == 0 || cl.lastChunk().IsFull() {
|
||||||
newChunk := Chunk(make([]Item, 0, chunkSize))
|
newChunk := Chunk(make([]Item, 0, chunkSize))
|
||||||
@@ -64,17 +63,19 @@ func (cl *ChunkList) Push(data []byte) bool {
|
|||||||
|
|
||||||
if cl.lastChunk().push(cl.trans, data, cl.count) {
|
if cl.lastChunk().push(cl.trans, data, cl.count) {
|
||||||
cl.count++
|
cl.count++
|
||||||
|
cl.mutex.Unlock()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
cl.mutex.Unlock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot returns immutable snapshot of the ChunkList
|
// Snapshot returns immutable snapshot of the ChunkList
|
||||||
func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
|
func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
|
||||||
cl.mutex.Lock()
|
cl.mutex.Lock()
|
||||||
defer cl.mutex.Unlock()
|
|
||||||
|
|
||||||
ret := make([]*Chunk, len(cl.chunks))
|
ret := make([]*Chunk, len(cl.chunks))
|
||||||
|
count := cl.count
|
||||||
copy(ret, cl.chunks)
|
copy(ret, cl.chunks)
|
||||||
|
|
||||||
// Duplicate the last chunk
|
// Duplicate the last chunk
|
||||||
@@ -82,5 +83,7 @@ func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
|
|||||||
newChunk := *ret[cnt-1]
|
newChunk := *ret[cnt-1]
|
||||||
ret[cnt-1] = &newChunk
|
ret[cnt-1] = &newChunk
|
||||||
}
|
}
|
||||||
return ret, cl.count
|
|
||||||
|
cl.mutex.Unlock()
|
||||||
|
return ret, count
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Current version
|
// Current version
|
||||||
version = "0.16.9"
|
version = "0.16.11"
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||||
|
@@ -69,14 +69,14 @@ func Run(opts *Options, revision string) {
|
|||||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
trimmed, offsets, newState := extractColor(string(data), state, nil)
|
trimmed, offsets, newState := extractColor(string(data), state, nil)
|
||||||
state = newState
|
state = newState
|
||||||
return util.RunesToChars([]rune(trimmed)), offsets
|
return util.ToChars([]byte(trimmed)), offsets
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// When color is disabled but ansi option is given,
|
// When color is disabled but ansi option is given,
|
||||||
// we simply strip out ANSI codes from the input
|
// we simply strip out ANSI codes from the input
|
||||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
trimmed, _, _ := extractColor(string(data), nil, nil)
|
trimmed, _, _ := extractColor(string(data), nil, nil)
|
||||||
return util.RunesToChars([]rune(trimmed)), nil
|
return util.ToChars([]byte(trimmed)), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,7 +205,6 @@ func Run(opts *Options, revision string) {
|
|||||||
delay := true
|
delay := true
|
||||||
ticks++
|
ticks++
|
||||||
eventBox.Wait(func(events *util.Events) {
|
eventBox.Wait(func(events *util.Events) {
|
||||||
defer events.Clear()
|
|
||||||
for evt, value := range *events {
|
for evt, value := range *events {
|
||||||
switch evt {
|
switch evt {
|
||||||
|
|
||||||
@@ -265,6 +264,7 @@ func Run(opts *Options, revision string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
events.Clear()
|
||||||
})
|
})
|
||||||
if delay && reading {
|
if delay && reading {
|
||||||
dur := util.DurWithin(
|
dur := util.DurWithin(
|
||||||
|
@@ -17,7 +17,7 @@ func assert(t *testing.T, cond bool, msg ...string) {
|
|||||||
|
|
||||||
func randResult() Result {
|
func randResult() Result {
|
||||||
str := fmt.Sprintf("%d", rand.Uint32())
|
str := fmt.Sprintf("%d", rand.Uint32())
|
||||||
chars := util.RunesToChars([]rune(str))
|
chars := util.ToChars([]byte(str))
|
||||||
chars.Index = rand.Int31()
|
chars.Index = rand.Int31()
|
||||||
return Result{item: &Item{text: chars}}
|
return Result{item: &Item{text: chars}}
|
||||||
}
|
}
|
||||||
|
@@ -301,7 +301,12 @@ func (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (*Result,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
||||||
input := p.prepareInput(item)
|
var input []Token
|
||||||
|
if len(p.nth) == 0 {
|
||||||
|
input = []Token{Token{text: &item.text, prefixLength: 0}}
|
||||||
|
} else {
|
||||||
|
input = p.transformInput(item)
|
||||||
|
}
|
||||||
if p.fuzzy {
|
if p.fuzzy {
|
||||||
return p.iter(p.fuzzyAlgo, input, p.caseSensitive, p.normalize, p.forward, p.text, withPos, slab)
|
return p.iter(p.fuzzyAlgo, input, p.caseSensitive, p.normalize, p.forward, p.text, withPos, slab)
|
||||||
}
|
}
|
||||||
@@ -309,7 +314,12 @@ func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, *[]int) {
|
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, *[]int) {
|
||||||
input := p.prepareInput(item)
|
var input []Token
|
||||||
|
if len(p.nth) == 0 {
|
||||||
|
input = []Token{Token{text: &item.text, prefixLength: 0}}
|
||||||
|
} else {
|
||||||
|
input = p.transformInput(item)
|
||||||
|
}
|
||||||
offsets := []Offset{}
|
offsets := []Offset{}
|
||||||
var totalScore int
|
var totalScore int
|
||||||
var allPos *[]int
|
var allPos *[]int
|
||||||
@@ -353,11 +363,7 @@ func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Of
|
|||||||
return offsets, totalScore, allPos
|
return offsets, totalScore, allPos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) prepareInput(item *Item) []Token {
|
func (p *Pattern) transformInput(item *Item) []Token {
|
||||||
if len(p.nth) == 0 {
|
|
||||||
return []Token{Token{text: &item.text, prefixLength: 0}}
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.transformed != nil {
|
if item.transformed != nil {
|
||||||
return *item.transformed
|
return *item.transformed
|
||||||
}
|
}
|
||||||
|
@@ -78,7 +78,7 @@ func TestExact(t *testing.T) {
|
|||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true,
|
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true,
|
||||||
[]Range{}, Delimiter{}, []rune("'abc"))
|
[]Range{}, Delimiter{}, []rune("'abc"))
|
||||||
res, pos := algo.ExactMatchNaive(
|
res, pos := algo.ExactMatchNaive(
|
||||||
pattern.caseSensitive, pattern.normalize, pattern.forward, util.RunesToChars([]rune("aabbcc abc")), pattern.termSets[0][0].text, true, nil)
|
pattern.caseSensitive, pattern.normalize, pattern.forward, util.ToChars([]byte("aabbcc abc")), pattern.termSets[0][0].text, true, nil)
|
||||||
if res.Start != 7 || res.End != 10 {
|
if res.Start != 7 || res.End != 10 {
|
||||||
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ func TestEqual(t *testing.T) {
|
|||||||
|
|
||||||
match := func(str string, sidxExpected int, eidxExpected int) {
|
match := func(str string, sidxExpected int, eidxExpected int) {
|
||||||
res, pos := algo.EqualMatch(
|
res, pos := algo.EqualMatch(
|
||||||
pattern.caseSensitive, pattern.normalize, pattern.forward, util.RunesToChars([]rune(str)), pattern.termSets[0][0].text, true, nil)
|
pattern.caseSensitive, pattern.normalize, pattern.forward, util.ToChars([]byte(str)), pattern.termSets[0][0].text, true, nil)
|
||||||
if res.Start != sidxExpected || res.End != eidxExpected {
|
if res.Start != sidxExpected || res.End != eidxExpected {
|
||||||
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
||||||
}
|
}
|
||||||
@@ -140,7 +140,7 @@ func TestOrigTextAndTransformed(t *testing.T) {
|
|||||||
for _, extended := range []bool{false, true} {
|
for _, extended := range []bool{false, true} {
|
||||||
chunk := Chunk{
|
chunk := Chunk{
|
||||||
Item{
|
Item{
|
||||||
text: util.RunesToChars([]rune("junegunn")),
|
text: util.ToChars([]byte("junegunn")),
|
||||||
origText: &origBytes,
|
origText: &origBytes,
|
||||||
transformed: &trans},
|
transformed: &trans},
|
||||||
}
|
}
|
||||||
|
@@ -101,6 +101,7 @@ type Terminal struct {
|
|||||||
printer func(string)
|
printer func(string)
|
||||||
merger *Merger
|
merger *Merger
|
||||||
selected map[int32]selectedItem
|
selected map[int32]selectedItem
|
||||||
|
version int64
|
||||||
reqBox *util.EventBox
|
reqBox *util.EventBox
|
||||||
preview previewOpts
|
preview previewOpts
|
||||||
previewer previewer
|
previewer previewer
|
||||||
@@ -712,7 +713,7 @@ func (t *Terminal) printHeader() {
|
|||||||
trimmed, colors, newState := extractColor(lineStr, state, nil)
|
trimmed, colors, newState := extractColor(lineStr, state, nil)
|
||||||
state = newState
|
state = newState
|
||||||
item := &Item{
|
item := &Item{
|
||||||
text: util.RunesToChars([]rune(trimmed)),
|
text: util.ToChars([]byte(trimmed)),
|
||||||
colors: colors}
|
colors: colors}
|
||||||
|
|
||||||
t.move(line, 2, true)
|
t.move(line, 2, true)
|
||||||
@@ -1173,8 +1174,7 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
for idx, item := range items {
|
for idx, item := range items {
|
||||||
chars := util.RunesToChars([]rune(item.AsString(stripAnsi)))
|
tokens := Tokenize(item.AsString(stripAnsi), delimiter)
|
||||||
tokens := Tokenize(chars.ToString(), delimiter)
|
|
||||||
trans := Transform(tokens, ranges)
|
trans := Transform(tokens, ranges)
|
||||||
str := string(joinTokens(trans))
|
str := string(joinTokens(trans))
|
||||||
if delimiter.str != nil {
|
if delimiter.str != nil {
|
||||||
@@ -1258,6 +1258,24 @@ func (t *Terminal) truncateQuery() {
|
|||||||
t.cx = util.Constrain(t.cx, 0, len(t.input))
|
t.cx = util.Constrain(t.cx, 0, len(t.input))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) selectItem(item *Item) {
|
||||||
|
t.selected[item.Index()] = selectedItem{time.Now(), item}
|
||||||
|
t.version++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) deselectItem(item *Item) {
|
||||||
|
delete(t.selected, item.Index())
|
||||||
|
t.version++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) toggleItem(item *Item) {
|
||||||
|
if _, found := t.selected[item.Index()]; !found {
|
||||||
|
t.selectItem(item)
|
||||||
|
} else {
|
||||||
|
t.deselectItem(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Loop is called to start Terminal I/O
|
// Loop is called to start Terminal I/O
|
||||||
func (t *Terminal) Loop() {
|
func (t *Terminal) Loop() {
|
||||||
// prof := profile.Start(profile.ProfilePath("/tmp/"))
|
// prof := profile.Start(profile.ProfilePath("/tmp/"))
|
||||||
@@ -1360,6 +1378,7 @@ func (t *Terminal) Loop() {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
var focused *Item
|
var focused *Item
|
||||||
|
var version int64
|
||||||
for {
|
for {
|
||||||
t.reqBox.Wait(func(events *util.Events) {
|
t.reqBox.Wait(func(events *util.Events) {
|
||||||
defer events.Clear()
|
defer events.Clear()
|
||||||
@@ -1376,7 +1395,8 @@ func (t *Terminal) Loop() {
|
|||||||
case reqList:
|
case reqList:
|
||||||
t.printList()
|
t.printList()
|
||||||
currentFocus := t.currentItem()
|
currentFocus := t.currentItem()
|
||||||
if currentFocus != focused {
|
if currentFocus != focused || version != t.version {
|
||||||
|
version = t.version
|
||||||
focused = currentFocus
|
focused = currentFocus
|
||||||
if t.isPreviewEnabled() {
|
if t.isPreviewEnabled() {
|
||||||
_, list := t.buildPlusList(t.preview.command, false)
|
_, list := t.buildPlusList(t.preview.command, false)
|
||||||
@@ -1442,22 +1462,9 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
selectItem := func(item *Item) bool {
|
|
||||||
if _, found := t.selected[item.Index()]; !found {
|
|
||||||
t.selected[item.Index()] = selectedItem{time.Now(), item}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
toggleY := func(y int) {
|
|
||||||
item := t.merger.Get(y).item
|
|
||||||
if !selectItem(item) {
|
|
||||||
delete(t.selected, item.Index())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toggle := func() {
|
toggle := func() {
|
||||||
if t.cy < t.merger.Length() {
|
if t.cy < t.merger.Length() {
|
||||||
toggleY(t.cy)
|
t.toggleItem(t.merger.Get(t.cy).item)
|
||||||
req(reqInfo)
|
req(reqInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1571,17 +1578,14 @@ func (t *Terminal) Loop() {
|
|||||||
case actSelectAll:
|
case actSelectAll:
|
||||||
if t.multi {
|
if t.multi {
|
||||||
for i := 0; i < t.merger.Length(); i++ {
|
for i := 0; i < t.merger.Length(); i++ {
|
||||||
item := t.merger.Get(i).item
|
t.selectItem(t.merger.Get(i).item)
|
||||||
selectItem(item)
|
|
||||||
}
|
}
|
||||||
req(reqList, reqInfo)
|
req(reqList, reqInfo)
|
||||||
}
|
}
|
||||||
case actDeselectAll:
|
case actDeselectAll:
|
||||||
if t.multi {
|
if t.multi {
|
||||||
for i := 0; i < t.merger.Length(); i++ {
|
t.selected = make(map[int32]selectedItem)
|
||||||
item := t.merger.Get(i)
|
t.version++
|
||||||
delete(t.selected, item.Index())
|
|
||||||
}
|
|
||||||
req(reqList, reqInfo)
|
req(reqList, reqInfo)
|
||||||
}
|
}
|
||||||
case actToggle:
|
case actToggle:
|
||||||
@@ -1592,7 +1596,7 @@ func (t *Terminal) Loop() {
|
|||||||
case actToggleAll:
|
case actToggleAll:
|
||||||
if t.multi {
|
if t.multi {
|
||||||
for i := 0; i < t.merger.Length(); i++ {
|
for i := 0; i < t.merger.Length(); i++ {
|
||||||
toggleY(i)
|
t.toggleItem(t.merger.Get(i).item)
|
||||||
}
|
}
|
||||||
req(reqList, reqInfo)
|
req(reqList, reqInfo)
|
||||||
}
|
}
|
||||||
|
@@ -10,7 +10,7 @@ import (
|
|||||||
func newItem(str string) *Item {
|
func newItem(str string) *Item {
|
||||||
bytes := []byte(str)
|
bytes := []byte(str)
|
||||||
trimmed, _, _ := extractColor(str, nil, nil)
|
trimmed, _, _ := extractColor(str, nil, nil)
|
||||||
return &Item{origText: &bytes, text: util.RunesToChars([]rune(trimmed))}
|
return &Item{origText: &bytes, text: util.ToChars([]byte(trimmed))}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReplacePlaceholder(t *testing.T) {
|
func TestReplacePlaceholder(t *testing.T) {
|
||||||
|
@@ -32,7 +32,8 @@ var offsetRegexp *regexp.Regexp = regexp.MustCompile("\x1b\\[([0-9]+);([0-9]+)R"
|
|||||||
func openTtyIn() *os.File {
|
func openTtyIn() *os.File {
|
||||||
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("Failed to open " + consoleDevice)
|
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
|
||||||
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
return in
|
return in
|
||||||
}
|
}
|
||||||
@@ -882,8 +883,8 @@ func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillRetu
|
|||||||
bg = w.bg
|
bg = w.bg
|
||||||
}
|
}
|
||||||
if w.csiColor(fg, bg, attr) {
|
if w.csiColor(fg, bg, attr) {
|
||||||
return w.fill(text, func() { w.csiColor(fg, bg, attr) })
|
|
||||||
defer w.csi("m")
|
defer w.csi("m")
|
||||||
|
return w.fill(text, func() { w.csiColor(fg, bg, attr) })
|
||||||
}
|
}
|
||||||
return w.fill(text, w.setBg)
|
return w.fill(text, w.setBg)
|
||||||
}
|
}
|
||||||
|
@@ -24,12 +24,12 @@ type Chars struct {
|
|||||||
|
|
||||||
func checkAscii(bytes []byte) (bool, int) {
|
func checkAscii(bytes []byte) (bool, int) {
|
||||||
i := 0
|
i := 0
|
||||||
for ; i < len(bytes)-8; i += 8 {
|
for ; i <= len(bytes)-8; i += 8 {
|
||||||
if (overflow64 & *(*uint64)(unsafe.Pointer(&bytes[i]))) > 0 {
|
if (overflow64 & *(*uint64)(unsafe.Pointer(&bytes[i]))) > 0 {
|
||||||
return false, i
|
return false, i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for ; i < len(bytes)-4; i += 4 {
|
for ; i <= len(bytes)-4; i += 4 {
|
||||||
if (overflow32 & *(*uint32)(unsafe.Pointer(&bytes[i]))) > 0 {
|
if (overflow32 & *(*uint32)(unsafe.Pointer(&bytes[i]))) > 0 {
|
||||||
return false, i
|
return false, i
|
||||||
}
|
}
|
||||||
@@ -65,6 +65,14 @@ func RunesToChars(runes []rune) Chars {
|
|||||||
return Chars{slice: *(*[]byte)(unsafe.Pointer(&runes)), inBytes: false}
|
return Chars{slice: *(*[]byte)(unsafe.Pointer(&runes)), inBytes: false}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) IsBytes() bool {
|
||||||
|
return chars.inBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) Bytes() []byte {
|
||||||
|
return chars.slice
|
||||||
|
}
|
||||||
|
|
||||||
func (chars *Chars) optionalRunes() []rune {
|
func (chars *Chars) optionalRunes() []rune {
|
||||||
if chars.inBytes {
|
if chars.inBytes {
|
||||||
return nil
|
return nil
|
||||||
|
@@ -26,23 +26,23 @@ func NewEventBox() *EventBox {
|
|||||||
// Wait blocks the goroutine until signaled
|
// Wait blocks the goroutine until signaled
|
||||||
func (b *EventBox) Wait(callback func(*Events)) {
|
func (b *EventBox) Wait(callback func(*Events)) {
|
||||||
b.cond.L.Lock()
|
b.cond.L.Lock()
|
||||||
defer b.cond.L.Unlock()
|
|
||||||
|
|
||||||
if len(b.events) == 0 {
|
if len(b.events) == 0 {
|
||||||
b.cond.Wait()
|
b.cond.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(&b.events)
|
callback(&b.events)
|
||||||
|
b.cond.L.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set turns on the event type on the box
|
// Set turns on the event type on the box
|
||||||
func (b *EventBox) Set(event EventType, value interface{}) {
|
func (b *EventBox) Set(event EventType, value interface{}) {
|
||||||
b.cond.L.Lock()
|
b.cond.L.Lock()
|
||||||
defer b.cond.L.Unlock()
|
|
||||||
b.events[event] = value
|
b.events[event] = value
|
||||||
if _, found := b.ignore[event]; !found {
|
if _, found := b.ignore[event]; !found {
|
||||||
b.cond.Broadcast()
|
b.cond.Broadcast()
|
||||||
}
|
}
|
||||||
|
b.cond.L.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear clears the events
|
// Clear clears the events
|
||||||
@@ -56,27 +56,27 @@ func (events *Events) Clear() {
|
|||||||
// Peek peeks at the event box if the given event is set
|
// Peek peeks at the event box if the given event is set
|
||||||
func (b *EventBox) Peek(event EventType) bool {
|
func (b *EventBox) Peek(event EventType) bool {
|
||||||
b.cond.L.Lock()
|
b.cond.L.Lock()
|
||||||
defer b.cond.L.Unlock()
|
|
||||||
_, ok := b.events[event]
|
_, ok := b.events[event]
|
||||||
|
b.cond.L.Unlock()
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watch deletes the events from the ignore list
|
// Watch deletes the events from the ignore list
|
||||||
func (b *EventBox) Watch(events ...EventType) {
|
func (b *EventBox) Watch(events ...EventType) {
|
||||||
b.cond.L.Lock()
|
b.cond.L.Lock()
|
||||||
defer b.cond.L.Unlock()
|
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
delete(b.ignore, event)
|
delete(b.ignore, event)
|
||||||
}
|
}
|
||||||
|
b.cond.L.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwatch adds the events to the ignore list
|
// Unwatch adds the events to the ignore list
|
||||||
func (b *EventBox) Unwatch(events ...EventType) {
|
func (b *EventBox) Unwatch(events ...EventType) {
|
||||||
b.cond.L.Lock()
|
b.cond.L.Lock()
|
||||||
defer b.cond.L.Unlock()
|
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
b.ignore[event] = true
|
b.ignore[event] = true
|
||||||
}
|
}
|
||||||
|
b.cond.L.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitFor blocks the execution until the event is received
|
// WaitFor blocks the execution until the event is received
|
||||||
|
@@ -1274,6 +1274,16 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| lines[-3] == '> 11' }
|
tmux.until { |lines| lines[-3] == '> 11' }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_preview_update_on_select
|
||||||
|
tmux.send_keys(%(seq 10 | fzf -m --preview 'echo {+}' --bind a:toggle-all),
|
||||||
|
:Enter)
|
||||||
|
tmux.until { |lines| lines.item_count == 10 }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| lines.any? { |line| line.include? '1 2 3 4 5' } }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| !lines.any? { |line| line.include? '1 2 3 4 5' } }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
|
Reference in New Issue
Block a user