Compare commits

..

53 Commits

Author SHA1 Message Date
Junegunn Choi
fccc93176b 0.13.3 2016-07-16 01:06:53 +09:00
Junegunn Choi
6439a138fe [install] Build fzf if prebuilt binary doesn't work
Close #617
2016-07-16 00:36:35 +09:00
Junegunn Choi
a9a29dff4f Fix duplicate rendering of the last line in preview window 2016-07-15 23:24:14 +09:00
Junegunn Choi
6a52f8b8dd [zsh-completion] setopt localoptions noksh_arrays
Close #607
2016-07-15 01:26:29 +09:00
Junegunn Choi
a1049328d6 [vim] Adjust split size when --header option is set
Close #622
2016-07-14 13:35:18 +09:00
Junegunn Choi
5c2b96bd00 [vim] Fix error with multi-line $FZF_DEFAULT_COMMAND
Close #620
2016-07-13 13:15:14 +09:00
Junegunn Choi
c36413fdf6 [zsh] Suppress error message when pipefail is not supported
Close #615
2016-07-11 17:47:41 +09:00
Junegunn Choi
52cf5af91c [test] Fix test failure on Travis CI
No guarantee in the order in which files are listed
2016-07-10 15:44:44 +09:00
Junegunn Choi
3a4e053af7 [bash] Fall back to send-keys if named paste buffer is not supported
Related: #616
2016-07-10 15:21:28 +09:00
Junegunn Choi
049bc9ec68 [fzf-tmux] Add man page 2016-07-10 14:44:00 +09:00
Junegunn Choi
b461a555b8 [fzf-tmux] Add --version and --help flags 2016-07-10 14:41:06 +09:00
Junegunn Choi
0f87b2d1e1 [fzf-tmux] Use double brackets
For consistency and (negligible) performance improvement
2016-07-10 14:34:29 +09:00
Junegunn Choi
0fb5b76c0d [fzf-tmux] Fail fast if fzf excutable is not found 2016-07-10 14:28:58 +09:00
Junegunn Choi
0c918dd87a Merge pull request #616 from seanlaguna/master
Use tmux buffers for sending output to preserve character encoding
2016-07-10 12:29:32 +09:00
Junegunn Choi
05299a0fee [test] Use tmux buffer in unicode test cases
Related #616
2016-07-10 12:27:01 +09:00
Sean
b36b0a91f5 use tmux buffers for sending output to preserve character encoding 2016-07-09 09:47:20 -05:00
Junegunn Choi
6081eac58a [shell] Suppress alias/function expansion
Close #611
2016-07-07 01:40:14 +09:00
Junegunn Choi
942ba749c7 [vim] Restore working directory even when new window is opened
Close #612
2016-07-06 13:31:04 +09:00
Junegunn Choi
f941012687 Merge pull request #610 from eigengrau/master
[zsh] Re-initialize zle when widgets finish
2016-07-05 21:50:43 +09:00
Sebastian Reuße
fed5e5d5af [zsh] Re-initialize zle when widgets finish
zle automatically calls zle-line-init when it starts to read a new line. Many
Zsh setups use this hook to set the terminal into application mode, since this
will then allow defining keybinds based on the $terminfo variable (the escape
codes in said variable are only valid in application mode).

However, fzf resets the terminal into raw mode, rendering $terminfo values
invalid once the widget has finished. Accordingly, keyboard bindings defined
via $terminfo won’t work anymore.

This fixes the issue by calling zle-line-init when widgets finish. Care is taken
to not call this widget when it is undefined.

