mirror of
https://github.com/junegunn/fzf.git
synced 2025-07-31 20:22:01 -07:00
Compare commits
41 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
40d934e378 | ||
|
e95d82748f | ||
|
30bd0b53db | ||
|
1893eca41a | ||
|
82067463b8 | ||
|
ce9c51d399 | ||
|
96176476f3 | ||
|
68c84264af | ||
|
69438a55ca | ||
|
8695b5e319 | ||
|
95970164ad | ||
|
f6c6e59a50 | ||
|
45143f9541 | ||
|
23244bb410 | ||
|
edb647667e | ||
|
8d3a302a17 | ||
|
1d2d32c847 | ||
|
d635b3fd3c | ||
|
0f281ef894 | ||
|
b18db4733c | ||
|
6e08fe337c | ||
|
2a2c0a0957 | ||
|
4230b6f3c9 | ||
|
aa171b45cb | ||
|
661d06c90a | ||
|
a9aa263d3a | ||
|
6208fc9cfd | ||
|
e1dd798482 | ||
|
c8a3f6f06a | ||
|
3b9984379c | ||
|
a1b60b1d42 | ||
|
b5850ebd4c | ||
|
ac0a62e494 | ||
|
54b4b0c56f | ||
|
033afde3b5 | ||
|
a07944a5bb | ||
|
32010055e1 | ||
|
971ea2217c | ||
|
d513a210c6 | ||
|
a1db64e7b1 | ||
|
0b9c4e1e74 |
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -1,6 +0,0 @@
|
||||
[submodule "src/vendor/github.com/junegunn/go-shellwords"]
|
||||
path = src/vendor/github.com/junegunn/go-shellwords
|
||||
url = https://github.com/junegunn/go-shellwords.git
|
||||
[submodule "src/vendor/github.com/junegunn/go-runewidth"]
|
||||
path = src/vendor/github.com/junegunn/go-runewidth
|
||||
url = https://github.com/junegunn/go-runewidth.git
|
||||
|
30
CHANGELOG.md
30
CHANGELOG.md
@@ -1,6 +1,36 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
0.11.3
|
||||
------
|
||||
|
||||
- Graceful exit on SIGTERM (#482)
|
||||
- `$SHELL` instead of `sh` for `execute` action and `$FZF_DEFAULT_COMMAND` (#481)
|
||||
- Changes in fuzzy completion API
|
||||
- [`_fzf_compgen_{path,dir}`](https://github.com/junegunn/fzf/commit/9617647)
|
||||
- [`_fzf_complete_COMMAND_post`](https://github.com/junegunn/fzf/commit/8206746)
|
||||
for post-processing
|
||||
|
||||
0.11.2
|
||||
------
|
||||
|
||||
- `--tiebreak` now accepts comma-separated list of sort criteria
|
||||
- Each criterion should appear only once in the list
|
||||
- `index` is only allowed at the end of the list
|
||||
- `index` is implicitly appended to the list when not specified
|
||||
- Default is `length` (or equivalently `length,index`)
|
||||
- `begin` criterion will ignore leading whitespaces when calculating the index
|
||||
- Added `toggle-in` and `toggle-out` actions
|
||||
- Switch direction depending on `--reverse`-ness
|
||||
- `export FZF_DEFAULT_OPTS="--bind tab:toggle-out,shift-tab:toggle-in"`
|
||||
- Reduced the initial delay when `--tac` is not given
|
||||
- fzf defers the initial rendering of the screen up to 100ms if the input
|
||||
stream is ongoing to prevent unnecessary redraw during the initial
|
||||
phase. However, 100ms delay is quite noticeable and might give the
|
||||
impression that fzf is not snappy enough. This commit reduces the
|
||||
maximum delay down to 20ms when `--tac` is not specified, in which case
|
||||
the input list quickly fills the entire screen.
|
||||
|
||||
0.11.1
|
||||
------
|
||||
|
||||
|
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Junegunn Choi
|
||||
Copyright (c) 2016 Junegunn Choi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@@ -259,6 +259,14 @@ export FZF_COMPLETION_TRIGGER='~~'
|
||||
|
||||
# Options to fzf command
|
||||
export FZF_COMPLETION_OPTS='+c -x'
|
||||
|
||||
# Use ag instead of the default find command for listing candidates.
|
||||
# - The first argument to the function is the base path to start traversal
|
||||
# - Note that ag only lists files not directories
|
||||
# - See the source code (completion.{bash,zsh}) for the details.
|
||||
_fzf_compgen_path() {
|
||||
ag -g "" "$1"
|
||||
}
|
||||
```
|
||||
|
||||
#### Supported commands
|
||||
|
24
bin/fzf-tmux
24
bin/fzf-tmux
@@ -82,11 +82,19 @@ while [ $# -gt 0 ]; do
|
||||
shift
|
||||
done
|
||||
|
||||
if [ -z "$TMUX_PANE" ] || tmux list-panes -F '#F' | grep -q Z; then
|
||||
if [ -z "$TMUX" ]; then
|
||||
fzf "${args[@]}"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
# Handle zoomed tmux pane by moving it to a temp window
|
||||
if tmux list-panes -F '#F' | grep -q Z; then
|
||||
zoomed=1
|
||||
original_window=$(tmux display-message -p "#{window_id}")
|
||||
tmp_window=$(tmux new-window -d -P -F "#{window_id}" "bash -c 'while :; do for c in \\| / - \\\\; do sleep 0.2; printf \"\\r\$c fzf-tmux is running\\r\"; done; done'")
|
||||
tmux swap-pane -t $tmp_window \; select-window -t $tmp_window
|
||||
fi
|
||||
|
||||
set -e
|
||||
|
||||
# Clean up named pipes on exit
|
||||
@@ -97,6 +105,14 @@ fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id"
|
||||
fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
|
||||
cleanup() {
|
||||
rm -f $argsf $fifo1 $fifo2 $fifo3
|
||||
|
||||
# Remove temp window if we were zoomed
|
||||
if [ -n "$zoomed" ]; then
|
||||
tmux swap-pane -t $original_window \; \
|
||||
select-window -t $original_window \; \
|
||||
kill-window -t $tmp_window \; \
|
||||
resize-pane -Z
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT SIGINT SIGTERM
|
||||
|
||||
@@ -111,8 +127,8 @@ envs="env TERM=$TERM "
|
||||
[ -n "$FZF_DEFAULT_OPTS" ] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
||||
[ -n "$FZF_DEFAULT_COMMAND" ] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
||||
|
||||
mkfifo $fifo2
|
||||
mkfifo $fifo3
|
||||
mkfifo -m o+w $fifo2
|
||||
mkfifo -m o+w $fifo3
|
||||
|
||||
# Build arguments to fzf
|
||||
opts=""
|
||||
@@ -125,11 +141,13 @@ done
|
||||
if [ -n "$term" -o -t 0 ]; then
|
||||
cat <<< "$fzf $opts > $fifo2; echo \$? > $fifo3 $close" > $argsf
|
||||
tmux set-window-option -q synchronize-panes off \;\
|
||||
set-window-option -q remain-on-exit off \;\
|
||||
split-window $opt "cd $(printf %q "$PWD");$envs bash $argsf" $swap
|
||||
else
|
||||
mkfifo $fifo1
|
||||
cat <<< "$fzf $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" > $argsf
|
||||
tmux set-window-option -q synchronize-panes off \;\
|
||||
set-window-option -q remain-on-exit off \;\
|
||||
split-window $opt "$envs bash $argsf" $swap
|
||||
cat <&0 > $fifo1 &
|
||||
fi
|
||||
|
4
install
4
install
@@ -2,8 +2,8 @@
|
||||
|
||||
set -u
|
||||
|
||||
[[ "$@" =~ --pre ]] && version=0.11.1 pre=1 ||
|
||||
version=0.11.1 pre=0
|
||||
[[ "$@" =~ --pre ]] && version=0.11.3 pre=1 ||
|
||||
version=0.11.3 pre=0
|
||||
|
||||
auto_completion=
|
||||
key_bindings=
|
||||
|
@@ -1,7 +1,7 @@
|
||||
.ig
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Junegunn Choi
|
||||
Copyright (c) 2016 Junegunn Choi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf 1 "Dec 2015" "fzf 0.11.1" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "Feb 2016" "fzf 0.11.3" "fzf - a command-line fuzzy finder"
|
||||
|
||||
.SH NAME
|
||||
fzf - a command-line fuzzy finder
|
||||
@@ -68,8 +68,8 @@ Reverse the order of the input
|
||||
e.g. \fBhistory | fzf --tac --no-sort\fR
|
||||
.RE
|
||||
.TP
|
||||
.BI "--tiebreak=" "CRI"
|
||||
Sort criterion to use when the scores are tied
|
||||
.BI "--tiebreak=" "CRI[,..]"
|
||||
Comma-separated list of sort criteria to apply when the scores are tied.
|
||||
.br
|
||||
.R ""
|
||||
.br
|
||||
@@ -81,6 +81,15 @@ Sort criterion to use when the scores are tied
|
||||
.br
|
||||
.BR index " Prefers item that appeared earlier in the input stream"
|
||||
.br
|
||||
.R ""
|
||||
.br
|
||||
- Each criterion should appear only once in the list
|
||||
.br
|
||||
- \fBindex\fR is only allowed at the end of the list
|
||||
.br
|
||||
- \fBindex\fR is implicitly appended to the list when not specified
|
||||
.br
|
||||
- Default is \fBlength\fR (or equivalently \fBlength\fR,index)
|
||||
.SS Interface
|
||||
.TP
|
||||
.B "-m, --multi"
|
||||
@@ -210,7 +219,7 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
||||
.RE
|
||||
|
||||
.RS
|
||||
\fBACTION: DEFAULT BINDINGS:
|
||||
\fBACTION: DEFAULT BINDINGS (NOTES):
|
||||
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
||||
\fBaccept\fR \fIenter double-click\fR
|
||||
\fBbackward-char\fR \fIctrl-b left\fR
|
||||
@@ -240,6 +249,8 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
||||
\fBtoggle\fR
|
||||
\fBtoggle-all\fR
|
||||
\fBtoggle-down\fR \fIctrl-i (tab)\fR
|
||||
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle-up\fR : \fBtoggle-down\fR)
|
||||
\fBtoggle-out\fR (\fB--reverse\fR ? \fBtoggle-down\fR : \fBtoggle-up\fR)
|
||||
\fBtoggle-sort\fR (equivalent to \fB--toggle-sort\fR)
|
||||
\fBtoggle-up\fR \fIbtab (shift-tab)\fR
|
||||
\fBunix-line-discard\fR \fIctrl-u\fR
|
||||
|
@@ -1,4 +1,4 @@
|
||||
" Copyright (c) 2015 Junegunn Choi
|
||||
" Copyright (c) 2016 Junegunn Choi
|
||||
"
|
||||
" MIT License
|
||||
"
|
||||
@@ -52,17 +52,13 @@ function! s:fzf_exec()
|
||||
return s:exec
|
||||
endfunction
|
||||
|
||||
function! s:tmux_not_zoomed()
|
||||
return system('tmux list-panes -F "#F"') !~# 'Z'
|
||||
endfunction
|
||||
|
||||
function! s:tmux_enabled()
|
||||
if has('gui_running')
|
||||
return 0
|
||||
endif
|
||||
|
||||
if exists('s:tmux')
|
||||
return s:tmux && s:tmux_not_zoomed()
|
||||
return s:tmux
|
||||
endif
|
||||
|
||||
let s:tmux = 0
|
||||
@@ -70,7 +66,7 @@ function! s:tmux_enabled()
|
||||
let output = system('tmux -V')
|
||||
let s:tmux = !v:shell_error && output >= 'tmux 1.7'
|
||||
endif
|
||||
return s:tmux && s:tmux_not_zoomed()
|
||||
return s:tmux
|
||||
endfunction
|
||||
|
||||
function! s:shellesc(arg)
|
||||
@@ -143,17 +139,13 @@ try
|
||||
let tmux = !has('nvim') && s:tmux_enabled() && s:splittable(dict)
|
||||
let command = prefix.(tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
||||
|
||||
try
|
||||
if tmux
|
||||
return s:execute_tmux(dict, command, temps)
|
||||
elseif has('nvim')
|
||||
return s:execute_term(dict, command, temps)
|
||||
else
|
||||
return s:execute(dict, command, temps)
|
||||
endif
|
||||
finally
|
||||
call s:popd(dict)
|
||||
endtry
|
||||
if has('nvim')
|
||||
return s:execute_term(dict, command, temps)
|
||||
endif
|
||||
|
||||
let ret = tmux ? s:execute_tmux(dict, command, temps) : s:execute(dict, command, temps)
|
||||
call s:popd(dict, ret)
|
||||
return ret
|
||||
finally
|
||||
let &shell = oshell
|
||||
endtry
|
||||
@@ -197,16 +189,28 @@ function! s:pushd(dict)
|
||||
return 1
|
||||
endif
|
||||
let a:dict.prev_dir = cwd
|
||||
execute 'chdir '.s:escape(a:dict.dir)
|
||||
execute 'chdir' s:escape(a:dict.dir)
|
||||
let a:dict.dir = getcwd()
|
||||
return 1
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
function! s:popd(dict)
|
||||
if has_key(a:dict, 'prev_dir')
|
||||
execute 'chdir '.s:escape(remove(a:dict, 'prev_dir'))
|
||||
function! s:popd(dict, lines)
|
||||
" Since anything can be done in the sink function, there is no telling that
|
||||
" the change of the working directory was made by &autochdir setting.
|
||||
"
|
||||
" We use the following heuristic to determine whether to restore CWD:
|
||||
" - Always restore the current directory when &autochdir is disabled.
|
||||
" FIXME This makes it impossible to change directory from inside the sink
|
||||
" function when &autochdir is not used.
|
||||
" - In case of an error or an interrupt, a:lines will be empty.
|
||||
" And it will be an array of a single empty string when fzf was finished
|
||||
" without a match. In these cases, we presume that the change of the
|
||||
" directory is not expected and should be undone.
|
||||
if has_key(a:dict, 'prev_dir') &&
|
||||
\ (!&autochdir || (empty(a:lines) || len(a:lines) == 1 && empty(a:lines[0])))
|
||||
execute 'chdir' s:escape(remove(a:dict, 'prev_dir'))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
@@ -318,7 +322,6 @@ endfunction
|
||||
|
||||
function! s:execute_term(dict, command, temps)
|
||||
call s:split(a:dict)
|
||||
call s:pushd(a:dict)
|
||||
|
||||
let fzf = { 'buf': bufnr('%'), 'dict': a:dict, 'temps': a:temps, 'name': 'FZF' }
|
||||
let s:command = a:command
|
||||
@@ -342,7 +345,7 @@ function! s:execute_term(dict, command, temps)
|
||||
|
||||
call s:pushd(self.dict)
|
||||
try
|
||||
call s:callback(self.dict, self.temps)
|
||||
let ret = s:callback(self.dict, self.temps)
|
||||
|
||||
if inplace && bufnr('') == self.buf
|
||||
execute "normal! \<c-^>"
|
||||
@@ -352,11 +355,14 @@ function! s:execute_term(dict, command, temps)
|
||||
endif
|
||||
endif
|
||||
finally
|
||||
call s:popd(self.dict)
|
||||
call s:popd(self.dict, ret)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
call s:pushd(a:dict)
|
||||
call termopen(a:command, fzf)
|
||||
call s:popd(a:dict, [])
|
||||
setlocal nospell
|
||||
setf fzf
|
||||
startinsert
|
||||
return []
|
||||
@@ -413,10 +419,16 @@ function! s:cmd_callback(lines) abort
|
||||
augroup END
|
||||
endif
|
||||
try
|
||||
let empty = empty(expand('%')) && line('$') == 1 && empty(getline(1)) && !&modified
|
||||
let autochdir = &autochdir
|
||||
set noautochdir
|
||||
for item in a:lines
|
||||
execute cmd s:escape(item)
|
||||
if empty
|
||||
execute 'e' s:escape(item)
|
||||
let empty = 0
|
||||
else
|
||||
execute cmd s:escape(item)
|
||||
endif
|
||||
if exists('#BufEnter') && isdirectory(item)
|
||||
doautocmd BufEnter
|
||||
endif
|
||||
|
@@ -10,6 +10,26 @@
|
||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||
|
||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||
_fzf_compgen_path() {
|
||||
echo "$1"
|
||||
\find -L "$1" \
|
||||
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||
}
|
||||
fi
|
||||
|
||||
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
||||
_fzf_compgen_dir() {
|
||||
\find -L "$1" \
|
||||
-name .git -prune -o -name .svn -prune -o -type d \
|
||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||
}
|
||||
fi
|
||||
|
||||
###########################################################
|
||||
|
||||
_fzf_orig_completion_filter() {
|
||||
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2";/' |
|
||||
awk -F= '{gsub(/[^a-z0-9_= ;]/, "_", $1); print $1"="$2}'
|
||||
@@ -110,9 +130,10 @@ __fzf_generic_path_completion() {
|
||||
if [ -z "$dir" -o -d "$dir" ]; then
|
||||
leftover=${base/#"$dir"}
|
||||
leftover=${leftover/#\/}
|
||||
[ "$dir" = './' ] && dir=''
|
||||
[ -z "$dir" ] && dir='.'
|
||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||
tput sc
|
||||
matches=$(find -L "$dir"* $1 2> /dev/null | $fzf $FZF_COMPLETION_OPTS $2 -q "$leftover" | while read item; do
|
||||
matches=$(eval "$1 $(printf %q "$dir")" | $fzf $FZF_COMPLETION_OPTS $2 -q "$leftover" | while read item; do
|
||||
printf "%q$3 " "$item"
|
||||
done)
|
||||
matches=${matches% }
|
||||
@@ -135,15 +156,10 @@ __fzf_generic_path_completion() {
|
||||
fi
|
||||
}
|
||||
|
||||
_fzf_feed_fifo() (
|
||||
rm -f "$fifo"
|
||||
mkfifo "$fifo"
|
||||
cat <&0 > "$fifo" &
|
||||
)
|
||||
|
||||
_fzf_complete() {
|
||||
local fifo cur selected trigger cmd fzf
|
||||
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
|
||||
local cur selected trigger cmd fzf post
|
||||
post="$(caller 0 | awk '{print $2}')_post"
|
||||
type -t $post > /dev/null 2>&1 || post=cat
|
||||
[ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
|
||||
|
||||
cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g')
|
||||
@@ -152,12 +168,10 @@ _fzf_complete() {
|
||||
if [[ ${cur} == *"$trigger" ]]; then
|
||||
cur=${cur:0:${#cur}-${#trigger}}
|
||||
|
||||
_fzf_feed_fifo "$fifo"
|
||||
tput sc
|
||||
selected=$(eval "cat '$fifo' | $fzf $FZF_COMPLETION_OPTS $1 -q '$cur'" | tr '\n' ' ')
|
||||
selected=$(cat | $fzf $FZF_COMPLETION_OPTS $1 -q "$cur" | $post | tr '\n' ' ')
|
||||
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
||||
tput rc
|
||||
rm -f "$fifo"
|
||||
|
||||
if [ -n "$selected" ]; then
|
||||
COMPREPLY=("$selected")
|
||||
@@ -170,21 +184,16 @@ _fzf_complete() {
|
||||
}
|
||||
|
||||
_fzf_path_completion() {
|
||||
__fzf_generic_path_completion \
|
||||
"-name .git -prune -o -name .svn -prune -o -type d -print -o -type f -print -o -type l -print" \
|
||||
"-m" "" "$@"
|
||||
__fzf_generic_path_completion _fzf_compgen_path "-m" "" "$@"
|
||||
}
|
||||
|
||||
# Deprecated. No file only completion.
|
||||
_fzf_file_completion() {
|
||||
__fzf_generic_path_completion \
|
||||
"-name .git -prune -o -name .svn -prune -o -type f -print -o -type l -print" \
|
||||
"-m" "" "$@"
|
||||
_fzf_path_completion "$@"
|
||||
}
|
||||
|
||||
_fzf_dir_completion() {
|
||||
__fzf_generic_path_completion \
|
||||
"-name .git -prune -o -name .svn -prune -o -type d -print" \
|
||||
"" "/" "$@"
|
||||
__fzf_generic_path_completion _fzf_compgen_dir "" "/" "$@"
|
||||
}
|
||||
|
||||
_fzf_complete_kill() {
|
||||
@@ -238,13 +247,12 @@ _fzf_complete_unalias() {
|
||||
# fzf options
|
||||
complete -o default -F _fzf_opts_completion fzf
|
||||
|
||||
d_cmds="cd pushd rmdir"
|
||||
f_cmds="
|
||||
d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
|
||||
a_cmds="
|
||||
awk cat diff diff3
|
||||
emacs emacsclient ex file ftp g++ gcc gvim head hg java
|
||||
javac ld less more mvim nvim patch perl python ruby
|
||||
sed sftp sort source tail tee uniq vi view vim wc xdg-open"
|
||||
a_cmds="
|
||||
sed sftp sort source tail tee uniq vi view vim wc xdg-open
|
||||
basename bunzip2 bzip2 chmod chown curl cp dirname du
|
||||
find git grep gunzip gzip hg jar
|
||||
ln ls mv open rm rsync scp
|
||||
@@ -252,11 +260,11 @@ a_cmds="
|
||||
x_cmds="kill ssh telnet unset unalias export"
|
||||
|
||||
# Preserve existing completion
|
||||
if [ "$_fzf_completion_loaded" != '0.10.8' ]; then
|
||||
if [ "$_fzf_completion_loaded" != '0.11.3' ]; then
|
||||
# Really wish I could use associative array but OSX comes with bash 3.2 :(
|
||||
eval $(complete | \grep '\-F' | \grep -v _fzf_ |
|
||||
\grep -E " ($(echo $d_cmds $f_cmds $a_cmds $x_cmds | sed 's/ /|/g' | sed 's/+/\\+/g'))$" | _fzf_orig_completion_filter)
|
||||
export _fzf_completion_loaded=0.10.8
|
||||
\grep -E " ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g' | sed 's/+/\\+/g'))$" | _fzf_orig_completion_filter)
|
||||
export _fzf_completion_loaded=0.11.3
|
||||
fi
|
||||
|
||||
if type _completion_loader > /dev/null 2>&1; then
|
||||
@@ -277,21 +285,16 @@ _fzf_defc() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Directory
|
||||
for cmd in $d_cmds; do
|
||||
_fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o plusdirs"
|
||||
done
|
||||
|
||||
# File
|
||||
for cmd in $f_cmds; do
|
||||
_fzf_defc "$cmd" _fzf_file_completion "-o default -o bashdefault"
|
||||
done
|
||||
|
||||
# Anything
|
||||
for cmd in $a_cmds; do
|
||||
_fzf_defc "$cmd" _fzf_path_completion "-o default -o bashdefault"
|
||||
done
|
||||
|
||||
# Directory
|
||||
for cmd in $d_cmds; do
|
||||
_fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o plusdirs"
|
||||
done
|
||||
|
||||
unset _fzf_defc
|
||||
|
||||
# Kill completion
|
||||
@@ -306,4 +309,4 @@ complete -F _fzf_complete_unset -o default -o bashdefault unset
|
||||
complete -F _fzf_complete_export -o default -o bashdefault export
|
||||
complete -F _fzf_complete_unalias -o default -o bashdefault unalias
|
||||
|
||||
unset cmd d_cmds f_cmds a_cmds x_cmds
|
||||
unset cmd d_cmds a_cmds x_cmds
|
||||
|
@@ -10,12 +10,32 @@
|
||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||
|
||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||
_fzf_compgen_path() {
|
||||
echo "$1"
|
||||
\find -L "$1" \
|
||||
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||
}
|
||||
fi
|
||||
|
||||
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
||||
_fzf_compgen_dir() {
|
||||
\find -L "$1" \
|
||||
-name .git -prune -o -name .svn -prune -o -type d \
|
||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||
}
|
||||
fi
|
||||
|
||||
###########################################################
|
||||
|
||||
__fzf_generic_path_completion() {
|
||||
local base lbuf find_opts fzf_opts suffix tail fzf dir leftover matches nnm
|
||||
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches nnm
|
||||
# (Q) flag removes a quoting level: "foo\ bar" => "foo bar"
|
||||
base=${(Q)1}
|
||||
lbuf=$2
|
||||
find_opts=$3
|
||||
compgen=$3
|
||||
fzf_opts=$4
|
||||
suffix=$5
|
||||
tail=$6
|
||||
@@ -30,9 +50,10 @@ __fzf_generic_path_completion() {
|
||||
if [ -z "$dir" -o -d ${~dir} ]; then
|
||||
leftover=${base/#"$dir"}
|
||||
leftover=${leftover/#\/}
|
||||
[ "$dir" = './' ] && dir=''
|
||||
[ -z "$dir" ] && dir='.'
|
||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||
dir=${~dir}
|
||||
matches=$(\find -L $dir* ${=find_opts} 2> /dev/null | ${=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"
|
||||
done)
|
||||
matches=${matches% }
|
||||
@@ -49,37 +70,29 @@ __fzf_generic_path_completion() {
|
||||
}
|
||||
|
||||
_fzf_path_completion() {
|
||||
__fzf_generic_path_completion "$1" "$2" \
|
||||
"-name .git -prune -o -name .svn -prune -o -type d -print -o -type f -print -o -type l -print" \
|
||||
__fzf_generic_path_completion "$1" "$2" _fzf_compgen_path \
|
||||
"-m" "" " "
|
||||
}
|
||||
|
||||
_fzf_dir_completion() {
|
||||
__fzf_generic_path_completion "$1" "$2" \
|
||||
"-name .git -prune -o -name .svn -prune -o -type d -print" \
|
||||
__fzf_generic_path_completion "$1" "$2" _fzf_compgen_dir \
|
||||
"" "/" ""
|
||||
}
|
||||
|
||||
_fzf_feed_fifo() (
|
||||
rm -f "$fifo"
|
||||
mkfifo "$fifo"
|
||||
cat <&0 > "$fifo" &
|
||||
)
|
||||
|
||||
_fzf_complete() {
|
||||
local fifo fzf_opts lbuf fzf matches
|
||||
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
|
||||
local fzf_opts lbuf fzf matches post
|
||||
fzf_opts=$1
|
||||
lbuf=$2
|
||||
post="${funcstack[2]}_post"
|
||||
type $post > /dev/null 2>&1 || post=cat
|
||||
|
||||
[ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
|
||||
|
||||
_fzf_feed_fifo "$fifo"
|
||||
matches=$(cat "$fifo" | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=fzf_opts} -q "${(Q)prefix}" | tr '\n' ' ')
|
||||
matches=$(cat | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=fzf_opts} -q "${(Q)prefix}" | $post | tr '\n' ' ')
|
||||
if [ -n "$matches" ]; then
|
||||
LBUFFER="$lbuf$matches"
|
||||
fi
|
||||
zle redisplay
|
||||
rm -f "$fifo"
|
||||
}
|
||||
|
||||
_fzf_complete_telnet() {
|
||||
@@ -144,7 +157,7 @@ fzf-completion() {
|
||||
zle redisplay
|
||||
# Trigger sequence given
|
||||
elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
||||
d_cmds=(cd pushd rmdir)
|
||||
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
|
||||
|
||||
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
|
||||
[ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}}
|
||||
|
@@ -25,7 +25,16 @@ __fzf_select_tmux__() {
|
||||
else
|
||||
height="-l $height"
|
||||
fi
|
||||
tmux split-window $height "cd $(printf %q "$PWD"); FZF_CTRL_T_COMMAND=$(printf %q "$FZF_CTRL_T_COMMAND") bash -c 'source ~/.fzf.bash; 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__() {
|
||||
if [ ${FZF_TMUX:-1} -ne 0 -a ${LINES:-40} -gt 15 ]; then
|
||||
__fzf_select_tmux__
|
||||
else
|
||||
tmux send-keys -t $TMUX_PANE "$(__fzf_select__)"
|
||||
fi
|
||||
}
|
||||
|
||||
__fzf_cd__() {
|
||||
@@ -50,7 +59,11 @@ __fzf_history__() (
|
||||
)
|
||||
|
||||
__use_tmux=0
|
||||
[ -n "$TMUX_PANE" -a ${FZF_TMUX:-1} -ne 0 -a ${LINES:-40} -gt 15 ] && __use_tmux=1
|
||||
__use_tmux_auto=0
|
||||
if [ -n "$TMUX_PANE" ]; then
|
||||
[ ${FZF_TMUX:-1} -ne 0 -a ${LINES:-40} -gt 15 ] && __use_tmux=1
|
||||
[ $BASH_VERSINFO -gt 3 ] && __use_tmux_auto=1
|
||||
fi
|
||||
|
||||
if [ -z "$(set -o | \grep '^vi.*on')" ]; then
|
||||
# Required to refresh the prompt after fzf
|
||||
@@ -58,7 +71,9 @@ if [ -z "$(set -o | \grep '^vi.*on')" ]; then
|
||||
bind '"\e^": history-expand-line'
|
||||
|
||||
# CTRL-T - Paste the selected file path into the command line
|
||||
if [ $__use_tmux -eq 1 ]; then
|
||||
if [ $__use_tmux_auto -eq 1 ]; then
|
||||
bind -x '"\C-t": "__fzf_select_tmux_auto__"'
|
||||
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"'
|
||||
else
|
||||
bind '"\C-t": " \C-u \C-a\C-k$(__fzf_select__)\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er \C-h"'
|
||||
@@ -76,7 +91,9 @@ else
|
||||
|
||||
# CTRL-T - Paste the selected file path into the command line
|
||||
# - FIXME: Selected items are attached to the end regardless of cursor position
|
||||
if [ $__use_tmux -eq 1 ]; then
|
||||
if [ $__use_tmux_auto -eq 1 ]; then
|
||||
bind -x '"\C-t": "__fzf_select_tmux_auto__"'
|
||||
elif [ $__use_tmux -eq 1 ]; then
|
||||
bind '"\C-t": "\e$a \eddi$(__fzf_select_tmux__)\C-x\C-e\e0P$xa"'
|
||||
else
|
||||
bind '"\C-t": "\e$a \eddi$(__fzf_select__)\C-x\C-e\e0Px$a \C-x\C-r\exa "'
|
||||
@@ -92,6 +109,6 @@ else
|
||||
bind -m vi-command '"\ec": "i\ec"'
|
||||
fi
|
||||
|
||||
unset __use_tmux
|
||||
unset -v __use_tmux __use_tmux_auto
|
||||
|
||||
fi
|
||||
|
@@ -20,6 +20,7 @@ function fzf_key_bindings
|
||||
-o -type d -print \
|
||||
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"
|
||||
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)" -m > $TMPDIR/fzf.result"
|
||||
and sleep 0
|
||||
and commandline -i (cat $TMPDIR/fzf.result | __fzf_escape)
|
||||
commandline -f repaint
|
||||
rm -f $TMPDIR/fzf.result
|
||||
|
@@ -16,7 +16,6 @@ RUN cd / && curl \
|
||||
https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz | \
|
||||
tar -xz && mv go go1.5
|
||||
|
||||
ENV GO15VENDOREXPERIMENT 1
|
||||
ENV GOROOT_BOOTSTRAP /go1.4
|
||||
ENV GOROOT /go1.5
|
||||
ENV GOPATH /go
|
||||
|
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Junegunn Choi
|
||||
Copyright (c) 2016 Junegunn Choi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
10
src/Makefile
10
src/Makefile
@@ -41,8 +41,8 @@ android-build:
|
||||
rm -f $(RELEASEARM7)
|
||||
|
||||
test:
|
||||
GO15VENDOREXPERIMENT=1 go get
|
||||
GO15VENDOREXPERIMENT=1 SHELL=/bin/sh go test -v ./...
|
||||
go get
|
||||
SHELL=/bin/sh go test -v ./...
|
||||
|
||||
install: $(BINDIR)/fzf
|
||||
|
||||
@@ -53,10 +53,10 @@ clean:
|
||||
cd fzf && rm -f fzf-*
|
||||
|
||||
fzf/$(BINARY32): $(SOURCES)
|
||||
cd fzf && GO15VENDOREXPERIMENT=1 GOARCH=386 CGO_ENABLED=1 go build -a -o $(BINARY32)
|
||||
cd fzf && GOARCH=386 CGO_ENABLED=1 go build -a -o $(BINARY32)
|
||||
|
||||
fzf/$(BINARY64): $(SOURCES)
|
||||
cd fzf && GO15VENDOREXPERIMENT=1 go build -a -tags "$(TAGS)" -o $(BINARY64)
|
||||
cd fzf && go build -a -tags "$(TAGS)" -o $(BINARY64)
|
||||
|
||||
$(BINDIR)/fzf: fzf/$(BINARY64) | $(BINDIR)
|
||||
cp -f fzf/$(BINARY64) $(BINDIR)
|
||||
@@ -94,7 +94,7 @@ linux: docker-centos
|
||||
/bin/bash -ci 'cd /go/src/github.com/junegunn/fzf/src; make TAGS=static'
|
||||
|
||||
ubuntu-android: docker-android
|
||||
docker run -i -t -v $(GOPATH):/go junegunn/$@-sandbox \
|
||||
docker run -i -t -v $(GOPATH):/go junegunn/android-sandbox \
|
||||
sh -c 'cd /go/src/github.com/junegunn/fzf/src; /bin/bash'
|
||||
|
||||
android: docker-android
|
||||
|
@@ -6,8 +6,11 @@ import (
|
||||
)
|
||||
|
||||
func TestChunkList(t *testing.T) {
|
||||
// FIXME global
|
||||
sortCriteria = []criterion{byMatchLen, byLength}
|
||||
|
||||
cl := NewChunkList(func(s []byte, i int) *Item {
|
||||
return &Item{text: []rune(string(s)), rank: Rank{0, 0, uint32(i * 2)}}
|
||||
return &Item{text: []rune(string(s)), rank: buildEmptyRank(int32(i * 2))}
|
||||
})
|
||||
|
||||
// Snapshot
|
||||
@@ -36,8 +39,11 @@ func TestChunkList(t *testing.T) {
|
||||
if len(*chunk1) != 2 {
|
||||
t.Error("Snapshot should contain only two items")
|
||||
}
|
||||
if string((*chunk1)[0].text) != "hello" || (*chunk1)[0].rank.index != 0 ||
|
||||
string((*chunk1)[1].text) != "world" || (*chunk1)[1].rank.index != 2 {
|
||||
last := func(arr [5]int32) int32 {
|
||||
return arr[len(arr)-1]
|
||||
}
|
||||
if string((*chunk1)[0].text) != "hello" || last((*chunk1)[0].rank) != 0 ||
|
||||
string((*chunk1)[1].text) != "world" || last((*chunk1)[1].rank) != 2 {
|
||||
t.Error("Invalid data")
|
||||
}
|
||||
if chunk1.IsFull() {
|
||||
|
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
const (
|
||||
// Current version
|
||||
version = "0.11.1"
|
||||
version = "0.11.3"
|
||||
|
||||
// Core
|
||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||
@@ -18,7 +18,8 @@ const (
|
||||
defaultCommand = `find . -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | sed s/^..//`
|
||||
|
||||
// Terminal
|
||||
initialDelay = 100 * time.Millisecond
|
||||
initialDelay = 20 * time.Millisecond
|
||||
initialDelayTac = 100 * time.Millisecond
|
||||
spinnerDuration = 200 * time.Millisecond
|
||||
|
||||
// Matcher
|
||||
|
22
src/core.go
22
src/core.go
@@ -3,7 +3,7 @@ Package fzf implements fzf, a command-line fuzzy finder.
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Junegunn Choi
|
||||
Copyright (c) 2016 Junegunn Choi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -52,7 +52,7 @@ func Run(opts *Options) {
|
||||
initProcs()
|
||||
|
||||
sort := opts.Sort > 0
|
||||
rankTiebreak = opts.Tiebreak
|
||||
sortCriteria = opts.Criteria
|
||||
|
||||
if opts.Version {
|
||||
fmt.Println(version)
|
||||
@@ -103,9 +103,8 @@ func Run(opts *Options) {
|
||||
runes, colors := ansiProcessor(data)
|
||||
return &Item{
|
||||
text: runes,
|
||||
index: uint32(index),
|
||||
colors: colors,
|
||||
rank: Rank{0, 0, uint32(index)}}
|
||||
rank: buildEmptyRank(int32(index))}
|
||||
})
|
||||
} else {
|
||||
chunkList = NewChunkList(func(data []byte, index int) *Item {
|
||||
@@ -120,9 +119,8 @@ func Run(opts *Options) {
|
||||
item := Item{
|
||||
text: joinTokens(trans),
|
||||
origText: &runes,
|
||||
index: uint32(index),
|
||||
colors: nil,
|
||||
rank: Rank{0, 0, uint32(index)}}
|
||||
rank: buildEmptyRank(int32(index))}
|
||||
|
||||
trimmed, colors := ansiProcessorRunes(item.text)
|
||||
item.text = trimmed
|
||||
@@ -141,9 +139,19 @@ func Run(opts *Options) {
|
||||
}
|
||||
|
||||
// Matcher
|
||||
forward := true
|
||||
for _, cri := range opts.Criteria[1:] {
|
||||
if cri == byEnd {
|
||||
forward = false
|
||||
break
|
||||
}
|
||||
if cri == byBegin {
|
||||
break
|
||||
}
|
||||
}
|
||||
patternBuilder := func(runes []rune) *Pattern {
|
||||
return BuildPattern(
|
||||
opts.Fuzzy, opts.Extended, opts.Case, opts.Tiebreak != byEnd,
|
||||
opts.Fuzzy, opts.Extended, opts.Case, forward,
|
||||
opts.Nth, opts.Delimiter, runes)
|
||||
}
|
||||
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)
|
||||
|
113
src/item.go
113
src/item.go
@@ -20,31 +20,41 @@ type Item struct {
|
||||
text []rune
|
||||
origText *[]rune
|
||||
transformed []Token
|
||||
index uint32
|
||||
offsets []Offset
|
||||
colors []ansiOffset
|
||||
rank Rank
|
||||
rank [5]int32
|
||||
}
|
||||
|
||||
// Rank is used to sort the search result
|
||||
type Rank struct {
|
||||
matchlen uint16
|
||||
tiebreak uint16
|
||||
index uint32
|
||||
// Sort criteria to use. Never changes once fzf is started.
|
||||
var sortCriteria []criterion
|
||||
|
||||
func isRankValid(rank [5]int32) bool {
|
||||
// Exclude ordinal index
|
||||
for _, r := range rank[:4] {
|
||||
if r > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Tiebreak criterion to use. Never changes once fzf is started.
|
||||
var rankTiebreak tiebreak
|
||||
func buildEmptyRank(index int32) [5]int32 {
|
||||
return [5]int32{0, 0, 0, 0, index}
|
||||
}
|
||||
|
||||
func (item *Item) Index() int32 {
|
||||
return item.rank[4]
|
||||
}
|
||||
|
||||
// Rank calculates rank of the Item
|
||||
func (item *Item) Rank(cache bool) Rank {
|
||||
if cache && (item.rank.matchlen > 0 || item.rank.tiebreak > 0) {
|
||||
func (item *Item) Rank(cache bool) [5]int32 {
|
||||
if cache && isRankValid(item.rank) {
|
||||
return item.rank
|
||||
}
|
||||
matchlen := 0
|
||||
prevEnd := 0
|
||||
lenSum := 0
|
||||
minBegin := math.MaxUint16
|
||||
minBegin := math.MaxInt32
|
||||
for _, offset := range item.offsets {
|
||||
begin := int(offset[0])
|
||||
end := int(offset[1])
|
||||
@@ -64,32 +74,42 @@ func (item *Item) Rank(cache bool) Rank {
|
||||
}
|
||||
}
|
||||
if matchlen == 0 {
|
||||
matchlen = math.MaxUint16
|
||||
matchlen = math.MaxInt32
|
||||
}
|
||||
var tiebreak uint16
|
||||
switch rankTiebreak {
|
||||
case byLength:
|
||||
// It is guaranteed that .transformed in not null in normal execution
|
||||
if item.transformed != nil {
|
||||
// If offsets is empty, lenSum will be 0, but we don't care
|
||||
tiebreak = uint16(lenSum)
|
||||
} else {
|
||||
tiebreak = uint16(len(item.text))
|
||||
rank := buildEmptyRank(item.Index())
|
||||
for idx, criterion := range sortCriteria {
|
||||
var val int32
|
||||
switch criterion {
|
||||
case byMatchLen:
|
||||
val = int32(matchlen)
|
||||
case byLength:
|
||||
// It is guaranteed that .transformed in not null in normal execution
|
||||
if item.transformed != nil {
|
||||
// If offsets is empty, lenSum will be 0, but we don't care
|
||||
val = int32(lenSum)
|
||||
} else {
|
||||
val = int32(len(item.text))
|
||||
}
|
||||
case byBegin:
|
||||
// We can't just look at item.offsets[0][0] because it can be an inverse term
|
||||
whitePrefixLen := 0
|
||||
for idx, r := range item.text {
|
||||
whitePrefixLen = idx
|
||||
if idx == minBegin || r != ' ' && r != '\t' {
|
||||
break
|
||||
}
|
||||
}
|
||||
val = int32(minBegin - whitePrefixLen)
|
||||
case byEnd:
|
||||
if prevEnd > 0 {
|
||||
val = int32(1 + len(item.text) - prevEnd)
|
||||
} else {
|
||||
// Empty offsets due to inverse terms.
|
||||
val = 1
|
||||
}
|
||||
}
|
||||
case byBegin:
|
||||
// We can't just look at item.offsets[0][0] because it can be an inverse term
|
||||
tiebreak = uint16(minBegin)
|
||||
case byEnd:
|
||||
if prevEnd > 0 {
|
||||
tiebreak = uint16(1 + len(item.text) - prevEnd)
|
||||
} else {
|
||||
// Empty offsets due to inverse terms.
|
||||
tiebreak = 1
|
||||
}
|
||||
case byIndex:
|
||||
tiebreak = 1
|
||||
rank[idx] = val
|
||||
}
|
||||
rank := Rank{uint16(matchlen), tiebreak, item.index}
|
||||
if cache {
|
||||
item.rank = rank
|
||||
}
|
||||
@@ -254,18 +274,15 @@ func (a ByRelevanceTac) Less(i, j int) bool {
|
||||
return compareRanks(irank, jrank, true)
|
||||
}
|
||||
|
||||
func compareRanks(irank Rank, jrank Rank, tac bool) bool {
|
||||
if irank.matchlen < jrank.matchlen {
|
||||
return true
|
||||
} else if irank.matchlen > jrank.matchlen {
|
||||
return false
|
||||
func compareRanks(irank [5]int32, jrank [5]int32, tac bool) bool {
|
||||
for idx := 0; idx < 4; idx++ {
|
||||
left := irank[idx]
|
||||
right := jrank[idx]
|
||||
if left < right {
|
||||
return true
|
||||
} else if left > right {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if irank.tiebreak < jrank.tiebreak {
|
||||
return true
|
||||
} else if irank.tiebreak > jrank.tiebreak {
|
||||
return false
|
||||
}
|
||||
|
||||
return (irank.index <= jrank.index) != tac
|
||||
return (irank[4] <= jrank[4]) != tac
|
||||
}
|
||||
|
@@ -23,31 +23,34 @@ func TestOffsetSort(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRankComparison(t *testing.T) {
|
||||
if compareRanks(Rank{3, 0, 5}, Rank{2, 0, 7}, false) ||
|
||||
!compareRanks(Rank{3, 0, 5}, Rank{3, 0, 6}, false) ||
|
||||
!compareRanks(Rank{1, 2, 3}, Rank{1, 3, 2}, false) ||
|
||||
!compareRanks(Rank{0, 0, 0}, Rank{0, 0, 0}, false) {
|
||||
if compareRanks([5]int32{3, 0, 0, 0, 5}, [5]int32{2, 0, 0, 0, 7}, false) ||
|
||||
!compareRanks([5]int32{3, 0, 0, 0, 5}, [5]int32{3, 0, 0, 0, 6}, false) ||
|
||||
!compareRanks([5]int32{1, 2, 0, 0, 3}, [5]int32{1, 3, 0, 0, 2}, false) ||
|
||||
!compareRanks([5]int32{0, 0, 0, 0, 0}, [5]int32{0, 0, 0, 0, 0}, false) {
|
||||
t.Error("Invalid order")
|
||||
}
|
||||
|
||||
if compareRanks(Rank{3, 0, 5}, Rank{2, 0, 7}, true) ||
|
||||
!compareRanks(Rank{3, 0, 5}, Rank{3, 0, 6}, false) ||
|
||||
!compareRanks(Rank{1, 2, 3}, Rank{1, 3, 2}, true) ||
|
||||
!compareRanks(Rank{0, 0, 0}, Rank{0, 0, 0}, false) {
|
||||
if compareRanks([5]int32{3, 0, 0, 0, 5}, [5]int32{2, 0, 0, 0, 7}, true) ||
|
||||
!compareRanks([5]int32{3, 0, 0, 0, 5}, [5]int32{3, 0, 0, 0, 6}, false) ||
|
||||
!compareRanks([5]int32{1, 2, 0, 0, 3}, [5]int32{1, 3, 0, 0, 2}, true) ||
|
||||
!compareRanks([5]int32{0, 0, 0, 0, 0}, [5]int32{0, 0, 0, 0, 0}, false) {
|
||||
t.Error("Invalid order (tac)")
|
||||
}
|
||||
}
|
||||
|
||||
// Match length, string length, index
|
||||
func TestItemRank(t *testing.T) {
|
||||
// FIXME global
|
||||
sortCriteria = []criterion{byMatchLen, byLength}
|
||||
|
||||
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
|
||||
item1 := Item{text: strs[0], index: 1, offsets: []Offset{}}
|
||||
item1 := Item{text: strs[0], offsets: []Offset{}, rank: [5]int32{0, 0, 0, 0, 1}}
|
||||
rank1 := item1.Rank(true)
|
||||
if rank1.matchlen != math.MaxUint16 || rank1.tiebreak != 3 || rank1.index != 1 {
|
||||
if rank1[0] != math.MaxInt32 || rank1[1] != 3 || rank1[4] != 1 {
|
||||
t.Error(item1.Rank(true))
|
||||
}
|
||||
// Only differ in index
|
||||
item2 := Item{text: strs[0], index: 0, offsets: []Offset{}}
|
||||
item2 := Item{text: strs[0], offsets: []Offset{}}
|
||||
|
||||
items := []*Item{&item1, &item2}
|
||||
sort.Sort(ByRelevance(items))
|
||||
@@ -63,10 +66,10 @@ func TestItemRank(t *testing.T) {
|
||||
}
|
||||
|
||||
// Sort by relevance
|
||||
item3 := Item{text: strs[1], rank: Rank{0, 0, 2}, offsets: []Offset{Offset{1, 3}, Offset{5, 7}}}
|
||||
item4 := Item{text: strs[1], rank: Rank{0, 0, 2}, offsets: []Offset{Offset{1, 2}, Offset{6, 7}}}
|
||||
item5 := Item{text: strs[2], rank: Rank{0, 0, 2}, offsets: []Offset{Offset{1, 3}, Offset{5, 7}}}
|
||||
item6 := Item{text: strs[2], rank: Rank{0, 0, 2}, offsets: []Offset{Offset{1, 2}, Offset{6, 7}}}
|
||||
item3 := Item{text: strs[1], rank: [5]int32{0, 0, 0, 0, 2}, offsets: []Offset{Offset{1, 3}, Offset{5, 7}}}
|
||||
item4 := Item{text: strs[1], rank: [5]int32{0, 0, 0, 0, 2}, offsets: []Offset{Offset{1, 2}, Offset{6, 7}}}
|
||||
item5 := Item{text: strs[2], rank: [5]int32{0, 0, 0, 0, 2}, offsets: []Offset{Offset{1, 3}, Offset{5, 7}}}
|
||||
item6 := Item{text: strs[2], rank: [5]int32{0, 0, 0, 0, 2}, offsets: []Offset{Offset{1, 2}, Offset{6, 7}}}
|
||||
items = []*Item{&item1, &item2, &item3, &item4, &item5, &item6}
|
||||
sort.Sort(ByRelevance(items))
|
||||
if items[0] != &item6 || items[1] != &item4 ||
|
||||
|
@@ -88,7 +88,7 @@ func (mg *Merger) cacheable() bool {
|
||||
|
||||
func (mg *Merger) mergedGet(idx int) *Item {
|
||||
for i := len(mg.merged); i <= idx; i++ {
|
||||
minRank := Rank{0, 0, 0}
|
||||
minRank := buildEmptyRank(0)
|
||||
minIdx := -1
|
||||
for listIdx, list := range mg.lists {
|
||||
cursor := mg.cursors[listIdx]
|
||||
|
@@ -23,7 +23,7 @@ func randItem() *Item {
|
||||
}
|
||||
return &Item{
|
||||
text: []rune(str),
|
||||
index: rand.Uint32(),
|
||||
rank: buildEmptyRank(rand.Int31()),
|
||||
offsets: offsets}
|
||||
}
|
||||
|
||||
|
@@ -27,7 +27,8 @@ const usage = `usage: fzf [options]
|
||||
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
|
||||
+s, --no-sort Do not sort the result
|
||||
--tac Reverse the order of the input
|
||||
--tiebreak=CRITERION Sort criterion when the scores are tied;
|
||||
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
|
||||
when the scores are tied;
|
||||
[length|begin|end|index] (default: length)
|
||||
|
||||
Interface
|
||||
@@ -75,13 +76,13 @@ const (
|
||||
)
|
||||
|
||||
// Sort criteria
|
||||
type tiebreak int
|
||||
type criterion int
|
||||
|
||||
const (
|
||||
byLength tiebreak = iota
|
||||
byMatchLen criterion = iota
|
||||
byLength
|
||||
byBegin
|
||||
byEnd
|
||||
byIndex
|
||||
)
|
||||
|
||||
func defaultMargin() [4]string {
|
||||
@@ -98,7 +99,7 @@ type Options struct {
|
||||
Delimiter Delimiter
|
||||
Sort int
|
||||
Tac bool
|
||||
Tiebreak tiebreak
|
||||
Criteria []criterion
|
||||
Multi bool
|
||||
Ansi bool
|
||||
Mouse bool
|
||||
@@ -145,7 +146,7 @@ func defaultOptions() *Options {
|
||||
Delimiter: Delimiter{},
|
||||
Sort: 1000,
|
||||
Tac: false,
|
||||
Tiebreak: byLength,
|
||||
Criteria: []criterion{byMatchLen, byLength},
|
||||
Multi: false,
|
||||
Ansi: false,
|
||||
Mouse: true,
|
||||
@@ -361,20 +362,39 @@ func parseKeyChords(str string, message string) map[int]string {
|
||||
return chords
|
||||
}
|
||||
|
||||
func parseTiebreak(str string) tiebreak {
|
||||
switch strings.ToLower(str) {
|
||||
case "length":
|
||||
return byLength
|
||||
case "index":
|
||||
return byIndex
|
||||
case "begin":
|
||||
return byBegin
|
||||
case "end":
|
||||
return byEnd
|
||||
default:
|
||||
errorExit("invalid sort criterion: " + str)
|
||||
func parseTiebreak(str string) []criterion {
|
||||
criteria := []criterion{byMatchLen}
|
||||
hasIndex := false
|
||||
hasLength := false
|
||||
hasBegin := false
|
||||
hasEnd := false
|
||||
check := func(notExpected *bool, name string) {
|
||||
if *notExpected {
|
||||
errorExit("duplicate sort criteria: " + name)
|
||||
}
|
||||
if hasIndex {
|
||||
errorExit("index should be the last criterion")
|
||||
}
|
||||
*notExpected = true
|
||||
}
|
||||
return byLength
|
||||
for _, str := range strings.Split(strings.ToLower(str), ",") {
|
||||
switch str {
|
||||
case "index":
|
||||
check(&hasIndex, "index")
|
||||
case "length":
|
||||
check(&hasLength, "length")
|
||||
criteria = append(criteria, byLength)
|
||||
case "begin":
|
||||
check(&hasBegin, "begin")
|
||||
criteria = append(criteria, byBegin)
|
||||
case "end":
|
||||
check(&hasEnd, "end")
|
||||
criteria = append(criteria, byEnd)
|
||||
default:
|
||||
errorExit("invalid sort criterion: " + str)
|
||||
}
|
||||
}
|
||||
return criteria
|
||||
}
|
||||
|
||||
func dupeTheme(theme *curses.ColorTheme) *curses.ColorTheme {
|
||||
@@ -546,6 +566,10 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, toggleSort b
|
||||
keymap[key] = actToggleDown
|
||||
case "toggle-up":
|
||||
keymap[key] = actToggleUp
|
||||
case "toggle-in":
|
||||
keymap[key] = actToggleIn
|
||||
case "toggle-out":
|
||||
keymap[key] = actToggleOut
|
||||
case "toggle-all":
|
||||
keymap[key] = actToggleAll
|
||||
case "select-all":
|
||||
@@ -715,7 +739,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
case "--expect":
|
||||
opts.Expect = parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required")
|
||||
case "--tiebreak":
|
||||
opts.Tiebreak = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
||||
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
||||
case "--bind":
|
||||
keymap, opts.Execmap, opts.ToggleSort =
|
||||
parseKeymap(keymap, opts.Execmap, opts.ToggleSort, nextString(allArgs, &i, "bind expression required"))
|
||||
@@ -850,7 +874,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
} else if match, value := optString(arg, "--expect="); match {
|
||||
opts.Expect = parseKeyChords(value, "key names required")
|
||||
} else if match, value := optString(arg, "--tiebreak="); match {
|
||||
opts.Tiebreak = parseTiebreak(value)
|
||||
opts.Criteria = parseTiebreak(value)
|
||||
} else if match, value := optString(arg, "--color="); match {
|
||||
opts.Theme = parseTheme(opts.Theme, value)
|
||||
} else if match, value := optString(arg, "--bind="); match {
|
||||
|
@@ -306,10 +306,9 @@ func dupItem(item *Item, offsets []Offset) *Item {
|
||||
text: item.text,
|
||||
origText: item.origText,
|
||||
transformed: item.transformed,
|
||||
index: item.index,
|
||||
offsets: offsets,
|
||||
colors: item.colors,
|
||||
rank: Rank{0, 0, item.index}}
|
||||
rank: buildEmptyRank(item.Index())}
|
||||
}
|
||||
|
||||
func (p *Pattern) basicMatch(item *Item) (int, int, int) {
|
||||
|
@@ -4,7 +4,6 @@ import (
|
||||
"bufio"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
)
|
||||
@@ -59,7 +58,7 @@ func (r *Reader) readFromStdin() {
|
||||
}
|
||||
|
||||
func (r *Reader) readFromCommand(cmd string) {
|
||||
listCommand := exec.Command("sh", "-c", cmd)
|
||||
listCommand := util.ExecCommand(cmd)
|
||||
out, err := listCommand.StdoutPipe()
|
||||
if err != nil {
|
||||
return
|
||||
|
@@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"sort"
|
||||
@@ -22,6 +21,7 @@ import (
|
||||
|
||||
// Terminal represents terminal input/output
|
||||
type Terminal struct {
|
||||
initDelay time.Duration
|
||||
inlineInfo bool
|
||||
prompt string
|
||||
reverse bool
|
||||
@@ -50,7 +50,7 @@ type Terminal struct {
|
||||
progress int
|
||||
reading bool
|
||||
merger *Merger
|
||||
selected map[uint32]selectedItem
|
||||
selected map[int32]selectedItem
|
||||
reqBox *util.EventBox
|
||||
eventBox *util.EventBox
|
||||
mutex sync.Mutex
|
||||
@@ -125,6 +125,8 @@ const (
|
||||
actToggleAll
|
||||
actToggleDown
|
||||
actToggleUp
|
||||
actToggleIn
|
||||
actToggleOut
|
||||
actDown
|
||||
actUp
|
||||
actPageUp
|
||||
@@ -196,7 +198,14 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
header = reverseStringArray(opts.Header)
|
||||
}
|
||||
_tabStop = opts.Tabstop
|
||||
var delay time.Duration
|
||||
if opts.Tac {
|
||||
delay = initialDelayTac
|
||||
} else {
|
||||
delay = initialDelay
|
||||
}
|
||||
return &Terminal{
|
||||
initDelay: delay,
|
||||
inlineInfo: opts.InlineInfo,
|
||||
prompt: opts.Prompt,
|
||||
reverse: opts.Reverse,
|
||||
@@ -223,7 +232,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
ansi: opts.Ansi,
|
||||
reading: true,
|
||||
merger: EmptyMerger,
|
||||
selected: make(map[uint32]selectedItem),
|
||||
selected: make(map[int32]selectedItem),
|
||||
reqBox: util.NewEventBox(),
|
||||
eventBox: eventBox,
|
||||
mutex: sync.Mutex{},
|
||||
@@ -464,9 +473,8 @@ func (t *Terminal) printHeader() {
|
||||
state = newState
|
||||
item := &Item{
|
||||
text: []rune(trimmed),
|
||||
index: 0,
|
||||
colors: colors,
|
||||
rank: Rank{0, 0, 0}}
|
||||
rank: buildEmptyRank(0)}
|
||||
|
||||
t.move(line, 2, true)
|
||||
t.printHighlighted(item, false, C.ColHeader, 0, false)
|
||||
@@ -491,7 +499,7 @@ func (t *Terminal) printList() {
|
||||
}
|
||||
|
||||
func (t *Terminal) printItem(item *Item, current bool) {
|
||||
_, selected := t.selected[item.index]
|
||||
_, selected := t.selected[item.Index()]
|
||||
if current {
|
||||
C.CPrint(C.ColCursor, true, ">")
|
||||
if selected {
|
||||
@@ -711,7 +719,7 @@ func quoteEntry(entry string) string {
|
||||
|
||||
func executeCommand(template string, replacement string) {
|
||||
command := strings.Replace(template, "{}", replacement, -1)
|
||||
cmd := exec.Command("sh", "-c", command)
|
||||
cmd := util.ExecCommand(command)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
@@ -725,7 +733,7 @@ func (t *Terminal) Loop() {
|
||||
<-t.startChan
|
||||
{ // Late initialization
|
||||
intChan := make(chan os.Signal, 1)
|
||||
signal.Notify(intChan, os.Interrupt, os.Kill)
|
||||
signal.Notify(intChan, os.Interrupt, os.Kill, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-intChan
|
||||
t.reqBox.Set(reqQuit, nil)
|
||||
@@ -750,7 +758,7 @@ func (t *Terminal) Loop() {
|
||||
t.printHeader()
|
||||
t.mutex.Unlock()
|
||||
go func() {
|
||||
timer := time.NewTimer(initialDelay)
|
||||
timer := time.NewTimer(t.initDelay)
|
||||
<-timer.C
|
||||
t.reqBox.Set(reqRefresh, nil)
|
||||
}()
|
||||
@@ -836,8 +844,8 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
}
|
||||
selectItem := func(item *Item) bool {
|
||||
if _, found := t.selected[item.index]; !found {
|
||||
t.selected[item.index] = selectedItem{time.Now(), item.StringPtr(t.ansi)}
|
||||
if _, found := t.selected[item.Index()]; !found {
|
||||
t.selected[item.Index()] = selectedItem{time.Now(), item.StringPtr(t.ansi)}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -845,7 +853,7 @@ func (t *Terminal) Loop() {
|
||||
toggleY := func(y int) {
|
||||
item := t.merger.Get(y)
|
||||
if !selectItem(item) {
|
||||
delete(t.selected, item.index)
|
||||
delete(t.selected, item.Index())
|
||||
}
|
||||
}
|
||||
toggle := func() {
|
||||
@@ -934,7 +942,7 @@ func (t *Terminal) Loop() {
|
||||
if t.multi {
|
||||
for i := 0; i < t.merger.Length(); i++ {
|
||||
item := t.merger.Get(i)
|
||||
delete(t.selected, item.index)
|
||||
delete(t.selected, item.Index())
|
||||
}
|
||||
req(reqList, reqInfo)
|
||||
}
|
||||
@@ -950,6 +958,16 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
req(reqList, reqInfo)
|
||||
}
|
||||
case actToggleIn:
|
||||
if t.reverse {
|
||||
return doAction(actToggleUp, mapkey)
|
||||
}
|
||||
return doAction(actToggleDown, mapkey)
|
||||
case actToggleOut:
|
||||
if t.reverse {
|
||||
return doAction(actToggleDown, mapkey)
|
||||
}
|
||||
return doAction(actToggleUp, mapkey)
|
||||
case actToggleDown:
|
||||
if t.multi && t.merger.Length() > 0 {
|
||||
toggle()
|
||||
|
@@ -5,6 +5,7 @@ import "C"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
@@ -126,3 +127,12 @@ func TrimLen(runes []rune) int {
|
||||
}
|
||||
return i - j + 1
|
||||
}
|
||||
|
||||
// ExecCommand executes the given command with $SHELL
|
||||
func ExecCommand(command string) *exec.Cmd {
|
||||
shell := os.Getenv("SHELL")
|
||||
if len(shell) == 0 {
|
||||
shell = "sh"
|
||||
}
|
||||
return exec.Command(shell, "-c", command)
|
||||
}
|
||||
|
1
src/vendor/github.com/junegunn/go-runewidth
generated
vendored
1
src/vendor/github.com/junegunn/go-runewidth
generated
vendored
Submodule src/vendor/github.com/junegunn/go-runewidth deleted from 63c378b851
1
src/vendor/github.com/junegunn/go-shellwords
generated
vendored
1
src/vendor/github.com/junegunn/go-shellwords
generated
vendored
Submodule src/vendor/github.com/junegunn/go-shellwords deleted from 35d512af75
@@ -1,6 +1,7 @@
|
||||
Execute (Setup):
|
||||
let g:dir = fnamemodify(g:vader_file, ':p:h')
|
||||
Log 'Test directory: ' . g:dir
|
||||
Save &acd
|
||||
|
||||
Execute (fzf#run with dir option):
|
||||
let cwd = getcwd()
|
||||
@@ -35,6 +36,34 @@ Execute (fzf#run with string source):
|
||||
let result = sort(fzf#run({ 'source': 'echo hi', 'options': '-f i' }))
|
||||
AssertEqual ['hi'], result
|
||||
|
||||
Execute (fzf#run with dir option and noautochdir):
|
||||
set noacd
|
||||
let cwd = getcwd()
|
||||
call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'})
|
||||
" No change in working directory
|
||||
AssertEqual cwd, getcwd()
|
||||
|
||||
Execute (Incomplete fzf#run with dir option and autochdir):
|
||||
set acd
|
||||
let cwd = getcwd()
|
||||
call fzf#run({'source': [], 'sink': 'e', 'dir': '/tmp', 'options': '-0'})
|
||||
" No change in working directory even if &acd is set
|
||||
AssertEqual cwd, getcwd()
|
||||
|
||||
Execute (fzf#run with dir option and autochdir):
|
||||
set acd
|
||||
let cwd = getcwd()
|
||||
call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'})
|
||||
" Working directory changed due to &acd
|
||||
AssertEqual '/', getcwd()
|
||||
|
||||
Execute (fzf#run with dir option and autochdir when final cwd is same as dir):
|
||||
set acd
|
||||
cd /tmp
|
||||
call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/', 'options': '-1'})
|
||||
" Working directory changed due to &acd
|
||||
AssertEqual '/', getcwd()
|
||||
|
||||
Execute (Cleanup):
|
||||
unlet g:dir
|
||||
Restore
|
||||
|
259
test/test_go.rb
259
test/test_go.rb
@@ -143,8 +143,10 @@ class TestBase < Minitest::Test
|
||||
attr_reader :tmux
|
||||
|
||||
def tempname
|
||||
@temp_suffix ||= 0
|
||||
[TEMPNAME,
|
||||
caller_locations.map(&:label).find { |l| l =~ /^test_/ }].join '-'
|
||||
caller_locations.map(&:label).find { |l| l =~ /^test_/ },
|
||||
@temp_suffix].join '-'
|
||||
end
|
||||
|
||||
def setup
|
||||
@@ -158,6 +160,7 @@ class TestBase < Minitest::Test
|
||||
File.read(tempname)
|
||||
ensure
|
||||
File.unlink tempname while File.exists?(tempname)
|
||||
@temp_suffix += 1
|
||||
tmux.prepare
|
||||
end
|
||||
|
||||
@@ -459,8 +462,8 @@ class TestGoFZF < TestBase
|
||||
|
||||
def test_unicode_case
|
||||
writelines tempname, %w[строКА1 СТРОКА2 строка3 Строка4]
|
||||
assert_equal %w[СТРОКА2 Строка4], `cat #{tempname} | #{FZF} -fС`.split($/)
|
||||
assert_equal %w[строКА1 СТРОКА2 строка3 Строка4], `cat #{tempname} | #{FZF} -fс`.split($/)
|
||||
assert_equal %w[СТРОКА2 Строка4], `#{FZF} -fС < #{tempname}`.split($/)
|
||||
assert_equal %w[строКА1 СТРОКА2 строка3 Строка4], `#{FZF} -fс < #{tempname}`.split($/)
|
||||
end
|
||||
|
||||
def test_tiebreak
|
||||
@@ -472,7 +475,7 @@ class TestGoFZF < TestBase
|
||||
]
|
||||
writelines tempname, input
|
||||
|
||||
assert_equal input, `cat #{tempname} | #{FZF} -ffoobar --tiebreak=index`.split($/)
|
||||
assert_equal input, `#{FZF} -ffoobar --tiebreak=index < #{tempname}`.split($/)
|
||||
|
||||
by_length = %w[
|
||||
----foobar--
|
||||
@@ -480,8 +483,8 @@ class TestGoFZF < TestBase
|
||||
-------foobar-
|
||||
--foobar--------
|
||||
]
|
||||
assert_equal by_length, `cat #{tempname} | #{FZF} -ffoobar`.split($/)
|
||||
assert_equal by_length, `cat #{tempname} | #{FZF} -ffoobar --tiebreak=length`.split($/)
|
||||
assert_equal by_length, `#{FZF} -ffoobar < #{tempname}`.split($/)
|
||||
assert_equal by_length, `#{FZF} -ffoobar --tiebreak=length < #{tempname}`.split($/)
|
||||
|
||||
by_begin = %w[
|
||||
--foobar--------
|
||||
@@ -489,17 +492,175 @@ class TestGoFZF < TestBase
|
||||
-----foobar---
|
||||
-------foobar-
|
||||
]
|
||||
assert_equal by_begin, `cat #{tempname} | #{FZF} -ffoobar --tiebreak=begin`.split($/)
|
||||
assert_equal by_begin, `cat #{tempname} | #{FZF} -f"!z foobar" -x --tiebreak begin`.split($/)
|
||||
assert_equal by_begin, `#{FZF} -ffoobar --tiebreak=begin < #{tempname}`.split($/)
|
||||
assert_equal by_begin, `#{FZF} -f"!z foobar" -x --tiebreak begin < #{tempname}`.split($/)
|
||||
|
||||
assert_equal %w[
|
||||
-------foobar-
|
||||
----foobar--
|
||||
-----foobar---
|
||||
--foobar--------
|
||||
], `cat #{tempname} | #{FZF} -ffoobar --tiebreak end`.split($/)
|
||||
], `#{FZF} -ffoobar --tiebreak end < #{tempname}`.split($/)
|
||||
|
||||
assert_equal input, `cat #{tempname} | #{FZF} -f"!z" -x --tiebreak end`.split($/)
|
||||
assert_equal input, `#{FZF} -f"!z" -x --tiebreak end < #{tempname}`.split($/)
|
||||
end
|
||||
|
||||
# Since 0.11.2
|
||||
def test_tiebreak_list
|
||||
input = %w[
|
||||
f-o-o-b-a-r
|
||||
foobar----
|
||||
--foobar
|
||||
----foobar
|
||||
foobar--
|
||||
--foobar--
|
||||
foobar
|
||||
]
|
||||
writelines tempname, input
|
||||
|
||||
assert_equal %w[
|
||||
foobar----
|
||||
--foobar
|
||||
----foobar
|
||||
foobar--
|
||||
--foobar--
|
||||
foobar
|
||||
f-o-o-b-a-r
|
||||
], `#{FZF} -ffb --tiebreak=index < #{tempname}`.split($/)
|
||||
|
||||
by_length = %w[
|
||||
foobar
|
||||
--foobar
|
||||
foobar--
|
||||
foobar----
|
||||
----foobar
|
||||
--foobar--
|
||||
f-o-o-b-a-r
|
||||
]
|
||||
assert_equal by_length, `#{FZF} -ffb < #{tempname}`.split($/)
|
||||
assert_equal by_length, `#{FZF} -ffb --tiebreak=length < #{tempname}`.split($/)
|
||||
|
||||
assert_equal %w[
|
||||
foobar
|
||||
foobar--
|
||||
--foobar
|
||||
foobar----
|
||||
--foobar--
|
||||
----foobar
|
||||
f-o-o-b-a-r
|
||||
], `#{FZF} -ffb --tiebreak=length,begin < #{tempname}`.split($/)
|
||||
|
||||
assert_equal %w[
|
||||
foobar
|
||||
--foobar
|
||||
foobar--
|
||||
----foobar
|
||||
--foobar--
|
||||
foobar----
|
||||
f-o-o-b-a-r
|
||||
], `#{FZF} -ffb --tiebreak=length,end < #{tempname}`.split($/)
|
||||
|
||||
assert_equal %w[
|
||||
foobar----
|
||||
foobar--
|
||||
foobar
|
||||
--foobar
|
||||
--foobar--
|
||||
----foobar
|
||||
f-o-o-b-a-r
|
||||
], `#{FZF} -ffb --tiebreak=begin < #{tempname}`.split($/)
|
||||
|
||||
by_begin_end = %w[
|
||||
foobar
|
||||
foobar--
|
||||
foobar----
|
||||
--foobar
|
||||
--foobar--
|
||||
----foobar
|
||||
f-o-o-b-a-r
|
||||
]
|
||||
assert_equal by_begin_end, `#{FZF} -ffb --tiebreak=begin,length < #{tempname}`.split($/)
|
||||
assert_equal by_begin_end, `#{FZF} -ffb --tiebreak=begin,end < #{tempname}`.split($/)
|
||||
|
||||
assert_equal %w[
|
||||
--foobar
|
||||
----foobar
|
||||
foobar
|
||||
foobar--
|
||||
--foobar--
|
||||
foobar----
|
||||
f-o-o-b-a-r
|
||||
], `#{FZF} -ffb --tiebreak=end < #{tempname}`.split($/)
|
||||
|
||||
by_begin_end = %w[
|
||||
foobar
|
||||
--foobar
|
||||
----foobar
|
||||
foobar--
|
||||
--foobar--
|
||||
foobar----
|
||||
f-o-o-b-a-r
|
||||
]
|
||||
assert_equal by_begin_end, `#{FZF} -ffb --tiebreak=end,begin < #{tempname}`.split($/)
|
||||
assert_equal by_begin_end, `#{FZF} -ffb --tiebreak=end,length < #{tempname}`.split($/)
|
||||
end
|
||||
|
||||
def test_tiebreak_white_prefix
|
||||
writelines tempname, [
|
||||
'f o o b a r',
|
||||
' foo bar',
|
||||
' foobar',
|
||||
'----foo bar',
|
||||
'----foobar',
|
||||
' foo bar',
|
||||
' foobar--',
|
||||
' foobar',
|
||||
'--foo bar',
|
||||
'--foobar',
|
||||
'foobar',
|
||||
]
|
||||
|
||||
assert_equal [
|
||||
' foobar',
|
||||
' foobar',
|
||||
'foobar',
|
||||
' foobar--',
|
||||
'--foobar',
|
||||
'----foobar',
|
||||
' foo bar',
|
||||
' foo bar',
|
||||
'--foo bar',
|
||||
'----foo bar',
|
||||
'f o o b a r',
|
||||
], `#{FZF} -ffb < #{tempname}`.split($/)
|
||||
|
||||
assert_equal [
|
||||
' foobar',
|
||||
' foobar--',
|
||||
' foobar',
|
||||
'foobar',
|
||||
'--foobar',
|
||||
'----foobar',
|
||||
' foo bar',
|
||||
' foo bar',
|
||||
'--foo bar',
|
||||
'----foo bar',
|
||||
'f o o b a r',
|
||||
], `#{FZF} -ffb --tiebreak=begin < #{tempname}`.split($/)
|
||||
|
||||
assert_equal [
|
||||
' foobar',
|
||||
' foobar',
|
||||
'foobar',
|
||||
' foobar--',
|
||||
'--foobar',
|
||||
'----foobar',
|
||||
' foo bar',
|
||||
' foo bar',
|
||||
'--foo bar',
|
||||
'----foo bar',
|
||||
'f o o b a r',
|
||||
], `#{FZF} -ffb --tiebreak=begin,length < #{tempname}`.split($/)
|
||||
end
|
||||
|
||||
def test_tiebreak_length_with_nth
|
||||
@@ -517,7 +678,7 @@ class TestGoFZF < TestBase
|
||||
123:hello
|
||||
1234567:h
|
||||
]
|
||||
assert_equal output, `cat #{tempname} | #{FZF} -fh`.split($/)
|
||||
assert_equal output, `#{FZF} -fh < #{tempname}`.split($/)
|
||||
|
||||
output = %w[
|
||||
1234567:h
|
||||
@@ -525,7 +686,7 @@ class TestGoFZF < TestBase
|
||||
1:hell
|
||||
123:hello
|
||||
]
|
||||
assert_equal output, `cat #{tempname} | #{FZF} -fh -n2 -d:`.split($/)
|
||||
assert_equal output, `#{FZF} -fh -n2 -d: < #{tempname}`.split($/)
|
||||
end
|
||||
|
||||
def test_tiebreak_length_with_nth_trim_length
|
||||
@@ -544,7 +705,7 @@ class TestGoFZF < TestBase
|
||||
"apple juice bottle 1",
|
||||
"apple ui bottle 2",
|
||||
]
|
||||
assert_equal output, `cat #{tempname} | #{FZF} -fa -n1`.split($/)
|
||||
assert_equal output, `#{FZF} -fa -n1 < #{tempname}`.split($/)
|
||||
|
||||
# len(1 ~ 2)
|
||||
output = [
|
||||
@@ -553,7 +714,7 @@ class TestGoFZF < TestBase
|
||||
"apple juice bottle 1",
|
||||
"app ice bottle 3",
|
||||
]
|
||||
assert_equal output, `cat #{tempname} | #{FZF} -fai -n1..2`.split($/)
|
||||
assert_equal output, `#{FZF} -fai -n1..2 < #{tempname}`.split($/)
|
||||
|
||||
# len(1) + len(2)
|
||||
output = [
|
||||
@@ -562,7 +723,7 @@ class TestGoFZF < TestBase
|
||||
"apple ui bottle 2",
|
||||
"apple juice bottle 1",
|
||||
]
|
||||
assert_equal output, `cat #{tempname} | #{FZF} -x -f"a i" -n1,2`.split($/)
|
||||
assert_equal output, `#{FZF} -x -f"a i" -n1,2 < #{tempname}`.split($/)
|
||||
|
||||
# len(2)
|
||||
output = [
|
||||
@@ -571,8 +732,8 @@ class TestGoFZF < TestBase
|
||||
"app ice bottle 3",
|
||||
"apple juice bottle 1",
|
||||
]
|
||||
assert_equal output, `cat #{tempname} | #{FZF} -fi -n2`.split($/)
|
||||
assert_equal output, `cat #{tempname} | #{FZF} -fi -n2,1..2`.split($/)
|
||||
assert_equal output, `#{FZF} -fi -n2 < #{tempname}`.split($/)
|
||||
assert_equal output, `#{FZF} -fi -n2,1..2 < #{tempname}`.split($/)
|
||||
end
|
||||
|
||||
def test_tiebreak_end_backward_scan
|
||||
@@ -582,8 +743,8 @@ class TestGoFZF < TestBase
|
||||
]
|
||||
writelines tempname, input
|
||||
|
||||
assert_equal input.reverse, `cat #{tempname} | #{FZF} -f fb`.split($/)
|
||||
assert_equal input, `cat #{tempname} | #{FZF} -f fb --tiebreak=end`.split($/)
|
||||
assert_equal input.reverse, `#{FZF} -f fb < #{tempname}`.split($/)
|
||||
assert_equal input, `#{FZF} -f fb --tiebreak=end < #{tempname}`.split($/)
|
||||
end
|
||||
|
||||
def test_invalid_cache
|
||||
@@ -613,7 +774,7 @@ class TestGoFZF < TestBase
|
||||
File.open(tempname, 'w') do |f|
|
||||
f << data
|
||||
end
|
||||
assert_equal data, `cat #{tempname} | #{FZF} -f .`.chomp
|
||||
assert_equal data, `#{FZF} -f . < #{tempname}`.chomp
|
||||
end
|
||||
|
||||
def test_read0
|
||||
@@ -732,6 +893,22 @@ class TestGoFZF < TestBase
|
||||
File.unlink output rescue nil
|
||||
end
|
||||
|
||||
def test_execute_shell
|
||||
# Custom script to use as $SHELL
|
||||
output = tempname + '.out'
|
||||
File.unlink output rescue nil
|
||||
writelines tempname, ['#!/usr/bin/env bash', "echo $1 / $2 > #{output}"]
|
||||
system "chmod +x #{tempname}"
|
||||
|
||||
tmux.send_keys "echo foo | SHELL=#{tempname} fzf --bind 'enter:execute:{}bar'", :Enter
|
||||
tmux.until { |lines| lines[-2].include? '1/1' }
|
||||
tmux.send_keys :Enter
|
||||
tmux.send_keys 'C-c'
|
||||
assert_equal ['-c / "foo"bar'], File.readlines(output).map(&:chomp)
|
||||
ensure
|
||||
File.unlink output rescue nil
|
||||
end
|
||||
|
||||
def test_cycle
|
||||
tmux.send_keys "seq 8 | #{fzf :cycle}", :Enter
|
||||
tmux.until { |lines| lines[-2].include? '8/8' }
|
||||
@@ -881,25 +1058,25 @@ class TestGoFZF < TestBase
|
||||
}.each do |ts, exp|
|
||||
tmux.prepare
|
||||
tmux.send_keys %[cat #{tempname} | fzf --tabstop=#{ts}], :Enter
|
||||
tmux.until { |lines| lines[-3] == exp }
|
||||
tmux.until { |lines| exp.start_with? lines[-3].to_s.strip.sub(/\.\.$/, '') }
|
||||
tmux.send_keys :Enter
|
||||
end
|
||||
end
|
||||
|
||||
def test_with_nth
|
||||
writelines tempname, ['hello world ', 'byebye']
|
||||
assert_equal 'hello world ', `cat #{tempname} | #{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1`.chomp
|
||||
assert_equal 'hello world ', `#{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1 < #{tempname}`.chomp
|
||||
end
|
||||
|
||||
def test_with_nth_ansi
|
||||
writelines tempname, ["\x1b[33mhello \x1b[34;1mworld\x1b[m ", 'byebye']
|
||||
assert_equal 'hello world ', `cat #{tempname} | #{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1 --ansi`.chomp
|
||||
assert_equal 'hello world ', `#{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1 --ansi < #{tempname}`.chomp
|
||||
end
|
||||
|
||||
def test_with_nth_no_ansi
|
||||
src = "\x1b[33mhello \x1b[34;1mworld\x1b[m "
|
||||
writelines tempname, [src, 'byebye']
|
||||
assert_equal src, `cat #{tempname} | #{FZF} -fhehe -x -n 2.. --with-nth 2,1,1 --no-ansi`.chomp
|
||||
assert_equal src, `#{FZF} -fhehe -x -n 2.. --with-nth 2,1,1 --no-ansi < #{tempname}`.chomp
|
||||
end
|
||||
|
||||
def test_exit_0_exit_code
|
||||
@@ -1071,6 +1248,8 @@ module CompletionTest
|
||||
tmux.prepare
|
||||
tmux.send_keys 'cat /tmp/fzf-test/10**', :Tab, pane: 0
|
||||
tmux.until(1) { |lines| lines.item_count > 0 }
|
||||
tmux.send_keys ' !d'
|
||||
tmux.until(1) { |lines| lines[-2].include?(' 2/') }
|
||||
tmux.send_keys :BTab, :BTab
|
||||
tmux.until(1) { |lines| lines[-2].include?('(2)') }
|
||||
tmux.send_keys :Enter
|
||||
@@ -1106,17 +1285,33 @@ module CompletionTest
|
||||
tmux.send_keys 'C-u'
|
||||
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab, pane: 0
|
||||
tmux.until(1) { |lines| lines.item_count > 0 }
|
||||
tmux.send_keys :Enter
|
||||
tmux.send_keys 'C-K', :Enter
|
||||
tmux.until do |lines|
|
||||
tmux.send_keys 'C-L'
|
||||
lines[-1].end_with?('/tmp/fzf\ test/foobar')
|
||||
end
|
||||
|
||||
# Should include hidden files
|
||||
(1..100).each { |i| FileUtils.touch "/tmp/fzf-test/.hidden-#{i}" }
|
||||
tmux.send_keys 'C-u'
|
||||
tmux.send_keys 'cat /tmp/fzf-test/hidden**', :Tab, pane: 0
|
||||
tmux.until(1) do |lines|
|
||||
tmux.send_keys 'C-L'
|
||||
lines[-2].include?('100/') &&
|
||||
lines[-3].include?('/tmp/fzf-test/.hidden-')
|
||||
end
|
||||
ensure
|
||||
['/tmp/fzf-test', '/tmp/fzf test', '~/.fzf-home', 'no~such~user'].each do |f|
|
||||
FileUtils.rm_rf File.expand_path(f)
|
||||
end
|
||||
end
|
||||
|
||||
def test_file_completion_root
|
||||
tmux.send_keys 'ls /**', :Tab, pane: 0
|
||||
tmux.until(1) { |lines| lines.item_count > 0 }
|
||||
tmux.send_keys :Enter
|
||||
end
|
||||
|
||||
def test_dir_completion
|
||||
tmux.send_keys 'mkdir -p /tmp/fzf-test/d{1..100}; touch /tmp/fzf-test/d55/xxx', :Enter
|
||||
tmux.prepare
|
||||
@@ -1160,6 +1355,20 @@ module CompletionTest
|
||||
tmux.send_keys 'C-L'
|
||||
lines[-1] == "kill #{pid}"
|
||||
end
|
||||
|
||||
def test_custom_completion
|
||||
tmux.send_keys '_fzf_compgen_path() { echo "\$1"; seq 10; }', :Enter
|
||||
tmux.prepare
|
||||
tmux.send_keys 'ls /tmp/**', :Tab, pane: 0
|
||||
tmux.until(1) { |lines| lines.item_count == 11 }
|
||||
tmux.send_keys :BTab, :BTab, :BTab
|
||||
tmux.until(1) { |lines| lines[-2].include? '(3)' }
|
||||
tmux.send_keys :Enter
|
||||
tmux.until do |lines|
|
||||
tmux.send_keys 'C-L'
|
||||
lines[-1] == "ls /tmp 1 2"
|
||||
end
|
||||
end
|
||||
ensure
|
||||
Process.kill 'KILL', pid.to_i rescue nil if pid
|
||||
end
|
||||
|
Reference in New Issue
Block a user