m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-15 06:43:47 -05:00

Compare commits

...

42 Commits

Author SHA1 Message Date
Junegunn Choi
35a9aff8e1 0.12.1 2016-04-25 01:23:52 +09:00
Junegunn Choi
988c9bd9be [zsh] Fix issues with unicode characters 2016-04-25 01:04:35 +09:00
Junegunn Choi
095f31b316 [vim] Explicitly set source to FZF_DEFAULT_COMMAND
Helps when your `$SHELL` is slow.

Close #552.
2016-04-24 18:02:01 +09:00
Junegunn Choi
d86cee2a69 [bash] Export fzf-file-widget function for bash 4+ (#546)
e.g. Remapping fzf-file-widget to CTRL-X CTRL-T intead of CTRL-T

    bind -x '"\C-x\C-t": fzf-file-widget'
    bind '"\C-t": transpose-chars'
2016-04-24 14:04:15 +09:00
Junegunn Choi
e986f20a85 [fish] Use consistent function names for key bindings (#546)
- fzf-file-widget
- fzf-history-widget
- fzf-cd-widget
2016-04-24 13:56:50 +09:00
Junegunn Choi
c727ba1d99 [fzf-tmux] Do not split pane if the height is too small 2016-04-24 13:32:33 +09:00
Junegunn Choi
bb70923cd8 Fix flaky test cases 2016-04-24 04:52:01 +09:00
Junegunn Choi
772fa42dcb [fish] Fix intermittent errors on CTRL-T
Related: 23244bb
2016-04-24 04:51:35 +09:00
Junegunn Choi
85ef3263fc Fix incorrect cache reference in --exact mode (#547)
When we prepend a single quote to our query in --exact mode, we are not
supposed to limit the scope of the new search to the previous
exact-match result.
2016-04-24 03:43:24 +09:00
Junegunn Choi
4bde8de63f Apply new ranking algorithm to exact match as well 2016-04-23 19:48:06 +09:00
Junegunn Choi
654a7df9b0 [neovim] Set bufhidden and nobuflisted after opening terminal 2016-04-23 17:53:54 +09:00
Junegunn Choi
c3aa836ec0 [bash] Update completion.bash
[bash] Update completion.bash
2016-04-23 11:36:50 +09:00
Junegunn Choi
95764bef6f Merge pull request #550 from gene-pavlovsky/gene-pavlovsky-patch-2
[bash] Update key-bindings.bash
2016-04-23 11:35:27 +09:00
Gene Pavlovsky
63dbf48546 Update key-bindings.bash
Faster startup. Use internal bash globbing instead of external grep binary (adapter from Gentoo's `/etc/bash/bashrc` TERM checking). Insignificant on Linux, but on Cygwin this cuts startup time by 40 ms on my Core i7 laptop.
2016-04-23 03:44:41 +03:00
Gene Pavlovsky
e2401350a3 Update completion.bash
Fixes #548. Avoid using a subshell in _fzf_defc().
2016-04-23 03:12:15 +03:00
Junegunn Choi
e867355b2a [neovim] Restore winfixwidth and winfixheight
Fix https://github.com/junegunn/fzf.vim/issues/128
2016-04-21 00:33:30 +09:00
Junegunn Choi
b28c14b93a 0.12.0 2016-04-16 14:45:16 +09:00
Junegunn Choi
879ead210f 0.11.2 2016-04-16 14:37:16 +09:00
Junegunn Choi
2f6d23b91e Enhanced ranking algorithm
Based on the patch by Matt Westcott (@mjwestcott).
But with a more conservative approach:
- Does not use linearly increasing penalties; It is agreed upon that we
  should prefer matching characters at the beginnings of the words, but
  it's not always clear that the relevance is inversely proportional to
  the distance from the beginning.
- The approach here is more conservative in that the bonus is never
  large enough to override the matchlen, so it can be thought of as the
  first implicit tiebreak criterion.
- One may argue the change breaks the contract of --tiebreak, but the
  judgement depends on the definition of "tie".
2016-04-16 14:33:38 +09:00
Junegunn Choi
5f63a7b587 Fix flaky test case 2016-04-15 12:57:38 +09:00
Junegunn Choi
d9ce797d88 Merge pull request #540 from WChargin/bash-vimode-delay-fix
[bash] Fix vi mode pre-launch delay
2016-04-15 09:15:12 +09:00
William Chargin
12230f8043 Fix bash-vimode normal-mode cd completion 2016-04-14 13:20:21 -04:00
William Chargin
0c8de1ca44 Fix Bash+vimode pre-launch delay
Summary:
Fix adapted from [@adamheins: fzf, vi-mode, and fixing delays][1].

  [1]: https://adamheins.com/blog/fzf-vi-mode-and-fixing-delays

The basic problem is that
fzf presses <Esc> to enter vi-movement-mode
(as opposed to insert mode)
and then presses a bunch of keys to set up the buffer.
But the <Esc> keypress is also the prefix for a bunch of other commands,
so Bash will dutifully wait an excruciating half-second
before actually executing this command.
Instead, we bind <C-x><C-a>, which is unused by default
and seems reasonably unlikely to be custom-bound,
to be another way to enter vi-movement-mode;
this binding is unambiguous, so fzf can use it without delay.

This change was made by just `:s/\\e/\\C-x\\C-a/gc`
in the relevant section,
after adding the actual binding and comment at the top.
2016-04-14 13:19:05 -04:00
Junegunn Choi
89687105f4 [install] Ask before updating shell configuration files 2016-04-14 14:51:58 +09:00
Junegunn Choi
74d1694be9 Fix #541 - Print double-click when --expect=double-click is set 2016-04-14 04:18:59 +09:00
Junegunn Choi
935e986be5 [zsh] Remove unnecessary evals 2016-04-12 21:20:03 +09:00
Junegunn Choi
e5ac2ebd7c [vim] Escape $
https://github.com/junegunn/fzf.vim/issues/114
2016-04-09 21:44:07 +09:00
Junegunn Choi
8d6e13bf94 Merge pull request #535 from mjwestcott/master
Fix algorithm tests
2016-04-01 09:05:33 +09:00
Matt Westcott
2ca704405a Fix algorithm tests 2016-04-01 00:06:09 +01:00
Junegunn Choi
802c1c2937 Clean up install script
- Do not create zsh files if zsh is not installed (@adam8157)
- Use command -v instead of which (@netei)
- Reenable --pre option

Close #531
2016-03-29 22:30:55 +09:00
Junegunn Choi
3cb5fef6b6 Merge pull request #529 from mjwestcott/master
Fix typo in README.md
2016-03-28 23:21:14 +09:00
Matt Westcott
6da2e0aa1e Fix typo in README.md 2016-03-28 13:40:28 +01:00
Junegunn Choi
24f3ec7f33 Fix FZF_CTRL_R_OPTS for zsh (#526) 2016-03-23 03:02:57 +09:00
Junegunn Choi
a57b375b41 Add $FZF_CTRL_R_OPTS for overriding the default options for CTRL-R
Close #526
2016-03-23 03:00:20 +09:00
Junegunn Choi
6cc9d53978 [fzf-tmux] Fix invalid redirection 2016-03-15 21:21:21 +09:00
Junegunn Choi
df32c05833 [fzf-tmux] Fix issues on tmux 1.8 2016-03-15 21:18:15 +09:00
Junegunn Choi
c0652adf4c [fzf-tmux] tmux 1.6 compatibility
Patch submitted by @netei. Close #524.
2016-03-15 20:35:25 +09:00
Junegunn Choi
6ea760a336 Make 32-bit linux binary (partially) static (#523) 2016-03-15 20:17:29 +09:00
Junegunn Choi
f704b94603 [neovim] Open tab before current tab
Related: https://github.com/junegunn/gv.vim/issues/19
2016-03-06 13:56:29 +09:00
Junegunn Choi
444a67cafa Fix flaky test cases 2016-03-06 12:46:28 +09:00
Junegunn Choi
f91cbd5688 Add ISSUE_TEMPLATE.md
Close #500
2016-03-06 11:24:03 +09:00
Junegunn Choi
3073ca3e5a [neovim] Take total number of tab pages into account (#520)
This fixes the problem where a new tab page is not closed when the
following configuration is used:

  let g:fzf_layout = { 'window': 'execute (tabpagenr()-1)."tabnew"' }
2016-03-04 15:23:07 +09:00
22 changed files with 460 additions and 203 deletions

29
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,29 @@
<!-- Check all that apply [x] -->
- Category
- [ ] fzf binary
- [ ] fzf-tmux script
- [ ] Key bindings
- [ ] Completion
- [ ] Vim
- [ ] Neovim
- [ ] Etc.
- OS
- [ ] Linux
- [ ] Mac OS X
- [ ] Windows
- [ ] Etc.
- Shell
- [ ] bash
- [ ] zsh
- [ ] fish
<!--
### Before submitting
- Make sure that you have the latest version of fzf
- If you use tmux, make sure $TERM is set to screen or screen-256color
- For more Vim stuff, check out https://github.com/junegunn/fzf.vim
Describe your problem or suggestion from here ...
-->

View File

@@ -1,6 +1,19 @@
CHANGELOG CHANGELOG
========= =========
0.12.1
------
- Ranking algorithm introduced in 0.12.0 is now universally applied
- Fixed invalid cache reference in exact mode
- Fixes and improvements in Vim plugin and shell extensions
0.12.0
------
- Enhanced ranking algorithm
- Minor bug fixes
0.11.4 0.11.4
------ ------

View File

@@ -21,7 +21,7 @@ Pros
Installation Installation
------------ ------------
fzf project consists of the followings: fzf project consists of the following:
- `fzf` executable - `fzf` executable
- `fzf-tmux` script for launching fzf in a tmux pane - `fzf-tmux` script for launching fzf in a tmux pane
@@ -343,7 +343,7 @@ Tips
#### Rendering issues #### Rendering issues
If you have any rendering issues, check the followings: If you have any rendering issues, check the following:
1. Make sure `$TERM` is correctly set. fzf will use 256-color only if it 1. Make sure `$TERM` is correctly set. fzf will use 256-color only if it
contains `256` (e.g. `xterm-256color`) contains `256` (e.g. `xterm-256color`)

View File

@@ -8,6 +8,7 @@ skip=""
swap="" swap=""
close="" close=""
term="" term=""
[ -n "$LINES" ] && lines=$LINES || lines=$(tput lines)
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
arg="$1" arg="$1"
case "$arg" in case "$arg" in
@@ -60,7 +61,7 @@ while [ $# -gt 0 ]; do
if [[ "$arg" =~ ^.l ]]; then if [[ "$arg" =~ ^.l ]]; then
[ -n "$COLUMNS" ] && max=$COLUMNS || max=$(tput cols) [ -n "$COLUMNS" ] && max=$COLUMNS || max=$(tput cols)
else else
[ -n "$LINES" ] && max=$LINES || max=$(tput lines) max=$lines
fi fi
size=$(( max - size )) size=$(( max - size ))
[ $size -lt 0 ] && size=0 [ $size -lt 0 ] && size=0
@@ -82,7 +83,7 @@ while [ $# -gt 0 ]; do
shift shift
done done
if [ -z "$TMUX" ]; then if ! [ -n "$TMUX_PANE" -a $lines -gt 15 ]; then
fzf "${args[@]}" fzf "${args[@]}"
exit $? exit $?
fi fi
@@ -140,15 +141,17 @@ done
if [ -n "$term" -o -t 0 ]; then if [ -n "$term" -o -t 0 ]; then
cat <<< "$fzf $opts > $fifo2; echo \$? > $fifo3 $close" > $argsf cat <<< "$fzf $opts > $fifo2; echo \$? > $fifo3 $close" > $argsf
tmux set-window-option -q synchronize-panes off \;\ tmux set-window-option synchronize-panes off \;\
set-window-option -q remain-on-exit off \;\ set-window-option remain-on-exit off \;\
split-window $opt "cd $(printf %q "$PWD");$envs bash $argsf" $swap split-window $opt "cd $(printf %q "$PWD");$envs bash $argsf" $swap \
> /dev/null 2>&1
else else
mkfifo $fifo1 mkfifo $fifo1
cat <<< "$fzf $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" > $argsf cat <<< "$fzf $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" > $argsf
tmux set-window-option -q synchronize-panes off \;\ tmux set-window-option synchronize-panes off \;\
set-window-option -q remain-on-exit off \;\ set-window-option remain-on-exit off \;\
split-window $opt "$envs bash $argsf" $swap split-window $opt "$envs bash $argsf" $swap \
> /dev/null 2>&1
cat <&0 > $fifo1 & cat <&0 > $fifo1 &
fi fi
cat $fifo2 cat $fifo2

76
install
View File

@@ -2,12 +2,12 @@
set -u set -u
[[ "$@" =~ --pre ]] && version=0.11.4 pre=1 || [[ "$@" =~ --pre ]] && version=0.12.1 pre=1 ||
version=0.11.4 pre=0 version=0.12.1 pre=0
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=1 update_config=2
binary_arch= binary_arch=
help() { help() {
@@ -27,7 +27,7 @@ usage: $0 [OPTIONS]
EOF EOF
} }
for opt in $@; do for opt in "$@"; do
case $opt in case $opt in
--help) --help)
help help
@@ -46,7 +46,7 @@ for opt in $@; do
--no-update-rc) update_config=0 ;; --no-update-rc) update_config=0 ;;
--32) binary_arch=386 ;; --32) binary_arch=386 ;;
--64) binary_arch=amd64 ;; --64) binary_arch=amd64 ;;
--bin) ;; --bin|--pre) ;;
*) *)
echo "unknown option: $opt" echo "unknown option: $opt"
help help
@@ -55,17 +55,14 @@ for opt in $@; do
esac esac
done done
cd $(dirname $BASH_SOURCE) cd "$(dirname "${BASH_SOURCE[0]}")"
fzf_base="$(pwd)" fzf_base="$(pwd)"
# If stdin is a tty, we are "interactive".
interactive=
[ -t 0 ] && interactive=yes
ask() { ask() {
# If stdin is a tty, we are "interactive".
# non-interactive shell: wait for a linefeed # non-interactive shell: wait for a linefeed
# interactive shell: continue after a single keypress # interactive shell: continue after a single keypress
[ -n "$interactive" ] && read_n='-n 1' || read_n= read_n=$([ -t 0 ] && echo "-n 1")
read -p "$1 ([y]/n) " $read_n -r read -p "$1 ([y]/n) " $read_n -r
echo echo
@@ -103,7 +100,7 @@ symlink() {
} }
link_fzf_in_path() { link_fzf_in_path() {
if which_fzf="$(which fzf 2> /dev/null)"; then if which_fzf="$(command -v fzf)"; then
echo " - Found in \$PATH" echo " - Found in \$PATH"
echo " - Creating symlink: $which_fzf -> bin/fzf" echo " - Creating symlink: $which_fzf -> bin/fzf"
(cd "$fzf_base"/bin && rm -f fzf && ln -sf "$which_fzf" fzf) (cd "$fzf_base"/bin && rm -f fzf && ln -sf "$which_fzf" fzf)
@@ -131,9 +128,9 @@ download() {
fi fi
local url=https://github.com/junegunn/fzf-bin/releases/download/$version/${1}.tgz local url=https://github.com/junegunn/fzf-bin/releases/download/$version/${1}.tgz
if which curl > /dev/null; then if command -v curl > /dev/null; then
curl -fL $url | tar -xz curl -fL $url | tar -xz
elif which wget > /dev/null; then elif command -v wget > /dev/null; then
wget -O - $url | tar -xz wget -O - $url | tar -xz
else else
binary_error="curl or wget not found" binary_error="curl or wget not found"
@@ -165,7 +162,7 @@ install_ruby_fzf() {
# ruby executable # ruby executable
echo -n "Checking Ruby executable ... " echo -n "Checking Ruby executable ... "
ruby=`which ruby` ruby=$(command -v ruby)
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "ruby executable not found !!!" echo "ruby executable not found !!!"
exit 1 exit 1
@@ -173,7 +170,7 @@ install_ruby_fzf() {
# System ruby is preferred # System ruby is preferred
system_ruby=/usr/bin/ruby system_ruby=/usr/bin/ruby
if [ -x $system_ruby -a $system_ruby != "$ruby" ]; then if [ -x $system_ruby ] && [ $system_ruby != "$ruby" ]; then
$system_ruby --disable-gems -rcurses -e0 2> /dev/null $system_ruby --disable-gems -rcurses -e0 2> /dev/null
[ $? -eq 0 ] && ruby=$system_ruby [ $? -eq 0 ] && ruby=$system_ruby
fi fi
@@ -232,7 +229,7 @@ cd "$fzf_base"
if [ -n "$binary_error" ]; then if [ -n "$binary_error" ]; then
if [ $binary_available -eq 0 ]; then if [ $binary_available -eq 0 ]; then
echo "No prebuilt binary for $archi ..." echo "No prebuilt binary for $archi ..."
if which go > /dev/null 2>&1; then if command -v go > /dev/null; then
echo -n "Building binary (go get github.com/junegunn/fzf/src/fzf) ... " echo -n "Building binary (go get github.com/junegunn/fzf/src/fzf) ... "
if go get github.com/junegunn/fzf/src/fzf; then if go get github.com/junegunn/fzf/src/fzf; then
echo "OK" echo "OK"
@@ -266,7 +263,9 @@ if [ -z "$key_bindings" ]; then
fi fi
echo echo
for shell in bash zsh; do has_zsh=$(command -v zsh > /dev/null && echo 1 || echo 0)
shells=$([ $has_zsh -eq 1 ] && echo "bash zsh" || echo "bash")
for shell in $shells; do
echo -n "Generate ~/.fzf.$shell ... " echo -n "Generate ~/.fzf.$shell ... "
src=~/.fzf.${shell} src=~/.fzf.${shell}
@@ -306,9 +305,8 @@ EOF
done done
# fish # fish
has_fish=0 has_fish=$(command -v fish > /dev/null && echo 1 || echo 0)
if [ -n "$(which fish 2> /dev/null)" ]; then if [ $has_fish -eq 1 ]; then
has_fish=1
echo -n "Update fish_user_paths ... " echo -n "Update fish_user_paths ... "
fish << EOF fish << EOF
echo \$fish_user_paths | grep $fzf_base/bin > /dev/null echo \$fish_user_paths | grep $fzf_base/bin > /dev/null
@@ -337,8 +335,8 @@ fi
append_line() { append_line() {
set -e set -e
local skip line file pat lno local update line file pat lno
skip="$1" update="$1"
line="$2" line="$2"
file="$3" file="$3"
pat="${4:-}" pat="${4:-}"
@@ -354,7 +352,7 @@ append_line() {
if [ -n "$lno" ]; then if [ -n "$lno" ]; then
echo " - Already exists: line #$lno" echo " - Already exists: line #$lno"
else else
if [ $skip -eq 1 ]; then if [ $update -eq 1 ]; then
echo >> "$file" echo >> "$file"
echo "$line" >> "$file" echo "$line" >> "$file"
echo " + Added" echo " + Added"
@@ -366,26 +364,30 @@ append_line() {
set +e set +e
} }
if [ $update_config -eq 2 ]; then
echo echo
for shell in bash zsh; do ask "Do you want to update your shell configuration files?"
update_config=$?
fi
echo
for shell in $shells; do
[ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc [ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc
append_line $update_config "[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" "$dest" "~/.fzf.${shell}" append_line $update_config "[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" "$dest" "~/.fzf.${shell}"
done done
if [ $key_bindings -eq 1 -a $has_fish -eq 1 ]; then if [ $key_bindings -eq 1 ] && [ $has_fish -eq 1 ]; then
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
append_line $update_config "fzf_key_bindings" "$bind_file" append_line $update_config "fzf_key_bindings" "$bind_file"
fi fi
cat << EOF if [ $update_config -eq 1 ]; then
Finished. Restart your shell or reload config file. echo 'Finished. Restart your shell or reload config file.'
source ~/.bashrc # bash echo ' source ~/.bashrc # bash'
source ${ZDOTDIR:-~}/.zshrc # zsh [ $has_zsh -eq 1 ] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh"
EOF [ $has_fish -eq 1 ] && [ $key_bindings -eq 1 ] && echo ' fzf_key_bindings # fish'
[ $has_fish -eq 1 ] && echo " fzf_key_bindings # fish"; cat << EOF echo
echo 'Use uninstall script to remove fzf.'
Use uninstall script to remove fzf. echo
fi
For more information, see: https://github.com/junegunn/fzf echo 'For more information, see: https://github.com/junegunn/fzf'
EOF

View File

@@ -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 "Mar 2016" "fzf 0.11.4" "fzf - a command-line fuzzy finder" .TH fzf 1 "Apr 2016" "fzf 0.12.1" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder

View File

@@ -74,7 +74,7 @@ function! s:shellesc(arg)
endfunction endfunction
function! s:escape(path) function! s:escape(path)
return escape(a:path, ' %#''"\') return escape(a:path, ' $%#''"\')
endfunction endfunction
" Upgrade legacy options " Upgrade legacy options
@@ -121,6 +121,10 @@ try
throw v:exception throw v:exception
endtry endtry
if !has_key(dict, 'source') && !empty($FZF_DEFAULT_COMMAND)
let dict.source = $FZF_DEFAULT_COMMAND
endif
if has_key(dict, 'source') if has_key(dict, 'source')
let source = dict.source let source = dict.source
let type = type(source) let type = type(source)
@@ -285,7 +289,7 @@ function! s:calc_size(max, val, dict)
endfunction endfunction
function! s:getpos() function! s:getpos()
return {'tab': tabpagenr(), 'win': winnr(), 'cnt': winnr('$')} return {'tab': tabpagenr(), 'win': winnr(), 'cnt': winnr('$'), 'tcnt': tabpagenr('$')}
endfunction endfunction
function! s:split(dict) function! s:split(dict)
@@ -307,28 +311,33 @@ function! s:split(dict)
endif endif
execute cmd sz.'new' execute cmd sz.'new'
execute resz sz execute resz sz
return return {}
endif endif
endfor endfor
if s:present(a:dict, 'window') if s:present(a:dict, 'window')
execute a:dict.window execute a:dict.window
else else
tabnew execute (tabpagenr()-1).'tabnew'
endif endif
return { '&l:wfw': &l:wfw, '&l:wfh': &l:wfh }
finally finally
setlocal winfixwidth winfixheight buftype=nofile bufhidden=wipe nobuflisted setlocal winfixwidth winfixheight
endtry endtry
endfunction endfunction
function! s:execute_term(dict, command, temps) abort function! s:execute_term(dict, command, temps) abort
call s:split(a:dict) let winopts = s:split(a:dict)
let fzf = { 'buf': bufnr('%'), 'dict': a:dict, 'temps': a:temps, 'name': 'FZF' } let fzf = { 'buf': bufnr('%'), 'dict': a:dict, 'temps': a:temps, 'name': 'FZF', 'winopts': winopts }
let s:command = a:command let s:command = a:command
function! fzf.on_exit(id, code) function! fzf.on_exit(id, code)
let pos = s:getpos() let pos = s:getpos()
let inplace = pos == s:ppos " {'window': 'enew'} let inplace = pos == s:ppos " {'window': 'enew'}
if !inplace if inplace
for [opt, val] in items(self.winopts)
execute 'let' opt '=' val
endfor
else
if bufnr('') == self.buf if bufnr('') == self.buf
" We use close instead of bd! since Vim does not close the split when " We use close instead of bd! since Vim does not close the split when
" there's no other listed buffer (nvim +'set nobuflisted') " there's no other listed buffer (nvim +'set nobuflisted')
@@ -363,7 +372,7 @@ function! s:execute_term(dict, command, temps) abort
call s:pushd(a:dict) call s:pushd(a:dict)
call termopen(a:command, fzf) call termopen(a:command, fzf)
call s:popd(a:dict, []) call s:popd(a:dict, [])
setlocal nospell setlocal nospell bufhidden=wipe nobuflisted
setf fzf setf fzf
startinsert startinsert
return [] return []

View File

@@ -272,14 +272,15 @@ if type _completion_loader > /dev/null 2>&1; then
fi fi
_fzf_defc() { _fzf_defc() {
local cmd func opts orig_var orig local cmd func opts orig_var orig def
cmd="$1" cmd="$1"
func="$2" func="$2"
opts="$3" opts="$3"
orig_var="_fzf_orig_completion_$cmd" orig_var="_fzf_orig_completion_$cmd"
orig="${!orig_var}" orig="${!orig_var}"
if [ -n "$orig" ]; then if [ -n "$orig" ]; then
eval "$(printf "$orig" "$func")" printf -v def "$orig" "$func"
eval "$def"
else else
complete -F "$func" $opts "$cmd" complete -F "$func" $opts "$cmd"
fi fi

View File

@@ -54,7 +54,7 @@ __fzf_generic_path_completion() {
[ "$dir" != "/" ] && dir="${dir/%\//}" [ "$dir" != "/" ] && dir="${dir/%\//}"
dir=${~dir} dir=${~dir}
matches=$(eval "$compgen $(printf %q "$dir")" | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=fzf_opts} -q "$leftover" | while read item; do matches=$(eval "$compgen $(printf %q "$dir")" | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=fzf_opts} -q "$leftover" | while read item; do
printf "%q$suffix " "$item" echo -n "${(q)item}$suffix "
done) done)
matches=${matches% } matches=${matches% }
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
@@ -145,7 +145,7 @@ fzf-completion() {
# http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags
tokens=(${(z)LBUFFER}) tokens=(${(z)LBUFFER})
if [ ${#tokens} -lt 1 ]; then if [ ${#tokens} -lt 1 ]; then
eval "zle ${fzf_default_completion:-expand-or-complete}" zle ${fzf_default_completion:-expand-or-complete}
return return
fi fi
@@ -180,7 +180,7 @@ fzf-completion() {
fi fi
# Fall back to default completion # Fall back to default completion
else else
eval "zle ${fzf_default_completion:-expand-or-complete}" zle ${fzf_default_completion:-expand-or-complete}
fi fi
} }

View File

@@ -29,11 +29,13 @@ __fzf_select_tmux__() {
tmux split-window $height "cd $(printf %q "$PWD"); FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS") PATH=$(printf %q "$PATH") FZF_CTRL_T_COMMAND=$(printf %q "$FZF_CTRL_T_COMMAND") bash -c 'source \"${BASH_SOURCE[0]}\"; tmux send-keys -t $TMUX_PANE \"\$(__fzf_select__)\"'" tmux split-window $height "cd $(printf %q "$PWD"); FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS") PATH=$(printf %q "$PATH") FZF_CTRL_T_COMMAND=$(printf %q "$FZF_CTRL_T_COMMAND") bash -c 'source \"${BASH_SOURCE[0]}\"; tmux send-keys -t $TMUX_PANE \"\$(__fzf_select__)\"'"
} }
__fzf_select_tmux_auto__() { fzf-file-widget() {
if [ "${FZF_TMUX:-1}" != 0 ] && [ ${LINES:-40} -gt 15 ]; then if __fzf_use_tmux__; then
__fzf_select_tmux__ __fzf_select_tmux__
else else
tmux send-keys -t "$TMUX_PANE" "$(__fzf_select__)" local selected="$(__fzf_select__)"
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
fi fi
} }
@@ -49,7 +51,7 @@ __fzf_history__() (
shopt -u nocaseglob nocasematch shopt -u nocaseglob nocasematch
line=$( line=$(
HISTTIMEFORMAT= history | HISTTIMEFORMAT= history |
$(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r | $(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS |
\grep '^ *[0-9]') && \grep '^ *[0-9]') &&
if [[ $- =~ H ]]; then if [[ $- =~ H ]]; then
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line" sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
@@ -58,21 +60,21 @@ __fzf_history__() (
fi fi
) )
__use_tmux=0 __fzf_use_tmux__() {
__use_tmux_auto=0 [ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-1}" != 0 ] && [ ${LINES:-40} -gt 15 ]
if [ -n "$TMUX_PANE" ]; then }
[ "${FZF_TMUX:-1}" != 0 ] && [ ${LINES:-40} -gt 15 ] && __use_tmux=1
[ $BASH_VERSINFO -gt 3 ] && __use_tmux_auto=1
fi
if [ -z "$(set -o | \grep '^vi.*on')" ]; then [ $BASH_VERSINFO -gt 3 ] && __use_bind_x=1 || __use_bind_x=0
__fzf_use_tmux__ && __use_tmux=1 || __use_tmux=0
if [[ $'\n'$(set -o) != *$'\n'vi*on* ]]; then
# Required to refresh the prompt after fzf # Required to refresh the prompt after fzf
bind '"\er": redraw-current-line' bind '"\er": redraw-current-line'
bind '"\e^": history-expand-line' bind '"\e^": history-expand-line'
# CTRL-T - Paste the selected file path into the command line # CTRL-T - Paste the selected file path into the command line
if [ $__use_tmux_auto -eq 1 ]; then if [ $__use_bind_x -eq 1 ]; then
bind -x '"\C-t": "__fzf_select_tmux_auto__"' bind -x '"\C-t": "fzf-file-widget"'
elif [ $__use_tmux -eq 1 ]; then elif [ $__use_tmux -eq 1 ]; then
bind '"\C-t": " \C-u \C-a\C-k$(__fzf_select_tmux__)\e\C-e\C-y\C-a\C-d\C-y\ey\C-h"' bind '"\C-t": " \C-u \C-a\C-k$(__fzf_select_tmux__)\e\C-e\C-y\C-a\C-d\C-y\ey\C-h"'
else else
@@ -85,30 +87,39 @@ if [ -z "$(set -o | \grep '^vi.*on')" ]; then
# ALT-C - cd into the selected directory # ALT-C - cd into the selected directory
bind '"\ec": " \C-e\C-u$(__fzf_cd__)\e\C-e\er\C-m"' bind '"\ec": " \C-e\C-u$(__fzf_cd__)\e\C-e\er\C-m"'
else else
# We'd usually use "\e" to enter vi-movement-mode so we can do our magic,
# but this incurs a very noticeable delay of a half second or so,
# because many other commands start with "\e".
# Instead, we bind an unused key, "\C-x\C-a",
# to also enter vi-movement-mode,
# and then use that thereafter.
# (We imagine that "\C-x\C-a" is relatively unlikely to be in use.)
bind '"\C-x\C-a": vi-movement-mode'
bind '"\C-x\C-e": shell-expand-line' bind '"\C-x\C-e": shell-expand-line'
bind '"\C-x\C-r": redraw-current-line' bind '"\C-x\C-r": redraw-current-line'
bind '"\C-x^": history-expand-line' bind '"\C-x^": history-expand-line'
# CTRL-T - Paste the selected file path into the command line # CTRL-T - Paste the selected file path into the command line
# - FIXME: Selected items are attached to the end regardless of cursor position # - FIXME: Selected items are attached to the end regardless of cursor position
if [ $__use_tmux_auto -eq 1 ]; then if [ $__use_bind_x -eq 1 ]; then
bind -x '"\C-t": "__fzf_select_tmux_auto__"' bind -x '"\C-t": "fzf-file-widget"'
elif [ $__use_tmux -eq 1 ]; then elif [ $__use_tmux -eq 1 ]; then
bind '"\C-t": "\e$a \eddi$(__fzf_select_tmux__)\C-x\C-e\e0P$xa"' bind '"\C-t": "\C-x\C-a$a \C-x\C-addi$(__fzf_select_tmux__)\C-x\C-e\C-x\C-a0P$xa"'
else else
bind '"\C-t": "\e$a \eddi$(__fzf_select__)\C-x\C-e\e0Px$a \C-x\C-r\exa "' bind '"\C-t": "\C-x\C-a$a \C-x\C-addi$(__fzf_select__)\C-x\C-e\C-x\C-a0Px$a \C-x\C-r\C-x\C-axa "'
fi fi
bind -m vi-command '"\C-t": "i\C-t"' bind -m vi-command '"\C-t": "i\C-t"'
# CTRL-R - Paste the selected command from history into the command line # CTRL-R - Paste the selected command from history into the command line
bind '"\C-r": "\eddi$(__fzf_history__)\C-x\C-e\C-x^\e$a\C-x\C-r"' bind '"\C-r": "\C-x\C-addi$(__fzf_history__)\C-x\C-e\C-x^\C-x\C-a$a\C-x\C-r"'
bind -m vi-command '"\C-r": "i\C-r"' bind -m vi-command '"\C-r": "i\C-r"'
# ALT-C - cd into the selected directory # ALT-C - cd into the selected directory
bind '"\ec": "\eddi$(__fzf_cd__)\C-x\C-e\C-x\C-r\C-m"' bind '"\ec": "\C-x\C-addi$(__fzf_cd__)\C-x\C-e\C-x\C-r\C-m"'
bind -m vi-command '"\ec": "i\ec"' bind -m vi-command '"\ec": "ddi$(__fzf_cd__)\C-x\C-e\C-x\C-r\C-m"'
fi fi
unset -v __use_tmux __use_tmux_auto unset -v __use_tmux __use_bind_x
fi fi

View File

@@ -13,27 +13,26 @@ function fzf_key_bindings
end end
end end
function __fzf_ctrl_t function fzf-file-widget
set -q FZF_CTRL_T_COMMAND; or set -l FZF_CTRL_T_COMMAND " set -q FZF_CTRL_T_COMMAND; or set -l FZF_CTRL_T_COMMAND "
command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \ command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
-o -type f -print \ -o -type f -print \
-o -type d -print \ -o -type d -print \
-o -type l -print 2> /dev/null | sed 1d | cut -b3-" -o -type l -print 2> /dev/null | sed 1d | cut -b3-"
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)" -m > $TMPDIR/fzf.result" eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)" -m > $TMPDIR/fzf.result"
and sleep 0 and for i in (seq 20); commandline -i (cat $TMPDIR/fzf.result | __fzf_escape) 2> /dev/null; and break; sleep 0.1; end
and commandline -i (cat $TMPDIR/fzf.result | __fzf_escape)
commandline -f repaint commandline -f repaint
rm -f $TMPDIR/fzf.result rm -f $TMPDIR/fzf.result
end end
function __fzf_ctrl_r function fzf-history-widget
history | eval (__fzfcmd) +s +m --tiebreak=index --toggle-sort=ctrl-r > $TMPDIR/fzf.result history | eval (__fzfcmd) +s +m --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS > $TMPDIR/fzf.result
and commandline (cat $TMPDIR/fzf.result) and commandline (cat $TMPDIR/fzf.result)
commandline -f repaint commandline -f repaint
rm -f $TMPDIR/fzf.result rm -f $TMPDIR/fzf.result
end end
function __fzf_alt_c function fzf-cd-widget
set -q FZF_ALT_C_COMMAND; or set -l FZF_ALT_C_COMMAND " set -q FZF_ALT_C_COMMAND; or set -l FZF_ALT_C_COMMAND "
command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \ command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | sed 1d | cut -b3-" -o -type d -print 2> /dev/null | sed 1d | cut -b3-"
@@ -59,14 +58,14 @@ function fzf_key_bindings
end end
end end
bind \ct '__fzf_ctrl_t' bind \ct fzf-file-widget
bind \cr '__fzf_ctrl_r' bind \cr fzf-history-widget
bind \ec '__fzf_alt_c' bind \ec fzf-cd-widget
if bind -M insert > /dev/null 2>&1 if bind -M insert > /dev/null 2>&1
bind -M insert \ct '__fzf_ctrl_t' bind -M insert \ct fzf-file-widget
bind -M insert \cr '__fzf_ctrl_r' bind -M insert \cr fzf-history-widget
bind -M insert \ec '__fzf_alt_c' bind -M insert \ec fzf-cd-widget
end end
end end

View File

@@ -9,7 +9,7 @@ __fsel() {
-o -type d -print \ -o -type d -print \
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"}" -o -type l -print 2> /dev/null | sed 1d | cut -b3-"}"
eval "$cmd" | $(__fzfcmd) -m | while read item; do eval "$cmd" | $(__fzfcmd) -m | while read item; do
printf '%q ' "$item" echo -n "${(q)item} "
done done
echo echo
} }
@@ -38,7 +38,7 @@ bindkey '\ec' fzf-cd-widget
# CTRL-R - Paste the selected command from history into the command line # CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() { fzf-history-widget() {
local selected num local selected num
selected=( $(fc -l 1 | $(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r -q "${LBUFFER//$/\\$}") ) selected=( $(fc -l 1 | $(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r ${=FZF_CTRL_R_OPTS} -q "${LBUFFER//$/\\$}") )
if [ -n "$selected" ]; then if [ -n "$selected" ]; then
num=$selected[1] num=$selected[1]
if [ -n "$num" ]; then if [ -n "$num" ]; then

View File

@@ -16,6 +16,10 @@ RUN cd / && curl \
https://storage.googleapis.com/golang/go1.5.3.linux-amd64.tar.gz | \ https://storage.googleapis.com/golang/go1.5.3.linux-amd64.tar.gz | \
tar -xz && mv go go1.5 tar -xz && mv go go1.5
# Install RPMs for building static 32-bit binary
RUN curl ftp://ftp.pbone.net/mirror/ftp.centos.org/6.7/os/i386/Packages/ncurses-static-5.7-4.20090207.el6.i686.rpm -o rpm && rpm -i rpm && \
curl ftp://ftp.pbone.net/mirror/ftp.centos.org/6.7/os/i386/Packages/gpm-static-1.20.6-12.el6.i686.rpm -o rpm && rpm -i rpm
ENV GOROOT_BOOTSTRAP /go1.4 ENV GOROOT_BOOTSTRAP /go1.4
ENV GOROOT /go1.5 ENV GOROOT /go1.5
ENV PATH /go1.5/bin:$PATH ENV PATH /go1.5/bin:$PATH

View File

@@ -60,7 +60,7 @@ clean:
cd fzf && rm -f fzf-* cd fzf && rm -f fzf-*
fzf/$(BINARY32): deps fzf/$(BINARY32): deps
cd fzf && GOARCH=386 CGO_ENABLED=1 go build -a -o $(BINARY32) cd fzf && GOARCH=386 CGO_ENABLED=1 go build -a -tags "$(TAGS)" -o $(BINARY32)
fzf/$(BINARY64): deps fzf/$(BINARY64): deps
cd fzf && go build -a -tags "$(TAGS)" -o $(BINARY64) cd fzf && go build -a -tags "$(TAGS)" -o $(BINARY64)

View File

@@ -22,10 +22,94 @@ func runeAt(runes []rune, index int, max int, forward bool) rune {
return runes[max-index-1] return runes[max-index-1]
} }
// Result conatins the results of running a match function.
type Result struct {
Start int32
End int32
// Items are basically sorted by the lengths of matched substrings.
// But we slightly adjust the score with bonus for better results.
Bonus int32
}
type charClass int
const (
charNonWord charClass = iota
charLower
charUpper
charLetter
charNumber
)
func evaluateBonus(caseSensitive bool, runes []rune, pattern []rune, sidx int, eidx int) int32 {
var bonus int32
pidx := 0
lenPattern := len(pattern)
consecutive := false
prevClass := charNonWord
for index := 0; index < eidx; index++ {
char := runes[index]
var class charClass
if unicode.IsLower(char) {
class = charLower
} else if unicode.IsUpper(char) {
class = charUpper
} else if unicode.IsLetter(char) {
class = charLetter
} else if unicode.IsNumber(char) {
class = charNumber
} else {
class = charNonWord
}
var point int32
if prevClass == charNonWord && class != charNonWord {
// Word boundary
point = 2
} else if prevClass == charLower && class == charUpper ||
prevClass != charNumber && class == charNumber {
// camelCase letter123
point = 1
}
prevClass = class
if index >= sidx {
if !caseSensitive {
if char >= 'A' && char <= 'Z' {
char += 32
} else if char > unicode.MaxASCII {
char = unicode.To(unicode.LowerCase, char)
}
}
pchar := pattern[pidx]
if pchar == char {
// Boost bonus for the first character in the pattern
if pidx == 0 {
point *= 2
}
// Bonus to consecutive matching chars
if consecutive {
point++
}
bonus += point
if pidx++; pidx == lenPattern {
break
}
consecutive = true
} else {
consecutive = false
}
}
}
return bonus
}
// FuzzyMatch performs fuzzy-match // FuzzyMatch performs fuzzy-match
func FuzzyMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune) (int, int) { func FuzzyMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune) Result {
if len(pattern) == 0 { if len(pattern) == 0 {
return 0, 0 return Result{0, 0, 0}
} }
// 0. (FIXME) How to find the shortest match? // 0. (FIXME) How to find the shortest match?
@@ -90,12 +174,17 @@ func FuzzyMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune)
} }
} }
} }
if forward {
return sidx, eidx // Calculate the bonus. This can't be done at the same time as the
// pattern scan above because 'forward' may be false.
if !forward {
sidx, eidx = lenRunes-eidx, lenRunes-sidx
} }
return lenRunes - eidx, lenRunes - sidx
return Result{int32(sidx), int32(eidx),
evaluateBonus(caseSensitive, runes, pattern, sidx, eidx)}
} }
return -1, -1 return Result{-1, -1, 0}
} }
// ExactMatchNaive is a basic string searching algorithm that handles case // ExactMatchNaive is a basic string searching algorithm that handles case
@@ -105,16 +194,16 @@ func FuzzyMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune)
// //
// We might try to implement better algorithms in the future: // We might try to implement better algorithms in the future:
// http://en.wikipedia.org/wiki/String_searching_algorithm // http://en.wikipedia.org/wiki/String_searching_algorithm
func ExactMatchNaive(caseSensitive bool, forward bool, runes []rune, pattern []rune) (int, int) { func ExactMatchNaive(caseSensitive bool, forward bool, runes []rune, pattern []rune) Result {
if len(pattern) == 0 { if len(pattern) == 0 {
return 0, 0 return Result{0, 0, 0}
} }
lenRunes := len(runes) lenRunes := len(runes)
lenPattern := len(pattern) lenPattern := len(pattern)
if lenRunes < lenPattern { if lenRunes < lenPattern {
return -1, -1 return Result{-1, -1, 0}
} }
pidx := 0 pidx := 0
@@ -131,23 +220,29 @@ func ExactMatchNaive(caseSensitive bool, forward bool, runes []rune, pattern []r
if pchar == char { if pchar == char {
pidx++ pidx++
if pidx == lenPattern { if pidx == lenPattern {
var sidx, eidx int
if forward { if forward {
return index - lenPattern + 1, index + 1 sidx = index - lenPattern + 1
eidx = index + 1
} else {
sidx = lenRunes - (index + 1)
eidx = lenRunes - (index - lenPattern + 1)
} }
return lenRunes - (index + 1), lenRunes - (index - lenPattern + 1) return Result{int32(sidx), int32(eidx),
evaluateBonus(caseSensitive, runes, pattern, sidx, eidx)}
} }
} else { } else {
index -= pidx index -= pidx
pidx = 0 pidx = 0
} }
} }
return -1, -1 return Result{-1, -1, 0}
} }
// PrefixMatch performs prefix-match // PrefixMatch performs prefix-match
func PrefixMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune) (int, int) { func PrefixMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune) Result {
if len(runes) < len(pattern) { if len(runes) < len(pattern) {
return -1, -1 return Result{-1, -1, 0}
} }
for index, r := range pattern { for index, r := range pattern {
@@ -156,19 +251,21 @@ func PrefixMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune)
char = unicode.ToLower(char) char = unicode.ToLower(char)
} }
if char != r { if char != r {
return -1, -1 return Result{-1, -1, 0}
} }
} }
return 0, len(pattern) lenPattern := len(pattern)
return Result{0, int32(lenPattern),
evaluateBonus(caseSensitive, runes, pattern, 0, lenPattern)}
} }
// SuffixMatch performs suffix-match // SuffixMatch performs suffix-match
func SuffixMatch(caseSensitive bool, forward bool, input []rune, pattern []rune) (int, int) { func SuffixMatch(caseSensitive bool, forward bool, input []rune, pattern []rune) Result {
runes := util.TrimRight(input) runes := util.TrimRight(input)
trimmedLen := len(runes) trimmedLen := len(runes)
diff := trimmedLen - len(pattern) diff := trimmedLen - len(pattern)
if diff < 0 { if diff < 0 {
return -1, -1 return Result{-1, -1, 0}
} }
for index, r := range pattern { for index, r := range pattern {
@@ -177,23 +274,28 @@ func SuffixMatch(caseSensitive bool, forward bool, input []rune, pattern []rune)
char = unicode.ToLower(char) char = unicode.ToLower(char)
} }
if char != r { if char != r {
return -1, -1 return Result{-1, -1, 0}
} }
} }
return trimmedLen - len(pattern), trimmedLen lenPattern := len(pattern)
sidx := trimmedLen - lenPattern
eidx := trimmedLen
return Result{int32(sidx), int32(eidx),
evaluateBonus(caseSensitive, runes, pattern, sidx, eidx)}
} }
// EqualMatch performs equal-match // EqualMatch performs equal-match
func EqualMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune) (int, int) { func EqualMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune) Result {
// Note: EqualMatch always return a zero bonus.
if len(runes) != len(pattern) { if len(runes) != len(pattern) {
return -1, -1 return Result{-1, -1, 0}
} }
runesStr := string(runes) runesStr := string(runes)
if !caseSensitive { if !caseSensitive {
runesStr = strings.ToLower(runesStr) runesStr = strings.ToLower(runesStr)
} }
if runesStr == string(pattern) { if runesStr == string(pattern) {
return 0, len(pattern) return Result{0, int32(len(pattern)), 0}
} }
return -1, -1 return Result{-1, -1, 0}
} }

