Compare commits

...

26 Commits

Author SHA1 Message Date
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
21 changed files with 339 additions and 158 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,12 @@
CHANGELOG CHANGELOG
========= =========
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

@@ -140,15 +140,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.0 pre=1 ||
version=0.11.4 pre=0 version=0.12.0 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
ask "Do you want to update your shell configuration files?"
update_config=$?
fi
echo echo
for shell in bash zsh; do 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.0" "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
@@ -285,7 +285,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)
@@ -313,7 +313,7 @@ function! s:split(dict)
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
finally finally
setlocal winfixwidth winfixheight buftype=nofile bufhidden=wipe nobuflisted setlocal winfixwidth winfixheight buftype=nofile bufhidden=wipe nobuflisted

View File

@@ -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

@@ -49,7 +49,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"
@@ -85,6 +85,15 @@ 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'
@@ -94,19 +103,19 @@ else
if [ $__use_tmux_auto -eq 1 ]; then if [ $__use_tmux_auto -eq 1 ]; then
bind -x '"\C-t": "__fzf_select_tmux_auto__"' bind -x '"\C-t": "__fzf_select_tmux_auto__"'
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_tmux_auto

View File

@@ -27,7 +27,7 @@ function fzf_key_bindings
end end
function __fzf_ctrl_r function __fzf_ctrl_r
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

View File

@@ -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,30 @@ 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
)
// 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 +110,76 @@ 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
var bonus int32
pidx := 0
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
} }
return -1, -1
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 Result{int32(sidx), int32(eidx), bonus}
}
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 +189,17 @@ 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 {
// Note: ExactMatchNaive always return a zero bonus.
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
@@ -132,22 +217,23 @@ func ExactMatchNaive(caseSensitive bool, forward bool, runes []rune, pattern []r
pidx++ pidx++
if pidx == lenPattern { if pidx == lenPattern {
if forward { if forward {
return index - lenPattern + 1, index + 1 return Result{int32(index - lenPattern + 1), int32(index + 1), 0}
} }
return lenRunes - (index + 1), lenRunes - (index - lenPattern + 1) return Result{int32(lenRunes - (index + 1)), int32(lenRunes - (index - lenPattern + 1)), 0}
} }
} 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 {
// Note: PrefixMatch always return a zero bonus.
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 +242,20 @@ 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) return Result{0, int32(len(pattern)), 0}
} }
// 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 {
// Note: SuffixMatch always return a zero bonus.
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 +264,24 @@ 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 return Result{int32(trimmedLen - len(pattern)), int32(trimmedLen), 0}
} }
// 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,84 @@ 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, 0)
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)
} }
} }
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, 0)
assertMatch(t, FuzzyMatch, false, false, "foobar foob", "oo", 8, 10) assertMatch(t, ExactMatchNaive, false, false, "foobar foob", "oo", 8, 10, 0)
} }
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, false, dir, "fooBarbaz", "Foo", 0, 3, 0)
assertMatch(t, PrefixMatch, true, dir, "fooBarbaz", "Foo", -1, -1) assertMatch(t, PrefixMatch, true, dir, "fooBarbaz", "Foo", -1, -1, 0)
assertMatch(t, PrefixMatch, false, dir, "fooBarbaz", "baz", -1, -1) assertMatch(t, PrefixMatch, false, dir, "fooBarbaz", "baz", -1, -1, 0)
} }
} }
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, 0)
assertMatch(t, SuffixMatch, true, dir, "fooBarbaz", "Baz", -1, -1) 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.0"
// 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
@@ -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

@@ -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
@@ -1314,6 +1320,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)