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

Compare commits

...

59 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
Junegunn Choi
68c84264af Update CHANGELOG 2016-01-16 21:13:57 +09:00
Junegunn Choi
69438a55ca Update CHANGELOG: 0.11.2 2016-01-16 21:11:40 +09:00
Junegunn Choi
8695b5e319 Reduce 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.
2016-01-16 18:07:50 +09:00
Junegunn Choi
95970164ad 0.11.2 2016-01-14 02:54:08 +09:00
Junegunn Choi
f6c6e59a50 Add toggle-in and toggle-out for --bind
Related: #452

When `--multi` is set, tab key will bring your cursor down, and
shift-tab up. But since fzf by default draws the screen in bottom-up
fashion, one may feel that the opposite of the behavior is more
desirable and choose to customize the key bindings as follows.

    export FZF_DEFAULT_OPTS="--bind tab:toggle-up,shift-tab:toggle-down"

This configuration, however, becomes no longer straightforward when
`--reverse` is set and fzf switches to top-down layout. To address the
requirement, this commit adds `toggle-in` and `toggle-out` option which
switch direction depending on `--reverse`-ness.

    export FZF_DEFAULT_OPTS="--bind tab:toggle-out,shift-tab:toggle-in"
2016-01-14 02:35:43 +09:00
Junegunn Choi
45143f9541 Ignore leading whitespaces when calculating 'begin' index 2016-01-14 01:32:03 +09:00
Junegunn Choi
23244bb410 [fish] Fix intermittent errors on CTRL-T
This seems like a bug of fish, but sometimes when you select an item
fish complains:

"insertion mode switches can not be used when not in insertion mode"

This only happens when using tmux pane. Injecting a dummy command
somehow fixes the issue.
2016-01-14 01:12:49 +09:00
Junegunn Choi
edb647667e Change temporary file names to fix flaky tests 2016-01-14 01:12:49 +09:00
Junegunn Choi
8d3a302a17 Simplify Item structure
This commit compensates for the performance overhead from the
extended tiebreak option.
2016-01-14 01:12:49 +09:00
Junegunn Choi
1d2d32c847 Accept comma-separated list of sort criteria 2016-01-13 21:27:43 +09:00
Junegunn Choi
d635b3fd3c Update license: 2016 2016-01-13 02:16:26 +09:00
Junegunn Choi
0f281ef894 [vim] Try to make 'dir' option compatible with &autochdir
When 'dir' option is passed to fzf#run(), the current working directory
is temporarily changed to the given directory, and restored at the end.
However, this behavior is not compatible with &autochdir. This commit
introduces a heuristic to determine whether or not to restore the
previous working directory.

Related: https://github.com/junegunn/fzf.vim/issues/70
2016-01-12 01:15:36 +09:00
Junegunn Choi
b18db4733c [vim] Do not restore working directory on unexpected cwd
We should not restore the previous working directory if the current
directory has changed somehow. This can happen when &autochdir is set.
2016-01-11 18:17:13 +09:00
Junegunn Choi
6e08fe337c [nvim] setlocal nospell on terminal buffer
Close #469. `setlocal nospell` should appear before `setf fzf` to allow
customization of the option.
2016-01-09 12:08:25 +09:00
Junegunn Choi
2a2c0a0957 [fzf-tmux] Turn off remain-on-exit option
Related: https://github.com/junegunn/fzf.vim/issues/67
2016-01-07 01:42:03 +09:00
Junegunn Choi
4230b6f3c9 [fzf-tmux] Fix #466 - Make fifos writable by other users 2016-01-07 00:32:38 +09:00
Junegunn Choi
aa171b45cb Fix ubuntu-android target of Makefile 2016-01-05 02:10:40 +09:00
Junegunn Choi
661d06c90a Add regression test case for #458 2015-12-29 13:02:16 +09:00
Junegunn Choi
a9aa263d3a Merge pull request #458 from frizinak/fix-autocomplete-abs
Fix auto-completion for `/`
2015-12-29 08:22:52 +09:00
Kobe Lipkens
6208fc9cfd Fix autocompletion for absolute paths 2015-12-28 21:11:04 +01:00
Junegunn Choi
e1dd798482 [bash/zsh-completion] List hidden files as well
Close #456 and #457
2015-12-29 00:21:38 +09:00
Junegunn Choi
c8a3f6f06a Merge pull request #455 from frizinak/master
Pass FZF_DEFAULT_OPTS to non-interactive bash instance
2015-12-28 00:09:22 +09:00
Kobe Lipkens
3b9984379c Pass FZF_DEFAULT_OPTS to non-interactive bash instance 2015-12-25 21:05:25 +01:00
Junegunn Choi
a1b60b1d42 Fix Travis CI build
The size of pseudo-terminal in Travis CI environment can be small
2015-12-20 01:47:07 +09:00
Junegunn Choi
b5850ebd4c [vim] Open selected file in the current window if it's empty
Close #451
2015-12-18 12:19:29 +09:00
Junegunn Choi
ac0a62e494 Merge pull request #446 from chaoren/master
Fix CTRL-T in tmux
2015-12-13 01:11:02 +09:00
Chaoren Lin
54b4b0c56f Dynamically select which __fzf_select__ to use for tmux with bash 4+.
Instead of choosing one at initialization, choose the correct one
when it's actually called, so that the behavior is correct even after
resizing.

