Compare commits

..

25 Commits

Author SHA1 Message Date
Junegunn Choi
b47ab633e2 0.11.4 2016-03-03 01:57:28 +09:00
Junegunn Choi
09a2ab39fe [bash] Fix shellcheck warnings
Close #516
2016-03-02 23:59:42 +09:00
Junegunn Choi
6cf54833f7 Fix flaky test case 2016-03-02 03:29:08 +09:00
Junegunn Choi
2ccdf21a1f Add --hscroll-off=COL option
Close #513
2016-03-02 03:14:35 +09:00
Junegunn Choi
cf8afc527e Remove .gitmodules 2016-03-02 02:04:38 +09:00
Junegunn Choi
1d6f05f974 [man] Fix invalid exit status in man page
Close #511
2016-02-26 23:36:07 +09:00
Junegunn Choi
85751966e9 Merge pull request #506 from justinmk/fixvarmismatch
[vim] s:callback: Always return list.
2016-02-23 15:39:53 +09:00
Justin M. Keyes
a7bc9d5351 s:callback: Always return list.
Fixes "E706: Variable type mismatch for: ret" when an exception is
caught.
2016-02-23 00:36:36 -05:00
Junegunn Choi
42c006d07c Update install script to try "go get ..."
Related: #470, #497
2016-02-21 22:11:28 +09:00
Junegunn Choi
1b9ca314b8 Update build script
- GOPATH is no longer required
- fzf repository does not have to be in GOPATH
- Build Linux binary with Go 1.5.3
2016-02-20 21:16:24 +09:00
Junegunn Choi
e72a360337 Minor refactoring
- Slightly more efficient processing of Options
- Do not return reference type arguments that are mutated inside the
  function
- Use util.Constrain function when appropriate
2016-02-18 01:46:18 +09:00
Junegunn Choi
45108ddd53 Merge pull request #496 from noscript/master
Go 1.3 compatibility
2016-02-16 18:48:39 +09:00
Sergey Vlasov
e3401a0645 Go 1.3 compatibility 2016-02-16 11:28:40 +02:00
Junegunn Choi
26b9100709 Minor code cleanup 2016-02-16 12:35:42 +09:00
Junegunn Choi
a568120e42 Fix #494 - _fzf_complete hangs on zsh when not using tmux pane 2016-02-16 12:32:05 +09:00
Junegunn Choi
e57182c658 Merge pull request #488 from nhooyr/man-fix-redirect
[man] Remove useless `.R` macros
2016-02-12 13:29:39 +09:00
Anmol Sethi
6354dbbbdf Removed the useless .R macros
If you do `man fzf > /dev/null`, you'll get the following output

`R' is a string (producing the registered sign), not a macro.
`R' is a string (producing the registered sign), not a macro.
`R' is a string (producing the registered sign), not a macro.
`R' is a string (producing the registered sign), not a macro.
`R' is a string (producing the registered sign), not a macro.
`R' is a string (producing the registered sign), not a macro.

Removing these `.R` macros with a newline seems to have no effect on the
page but gets rid of the error.
2016-02-11 23:04:38 -05:00
Junegunn Choi
2b3e740569 [neovim] Fix error in finally block when callback failed
e.g. Opening another buffer when `set nohidden`