View File

@@ -5,65 +5,91 @@ import (
"testing" "testing"
) )
func assertMatch(t *testing.T, fun func(bool, bool, []rune, []rune) (int, int), caseSensitive bool, forward bool, input string, pattern string, sidx int, eidx int) { func assertMatch(t *testing.T, fun func(bool, bool, []rune, []rune) Result, caseSensitive, forward bool, input, pattern string, sidx int32, eidx int32, bonus int32) {
if !caseSensitive { if !caseSensitive {
pattern = strings.ToLower(pattern) pattern = strings.ToLower(pattern)
} }
s, e := fun(caseSensitive, forward, []rune(input), []rune(pattern)) res := fun(caseSensitive, forward, []rune(input), []rune(pattern))
if s != sidx { if res.Start != sidx {
t.Errorf("Invalid start index: %d (expected: %d, %s / %s)", s, sidx, input, pattern) t.Errorf("Invalid start index: %d (expected: %d, %s / %s)", res.Start, sidx, input, pattern)
} }
if e != eidx { if res.End != eidx {
t.Errorf("Invalid end index: %d (expected: %d, %s / %s)", e, eidx, input, pattern) t.Errorf("Invalid end index: %d (expected: %d, %s / %s)", res.End, eidx, input, pattern)
}
if res.Bonus != bonus {
t.Errorf("Invalid bonus: %d (expected: %d, %s / %s)", res.Bonus, bonus, input, pattern)
} }
} }
func TestFuzzyMatch(t *testing.T) { func TestFuzzyMatch(t *testing.T) {
assertMatch(t, FuzzyMatch, false, true, "fooBarbaz", "oBZ", 2, 9) assertMatch(t, FuzzyMatch, false, true, "fooBarbaz", "oBZ", 2, 9, 2)
assertMatch(t, FuzzyMatch, true, true, "fooBarbaz", "oBZ", -1, -1) assertMatch(t, FuzzyMatch, false, true, "foo bar baz", "fbb", 0, 9, 8)
assertMatch(t, FuzzyMatch, true, true, "fooBarbaz", "oBz", 2, 9) assertMatch(t, FuzzyMatch, false, true, "/AutomatorDocument.icns", "rdoc", 9, 13, 4)
assertMatch(t, FuzzyMatch, true, true, "fooBarbaz", "fooBarbazz", -1, -1) assertMatch(t, FuzzyMatch, false, true, "/man1/zshcompctl.1", "zshc", 6, 10, 7)
assertMatch(t, FuzzyMatch, false, true, "/.oh-my-zsh/cache", "zshc", 8, 13, 8)
assertMatch(t, FuzzyMatch, false, true, "ab0123 456", "12356", 3, 10, 3)
assertMatch(t, FuzzyMatch, false, true, "abc123 456", "12356", 3, 10, 5)
assertMatch(t, FuzzyMatch, false, true, "foo/bar/baz", "fbb", 0, 9, 8)
assertMatch(t, FuzzyMatch, false, true, "fooBarBaz", "fbb", 0, 7, 6)
assertMatch(t, FuzzyMatch, false, true, "foo barbaz", "fbb", 0, 8, 6)
assertMatch(t, FuzzyMatch, false, true, "fooBar Baz", "foob", 0, 4, 8)
assertMatch(t, FuzzyMatch, true, true, "fooBarbaz", "oBZ", -1, -1, 0)
assertMatch(t, FuzzyMatch, true, true, "fooBarbaz", "oBz", 2, 9, 2)
assertMatch(t, FuzzyMatch, true, true, "Foo Bar Baz", "fbb", -1, -1, 0)
assertMatch(t, FuzzyMatch, true, true, "Foo/Bar/Baz", "FBB", 0, 9, 8)
assertMatch(t, FuzzyMatch, true, true, "FooBarBaz", "FBB", 0, 7, 6)
assertMatch(t, FuzzyMatch, true, true, "foo BarBaz", "fBB", 0, 8, 7)
assertMatch(t, FuzzyMatch, true, true, "FooBar Baz", "FooB", 0, 4, 8)
assertMatch(t, FuzzyMatch, true, true, "fooBarbaz", "fooBarbazz", -1, -1, 0)
} }
func TestFuzzyMatchBackward(t *testing.T) { func TestFuzzyMatchBackward(t *testing.T) {
assertMatch(t, FuzzyMatch, false, true, "foobar fb", "fb", 0, 4) assertMatch(t, FuzzyMatch, false, true, "foobar fb", "fb", 0, 4, 4)
assertMatch(t, FuzzyMatch, false, false, "foobar fb", "fb", 7, 9) assertMatch(t, FuzzyMatch, false, false, "foobar fb", "fb", 7, 9, 5)
} }
func TestExactMatchNaive(t *testing.T) { func TestExactMatchNaive(t *testing.T) {
for _, dir := range []bool{true, false} { for _, dir := range []bool{true, false} {
assertMatch(t, ExactMatchNaive, false, dir, "fooBarbaz", "oBA", 2, 5) assertMatch(t, ExactMatchNaive, false, dir, "fooBarbaz", "oBA", 2, 5, 3)
assertMatch(t, ExactMatchNaive, true, dir, "fooBarbaz", "oBA", -1, -1) assertMatch(t, ExactMatchNaive, true, dir, "fooBarbaz", "oBA", -1, -1, 0)
assertMatch(t, ExactMatchNaive, true, dir, "fooBarbaz", "fooBarbazz", -1, -1) assertMatch(t, ExactMatchNaive, true, dir, "fooBarbaz", "fooBarbazz", -1, -1, 0)
assertMatch(t, ExactMatchNaive, false, dir, "/AutomatorDocument.icns", "rdoc", 9, 13, 4)
assertMatch(t, ExactMatchNaive, false, dir, "/man1/zshcompctl.1", "zshc", 6, 10, 7)
assertMatch(t, ExactMatchNaive, false, dir, "/.oh-my-zsh/cache", "zsh/c", 8, 13, 10)
} }
} }
func TestExactMatchNaiveBackward(t *testing.T) { func TestExactMatchNaiveBackward(t *testing.T) {
assertMatch(t, FuzzyMatch, false, true, "foobar foob", "oo", 1, 3) assertMatch(t, ExactMatchNaive, false, true, "foobar foob", "oo", 1, 3, 1)
assertMatch(t, FuzzyMatch, false, false, "foobar foob", "oo", 8, 10) assertMatch(t, ExactMatchNaive, false, false, "foobar foob", "oo", 8, 10, 1)
} }
func TestPrefixMatch(t *testing.T) { func TestPrefixMatch(t *testing.T) {
for _, dir := range []bool{true, false} { for _, dir := range []bool{true, false} {
assertMatch(t, PrefixMatch, false, dir, "fooBarbaz", "Foo", 0, 3) assertMatch(t, PrefixMatch, true, dir, "fooBarbaz", "Foo", -1, -1, 0)
assertMatch(t, PrefixMatch, true, dir, "fooBarbaz", "Foo", -1, -1) assertMatch(t, PrefixMatch, false, dir, "fooBarBaz", "baz", -1, -1, 0)
assertMatch(t, PrefixMatch, false, dir, "fooBarbaz", "baz", -1, -1) assertMatch(t, PrefixMatch, false, dir, "fooBarbaz", "Foo", 0, 3, 6)
assertMatch(t, PrefixMatch, false, dir, "foOBarBaZ", "foo", 0, 3, 7)
assertMatch(t, PrefixMatch, false, dir, "f-oBarbaz", "f-o", 0, 3, 8)
} }
} }
func TestSuffixMatch(t *testing.T) { func TestSuffixMatch(t *testing.T) {
for _, dir := range []bool{true, false} { for _, dir := range []bool{true, false} {
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz", "Foo", -1, -1) assertMatch(t, SuffixMatch, false, dir, "fooBarbaz", "Foo", -1, -1, 0)
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz", "baz", 6, 9) assertMatch(t, SuffixMatch, false, dir, "fooBarbaz", "baz", 6, 9, 2)
assertMatch(t, SuffixMatch, true, dir, "fooBarbaz", "Baz", -1, -1) assertMatch(t, SuffixMatch, false, dir, "fooBarBaZ", "baz", 6, 9, 5)
assertMatch(t, SuffixMatch, true, dir, "fooBarbaz", "Baz", -1, -1, 0)
} }
} }
func TestEmptyPattern(t *testing.T) { func TestEmptyPattern(t *testing.T) {
for _, dir := range []bool{true, false} { for _, dir := range []bool{true, false} {
assertMatch(t, FuzzyMatch, true, dir, "foobar", "", 0, 0) assertMatch(t, FuzzyMatch, true, dir, "foobar", "", 0, 0, 0)
assertMatch(t, ExactMatchNaive, true, dir, "foobar", "", 0, 0) assertMatch(t, ExactMatchNaive, true, dir, "foobar", "", 0, 0, 0)
assertMatch(t, PrefixMatch, true, dir, "foobar", "", 0, 0) assertMatch(t, PrefixMatch, true, dir, "foobar", "", 0, 0, 0)
assertMatch(t, SuffixMatch, true, dir, "foobar", "", 6, 6) assertMatch(t, SuffixMatch, true, dir, "foobar", "", 6, 6, 0)
} }
} }