Bonus fixes for tmux with bash 4+:
- No extra space when cancelling CTRL-T.
- Fix cursor position problem in vi mode.
2015-12-11 10:02:35 -08:00
Chaoren Lin
033afde3b5 Fix CTRL-T in tmux with non-standard configuration.
- Don't assume ~/.fzf.bash exists.
- Source the current script for __fzf_select__.
- Forward $PATH.
2015-12-11 00:18:45 -08:00
Junegunn Choi
a07944a5bb Merge pull request #439 from pokey/master
Correct fzf-tmux tmux checking bug
2015-12-09 12:43:42 +09:00
Pokey Rule
32010055e1 Correct fzf-tmux tmux checking bug 2015-12-08 17:33:44 -08:00
Junegunn Choi
971ea2217c Merge pull request #433 from pokey/master
Support fzf-tmux when zoomed
2015-12-08 10:56:06 +09:00
Pokey Rule
d513a210c6 Support fzf-tmux when zoomed 2015-12-07 17:45:22 -08:00
Junegunn Choi
a1db64e7b1 Unset GO15VENDOREXPERIMENT in linux build env (#430) 2015-12-04 16:47:02 +09:00
Junegunn Choi
0b9c4e1e74 Remove submodules and disable GO15VENDOREXPERIMENT (#430)
Having submodules causes vim-plug or other vim plugin managers to clone
them with no real benefit to the end-users. There's currently no
compelling reason for me to use submodules.
2015-12-04 16:45:23 +09:00
37 changed files with 962 additions and 404 deletions

3
.gitignore vendored
View File

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

6
.gitmodules vendored
View File

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

View File

@@ -1,6 +1,42 @@
CHANGELOG 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
------
- `--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 0.11.1
------ ------

View File

@@ -1,6 +1,6 @@
The MIT License (MIT) 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -259,6 +259,14 @@ export FZF_COMPLETION_TRIGGER='~~'
# Options to fzf command # Options to fzf command
export FZF_COMPLETION_OPTS='+c -x' 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 #### Supported commands

View File

@@ -82,11 +82,19 @@ while [ $# -gt 0 ]; do
shift shift
done done
if [ -z "$TMUX_PANE" ] || tmux list-panes -F '#F' | grep -q Z; then if [ -z "$TMUX" ]; then
fzf "${args[@]}" fzf "${args[@]}"
exit $? exit $?
fi 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 set -e
# Clean up named pipes on exit # Clean up named pipes on exit
@@ -97,6 +105,14 @@ fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id"
fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id" fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
cleanup() { cleanup() {
rm -f $argsf $fifo1 $fifo2 $fifo3 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 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_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")" [ -n "$FZF_DEFAULT_COMMAND" ] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
mkfifo $fifo2 mkfifo -m o+w $fifo2
mkfifo $fifo3 mkfifo -m o+w $fifo3
# Build arguments to fzf # Build arguments to fzf
opts="" opts=""
@@ -125,11 +141,13 @@ done
if [ -n "$term" -o -t 0 ]; then if [ -n "$term" -o -t 0 ]; then
cat <<< "$fzf $opts > $fifo2; echo \$? > $fifo3 $close" > $argsf cat <<< "$fzf $opts > $fifo2; echo \$? > $fifo3 $close" > $argsf
tmux set-window-option -q synchronize-panes off \;\ tmux set-window-option -q synchronize-panes off \;\
set-window-option -q remain-on-exit off \;\
split-window $opt "cd $(printf %q "$PWD");$envs bash $argsf" $swap split-window $opt "cd $(printf %q "$PWD");$envs bash $argsf" $swap
else else
mkfifo $fifo1 mkfifo $fifo1
cat <<< "$fzf $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" > $argsf cat <<< "$fzf $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" > $argsf
tmux set-window-option -q synchronize-panes off \;\ tmux set-window-option -q synchronize-panes off \;\
set-window-option -q remain-on-exit off \;\
split-window $opt "$envs bash $argsf" $swap split-window $opt "$envs bash $argsf" $swap
cat <&0 > $fifo1 & cat <&0 > $fifo1 &
fi fi

53
install
View File

@@ -2,8 +2,8 @@
set -u set -u
[[ "$@" =~ --pre ]] && version=0.11.1 pre=1 || [[ "$@" =~ --pre ]] && version=0.11.4 pre=1 ||
version=0.11.1 pre=0 version=0.11.4 pre=0
auto_completion= auto_completion=
key_bindings= key_bindings=
@@ -102,6 +102,16 @@ symlink() {
fi 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() { download() {
echo "Downloading bin/fzf ..." echo "Downloading bin/fzf ..."
if [ $pre = 0 ]; then if [ $pre = 0 ]; then
@@ -112,12 +122,7 @@ download() {
if [ -x "$fzf_base"/bin/$1 ]; then if [ -x "$fzf_base"/bin/$1 ]; then
symlink $1 && check_binary && return symlink $1 && check_binary && return
fi fi
if which_fzf="$(which fzf 2> /dev/null)"; then link_fzf_in_path && return
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
fi fi
mkdir -p "$fzf_base"/bin && cd "$fzf_base"/bin mkdir -p "$fzf_base"/bin && cd "$fzf_base"/bin
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
@@ -155,14 +160,7 @@ case "$archi" in
*) binary_available=0 binary_error=1 ;; *) binary_available=0 binary_error=1 ;;
esac esac
cd "$fzf_base" install_ruby_fzf() {
if [ -n "$binary_error" ]; then
if [ $binary_available -eq 0 ]; then
echo "No prebuilt binary for $archi ... "
else
echo " - $binary_error !!!"
exit 1
fi
echo "Installing legacy Ruby version ..." echo "Installing legacy Ruby version ..."
# ruby executable # ruby executable
@@ -228,6 +226,29 @@ if [ -n "$binary_error" ]; then
echo "$fzf_cmd \"\$@\"" >> "$fzf_base"/bin/fzf echo "$fzf_cmd \"\$@\"" >> "$fzf_base"/bin/fzf
chmod +x "$fzf_base"/bin/fzf chmod +x "$fzf_base"/bin/fzf
echo "OK" 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 fi
[[ "$*" =~ "--bin" ]] && exit 0 [[ "$*" =~ "--bin" ]] && exit 0

View File

@@ -1,7 +1,7 @@
.ig .ig
The MIT License (MIT) 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Dec 2015" "fzf 0.11.1" "fzf - a command-line fuzzy finder" .TH fzf 1 "Mar 2016" "fzf 0.11.4" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -68,10 +68,10 @@ Reverse the order of the input
e.g. \fBhistory | fzf --tac --no-sort\fR e.g. \fBhistory | fzf --tac --no-sort\fR
.RE .RE
.TP .TP
.BI "--tiebreak=" "CRI" .BI "--tiebreak=" "CRI[,..]"
Sort criterion to use when the scores are tied Comma-separated list of sort criteria to apply when the scores are tied.
.br .br
.R ""
.br .br
.BR length " Prefers item with shorter length" .BR length " Prefers item with shorter length"
.br .br
@@ -81,6 +81,15 @@ Sort criterion to use when the scores are tied
.br .br
.BR index " Prefers item that appeared earlier in the input stream" .BR index " Prefers item that appeared earlier in the input stream"
.br .br
.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 .SS Interface
.TP .TP
.B "-m, --multi" .B "-m, --multi"
@@ -135,7 +144,7 @@ Reverse orientation
.BI "--margin=" MARGIN .BI "--margin=" MARGIN
Comma-separated expression for margins around the finder. Comma-separated expression for margins around the finder.
.br .br
.R ""
.br .br
.RS .RS
.BR TRBL " Same margin for top, right, bottom, and left" .BR TRBL " Same margin for top, right, bottom, and left"
@@ -146,12 +155,12 @@ Comma-separated expression for margins around the finder.
.br .br
.BR T,R,B,L " Top, right, bottom, left margin" .BR T,R,B,L " Top, right, bottom, left margin"
.br .br
.R ""
.br .br
Each part can be given in absolute number or in percentage relative to the Each part can be given in absolute number or in percentage relative to the
terminal size with \fB%\fR suffix. terminal size with \fB%\fR suffix.
.br .br
.R ""
.br .br
e.g. \fBfzf --margin 10%\fR e.g. \fBfzf --margin 10%\fR
\fBfzf --margin 1,5%\fR \fBfzf --margin 1,5%\fR
@@ -166,6 +175,11 @@ Enable cyclic scroll
.B "--no-hscroll" .B "--no-hscroll"
Disable horizontal scroll Disable horizontal scroll
.TP .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" .B "--inline-info"
Display finder info inline with the query Display finder info inline with the query
.TP .TP
@@ -210,7 +224,7 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
.RE .RE
.RS .RS
\fBACTION: DEFAULT BINDINGS: \fBACTION: DEFAULT BINDINGS (NOTES):
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR \fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
\fBaccept\fR \fIenter double-click\fR \fBaccept\fR \fIenter double-click\fR
\fBbackward-char\fR \fIctrl-b left\fR \fBbackward-char\fR \fIctrl-b left\fR
@@ -240,6 +254,8 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
\fBtoggle\fR \fBtoggle\fR
\fBtoggle-all\fR \fBtoggle-all\fR
\fBtoggle-down\fR \fIctrl-i (tab)\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-sort\fR (equivalent to \fB--toggle-sort\fR)
\fBtoggle-up\fR \fIbtab (shift-tab)\fR \fBtoggle-up\fR \fIbtab (shift-tab)\fR
\fBunix-line-discard\fR \fIctrl-u\fR \fBunix-line-discard\fR \fIctrl-u\fR
@@ -353,7 +369,11 @@ Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
.SH EXIT STATUS .SH EXIT STATUS
.BR 0 " Normal exit" .BR 0 " Normal exit"
.br .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 .SH FIELD INDEX EXPRESSION
@@ -420,7 +440,7 @@ Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
.I https://github.com/junegunn/fzf .I https://github.com/junegunn/fzf
.RE .RE
.br .br
.R ""
.br .br
.B Extra Vim plugin: .B Extra Vim plugin:
.RS .RS

View File

@@ -1,4 +1,4 @@
" Copyright (c) 2015 Junegunn Choi " Copyright (c) 2016 Junegunn Choi
" "
" MIT License " MIT License
" "
@@ -52,17 +52,13 @@ function! s:fzf_exec()
return s:exec return s:exec
endfunction endfunction
function! s:tmux_not_zoomed()
return system('tmux list-panes -F "#F"') !~# 'Z'
endfunction
function! s:tmux_enabled() function! s:tmux_enabled()
if has('gui_running') if has('gui_running')
return 0 return 0
endif endif
if exists('s:tmux') if exists('s:tmux')
return s:tmux && s:tmux_not_zoomed() return s:tmux
endif endif
let s:tmux = 0 let s:tmux = 0
@@ -70,7 +66,7 @@ function! s:tmux_enabled()
let output = system('tmux -V') let output = system('tmux -V')
let s:tmux = !v:shell_error && output >= 'tmux 1.7' let s:tmux = !v:shell_error && output >= 'tmux 1.7'
endif endif
return s:tmux && s:tmux_not_zoomed() return s:tmux
endfunction endfunction
function! s:shellesc(arg) function! s:shellesc(arg)
@@ -143,17 +139,13 @@ try
let tmux = !has('nvim') && s:tmux_enabled() && s:splittable(dict) let tmux = !has('nvim') && s:tmux_enabled() && s:splittable(dict)
let command = prefix.(tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result let command = prefix.(tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
try if has('nvim')
if tmux
return s:execute_tmux(dict, command, temps)
elseif has('nvim')
return s:execute_term(dict, command, temps) return s:execute_term(dict, command, temps)
else
return s:execute(dict, command, temps)
endif endif
finally
call s:popd(dict) let ret = tmux ? s:execute_tmux(dict, command, temps) : s:execute(dict, command, temps)
endtry call s:popd(dict, ret)
return ret
finally finally
let &shell = oshell let &shell = oshell
endtry endtry
@@ -197,16 +189,28 @@ function! s:pushd(dict)
return 1 return 1
endif endif
let a:dict.prev_dir = cwd 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() let a:dict.dir = getcwd()
return 1 return 1
endif endif
return 0 return 0
endfunction endfunction
function! s:popd(dict) function! s:popd(dict, lines)
if has_key(a:dict, 'prev_dir') " Since anything can be done in the sink function, there is no telling that
execute 'chdir '.s:escape(remove(a:dict, 'prev_dir')) " 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 endif
endfunction endfunction
@@ -235,7 +239,7 @@ function! s:exit_handler(code, command, ...)
return 1 return 1
endfunction endfunction
function! s:execute(dict, command, temps) function! s:execute(dict, command, temps) abort
call s:pushd(a:dict) call s:pushd(a:dict)
silent! !clear 2> /dev/null silent! !clear 2> /dev/null
let escaped = escape(substitute(a:command, '\n', '\\n', 'g'), '%#') let escaped = escape(substitute(a:command, '\n', '\\n', 'g'), '%#')
@@ -251,7 +255,7 @@ function! s:execute(dict, command, temps)
return s:exit_handler(v:shell_error, command) ? s:callback(a:dict, a:temps) : [] return s:exit_handler(v:shell_error, command) ? s:callback(a:dict, a:temps) : []
endfunction endfunction
function! s:execute_tmux(dict, command, temps) function! s:execute_tmux(dict, command, temps) abort
let command = a:command let command = a:command
if s:pushd(a:dict) if s:pushd(a:dict)
" -c '#{pane_current_path}' is only available on tmux 1.9 or above " -c '#{pane_current_path}' is only available on tmux 1.9 or above
@@ -316,9 +320,8 @@ function! s:split(dict)
endtry endtry
endfunction endfunction
function! s:execute_term(dict, command, temps) function! s:execute_term(dict, command, temps) abort
call s:split(a:dict) call s:split(a:dict)
call s:pushd(a:dict)
let fzf = { 'buf': bufnr('%'), 'dict': a:dict, 'temps': a:temps, 'name': 'FZF' } let fzf = { 'buf': bufnr('%'), 'dict': a:dict, 'temps': a:temps, 'name': 'FZF' }
let s:command = a:command let s:command = a:command
@@ -341,8 +344,9 @@ function! s:execute_term(dict, command, temps)
endif endif
call s:pushd(self.dict) call s:pushd(self.dict)
let ret = []
try try
call s:callback(self.dict, self.temps) let ret = s:callback(self.dict, self.temps)
if inplace && bufnr('') == self.buf if inplace && bufnr('') == self.buf
execute "normal! \<c-^>" execute "normal! \<c-^>"
@@ -352,21 +356,23 @@ function! s:execute_term(dict, command, temps)
endif endif
endif endif
finally finally
call s:popd(self.dict) call s:popd(self.dict, ret)
endtry endtry
endfunction endfunction
call s:pushd(a:dict)
call termopen(a:command, fzf) call termopen(a:command, fzf)
call s:popd(a:dict, [])
setlocal nospell
setf fzf setf fzf
startinsert startinsert
return [] return []
endfunction endfunction
function! s:callback(dict, temps) function! s:callback(dict, temps) abort
try
if !filereadable(a:temps.result)
let lines = [] let lines = []
else try
if filereadable(a:temps.result)
let lines = readfile(a:temps.result) let lines = readfile(a:temps.result)
if has_key(a:dict, 'sink') if has_key(a:dict, 'sink')
for line in lines for line in lines
@@ -385,12 +391,12 @@ try
for tf in values(a:temps) for tf in values(a:temps)
silent! call delete(tf) silent! call delete(tf)
endfor endfor
return lines
catch catch
if stridx(v:exception, ':E325:') < 0 if stridx(v:exception, ':E325:') < 0
echoerr v:exception echoerr v:exception
endif endif
finally
return lines
endtry endtry
endfunction endfunction
@@ -413,10 +419,16 @@ function! s:cmd_callback(lines) abort
augroup END augroup END
endif endif
try try
let empty = empty(expand('%')) && line('$') == 1 && empty(getline(1)) && !&modified
let autochdir = &autochdir let autochdir = &autochdir
set noautochdir set noautochdir
for item in a:lines for item in a:lines
if empty
execute 'e' s:escape(item)
let empty = 0
else
execute cmd s:escape(item) execute cmd s:escape(item)
endif
if exists('#BufEnter') && isdirectory(item) if exists('#BufEnter') && isdirectory(item)
doautocmd BufEnter doautocmd BufEnter
endif endif

View File

@@ -10,6 +10,26 @@
# - $FZF_COMPLETION_TRIGGER (default: '**') # - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty) # - $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() { _fzf_orig_completion_filter() {
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2";/' | sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2";/' |
awk -F= '{gsub(/[^a-z0-9_= ;]/, "_", $1); print $1"="$2}' awk -F= '{gsub(/[^a-z0-9_= ;]/, "_", $1); print $1"="$2}'
@@ -55,11 +75,11 @@ _fzf_opts_completion() {
case "${prev}" in case "${prev}" in
--tiebreak) --tiebreak)
COMPREPLY=( $(compgen -W "length begin end index" -- ${cur}) ) COMPREPLY=( $(compgen -W "length begin end index" -- "$cur") )
return 0 return 0
;; ;;
--color) --color)
COMPREPLY=( $(compgen -W "dark light 16 bw" -- ${cur}) ) COMPREPLY=( $(compgen -W "dark light 16 bw" -- "$cur") )
return 0 return 0
;; ;;
--history) --history)
@@ -68,8 +88,8 @@ _fzf_opts_completion() {
;; ;;
esac esac
if [[ ${cur} =~ ^-|\+ ]]; then if [[ "$cur" =~ ^-|\+ ]]; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) COMPREPLY=( $(compgen -W "${opts}" -- "$cur") )
return 0 return 0
fi fi
@@ -88,31 +108,32 @@ _fzf_handle_dynamic_completion() {
elif [ -n "$_fzf_completion_loader" ]; then elif [ -n "$_fzf_completion_loader" ]; then
_completion_loader "$@" _completion_loader "$@"
ret=$? ret=$?
eval $(complete | \grep "\-F.* $orig_cmd$" | _fzf_orig_completion_filter) eval "$(complete | \grep "\-F.* $orig_cmd$" | _fzf_orig_completion_filter)"
source $BASH_SOURCE source "${BASH_SOURCE[0]}"
return $ret return $ret
fi fi
} }
__fzf_generic_path_completion() { __fzf_generic_path_completion() {
local cur base dir leftover matches trigger cmd fzf local cur base dir leftover matches trigger cmd 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"
cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g') cmd=$(echo "${COMP_WORDS[0]}" | sed 's/[^a-z0-9_=]/_/g')
COMPREPLY=() COMPREPLY=()
trigger=${FZF_COMPLETION_TRIGGER-'**'} trigger=${FZF_COMPLETION_TRIGGER-'**'}
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
if [[ ${cur} == *"$trigger" ]]; then if [[ "$cur" == *"$trigger" ]]; then
base=${cur:0:${#cur}-${#trigger}} base=${cur:0:${#cur}-${#trigger}}
eval base=$base eval "base=$base"
dir="$base" dir="$base"
while [ 1 ]; do while true; do
if [ -z "$dir" -o -d "$dir" ]; then if [ -z "$dir" ] || [ -d "$dir" ]; then
leftover=${base/#"$dir"} leftover=${base/#"$dir"}
leftover=${leftover/#\/} leftover=${leftover/#\/}
[ "$dir" = './' ] && dir='' [ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}"
tput sc 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 -r item; do
printf "%q$3 " "$item" printf "%q$3 " "$item"
done) done)
matches=${matches% } matches=${matches% }
@@ -135,29 +156,22 @@ __fzf_generic_path_completion() {
fi fi
} }
_fzf_feed_fifo() (
rm -f "$fifo"
mkfifo "$fifo"
cat <&0 > "$fifo" &
)
_fzf_complete() { _fzf_complete() {
local fifo cur selected trigger cmd fzf local cur selected trigger cmd fzf post
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$" post="$(caller 0 | awk '{print $2}')_post"
[ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf" 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-'**'} trigger=${FZF_COMPLETION_TRIGGER-'**'}
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
if [[ ${cur} == *"$trigger" ]]; then if [[ "$cur" == *"$trigger" ]]; then
cur=${cur:0:${#cur}-${#trigger}} cur=${cur:0:${#cur}-${#trigger}}
_fzf_feed_fifo "$fifo"
tput sc 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" selected=${selected% } # Strip trailing space not to repeat "-o nospace"
tput rc tput rc
rm -f "$fifo"
if [ -n "$selected" ]; then if [ -n "$selected" ]; then
COMPREPLY=("$selected") COMPREPLY=("$selected")
@@ -170,28 +184,23 @@ _fzf_complete() {
} }
_fzf_path_completion() { _fzf_path_completion() {
__fzf_generic_path_completion \ __fzf_generic_path_completion _fzf_compgen_path "-m" "" "$@"
"-name .git -prune -o -name .svn -prune -o -type d -print -o -type f -print -o -type l -print" \
"-m" "" "$@"
} }
# Deprecated. No file only completion.
_fzf_file_completion() { _fzf_file_completion() {
__fzf_generic_path_completion \ _fzf_path_completion "$@"
"-name .git -prune -o -name .svn -prune -o -type f -print -o -type l -print" \
"-m" "" "$@"
} }
_fzf_dir_completion() { _fzf_dir_completion() {
__fzf_generic_path_completion \ __fzf_generic_path_completion _fzf_compgen_dir "" "/" "$@"
"-name .git -prune -o -name .svn -prune -o -type d -print" \
"" "/" "$@"
} }
_fzf_complete_kill() { _fzf_complete_kill() {
[ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1 [ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1
local selected fzf 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 tput sc
selected=$(ps -ef | sed 1d | $fzf -m $FZF_COMPLETION_OPTS | awk '{print $2}' | tr '\n' ' ') selected=$(ps -ef | sed 1d | $fzf -m $FZF_COMPLETION_OPTS | awk '{print $2}' | tr '\n' ' ')
tput rc tput rc
@@ -238,13 +247,12 @@ _fzf_complete_unalias() {
# fzf options # fzf options
complete -o default -F _fzf_opts_completion fzf complete -o default -F _fzf_opts_completion fzf
d_cmds="cd pushd rmdir" d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
f_cmds=" a_cmds="
awk cat diff diff3 awk cat diff diff3
emacs emacsclient ex file ftp g++ gcc gvim head hg java emacs emacsclient ex file ftp g++ gcc gvim head hg java
javac ld less more mvim nvim patch perl python ruby javac ld less more mvim nvim patch perl python ruby
sed sftp sort source tail tee uniq vi view vim wc xdg-open" sed sftp sort source tail tee uniq vi view vim wc xdg-open
a_cmds="
basename bunzip2 bzip2 chmod chown curl cp dirname du basename bunzip2 bzip2 chmod chown curl cp dirname du
find git grep gunzip gzip hg jar find git grep gunzip gzip hg jar
ln ls mv open rm rsync scp ln ls mv open rm rsync scp
@@ -252,11 +260,11 @@ a_cmds="
x_cmds="kill ssh telnet unset unalias export" x_cmds="kill ssh telnet unset unalias export"
# Preserve existing completion # 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 :( # Really wish I could use associative array but OSX comes with bash 3.2 :(
eval $(complete | \grep '\-F' | \grep -v _fzf_ | 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) \grep -E " ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g' | sed 's/+/\\+/g'))$" | _fzf_orig_completion_filter)
export _fzf_completion_loaded=0.10.8 export _fzf_completion_loaded=0.11.3
fi fi
if type _completion_loader > /dev/null 2>&1; then if type _completion_loader > /dev/null 2>&1; then
@@ -277,21 +285,16 @@ _fzf_defc() {
fi 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 # Anything
for cmd in $a_cmds; do for cmd in $a_cmds; do
_fzf_defc "$cmd" _fzf_path_completion "-o default -o bashdefault" _fzf_defc "$cmd" _fzf_path_completion "-o default -o bashdefault"
done done
# Directory
for cmd in $d_cmds; do
_fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o plusdirs"
done
unset _fzf_defc unset _fzf_defc
# Kill completion # 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_export -o default -o bashdefault export
complete -F _fzf_complete_unalias -o default -o bashdefault unalias 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_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty) # - $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() { __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" # (Q) flag removes a quoting level: "foo\ bar" => "foo bar"
base=${(Q)1} base=${(Q)1}
lbuf=$2 lbuf=$2
find_opts=$3 compgen=$3
fzf_opts=$4 fzf_opts=$4
suffix=$5 suffix=$5
tail=$6 tail=$6
@@ -30,9 +50,10 @@ __fzf_generic_path_completion() {
if [ -z "$dir" -o -d ${~dir} ]; then if [ -z "$dir" -o -d ${~dir} ]; then
leftover=${base/#"$dir"} leftover=${base/#"$dir"}
leftover=${leftover/#\/} leftover=${leftover/#\/}
[ "$dir" = './' ] && dir='' [ -z "$dir" ] && dir='.'
[ "$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" printf "%q$suffix " "$item"
done) done)
matches=${matches% } matches=${matches% }
@@ -49,32 +70,33 @@ __fzf_generic_path_completion() {
} }
_fzf_path_completion() { _fzf_path_completion() {
__fzf_generic_path_completion "$1" "$2" \ __fzf_generic_path_completion "$1" "$2" _fzf_compgen_path \
"-name .git -prune -o -name .svn -prune -o -type d -print -o -type f -print -o -type l -print" \
"-m" "" " " "-m" "" " "
} }
_fzf_dir_completion() { _fzf_dir_completion() {
__fzf_generic_path_completion "$1" "$2" \ __fzf_generic_path_completion "$1" "$2" _fzf_compgen_dir \
"-name .git -prune -o -name .svn -prune -o -type d -print" \
"" "/" "" "" "/" ""
} }
_fzf_feed_fifo() ( _fzf_feed_fifo() (
rm -f "$fifo" rm -f "$1"
mkfifo "$fifo" mkfifo "$1"
cat <&0 > "$fifo" & cat <&0 > "$1" &
) )
_fzf_complete() { _fzf_complete() {
local fifo fzf_opts lbuf fzf matches local fifo fzf_opts lbuf fzf matches post
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$" fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
fzf_opts=$1 fzf_opts=$1
lbuf=$2 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_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
_fzf_feed_fifo "$fifo" _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 if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches" LBUFFER="$lbuf$matches"
fi fi
@@ -144,7 +166,7 @@ fzf-completion() {
zle redisplay zle redisplay
# Trigger sequence given # Trigger sequence given
elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then 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 "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
[ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}} [ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}}

View File

@@ -5,7 +5,7 @@ __fzf_select__() {
-o -type f -print \ -o -type f -print \
-o -type d -print \ -o -type d -print \
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"}" -o -type l -print 2> /dev/null | sed 1d | cut -b3-"}"
eval "$cmd" | fzf -m | while read item; do eval "$cmd" | fzf -m | while read -r item; do
printf '%q ' "$item" printf '%q ' "$item"
done done
echo echo
@@ -14,7 +14,7 @@ __fzf_select__() {
if [[ $- =~ i ]]; then if [[ $- =~ i ]]; then
__fzfcmd() { __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__() { __fzf_select_tmux__() {
@@ -25,7 +25,16 @@ __fzf_select_tmux__() {
else else
height="-l $height" height="-l $height"
fi 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}" != 0 ] && [ ${LINES:-40} -gt 15 ]; then
__fzf_select_tmux__
else
tmux send-keys -t "$TMUX_PANE" "$(__fzf_select__)"
fi
} }
__fzf_cd__() { __fzf_cd__() {
@@ -50,7 +59,11 @@ __fzf_history__() (
) )
__use_tmux=0 __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}" != 0 ] && [ ${LINES:-40} -gt 15 ] && __use_tmux=1
[ $BASH_VERSINFO -gt 3 ] && __use_tmux_auto=1
fi
if [ -z "$(set -o | \grep '^vi.*on')" ]; then if [ -z "$(set -o | \grep '^vi.*on')" ]; then
# Required to refresh the prompt after fzf # Required to refresh the prompt after fzf
@@ -58,7 +71,9 @@ if [ -z "$(set -o | \grep '^vi.*on')" ]; then
bind '"\e^": history-expand-line' bind '"\e^": history-expand-line'
# CTRL-T - Paste the selected file path into the command line # CTRL-T - Paste the selected file path into the command line
if [ $__use_tmux -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"' bind '"\C-t": " \C-u \C-a\C-k$(__fzf_select_tmux__)\e\C-e\C-y\C-a\C-d\C-y\ey\C-h"'
else else
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"' 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 # CTRL-T - Paste the selected file path into the command line
# - FIXME: Selected items are attached to the end regardless of cursor position # - FIXME: Selected items are attached to the end regardless of cursor position
if [ $__use_tmux -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"' bind '"\C-t": "\e$a \eddi$(__fzf_select_tmux__)\C-x\C-e\e0P$xa"'
else else
bind '"\C-t": "\e$a \eddi$(__fzf_select__)\C-x\C-e\e0Px$a \C-x\C-r\exa "' bind '"\C-t": "\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"' bind -m vi-command '"\ec": "i\ec"'
fi fi
unset __use_tmux unset -v __use_tmux __use_tmux_auto
fi fi

View File

@@ -20,6 +20,7 @@ function fzf_key_bindings
-o -type d -print \ -o -type d -print \
-o -type l -print 2> /dev/null | sed 1d | cut -b3-" -o -type l -print 2> /dev/null | sed 1d | cut -b3-"
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)" -m > $TMPDIR/fzf.result" eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)" -m > $TMPDIR/fzf.result"
and sleep 0
and commandline -i (cat $TMPDIR/fzf.result | __fzf_escape) and commandline -i (cat $TMPDIR/fzf.result | __fzf_escape)
commandline -f repaint commandline -f repaint
rm -f $TMPDIR/fzf.result rm -f $TMPDIR/fzf.result

View File

@@ -11,7 +11,6 @@ RUN cd / && curl \
tar -xz && mv go go1.4 && \ tar -xz && mv go go1.4 && \
sed -i 's@#define PTHREAD_KEYS_MAX 128@@' /go1.4/src/runtime/cgo/gcc_android_arm.c 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 GOROOT /go1.4
ENV PATH /go1.4/bin:$PATH ENV PATH /go1.4/bin:$PATH
@@ -37,8 +36,5 @@ RUN cd / && curl \
make install && \ make install && \
mv /ndk/sysroot/usr/lib/libncursesw.a /ndk/sysroot/usr/lib/libncurses.a mv /ndk/sysroot/usr/lib/libncursesw.a /ndk/sysroot/usr/lib/libncurses.a
# Volume
VOLUME /go
# Default CMD # 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 | \ https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz | \
tar -xz && mv go go1.4 tar -xz && mv go go1.4
ENV GOPATH /go
ENV GOROOT /go1.4 ENV GOROOT /go1.4
ENV PATH /go1.4/bin:$PATH 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 && \ pacman-db-upgrade && yes | pacman -Sy gcc-multilib lib32-ncurses && \
cd $GOROOT/src && GOARCH=386 ./make.bash cd $GOROOT/src && GOARCH=386 ./make.bash
# Volume
VOLUME /go
# Default CMD # Default CMD
CMD cd /go/src/github.com/junegunn/fzf/src && /bin/bash CMD cd /fzf/src && /bin/bash

View File

@@ -13,21 +13,16 @@ RUN cd / && curl \
# Install Go 1.5 # Install Go 1.5
RUN cd / && curl \ 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 tar -xz && mv go go1.5
ENV GO15VENDOREXPERIMENT 1
ENV GOROOT_BOOTSTRAP /go1.4 ENV GOROOT_BOOTSTRAP /go1.4
ENV GOROOT /go1.5 ENV GOROOT /go1.5
ENV GOPATH /go
ENV PATH /go1.5/bin:$PATH ENV PATH /go1.5/bin:$PATH
# For i386 build # For i386 build
RUN cd $GOROOT/src && GOARCH=386 ./make.bash RUN cd $GOROOT/src && GOARCH=386 ./make.bash
# Volume
VOLUME /go
# Default CMD # 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 | \ https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz | \
tar -xz && mv go go1.4 tar -xz && mv go go1.4
ENV GOPATH /go
ENV GOROOT /go1.4 ENV GOROOT /go1.4
ENV PATH /go1.4/bin:$PATH ENV PATH /go1.4/bin:$PATH
@@ -18,9 +17,6 @@ ENV PATH /go1.4/bin:$PATH
RUN apt-get install -y lib32ncurses5-dev && \ RUN apt-get install -y lib32ncurses5-dev && \
cd $GOROOT/src && GOARCH=386 ./make.bash cd $GOROOT/src && GOARCH=386 ./make.bash
# Volume
VOLUME /go
# Default CMD # Default CMD
CMD cd /go/src/github.com/junegunn/fzf/src && /bin/bash CMD cd /fzf/src && /bin/bash

View File

@@ -1,6 +1,6 @@
The MIT License (MIT) 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

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

View File

@@ -6,8 +6,11 @@ import (
) )
func TestChunkList(t *testing.T) { func TestChunkList(t *testing.T) {
// FIXME global
sortCriteria = []criterion{byMatchLen, byLength}
cl := NewChunkList(func(s []byte, i int) *Item { 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 // Snapshot
@@ -36,8 +39,11 @@ func TestChunkList(t *testing.T) {
if len(*chunk1) != 2 { if len(*chunk1) != 2 {
t.Error("Snapshot should contain only two items") t.Error("Snapshot should contain only two items")
} }
if string((*chunk1)[0].text) != "hello" || (*chunk1)[0].rank.index != 0 || last := func(arr [5]int32) int32 {
string((*chunk1)[1].text) != "world" || (*chunk1)[1].rank.index != 2 { 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") t.Error("Invalid data")
} }
if chunk1.IsFull() { if chunk1.IsFull() {

View File

@@ -8,7 +8,7 @@ import (
const ( const (
// Current version // Current version
version = "0.11.1" version = "0.11.4"
// Core // Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond 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/^..//` defaultCommand = `find . -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | sed s/^..//`
// Terminal // Terminal
initialDelay = 100 * time.Millisecond initialDelay = 20 * time.Millisecond
initialDelayTac = 100 * time.Millisecond
spinnerDuration = 200 * time.Millisecond spinnerDuration = 200 * time.Millisecond
// Matcher // Matcher

View File

@@ -3,7 +3,7 @@ Package fzf implements fzf, a command-line fuzzy finder.
The MIT License (MIT) 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -52,7 +52,7 @@ func Run(opts *Options) {
initProcs() initProcs()
sort := opts.Sort > 0 sort := opts.Sort > 0
rankTiebreak = opts.Tiebreak sortCriteria = opts.Criteria
if opts.Version { if opts.Version {
fmt.Println(version) fmt.Println(version)
@@ -103,9 +103,8 @@ func Run(opts *Options) {
runes, colors := ansiProcessor(data) runes, colors := ansiProcessor(data)
return &Item{ return &Item{
text: runes, text: runes,
index: uint32(index),
colors: colors, colors: colors,
rank: Rank{0, 0, uint32(index)}} rank: buildEmptyRank(int32(index))}
}) })
} else { } else {
chunkList = NewChunkList(func(data []byte, index int) *Item { chunkList = NewChunkList(func(data []byte, index int) *Item {
@@ -120,9 +119,8 @@ func Run(opts *Options) {
item := Item{ item := Item{
text: joinTokens(trans), text: joinTokens(trans),
origText: &runes, origText: &runes,
index: uint32(index),
colors: nil, colors: nil,
rank: Rank{0, 0, uint32(index)}} rank: buildEmptyRank(int32(index))}
trimmed, colors := ansiProcessorRunes(item.text) trimmed, colors := ansiProcessorRunes(item.text)
item.text = trimmed item.text = trimmed
@@ -141,9 +139,19 @@ func Run(opts *Options) {
} }
// Matcher // Matcher
forward := true
for _, cri := range opts.Criteria[1:] {
if cri == byEnd {
forward = false
break
}
if cri == byBegin {
break
}
}
patternBuilder := func(runes []rune) *Pattern { patternBuilder := func(runes []rune) *Pattern {
return BuildPattern( return BuildPattern(
opts.Fuzzy, opts.Extended, opts.Case, opts.Tiebreak != byEnd, opts.Fuzzy, opts.Extended, opts.Case, forward,
opts.Nth, opts.Delimiter, runes) opts.Nth, opts.Delimiter, runes)
} }
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox) matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)

View File

@@ -20,31 +20,41 @@ type Item struct {
text []rune text []rune
origText *[]rune origText *[]rune
transformed []Token transformed []Token
index uint32
offsets []Offset offsets []Offset
colors []ansiOffset colors []ansiOffset
rank Rank rank [5]int32
} }
// Rank is used to sort the search result // Sort criteria to use. Never changes once fzf is started.
type Rank struct { var sortCriteria []criterion
matchlen uint16
tiebreak uint16 func isRankValid(rank [5]int32) bool {
index uint32 // 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. func buildEmptyRank(index int32) [5]int32 {
var rankTiebreak tiebreak return [5]int32{0, 0, 0, 0, index}
}
func (item *Item) Index() int32 {
return item.rank[4]
}
// Rank calculates rank of the Item // Rank calculates rank of the Item
func (item *Item) Rank(cache bool) Rank { func (item *Item) Rank(cache bool) [5]int32 {
if cache && (item.rank.matchlen > 0 || item.rank.tiebreak > 0) { if cache && isRankValid(item.rank) {
return item.rank return item.rank
} }
matchlen := 0 matchlen := 0
prevEnd := 0 prevEnd := 0
lenSum := 0 lenSum := 0
minBegin := math.MaxUint16 minBegin := math.MaxInt32
for _, offset := range item.offsets { for _, offset := range item.offsets {
begin := int(offset[0]) begin := int(offset[0])
end := int(offset[1]) end := int(offset[1])
@@ -64,32 +74,42 @@ func (item *Item) Rank(cache bool) Rank {
} }
} }
if matchlen == 0 { if matchlen == 0 {
matchlen = math.MaxUint16 matchlen = math.MaxInt32
} }
var tiebreak uint16 rank := buildEmptyRank(item.Index())
switch rankTiebreak { for idx, criterion := range sortCriteria {
var val int32
switch criterion {
case byMatchLen:
val = int32(matchlen)
case byLength: case byLength:
// It is guaranteed that .transformed in not null in normal execution // It is guaranteed that .transformed in not null in normal execution
if item.transformed != nil { if item.transformed != nil {
// If offsets is empty, lenSum will be 0, but we don't care // If offsets is empty, lenSum will be 0, but we don't care
tiebreak = uint16(lenSum) val = int32(lenSum)
} else { } else {
tiebreak = uint16(len(item.text)) val = int32(len(item.text))
} }
case byBegin: case byBegin:
// We can't just look at item.offsets[0][0] because it can be an inverse term // We can't just look at item.offsets[0][0] because it can be an inverse term
tiebreak = uint16(minBegin) whitePrefixLen := 0
for idx, r := range item.text {
whitePrefixLen = idx
if idx == minBegin || r != ' ' && r != '\t' {
break
}
}
val = int32(minBegin - whitePrefixLen)
case byEnd: case byEnd:
if prevEnd > 0 { if prevEnd > 0 {
tiebreak = uint16(1 + len(item.text) - prevEnd) val = int32(1 + len(item.text) - prevEnd)
} else { } else {
// Empty offsets due to inverse terms. // Empty offsets due to inverse terms.
tiebreak = 1 val = 1
} }
case byIndex:
tiebreak = 1
} }
rank := Rank{uint16(matchlen), tiebreak, item.index} rank[idx] = val
}
if cache { if cache {
item.rank = rank item.rank = rank
} }
@@ -254,18 +274,15 @@ func (a ByRelevanceTac) Less(i, j int) bool {
return compareRanks(irank, jrank, true) return compareRanks(irank, jrank, true)
} }
func compareRanks(irank Rank, jrank Rank, tac bool) bool { func compareRanks(irank [5]int32, jrank [5]int32, tac bool) bool {
if irank.matchlen < jrank.matchlen { for idx := 0; idx < 4; idx++ {
left := irank[idx]
right := jrank[idx]
if left < right {
return true return true
} else if irank.matchlen > jrank.matchlen { } else if left > right {
return false return false
} }
if irank.tiebreak < jrank.tiebreak {
return true
} else if irank.tiebreak > jrank.tiebreak {
return false
} }
return (irank[4] <= jrank[4]) != tac
return (irank.index <= jrank.index) != tac
} }

View File

@@ -23,31 +23,34 @@ func TestOffsetSort(t *testing.T) {
} }
func TestRankComparison(t *testing.T) { func TestRankComparison(t *testing.T) {
if compareRanks(Rank{3, 0, 5}, Rank{2, 0, 7}, false) || if compareRanks([5]int32{3, 0, 0, 0, 5}, [5]int32{2, 0, 0, 0, 7}, false) ||
!compareRanks(Rank{3, 0, 5}, Rank{3, 0, 6}, false) || !compareRanks([5]int32{3, 0, 0, 0, 5}, [5]int32{3, 0, 0, 0, 6}, false) ||
!compareRanks(Rank{1, 2, 3}, Rank{1, 3, 2}, false) || !compareRanks([5]int32{1, 2, 0, 0, 3}, [5]int32{1, 3, 0, 0, 2}, false) ||
!compareRanks(Rank{0, 0, 0}, Rank{0, 0, 0}, false) { !compareRanks([5]int32{0, 0, 0, 0, 0}, [5]int32{0, 0, 0, 0, 0}, false) {
t.Error("Invalid order") t.Error("Invalid order")
} }
if compareRanks(Rank{3, 0, 5}, Rank{2, 0, 7}, true) || if compareRanks([5]int32{3, 0, 0, 0, 5}, [5]int32{2, 0, 0, 0, 7}, true) ||
!compareRanks(Rank{3, 0, 5}, Rank{3, 0, 6}, false) || !compareRanks([5]int32{3, 0, 0, 0, 5}, [5]int32{3, 0, 0, 0, 6}, false) ||
!compareRanks(Rank{1, 2, 3}, Rank{1, 3, 2}, true) || !compareRanks([5]int32{1, 2, 0, 0, 3}, [5]int32{1, 3, 0, 0, 2}, true) ||
!compareRanks(Rank{0, 0, 0}, Rank{0, 0, 0}, false) { !compareRanks([5]int32{0, 0, 0, 0, 0}, [5]int32{0, 0, 0, 0, 0}, false) {
t.Error("Invalid order (tac)") t.Error("Invalid order (tac)")
} }
} }
// Match length, string length, index // Match length, string length, index
func TestItemRank(t *testing.T) { func TestItemRank(t *testing.T) {
// FIXME global
sortCriteria = []criterion{byMatchLen, byLength}
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")} 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) 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)) t.Error(item1.Rank(true))
} }
// Only differ in index // Only differ in index
item2 := Item{text: strs[0], index: 0, offsets: []Offset{}} item2 := Item{text: strs[0], offsets: []Offset{}}
items := []*Item{&item1, &item2} items := []*Item{&item1, &item2}
sort.Sort(ByRelevance(items)) sort.Sort(ByRelevance(items))
@@ -63,10 +66,10 @@ func TestItemRank(t *testing.T) {
} }
// Sort by relevance // Sort by relevance
item3 := Item{text: strs[1], rank: Rank{0, 0, 2}, offsets: []Offset{Offset{1, 3}, Offset{5, 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: Rank{0, 0, 2}, offsets: []Offset{Offset{1, 2}, Offset{6, 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: Rank{0, 0, 2}, offsets: []Offset{Offset{1, 3}, Offset{5, 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: Rank{0, 0, 2}, offsets: []Offset{Offset{1, 2}, Offset{6, 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} items = []*Item{&item1, &item2, &item3, &item4, &item5, &item6}
sort.Sort(ByRelevance(items)) sort.Sort(ByRelevance(items))
if items[0] != &item6 || items[1] != &item4 || if items[0] != &item6 || items[1] != &item4 ||

View File

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

View File

@@ -88,7 +88,7 @@ func (mg *Merger) cacheable() bool {
func (mg *Merger) mergedGet(idx int) *Item { func (mg *Merger) mergedGet(idx int) *Item {
for i := len(mg.merged); i <= idx; i++ { for i := len(mg.merged); i <= idx; i++ {
minRank := Rank{0, 0, 0} minRank := buildEmptyRank(0)
minIdx := -1 minIdx := -1
for listIdx, list := range mg.lists { for listIdx, list := range mg.lists {
cursor := mg.cursors[listIdx] cursor := mg.cursors[listIdx]

View File

@@ -23,7 +23,7 @@ func randItem() *Item {
} }
return &Item{ return &Item{
text: []rune(str), text: []rune(str),
index: rand.Uint32(), rank: buildEmptyRank(rand.Int31()),
offsets: offsets} offsets: offsets}
} }

View File

@@ -27,7 +27,8 @@ const usage = `usage: fzf [options]
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style) -d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
+s, --no-sort Do not sort the result +s, --no-sort Do not sort the result
--tac Reverse the order of the input --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) [length|begin|end|index] (default: length)
Interface Interface
@@ -41,6 +42,8 @@ const usage = `usage: fzf [options]
--tabstop=SPACES Number of spaces for a tab character (default: 8) --tabstop=SPACES Number of spaces for a tab character (default: 8)
--cycle Enable cyclic scroll --cycle Enable cyclic scroll
--no-hscroll Disable horizontal 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 --inline-info Display finder info inline with the query
--prompt=STR Input prompt (default: '> ') --prompt=STR Input prompt (default: '> ')
--bind=KEYBINDS Custom key bindings. Refer to the man page. --bind=KEYBINDS Custom key bindings. Refer to the man page.
@@ -75,13 +78,13 @@ const (
) )
// Sort criteria // Sort criteria
type tiebreak int type criterion int
const ( const (
byLength tiebreak = iota byMatchLen criterion = iota
byLength
byBegin byBegin
byEnd byEnd
byIndex
) )
func defaultMargin() [4]string { func defaultMargin() [4]string {
@@ -98,7 +101,7 @@ type Options struct {
Delimiter Delimiter Delimiter Delimiter
Sort int Sort int
Tac bool Tac bool
Tiebreak tiebreak Criteria []criterion
Multi bool Multi bool
Ansi bool Ansi bool
Mouse bool Mouse bool
@@ -107,6 +110,7 @@ type Options struct {
Reverse bool Reverse bool
Cycle bool Cycle bool
Hscroll bool Hscroll bool
HscrollOff int
InlineInfo bool InlineInfo bool
Prompt string Prompt string
Query string Query string
@@ -145,7 +149,7 @@ func defaultOptions() *Options {
Delimiter: Delimiter{}, Delimiter: Delimiter{},
Sort: 1000, Sort: 1000,
Tac: false, Tac: false,
Tiebreak: byLength, Criteria: []criterion{byMatchLen, byLength},
Multi: false, Multi: false,
Ansi: false, Ansi: false,
Mouse: true, Mouse: true,
@@ -154,6 +158,7 @@ func defaultOptions() *Options {
Reverse: false, Reverse: false,
Cycle: false, Cycle: false,
Hscroll: true, Hscroll: true,
HscrollOff: 10,
InlineInfo: false, InlineInfo: false,
Prompt: "> ", Prompt: "> ",
Query: "", Query: "",
@@ -162,7 +167,7 @@ func defaultOptions() *Options {
Filter: nil, Filter: nil,
ToggleSort: false, ToggleSort: false,
Expect: make(map[int]string), Expect: make(map[int]string),
Keymap: defaultKeymap(), Keymap: make(map[int]actionType),
Execmap: make(map[int]string), Execmap: make(map[int]string),
PrintQuery: false, PrintQuery: false,
ReadZero: false, ReadZero: false,
@@ -361,20 +366,39 @@ func parseKeyChords(str string, message string) map[int]string {
return chords return chords
} }
func parseTiebreak(str string) tiebreak { func parseTiebreak(str string) []criterion {
switch strings.ToLower(str) { criteria := []criterion{byMatchLen}
case "length": hasIndex := false
return byLength 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
}
for _, str := range strings.Split(strings.ToLower(str), ",") {
switch str {
case "index": case "index":
return byIndex check(&hasIndex, "index")
case "length":
check(&hasLength, "length")
criteria = append(criteria, byLength)
case "begin": case "begin":
return byBegin check(&hasBegin, "begin")
criteria = append(criteria, byBegin)
case "end": case "end":
return byEnd check(&hasEnd, "end")
criteria = append(criteria, byEnd)
default: default:
errorExit("invalid sort criterion: " + str) errorExit("invalid sort criterion: " + str)
} }
return byLength }
return criteria
} }
func dupeTheme(theme *curses.ColorTheme) *curses.ColorTheme { func dupeTheme(theme *curses.ColorTheme) *curses.ColorTheme {
@@ -464,7 +488,7 @@ const (
escapedComma = 1 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 { if executeRegexp == nil {
// Backreferences are not supported. // Backreferences are not supported.
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|') // "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
@@ -546,6 +570,10 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, toggleSort b
keymap[key] = actToggleDown keymap[key] = actToggleDown
case "toggle-up": case "toggle-up":
keymap[key] = actToggleUp keymap[key] = actToggleUp
case "toggle-in":
keymap[key] = actToggleIn
case "toggle-out":
keymap[key] = actToggleOut
case "toggle-all": case "toggle-all":
keymap[key] = actToggleAll keymap[key] = actToggleAll
case "select-all": case "select-all":
@@ -568,7 +596,6 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, toggleSort b
keymap[key] = actNextHistory keymap[key] = actNextHistory
case "toggle-sort": case "toggle-sort":
keymap[key] = actToggleSort keymap[key] = actToggleSort
toggleSort = true
default: default:
if isExecuteAction(actLower) { if isExecuteAction(actLower) {
var offset int var offset int
@@ -589,7 +616,6 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, toggleSort b
} }
} }
} }
return keymap, execmap, toggleSort
} }
func isExecuteAction(str string) bool { func isExecuteAction(str string) bool {
@@ -611,13 +637,12 @@ func isExecuteAction(str string) bool {
return false 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") keys := parseKeyChords(str, "key name required")
if len(keys) != 1 { if len(keys) != 1 {
errorExit("multiple keys specified") errorExit("multiple keys specified")
} }
keymap[firstKey(keys)] = actToggleSort keymap[firstKey(keys)] = actToggleSort
return keymap
} }
func strLines(str string) []string { func strLines(str string) []string {
@@ -667,7 +692,6 @@ func parseMargin(margin string) [4]string {
} }
func parseOptions(opts *Options, allArgs []string) { func parseOptions(opts *Options, allArgs []string) {
keymap := make(map[int]actionType)
var historyMax int var historyMax int
if opts.History == nil { if opts.History == nil {
historyMax = defaultHistoryMax historyMax = defaultHistoryMax
@@ -715,10 +739,9 @@ func parseOptions(opts *Options, allArgs []string) {
case "--expect": case "--expect":
opts.Expect = parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required") opts.Expect = parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required")
case "--tiebreak": case "--tiebreak":
opts.Tiebreak = parseTiebreak(nextString(allArgs, &i, "sort criterion required")) opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
case "--bind": case "--bind":
keymap, opts.Execmap, opts.ToggleSort = parseKeymap(opts.Keymap, opts.Execmap, nextString(allArgs, &i, "bind expression required"))
parseKeymap(keymap, opts.Execmap, opts.ToggleSort, nextString(allArgs, &i, "bind expression required"))
case "--color": case "--color":
spec := optionalNextString(allArgs, &i) spec := optionalNextString(allArgs, &i)
if len(spec) == 0 { if len(spec) == 0 {
@@ -727,8 +750,7 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Theme = parseTheme(opts.Theme, spec) opts.Theme = parseTheme(opts.Theme, spec)
} }
case "--toggle-sort": case "--toggle-sort":
keymap = checkToggleSort(keymap, nextString(allArgs, &i, "key name required")) parseToggleSort(opts.Keymap, nextString(allArgs, &i, "key name required"))
opts.ToggleSort = true
case "-d", "--delimiter": case "-d", "--delimiter":
opts.Delimiter = delimiterRegexp(nextString(allArgs, &i, "delimiter required")) opts.Delimiter = delimiterRegexp(nextString(allArgs, &i, "delimiter required"))
case "-n", "--nth": case "-n", "--nth":
@@ -777,6 +799,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Hscroll = true opts.Hscroll = true
case "--no-hscroll": case "--no-hscroll":
opts.Hscroll = false opts.Hscroll = false
case "--hscroll-off":
opts.HscrollOff = nextInt(allArgs, &i, "hscroll offset required")
case "--inline-info": case "--inline-info":
opts.InlineInfo = true opts.InlineInfo = true
case "--no-inline-info": case "--no-inline-info":
@@ -845,17 +869,15 @@ func parseOptions(opts *Options, allArgs []string) {
} else if match, _ := optString(arg, "-s", "--sort="); match { } else if match, _ := optString(arg, "-s", "--sort="); match {
opts.Sort = 1 // Don't care opts.Sort = 1 // Don't care
} else if match, value := optString(arg, "--toggle-sort="); match { } else if match, value := optString(arg, "--toggle-sort="); match {
keymap = checkToggleSort(keymap, value) parseToggleSort(opts.Keymap, value)
opts.ToggleSort = true
} else if match, value := optString(arg, "--expect="); match { } else if match, value := optString(arg, "--expect="); match {
opts.Expect = parseKeyChords(value, "key names required") opts.Expect = parseKeyChords(value, "key names required")
} else if match, value := optString(arg, "--tiebreak="); match { } else if match, value := optString(arg, "--tiebreak="); match {
opts.Tiebreak = parseTiebreak(value) opts.Criteria = parseTiebreak(value)
} else if match, value := optString(arg, "--color="); match { } else if match, value := optString(arg, "--color="); match {
opts.Theme = parseTheme(opts.Theme, value) opts.Theme = parseTheme(opts.Theme, value)
} else if match, value := optString(arg, "--bind="); match { } else if match, value := optString(arg, "--bind="); match {
keymap, opts.Execmap, opts.ToggleSort = parseKeymap(opts.Keymap, opts.Execmap, value)
parseKeymap(keymap, opts.Execmap, opts.ToggleSort, value)
} else if match, value := optString(arg, "--history="); match { } else if match, value := optString(arg, "--history="); match {
setHistory(value) setHistory(value)
} else if match, value := optString(arg, "--history-size="); match { } else if match, value := optString(arg, "--history-size="); match {
@@ -868,6 +890,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Margin = parseMargin(value) opts.Margin = parseMargin(value)
} else if match, value := optString(arg, "--tabstop="); match { } else if match, value := optString(arg, "--tabstop="); match {
opts.Tabstop = atoi(value) opts.Tabstop = atoi(value)
} else if match, value := optString(arg, "--hscroll-off="); match {
opts.HscrollOff = atoi(value)
} else { } else {
errorExit("unknown option: " + arg) errorExit("unknown option: " + arg)
} }
@@ -878,24 +902,35 @@ func parseOptions(opts *Options, allArgs []string) {
errorExit("header lines must be a non-negative integer") 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 { if opts.Tabstop < 1 {
errorExit("tab stop must be a positive integer") 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 opts.History != nil {
if _, prs := keymap[curses.CtrlP]; !prs { if _, prs := opts.Keymap[curses.CtrlP]; !prs {
keymap[curses.CtrlP] = actPreviousHistory opts.Keymap[curses.CtrlP] = actPreviousHistory
} }
if _, prs := keymap[curses.CtrlN]; !prs { if _, prs := opts.Keymap[curses.CtrlN]; !prs {
keymap[curses.CtrlN] = actNextHistory opts.Keymap[curses.CtrlN] = actNextHistory
} }
} }
// Override default key bindings // Extend the default key map
for key, act := range keymap { keymap := defaultKeymap()
opts.Keymap[key] = act 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 we're not using extended search mode, --nth option becomes irrelevant
// if it contains the whole range // if it contains the whole range
@@ -915,9 +950,13 @@ func ParseOptions() *Options {
// Options from Env var // Options from Env var
words, _ := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS")) words, _ := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS"))
if len(words) > 0 {
parseOptions(opts, words) parseOptions(opts, words)
}
// Options from command-line arguments // Options from command-line arguments
parseOptions(opts, os.Args[1:]) parseOptions(opts, os.Args[1:])
postProcessOptions(opts)
return opts return opts
} }

View File

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

@@ -306,10 +306,9 @@ func dupItem(item *Item, offsets []Offset) *Item {
text: item.text, text: item.text,
origText: item.origText, origText: item.origText,
transformed: item.transformed, transformed: item.transformed,
index: item.index,
offsets: offsets, offsets: offsets,
colors: item.colors, colors: item.colors,
rank: Rank{0, 0, item.index}} rank: buildEmptyRank(item.Index())}
} }
func (p *Pattern) basicMatch(item *Item) (int, int, int) { func (p *Pattern) basicMatch(item *Item) (int, int, int) {

View File

@@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"io" "io"
"os" "os"
"os/exec"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
) )
@@ -59,7 +58,7 @@ func (r *Reader) readFromStdin() {
} }
func (r *Reader) readFromCommand(cmd string) { func (r *Reader) readFromCommand(cmd string) {
listCommand := exec.Command("sh", "-c", cmd) listCommand := util.ExecCommand(cmd)
out, err := listCommand.StdoutPipe() out, err := listCommand.StdoutPipe()
if err != nil { if err != nil {
return return

View File

@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"os" "os"
"os/exec"
"os/signal" "os/signal"
"regexp" "regexp"
"sort" "sort"
@@ -22,10 +21,12 @@ import (
// Terminal represents terminal input/output // Terminal represents terminal input/output
type Terminal struct { type Terminal struct {
initDelay time.Duration
inlineInfo bool inlineInfo bool
prompt string prompt string
reverse bool reverse bool
hscroll bool hscroll bool
hscrollOff int
cx int cx int
cy int cy int
offset int offset int
@@ -50,7 +51,7 @@ type Terminal struct {
progress int progress int
reading bool reading bool
merger *Merger merger *Merger
selected map[uint32]selectedItem selected map[int32]selectedItem
reqBox *util.EventBox reqBox *util.EventBox
eventBox *util.EventBox eventBox *util.EventBox
mutex sync.Mutex mutex sync.Mutex
@@ -125,6 +126,8 @@ const (
actToggleAll actToggleAll
actToggleDown actToggleDown
actToggleUp actToggleUp
actToggleIn
actToggleOut
actDown actDown
actUp actUp
actPageUp actPageUp
@@ -196,11 +199,19 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
header = reverseStringArray(opts.Header) header = reverseStringArray(opts.Header)
} }
_tabStop = opts.Tabstop _tabStop = opts.Tabstop
var delay time.Duration
if opts.Tac {
delay = initialDelayTac
} else {
delay = initialDelay
}
return &Terminal{ return &Terminal{
initDelay: delay,
inlineInfo: opts.InlineInfo, inlineInfo: opts.InlineInfo,
prompt: opts.Prompt, prompt: opts.Prompt,
reverse: opts.Reverse, reverse: opts.Reverse,
hscroll: opts.Hscroll, hscroll: opts.Hscroll,
hscrollOff: opts.HscrollOff,
cx: len(input), cx: len(input),
cy: 0, cy: 0,
offset: 0, offset: 0,
@@ -223,7 +234,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
ansi: opts.Ansi, ansi: opts.Ansi,
reading: true, reading: true,
merger: EmptyMerger, merger: EmptyMerger,
selected: make(map[uint32]selectedItem), selected: make(map[int32]selectedItem),
reqBox: util.NewEventBox(), reqBox: util.NewEventBox(),
eventBox: eventBox, eventBox: eventBox,
mutex: sync.Mutex{}, mutex: sync.Mutex{},
@@ -464,9 +475,8 @@ func (t *Terminal) printHeader() {
state = newState state = newState
item := &Item{ item := &Item{
text: []rune(trimmed), text: []rune(trimmed),
index: 0,
colors: colors, colors: colors,
rank: Rank{0, 0, 0}} rank: buildEmptyRank(0)}
t.move(line, 2, true) t.move(line, 2, true)
t.printHighlighted(item, false, C.ColHeader, 0, false) t.printHighlighted(item, false, C.ColHeader, 0, false)
@@ -491,7 +501,7 @@ func (t *Terminal) printList() {
} }
func (t *Terminal) printItem(item *Item, current bool) { func (t *Terminal) printItem(item *Item, current bool) {
_, selected := t.selected[item.index] _, selected := t.selected[item.Index()]
if current { if current {
C.CPrint(C.ColCursor, true, ">") C.CPrint(C.ColCursor, true, ">")
if selected { if selected {
@@ -548,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) { 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 { for _, offset := range item.offsets {
if offset[1] > maxe { maxe = util.Max(maxe, int(offset[1]))
maxe = offset[1]
}
} }
// Overflow // Overflow
@@ -560,6 +568,7 @@ func (t *Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int, c
copy(text, item.text) copy(text, item.text)
offsets := item.colorOffsets(col2, bold, current) offsets := item.colorOffsets(col2, bold, current)
maxWidth := C.MaxX() - 3 - t.marginInt[1] - t.marginInt[3] 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) fullWidth := displayWidth(text)
if fullWidth > maxWidth { if fullWidth > maxWidth {
if t.hscroll { if t.hscroll {
@@ -711,7 +720,7 @@ func quoteEntry(entry string) string {
func executeCommand(template string, replacement string) { func executeCommand(template string, replacement string) {
command := strings.Replace(template, "{}", replacement, -1) command := strings.Replace(template, "{}", replacement, -1)
cmd := exec.Command("sh", "-c", command) cmd := util.ExecCommand(command)
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
@@ -725,7 +734,7 @@ func (t *Terminal) Loop() {
<-t.startChan <-t.startChan
{ // Late initialization { // Late initialization
intChan := make(chan os.Signal, 1) intChan := make(chan os.Signal, 1)
signal.Notify(intChan, os.Interrupt, os.Kill) signal.Notify(intChan, os.Interrupt, os.Kill, syscall.SIGTERM)
go func() { go func() {
<-intChan <-intChan
t.reqBox.Set(reqQuit, nil) t.reqBox.Set(reqQuit, nil)
@@ -750,7 +759,7 @@ func (t *Terminal) Loop() {
t.printHeader() t.printHeader()
t.mutex.Unlock() t.mutex.Unlock()
go func() { go func() {
timer := time.NewTimer(initialDelay) timer := time.NewTimer(t.initDelay)
<-timer.C <-timer.C
t.reqBox.Set(reqRefresh, nil) t.reqBox.Set(reqRefresh, nil)
}() }()
@@ -836,8 +845,8 @@ func (t *Terminal) Loop() {
} }
} }
selectItem := func(item *Item) bool { selectItem := func(item *Item) bool {
if _, found := t.selected[item.index]; !found { if _, found := t.selected[item.Index()]; !found {
t.selected[item.index] = selectedItem{time.Now(), item.StringPtr(t.ansi)} t.selected[item.Index()] = selectedItem{time.Now(), item.StringPtr(t.ansi)}
return true return true
} }
return false return false
@@ -845,7 +854,7 @@ func (t *Terminal) Loop() {
toggleY := func(y int) { toggleY := func(y int) {
item := t.merger.Get(y) item := t.merger.Get(y)
if !selectItem(item) { if !selectItem(item) {
delete(t.selected, item.index) delete(t.selected, item.Index())
} }
} }
toggle := func() { toggle := func() {
@@ -934,7 +943,7 @@ func (t *Terminal) Loop() {
if t.multi { if t.multi {
for i := 0; i < t.merger.Length(); i++ { for i := 0; i < t.merger.Length(); i++ {
item := t.merger.Get(i) item := t.merger.Get(i)
delete(t.selected, item.index) delete(t.selected, item.Index())
} }
req(reqList, reqInfo) req(reqList, reqInfo)
} }
@@ -950,6 +959,16 @@ func (t *Terminal) Loop() {
} }
req(reqList, reqInfo) 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: case actToggleDown:
if t.multi && t.merger.Length() > 0 { if t.multi && t.merger.Length() > 0 {
toggle() toggle()
@@ -1104,15 +1123,7 @@ func (t *Terminal) constrain() {
diffpos := t.cy - t.offset diffpos := t.cy - t.offset
t.cy = util.Constrain(t.cy, 0, count-1) t.cy = util.Constrain(t.cy, 0, count-1)
t.offset = util.Constrain(t.offset, t.cy-height+1, t.cy)
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
}
// Adjustment // Adjustment
if count-t.offset < height { if count-t.offset < height {
t.offset = util.Max(0, count-height) t.offset = util.Max(0, count-height)

View File

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

@@ -1,6 +1,7 @@
Execute (Setup): Execute (Setup):
let g:dir = fnamemodify(g:vader_file, ':p:h') let g:dir = fnamemodify(g:vader_file, ':p:h')
Log 'Test directory: ' . g:dir Log 'Test directory: ' . g:dir
Save &acd
Execute (fzf#run with dir option): Execute (fzf#run with dir option):
let cwd = getcwd() let cwd = getcwd()
@@ -35,6 +36,34 @@ Execute (fzf#run with string source):
let result = sort(fzf#run({ 'source': 'echo hi', 'options': '-f i' })) let result = sort(fzf#run({ 'source': 'echo hi', 'options': '-f i' }))
AssertEqual ['hi'], result 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): Execute (Cleanup):
unlet g:dir unlet g:dir
Restore Restore

View File

@@ -143,8 +143,10 @@ class TestBase < Minitest::Test
attr_reader :tmux attr_reader :tmux
def tempname def tempname
@temp_suffix ||= 0
[TEMPNAME, [TEMPNAME,
caller_locations.map(&:label).find { |l| l =~ /^test_/ }].join '-' caller_locations.map(&:label).find { |l| l =~ /^test_/ },
@temp_suffix].join '-'
end end
def setup def setup
@@ -158,6 +160,7 @@ class TestBase < Minitest::Test
File.read(tempname) File.read(tempname)
ensure ensure
File.unlink tempname while File.exists?(tempname) File.unlink tempname while File.exists?(tempname)
@temp_suffix += 1
tmux.prepare tmux.prepare
end end
@@ -459,8 +462,8 @@ class TestGoFZF < TestBase
def test_unicode_case def test_unicode_case
writelines tempname, %w[строКА1 СТРОКА2 строка3 Строка4] writelines tempname, %w[строКА1 СТРОКА2 строка3 Строка4]
assert_equal %w[СТРОКА2 Строка4], `cat #{tempname} | #{FZF} -fС`.split($/) assert_equal %w[СТРОКА2 Строка4], `#{FZF} -fС < #{tempname}`.split($/)
assert_equal %w[строКА1 СТРОКА2 строка3 Строка4], `cat #{tempname} | #{FZF} -fс`.split($/) assert_equal %w[строКА1 СТРОКА2 строка3 Строка4], `#{FZF} -fс < #{tempname}`.split($/)
end end
def test_tiebreak def test_tiebreak
@@ -472,7 +475,7 @@ class TestGoFZF < TestBase
] ]
writelines tempname, input 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[ by_length = %w[
----foobar-- ----foobar--
@@ -480,8 +483,8 @@ class TestGoFZF < TestBase
-------foobar- -------foobar-
--foobar-------- --foobar--------
] ]
assert_equal by_length, `cat #{tempname} | #{FZF} -ffoobar`.split($/) assert_equal by_length, `#{FZF} -ffoobar < #{tempname}`.split($/)
assert_equal by_length, `cat #{tempname} | #{FZF} -ffoobar --tiebreak=length`.split($/) assert_equal by_length, `#{FZF} -ffoobar --tiebreak=length < #{tempname}`.split($/)
by_begin = %w[ by_begin = %w[
--foobar-------- --foobar--------
@@ -489,17 +492,175 @@ class TestGoFZF < TestBase
-----foobar--- -----foobar---
-------foobar- -------foobar-
] ]
assert_equal by_begin, `cat #{tempname} | #{FZF} -ffoobar --tiebreak=begin`.split($/) assert_equal by_begin, `#{FZF} -ffoobar --tiebreak=begin < #{tempname}`.split($/)
assert_equal by_begin, `cat #{tempname} | #{FZF} -f"!z foobar" -x --tiebreak begin`.split($/) assert_equal by_begin, `#{FZF} -f"!z foobar" -x --tiebreak begin < #{tempname}`.split($/)
assert_equal %w[ assert_equal %w[
-------foobar- -------foobar-
----foobar-- ----foobar--
-----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 end
def test_tiebreak_length_with_nth def test_tiebreak_length_with_nth
@@ -517,7 +678,7 @@ class TestGoFZF < TestBase
123:hello 123:hello
1234567:h 1234567:h
] ]
assert_equal output, `cat #{tempname} | #{FZF} -fh`.split($/) assert_equal output, `#{FZF} -fh < #{tempname}`.split($/)
output = %w[ output = %w[
1234567:h 1234567:h
@@ -525,7 +686,7 @@ class TestGoFZF < TestBase
1:hell 1:hell
123:hello 123:hello
] ]
assert_equal output, `cat #{tempname} | #{FZF} -fh -n2 -d:`.split($/) assert_equal output, `#{FZF} -fh -n2 -d: < #{tempname}`.split($/)
end end
def test_tiebreak_length_with_nth_trim_length def test_tiebreak_length_with_nth_trim_length
@@ -544,7 +705,7 @@ class TestGoFZF < TestBase
"apple juice bottle 1", "apple juice bottle 1",
"apple ui bottle 2", "apple ui bottle 2",
] ]
assert_equal output, `cat #{tempname} | #{FZF} -fa -n1`.split($/) assert_equal output, `#{FZF} -fa -n1 < #{tempname}`.split($/)
# len(1 ~ 2) # len(1 ~ 2)
output = [ output = [
@@ -553,7 +714,7 @@ class TestGoFZF < TestBase
"apple juice bottle 1", "apple juice bottle 1",
"app ice bottle 3", "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) # len(1) + len(2)
output = [ output = [
@@ -562,7 +723,7 @@ class TestGoFZF < TestBase
"apple ui bottle 2", "apple ui bottle 2",
"apple juice bottle 1", "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) # len(2)
output = [ output = [
@@ -571,8 +732,8 @@ class TestGoFZF < TestBase
"app ice bottle 3", "app ice bottle 3",
"apple juice bottle 1", "apple juice bottle 1",
] ]
assert_equal output, `cat #{tempname} | #{FZF} -fi -n2`.split($/) assert_equal output, `#{FZF} -fi -n2 < #{tempname}`.split($/)
assert_equal output, `cat #{tempname} | #{FZF} -fi -n2,1..2`.split($/) assert_equal output, `#{FZF} -fi -n2,1..2 < #{tempname}`.split($/)
end end
def test_tiebreak_end_backward_scan def test_tiebreak_end_backward_scan
@@ -582,8 +743,8 @@ class TestGoFZF < TestBase
] ]
writelines tempname, input writelines tempname, input
assert_equal input.reverse, `cat #{tempname} | #{FZF} -f fb`.split($/) assert_equal input.reverse, `#{FZF} -f fb < #{tempname}`.split($/)
assert_equal input, `cat #{tempname} | #{FZF} -f fb --tiebreak=end`.split($/) assert_equal input, `#{FZF} -f fb --tiebreak=end < #{tempname}`.split($/)
end end
def test_invalid_cache def test_invalid_cache
@@ -613,7 +774,7 @@ class TestGoFZF < TestBase
File.open(tempname, 'w') do |f| File.open(tempname, 'w') do |f|
f << data f << data
end end
assert_equal data, `cat #{tempname} | #{FZF} -f .`.chomp assert_equal data, `#{FZF} -f . < #{tempname}`.chomp
end end
def test_read0 def test_read0
@@ -732,6 +893,24 @@ class TestGoFZF < TestBase
File.unlink output rescue nil File.unlink output rescue nil
end 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 def test_cycle
tmux.send_keys "seq 8 | #{fzf :cycle}", :Enter tmux.send_keys "seq 8 | #{fzf :cycle}", :Enter
tmux.until { |lines| lines[-2].include? '8/8' } tmux.until { |lines| lines[-2].include? '8/8' }
@@ -881,25 +1060,25 @@ class TestGoFZF < TestBase
}.each do |ts, exp| }.each do |ts, exp|
tmux.prepare tmux.prepare
tmux.send_keys %[cat #{tempname} | fzf --tabstop=#{ts}], :Enter 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 tmux.send_keys :Enter
end end
end end
def test_with_nth def test_with_nth
writelines tempname, ['hello world ', 'byebye'] 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 end
def test_with_nth_ansi def test_with_nth_ansi
writelines tempname, ["\x1b[33mhello \x1b[34;1mworld\x1b[m ", 'byebye'] 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 end
def test_with_nth_no_ansi def test_with_nth_no_ansi
src = "\x1b[33mhello \x1b[34;1mworld\x1b[m " src = "\x1b[33mhello \x1b[34;1mworld\x1b[m "
writelines tempname, [src, 'byebye'] 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 end
def test_exit_0_exit_code def test_exit_0_exit_code
@@ -960,10 +1139,22 @@ class TestGoFZF < TestBase
`seq 10 | #{FZF} -f '1 | !1'`.lines.map(&:chomp) `seq 10 | #{FZF} -f '1 | !1'`.lines.map(&:chomp)
end 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 private
def writelines path, lines def writelines path, lines
File.unlink path while File.exists? path 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
end end
@@ -1071,6 +1262,8 @@ module CompletionTest
tmux.prepare tmux.prepare
tmux.send_keys 'cat /tmp/fzf-test/10**', :Tab, pane: 0 tmux.send_keys 'cat /tmp/fzf-test/10**', :Tab, pane: 0
tmux.until(1) { |lines| lines.item_count > 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.send_keys :BTab, :BTab
tmux.until(1) { |lines| lines[-2].include?('(2)') } tmux.until(1) { |lines| lines[-2].include?('(2)') }
tmux.send_keys :Enter tmux.send_keys :Enter
@@ -1106,17 +1299,33 @@ module CompletionTest
tmux.send_keys 'C-u' tmux.send_keys 'C-u'
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab, pane: 0 tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab, pane: 0
tmux.until(1) { |lines| lines.item_count > 0 } tmux.until(1) { |lines| lines.item_count > 0 }
tmux.send_keys :Enter tmux.send_keys 'C-K', :Enter
tmux.until do |lines| tmux.until do |lines|
tmux.send_keys 'C-L' tmux.send_keys 'C-L'
lines[-1].end_with?('/tmp/fzf\ test/foobar') lines[-1].end_with?('/tmp/fzf\ test/foobar')
end 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 ensure
['/tmp/fzf-test', '/tmp/fzf test', '~/.fzf-home', 'no~such~user'].each do |f| ['/tmp/fzf-test', '/tmp/fzf test', '~/.fzf-home', 'no~such~user'].each do |f|
FileUtils.rm_rf File.expand_path(f) FileUtils.rm_rf File.expand_path(f)
end end
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 def test_dir_completion
tmux.send_keys 'mkdir -p /tmp/fzf-test/d{1..100}; touch /tmp/fzf-test/d55/xxx', :Enter tmux.send_keys 'mkdir -p /tmp/fzf-test/d{1..100}; touch /tmp/fzf-test/d55/xxx', :Enter
tmux.prepare tmux.prepare
@@ -1163,6 +1372,39 @@ module CompletionTest
ensure ensure
Process.kill 'KILL', pid.to_i rescue nil if pid Process.kill 'KILL', pid.to_i rescue nil if pid
end 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 end
class TestBash < TestBase class TestBash < TestBase