Fixes #279
2016-07-05 08:57:11 +02:00
Junegunn Choi
b864885753 [install] Make sure to unset pipefail 2016-07-04 13:05:26 +09:00
Junegunn Choi
64747c2324 [install] Fix error in install script
Close #608
2016-07-04 13:00:30 +09:00
Junegunn Choi
34965edcda [install] Fall back to wget if curl failed
Close #605
2016-07-04 01:41:43 +09:00
Junegunn Choi
bd4377084d Merge pull request #601 from blueyed/zsh-ret-for-fzf-file-widget
zsh: pass through exit code from fzf with fzf-file-widget
2016-06-18 23:17:10 +09:00
Daniel Hahler
38a2076b89 zsh: pass through exit code from widgets
This allows to have a custom widget like the following, which would
additionally accept the line, but only in case of entries being
selected:

    fzf-file-widget-with-accept() {
      zle fzf-file-widget
      if [[ "$?" == 0 ]] && (( $#BUFFER )); then
        zle accept-line
      fi
    }
    zle     -N   fzf-file-widget-with-accept
    bindkey '\e^T' fzf-file-widget-with-accept

With this `<C-a>t` will launch fzf, and simulate the pressing of "Enter"
afterwards.
2016-06-16 20:20:29 +02:00
Junegunn Choi
5759d50d4a 0.13.2 2016-06-16 02:16:13 +09:00
Junegunn Choi
e455836cc9 Fix race condition where preview window is not properly cleared 2016-06-15 13:15:17 +09:00
Junegunn Choi
8a90f26c8a 0.13.1 2016-06-14 21:53:00 +09:00
Junegunn Choi
24e1fabf2e Do not process ANSI codes in --preview output at once
Close #598
2016-06-14 21:52:47 +09:00
Junegunn Choi
c39c039e15 [shell] Add $FZF_CTRL_T_OPTS and $FZF_ALT_C_OPTS
Close #596
2016-06-12 20:48:23 +09:00
Junegunn Choi
07f176f426 Merge pull request #595 from aykamko/speed-up-fzf-completion
Optimize fzf_default_completion binding
2016-06-12 11:56:49 +09:00
Aleks Kamko
19339e3a6d optimize fzf_default_completion binding 2016-06-11 15:19:16 -07:00
Junegunn Choi
3e1d6a7bcf 0.13.0 2016-06-12 02:15:11 +09:00
Junegunn Choi
2bbc12063c Add --preview and --preview-window
Close #587
2016-06-11 19:59:12 +09:00
Junegunn Choi
b8737b724b Ignore controls chars for bracketed paste mode
Close #594
2016-06-11 12:14:34 +09:00
Junegunn Choi
d91c3a2f5e Merge pull request #593 from edi9999/master
Add fzf_prefer_tmux option
2016-06-10 22:58:58 +09:00
Edgar Hipp
fe5db5aadc Add fzf_prefer_tmux option 2016-06-10 09:05:05 +02:00
Junegunn Choi
cf9c957c66 Update test_execute_shell (#590) 2016-06-08 02:16:07 +09:00
Junegunn Choi
68b60c6d19 Update test_execute_multi (#590) 2016-06-08 02:15:22 +09:00
Junegunn Choi
3a644b16a4 Update test_execute (#590) 2016-06-08 02:04:40 +09:00
Junegunn Choi
95b34de339 [bash/zsh] Fix $FZF_CTRL_R_OPTS with option values with spaces 2016-06-08 01:30:26 +09:00
Junegunn Choi
6a431cbf49 [fzf-tmux] Escape $ in arguments
e.g. fzf-tmux -q '$PATH'

Related: #343
2016-06-08 01:27:22 +09:00
Junegunn Choi
56fb2f00b3 Use single-quoted strings in execute action
Close #590
2016-06-08 00:54:21 +09:00
Junegunn Choi
1c86aaf342 [vim/fzf-tmux] Handle fzf project directory with spaces
Close #583
2016-06-03 12:09:31 +09:00
Junegunn Choi
cfc0b18eaa Revert "Change tmux pane title for fzf splits"
This reverts commit f074709fc9.

Close #586. /cc @akashin
2016-06-03 12:02:21 +09:00
Junegunn Choi
412c211655 [vim] Use lcd instead of chdir
https://github.com/junegunn/fzf.vim/issues/147
2016-06-02 22:24:47 +09:00
Junegunn Choi
923feb69ab [zsh] Fix indentation 2016-06-02 22:01:26 +09:00
Junegunn Choi
92dba7035a Merge pull request #584 from jimbocoder/master
Take SSH completion hints from known_hosts
2016-06-02 21:58:59 +09:00
Jim Howell
b8a3ba16a2 [bash/zsh] Take SSH completion hints from known_hosts
Signed-off-by: Junegunn Choi <junegunn.c@gmail.com>
2016-06-02 21:58:01 +09:00
Junegunn Choi
cd5e4d9402 Merge pull request #582 from akashin/master
[fzf-tmux] Change tmux pane title for fzf splits
2016-06-01 17:07:07 +09:00
Andrey Kashin
f074709fc9 Change tmux pane title for fzf splits 2016-06-01 10:19:26 +03:00
Junegunn Choi
e0b29e437b [bash] Use backticks to avoid delay with blink-matching-paren
Close #580
2016-05-29 02:11:50 +09:00
Junegunn Choi
bdb94fba7d [zsh] Fix #579 - Locally unset globsubst 2016-05-26 00:52:06 +09:00
22 changed files with 1075 additions and 500 deletions

View File

@@ -1,6 +1,27 @@
CHANGELOG
=========
0.13.3
------
- Fixed duplicate rendering of the last line in preview window
0.13.2
------
- Fixed race condition where preview window is not properly cleared
0.13.1
------
- Fixed UI issue with large `--preview` output with many ANSI codes
0.13.0
------
- Added preview feature
- `--preview CMD`
- `--preview-window POS[:SIZE][:hidden]`
- `{}` in execute action is now replaced to the single-quoted (instead of
double-quoted) string of the current line
- Fixed to ignore control characters for bracketed paste mode
0.12.2
------

View File

@@ -178,11 +178,14 @@ fish.
- `CTRL-T` - Paste the selected files and directories onto the command line
- Set `FZF_CTRL_T_COMMAND` to override the default command
- Set `FZF_CTRL_T_OPTS` to pass additional options
- `CTRL-R` - Paste the selected command from history onto the command line
- Sort is disabled by default to respect chronological ordering
- Press `CTRL-R` again to toggle sort
- Set `FZF_CTRL_R_OPTS` to pass additional options
- `ALT-C` - cd into the selected directory
- Set `FZF_ALT_C_COMMAND` to override the default command
- Set `FZF_ALT_C_OPTS` to pass additional options
If you're on a tmux session, fzf will start in a split pane. You may disable
this tmux integration by setting `FZF_TMUX` to 0, or change the height of the

View File

@@ -2,25 +2,51 @@
# fzf-tmux: starts fzf in a tmux pane
# usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
fail() {
>&2 echo "$1"
exit 2
}
fzf="$(command -v fzf 2> /dev/null)" || fzf="$(dirname "$0")/fzf"
[[ -x "$fzf" ]] || fail 'fzf executable not found'
args=()
opt=""
skip=""
swap=""
close=""
term=""
[ -n "$LINES" ] && lines=$LINES || lines=$(tput lines)
while [ $# -gt 0 ]; do
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines)
help() {
>&2 echo 'usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
Layout
-u [HEIGHT[%]] Split above (up)
-d [HEIGHT[%]] Split below (down)
-l [WIDTH[%]] Split left
-r [WIDTH[%]] Split right
(default: -d 50%)
'
exit
}
while [[ $# -gt 0 ]]; do
arg="$1"
case "$arg" in
shift
[[ -z "$skip" ]] && case "$arg" in
-)
term=1
;;
--help)
help
;;
--version)
echo "fzf-tmux (with fzf $("$fzf" --version))"
exit
;;
-w*|-h*|-d*|-u*|-r*|-l*)
if [ -n "$skip" ]; then
args+=("$1")
shift
continue
fi
if [[ "$arg" =~ ^.[lrw] ]]; then
opt="-h"
if [[ "$arg" =~ ^.l ]]; then
@@ -36,35 +62,33 @@ while [ $# -gt 0 ]; do
close="; tmux swap-pane -D"
fi
fi
if [ ${#arg} -gt 2 ]; then
if [[ ${#arg} -gt 2 ]]; then
size="${arg:2}"
else
shift
if [[ "$1" =~ ^[0-9]+%?$ ]]; then
size="$1"
else
[ -n "$1" -a "$1" != "--" ] && args+=("$1")
shift
else
continue
fi
fi
if [[ "$size" =~ %$ ]]; then
size=${size:0:((${#size}-1))}
if [ -n "$swap" ]; then
if [[ -n "$swap" ]]; then
opt="$opt -p $(( 100 - size ))"
else
opt="$opt -p $size"
fi
else
if [ -n "$swap" ]; then
if [[ -n "$swap" ]]; then
if [[ "$arg" =~ ^.l ]]; then
[ -n "$COLUMNS" ] && max=$COLUMNS || max=$(tput cols)
[[ -n "$COLUMNS" ]] && max=$COLUMNS || max=$(tput cols)
else
max=$lines
fi
size=$(( max - size ))
[ $size -lt 0 ] && size=0
[[ $size -lt 0 ]] && size=0
opt="$opt -l $size"
else
opt="$opt -l $size"
@@ -75,16 +99,17 @@ while [ $# -gt 0 ]; do
# "--" can be used to separate fzf-tmux options from fzf options to
# avoid conflicts
skip=1
continue
;;
*)
args+=("$1")
args+=("$arg")
;;
esac
shift
[[ -n "$skip" ]] && args+=("$arg")
done
if ! [ -n "$TMUX" -a "$lines" -gt 15 ]; then
fzf "${args[@]}"
if [[ -z "$TMUX" ]] || [[ "$lines" -le 15 ]]; then
"$fzf" "${args[@]}"
exit $?
fi
@@ -108,7 +133,7 @@ cleanup() {
rm -f $argsf $fifo1 $fifo2 $fifo3
# Remove temp window if we were zoomed
if [ -n "$zoomed" ]; then
if [[ -n "$zoomed" ]]; then
tmux swap-pane -t $original_window \; \
select-window -t $original_window \; \
kill-window -t $tmp_window \; \
@@ -117,16 +142,9 @@ cleanup() {
}
trap cleanup EXIT SIGINT SIGTERM
fail() {
>&2 echo "$1"
exit 2
}
fzf="$(which fzf 2> /dev/null)" || fzf="$(dirname "$0")/fzf"
[ -x "$fzf" ] || fail "fzf executable not found"
envs="env TERM=$TERM "
[ -n "$FZF_DEFAULT_OPTS" ] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
[ -n "$FZF_DEFAULT_COMMAND" ] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
mkfifo -m o+w $fifo2
mkfifo -m o+w $fifo3
@@ -137,18 +155,19 @@ for arg in "${args[@]}"; do
arg="${arg//\\/\\\\}"
arg="${arg//\"/\\\"}"
arg="${arg//\`/\\\`}"
arg="${arg//$/\\$}"
opts="$opts \"$arg\""
done
if [ -n "$term" -o -t 0 ]; then
cat <<< "$fzf $opts > $fifo2; echo \$? > $fifo3 $close" > $argsf
if [[ -n "$term" ]] || [[ -t 0 ]]; then
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" > $argsf
tmux set-window-option synchronize-panes off \;\
set-window-option remain-on-exit off \;\
split-window $opt "cd $(printf %q "$PWD");$envs bash $argsf" $swap \
> /dev/null 2>&1
else
mkfifo $fifo1
cat <<< "$fzf $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" > $argsf
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" > $argsf
tmux set-window-option synchronize-panes off \;\
set-window-option remain-on-exit off \;\
split-window $opt "$envs bash $argsf" $swap \

59
install
View File

@@ -2,13 +2,14 @@
set -u
[[ "$@" =~ --pre ]] && version=0.12.2 pre=1 ||
version=0.12.2 pre=0
[[ "$@" =~ --pre ]] && version=0.13.3 pre=1 ||
version=0.13.3 pre=0
auto_completion=
key_bindings=
update_config=2
binary_arch=
allow_legacy=
help() {
cat << EOF
@@ -37,6 +38,7 @@ for opt in "$@"; do
auto_completion=1
key_bindings=1
update_config=1
allow_legacy=1
;;
--key-bindings) key_bindings=1 ;;
--no-key-bindings) key_bindings=0 ;;
@@ -109,6 +111,14 @@ link_fzf_in_path() {
return 1
}
try_curl() {
command -v curl > /dev/null && curl -fL $1 | tar -xz
}
try_wget() {
command -v wget > /dev/null && wget -O - $1 | tar -xz
}
download() {
echo "Downloading bin/fzf ..."
if [ $pre = 0 ]; then
@@ -128,14 +138,13 @@ download() {
fi
local url=https://github.com/junegunn/fzf-bin/releases/download/$version/${1}.tgz
if command -v curl > /dev/null; then
curl -fL $url | tar -xz
elif command -v wget > /dev/null; then
wget -O - $url | tar -xz
else
binary_error="curl or wget not found"
set -o pipefail
if ! (try_curl $url || try_wget $url); then
set +o pipefail
binary_error="Failed to download with curl and wget"
return
fi
set +o pipefail
if [ ! -f $1 ]; then
binary_error="Failed to download ${1}"
@@ -158,6 +167,9 @@ case "$archi" in
esac
install_ruby_fzf() {
if [ -z "$allow_legacy" ]; then
ask "Do you want to install legacy Ruby version instead?" && exit 1
fi
echo "Installing legacy Ruby version ..."
# ruby executable
@@ -229,26 +241,25 @@ cd "$fzf_base"
if [ -n "$binary_error" ]; then
if [ $binary_available -eq 0 ]; then
echo "No prebuilt binary for $archi ..."
if command -v go > /dev/null; then
echo -n "Building binary (go get -u github.com/junegunn/fzf/src/fzf) ... "
if [ -z "${GOPATH-}" ]; then
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
mkdir -p "$GOPATH"
fi
if go get -u github.com/junegunn/fzf/src/fzf; then
echo "OK"
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
else
echo "Failed to build binary ..."
install_ruby_fzf
fi
else
echo " - $binary_error !!!"
fi
if command -v go > /dev/null; then
echo -n "Building binary (go get -u github.com/junegunn/fzf/src/fzf) ... "
if [ -z "${GOPATH-}" ]; then
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
mkdir -p "$GOPATH"
fi
if go get -u github.com/junegunn/fzf/src/fzf; then
echo "OK"
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
else
echo "go executable not found. Cannot build binary ..."
echo "Failed to build binary ..."
install_ruby_fzf
fi
else
echo " - $binary_error !!!"
exit 1
echo "go executable not found. Cannot build binary ..."
install_ruby_fzf
fi
fi

54
man/man1/fzf-tmux.1 Normal file
View File

@@ -0,0 +1,54 @@
.ig
The MIT License (MIT)
Copyright (c) 2016 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf-tmux 1 "Jul 2016" "fzf 0.13.3" "fzf-tmux - open fzf in tmux split pane"
.SH NAME
fzf-tmux - open fzf in tmux split pane
.SH SYNOPSIS
.B fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
.SH DESCRIPTION
fzf-tmux is a wrapper script for fzf that opens fzf in a tmux split pane. It is
designed to work just like fzf except that it does not take up the whole
screen. You can safely use fzf-tmux instead of fzf in your scripts as the extra
options will be silently ignored if you're not on tmux.
.SH OPTIONS
.SS Layout
(default: \fB-d 50%\fR)
.TP
.B "-u [height[%]]"
Split above (up)
.TP
.B "-d [height[%]]"
Split below (down)
.TP
.B "-l [width[%]]"
Split left
.TP
.B "-r [width[%]]"
Split right

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf 1 "May 2016" "fzf 0.12.2" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Jul 2016" "fzf 0.13.3" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -50,10 +50,10 @@ Case-sensitive match
.TP
.BI "-n, --nth=" "N[,..]"
Comma-separated list of field index expressions for limiting search scope.
See \fBFIELD INDEX EXPRESSION\fR for details.
See \fBFIELD INDEX EXPRESSION\fR for the details.
.TP
.BI "--with-nth=" "N[,..]"
Transform each item using index expressions within finder
Transform the presentation of each line using field index expressions
.TP
.BI "-d, --delimiter=" "STR"
Field delimiter regex for \fB--nth\fR and \fB--with-nth\fR (default: AWK-style)
@@ -64,6 +64,7 @@ Do not sort the result
.TP
.B "--tac"
Reverse the order of the input
.RS
e.g. \fBhistory | fzf --tac --no-sort\fR
.RE
@@ -73,13 +74,13 @@ Comma-separated list of sort criteria to apply when the scores are tied.
.br
.br
.BR length " Prefers item with shorter length"
.BR length " Prefers line with shorter length"
.br
.BR begin " Prefers item with matched substring closer to the beginning"
.BR begin " Prefers line with matched substring closer to the beginning"
.br
.BR end " Prefers item with matched substring closer to the end"
.BR end " Prefers line with matched substring closer to the end"
.br
.BR index " Prefers item that appeared earlier in the input stream"
.BR index " Prefers line that appeared earlier in the input stream"
.br
.br
@@ -90,16 +91,86 @@ Comma-separated list of sort criteria to apply when the scores are tied.
- \fBindex\fR is implicitly appended to the list when not specified
.br
- Default is \fBlength\fR (or equivalently \fBlength\fR,index)
.br
- If \fBend\fR is found in the list, fzf will scan each line backwards
.SS Interface
.TP
.B "-m, --multi"
Enable multi-select with tab/shift-tab
.TP
.B "--no-mouse"
Disable mouse
.TP
.BI "--bind=" "KEYBINDS"
Comma-separated list of custom key bindings. See \fBKEY BINDINGS\fR for the
details.
.TP
.B "--cycle"
Enable cyclic scroll
.TP
.B "--no-hscroll"
Disable horizontal scroll
.TP
.BI "--hscroll-off=" "COL"
Number of screen columns to keep to the right of the highlighted substring
(default: 10). Setting it to a large value will cause the text to be positioned
on the center of the screen.
.TP
.BI "--jump-labels=" "CHARS"
Label characters for \fBjump\fR and \fBjump-accept\fR
.SS Layout
.TP
.B "--reverse"
Reverse orientation
.TP
.BI "--margin=" MARGIN
Comma-separated expression for margins around the finder.
.br
.br
.RS
.BR TRBL " Same margin for top, right, bottom, and left"
.br
.BR TB,RL " Vertical, horizontal margin"
.br
.BR T,RL,B " Top, horizontal, bottom margin"
.br
.BR T,R,B,L " Top, right, bottom, left margin"
.br
.br
Each part can be given in absolute number or in percentage relative to the
terminal size with \fB%\fR suffix.
.br
.br
e.g. \fBfzf --margin 10%\fR
\fBfzf --margin 1,5%\fR
.RE
.TP
.B "--inline-info"
Display finder info inline with the query
.TP
.BI "--prompt=" "STR"
Input prompt (default: '> ')
.TP
.BI "--header=" "STR"
The given string will be printed as the sticky header. The lines are displayed
in the given order from top to bottom regardless of \fB--reverse\fR option, and
are not affected by \fB--with-nth\fR. ANSI color codes are processed even when
\fB--ansi\fR is not set.
.TP
.BI "--header-lines=" "N"
The first N lines of the input are treated as the sticky header. When
\fB--with-nth\fR is set, the lines are transformed just like the other
lines that follow.
.SS Display
.TP
.B "--ansi"
Enable processing of ANSI color codes
.TP
.B "--no-mouse"
Disable mouse
.BI "--tabstop=" SPACES
Number of spaces for a tab character (default: 8)
.TP
.BI "--color=" "[BASE_SCHEME][,COLOR:ANSI]"
Color configuration. The name of the base color scheme is followed by custom
@@ -137,181 +208,7 @@ e.g. \fBfzf --color=bg+:24\fR
.TP
.B "--black"
Use black background
.TP
.B "--reverse"
Reverse orientation
.TP
.BI "--margin=" MARGIN
Comma-separated expression for margins around the finder.
.br
.br
.RS
.BR TRBL " Same margin for top, right, bottom, and left"
.br
.BR TB,RL " Vertical, horizontal margin"
.br
.BR T,RL,B " Top, horizontal, bottom margin"
.br
.BR T,R,B,L " Top, right, bottom, left margin"
.br
.br
Each part can be given in absolute number or in percentage relative to the
terminal size with \fB%\fR suffix.
.br
.br
e.g. \fBfzf --margin 10%\fR
\fBfzf --margin 1,5%\fR
.RE
.TP
.BI "--tabstop=" SPACES
Number of spaces for a tab character (default: 8)
.TP
.B "--cycle"
Enable cyclic scroll
.TP
.B "--no-hscroll"
Disable horizontal scroll
.TP
.BI "--hscroll-off=" "COL"
Number of screen columns to keep to the right of the highlighted substring
(default: 10). Setting it to a large value will cause the text to be positioned
on the center of the screen.
.TP
.B "--inline-info"
Display finder info inline with the query
.TP
.BI "--jump-labels=" "CHARS"
Label characters for \fBjump\fR and \fBjump-accept\fR
.TP
.BI "--prompt=" "STR"
Input prompt (default: '> ')
.TP
.BI "--toggle-sort=" "KEY"
Key to toggle sort. For the list of the allowed key names, see \fB--bind\fR.
.TP
.BI "--bind=" "KEYBINDS"
Comma-separated list of custom key bindings. Each key binding expression
follows the following format: \fBKEY:ACTION\fR
.RS
e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
.RE
.RS
.B AVAILABLE KEYS: (SYNONYMS)
\fIctrl-[a-z]\fR
\fIalt-[a-z]\fR
\fIf[1-10]\fR
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
\fIspace\fR
\fIbspace\fR (\fIbs\fR)
\fIalt-enter\fR
\fIalt-space\fR
\fIalt-bspace\fR (\fIalt-bs\fR)
\fIalt-/\fR
\fItab\fR
\fIbtab\fR (\fIshift-tab\fR)
\fIesc\fR
\fIdel\fR
\fIup\fR
\fIdown\fR
\fIleft\fR
\fIright\fR
\fIhome\fR
\fIend\fR
\fIpgup\fR (\fIpage-up\fR)
\fIpgdn\fR (\fIpage-down\fR)
\fIshift-left\fR
\fIshift-right\fR
\fIdouble-click\fR
or any single character
.RE
.RS
\fBACTION: DEFAULT BINDINGS (NOTES):
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
\fBaccept\fR \fIenter double-click\fR
\fBbackward-char\fR \fIctrl-b left\fR
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
\fBbackward-kill-word\fR \fIalt-bs\fR
\fBbackward-word\fR \fIalt-b shift-left\fR
\fBbeginning-of-line\fR \fIctrl-a home\fR
\fBcancel\fR
\fBclear-screen\fR \fIctrl-l\fR
\fBdelete-char\fR \fIdel\fR
\fBdelete-char/eof\fR \fIctrl-d\fR
\fBdeselect-all\fR
\fBdown\fR \fIctrl-j ctrl-n down\fR
\fBend-of-line\fR \fIctrl-e end\fR
\fBexecute(...)\fR (see below for the details)
\fBexecute-multi(...)\fR (see below for the details)
\fBforward-char\fR \fIctrl-f right\fR
\fBforward-word\fR \fIalt-f shift-right\fR
\fBignore\fR
\fBjump\fR (EasyMotion-like 2-keystroke movement)
\fBjump-accept\fR (jump and accept)
\fBkill-line\fR
\fBkill-word\fR \fIalt-d\fR
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
\fBpage-down\fR \fIpgdn\fR
\fBpage-up\fR \fIpgup\fR
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
\fBprint-query\fR (print query and exit)
\fBselect-all\fR
\fBtoggle\fR
\fBtoggle-all\fR
\fBtoggle-down\fR \fIctrl-i (tab)\fR
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle-up\fR : \fBtoggle-down\fR)
\fBtoggle-out\fR (\fB--reverse\fR ? \fBtoggle-down\fR : \fBtoggle-up\fR)
\fBtoggle-sort\fR (equivalent to \fB--toggle-sort\fR)
\fBtoggle-up\fR \fIbtab (shift-tab)\fR
\fBunix-line-discard\fR \fIctrl-u\fR
\fBunix-word-rubout\fR \fIctrl-w\fR
\fBup\fR \fIctrl-k ctrl-p up\fR
\fByank\fR \fIctrl-y\fR
.RE
.RS
With \fBexecute(...)\fR action, you can execute arbitrary commands without
leaving fzf. For example, you can turn fzf into a simple file browser by
binding \fBenter\fR key to \fBless\fR command like follows.
.RS
\fBfzf --bind "enter:execute(less {})"\fR
.RE
\fB{}\fR is the placeholder for the double-quoted string of the current line.
If the command contains parentheses, you can use any of the following
alternative notations to avoid parse errors.
\fBexecute[...]\fR
\fBexecute~...~\fR
\fBexecute!...!\fR
\fBexecute@...@\fR
\fBexecute#...#\fR
\fBexecute$...$\fR
\fBexecute%...%\fR
\fBexecute^...^\fR
\fBexecute&...&\fR
\fBexecute*...*\fR
\fBexecute;...;\fR
\fBexecute/.../\fR
\fBexecute|...|\fR
\fBexecute:...\fR
.RS
This is the special form that frees you from parse errors as it does not expect
the closing character. The catch is that it should be the last one in the
comma-separated list.
.RE
\fBexecute-multi(...)\fR is an alternative action that executes the command
with the selected entries when multi-select is enabled (\fB--multi\fR). With
this action, \fB{}\fR is replaced with the double-quoted strings of the
selected entries separated by spaces.
.RE
.SS History
.TP
.BI "--history=" "HISTORY_FILE"
Load search history from the specified file and update the file on completion.
@@ -321,17 +218,34 @@ When enabled, \fBCTRL-N\fR and \fBCTRL-P\fR are automatically remapped to
.BI "--history-size=" "N"
Maximum number of entries in the history file (default: 1000). The file is
automatically truncated when the number of the lines exceeds the value.
.SS Preview
.TP
.BI "--header=" "STR"
The given string will be printed as the sticky header. The lines are displayed
in the given order from top to bottom regardless of \fB--reverse\fR option, and
are not affected by \fB--with-nth\fR. ANSI color codes are processed even when
\fB--ansi\fR is not set.
.BI "--preview=" "COMMAND"
Execute the given command for the current line and display the result on the
preview window. \fB{}\fR is the placeholder for the quoted string of the
current line.
.RS
e.g. \fBfzf --preview="head -$LINES {}"\fR
.RE
.TP
.BI "--header-lines=" "N"
The first N lines of the input are treated as the sticky header. When
\fB--with-nth\fR is set, the lines are transformed just like the other
lines that follow.
.BI "--preview-window=" "[POSITION][:SIZE[%]][:hidden]"
Determine the layout of the preview window. If the argument ends with
\fB:hidden\fR, the preview window will be hidden by default until
\fBtoggle-preview\fR action is triggered.
.RS
.B POSITION: (default: right)
\fBup
\fBdown
\fBleft
\fBright
.RE
.RS
e.g. \fBfzf --preview="head {}" --preview-window=up:30%\fR
\fBfzf --preview="file {}" --preview-window=down:1\fR
.RE
.SS Scripting
.TP
.BI "-q, --query=" "STR"
@@ -356,6 +270,7 @@ the default enter key. When this option is set, fzf will print the name of the
key pressed as the first line of its output (or as the second line if
\fB--print-query\fR is also used). The line will be empty if fzf is completed
with the default enter key.
.RS
e.g. \fBfzf --expect=ctrl-v,ctrl-t,alt-s,f1,f2,~,@\fR
.RE
@@ -363,11 +278,12 @@ e.g. \fBfzf --expect=ctrl-v,ctrl-t,alt-s,f1,f2,~,@\fR
.B "--sync"
Synchronous search for multi-staged filtering. If specified, fzf will launch
ncurses finder only after the input stream is complete.
.RS
e.g. \fBfzf --multi | fzf --sync\fR
.RE
.SH ENVIRONMENT
.SH ENVIRONMENT VARIABLES
.TP
.B FZF_DEFAULT_COMMAND
Default command to use when input is tty
@@ -421,11 +337,11 @@ occurrences of the string.
.SS Anchored-match
A term can be prefixed by \fB^\fR, or suffixed by \fB$\fR to become an
anchored-match term. Then fzf will search for the items that start with or end
anchored-match term. Then fzf will search for the lines that start with or end
with the given string. An anchored-match term is also an exact-match term.
.SS Negation
If a term is prefixed by \fB!\fR, fzf will exclude the items that satisfy the
If a term is prefixed by \fB!\fR, fzf will exclude the lines that satisfy the
term from the result.
.SS Exact-match by default
@@ -440,6 +356,119 @@ query matches entries that start with \fBcore\fR and end with either \fBgo\fR,
e.g. \fB^core go$ | rb$ | py$\fR
.SH KEY BINDINGS
You can customize key bindings of fzf with \fB--bind\fR option which takes
a comma-separated list of key binding expressions. Each key binding expression
follows the following format: \fBKEY:ACTION\fR
e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
.B AVAILABLE KEYS: (SYNONYMS)
\fIctrl-[a-z]\fR
\fIalt-[a-z]\fR
\fIf[1-10]\fR
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
\fIspace\fR
\fIbspace\fR (\fIbs\fR)
\fIalt-enter\fR
\fIalt-space\fR
\fIalt-bspace\fR (\fIalt-bs\fR)
\fIalt-/\fR
\fItab\fR
\fIbtab\fR (\fIshift-tab\fR)
\fIesc\fR
\fIdel\fR
\fIup\fR
\fIdown\fR
\fIleft\fR
\fIright\fR
\fIhome\fR
\fIend\fR
\fIpgup\fR (\fIpage-up\fR)
\fIpgdn\fR (\fIpage-down\fR)
\fIshift-left\fR
\fIshift-right\fR
\fIdouble-click\fR
or any single character
\fBACTION: DEFAULT BINDINGS (NOTES):
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
\fBaccept\fR \fIenter double-click\fR
\fBbackward-char\fR \fIctrl-b left\fR
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
\fBbackward-kill-word\fR \fIalt-bs\fR
\fBbackward-word\fR \fIalt-b shift-left\fR
\fBbeginning-of-line\fR \fIctrl-a home\fR
\fBcancel\fR
\fBclear-screen\fR \fIctrl-l\fR
\fBdelete-char\fR \fIdel\fR
\fBdelete-char/eof\fR \fIctrl-d\fR
\fBdeselect-all\fR
\fBdown\fR \fIctrl-j ctrl-n down\fR
\fBend-of-line\fR \fIctrl-e end\fR
\fBexecute(...)\fR (see below for the details)
\fBexecute-multi(...)\fR (see below for the details)
\fBforward-char\fR \fIctrl-f right\fR
\fBforward-word\fR \fIalt-f shift-right\fR
\fBignore\fR
\fBjump\fR (EasyMotion-like 2-keystroke movement)
\fBjump-accept\fR (jump and accept)
\fBkill-line\fR
\fBkill-word\fR \fIalt-d\fR
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
\fBpage-down\fR \fIpgdn\fR
\fBpage-up\fR \fIpgup\fR
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
\fBprint-query\fR (print query and exit)
\fBselect-all\fR
\fBtoggle\fR
\fBtoggle-all\fR
\fBtoggle-down\fR \fIctrl-i (tab)\fR
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle-up\fR : \fBtoggle-down\fR)
\fBtoggle-out\fR (\fB--reverse\fR ? \fBtoggle-down\fR : \fBtoggle-up\fR)
\fBtoggle-preview\fR
\fBtoggle-sort\fR (equivalent to \fB--toggle-sort\fR)
\fBtoggle-up\fR \fIbtab (shift-tab)\fR
\fBunix-line-discard\fR \fIctrl-u\fR
\fBunix-word-rubout\fR \fIctrl-w\fR
\fBup\fR \fIctrl-k ctrl-p up\fR
\fByank\fR \fIctrl-y\fR
With \fBexecute(...)\fR action, you can execute arbitrary commands without
leaving fzf. For example, you can turn fzf into a simple file browser by
binding \fBenter\fR key to \fBless\fR command like follows.
\fBfzf --bind "enter:execute(less {})"\fR
\fB{}\fR is the placeholder for the quoted string of the current line.
If the command contains parentheses, you can use any of the following
alternative notations to avoid parse errors.
\fBexecute[...]\fR
\fBexecute~...~\fR
\fBexecute!...!\fR
\fBexecute@...@\fR
\fBexecute#...#\fR
\fBexecute$...$\fR
\fBexecute%...%\fR
\fBexecute^...^\fR
\fBexecute&...&\fR
\fBexecute*...*\fR
\fBexecute;...;\fR
\fBexecute/.../\fR
\fBexecute|...|\fR
\fBexecute:...\fR
.RS
This is the special form that frees you from parse errors as it does not expect
the closing character. The catch is that it should be the last one in the
comma-separated list.
.RE
\fBexecute-multi(...)\fR is an alternative action that executes the command
with the selected entries when multi-select is enabled (\fB--multi\fR). With
this action, \fB{}\fR is replaced with the quoted strings of the selected
entries separated by spaces.
.SH AUTHOR
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)

View File

@@ -49,7 +49,7 @@ function! s:fzf_exec()
throw 'fzf executable not found'
endif
endif
return s:exec
return s:shellesc(s:exec)
endfunction
function! s:tmux_enabled()
@@ -122,7 +122,9 @@ try
endtry
if !has_key(dict, 'source') && !empty($FZF_DEFAULT_COMMAND)
let dict.source = $FZF_DEFAULT_COMMAND
let temps.source = tempname()
call writefile(split($FZF_DEFAULT_COMMAND, "\n"), temps.source)
let dict.source = (empty($SHELL) ? 'sh' : $SHELL) . ' ' . s:shellesc(temps.source)
endif
if has_key(dict, 'source')
@@ -140,16 +142,16 @@ try
else
let prefix = ''
endif
let tmux = !has('nvim') && s:tmux_enabled() && s:splittable(dict)
let tmux = (!has('nvim') || get(g:, 'fzf_prefer_tmux', 0)) && s:tmux_enabled() && s:splittable(dict)
let command = prefix.(tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
if has('nvim')
if has('nvim') && !tmux
return s:execute_term(dict, command, temps)
endif
let ret = tmux ? s:execute_tmux(dict, command, temps) : s:execute(dict, command, temps)
call s:popd(dict, ret)
return ret
let lines = tmux ? s:execute_tmux(dict, command, temps) : s:execute(dict, command, temps)
call s:callback(dict, lines)
return lines
finally
let &shell = oshell
endtry
@@ -179,7 +181,7 @@ function! s:fzf_tmux(dict)
endif
endfor
return printf('LINES=%d COLUMNS=%d %s %s %s --',
\ &lines, &columns, s:fzf_tmux, size, (has_key(a:dict, 'source') ? '' : '-'))
\ &lines, &columns, s:shellesc(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-'))
endfunction
function! s:splittable(dict)
@@ -193,29 +195,24 @@ function! s:pushd(dict)
return 1
endif
let a:dict.prev_dir = cwd
execute 'chdir' s:escape(a:dict.dir)
execute 'lcd' s:escape(a:dict.dir)
let a:dict.dir = getcwd()
return 1
endif
return 0
endfunction
function! s:popd(dict, lines)
" Since anything can be done in the sink function, there is no telling that
" the change of the working directory was made by &autochdir setting.
"
" We use the following heuristic to determine whether to restore CWD:
" - Always restore the current directory when &autochdir is disabled.
" FIXME This makes it impossible to change directory from inside the sink
" function when &autochdir is not used.
" - In case of an error or an interrupt, a:lines will be empty.
" And it will be an array of a single empty string when fzf was finished
" without a match. In these cases, we presume that the change of the
" directory is not expected and should be undone.
if has_key(a:dict, 'prev_dir') &&
\ (!&autochdir || (empty(a:lines) || len(a:lines) == 1 && empty(a:lines[0])))
execute 'chdir' s:escape(remove(a:dict, 'prev_dir'))
augroup fzf_popd
autocmd!
autocmd WinEnter * call s:dopopd()
augroup END
function! s:dopopd()
if !exists('w:fzf_prev_dir') || exists('*haslocaldir') && !haslocaldir()
return
endif
execute 'lcd' s:escape(w:fzf_prev_dir)
unlet w:fzf_prev_dir
endfunction
function! s:xterm_launcher()
@@ -256,7 +253,7 @@ function! s:execute(dict, command, temps) abort
endif
execute 'silent !'.command
redraw!
return s:exit_handler(v:shell_error, command) ? s:callback(a:dict, a:temps) : []
return s:exit_handler(v:shell_error, command) ? s:collect(a:temps) : []
endfunction
function! s:execute_tmux(dict, command, temps) abort
@@ -268,7 +265,7 @@ function! s:execute_tmux(dict, command, temps) abort
call system(command)
redraw!
return s:exit_handler(v:shell_error, command) ? s:callback(a:dict, a:temps) : []
return s:exit_handler(v:shell_error, command) ? s:collect(a:temps) : []
endfunction
function! s:calc_size(max, val, dict)
@@ -285,6 +282,7 @@ function! s:calc_size(max, val, dict)
let opts = get(a:dict, 'options', '').$FZF_DEFAULT_OPTS
let margin = stridx(opts, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2
let margin += stridx(opts, '--header') > stridx(opts, '--no-header')
return srcsz >= 0 ? min([srcsz + margin, size]) : size
endfunction
@@ -361,31 +359,58 @@ function! s:execute_term(dict, command, temps) abort
endif
call s:pushd(self.dict)
let ret = []
try
let ret = s:callback(self.dict, self.temps)
call self.switch_back(s:getpos() == self.ppos)
finally
call s:popd(self.dict, ret)
endtry
let lines = s:collect(self.temps)
call s:callback(self.dict, lines)
call self.switch_back(s:getpos() == self.ppos)
endfunction
call s:pushd(a:dict)
call termopen(a:command, fzf)
call s:popd(a:dict, [])
try
if s:present(a:dict, 'dir')
execute 'lcd' s:escape(a:dict.dir)
endif
call termopen(a:command, fzf)
finally
if s:present(a:dict, 'dir')
lcd -
endif
endtry
setlocal nospell bufhidden=wipe nobuflisted
setf fzf
startinsert
return []
endfunction
function! s:callback(dict, temps) abort
let lines = []
try
if filereadable(a:temps.result)
let lines = readfile(a:temps.result)
function! s:collect(temps) abort
try
return filereadable(a:temps.result) ? readfile(a:temps.result) : []
finally
for tf in values(a:temps)
silent! call delete(tf)
endfor
endtry
endfunction
function! s:callback(dict, lines) abort
" Since anything can be done in the sink function, there is no telling that
" the change of the working directory was made by &autochdir setting.
"
" We use the following heuristic to determine whether to restore CWD:
" - Always restore the current directory when &autochdir is disabled.
" FIXME This makes it impossible to change directory from inside the sink
" function when &autochdir is not used.
" - In case of an error or an interrupt, a:lines will be empty.
" And it will be an array of a single empty string when fzf was finished
" without a match. In these cases, we presume that the change of the
" directory is not expected and should be undone.
let popd = has_key(a:dict, 'prev_dir') &&
\ (!&autochdir || (empty(a:lines) || len(a:lines) == 1 && empty(a:lines[0])))
if popd
let w:fzf_prev_dir = a:dict.prev_dir
endif
try
if has_key(a:dict, 'sink')
for line in lines
for line in a:lines
if type(a:dict.sink) == 2
call a:dict.sink(line)
else
@@ -394,20 +419,19 @@ try
endfor
endif
if has_key(a:dict, 'sink*')
call a:dict['sink*'](lines)
call a:dict['sink*'](a:lines)
endif
endif
catch
if stridx(v:exception, ':E325:') < 0
echoerr v:exception
endif
endtry
for tf in values(a:temps)
silent! call delete(tf)
endfor
catch
if stridx(v:exception, ':E325:') < 0
echoerr v:exception
" We may have opened a new window or tab
if popd
let w:fzf_prev_dir = a:dict.prev_dir
call s:dopopd()
endif
finally
return lines
endtry
endfunction
let s:default_action = {

View File

@@ -14,7 +14,7 @@
if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() {
echo "$1"
\find -L "$1" \
command 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@^\./@@'
}
@@ -22,7 +22,7 @@ fi
if ! declare -f _fzf_compgen_dir > /dev/null; then
_fzf_compgen_dir() {
\find -L "$1" \
command find -L "$1" \
-name .git -prune -o -name .svn -prune -o -type d \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
}
@@ -108,7 +108,7 @@ _fzf_handle_dynamic_completion() {
elif [ -n "$_fzf_completion_loader" ]; then
_completion_loader "$@"
ret=$?
eval "$(complete | \grep "\-F.* $orig_cmd$" | _fzf_orig_completion_filter)"
eval "$(complete | command grep "\-F.* $orig_cmd$" | _fzf_orig_completion_filter)"
source "${BASH_SOURCE[0]}"
return $ret
fi
@@ -213,15 +213,16 @@ _fzf_complete_kill() {
_fzf_complete_telnet() {
_fzf_complete '+m' "$@" < <(
\grep -v '^\s*\(#\|$\)' /etc/hosts | \grep -Fv '0.0.0.0' |
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
}
_fzf_complete_ssh() {
_fzf_complete '+m' "$@" < <(
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | \grep -i '^host' | \grep -v '*') \
<(\grep -v '^\s*\(#\|$\)' /etc/hosts | \grep -Fv '0.0.0.0') |
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*') \
<(command grep -oE '^[^ ]+' ~/.ssh/known_hosts | tr ',' '\n' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
}
@@ -262,8 +263,8 @@ x_cmds="kill ssh telnet unset unalias export"
# Preserve existing completion
if [ "$_fzf_completion_loaded" != '0.11.3' ]; then
# Really wish I could use associative array but OSX comes with bash 3.2 :(
eval $(complete | \grep '\-F' | \grep -v _fzf_ |
\grep -E " ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g' | sed 's/+/\\+/g'))$" | _fzf_orig_completion_filter)
eval $(complete | command grep '\-F' | command grep -v _fzf_ |
command grep -E " ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g' | sed 's/+/\\+/g'))$" | _fzf_orig_completion_filter)
export _fzf_completion_loaded=0.11.3
fi

View File

@@ -14,7 +14,7 @@
if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() {
echo "$1"
\find -L "$1" \
command 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@^\./@@'
}
@@ -22,7 +22,7 @@ fi
if ! declare -f _fzf_compgen_dir > /dev/null; then
_fzf_compgen_dir() {
\find -L "$1" \
command find -L "$1" \
-name .git -prune -o -name .svn -prune -o -type d \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
}
@@ -31,7 +31,7 @@ fi
###########################################################
__fzf_generic_path_completion() {
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches nnm
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches
# (Q) flag removes a quoting level: "foo\ bar" => "foo bar"
base=${(Q)1}
lbuf=$2
@@ -41,10 +41,7 @@ __fzf_generic_path_completion() {
tail=$6
[ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
if ! setopt | \grep nonomatch > /dev/null; then
nnm=1
setopt nonomatch
fi
setopt localoptions nonomatch
dir="$base"
while [ 1 ]; do
if [ -z "$dir" -o -d ${~dir} ]; then
@@ -61,12 +58,12 @@ __fzf_generic_path_completion() {
LBUFFER="$lbuf$matches$tail"
fi
zle redisplay
typeset -f zle-line-init >/dev/null && zle zle-line-init
break
fi
dir=$(dirname "$dir")
dir=${dir%/}/
done
[ -n "$nnm" ] && unsetopt nonomatch
}
_fzf_path_completion() {
@@ -80,7 +77,7 @@ _fzf_dir_completion() {
}
_fzf_feed_fifo() (
rm -f "$1"
command rm -f "$1"
mkfifo "$1"
cat <&0 > "$1" &
)
@@ -101,20 +98,22 @@ _fzf_complete() {
LBUFFER="$lbuf$matches"
fi
zle redisplay
rm -f "$fifo"
typeset -f zle-line-init >/dev/null && zle zle-line-init
command rm -f "$fifo"
}
_fzf_complete_telnet() {
_fzf_complete '+m' "$@" < <(
\grep -v '^\s*\(#\|$\)' /etc/hosts | \grep -Fv '0.0.0.0' |
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
}
_fzf_complete_ssh() {
_fzf_complete '+m' "$@" < <(
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | \grep -i '^host' | \grep -v '*') \
<(\grep -v '^\s*\(#\|$\)' /etc/hosts | \grep -Fv '0.0.0.0') |
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*') \
<(command grep -oE '^[^ ]+' ~/.ssh/known_hosts | tr ',' '\n' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
}
@@ -139,7 +138,7 @@ _fzf_complete_unalias() {
fzf-completion() {
local tokens cmd prefix trigger tail fzf matches lbuf d_cmds
setopt localoptions noshwordsplit
setopt localoptions noshwordsplit noksh_arrays
# http://zsh.sourceforge.net/FAQ/zshfaq03.html
# http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags
@@ -164,6 +163,7 @@ fzf-completion() {
LBUFFER="$LBUFFER$matches"
fi
zle redisplay
typeset -f zle-line-init >/dev/null && zle zle-line-init
# Trigger sequence given
elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
@@ -184,8 +184,11 @@ fzf-completion() {
fi
}
[ -z "$fzf_default_completion" ] &&
fzf_default_completion=$(bindkey '^I' | \grep -v undefined-key | awk '{print $2}')
[ -z "$fzf_default_completion" ] && {
binding=$(bindkey '^I')
[[ $binding =~ 'undefined-key' ]] || fzf_default_completion=$binding[(w)2]
unset binding
}
zle -N fzf-completion
bindkey '^I' fzf-completion

View File

@@ -5,7 +5,7 @@ __fzf_select__() {
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"}"
eval "$cmd" | fzf -m | while read -r item; do
eval "$cmd | fzf -m $FZF_CTRL_T_OPTS" | while read -r item; do
printf '%q ' "$item"
done
echo
@@ -26,7 +26,7 @@ __fzf_select_tmux__() {
height="-l $height"
fi
tmux split-window $height "cd $(printf %q "$PWD"); FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS") PATH=$(printf %q "$PATH") FZF_CTRL_T_COMMAND=$(printf %q "$FZF_CTRL_T_COMMAND") bash -c 'source \"${BASH_SOURCE[0]}\"; tmux send-keys -t $TMUX_PANE \"\$(__fzf_select__)\"'"
tmux split-window $height "cd $(printf %q "$PWD"); FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS") PATH=$(printf %q "$PATH") FZF_CTRL_T_COMMAND=$(printf %q "$FZF_CTRL_T_COMMAND") FZF_CTRL_T_OPTS=$(printf %q "$FZF_CTRL_T_OPTS") bash -c 'source \"${BASH_SOURCE[0]}\"; RESULT=\"\$(__fzf_select__)\"; tmux setb -b fzf \"\$RESULT\" \\; pasteb -b fzf -t $TMUX_PANE \\; deleteb -b fzf || tmux send-keys -t $TMUX_PANE \"\$RESULT\"'"
}
fzf-file-widget() {
@@ -43,7 +43,7 @@ __fzf_cd__() {
local cmd dir
cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"}"
dir=$(eval "$cmd" | $(__fzfcmd) +m) && printf 'cd %q' "$dir"
dir=$(eval "$cmd | $(__fzfcmd) +m $FZF_ALT_C_OPTS") && printf 'cd %q' "$dir"
}
__fzf_history__() (
@@ -51,8 +51,8 @@ __fzf_history__() (
shopt -u nocaseglob nocasematch
line=$(
HISTTIMEFORMAT= history |
$(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS |
\grep '^ *[0-9]') &&
eval "$(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS" |
command grep '^ *[0-9]') &&
if [[ $- =~ H ]]; then
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
else
@@ -67,7 +67,7 @@ __fzf_use_tmux__() {
[ $BASH_VERSINFO -gt 3 ] && __use_bind_x=1 || __use_bind_x=0
__fzf_use_tmux__ && __use_tmux=1 || __use_tmux=0
if [[ $'\n'$(set -o) != *$'\n'vi*on* ]]; then
if [[ ! -o vi ]]; then
# Required to refresh the prompt after fzf
bind '"\er": redraw-current-line'
bind '"\e^": history-expand-line'
@@ -82,10 +82,10 @@ if [[ $'\n'$(set -o) != *$'\n'vi*on* ]]; then
fi
# CTRL-R - Paste the selected command from history into the command line
bind '"\C-r": " \C-e\C-u$(__fzf_history__)\e\C-e\e^\er"'
bind '"\C-r": " \C-e\C-u`__fzf_history__`\e\C-e\e^\er"'
# ALT-C - cd into the selected directory
bind '"\ec": " \C-e\C-u$(__fzf_cd__)\e\C-e\er\C-m"'
bind '"\ec": " \C-e\C-u`__fzf_cd__`\e\C-e\er\C-m"'
else
# We'd usually use "\e" to enter vi-movement-mode so we can do our magic,
# but this incurs a very noticeable delay of a half second or so,

View File

@@ -19,7 +19,7 @@ function fzf_key_bindings
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)" -m > $TMPDIR/fzf.result"
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)" -m $FZF_CTRL_T_OPTS > $TMPDIR/fzf.result"
and for i in (seq 20); commandline -i (cat $TMPDIR/fzf.result | __fzf_escape) 2> /dev/null; and break; sleep 0.1; end
commandline -f repaint
rm -f $TMPDIR/fzf.result
@@ -37,7 +37,7 @@ function fzf_key_bindings
command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"
# Fish hangs if the command before pipe redirects (2> /dev/null)
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)" +m > $TMPDIR/fzf.result"
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)" +m $FZF_ALT_C_OPTS > $TMPDIR/fzf.result"
[ (cat $TMPDIR/fzf.result | wc -l) -gt 0 ]
and cd (cat $TMPDIR/fzf.result)
commandline -f repaint

View File

@@ -8,10 +8,13 @@ __fsel() {
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"}"
eval "$cmd" | $(__fzfcmd) -m | while read item; do
setopt localoptions pipefail 2> /dev/null
eval "$cmd | $(__fzfcmd) -m $FZF_CTRL_T_OPTS" | while read item; do
echo -n "${(q)item} "
done
local ret=$?
echo
return $ret
}
__fzfcmd() {
@@ -20,7 +23,10 @@ __fzfcmd() {
fzf-file-widget() {
LBUFFER="${LBUFFER}$(__fsel)"
local ret=$?
zle redisplay
typeset -f zle-line-init >/dev/null && zle zle-line-init
return $ret
}
zle -N fzf-file-widget
bindkey '^T' fzf-file-widget
@@ -29,8 +35,12 @@ bindkey '^T' fzf-file-widget
fzf-cd-widget() {
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"}"
cd "${$(eval "$cmd" | $(__fzfcmd) +m):-.}"
setopt localoptions pipefail 2> /dev/null
cd "${$(eval "$cmd | $(__fzfcmd) +m $FZF_ALT_C_OPTS"):-.}"
local ret=$?
zle reset-prompt
typeset -f zle-line-init >/dev/null && zle zle-line-init
return $ret
}
zle -N fzf-cd-widget
bindkey '\ec' fzf-cd-widget
@@ -38,7 +48,9 @@ bindkey '\ec' fzf-cd-widget
# CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() {
local selected num
selected=( $(fc -l 1 | $(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r ${=FZF_CTRL_R_OPTS} -q "${LBUFFER//$/\\$}") )
setopt localoptions noglobsubst pipefail 2> /dev/null
selected=( $(fc -l 1 | eval "$(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS -q ${(q)LBUFFER}") )
local ret=$?
if [ -n "$selected" ]; then
num=$selected[1]
if [ -n "$num" ]; then
@@ -46,6 +58,8 @@ fzf-history-widget() {
fi
fi
zle redisplay
typeset -f zle-line-init >/dev/null && zle zle-line-init
return $ret
}
zle -N fzf-history-widget
bindkey '^R' fzf-history-widget

View File

@@ -36,7 +36,7 @@ func init() {
ansiRegex = regexp.MustCompile("\x1b\\[[0-9;]*[mK]")
}
func extractColor(str string, state *ansiState) (string, []ansiOffset, *ansiState) {
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, []ansiOffset, *ansiState) {
var offsets []ansiOffset
var output bytes.Buffer
@@ -46,7 +46,11 @@ func extractColor(str string, state *ansiState) (string, []ansiOffset, *ansiStat
idx := 0
for _, offset := range ansiRegex.FindAllStringIndex(str, -1) {
output.WriteString(str[idx:offset[0]])
prev := str[idx:offset[0]]
output.WriteString(prev)
if proc != nil && !proc(prev, state) {
return "", nil, nil
}
newState := interpretCode(str[offset[0]:offset[1]], state)
if !newState.equals(state) {
@@ -77,6 +81,9 @@ func extractColor(str string, state *ansiState) (string, []ansiOffset, *ansiStat
(&offsets[len(offsets)-1]).offset[1] = int32(utf8.RuneCount(output.Bytes()))
}
}
if proc != nil {
proc(rest, state)
}
return output.String(), offsets, state
}

View File

@@ -17,7 +17,7 @@ func TestExtractColor(t *testing.T) {
var state *ansiState
clean := "\x1b[0m"
check := func(assertion func(ansiOffsets []ansiOffset, state *ansiState)) {
output, ansiOffsets, newState := extractColor(src, state)
output, ansiOffsets, newState := extractColor(src, state, nil)
state = newState
if output != "hello world" {
t.Errorf("Invalid output: {}", output)

View File

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

View File

@@ -73,7 +73,7 @@ func Run(opts *Options) {
if opts.Theme != nil {
var state *ansiState
ansiProcessor = func(data []byte) ([]rune, []ansiOffset) {
trimmed, offsets, newState := extractColor(string(data), state)
trimmed, offsets, newState := extractColor(string(data), state, nil)
state = newState
return []rune(trimmed), offsets
}
@@ -81,7 +81,7 @@ func Run(opts *Options) {
// When color is disabled but ansi option is given,
// we simply strip out ANSI codes from the input
ansiProcessor = func(data []byte) ([]rune, []ansiOffset) {
trimmed, _, _ := extractColor(string(data), nil)
trimmed, _, _ := extractColor(string(data), nil, nil)
return []rune(trimmed), nil
}
}

View File

@@ -113,7 +113,8 @@ const (
ColCursor
ColSelected
ColHeader
ColUser
ColBorder
ColUser // Should be the last entry
)
const (
@@ -136,6 +137,7 @@ type ColorTheme struct {
Cursor int16
Selected int16
Header int16
Border int16
}
type Event struct {
@@ -170,6 +172,31 @@ var (
DarkBG int
)
type Window struct {
win *C.WINDOW
Top int
Left int
Width int
Height int
}
func NewWindow(top int, left int, width int, height int, border bool) *Window {
win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
if border {
attr := _color(ColBorder, false)
C.wattron(win, attr)
C.box(win, 0, 0)
C.wattroff(win, attr)
}
return &Window{
win: win,
Top: top,
Left: left,
Width: width,
Height: height,
}
}
func EmptyTheme() *ColorTheme {
return &ColorTheme{
UseDefault: true,
@@ -184,7 +211,8 @@ func EmptyTheme() *ColorTheme {
Info: colUndefined,
Cursor: colUndefined,
Selected: colUndefined,
Header: colUndefined}
Header: colUndefined,
Border: colUndefined}
}
func init() {
@@ -204,7 +232,8 @@ func init() {
Info: C.COLOR_WHITE,
Cursor: C.COLOR_RED,
Selected: C.COLOR_MAGENTA,
Header: C.COLOR_CYAN}
Header: C.COLOR_CYAN,
Border: C.COLOR_BLACK}
Dark256 = &ColorTheme{
UseDefault: true,
Fg: 15,
@@ -218,7 +247,8 @@ func init() {
Info: 144,
Cursor: 161,
Selected: 168,
Header: 109}
Header: 109,
Border: 59}
Light256 = &ColorTheme{
UseDefault: true,
Fg: 15,
@@ -232,7 +262,8 @@ func init() {
Info: 101,
Cursor: 161,
Selected: 168,
Header: 31}
Header: 31,
Border: 145}
}
func attrColored(pair int, bold bool) C.int {
@@ -360,6 +391,7 @@ func initPairs(baseTheme *ColorTheme, theme *ColorTheme, black bool) {
C.init_pair(ColCursor, override(baseTheme.Cursor, theme.Cursor), darkBG)
C.init_pair(ColSelected, override(baseTheme.Selected, theme.Selected), darkBG)
C.init_pair(ColHeader, override(baseTheme.Header, theme.Header), bg)
C.init_pair(ColBorder, override(baseTheme.Border, theme.Border), bg)
}
func Close() {
@@ -415,7 +447,9 @@ func mouseSequence(sz *int) Event {
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
mod := _buf[3] >= 100
s := 1 - int(_buf[3]%2)*2
return Event{Mouse, 0, &MouseEvent{0, 0, s, false, false, mod}}
x := int(_buf[4] - 33)
y := int(_buf[5] - 33)
return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, mod}}
}
return Event{Invalid, 0, nil}
}
@@ -486,6 +520,11 @@ func escSequence(sz *int) Event {
return Event{F10, 0, nil}
}
}
// Bracketed paste mode \e[200~ / \e[201
if _buf[3] == 48 && (_buf[4] == 48 || _buf[4] == 49) && _buf[5] == 126 {
*sz = 6
return Event{Invalid, 0, nil}
}
return Event{Invalid, 0, nil} // INS
case 51:
return Event{Del, 0, nil}
@@ -583,17 +622,25 @@ func GetChar() Event {
return Event{Rune, r, nil}
}
func Move(y int, x int) {
C.move(C.int(y), C.int(x))
func (w *Window) Close() {
C.delwin(w.win)
}
func MoveAndClear(y int, x int) {
Move(y, x)
C.clrtoeol()
func (w *Window) Enclose(y int, x int) bool {
return bool(C.wenclose(w.win, C.int(y), C.int(x)))
}
func Print(text string) {
C.addstr(C.CString(strings.Map(func(r rune) rune {
func (w *Window) Move(y int, x int) {
C.wmove(w.win, C.int(y), C.int(x))
}
func (w *Window) MoveAndClear(y int, x int) {
w.Move(y, x)
C.wclrtoeol(w.win)
}
func (w *Window) Print(text string) {
C.waddstr(w.win, C.CString(strings.Map(func(r rune) rune {
if r < 32 {
return -1
}
@@ -601,11 +648,11 @@ func Print(text string) {
}, text)))
}
func CPrint(pair int, bold bool, text string) {
func (w *Window) CPrint(pair int, bold bool, text string) {
attr := _color(pair, bold)
C.attron(attr)
Print(text)
C.attroff(attr)
C.wattron(w.win, attr)
w.Print(text)
C.wattroff(w.win, attr)
}
func Clear() {
@@ -620,6 +667,30 @@ func Refresh() {
C.refresh()
}
func (w *Window) Erase() {
C.werase(w.win)
}
func (w *Window) Fill(str string) bool {
return C.waddstr(w.win, C.CString(str)) == C.OK
}
func (w *Window) CFill(str string, fg int, bg int, bold bool) bool {
attr := _color(PairFor(fg, bg), bold)
C.wattron(w.win, attr)
ret := w.Fill(str)
C.wattroff(w.win, attr)
return ret
}
func (w *Window) Refresh() {
C.wnoutrefresh(w.win)
}
func DoUpdate() {
C.doupdate()
}
func PairFor(fg int, bg int) int {
key := (fg << 8) + bg
if found, prs := _colorMap[key]; prs {

View File

@@ -128,7 +128,7 @@ func (item *Item) AsString(stripAnsi bool) string {
func (item *Item) StringPtr(stripAnsi bool) *string {
if item.origText != nil {
if stripAnsi {
trimmed, _, _ := extractColor(string(*item.origText), nil)
trimmed, _, _ := extractColor(string(*item.origText), nil, nil)
return &trimmed
}
orig := string(*item.origText)

View File

@@ -1,6 +1,7 @@
package fzf
import (
"fmt"
"os"
"regexp"
"strconv"
@@ -23,36 +24,47 @@ const usage = `usage: fzf [options]
-n, --nth=N[,..] Comma-separated list of field index expressions
for limiting search scope. Each can be a non-zero
integer or a range expression ([BEGIN]..[END]).
--with-nth=N[,..] Transform item using index expressions within finder
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
--with-nth=N[,..] Transform the presentation of each line using
field index expressions
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
+s, --no-sort Do not sort the result
--tac Reverse the order of the input
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
when the scores are tied;
[length|begin|end|index] (default: length)
when the scores are tied [length|begin|end|index]
(default: length)
Interface
-m, --multi Enable multi-select with tab/shift-tab
--ansi Enable processing of ANSI color codes
--no-mouse Disable mouse
--color=COLSPEC Base scheme (dark|light|16|bw) and/or custom colors
--black Use black background
--reverse Reverse orientation
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
--tabstop=SPACES Number of spaces for a tab character (default: 8)
--bind=KEYBINDS Custom key bindings. Refer to the man page.
--cycle Enable cyclic scroll
--no-hscroll Disable horizontal scroll
--hscroll-off=COL Number of screen columns to keep to the right of the
highlighted substring (default: 10)
--inline-info Display finder info inline with the query
--jump-labels=CHARS Label characters for jump and jump-accept
Layout
--reverse Reverse orientation
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
--inline-info Display finder info inline with the query
--prompt=STR Input prompt (default: '> ')
--bind=KEYBINDS Custom key bindings. Refer to the man page.
--history=FILE History file
--history-size=N Maximum number of history entries (default: 1000)
--header=STR String to print as header
--header-lines=N The first N lines of the input are treated as header
Display
--ansi Enable processing of ANSI color codes
--tabstop=SPACES Number of spaces for a tab character (default: 8)
--color=COLSPEC Base scheme (dark|light|16|bw) and/or custom colors
History
--history=FILE History file
--history-size=N Maximum number of history entries (default: 1000)
Preview
--preview=COMMAND Command to preview highlighted line ({})
--preview-window=OPT Preview window layout (default: right:50%)
[up|down|left|right][:SIZE[%]][:hidden]
Scripting
-q, --query=STR Start the finder with the given query
-1, --select-1 Automatically select the only match
@@ -88,8 +100,29 @@ const (
byEnd
)
func defaultMargin() [4]string {
return [4]string{"0", "0", "0", "0"}
type sizeSpec struct {
size float64
percent bool
}
func defaultMargin() [4]sizeSpec {
return [4]sizeSpec{}
}
type windowPosition int
const (
posUp windowPosition = iota
posDown
posLeft
posRight
)
type previewOpts struct {
command string
position windowPosition
size sizeSpec
hidden bool
}
// Options stores the values of command-line options
@@ -123,13 +156,14 @@ type Options struct {
Expect map[int]string
Keymap map[int]actionType
Execmap map[int]string
Preview previewOpts
PrintQuery bool
ReadZero bool
Sync bool
History *History
Header []string
HeaderLines int
Margin [4]string
Margin [4]sizeSpec
Tabstop int
Version bool
}
@@ -165,6 +199,7 @@ func defaultOptions() *Options {
Expect: make(map[int]string),
Keymap: make(map[int]actionType),
Execmap: make(map[int]string),
Preview: previewOpts{"", posRight, sizeSpec{50, true}, false},
PrintQuery: false,
ReadZero: false,
Sync: false,
@@ -458,6 +493,8 @@ func parseTheme(defaultTheme *curses.ColorTheme, str string) *curses.ColorTheme
theme.Match = ansi
case "hl+":
theme.CurrentMatch = ansi
case "border":
theme.Border = ansi
case "prompt":
theme.Prompt = ansi
case "spinner":
@@ -604,6 +641,8 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, str string)
keymap[key] = actPreviousHistory
case "next-history":
keymap[key] = actNextHistory
case "toggle-preview":
keymap[key] = actTogglePreview
case "toggle-sort":
keymap[key] = actToggleSort
default:
@@ -659,40 +698,86 @@ func strLines(str string) []string {
return strings.Split(strings.TrimSuffix(str, "\n"), "\n")
}
func parseMargin(margin string) [4]string {
margins := strings.Split(margin, ",")
checked := func(str string) string {
if strings.HasSuffix(str, "%") {
val := atof(str[:len(str)-1])
if val < 0 {
errorExit("margin must be non-negative")
}
if val > 100 {
errorExit("margin too large")
}
} else {
val := atoi(str)
if val < 0 {
errorExit("margin must be non-negative")
}
func parseSize(str string, maxPercent float64, label string) sizeSpec {
var val float64
percent := strings.HasSuffix(str, "%")
if percent {
val = atof(str[:len(str)-1])
if val < 0 {
errorExit(label + " must be non-negative")
}
return str
if val > maxPercent {
errorExit(fmt.Sprintf("%s too large (max: %d%%)", label, int(maxPercent)))
}
} else {
if strings.Contains(str, ".") {
errorExit(label + " (without %) must be a non-negative integer")
}
val = float64(atoi(str))
if val < 0 {
errorExit(label + " must be non-negative")
}
}
return sizeSpec{val, percent}
}
func parsePreviewWindow(opts *previewOpts, input string) {
layout := input
if strings.HasSuffix(layout, ":hidden") {
opts.hidden = true
layout = strings.TrimSuffix(layout, ":hidden")
}
tokens := strings.Split(layout, ":")
if len(tokens) == 0 || len(tokens) > 2 {
errorExit("invalid window layout: " + input)
}
if len(tokens) > 1 {
opts.size = parseSize(tokens[1], 99, "window size")
} else {
opts.size = sizeSpec{50, true}
}
if !opts.size.percent && opts.size.size > 0 {
// Adjust size for border
opts.size.size += 2
}
switch tokens[0] {
case "up":
opts.position = posUp
case "down":
opts.position = posDown
case "left":
opts.position = posLeft
case "right":
opts.position = posRight
default:
errorExit("invalid window position: " + input)
}
}
func parseMargin(margin string) [4]sizeSpec {
margins := strings.Split(margin, ",")
checked := func(str string) sizeSpec {
return parseSize(str, 49, "margin")
}
switch len(margins) {
case 1:
m := checked(margins[0])
return [4]string{m, m, m, m}
return [4]sizeSpec{m, m, m, m}
case 2:
tb := checked(margins[0])
rl := checked(margins[1])
return [4]string{tb, rl, tb, rl}
return [4]sizeSpec{tb, rl, tb, rl}
case 3:
t := checked(margins[0])
rl := checked(margins[1])
b := checked(margins[2])
return [4]string{t, rl, b, rl}
return [4]sizeSpec{t, rl, b, rl}
case 4:
return [4]string{
return [4]sizeSpec{
checked(margins[0]), checked(margins[1]),
checked(margins[2]), checked(margins[3])}
default:
@@ -858,6 +943,13 @@ func parseOptions(opts *Options, allArgs []string) {
case "--header-lines":
opts.HeaderLines = atoi(
nextString(allArgs, &i, "number of header lines required"))
case "--preview":
opts.Preview.command = nextString(allArgs, &i, "preview command required")
case "--no-preview":
opts.Preview.command = ""
case "--preview-window":
parsePreviewWindow(&opts.Preview,
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]]"))
case "--no-margin":
opts.Margin = defaultMargin()
case "--margin":
@@ -900,6 +992,10 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Header = strLines(value)
} else if match, value := optString(arg, "--header-lines="); match {
opts.HeaderLines = atoi(value)
} else if match, value := optString(arg, "--preview="); match {
opts.Preview.command = value
} else if match, value := optString(arg, "--preview-window="); match {
parsePreviewWindow(&opts.Preview, value)
} else if match, value := optString(arg, "--margin="); match {
opts.Margin = parseMargin(value)
} else if match, value := optString(arg, "--tabstop="); match {

View File

@@ -7,7 +7,6 @@ import (
"os/signal"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"syscall"
@@ -53,8 +52,10 @@ type Terminal struct {
header []string
header0 []string
ansi bool
margin [4]string
marginInt [4]int
margin [4]sizeSpec
window *C.Window
bwindow *C.Window
pwindow *C.Window
count int
progress int
reading bool
@@ -63,6 +64,10 @@ type Terminal struct {
merger *Merger
selected map[int32]selectedItem
reqBox *util.EventBox
preview previewOpts
previewing bool
previewTxt string
previewBox *util.EventBox
eventBox *util.EventBox
mutex sync.Mutex
initFunc func()
@@ -77,6 +82,11 @@ type selectedItem struct {
type byTimeOrder []selectedItem
type previewRequest struct {
ok bool
str string
}
func (a byTimeOrder) Len() int {
return len(a)
}
@@ -103,6 +113,8 @@ const (
reqRedraw
reqClose
reqPrintQuery
reqPreviewEnqueue
reqPreviewDisplay
reqQuit
)
@@ -148,6 +160,7 @@ const (
actJumpAccept
actPrintQuery
actToggleSort
actTogglePreview
actPreviousHistory
actNextHistory
actExecute
@@ -220,6 +233,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
} else {
delay = initialDelay
}
var previewBox *util.EventBox
if len(opts.Preview.command) > 0 {
previewBox = util.NewEventBox()
}
return &Terminal{
initDelay: delay,
inlineInfo: opts.InlineInfo,
@@ -242,7 +259,6 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
printQuery: opts.PrintQuery,
history: opts.History,
margin: opts.Margin,
marginInt: [4]int{0, 0, 0, 0},
cycle: opts.Cycle,
header: header,
header0: header,
@@ -253,6 +269,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
merger: EmptyMerger,
selected: make(map[int32]selectedItem),
reqBox: util.NewEventBox(),
preview: opts.Preview,
previewing: previewBox != nil && !opts.Preview.hidden,
previewTxt: "",
previewBox: previewBox,
eventBox: eventBox,
mutex: sync.Mutex{},
suppress: true,
@@ -332,7 +352,7 @@ func (t *Terminal) output() bool {
if !found {
cnt := t.merger.Length()
if cnt > 0 && cnt > t.cy {
fmt.Println(t.merger.Get(t.cy).AsString(t.ansi))
fmt.Println(t.current())
found = true
}
} else {
@@ -372,56 +392,113 @@ func displayWidth(runes []rune) int {
return l
}
const minWidth = 16
const minHeight = 4
const (
minWidth = 16
minHeight = 4
)
func (t *Terminal) calculateMargins() {
func calculateSize(base int, size sizeSpec, margin int, minSize int) int {
max := base - margin
if size.percent {
return util.Constrain(int(float64(base)*0.01*size.size), minSize, max)
}
return util.Constrain(int(size.size), minSize, max)
}
func (t *Terminal) resizeWindows() {
screenWidth := C.MaxX()
screenHeight := C.MaxY()
for idx, str := range t.margin {
if str == "0" {
t.marginInt[idx] = 0
} else if strings.HasSuffix(str, "%") {
num, _ := strconv.ParseFloat(str[:len(str)-1], 64)
var val float64
marginInt := [4]int{}
for idx, sizeSpec := range t.margin {
if sizeSpec.percent {
var max float64
if idx%2 == 0 {
val = float64(screenHeight)
max = float64(screenHeight)
} else {
val = float64(screenWidth)
max = float64(screenWidth)
}
t.marginInt[idx] = int(val * num * 0.01)
marginInt[idx] = int(max * sizeSpec.size * 0.01)
} else {
num, _ := strconv.Atoi(str)
t.marginInt[idx] = num
marginInt[idx] = int(sizeSpec.size)
}
}
adjust := func(idx1 int, idx2 int, max int, min int) {
if max >= min {
margin := t.marginInt[idx1] + t.marginInt[idx2]
margin := marginInt[idx1] + marginInt[idx2]
if max-margin < min {
desired := max - min
t.marginInt[idx1] = desired * t.marginInt[idx1] / margin
t.marginInt[idx2] = desired * t.marginInt[idx2] / margin
marginInt[idx1] = desired * marginInt[idx1] / margin
marginInt[idx2] = desired * marginInt[idx2] / margin
}
}
}
adjust(1, 3, screenWidth, minWidth)
adjust(0, 2, screenHeight, minHeight)
minAreaWidth := minWidth
minAreaHeight := minHeight
if t.isPreviewEnabled() {
switch t.preview.position {
case posUp, posDown:
minAreaHeight *= 2
case posLeft, posRight:
minAreaWidth *= 2
}
}
adjust(1, 3, screenWidth, minAreaWidth)
adjust(0, 2, screenHeight, minAreaHeight)
if t.window != nil {
t.window.Close()
}
if t.bwindow != nil {
t.bwindow.Close()
t.pwindow.Close()
}
width := screenWidth - marginInt[1] - marginInt[3]
height := screenHeight - marginInt[0] - marginInt[2]
if t.isPreviewEnabled() {
createPreviewWindow := func(y int, x int, w int, h int) {
t.bwindow = C.NewWindow(y, x, w, h, true)
t.pwindow = C.NewWindow(y+1, x+2, w-4, h-2, false)
}
switch t.preview.position {
case posUp:
pheight := calculateSize(height, t.preview.size, minHeight, 3)
t.window = C.NewWindow(
marginInt[0]+pheight, marginInt[3], width, height-pheight, false)
createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
case posDown:
pheight := calculateSize(height, t.preview.size, minHeight, 3)
t.window = C.NewWindow(
marginInt[0], marginInt[3], width, height-pheight, false)
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
case posLeft:
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
t.window = C.NewWindow(
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false)
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
case posRight:
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
t.window = C.NewWindow(
marginInt[0], marginInt[3], width-pwidth, height, false)
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
}
} else {
t.window = C.NewWindow(
marginInt[0],
marginInt[3],
width,
height, false)
}
}
func (t *Terminal) move(y int, x int, clear bool) {
x += t.marginInt[3]
maxy := C.MaxY()
if !t.reverse {
y = maxy - y - 1 - t.marginInt[2]
} else {
y += t.marginInt[0]
y = t.window.Height - y - 1
}
if clear {
C.MoveAndClear(y, x)
t.window.MoveAndClear(y, x)
} else {
C.Move(y, x)
t.window.Move(y, x)
}
}
@@ -431,24 +508,24 @@ func (t *Terminal) placeCursor() {
func (t *Terminal) printPrompt() {
t.move(0, 0, true)
C.CPrint(C.ColPrompt, true, t.prompt)
C.CPrint(C.ColNormal, true, string(t.input))
t.window.CPrint(C.ColPrompt, true, t.prompt)
t.window.CPrint(C.ColNormal, true, string(t.input))
}
func (t *Terminal) printInfo() {
if t.inlineInfo {
t.move(0, displayWidth([]rune(t.prompt))+displayWidth(t.input)+1, true)
if t.reading {
C.CPrint(C.ColSpinner, true, " < ")
t.window.CPrint(C.ColSpinner, true, " < ")
} else {
C.CPrint(C.ColPrompt, true, " < ")
t.window.CPrint(C.ColPrompt, true, " < ")
}
} else {
t.move(1, 0, true)
if t.reading {
duration := int64(spinnerDuration)
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
C.CPrint(C.ColSpinner, true, _spinner[idx])
t.window.CPrint(C.ColSpinner, true, _spinner[idx])
}
t.move(1, 2, false)
}
@@ -467,18 +544,14 @@ func (t *Terminal) printInfo() {
if t.progress > 0 && t.progress < 100 {
output += fmt.Sprintf(" (%d%%)", t.progress)
}
C.CPrint(C.ColInfo, false, output)
}
func (t *Terminal) maxHeight() int {
return C.MaxY() - t.marginInt[0] - t.marginInt[2]
t.window.CPrint(C.ColInfo, false, output)
}
func (t *Terminal) printHeader() {
if len(t.header) == 0 {
return
}
max := t.maxHeight()
max := t.window.Height
var state *ansiState
for idx, lineStr := range t.header {
line := idx + 2
@@ -488,7 +561,7 @@ func (t *Terminal) printHeader() {
if line >= max {
continue
}
trimmed, colors, newState := extractColor(lineStr, state)
trimmed, colors, newState := extractColor(lineStr, state, nil)
state = newState
item := &Item{
text: []rune(trimmed),
@@ -529,19 +602,19 @@ func (t *Terminal) printItem(item *Item, i int, current bool) {
} else if current {
label = ">"
}
C.CPrint(C.ColCursor, true, label)
t.window.CPrint(C.ColCursor, true, label)
if current {
if selected {
C.CPrint(C.ColSelected, true, ">")
t.window.CPrint(C.ColSelected, true, ">")
} else {
C.CPrint(C.ColCurrent, true, " ")
t.window.CPrint(C.ColCurrent, true, " ")
}
t.printHighlighted(item, true, C.ColCurrent, C.ColCurrentMatch, true)
} else {
if selected {
C.CPrint(C.ColSelected, true, ">")
t.window.CPrint(C.ColSelected, true, ">")
} else {
C.Print(" ")
t.window.Print(" ")
}
t.printHighlighted(item, false, 0, C.ColMatch, false)
}
@@ -593,7 +666,7 @@ func (t *Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int, c
text := make([]rune, len(item.text))
copy(text, item.text)
offsets := item.colorOffsets(col2, bold, current)
maxWidth := C.MaxX() - 3 - t.marginInt[1] - t.marginInt[3]
maxWidth := t.window.Width - 3
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
fullWidth := displayWidth(text)
if fullWidth > maxWidth {
@@ -643,11 +716,11 @@ func (t *Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int, c
e := util.Constrain32(offset.offset[1], index, maxOffset)
substr, prefixWidth = processTabs(text[index:b], prefixWidth)
C.CPrint(col1, bold, substr)
t.window.CPrint(col1, bold, substr)
if b < e {
substr, prefixWidth = processTabs(text[b:e], prefixWidth)
C.CPrint(offset.color, offset.bold, substr)
t.window.CPrint(offset.color, offset.bold, substr)
}
index = e
@@ -657,10 +730,20 @@ func (t *Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int, c
}
if index < maxOffset {
substr, _ = processTabs(text[index:], prefixWidth)
C.CPrint(col1, bold, substr)
t.window.CPrint(col1, bold, substr)
}
}
func (t *Terminal) printPreview() {
t.pwindow.Erase()
extractColor(t.previewTxt, nil, func(str string, ansi *ansiState) bool {
if ansi != nil && ansi.colored() {
return t.pwindow.CFill(str, ansi.fg, ansi.bg, ansi.bold)
}
return t.pwindow.Fill(str)
})
}
func processTabs(runes []rune, prefixWidth int) (string, int) {
var strbuf bytes.Buffer
l := prefixWidth
@@ -677,16 +760,24 @@ func processTabs(runes []rune, prefixWidth int) (string, int) {
}
func (t *Terminal) printAll() {
t.calculateMargins()
t.resizeWindows()
t.printList()
t.printPrompt()
t.printInfo()
t.printHeader()
if t.isPreviewEnabled() {
t.printPreview()
}
}
func (t *Terminal) refresh() {
if !t.suppress {
C.Refresh()
if t.isPreviewEnabled() {
t.bwindow.Refresh()
t.pwindow.Refresh()
}
t.window.Refresh()
C.DoUpdate()
}
}
@@ -743,10 +834,10 @@ func keyMatch(key int, event C.Event) bool {
}
func quoteEntry(entry string) string {
return fmt.Sprintf("%q", entry)
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
}
func executeCommand(template string, replacement string) {
func (t *Terminal) executeCommand(template string, replacement string) {
command := strings.Replace(template, "{}", replacement, -1)
cmd := util.ExecCommand(command)
cmd.Stdin = os.Stdin
@@ -754,7 +845,19 @@ func executeCommand(template string, replacement string) {
cmd.Stderr = os.Stderr
C.Endwin()
cmd.Run()
C.Refresh()
t.refresh()
}
func (t *Terminal) hasPreviewWindow() bool {
return t.previewBox != nil
}
func (t *Terminal) isPreviewEnabled() bool {
return t.previewBox != nil && t.previewing
}
func (t *Terminal) current() string {
return t.merger.Get(t.cy).AsString(t.ansi)
}
// Loop is called to start Terminal I/O
@@ -779,10 +882,10 @@ func (t *Terminal) Loop() {
t.mutex.Lock()
t.initFunc()
t.calculateMargins()
t.resizeWindows()
t.printPrompt()
t.placeCursor()
C.Refresh()
t.refresh()
t.printInfo()
t.printHeader()
t.mutex.Unlock()
@@ -807,6 +910,31 @@ func (t *Terminal) Loop() {
}()
}
if t.hasPreviewWindow() {
go func() {
for {
request := previewRequest{false, ""}
t.previewBox.Wait(func(events *util.Events) {
for req, value := range *events {
switch req {
case reqPreviewEnqueue:
request = value.(previewRequest)
}
}
events.Clear()
})
if request.ok {
command := strings.Replace(t.preview.command, "{}", quoteEntry(request.str), -1)
cmd := util.ExecCommand(command)
out, _ := cmd.CombinedOutput()
t.reqBox.Set(reqPreviewDisplay, string(out))
} else {
t.reqBox.Set(reqPreviewDisplay, "")
}
}
}()
}
exit := func(code int) {
if code <= exitNoMatch && t.history != nil {
t.history.append(string(t.input))
@@ -815,11 +943,12 @@ func (t *Terminal) Loop() {
}
go func() {
focused := previewRequest{false, ""}
for {
t.reqBox.Wait(func(events *util.Events) {
defer events.Clear()
t.mutex.Lock()
for req := range *events {
for req, value := range *events {
switch req {
case reqPrompt:
t.printPrompt()
@@ -830,6 +959,19 @@ func (t *Terminal) Loop() {
t.printInfo()
case reqList:
t.printList()
cnt := t.merger.Length()
var currentFocus previewRequest
if cnt > 0 && cnt > t.cy {
currentFocus = previewRequest{true, t.current()}
} else {
currentFocus = previewRequest{false, ""}
}
if currentFocus != focused {
focused = currentFocus
if t.isPreviewEnabled() {
t.previewBox.Set(reqPreviewEnqueue, focused)
}
}
case reqJump:
if t.merger.Length() == 0 {
t.jumping = jumpDisabled
@@ -850,6 +992,9 @@ func (t *Terminal) Loop() {
exit(exitOk)
}
exit(exitNoMatch)
case reqPreviewDisplay:
t.previewTxt = value.(string)
t.printPreview()
case reqPrintQuery:
C.Close()
fmt.Println(string(t.input))
@@ -915,7 +1060,7 @@ func (t *Terminal) Loop() {
case actExecute:
if t.cy >= 0 && t.cy < t.merger.Length() {
item := t.merger.Get(t.cy)
executeCommand(t.execmap[mapkey], quoteEntry(item.AsString(t.ansi)))
t.executeCommand(t.execmap[mapkey], quoteEntry(item.AsString(t.ansi)))
}
case actExecuteMulti:
if len(t.selected) > 0 {
@@ -923,13 +1068,23 @@ func (t *Terminal) Loop() {
for i, sel := range t.sortSelected() {
sels[i] = quoteEntry(*sel.text)
}
executeCommand(t.execmap[mapkey], strings.Join(sels, " "))
t.executeCommand(t.execmap[mapkey], strings.Join(sels, " "))
} else {
return doAction(actExecute, mapkey)
}
case actInvalid:
t.mutex.Unlock()
return false
case actTogglePreview:
if t.hasPreviewWindow() {
t.previewing = !t.previewing
t.resizeWindows()
cnt := t.merger.Length()
if t.previewing && cnt > 0 && cnt > t.cy {
t.previewBox.Set(reqPreviewEnqueue, previewRequest{true, t.current()})
}
req(reqList, reqInfo)
}
case actToggleSort:
t.sort = !t.sort
t.eventBox.Set(EvtSearchNew, t.sort)
@@ -1097,20 +1252,19 @@ func (t *Terminal) Loop() {
mx, my := me.X, me.Y
if me.S != 0 {
// Scroll
if t.merger.Length() > 0 {
if t.window.Enclose(my, mx) && t.merger.Length() > 0 {
if t.multi && me.Mod {
toggle()
}
t.vmove(me.S)
req(reqList)
}
} else if mx >= t.marginInt[3] && mx < C.MaxX()-t.marginInt[1] &&
my >= t.marginInt[0] && my < C.MaxY()-t.marginInt[2] {
mx -= t.marginInt[3]
my -= t.marginInt[0]
} else if t.window.Enclose(my, mx) {
mx -= t.window.Left
my -= t.window.Top
mx = util.Constrain(mx-displayWidth([]rune(t.prompt)), 0, len(t.input))
if !t.reverse {
my = t.maxHeight() - my - 1
my = t.window.Height - my - 1
}
min := 2 + len(t.header)
if t.inlineInfo {
@@ -1217,7 +1371,7 @@ func (t *Terminal) vset(o int) bool {
}
func (t *Terminal) maxItems() int {
max := t.maxHeight() - 2 - len(t.header)
max := t.window.Height - 2 - len(t.header)
if t.inlineInfo {
max++
}

View File

@@ -43,6 +43,11 @@ Execute (fzf#run with dir option and noautochdir):
" No change in working directory
AssertEqual cwd, getcwd()
call fzf#run({'source': ['/foobar'], 'sink': 'tabe', 'dir': '/tmp', 'options': '-1'})
AssertEqual cwd, getcwd()
tabclose
AssertEqual cwd, getcwd()
Execute (Incomplete fzf#run with dir option and autochdir):
set acd
let cwd = getcwd()

View File

@@ -90,6 +90,10 @@ class Tmux
go("send-keys -t #{target} #{args}")
end
def paste str
%x[tmux setb '#{str.gsub("'", "'\\''")}' \\; pasteb -t #{win} \\; send-keys -t #{win} Enter]
end
def capture pane = 0
File.unlink TEMPNAME while File.exists? TEMPNAME
wait do
@@ -362,7 +366,7 @@ class TestGoFZF < TestBase
end
def test_query_unicode
tmux.send_keys "(echo abc; echo 가나다) | #{fzf :query, '가다'}", :Enter
tmux.paste "(echo abc; echo 가나다) | #{fzf :query, '가다'}"
tmux.until { |lines| lines[-2].include? '1/2' }
tmux.send_keys :Enter
assert_equal ['가나다'], readonce.split($/)
@@ -869,31 +873,34 @@ class TestGoFZF < TestBase
def test_execute
output = '/tmp/fzf-test-execute'
opts = %[--bind \\"alt-a:execute(echo '[{}]' >> #{output}),alt-b:execute[echo '({}), ({})' >> #{output}],C:execute:echo '({}), [{}], @{}@' >> #{output}\\"]
opts = %[--bind \\"alt-a:execute(echo [{}] >> #{output}),alt-b:execute[echo /{}{}/ >> #{output}],C:execute:echo /{}{}{}/ >> #{output}\\"]
wait = lambda { |exp| tmux.until { |lines| lines[-2].include? exp } }
tmux.send_keys "seq 100 | #{fzf opts}; sync", :Enter
wait['100/100']
writelines tempname, %w[foo'bar foo"bar foo$bar]
tmux.send_keys "cat #{tempname} | #{fzf opts}; sync", :Enter
wait['3/3']
tmux.send_keys :Escape, :a
wait['/100']
wait['/3']
tmux.send_keys :Escape, :a
wait['/100']
wait['/3']
tmux.send_keys :Up
tmux.send_keys :Escape, :b
wait['/100']
wait['/3']
tmux.send_keys :Escape, :b
wait['/100']
wait['/3']
tmux.send_keys :Up
tmux.send_keys :C
wait['100/100']
tmux.send_keys 'foobar'
wait['0/100']
wait['3/3']
tmux.send_keys 'barfoo'
wait['0/3']
tmux.send_keys :Escape, :a
wait['/100']
wait['/3']
tmux.send_keys :Escape, :b
wait['/100']
wait['/3']
tmux.send_keys :Enter
readonce
assert_equal ['["1"]', '["1"]', '("2"), ("2")', '("2"), ("2")', '("3"), ["3"], @"3"@'],
assert_equal %w[[foo'bar] [foo'bar]
/foo"barfoo"bar/ /foo"barfoo"bar/
/foo$barfoo$barfoo$bar/],
File.readlines(output).map(&:chomp)
ensure
File.unlink output rescue nil
@@ -901,21 +908,24 @@ class TestGoFZF < TestBase
def test_execute_multi
output = '/tmp/fzf-test-execute-multi'
opts = %[--multi --bind \\"alt-a:execute-multi(echo '[{}], @{}@' >> #{output}; sync)\\"]
tmux.send_keys "seq 100 | #{fzf opts}", :Enter
tmux.until { |lines| lines[-2].include? '100/100' }
opts = %[--multi --bind \\"alt-a:execute-multi(echo {}/{} >> #{output}; sync)\\"]
writelines tempname, %w[foo'bar foo"bar foo$bar foobar]
tmux.send_keys "cat #{tempname} | #{fzf opts}", :Enter
tmux.until { |lines| lines[-2].include? '4/4' }
tmux.send_keys :Escape, :a
tmux.until { |lines| lines[-2].include? '/100' }
tmux.until { |lines| lines[-2].include? '/4' }
tmux.send_keys :BTab, :BTab, :BTab
tmux.send_keys :Escape, :a
tmux.until { |lines| lines[-2].include? '/100' }
tmux.until { |lines| lines[-2].include? '/4' }
tmux.send_keys :Tab, :Tab
tmux.send_keys :Escape, :a
tmux.until { |lines| lines[-2].include? '/100' }
tmux.until { |lines| lines[-2].include? '/4' }
tmux.send_keys :Enter
tmux.prepare
readonce
assert_equal ['["1"], @"1"@', '["1" "2" "3"], @"1" "2" "3"@', '["1" "2" "4"], @"1" "2" "4"@'],
assert_equal [%[foo'bar/foo'bar],
%[foo'bar foo"bar foo$bar/foo'bar foo"bar foo$bar],
%[foo'bar foo"bar foobar/foo'bar foo"bar foobar]],
File.readlines(output).map(&:chomp)
ensure
File.unlink output rescue nil
@@ -934,7 +944,7 @@ class TestGoFZF < TestBase
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)
assert_equal ["-c / 'foo'bar"], File.readlines(output).map(&:chomp)
ensure
File.unlink output rescue nil
end
@@ -1222,6 +1232,34 @@ class TestGoFZF < TestBase
assert_equal '3', readonce.chomp
end
def test_preview
tmux.send_keys %[seq 1000 | sed s/^2$// | #{FZF} --preview 'sleep 0.2; echo {{}-{}}' --bind ?:toggle-preview], :Enter
tmux.until { |lines| lines[1].include?(' {1-1}') }
tmux.send_keys :Up
tmux.until { |lines| lines[1].include?(' {-}') }
tmux.send_keys '555'
tmux.until { |lines| lines[1].include?(' {555-555}') }
tmux.send_keys '?'
tmux.until { |lines| !lines[1].include?(' {555-555}') }
tmux.send_keys '?'
tmux.until { |lines| lines[1].include?(' {555-555}') }
tmux.send_keys :BSpace
tmux.until { |lines| lines[-2].start_with? ' 28/1000' }
tmux.send_keys 'foobar'
tmux.until { |lines| !lines[1].include?('{') }
end
def test_preview_hidden
tmux.send_keys %[seq 1000 | #{FZF} --preview 'echo {{}-{}}' --preview-window down:1:hidden --bind ?:toggle-preview], :Enter
tmux.until { |lines| lines[-1] == '>' }
tmux.send_keys '?'
tmux.until { |lines| lines[-2].include?(' {1-1}') }
tmux.send_keys '555'
tmux.until { |lines| lines[-2].include?(' {555-555}') }
tmux.send_keys '?'
tmux.until { |lines| lines[-1] == '> 555' }
end
private
def writelines path, lines
File.unlink path while File.exists? path
@@ -1279,16 +1317,29 @@ module TestShell
def test_ctrl_t_unicode
FileUtils.mkdir_p '/tmp/fzf-test'
tmux.send_keys 'cd /tmp/fzf-test; echo -n test1 > "fzf-unicode 테스트1"; echo -n test2 > "fzf-unicode 테스트2"', :Enter
tmux.paste 'cd /tmp/fzf-test; echo -n test1 > "fzf-unicode 테스트1"; echo -n test2 > "fzf-unicode 테스트2"'
tmux.prepare
tmux.send_keys 'cat ', 'C-t', pane: 0
tmux.until(1) { |lines| lines.item_count >= 1 }
tmux.send_keys 'fzf-unicode', pane: 1
tmux.until(1) { |lines| lines[-2].start_with? ' 2/' }
tmux.send_keys :BTab, :BTab, pane: 1
tmux.send_keys '1', pane: 1
tmux.until(1) { |lines| lines[-2].start_with? ' 1/' }
tmux.send_keys :BTab, pane: 1
tmux.until(1) { |lines| lines[-2].include? '(1)' }
tmux.send_keys :BSpace, pane: 1
tmux.until(1) { |lines| lines[-2].start_with? ' 2/' }
tmux.send_keys '2', pane: 1
tmux.until(1) { |lines| lines[-2].start_with? ' 1/' }
tmux.send_keys :BTab, pane: 1
tmux.until(1) { |lines| lines[-2].include? '(2)' }
tmux.send_keys :Enter, pane: 1
tmux.until { |lines| lines[-1].include?('cat') || lines[-2].include?('cat') }
tmux.until { |lines| lines[-1].include?('fzf-unicode') || lines[-2].include?('fzf-unicode') }
tmux.send_keys :Enter
tmux.until { |lines| lines[-1].include? 'test1test2' }
end
@@ -1496,12 +1547,24 @@ module CompletionTest
def test_file_completion_unicode
FileUtils.mkdir_p '/tmp/fzf-test'
tmux.send_keys 'cd /tmp/fzf-test; echo -n test3 > "fzf-unicode 테스트1"; echo -n test4 > "fzf-unicode 테스트2"', :Enter
tmux.paste 'cd /tmp/fzf-test; echo -n test3 > "fzf-unicode 테스트1"; echo -n test4 > "fzf-unicode 테스트2"'
tmux.prepare
tmux.send_keys 'cat fzf-unicode**', :Tab, pane: 0
tmux.until(1) { |lines| lines[-2].start_with? ' 2/' }
tmux.send_keys :BTab, :BTab, pane: 1
tmux.send_keys '1', pane: 1
tmux.until(1) { |lines| lines[-2].start_with? ' 1/' }
tmux.send_keys :BTab, pane: 1
tmux.until(1) { |lines| lines[-2].include? '(1)' }
tmux.send_keys :BSpace, pane: 1
tmux.until(1) { |lines| lines[-2].start_with? ' 2/' }
tmux.send_keys '2', pane: 1
tmux.until(1) { |lines| lines[-2].start_with? ' 1/' }
tmux.send_keys :BTab, pane: 1
tmux.until(1) { |lines| lines[-2].include? '(2)' }
tmux.send_keys :Enter, pane: 1
tmux.until { |lines| lines[-1].include?('cat') || lines[-2].include?('cat') }
tmux.send_keys :Enter