https://github.com/junegunn/fzf.vim/issues/77
2016-02-12 12:33:47 +09:00
Junegunn Choi
40d934e378 0.11.3 2016-02-07 11:00:10 +09:00
Junegunn Choi
e95d82748f Use $SHELL to start $FZF_DEFAULT_COMMAND (#481) 2016-02-07 01:49:29 +09:00
Junegunn Choi
30bd0b53db Fix #481 - Use $SHELL instead of sh in execute action
Note that $SHELL only points to the default shell instead of the current
shell. If you're on a non-default shell, you might want to override the
value like follows.

  SHELL=zsh fzf --bind 'enter:execute:echo $ZSH_VERSION; sleep 1'
2016-02-03 04:46:02 +09:00
Junegunn Choi
1893eca41a Handle SIGTERM gracefully (#482) 2016-02-02 17:51:21 +09:00
Junegunn Choi
82067463b8 [completion] _fzf_complete_COMMAND_post for post processing
e.g.

_fzf_complete_foo() {
  _fzf_complete "--multi --reverse --header-lines=3" "$@" < <(
    ls -al
  )
}

_fzf_complete_foo_post() {
  awk '{print $NF}'
}

[ -n "$BASH" ] && complete -F _fzf_complete_foo -o default -o bashdefault foo
2016-01-29 01:31:04 +09:00
Junegunn Choi
ce9c51d399 Typo 2016-01-20 01:39:55 +09:00
Junegunn Choi
96176476f3 Make fuzzy completion customizable with _fzf_compgen_{path,dir}
Notes:
- You can now override _fzf_compgen_path and _fzf_compgen_dir functions
  to use custom commands such as ag instead of find for listing
  completion candidates.
    - The first argument is the base path to start traversal
- Removed file-only completion in bash, i.e. _fzf_file_completion.
  Maintaining a list of commands that only expect files, not
  directories, is cumbersome (there are too many) and error-prone.

TBD:
- Added $FZF_COMPLETION_DIR_COMMANDS to customize the list of commands
  which use directory-only completion. The default is "cd pushd rmdir".
  Not sure if it's the best approach to address the requirement, I'll
  leave it as an undocumented feature.

Related: #406 (@thomcom), #456 (@frizinak)
2016-01-20 01:38:24 +09:00
23 changed files with 422 additions and 221 deletions

3
.gitignore vendored
View File

@@ -1,5 +1,6 @@
bin
src/fzf/fzf_*
src/fzf/fzf-*
gopath
pkg
Gemfile.lock
.DS_Store

0
.gitmodules vendored
View File

View File

@@ -1,6 +1,22 @@
CHANGELOG
=========
0.11.4
------
- Added `--hscroll-off=COL` option (default: 10) (#513)
- Some fixes in Vim plugin and shell extensions
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
------

View File

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

53
install
View File

@@ -2,8 +2,8 @@
set -u
[[ "$@" =~ --pre ]] && version=0.11.2 pre=1 ||
version=0.11.2 pre=0
[[ "$@" =~ --pre ]] && version=0.11.4 pre=1 ||
version=0.11.4 pre=0
auto_completion=
key_bindings=
@@ -102,6 +102,16 @@ symlink() {
fi
}
link_fzf_in_path() {
if which_fzf="$(which fzf 2> /dev/null)"; then
echo " - Found in \$PATH"
echo " - Creating symlink: $which_fzf -> bin/fzf"
(cd "$fzf_base"/bin && rm -f fzf && ln -sf "$which_fzf" fzf)
check_binary && return
fi
return 1
}
download() {
echo "Downloading bin/fzf ..."
if [ $pre = 0 ]; then
@@ -112,12 +122,7 @@ download() {
if [ -x "$fzf_base"/bin/$1 ]; then
symlink $1 && check_binary && return
fi
if which_fzf="$(which fzf 2> /dev/null)"; then
echo " - Found in \$PATH"
echo " - Creating symlink: $which_fzf -> bin/fzf"
(cd "$fzf_base"/bin && rm -f fzf && ln -sf "$which_fzf" fzf)
check_binary && return
fi
link_fzf_in_path && return
fi
mkdir -p "$fzf_base"/bin && cd "$fzf_base"/bin
if [ $? -ne 0 ]; then
@@ -155,14 +160,7 @@ case "$archi" in
*) binary_available=0 binary_error=1 ;;
esac
cd "$fzf_base"
if [ -n "$binary_error" ]; then
if [ $binary_available -eq 0 ]; then
echo "No prebuilt binary for $archi ... "
else
echo " - $binary_error !!!"
exit 1
fi
install_ruby_fzf() {
echo "Installing legacy Ruby version ..."
# ruby executable
@@ -228,6 +226,29 @@ if [ -n "$binary_error" ]; then
echo "$fzf_cmd \"\$@\"" >> "$fzf_base"/bin/fzf
chmod +x "$fzf_base"/bin/fzf
echo "OK"
}
cd "$fzf_base"
if [ -n "$binary_error" ]; then
if [ $binary_available -eq 0 ]; then
echo "No prebuilt binary for $archi ..."
if which go > /dev/null 2>&1; then
echo -n "Building binary (go get github.com/junegunn/fzf/src/fzf) ... "
if go get github.com/junegunn/fzf/src/fzf; then
echo "OK"
link_fzf_in_path
else
echo "Failed to build binary ..."
install_ruby_fzf
fi
else
echo "go executable not found. Cannot build binary ..."
install_ruby_fzf
fi
else
echo " - $binary_error !!!"
exit 1
fi
fi
[[ "$*" =~ "--bin" ]] && exit 0

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
THE SOFTWARE.
..
.TH fzf 1 "Jan 2016" "fzf 0.11.2" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Mar 2016" "fzf 0.11.4" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -71,7 +71,7 @@ e.g. \fBhistory | fzf --tac --no-sort\fR
.BI "--tiebreak=" "CRI[,..]"
Comma-separated list of sort criteria to apply when the scores are tied.
.br
.R ""
.br
.BR length " Prefers item with shorter length"
.br
@@ -81,7 +81,7 @@ Comma-separated list of sort criteria to apply 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
@@ -144,7 +144,7 @@ Reverse orientation
.BI "--margin=" MARGIN
Comma-separated expression for margins around the finder.
.br
.R ""
.br
.RS
.BR TRBL " Same margin for top, right, bottom, and left"
@@ -155,12 +155,12 @@ Comma-separated expression for margins around the finder.
.br
.BR T,R,B,L " Top, right, bottom, left margin"
.br
.R ""
.br
Each part can be given in absolute number or in percentage relative to the
terminal size with \fB%\fR suffix.
.br
.R ""
.br
e.g. \fBfzf --margin 10%\fR
\fBfzf --margin 1,5%\fR
@@ -175,6 +175,11 @@ Enable cyclic scroll
.B "--no-hscroll"
Disable horizontal scroll
.TP
.BI "--hscroll-off=" "COL"
Number of screen columns to keep to the right of the highlighted substring
(default: 10). Setting it to a large value will cause the text to be positioned
on the center of the screen.
.TP
.B "--inline-info"
Display finder info inline with the query
.TP
@@ -364,7 +369,11 @@ Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
.SH EXIT STATUS
.BR 0 " Normal exit"
.br
.BR 1 " Interrupted with \fBCTRL-C\fR or \fBESC\fR"
.BR 1 " No match"
.br
.BR 2 " Error"
.br
.BR 130 " Interrupted with \fBCTRL-C\fR or \fBESC\fR"
.SH FIELD INDEX EXPRESSION
@@ -431,7 +440,7 @@ Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
.I https://github.com/junegunn/fzf
.RE
.br
.R ""
.br
.B Extra Vim plugin:
.RS

View File

@@ -239,7 +239,7 @@ function! s:exit_handler(code, command, ...)
return 1
endfunction
function! s:execute(dict, command, temps)
function! s:execute(dict, command, temps) abort
call s:pushd(a:dict)
silent! !clear 2> /dev/null
let escaped = escape(substitute(a:command, '\n', '\\n', 'g'), '%#')
@@ -255,7 +255,7 @@ function! s:execute(dict, command, temps)
return s:exit_handler(v:shell_error, command) ? s:callback(a:dict, a:temps) : []
endfunction
function! s:execute_tmux(dict, command, temps)
function! s:execute_tmux(dict, command, temps) abort
let command = a:command
if s:pushd(a:dict)
" -c '#{pane_current_path}' is only available on tmux 1.9 or above
@@ -320,7 +320,7 @@ function! s:split(dict)
endtry
endfunction
function! s:execute_term(dict, command, temps)
function! s:execute_term(dict, command, temps) abort
call s:split(a:dict)
let fzf = { 'buf': bufnr('%'), 'dict': a:dict, 'temps': a:temps, 'name': 'FZF' }
@@ -344,6 +344,7 @@ function! s:execute_term(dict, command, temps)
endif
call s:pushd(self.dict)
let ret = []
try
let ret = s:callback(self.dict, self.temps)
@@ -368,11 +369,10 @@ function! s:execute_term(dict, command, temps)
return []
endfunction
function! s:callback(dict, temps)
function! s:callback(dict, temps) abort
let lines = []
try
if !filereadable(a:temps.result)
let lines = []
else
if filereadable(a:temps.result)
let lines = readfile(a:temps.result)
if has_key(a:dict, 'sink')
for line in lines
@@ -391,12 +391,12 @@ try
for tf in values(a:temps)
silent! call delete(tf)
endfor
return lines
catch
if stridx(v:exception, ':E325:') < 0
echoerr v:exception
endif
finally
return lines
endtry
endfunction

View File

@@ -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}'
@@ -55,11 +75,11 @@ _fzf_opts_completion() {
case "${prev}" in
--tiebreak)
COMPREPLY=( $(compgen -W "length begin end index" -- ${cur}) )
COMPREPLY=( $(compgen -W "length begin end index" -- "$cur") )
return 0
;;
--color)
COMPREPLY=( $(compgen -W "dark light 16 bw" -- ${cur}) )
COMPREPLY=( $(compgen -W "dark light 16 bw" -- "$cur") )
return 0
;;
--history)
@@ -68,8 +88,8 @@ _fzf_opts_completion() {
;;
esac
if [[ ${cur} =~ ^-|\+ ]]; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
if [[ "$cur" =~ ^-|\+ ]]; then
COMPREPLY=( $(compgen -W "${opts}" -- "$cur") )
return 0
fi
@@ -88,32 +108,32 @@ _fzf_handle_dynamic_completion() {
elif [ -n "$_fzf_completion_loader" ]; then
_completion_loader "$@"
ret=$?
eval $(complete | \grep "\-F.* $orig_cmd$" | _fzf_orig_completion_filter)
source $BASH_SOURCE
eval "$(complete | \grep "\-F.* $orig_cmd$" | _fzf_orig_completion_filter)"
source "${BASH_SOURCE[0]}"
return $ret
fi
}
__fzf_generic_path_completion() {
local cur base dir leftover matches trigger cmd fzf
[ ${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')
[ "${FZF_TMUX:-1}" != 0 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
cmd=$(echo "${COMP_WORDS[0]}" | sed 's/[^a-z0-9_=]/_/g')
COMPREPLY=()
trigger=${FZF_COMPLETION_TRIGGER-'**'}
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ ${cur} == *"$trigger" ]]; then
if [[ "$cur" == *"$trigger" ]]; then
base=${cur:0:${#cur}-${#trigger}}
eval base=$base
eval "base=$base"
dir="$base"
while [ 1 ]; do
if [ -z "$dir" -o -d "$dir" ]; then
while true; do
if [ -z "$dir" ] || [ -d "$dir" ]; then
leftover=${base/#"$dir"}
leftover=${leftover/#\/}
[ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}"
tput sc
matches=$(\find -L "$dir" $1 -a -not -path "$dir" -print 2> /dev/null | sed 's@^\./@@' | $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 -r item; do
printf "%q$3 " "$item"
done)
matches=${matches% }
@@ -136,29 +156,22 @@ __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-$$"
[ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
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}" != 0 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g')
cmd=$(echo "${COMP_WORDS[0]}" | sed 's/[^a-z0-9_=]/_/g')
trigger=${FZF_COMPLETION_TRIGGER-'**'}
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ ${cur} == *"$trigger" ]]; then
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")
@@ -171,28 +184,23 @@ _fzf_complete() {
}
_fzf_path_completion() {
__fzf_generic_path_completion \
"-name .git -prune -o -name .svn -prune -o ( -type d -o -type f -o -type l )" \
"-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 -o -type l )" \
"-m" "" "$@"
_fzf_path_completion "$@"
}
_fzf_dir_completion() {
__fzf_generic_path_completion \
"-name .git -prune -o -name .svn -prune -o -type d" \
"" "/" "$@"
__fzf_generic_path_completion _fzf_compgen_dir "" "/" "$@"
}
_fzf_complete_kill() {
[ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1
local selected fzf
[ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
[ "${FZF_TMUX:-1}" != 0 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
tput sc
selected=$(ps -ef | sed 1d | $fzf -m $FZF_COMPLETION_OPTS | awk '{print $2}' | tr '\n' ' ')
tput rc
@@ -239,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
@@ -253,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
@@ -278,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
@@ -307,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

View File

@@ -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
@@ -33,7 +53,7 @@ __fzf_generic_path_completion() {
[ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}"
dir=${~dir}
matches=$(\find -L "$dir" ${=find_opts} -a -not -path "$dir" -print 2> /dev/null | sed 's@^\./@@' | ${=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% }
@@ -50,32 +70,33 @@ __fzf_generic_path_completion() {
}
_fzf_path_completion() {
__fzf_generic_path_completion "$1" "$2" \
"-name .git -prune -o -name .svn -prune -o ( -type d -o -type f -o -type l )" \
__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" \
__fzf_generic_path_completion "$1" "$2" _fzf_compgen_dir \
"" "/" ""
}
_fzf_feed_fifo() (
rm -f "$fifo"
mkfifo "$fifo"
cat <&0 > "$fifo" &
rm -f "$1"
mkfifo "$1"
cat <&0 > "$1" &
)
_fzf_complete() {
local fifo fzf_opts lbuf fzf matches
local fifo fzf_opts lbuf fzf matches post
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
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 "$fifo" | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=fzf_opts} -q "${(Q)prefix}" | $post | tr '\n' ' ')
if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches"
fi
@@ -145,7 +166,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]}}

View File

@@ -5,7 +5,7 @@ __fzf_select__() {
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"}"
eval "$cmd" | fzf -m | while read item; do
eval "$cmd" | fzf -m | while read -r item; do
printf '%q ' "$item"
done
echo
@@ -14,7 +14,7 @@ __fzf_select__() {
if [[ $- =~ i ]]; then
__fzfcmd() {
[ ${FZF_TMUX:-1} -eq 1 ] && echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
[ "${FZF_TMUX:-1}" != 0 ] && echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
}
__fzf_select_tmux__() {
@@ -30,10 +30,10 @@ __fzf_select_tmux__() {
}
__fzf_select_tmux_auto__() {
if [ ${FZF_TMUX:-1} -ne 0 -a ${LINES:-40} -gt 15 ]; then
if [ "${FZF_TMUX:-1}" != 0 ] && [ ${LINES:-40} -gt 15 ]; then
__fzf_select_tmux__
else
tmux send-keys -t $TMUX_PANE "$(__fzf_select__)"
tmux send-keys -t "$TMUX_PANE" "$(__fzf_select__)"
fi
}
@@ -61,7 +61,7 @@ __fzf_history__() (
__use_tmux=0
__use_tmux_auto=0
if [ -n "$TMUX_PANE" ]; then
[ ${FZF_TMUX:-1} -ne 0 -a ${LINES:-40} -gt 15 ] && __use_tmux=1
[ "${FZF_TMUX:-1}" != 0 ] && [ ${LINES:-40} -gt 15 ] && __use_tmux=1
[ $BASH_VERSINFO -gt 3 ] && __use_tmux_auto=1
fi

View File

@@ -11,7 +11,6 @@ RUN cd / && curl \
tar -xz && mv go go1.4 && \
sed -i 's@#define PTHREAD_KEYS_MAX 128@@' /go1.4/src/runtime/cgo/gcc_android_arm.c
ENV GOPATH /go
ENV GOROOT /go1.4
ENV PATH /go1.4/bin:$PATH
@@ -37,8 +36,5 @@ RUN cd / && curl \
make install && \
mv /ndk/sysroot/usr/lib/libncursesw.a /ndk/sysroot/usr/lib/libncurses.a
# Volume
VOLUME /go
# Default CMD
CMD cd /go/src/github.com/junegunn/fzf/src && /bin/bash
CMD cd /fzf/src && /bin/bash

View File

@@ -10,7 +10,6 @@ RUN cd / && curl \
https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz | \
tar -xz && mv go go1.4
ENV GOPATH /go
ENV GOROOT /go1.4
ENV PATH /go1.4/bin:$PATH
@@ -20,9 +19,6 @@ RUN echo '[multilib]' >> /etc/pacman.conf && \
pacman-db-upgrade && yes | pacman -Sy gcc-multilib lib32-ncurses && \
cd $GOROOT/src && GOARCH=386 ./make.bash
# Volume
VOLUME /go
# Default CMD
CMD cd /go/src/github.com/junegunn/fzf/src && /bin/bash
CMD cd /fzf/src && /bin/bash

View File

@@ -13,20 +13,16 @@ RUN cd / && curl \
# Install Go 1.5
RUN cd / && curl \
https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz | \
https://storage.googleapis.com/golang/go1.5.3.linux-amd64.tar.gz | \
tar -xz && mv go go1.5
ENV GOROOT_BOOTSTRAP /go1.4
ENV GOROOT /go1.5
ENV GOPATH /go
ENV PATH /go1.5/bin:$PATH
# For i386 build
RUN cd $GOROOT/src && GOARCH=386 ./make.bash
# Volume
VOLUME /go
# Default CMD
CMD cd /go/src/github.com/junegunn/fzf/src && /bin/bash
CMD cd /fzf/src && /bin/bash

View File

@@ -10,7 +10,6 @@ RUN cd / && curl \
https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz | \
tar -xz && mv go go1.4
ENV GOPATH /go
ENV GOROOT /go1.4
ENV PATH /go1.4/bin:$PATH
@@ -18,9 +17,6 @@ ENV PATH /go1.4/bin:$PATH
RUN apt-get install -y lib32ncurses5-dev && \
cd $GOROOT/src && GOARCH=386 ./make.bash
# Volume
VOLUME /go
# Default CMD
CMD cd /go/src/github.com/junegunn/fzf/src && /bin/bash
CMD cd /fzf/src && /bin/bash

View File

@@ -1,7 +1,3 @@
ifndef GOPATH
$(error GOPATH is undefined)
endif
ifndef GOOS
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
@@ -15,33 +11,44 @@ ifneq ($(shell uname -m),x86_64)
$(error "Build on $(UNAME_M) is not supported, yet.")
endif
SOURCES := $(wildcard *.go */*.go)
BINDIR := ../bin
SOURCES := $(wildcard *.go */*.go)
ROOTDIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
BINDIR := $(shell dirname $(ROOTDIR))/bin
GOPATH := $(shell dirname $(ROOTDIR))/gopath
SRCDIR := $(GOPATH)/src/github.com/junegunn/fzf/src
DOCKEROPTS := -i -t -v $(ROOTDIR):/fzf/src
BINARY32 := fzf-$(GOOS)_386
BINARY64 := fzf-$(GOOS)_amd64
BINARYARM7 := fzf-$(GOOS)_arm7
VERSION := $(shell awk -F= '/version =/ {print $$2}' constants.go | tr -d "\" ")
RELEASE32 = fzf-$(VERSION)-$(GOOS)_386
RELEASE64 = fzf-$(VERSION)-$(GOOS)_amd64
RELEASEARM7 = fzf-$(VERSION)-$(GOOS)_arm7
RELEASE32 := fzf-$(VERSION)-$(GOOS)_386
RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
export GOPATH
all: release
release: build
release: test build
-cd fzf && cp $(BINARY32) $(RELEASE32) && tar -czf $(RELEASE32).tgz $(RELEASE32)
cd fzf && cp $(BINARY64) $(RELEASE64) && tar -czf $(RELEASE64).tgz $(RELEASE64) && \
rm -f $(RELEASE32) $(RELEASE64)
build: test fzf/$(BINARY32) fzf/$(BINARY64)
build: fzf/$(BINARY32) fzf/$(BINARY64)
android-build:
cd fzf && GOARCH=arm GOARM=7 CGO_ENABLED=1 go build -a -ldflags="-extldflags=-pie" -o $(BINARYARM7)
cd fzf && cp $(BINARYARM7) $(RELEASEARM7) && tar -czf $(RELEASEARM7).tgz $(RELEASEARM7) && \
$(SRCDIR):
mkdir -p $(shell dirname $(SRCDIR))
ln -s $(ROOTDIR) $(SRCDIR)
deps: $(SRCDIR) $(SOURCES)
cd $(SRCDIR) && go get
android-build: $(SRCDIR)
cd $(SRCDIR) && GOARCH=arm GOARM=7 CGO_ENABLED=1 go get
cd $(SRCDIR)/fzf && GOARCH=arm GOARM=7 CGO_ENABLED=1 go build -a -ldflags="-extldflags=-pie" -o $(BINARYARM7)
cd $(SRCDIR)/fzf && cp $(BINARYARM7) $(RELEASEARM7) && tar -czf $(RELEASEARM7).tgz $(RELEASEARM7) && \
rm -f $(RELEASEARM7)
test:
go get
test: deps
SHELL=/bin/sh go test -v ./...
install: $(BINDIR)/fzf
@@ -52,10 +59,10 @@ uninstall:
clean:
cd fzf && rm -f fzf-*
fzf/$(BINARY32): $(SOURCES)
fzf/$(BINARY32): deps
cd fzf && GOARCH=386 CGO_ENABLED=1 go build -a -o $(BINARY32)
fzf/$(BINARY64): $(SOURCES)
fzf/$(BINARY64): deps
cd fzf && go build -a -tags "$(TAGS)" -o $(BINARY64)
$(BINDIR)/fzf: fzf/$(BINARY64) | $(BINDIR)
@@ -78,29 +85,29 @@ docker-android:
docker build -t junegunn/android-sandbox - < Dockerfile.android
arch: docker-arch
docker run -i -t -v $(GOPATH):/go junegunn/$@-sandbox \
sh -c 'cd /go/src/github.com/junegunn/fzf/src; /bin/bash'
docker run $(DOCKEROPTS) junegunn/$@-sandbox \
sh -c 'cd /fzf/src; /bin/bash'
ubuntu: docker-ubuntu
docker run -i -t -v $(GOPATH):/go junegunn/$@-sandbox \
sh -c 'cd /go/src/github.com/junegunn/fzf/src; /bin/bash'
docker run $(DOCKEROPTS) junegunn/$@-sandbox \
sh -c 'cd /fzf/src; /bin/bash'
centos: docker-centos
docker run -i -t -v $(GOPATH):/go junegunn/$@-sandbox \
sh -c 'cd /go/src/github.com/junegunn/fzf/src; /bin/bash'
docker run $(DOCKEROPTS) junegunn/$@-sandbox \
sh -c 'cd /fzf/src; /bin/bash'
linux: docker-centos
docker run -i -t -v $(GOPATH):/go junegunn/centos-sandbox \
/bin/bash -ci 'cd /go/src/github.com/junegunn/fzf/src; make TAGS=static'
docker run $(DOCKEROPTS) junegunn/centos-sandbox \
/bin/bash -ci 'cd /fzf/src; make TAGS=static'
ubuntu-android: docker-android
docker run -i -t -v $(GOPATH):/go junegunn/android-sandbox \
sh -c 'cd /go/src/github.com/junegunn/fzf/src; /bin/bash'
docker run $(DOCKEROPTS) junegunn/android-sandbox \
sh -c 'cd /fzf/src; /bin/bash'
android: docker-android
docker run -i -t -v $(GOPATH):/go junegunn/android-sandbox \
/bin/bash -ci 'cd /go/src/github.com/junegunn/fzf/src; GOOS=android make android-build'
docker run $(DOCKEROPTS) junegunn/android-sandbox \
/bin/bash -ci 'cd /fzf/src; GOOS=android make android-build'
.PHONY: all build release test install uninstall clean docker \
.PHONY: all build deps release test install uninstall clean \
linux arch ubuntu centos docker-arch docker-ubuntu docker-centos \
android-build docker-android ubuntu-android android

View File

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

View File

@@ -200,7 +200,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
}
partialResults := make([][]*Item, numSlices)
for range slices {
for _, _ = range slices {
partialResult := <-resultChan
partialResults[partialResult.index] = partialResult.matches
}

View File

@@ -42,6 +42,8 @@ const usage = `usage: fzf [options]
--tabstop=SPACES Number of spaces for a tab character (default: 8)
--cycle Enable cyclic scroll
--no-hscroll Disable horizontal scroll
--hscroll-off=COL Number of screen columns to keep to the right of the
highlighted substring (default: 10)
--inline-info Display finder info inline with the query
--prompt=STR Input prompt (default: '> ')
--bind=KEYBINDS Custom key bindings. Refer to the man page.
@@ -108,6 +110,7 @@ type Options struct {
Reverse bool
Cycle bool
Hscroll bool
HscrollOff int
InlineInfo bool
Prompt string
Query string
@@ -155,6 +158,7 @@ func defaultOptions() *Options {
Reverse: false,
Cycle: false,
Hscroll: true,
HscrollOff: 10,
InlineInfo: false,
Prompt: "> ",
Query: "",
@@ -163,7 +167,7 @@ func defaultOptions() *Options {
Filter: nil,
ToggleSort: false,
Expect: make(map[int]string),
Keymap: defaultKeymap(),
Keymap: make(map[int]actionType),
Execmap: make(map[int]string),
PrintQuery: false,
ReadZero: false,
@@ -484,7 +488,7 @@ const (
escapedComma = 1
)
func parseKeymap(keymap map[int]actionType, execmap map[int]string, toggleSort bool, str string) (map[int]actionType, map[int]string, bool) {
func parseKeymap(keymap map[int]actionType, execmap map[int]string, str string) {
if executeRegexp == nil {
// Backreferences are not supported.
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
@@ -592,7 +596,6 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, toggleSort b
keymap[key] = actNextHistory
case "toggle-sort":
keymap[key] = actToggleSort
toggleSort = true
default:
if isExecuteAction(actLower) {
var offset int
@@ -613,7 +616,6 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, toggleSort b
}
}
}
return keymap, execmap, toggleSort
}
func isExecuteAction(str string) bool {
@@ -635,13 +637,12 @@ func isExecuteAction(str string) bool {
return false
}
func checkToggleSort(keymap map[int]actionType, str string) map[int]actionType {
func parseToggleSort(keymap map[int]actionType, str string) {
keys := parseKeyChords(str, "key name required")
if len(keys) != 1 {
errorExit("multiple keys specified")
}
keymap[firstKey(keys)] = actToggleSort
return keymap
}
func strLines(str string) []string {
@@ -691,7 +692,6 @@ func parseMargin(margin string) [4]string {
}
func parseOptions(opts *Options, allArgs []string) {
keymap := make(map[int]actionType)
var historyMax int
if opts.History == nil {
historyMax = defaultHistoryMax
@@ -741,8 +741,7 @@ func parseOptions(opts *Options, allArgs []string) {
case "--tiebreak":
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"))
parseKeymap(opts.Keymap, opts.Execmap, nextString(allArgs, &i, "bind expression required"))
case "--color":
spec := optionalNextString(allArgs, &i)
if len(spec) == 0 {
@@ -751,8 +750,7 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Theme = parseTheme(opts.Theme, spec)
}
case "--toggle-sort":
keymap = checkToggleSort(keymap, nextString(allArgs, &i, "key name required"))
opts.ToggleSort = true
parseToggleSort(opts.Keymap, nextString(allArgs, &i, "key name required"))
case "-d", "--delimiter":
opts.Delimiter = delimiterRegexp(nextString(allArgs, &i, "delimiter required"))
case "-n", "--nth":
@@ -801,6 +799,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Hscroll = true
case "--no-hscroll":
opts.Hscroll = false
case "--hscroll-off":
opts.HscrollOff = nextInt(allArgs, &i, "hscroll offset required")
case "--inline-info":
opts.InlineInfo = true
case "--no-inline-info":
@@ -869,8 +869,7 @@ func parseOptions(opts *Options, allArgs []string) {
} else if match, _ := optString(arg, "-s", "--sort="); match {
opts.Sort = 1 // Don't care
} else if match, value := optString(arg, "--toggle-sort="); match {
keymap = checkToggleSort(keymap, value)
opts.ToggleSort = true
parseToggleSort(opts.Keymap, value)
} else if match, value := optString(arg, "--expect="); match {
opts.Expect = parseKeyChords(value, "key names required")
} else if match, value := optString(arg, "--tiebreak="); match {
@@ -878,8 +877,7 @@ func parseOptions(opts *Options, allArgs []string) {
} else if match, value := optString(arg, "--color="); match {
opts.Theme = parseTheme(opts.Theme, value)
} else if match, value := optString(arg, "--bind="); match {
keymap, opts.Execmap, opts.ToggleSort =
parseKeymap(keymap, opts.Execmap, opts.ToggleSort, value)
parseKeymap(opts.Keymap, opts.Execmap, value)
} else if match, value := optString(arg, "--history="); match {
setHistory(value)
} else if match, value := optString(arg, "--history-size="); match {
@@ -892,6 +890,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Margin = parseMargin(value)
} else if match, value := optString(arg, "--tabstop="); match {
opts.Tabstop = atoi(value)
} else if match, value := optString(arg, "--hscroll-off="); match {
opts.HscrollOff = atoi(value)
} else {
errorExit("unknown option: " + arg)
}
@@ -902,24 +902,35 @@ func parseOptions(opts *Options, allArgs []string) {
errorExit("header lines must be a non-negative integer")
}
if opts.HscrollOff < 0 {
errorExit("hscroll offset must be a non-negative integer")
}
if opts.Tabstop < 1 {
errorExit("tab stop must be a positive integer")
}
}
// Change default actions for CTRL-N / CTRL-P when --history is used
func postProcessOptions(opts *Options) {
// Default actions for CTRL-N / CTRL-P when --history is set
if opts.History != nil {
if _, prs := keymap[curses.CtrlP]; !prs {
keymap[curses.CtrlP] = actPreviousHistory
if _, prs := opts.Keymap[curses.CtrlP]; !prs {
opts.Keymap[curses.CtrlP] = actPreviousHistory
}
if _, prs := keymap[curses.CtrlN]; !prs {
keymap[curses.CtrlN] = actNextHistory
if _, prs := opts.Keymap[curses.CtrlN]; !prs {
opts.Keymap[curses.CtrlN] = actNextHistory
}
}
// Override default key bindings
for key, act := range keymap {
opts.Keymap[key] = act
// Extend the default key map
keymap := defaultKeymap()
for key, act := range opts.Keymap {
if act == actToggleSort {
opts.ToggleSort = true
}
keymap[key] = act
}
opts.Keymap = keymap
// If we're not using extended search mode, --nth option becomes irrelevant
// if it contains the whole range
@@ -939,9 +950,13 @@ func ParseOptions() *Options {
// Options from Env var
words, _ := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS"))
parseOptions(opts, words)
if len(words) > 0 {
parseOptions(opts, words)
}
// Options from command-line arguments
parseOptions(opts, os.Args[1:])
postProcessOptions(opts)
return opts
}

View File

@@ -96,6 +96,7 @@ func TestIrrelevantNth(t *testing.T) {
opts := defaultOptions()
words := []string{"--nth", "..", "-x"}
parseOptions(opts, words)
postProcessOptions(opts)
if len(opts.Nth) != 0 {
t.Errorf("nth should be empty: %s", opts.Nth)
}
@@ -104,6 +105,7 @@ func TestIrrelevantNth(t *testing.T) {
{
opts := defaultOptions()
parseOptions(opts, words)
postProcessOptions(opts)
if len(opts.Nth) != 0 {
t.Errorf("nth should be empty: %s", opts.Nth)
}
@@ -112,6 +114,7 @@ func TestIrrelevantNth(t *testing.T) {
opts := defaultOptions()
words = append(words, "-x")
parseOptions(opts, words)
postProcessOptions(opts)
if len(opts.Nth) != 2 {
t.Errorf("nth should not be empty: %s", opts.Nth)
}
@@ -231,15 +234,11 @@ func TestBind(t *testing.T) {
keymap := defaultKeymap()
execmap := make(map[int]string)
check(actBeginningOfLine, keymap[curses.CtrlA])
keymap, execmap, toggleSort :=
parseKeymap(keymap, execmap, false,
"ctrl-a:kill-line,ctrl-b:toggle-sort,c:page-up,alt-z:page-down,"+
"f1:execute(ls {}),f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
"alt-a:execute@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};"+
",,:abort,::accept,X:execute:\nfoobar,Y:execute(baz)")
if !toggleSort {
t.Errorf("toggleSort not set")
}
parseKeymap(keymap, execmap,
"ctrl-a:kill-line,ctrl-b:toggle-sort,c:page-up,alt-z:page-down,"+
"f1:execute(ls {}),f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
"alt-a:execute@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};"+
",,:abort,::accept,X:execute:\nfoobar,Y:execute(baz)")
check(actKillLine, keymap[curses.CtrlA])
check(actToggleSort, keymap[curses.CtrlB])
check(actPageUp, keymap[curses.AltZ+'c'])
@@ -259,15 +258,11 @@ func TestBind(t *testing.T) {
checkString("\nfoobar,Y:execute(baz)", execmap[curses.AltZ+'X'])
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
keymap, execmap, toggleSort =
parseKeymap(keymap, execmap, false, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
parseKeymap(keymap, execmap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
checkString("foobar", execmap[curses.AltZ+int([]rune(fmt.Sprintf("%d", idx%10))[0])])
}
keymap, execmap, toggleSort = parseKeymap(keymap, execmap, false, "f1:abort")
if toggleSort {
t.Errorf("toggleSort set")
}
parseKeymap(keymap, execmap, "f1:abort")
check(actAbort, keymap[curses.F1])
}
@@ -328,3 +323,53 @@ func TestParseNilTheme(t *testing.T) {
t.Errorf("color should now be enabled and customized")
}
}
func TestDefaultCtrlNP(t *testing.T) {
check := func(words []string, key int, expected actionType) {
opts := defaultOptions()
parseOptions(opts, words)
postProcessOptions(opts)
if opts.Keymap[key] != expected {
t.Error()
}
}
check([]string{}, curses.CtrlN, actDown)
check([]string{}, curses.CtrlP, actUp)
check([]string{"--bind=ctrl-n:accept"}, curses.CtrlN, actAccept)
check([]string{"--bind=ctrl-p:accept"}, curses.CtrlP, actAccept)
hist := "--history=/tmp/foo"
check([]string{hist}, curses.CtrlN, actNextHistory)
check([]string{hist}, curses.CtrlP, actPreviousHistory)
check([]string{hist, "--bind=ctrl-n:accept"}, curses.CtrlN, actAccept)
check([]string{hist, "--bind=ctrl-n:accept"}, curses.CtrlP, actPreviousHistory)
check([]string{hist, "--bind=ctrl-p:accept"}, curses.CtrlN, actNextHistory)
check([]string{hist, "--bind=ctrl-p:accept"}, curses.CtrlP, actAccept)
}
func TestToggle(t *testing.T) {
optsFor := func(words ...string) *Options {
opts := defaultOptions()
parseOptions(opts, words)
postProcessOptions(opts)
return opts
}
opts := optsFor()
if opts.ToggleSort {
t.Error()
}
opts = optsFor("--bind=a:toggle-sort")
if !opts.ToggleSort {
t.Error()
}
opts = optsFor("--bind=a:toggle-sort", "--bind=a:up")
if opts.ToggleSort {
t.Error()
}
}

View File

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

View File

@@ -4,7 +4,6 @@ import (
"bytes"
"fmt"
"os"
"os/exec"
"os/signal"
"regexp"
"sort"
@@ -27,6 +26,7 @@ type Terminal struct {
prompt string
reverse bool
hscroll bool
hscrollOff int
cx int
cy int
offset int
@@ -211,6 +211,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
prompt: opts.Prompt,
reverse: opts.Reverse,
hscroll: opts.Hscroll,
hscrollOff: opts.HscrollOff,
cx: len(input),
cy: 0,
offset: 0,
@@ -557,11 +558,9 @@ func trimLeft(runes []rune, width int) ([]rune, int32) {
}
func (t *Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int, current bool) {
var maxe int32
var maxe int
for _, offset := range item.offsets {
if offset[1] > maxe {
maxe = offset[1]
}
maxe = util.Max(maxe, int(offset[1]))
}
// Overflow
@@ -569,6 +568,7 @@ func (t *Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int, c
copy(text, item.text)
offsets := item.colorOffsets(col2, bold, current)
maxWidth := C.MaxX() - 3 - t.marginInt[1] - t.marginInt[3]
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
fullWidth := displayWidth(text)
if fullWidth > maxWidth {
if t.hscroll {
@@ -720,7 +720,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
@@ -734,7 +734,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)
@@ -1123,15 +1123,7 @@ func (t *Terminal) constrain() {
diffpos := t.cy - t.offset
t.cy = util.Constrain(t.cy, 0, count-1)
if t.cy > t.offset+(height-1) {
// Ceil
t.offset = t.cy - (height - 1)
} else if t.offset > t.cy {
// Floor
t.offset = t.cy
}
t.offset = util.Constrain(t.offset, t.cy-height+1, t.cy)
// Adjustment
if count-t.offset < height {
t.offset = util.Max(0, count-height)

View File

@@ -5,6 +5,7 @@ import "C"
import (
"os"
"os/exec"
"time"
"unicode/utf8"
)
@@ -20,6 +21,14 @@ func Max(first int, items ...int) int {
return max
}
// Min returns the smallest integer
func Min(first int, second int) int {
if first <= second {
return first
}
return second
}
// Min32 returns the smallest 32-bit integer
func Min32(first int32, second int32) int32 {
if first <= second {
@@ -126,3 +135,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)
}

View File

@@ -893,6 +893,24 @@ 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}", "sync"]
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.until { |lines| lines[-2].include? '1/1' }
tmux.send_keys 'C-c'
tmux.prepare
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' }
@@ -1121,10 +1139,22 @@ class TestGoFZF < TestBase
`seq 10 | #{FZF} -f '1 | !1'`.lines.map(&:chomp)
end
def test_hscroll_off
writelines tempname, ['=' * 10000 + '0123456789']
[0, 3, 6].each do |off|
tmux.prepare
tmux.send_keys "#{FZF} --hscroll-off=#{off} -q 0 < #{tempname}", :Enter
tmux.until { |lines| lines[-3].end_with?((0..off).to_a.join + '..') }
tmux.send_keys '9'
tmux.until { |lines| lines[-3].end_with? '789' }
tmux.send_keys :Enter
end
end
private
def writelines path, lines
File.unlink path while File.exists? path
File.open(path, 'w') { |f| f << lines.join($/) }
File.open(path, 'w') { |f| f << lines.join($/) + $/ }
end
end
@@ -1269,7 +1299,7 @@ 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')
@@ -1342,6 +1372,39 @@ module CompletionTest
ensure
Process.kill 'KILL', pid.to_i rescue nil if 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
def test_unset_completion
tmux.send_keys 'export FOO=BAR', :Enter
tmux.prepare
# Using tmux
tmux.send_keys 'unset FOO**', :Tab, pane: 0
tmux.until(1) { |lines| lines[-2].include? ' 1/' }
tmux.send_keys :Enter
tmux.until { |lines| lines[-1] == 'unset FOO' }
tmux.send_keys 'C-c'
# FZF_TMUX=0
new_shell
tmux.send_keys 'unset FOO**', :Tab
tmux.until { |lines| lines[-2].include? ' 1/' }
tmux.send_keys :Enter
tmux.until { |lines| lines[-1] == 'unset FOO' }
end
end
class TestBash < TestBase