View File

@@ -8,7 +8,7 @@ import (
const ( const (
// Current version // Current version
version = "0.11.4" version = "0.12.1"
// Core // Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond coordinatorDelayMax time.Duration = 100 * time.Millisecond

View File

@@ -23,6 +23,7 @@ type Item struct {
offsets []Offset offsets []Offset
colors []ansiOffset colors []ansiOffset
rank [5]int32 rank [5]int32
bonus int32
} }
// Sort criteria to use. Never changes once fzf is started. // Sort criteria to use. Never changes once fzf is started.
@@ -73,15 +74,17 @@ func (item *Item) Rank(cache bool) [5]int32 {
matchlen += end - begin matchlen += end - begin
} }
} }
if matchlen == 0 {
matchlen = math.MaxInt32
}
rank := buildEmptyRank(item.Index()) rank := buildEmptyRank(item.Index())
for idx, criterion := range sortCriteria { for idx, criterion := range sortCriteria {
var val int32 var val int32
switch criterion { switch criterion {
case byMatchLen: case byMatchLen:
val = int32(matchlen) if matchlen == 0 {
val = math.MaxInt32
} else {
// It is extremely unlikely that bonus exceeds 128
val = 128*int32(matchlen) - item.bonus
}
case byLength: case byLength:
// It is guaranteed that .transformed in not null in normal execution // It is guaranteed that .transformed in not null in normal execution
if item.transformed != nil { if item.transformed != nil {

View File

@@ -49,7 +49,7 @@ type Pattern struct {
cacheable bool cacheable bool
delimiter Delimiter delimiter Delimiter
nth []Range nth []Range
procFun map[termType]func(bool, bool, []rune, []rune) (int, int) procFun map[termType]func(bool, bool, []rune, []rune) algo.Result
} }
var ( var (
@@ -125,7 +125,7 @@ func BuildPattern(fuzzy bool, extended bool, caseMode Case, forward bool,
cacheable: cacheable, cacheable: cacheable,
nth: nth, nth: nth,
delimiter: delimiter, delimiter: delimiter,
procFun: make(map[termType]func(bool, bool, []rune, []rune) (int, int))} procFun: make(map[termType]func(bool, bool, []rune, []rune) algo.Result)}
ptr.procFun[termFuzzy] = algo.FuzzyMatch ptr.procFun[termFuzzy] = algo.FuzzyMatch
ptr.procFun[termEqual] = algo.EqualMatch ptr.procFun[termEqual] = algo.EqualMatch
@@ -227,7 +227,7 @@ func (p *Pattern) CacheKey() string {
} }
cacheableTerms := []string{} cacheableTerms := []string{}
for _, termSet := range p.termSets { for _, termSet := range p.termSets {
if len(termSet) == 1 && !termSet[0].inv { if len(termSet) == 1 && !termSet[0].inv && (p.fuzzy || termSet[0].typ == termExact) {
cacheableTerms = append(cacheableTerms, string(termSet[0].origText)) cacheableTerms = append(cacheableTerms, string(termSet[0].origText))
} }
} }
@@ -275,15 +275,16 @@ func (p *Pattern) matchChunk(chunk *Chunk) []*Item {
matches := []*Item{} matches := []*Item{}
if !p.extended { if !p.extended {
for _, item := range *chunk { for _, item := range *chunk {
if sidx, eidx, tlen := p.basicMatch(item); sidx >= 0 { offset, bonus := p.basicMatch(item)
if sidx := offset[0]; sidx >= 0 {
matches = append(matches, matches = append(matches,
dupItem(item, []Offset{Offset{int32(sidx), int32(eidx), int32(tlen)}})) dupItem(item, []Offset{offset}, bonus))
} }
} }
} else { } else {
for _, item := range *chunk { for _, item := range *chunk {
if offsets := p.extendedMatch(item); len(offsets) == len(p.termSets) { if offsets, bonus := p.extendedMatch(item); len(offsets) == len(p.termSets) {
matches = append(matches, dupItem(item, offsets)) matches = append(matches, dupItem(item, offsets, bonus))
} }
} }
} }
@@ -293,25 +294,27 @@ func (p *Pattern) matchChunk(chunk *Chunk) []*Item {
// MatchItem returns true if the Item is a match // MatchItem returns true if the Item is a match
func (p *Pattern) MatchItem(item *Item) bool { func (p *Pattern) MatchItem(item *Item) bool {
if !p.extended { if !p.extended {
sidx, _, _ := p.basicMatch(item) offset, _ := p.basicMatch(item)
sidx := offset[0]
return sidx >= 0 return sidx >= 0
} }
offsets := p.extendedMatch(item) offsets, _ := p.extendedMatch(item)
return len(offsets) == len(p.termSets) return len(offsets) == len(p.termSets)
} }
func dupItem(item *Item, offsets []Offset) *Item { func dupItem(item *Item, offsets []Offset, bonus int32) *Item {
sort.Sort(ByOrder(offsets)) sort.Sort(ByOrder(offsets))
return &Item{ return &Item{
text: item.text, text: item.text,
origText: item.origText, origText: item.origText,
transformed: item.transformed, transformed: item.transformed,
offsets: offsets, offsets: offsets,
bonus: bonus,
colors: item.colors, colors: item.colors,
rank: buildEmptyRank(item.Index())} rank: buildEmptyRank(item.Index())}
} }
func (p *Pattern) basicMatch(item *Item) (int, int, int) { func (p *Pattern) basicMatch(item *Item) (Offset, int32) {
input := p.prepareInput(item) input := p.prepareInput(item)
if p.fuzzy { if p.fuzzy {
return p.iter(algo.FuzzyMatch, input, p.caseSensitive, p.forward, p.text) return p.iter(algo.FuzzyMatch, input, p.caseSensitive, p.forward, p.text)
@@ -319,29 +322,33 @@ func (p *Pattern) basicMatch(item *Item) (int, int, int) {
return p.iter(algo.ExactMatchNaive, input, p.caseSensitive, p.forward, p.text) return p.iter(algo.ExactMatchNaive, input, p.caseSensitive, p.forward, p.text)
} }
func (p *Pattern) extendedMatch(item *Item) []Offset { func (p *Pattern) extendedMatch(item *Item) ([]Offset, int32) {
input := p.prepareInput(item) input := p.prepareInput(item)
offsets := []Offset{} offsets := []Offset{}
var totalBonus int32
for _, termSet := range p.termSets { for _, termSet := range p.termSets {
var offset *Offset var offset *Offset
var bonus int32
for _, term := range termSet { for _, term := range termSet {
pfun := p.procFun[term.typ] pfun := p.procFun[term.typ]
if sidx, eidx, tlen := p.iter(pfun, input, term.caseSensitive, p.forward, term.text); sidx >= 0 { off, pen := p.iter(pfun, input, term.caseSensitive, p.forward, term.text)
if sidx := off[0]; sidx >= 0 {
if term.inv { if term.inv {
continue continue
} }
offset = &Offset{int32(sidx), int32(eidx), int32(tlen)} offset, bonus = &off, pen
break break
} else if term.inv { } else if term.inv {
offset = &Offset{0, 0, 0} offset, bonus = &Offset{0, 0, 0}, 0
continue continue
} }
} }
if offset != nil { if offset != nil {
offsets = append(offsets, *offset) offsets = append(offsets, *offset)
totalBonus += bonus
} }
} }
return offsets return offsets, totalBonus
} }
func (p *Pattern) prepareInput(item *Item) []Token { func (p *Pattern) prepareInput(item *Item) []Token {
@@ -360,13 +367,16 @@ func (p *Pattern) prepareInput(item *Item) []Token {
return ret return ret
} }
func (p *Pattern) iter(pfun func(bool, bool, []rune, []rune) (int, int), func (p *Pattern) iter(pfun func(bool, bool, []rune, []rune) algo.Result,
tokens []Token, caseSensitive bool, forward bool, pattern []rune) (int, int, int) { tokens []Token, caseSensitive bool, forward bool, pattern []rune) (Offset, int32) {
for _, part := range tokens { for _, part := range tokens {
prefixLength := part.prefixLength prefixLength := int32(part.prefixLength)
if sidx, eidx := pfun(caseSensitive, forward, part.text, pattern); sidx >= 0 { if res := pfun(caseSensitive, forward, part.text, pattern); res.Start >= 0 {
return sidx + prefixLength, eidx + prefixLength, part.trimLength var sidx int32 = res.Start + prefixLength
var eidx int32 = res.End + prefixLength
return Offset{sidx, eidx, int32(part.trimLength)}, res.Bonus
} }
} }
return -1, -1, -1 // math.MaxUint16 // TODO: math.MaxUint16
return Offset{-1, -1, -1}, 0.0
} }

View File

@@ -70,10 +70,10 @@ func TestExact(t *testing.T) {
clearPatternCache() clearPatternCache()
pattern := BuildPattern(true, true, CaseSmart, true, pattern := BuildPattern(true, true, CaseSmart, true,
[]Range{}, Delimiter{}, []rune("'abc")) []Range{}, Delimiter{}, []rune("'abc"))
sidx, eidx := algo.ExactMatchNaive( res := algo.ExactMatchNaive(
pattern.caseSensitive, pattern.forward, []rune("aabbcc abc"), pattern.termSets[0][0].text) pattern.caseSensitive, pattern.forward, []rune("aabbcc abc"), pattern.termSets[0][0].text)
if sidx != 7 || eidx != 10 { if res.Start != 7 || res.End != 10 {
t.Errorf("%s / %d / %d", pattern.termSets, sidx, eidx) t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
} }
} }
@@ -82,11 +82,11 @@ func TestEqual(t *testing.T) {
clearPatternCache() clearPatternCache()
pattern := BuildPattern(true, true, CaseSmart, true, []Range{}, Delimiter{}, []rune("^AbC$")) pattern := BuildPattern(true, true, CaseSmart, true, []Range{}, Delimiter{}, []rune("^AbC$"))
match := func(str string, sidxExpected int, eidxExpected int) { match := func(str string, sidxExpected int32, eidxExpected int32) {
sidx, eidx := algo.EqualMatch( res := algo.EqualMatch(
pattern.caseSensitive, pattern.forward, []rune(str), pattern.termSets[0][0].text) pattern.caseSensitive, pattern.forward, []rune(str), pattern.termSets[0][0].text)
if sidx != sidxExpected || eidx != eidxExpected { if res.Start != sidxExpected || res.End != eidxExpected {
t.Errorf("%s / %d / %d", pattern.termSets, sidx, eidx) t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
} }
} }
match("ABC", -1, -1) match("ABC", -1, -1)

View File

@@ -711,7 +711,9 @@ func (t *Terminal) rubout(pattern string) {
} }
func keyMatch(key int, event C.Event) bool { func keyMatch(key int, event C.Event) bool {
return event.Type == key || event.Type == C.Rune && int(event.Char) == key-C.AltZ return event.Type == key ||
event.Type == C.Rune && int(event.Char) == key-C.AltZ ||
event.Type == C.Mouse && key == C.DoubleClick && event.MouseEvent.Double
} }
func quoteEntry(entry string) string { func quoteEntry(entry string) string {

View File

@@ -31,7 +31,7 @@ def wait
return if yield return if yield
sleep 0.05 sleep 0.05
end end
throw 'timeout' raise 'timeout'
end end
class Shell class Shell
@@ -207,13 +207,13 @@ class TestGoFZF < TestBase
tmux.send_keys '99', 'C-a', '1', 'C-f', '3', 'C-b', 'C-h', 'C-u', 'C-e', 'C-y', 'C-k', 'Tab', 'BTab' tmux.send_keys '99', 'C-a', '1', 'C-f', '3', 'C-b', 'C-h', 'C-u', 'C-e', 'C-y', 'C-k', 'Tab', 'BTab'
tmux.until { |lines| lines[-2] == ' 856/100000' } tmux.until { |lines| lines[-2] == ' 856/100000' }
lines = tmux.capture lines = tmux.capture
assert_equal '> 1391', lines[-4] assert_equal '> 3910', lines[-4]
assert_equal ' 391', lines[-3] assert_equal ' 391', lines[-3]
assert_equal ' 856/100000', lines[-2] assert_equal ' 856/100000', lines[-2]
assert_equal '> 391', lines[-1] assert_equal '> 391', lines[-1]
tmux.send_keys :Enter tmux.send_keys :Enter
assert_equal '1391', readonce.chomp assert_equal '3910', readonce.chomp
end end
def test_fzf_default_command def test_fzf_default_command
@@ -357,7 +357,7 @@ class TestGoFZF < TestBase
tmux.send_keys :BTab, :BTab, :BTab tmux.send_keys :BTab, :BTab, :BTab
tmux.until { |lines| lines[-2].include?('(3)') } tmux.until { |lines| lines[-2].include?('(3)') }
tmux.send_keys :Enter tmux.send_keys :Enter
assert_equal ['5', '5', '15', '25'], readonce.split($/) assert_equal ['5', '5', '50', '51'], readonce.split($/)
end end
end end
@@ -378,7 +378,7 @@ class TestGoFZF < TestBase
tmux.send_keys :Enter tmux.send_keys :Enter
tmux.until { |lines| lines[-1] == '>' } tmux.until { |lines| lines[-1] == '>' }
tmux.send_keys 'C-K', :Enter tmux.send_keys 'C-K', :Enter
assert_equal ['1919'], readonce.split($/) assert_equal ['9090'], readonce.split($/)
end end
def test_tac def test_tac
@@ -394,6 +394,7 @@ class TestGoFZF < TestBase
tmux.send_keys "seq 1 1000 | #{fzf :tac, :multi}", :Enter tmux.send_keys "seq 1 1000 | #{fzf :tac, :multi}", :Enter
tmux.until { |lines| lines[-2].include? '1000/1000' } tmux.until { |lines| lines[-2].include? '1000/1000' }
tmux.send_keys '99' tmux.send_keys '99'
tmux.until { |lines| lines[-2].include? '28/1000' }
tmux.send_keys :BTab, :BTab, :BTab tmux.send_keys :BTab, :BTab, :BTab
tmux.until { |lines| lines[-2].include?('(3)') } tmux.until { |lines| lines[-2].include?('(3)') }
tmux.send_keys :Enter tmux.send_keys :Enter
@@ -413,11 +414,12 @@ class TestGoFZF < TestBase
def test_expect def test_expect
test = lambda do |key, feed, expected = key| test = lambda do |key, feed, expected = key|
tmux.send_keys "seq 1 100 | #{fzf :expect, key}", :Enter tmux.send_keys "seq 1 100 | #{fzf :expect, key}; sync", :Enter
tmux.until { |lines| lines[-2].include? '100/100' } tmux.until { |lines| lines[-2].include? '100/100' }
tmux.send_keys '55' tmux.send_keys '55'
tmux.until { |lines| lines[-2].include? '1/100' } tmux.until { |lines| lines[-2].include? '1/100' }
tmux.send_keys *feed tmux.send_keys *feed
tmux.prepare
assert_equal [expected, '55'], readonce.split($/) assert_equal [expected, '55'], readonce.split($/)
end end
test.call 'ctrl-t', 'C-T' test.call 'ctrl-t', 'C-T'
@@ -709,10 +711,10 @@ class TestGoFZF < TestBase
# len(1 ~ 2) # len(1 ~ 2)
output = [ output = [
"apple ui bottle 2",
"app ic bottle 4", "app ic bottle 4",
"apple juice bottle 1",
"app ice bottle 3", "app ice bottle 3",
"apple ui bottle 2",
"apple juice bottle 1",
] ]
assert_equal output, `#{FZF} -fai -n1..2 < #{tempname}`.split($/) assert_equal output, `#{FZF} -fai -n1..2 < #{tempname}`.split($/)
@@ -727,9 +729,9 @@ class TestGoFZF < TestBase
# len(2) # len(2)
output = [ output = [
"apple ui bottle 2",
"app ic bottle 4", "app ic bottle 4",
"app ice bottle 3", "app ice bottle 3",
"apple ui bottle 2",
"apple juice bottle 1", "apple juice bottle 1",
] ]
assert_equal output, `#{FZF} -fi -n2 < #{tempname}`.split($/) assert_equal output, `#{FZF} -fi -n2 < #{tempname}`.split($/)
@@ -877,15 +879,19 @@ class TestGoFZF < TestBase
def test_execute_multi def test_execute_multi
output = '/tmp/fzf-test-execute-multi' output = '/tmp/fzf-test-execute-multi'
opts = %[--multi --bind \\"alt-a:execute-multi(echo '[{}], @{}@' >> #{output})\\"] opts = %[--multi --bind \\"alt-a:execute-multi(echo '[{}], @{}@' >> #{output}; sync)\\"]
tmux.send_keys "seq 100 | #{fzf opts}", :Enter tmux.send_keys "seq 100 | #{fzf opts}", :Enter
tmux.until { |lines| lines[-2].include? '100/100' } tmux.until { |lines| lines[-2].include? '100/100' }
tmux.send_keys :Escape, :a tmux.send_keys :Escape, :a
tmux.until { |lines| lines[-2].include? '/100' }
tmux.send_keys :BTab, :BTab, :BTab tmux.send_keys :BTab, :BTab, :BTab
tmux.send_keys :Escape, :a tmux.send_keys :Escape, :a
tmux.until { |lines| lines[-2].include? '/100' }
tmux.send_keys :Tab, :Tab tmux.send_keys :Tab, :Tab
tmux.send_keys :Escape, :a tmux.send_keys :Escape, :a
tmux.until { |lines| lines[-2].include? '/100' }
tmux.send_keys :Enter tmux.send_keys :Enter
tmux.prepare
readonce readonce
assert_equal ['["1"], @"1"@', '["1" "2" "3"], @"1" "2" "3"@', '["1" "2" "4"], @"1" "2" "4"@'], assert_equal ['["1"], @"1"@', '["1" "2" "3"], @"1" "2" "3"@', '["1" "2" "4"], @"1" "2" "4"@'],
File.readlines(output).map(&:chomp) File.readlines(output).map(&:chomp)
@@ -937,12 +943,12 @@ class TestGoFZF < TestBase
lines[-2].include?('/90') && lines[-2].include?('/90') &&
lines[-3] == ' 1' && lines[-3] == ' 1' &&
lines[-4] == ' 2' && lines[-4] == ' 2' &&
lines[-13] == '> 15' lines[-13] == '> 50'
end end
tmux.send_keys :Down tmux.send_keys :Down
end end
tmux.send_keys :Enter tmux.send_keys :Enter
assert_equal '15', readonce.chomp assert_equal '50', readonce.chomp
end end
def test_header_lines_reverse def test_header_lines_reverse
@@ -952,12 +958,12 @@ class TestGoFZF < TestBase
lines[1].include?('/90') && lines[1].include?('/90') &&
lines[2] == ' 1' && lines[2] == ' 1' &&
lines[3] == ' 2' && lines[3] == ' 2' &&
lines[12] == '> 15' lines[12] == '> 50'
end end
tmux.send_keys :Up tmux.send_keys :Up
end end
tmux.send_keys :Enter tmux.send_keys :Enter
assert_equal '15', readonce.chomp assert_equal '50', readonce.chomp
end end
def test_header_lines_overflow def test_header_lines_overflow
@@ -1111,13 +1117,9 @@ class TestGoFZF < TestBase
def test_exitstatus_empty def test_exitstatus_empty
{ '99' => '0', '999' => '1' }.each do |query, status| { '99' => '0', '999' => '1' }.each do |query, status|
tmux.send_keys "seq 100 | #{FZF} -q #{query}", :Enter tmux.send_keys "seq 100 | #{FZF} -q #{query}; echo --\\$?--", :Enter
tmux.until { |lines| lines[-2] =~ %r{ [10]/100} } tmux.until { |lines| lines[-2] =~ %r{ [10]/100} }
tmux.send_keys :Enter tmux.send_keys :Enter
tmux.send_keys 'echo --\$?--'
tmux.until { |lines| lines.last.include? "echo --$?--" }
tmux.send_keys :Enter
tmux.until { |lines| lines.last.include? "--#{status}--" } tmux.until { |lines| lines.last.include? "--#{status}--" }
end end
end end
@@ -1151,6 +1153,16 @@ class TestGoFZF < TestBase
end end
end end
def test_partial_caching
tmux.send_keys 'seq 1000 | fzf -e', :Enter
tmux.until { |lines| lines[-2] == ' 1000/1000' }
tmux.send_keys 11
tmux.until { |lines| lines[-2] == ' 19/1000' }
tmux.send_keys 'C-a', "'"
tmux.until { |lines| lines[-2] == ' 28/1000' }
tmux.send_keys :Enter
end
private private
def writelines path, lines def writelines path, lines
File.unlink path while File.exists? path File.unlink path while File.exists? path
@@ -1206,6 +1218,22 @@ module TestShell
tmux.until(0) { |lines| lines[-1].include? '1 2 3' } tmux.until(0) { |lines| lines[-1].include? '1 2 3' }
end end
def test_ctrl_t_unicode
FileUtils.mkdir_p '/tmp/fzf-test'
tmux.send_keys 'cd /tmp/fzf-test; echo -n test1 > "fzf-unicode 테스트1"; echo -n test2 > "fzf-unicode 테스트2"', :Enter
tmux.prepare
tmux.send_keys 'cat ', 'C-t', pane: 0
tmux.until(1) { |lines| lines.item_count >= 1 }
tmux.send_keys 'fzf-unicode', pane: 1
tmux.until(1) { |lines| lines[-2].start_with? ' 2/' }
tmux.send_keys :BTab, :BTab, pane: 1
tmux.until(1) { |lines| lines[-2].include? '(2)' }
tmux.send_keys :Enter, pane: 1
tmux.until { |lines| lines[-1].include? 'cat' }
tmux.send_keys :Enter
tmux.until { |lines| lines[-1].include? 'test1test2' }
end
def test_alt_c def test_alt_c
tmux.prepare tmux.prepare
tmux.send_keys :Escape, :c, pane: 0 tmux.send_keys :Escape, :c, pane: 0
@@ -1314,6 +1342,7 @@ module CompletionTest
lines[-2].include?('100/') && lines[-2].include?('100/') &&
lines[-3].include?('/tmp/fzf-test/.hidden-') lines[-3].include?('/tmp/fzf-test/.hidden-')
end end
tmux.send_keys :Enter
ensure ensure
['/tmp/fzf-test', '/tmp/fzf test', '~/.fzf-home', 'no~such~user'].each do |f| ['/tmp/fzf-test', '/tmp/fzf test', '~/.fzf-home', 'no~such~user'].each do |f|
FileUtils.rm_rf File.expand_path(f) FileUtils.rm_rf File.expand_path(f)
@@ -1405,6 +1434,20 @@ module CompletionTest
tmux.send_keys :Enter tmux.send_keys :Enter
tmux.until { |lines| lines[-1] == 'unset FOO' } tmux.until { |lines| lines[-1] == 'unset FOO' }
end end
def test_file_completion_unicode
FileUtils.mkdir_p '/tmp/fzf-test'
tmux.send_keys 'cd /tmp/fzf-test; echo -n test3 > "fzf-unicode 테스트1"; echo -n test4 > "fzf-unicode 테스트2"', :Enter
tmux.prepare
tmux.send_keys 'cat fzf-unicode**', :Tab, pane: 0
tmux.until(1) { |lines| lines[-2].start_with? ' 2/' }
tmux.send_keys :BTab, :BTab, pane: 1
tmux.until(1) { |lines| lines[-2].include? '(2)' }
tmux.send_keys :Enter, pane: 1
tmux.until { |lines| lines[-1].include? 'cat' }
tmux.send_keys :Enter
tmux.until { |lines| lines[-1].include? 'test3test4' }
end
end end
class TestBash < TestBase class TestBash < TestBase