m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-16 15:23:48 -05:00

Compare commits

..

29 Commits

Author SHA1 Message Date
Junegunn Choi
248320fa55 0.11.1 2015-12-01 00:39:45 +09:00
Junegunn Choi
d4e26707c7 GO15VENDOREXPERIMENT=1 (#430) 2015-11-30 18:41:53 +09:00
Junegunn Choi
99ea1056ac Add --tabstop option
Related: https://github.com/junegunn/fzf.vim/issues/49
2015-11-30 17:35:03 +09:00
Junegunn Choi
7bcf4effa5 Fix test failure - use absolute path 2015-11-30 17:32:40 +09:00
Junegunn Choi
e1df876b61 Merge pull request #380 from acornejo/android
Add android build: `make android`
2015-11-20 03:28:41 +09:00
Alex Cornejo
28ffb9638d add android build 2015-11-18 23:20:51 -08:00
Junegunn Choi
1c20255504 Fix typos in help message
Close #425. Thanks to @blueyed.
2015-11-19 09:58:07 +09:00
Junegunn Choi
1fd884b34f Merge pull request #423 from blueyed/zsh-fzf-completion-localoptions
zsh: fzf-completion: use noshwordsplit local option
2015-11-19 00:54:42 +09:00
Daniel Hahler
701687faab zsh: fzf-completion: use noshwordsplit local option
This also fixes the completion causing a bell / flickering in case
"shwordsplit" was not set, because then the function would return false.
2015-11-18 16:09:00 +01:00
Junegunn Choi
bbc3055feb Merge pull request #421 from blueyed/zsh-completion-grep-command
zsh completion: use \grep to skip any alias
2015-11-18 03:32:39 +09:00
Daniel Hahler
95c69083c7 zsh completion: use \grep to skip any alias 2015-11-17 18:51:29 +01:00
Junegunn Choi
57a37b5832 [bash-completion] Fix #417 - Update command list 2015-11-12 13:53:36 +09:00
Junegunn Choi
d29ae1c462 [install] Add --32 / --64 options
Related: #373
2015-11-12 13:42:56 +09:00
Junegunn Choi
df468fc482 0.11.0 2015-11-10 01:54:53 +09:00
Junegunn Choi
31278bcc68 Fix compatibility issues with OR operator and inverse terms 2015-11-10 01:54:37 +09:00
Junegunn Choi
e7e86b68f4 Add OR operator
Close #412
2015-11-09 23:58:53 +09:00
Junegunn Choi
a89d8995c3 Add execute-multi action
Close #413
2015-11-09 23:58:19 +09:00
Junegunn Choi
dbc854d5f4 Handle wide unicode characters in --prompt 2015-11-09 22:01:40 +09:00
Junegunn Choi
f1cd0e2daf [zsh] Fix #404 - Escape $ in $LBUFFER 2015-11-09 12:06:10 +09:00
Junegunn Choi
90d32bd756 [install] Fix #414 - Respect $ZDOTDIR 2015-11-09 01:48:55 +09:00
Junegunn Choi
e99731ea85 [shell] Add FZF_ALT_C_COMMAND for ALT-C (#408) 2015-11-08 00:12:12 +09:00
Junegunn Choi
15659ac6e6 Merge pull request #409 from freitass/master
[bash-completion] Add nvim to f_cmds
2015-11-07 00:23:30 +09:00
Leandro Freitas
3ef41845a9 [bash-completion] Add nvim to f_cmds 2015-11-06 11:22:35 -02:00
Junegunn Choi
c84e681581 Merge pull request #403 from JackDanger/not-relying-on-exit-status-for-ctrl-r
Not relying on exit status for CTRL-R

Patch submitted by @robinro and @JackDanger
Close #403 #242 #241 #203
2015-11-06 08:38:36 +09:00
Jack Danger Canty
c3cf3427b1 Not relying on exit status for CTRL-R
In the case that fzf-tmux returns a user-selected result but with a
non-zero exit status (which can happen if a function inside $PS1 returns
non-zero) this allows CTRL-R to continue working as expected.

Addresses #203 (Tranquility's comment)
2015-11-05 10:08:54 -08:00
Junegunn Choi
2c4f71d85b [zsh] fzf-history-widget - update local declaration 2015-11-04 21:57:18 +09:00
Junegunn Choi
c6328affae Update extended-search mode section of README 2015-11-04 03:16:36 +09:00
Junegunn Choi
aaef18295d Update FZF_DEFAULT_COMMAND example 2015-11-04 03:14:38 +09:00
Junegunn Choi
14f0d2035e Update Homebrew instructions 2015-11-04 03:13:22 +09:00
25 changed files with 474 additions and 156 deletions

6
.gitmodules vendored Normal file
View File

@@ -0,0 +1,6 @@
[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,20 @@
CHANGELOG
=========
0.11.1
------
- Added `--tabstop=SPACES` option
0.11.0
------
- Added OR operator for extended-search mode
- Added `--execute-multi` action
- Fixed incorrect cursor position when unicode wide characters are used in
`--prompt`
- Fixes and improvements in shell extensions
0.10.9
------

View File

@@ -50,10 +50,10 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
On OS X, you can use [Homebrew](http://brew.sh/) to install fzf.
```sh
brew reinstall --HEAD fzf
brew install fzf
# Install shell extensions
/usr/local/Cellar/fzf/HEAD/install
/usr/local/opt/fzf/install
```
#### Install as Vim plugin
@@ -78,7 +78,7 @@ while. Please follow the instruction below depending on the installation
method.
- git: `cd ~/.fzf && git pull && ./install`
- brew: `brew reinstall --HEAD fzf`
- brew: `brew update; brew reinstall fzf`
- vim-plug: `:PlugUpdate fzf`
Usage
@@ -108,32 +108,41 @@ vim $(fzf)
- Mouse: scroll, click, double-click; shift-click and shift-scroll on
multi-select mode
#### Extended-search mode
#### Search syntax
Since 0.10.9, fzf starts in "extended-search mode" by default.
Unless otherwise specified, fzf starts in "extended-search mode" where you can
type in multiple search terms delimited by spaces. e.g. `^music .mp3$ sbtrkt
!rmx`
In this mode, you can specify multiple patterns delimited by spaces,
such as: `^music .mp3$ sbtrkt !rmx`
| Token | Description | Match type |
| -------- | -------------------------------- | -------------------- |
| `^music` | Items that start with `music` | prefix-exact-match |
| `.mp3$` | Items that end with `.mp3` | suffix-exact-match |
| `sbtrkt` | Items that match `sbtrkt` | fuzzy-match |
| `!rmx` | Items that do not match `rmx` | inverse-fuzzy-match |
| `'wild` | Items that include `wild` | exact-match (quoted) |
| `!'fire` | Items that do not include `fire` | inverse-exact-match |
| Token | Match type | Description |
| -------- | -------------------- | -------------------------------- |
| `sbtrkt` | fuzzy-match | Items that match `sbtrkt` |
| `^music` | prefix-exact-match | Items that start with `music` |
| `.mp3$` | suffix-exact-match | Items that end with `.mp3` |
| `'wild` | exact-match (quoted) | Items that include `wild` |
| `!rmx` | inverse-fuzzy-match | Items that do not match `rmx` |
| `!'fire` | inverse-exact-match | Items that do not include `fire` |
If you don't prefer fuzzy matching and do not wish to "quote" every word,
start fzf with `-e` or `--exact` option. Note that when `--exact` is set,
`'`-prefix "unquotes" the term.
A single bar character term acts as an OR operator. For example, the following
query matches entries that start with `core` and end with either `go`, `rb`,
or `py`.
```
^core go$ | rb$ | py$
```
#### Environment variables
- `FZF_DEFAULT_COMMAND`
- Default command to use when input is tty
- e.g. `export FZF_DEFAULT_COMMAND='ag -g ""'`
- `FZF_DEFAULT_OPTS`
- Default options. e.g. `export FZF_DEFAULT_OPTS="--reverse --inline-info"`
- Default options
- e.g. `export FZF_DEFAULT_OPTS="--reverse --inline-info"`
Examples
--------
@@ -252,6 +261,17 @@ export FZF_COMPLETION_TRIGGER='~~'
export FZF_COMPLETION_OPTS='+c -x'
```
#### Supported commands
On bash, fuzzy completion is enabled only for a predefined set of commands
(`complete | grep _fzf` to see the list). But you can enable it for other
commands as well like follows.
```sh
# There are also _fzf_path_completion and _fzf_dir_completion
complete -F _fzf_file_completion -o default -o bashdefault doge
```
Usage as Vim plugin
-------------------
@@ -335,10 +355,10 @@ filtering:
```sh
# Feed the output of ag into fzf
ag -l -g "" | fzf
ag -g "" | fzf
# Setting ag as the default source for fzf
export FZF_DEFAULT_COMMAND='ag -l -g ""'
export FZF_DEFAULT_COMMAND='ag -g ""'
# Now fzf (w/o pipe) will use ag instead of find
fzf

4
fzf
View File

@@ -370,7 +370,7 @@ class FZF
+i Case-sensitive match
-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])
integer or a range expression ([BEGIN]..[END]).
--with-nth=N[,..] Transform the item using index expressions for search
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
@@ -396,7 +396,7 @@ class FZF
Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty
FZF_DEFAULT_OPTS Defaults options. (e.g. "-x -m --sort 10000")] + $/ + $/
FZF_DEFAULT_OPTS Default options (e.g. "-x -m --sort 10000")] + $/ + $/
exit x
end

24
install
View File

@@ -2,25 +2,28 @@
set -u
[[ "$@" =~ --pre ]] && version=0.10.9 pre=1 ||
version=0.10.9 pre=0
[[ "$@" =~ --pre ]] && version=0.11.1 pre=1 ||
version=0.11.1 pre=0
auto_completion=
key_bindings=
update_config=1
binary_arch=
help() {
cat << EOF
usage: $0 [OPTIONS]
--help Show this message
--bin Download fzf binary only
--bin Download fzf binary only; Do not generate ~/.fzf.{bash,zsh}
--all Download fzf binary and update configuration files
to enable key bindings and fuzzy completion
--[no-]key-bindings Enable/disable key bindings (CTRL-T, CTRL-R, ALT-C)
--[no-]completion Enable/disable fuzzy completion (bash & zsh)
--[no-]update-rc Whether or not to update shell configuration files
--32 Download 32-bit binary
--64 Download 64-bit binary
EOF
}
@@ -41,6 +44,8 @@ for opt in $@; do
--no-completion) auto_completion=0 ;;
--update-rc) update_config=1 ;;
--no-update-rc) update_config=0 ;;
--32) binary_arch=386 ;;
--64) binary_arch=amd64 ;;
--bin) ;;
*)
echo "unknown option: $opt"
@@ -143,10 +148,10 @@ archi=$(uname -sm)
binary_available=1
binary_error=""
case "$archi" in
Darwin\ x86_64) download fzf-$version-darwin_amd64 ;;
Darwin\ i*86) download fzf-$version-darwin_386 ;;
Linux\ x86_64) download fzf-$version-linux_amd64 ;;
Linux\ i*86) download fzf-$version-linux_386 ;;
Darwin\ x86_64) download fzf-$version-darwin_${binary_arch:-amd64} ;;
Darwin\ i*86) download fzf-$version-darwin_${binary_arch:-386} ;;
Linux\ x86_64) download fzf-$version-linux_${binary_arch:-amd64} ;;
Linux\ i*86) download fzf-$version-linux_${binary_arch:-386} ;;
*) binary_available=0 binary_error=1 ;;
esac
@@ -342,7 +347,8 @@ append_line() {
echo
for shell in bash zsh; do
append_line $update_config "[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" ~/.${shell}rc "~/.fzf.${shell}"
[ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc
append_line $update_config "[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" "$dest" "~/.fzf.${shell}"
done
if [ $key_bindings -eq 1 -a $has_fish -eq 1 ]; then
@@ -353,7 +359,7 @@ fi
cat << EOF
Finished. Restart your shell or reload config file.
source ~/.bashrc # bash
source ~/.zshrc # zsh
source ${ZDOTDIR:-~}/.zshrc # zsh
EOF
[ $has_fish -eq 1 ] && echo " fzf_key_bindings # fish"; cat << EOF

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf 1 "Nov 2015" "fzf 0.10.9" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Dec 2015" "fzf 0.11.1" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -157,6 +157,9 @@ 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
@@ -223,6 +226,7 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\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
@@ -276,6 +280,12 @@ 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
.TP
.BI "--history=" "HISTORY_FILE"
@@ -394,6 +404,13 @@ If you don't prefer fuzzy matching and do not wish to "quote" (prefixing with
\fB'\fR) every word, start fzf with \fB-e\fR or \fB--exact\fR option. Note that
when \fB--exact\fR is set, \fB'\fR-prefix "unquotes" the term.
.SS OR operator
A single bar character term acts as an OR operator. For example, the following
query matches entries that start with \fBcore\fR and end with either \fBgo\fR,
\fBrb\fR, or \fBpy\fR.
e.g. \fB^core go$ | rb$ | py$\fR
.SH AUTHOR
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)

View File

@@ -241,9 +241,9 @@ complete -o default -F _fzf_opts_completion fzf
d_cmds="cd pushd rmdir"
f_cmds="
awk cat diff diff3
emacs ex file ftp g++ gcc gvim head hg java
javac ld less more mvim patch perl python ruby
sed sftp sort source tail tee uniq vi view vim wc"
emacs emacsclient ex file ftp g++ gcc gvim head hg java
javac ld less more mvim nvim patch perl python ruby
sed sftp sort source tail tee uniq vi view vim wc xdg-open"
a_cmds="
basename bunzip2 bzip2 chmod chown curl cp dirname du
find git grep gunzip gzip hg jar

View File

@@ -21,7 +21,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
if ! setopt | \grep nonomatch > /dev/null; then
nnm=1
setopt nonomatch
fi
@@ -116,11 +116,8 @@ _fzf_complete_unalias() {
}
fzf-completion() {
local tokens cmd prefix trigger tail fzf matches lbuf d_cmds sws
if setopt | grep shwordsplit > /dev/null; then
sws=1
unsetopt shwordsplit
fi
local tokens cmd prefix trigger tail fzf matches lbuf d_cmds
setopt localoptions noshwordsplit
# http://zsh.sourceforge.net/FAQ/zshfaq03.html
# http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags
@@ -163,12 +160,10 @@ fzf-completion() {
else
eval "zle ${fzf_default_completion:-expand-or-complete}"
fi
[ -n "$sws" ] && setopt shwordsplit
}
[ -z "$fzf_default_completion" ] &&
fzf_default_completion=$(bindkey '^I' | grep -v undefined-key | awk '{print $2}')
fzf_default_completion=$(bindkey '^I' | \grep -v undefined-key | awk '{print $2}')
zle -N fzf-completion
bindkey '^I' fzf-completion

View File

@@ -1,7 +1,7 @@
# Key bindings
# ------------
__fzf_select__() {
local cmd="${FZF_CTRL_T_COMMAND:-"command \\find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"}"
@@ -29,9 +29,10 @@ __fzf_select_tmux__() {
}
__fzf_cd__() {
local dir
dir=$(command \find -L ${1:-.} \( -path '*/\.*' -o -fstype 'dev' -o -fstype 'proc' \) -prune \
-o -type d -print 2> /dev/null | sed 1d | cut -b3- | $(__fzfcmd) +m) && printf 'cd %q' "$dir"
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"
}
__fzf_history__() (

View File

@@ -33,9 +33,11 @@ function fzf_key_bindings
end
function __fzf_alt_c
set -q FZF_ALT_C_COMMAND; or set -l 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-"
# Fish hangs if the command before pipe redirects (2> /dev/null)
command find -L . \( -path '*/\.*' -o -fstype 'dev' -o -fstype 'proc' \) \
-prune -o -type d -print 2> /dev/null | sed 1d | cut -b3- | eval (__fzfcmd) +m > $TMPDIR/fzf.result
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)" +m > $TMPDIR/fzf.result"
[ (cat $TMPDIR/fzf.result | wc -l) -gt 0 ]
and cd (cat $TMPDIR/fzf.result)
commandline -f repaint

View File

@@ -4,7 +4,7 @@ if [[ $- == *i* ]]; then
# CTRL-T - Paste the selected file path(s) into the command line
__fsel() {
local cmd="${FZF_CTRL_T_COMMAND:-"command \\find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"}"
@@ -27,8 +27,9 @@ bindkey '^T' fzf-file-widget
# ALT-C - cd into the selected directory
fzf-cd-widget() {
cd "${$(command \find -L . \( -path '*/\.*' -o -fstype 'dev' -o -fstype 'proc' \) -prune \
-o -type d -print 2> /dev/null | sed 1d | cut -b3- | $(__fzfcmd) +m):-.}"
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):-.}"
zle reset-prompt
}
zle -N fzf-cd-widget
@@ -36,8 +37,9 @@ bindkey '\ec' fzf-cd-widget
# CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() {
local selected restore_no_bang_hist
if selected=( $(fc -l 1 | $(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r -q "$LBUFFER") ); then
local selected num
selected=( $(fc -l 1 | $(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r -q "${LBUFFER//$/\\$}") )
if [ -n "$selected" ]; then
num=$selected[1]
if [ -n "$num" ]; then
zle vi-fetch-history -n $num

44
src/Dockerfile.android Normal file
View File

@@ -0,0 +1,44 @@
FROM ubuntu:14.04
MAINTAINER Junegunn Choi <junegunn.c@gmail.com>
# apt-get
RUN apt-get update && apt-get -y upgrade && \
apt-get install -y --force-yes git curl build-essential
# Install Go 1.4
RUN cd / && curl \
https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz | \
tar -xz && mv go go1.4 && \
sed -i 's@#define PTHREAD_KEYS_MAX 128@@' /go1.4/src/runtime/cgo/gcc_android_arm.c
ENV GOPATH /go
ENV GOROOT /go1.4
ENV PATH /go1.4/bin:$PATH
RUN cd / && \
curl -O http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin && \
chmod 755 /android-ndk* && /android-ndk-r10e-linux-x86_64.bin && \
mv android-ndk-r10e /android-ndk
RUN cd /android-ndk && bash ./build/tools/make-standalone-toolchain.sh --platform=android-21 --install-dir=/ndk --arch=arm
ENV NDK_CC /ndk/bin/arm-linux-androideabi-gcc
RUN cd $GOROOT/src && \
CC_FOR_TARGET=$NDK_CC GOOS=android GOARCH=arm GOARM=7 ./make.bash
RUN cd / && curl \
http://ftp.gnu.org/gnu/ncurses/ncurses-5.9.tar.gz | \
tar -xz && cd /ncurses-5.9 && \
./configure CC=$NDK_CC CFLAGS="-fPIE -march=armv7-a -mfpu=neon -mhard-float -Wl,--no-warn-mismatch" LDFLAGS="-march=armv7-a -Wl,--no-warn-mismatch" --host=arm-linux --enable-overwrite --enable-const --without-cxx-binding --without-shared --without-debug --enable-widec --enable-ext-colors --enable-ext-mouse --enable-pc-files --with-pkg-config-libdir=$PKG_CONFIG_LIBDIR --without-manpages --without-ada --disable-shared --without-tests --prefix=/ndk/sysroot/usr --with-default-terminfo-dirs=/usr/share/terminfo --with-terminfo-dirs=/usr/share/terminfo ac_cv_header_locale_h=n ac_cv_func_getpwent=no ac_cv_func_getpwnam=no ac_cv_func_getpwuid=no && \
sed -i 's@#define HAVE_LOCALE_H 1@/* #undef HAVE_LOCALE_H */@' include/ncurses_cfg.h && \
make && \
sed -i '0,/echo.*/{s/echo.*/exit 0/}' misc/run_tic.sh && \
make install && \
mv /ndk/sysroot/usr/lib/libncursesw.a /ndk/sysroot/usr/lib/libncurses.a
# Volume
VOLUME /go
# Default CMD
CMD cd /go/src/github.com/junegunn/fzf/src && /bin/bash

View File

@@ -11,9 +11,16 @@ RUN cd / && curl \
https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz | \
tar -xz && mv go go1.4
# Install Go 1.5
RUN cd / && curl \
https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz | \
tar -xz && mv go go1.5
ENV GO15VENDOREXPERIMENT 1
ENV GOROOT_BOOTSTRAP /go1.4
ENV GOROOT /go1.5
ENV GOPATH /go
ENV GOROOT /go1.4
ENV PATH /go1.4/bin:$PATH
ENV PATH /go1.5/bin:$PATH
# For i386 build
RUN cd $GOROOT/src && GOARCH=386 ./make.bash

View File

@@ -2,12 +2,14 @@ ifndef GOPATH
$(error GOPATH is undefined)
endif
ifndef GOOS
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
GOOS := darwin
else ifeq ($(UNAME_S),Linux)
GOOS := linux
endif
endif
ifneq ($(shell uname -m),x86_64)
$(error "Build on $(UNAME_M) is not supported, yet.")
@@ -16,11 +18,13 @@ endif
SOURCES := $(wildcard *.go */*.go)
BINDIR := ../bin
BINARY32 := fzf-$(GOOS)_386
BINARY64 := fzf-$(GOOS)_amd64
VERSION = $(shell fzf/$(BINARY64) --version)
RELEASE32 = fzf-$(VERSION)-$(GOOS)_386
RELEASE64 = fzf-$(VERSION)-$(GOOS)_amd64
BINARY32 := fzf-$(GOOS)_386
BINARY64 := fzf-$(GOOS)_amd64
BINARYARM7 := fzf-$(GOOS)_arm7
VERSION := $(shell awk -F= '/version =/ {print $$2}' constants.go | tr -d "\" ")
RELEASE32 = fzf-$(VERSION)-$(GOOS)_386
RELEASE64 = fzf-$(VERSION)-$(GOOS)_amd64
RELEASEARM7 = fzf-$(VERSION)-$(GOOS)_arm7
all: release
@@ -31,9 +35,14 @@ release: build
build: test fzf/$(BINARY32) fzf/$(BINARY64)
android-build:
cd fzf && GOARCH=arm GOARM=7 CGO_ENABLED=1 go build -a -ldflags="-extldflags=-pie" -o $(BINARYARM7)
cd fzf && cp $(BINARYARM7) $(RELEASEARM7) && tar -czf $(RELEASEARM7).tgz $(RELEASEARM7) && \
rm -f $(RELEASEARM7)
test:
go get
go test -v ./...
GO15VENDOREXPERIMENT=1 go get
GO15VENDOREXPERIMENT=1 SHELL=/bin/sh go test -v ./...
install: $(BINDIR)/fzf
@@ -44,10 +53,10 @@ clean:
cd fzf && rm -f fzf-*
fzf/$(BINARY32): $(SOURCES)
cd fzf && GOARCH=386 CGO_ENABLED=1 go build -a -o $(BINARY32)
cd fzf && GO15VENDOREXPERIMENT=1 GOARCH=386 CGO_ENABLED=1 go build -a -o $(BINARY32)
fzf/$(BINARY64): $(SOURCES)
cd fzf && go build -a -tags "$(TAGS)" -o $(BINARY64)
cd fzf && GO15VENDOREXPERIMENT=1 go build -a -tags "$(TAGS)" -o $(BINARY64)
$(BINDIR)/fzf: fzf/$(BINARY64) | $(BINDIR)
cp -f fzf/$(BINARY64) $(BINDIR)
@@ -65,6 +74,9 @@ docker-ubuntu:
docker-centos:
docker build -t junegunn/centos-sandbox - < Dockerfile.centos
docker-android:
docker build -t junegunn/android-sandbox - < Dockerfile.android
arch: docker-arch
docker run -i -t -v $(GOPATH):/go junegunn/$@-sandbox \
sh -c 'cd /go/src/github.com/junegunn/fzf/src; /bin/bash'
@@ -81,5 +93,14 @@ linux: docker-centos
docker run -i -t -v $(GOPATH):/go junegunn/centos-sandbox \
/bin/bash -ci 'cd /go/src/github.com/junegunn/fzf/src; make TAGS=static'
ubuntu-android: docker-android
docker run -i -t -v $(GOPATH):/go junegunn/$@-sandbox \
sh -c 'cd /go/src/github.com/junegunn/fzf/src; /bin/bash'
android: docker-android
docker run -i -t -v $(GOPATH):/go junegunn/android-sandbox \
/bin/bash -ci 'cd /go/src/github.com/junegunn/fzf/src; GOOS=android make android-build'
.PHONY: all build release test install uninstall clean docker \
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

View File

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

View File

@@ -5,6 +5,12 @@ package curses
#include <locale.h>
#cgo !static LDFLAGS: -lncurses
#cgo static LDFLAGS: -l:libncursesw.a -l:libtinfo.a -l:libgpm.a -ldl
#cgo android static LDFLAGS: -l:libncurses.a -fPIE -march=armv7-a -mfpu=neon -mhard-float -Wl,--no-warn-mismatch
SCREEN *c_newterm () {
return newterm(NULL, stderr, stdin);
}
*/
import "C"
@@ -260,7 +266,7 @@ func Init(theme *ColorTheme, black bool, mouse bool) {
}
C.setlocale(C.LC_ALL, C.CString(""))
_screen = C.newterm(nil, C.stderr, C.stdin)
_screen = C.c_newterm()
if _screen == nil {
fmt.Println("Invalid $TERM: " + os.Getenv("TERM"))
os.Exit(2)

View File

@@ -63,6 +63,9 @@ func (item *Item) Rank(cache bool) Rank {
matchlen += end - begin
}
}
if matchlen == 0 {
matchlen = math.MaxUint16
}
var tiebreak uint16
switch rankTiebreak {
case byLength:

View File

@@ -1,6 +1,7 @@
package fzf
import (
"math"
"sort"
"testing"
@@ -42,7 +43,7 @@ func TestItemRank(t *testing.T) {
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
item1 := Item{text: strs[0], index: 1, offsets: []Offset{}}
rank1 := item1.Rank(true)
if rank1.matchlen != 0 || rank1.tiebreak != 3 || rank1.index != 1 {
if rank1.matchlen != math.MaxUint16 || rank1.tiebreak != 3 || rank1.index != 1 {
t.Error(item1.Rank(true))
}
// Only differ in index
@@ -68,9 +69,9 @@ func TestItemRank(t *testing.T) {
item6 := Item{text: strs[2], rank: Rank{0, 0, 2}, offsets: []Offset{Offset{1, 2}, Offset{6, 7}}}
items = []*Item{&item1, &item2, &item3, &item4, &item5, &item6}
sort.Sort(ByRelevance(items))
if items[0] != &item2 || items[1] != &item1 ||
items[2] != &item6 || items[3] != &item4 ||
items[4] != &item5 || items[5] != &item3 {
if items[0] != &item6 || items[1] != &item4 ||
items[2] != &item5 || items[3] != &item3 ||
items[4] != &item2 || items[5] != &item1 {
t.Error(items)
}
}

View File

@@ -22,7 +22,7 @@ const usage = `usage: fzf [options]
+i Case-sensitive match
-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])
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)
+s, --no-sort Do not sort the result
@@ -38,6 +38,7 @@ const usage = `usage: fzf [options]
--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)
--cycle Enable cyclic scroll
--no-hscroll Disable horizontal scroll
--inline-info Display finder info inline with the query
@@ -59,7 +60,7 @@ const usage = `usage: fzf [options]
Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty
FZF_DEFAULT_OPTS Defaults options. (e.g. '--reverse --inline-info')
FZF_DEFAULT_OPTS Default options (e.g. '--reverse --inline-info')
`
@@ -123,6 +124,7 @@ type Options struct {
Header []string
HeaderLines int
Margin [4]string
Tabstop int
Version bool
}
@@ -169,6 +171,7 @@ func defaultOptions() *Options {
Header: make([]string, 0),
HeaderLines: 0,
Margin: defaultMargin(),
Tabstop: 8,
Version: false}
}
@@ -466,10 +469,13 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, toggleSort b
// Backreferences are not supported.
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
executeRegexp = regexp.MustCompile(
"(?s):execute:.*|:execute(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
"(?s):execute(-multi)?:.*|:execute(-multi)?(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
}
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
return ":execute(" + strings.Repeat(" ", len(src)-10) + ")"
if strings.HasPrefix(src, ":execute-multi") {
return ":execute-multi(" + strings.Repeat(" ", len(src)-len(":execute-multi()")) + ")"
}
return ":execute(" + strings.Repeat(" ", len(src)-len(":execute()")) + ")"
})
masked = strings.Replace(masked, "::", string([]rune{escapedColon, ':'}), -1)
masked = strings.Replace(masked, ",:", string([]rune{escapedComma, ':'}), -1)
@@ -565,11 +571,18 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, toggleSort b
toggleSort = true
default:
if isExecuteAction(actLower) {
keymap[key] = actExecute
if act[7] == ':' {
execmap[key] = act[8:]
var offset int
if strings.HasPrefix(actLower, "execute-multi") {
keymap[key] = actExecuteMulti
offset = len("execute-multi")
} else {
execmap[key] = act[8 : len(act)-1]
keymap[key] = actExecute
offset = len("execute")
}
if act[offset] == ':' {
execmap[key] = act[offset+1:]
} else {
execmap[key] = act[offset+1 : len(act)-1]
}
} else {
errorExit("unknown action: " + act)
@@ -580,10 +593,16 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, toggleSort b
}
func isExecuteAction(str string) bool {
if !strings.HasPrefix(str, "execute") || len(str) < 9 {
if !strings.HasPrefix(str, "execute") || len(str) < len("execute()") {
return false
}
b := str[7]
b := str[len("execute")]
if strings.HasPrefix(str, "execute-multi") {
if len(str) < len("execute-multi()") {
return false
}
b = str[len("execute-multi")]
}
e := str[len(str)-1]
if b == ':' || b == '(' && e == ')' || b == '[' && e == ']' ||
b == e && strings.ContainsAny(string(b), "~!@#$%^&*;/|") {
@@ -806,6 +825,8 @@ func parseOptions(opts *Options, allArgs []string) {
case "--margin":
opts.Margin = parseMargin(
nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
case "--tabstop":
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
case "--version":
opts.Version = true
default:
@@ -845,6 +866,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.HeaderLines = atoi(value)
} else if match, value := optString(arg, "--margin="); match {
opts.Margin = parseMargin(value)
} else if match, value := optString(arg, "--tabstop="); match {
opts.Tabstop = atoi(value)
} else {
errorExit("unknown option: " + arg)
}
@@ -855,6 +878,10 @@ func parseOptions(opts *Options, allArgs []string) {
errorExit("header lines must be a non-negative integer")
}
if opts.Tabstop < 1 {
errorExit("tab stop must be a positive integer")
}
// Change default actions for CTRL-N / CTRL-P when --history is used
if opts.History != nil {
if _, prs := keymap[curses.CtrlP]; !prs {

View File

@@ -36,6 +36,8 @@ type term struct {
origText []rune
}
type termSet []term
// Pattern represents search pattern
type Pattern struct {
fuzzy bool
@@ -43,8 +45,8 @@ type Pattern struct {
caseSensitive bool
forward bool
text []rune
terms []term
hasInvTerm bool
termSets []termSet
cacheable bool
delimiter Delimiter
nth []Range
procFun map[termType]func(bool, bool, []rune, []rune) (int, int)
@@ -88,14 +90,20 @@ func BuildPattern(fuzzy bool, extended bool, caseMode Case, forward bool,
return cached
}
caseSensitive, hasInvTerm := true, false
terms := []term{}
caseSensitive, cacheable := true, true
termSets := []termSet{}
if extended {
terms = parseTerms(fuzzy, caseMode, asString)
for _, term := range terms {
if term.inv {
hasInvTerm = true
termSets = parseTerms(fuzzy, caseMode, asString)
Loop:
for _, termSet := range termSets {
for idx, term := range termSet {
// If the query contains inverse search terms or OR operators,
// we cannot cache the search scope
if idx > 0 || term.inv {
cacheable = false
break Loop
}
}
}
} else {
@@ -113,8 +121,8 @@ func BuildPattern(fuzzy bool, extended bool, caseMode Case, forward bool,
caseSensitive: caseSensitive,
forward: forward,
text: []rune(asString),
terms: terms,
hasInvTerm: hasInvTerm,
termSets: termSets,
cacheable: cacheable,
nth: nth,
delimiter: delimiter,
procFun: make(map[termType]func(bool, bool, []rune, []rune) (int, int))}
@@ -129,9 +137,11 @@ func BuildPattern(fuzzy bool, extended bool, caseMode Case, forward bool,
return ptr
}
func parseTerms(fuzzy bool, caseMode Case, str string) []term {
func parseTerms(fuzzy bool, caseMode Case, str string) []termSet {
tokens := _splitRegex.Split(str, -1)
terms := []term{}
sets := []termSet{}
set := termSet{}
switchSet := false
for _, token := range tokens {
typ, inv, text := termFuzzy, false, token
lowerText := strings.ToLower(text)
@@ -145,6 +155,11 @@ func parseTerms(fuzzy bool, caseMode Case, str string) []term {
typ = termExact
}
if text == "|" {
switchSet = false
continue
}
if strings.HasPrefix(text, "!") {
inv = true
text = text[1:]
@@ -173,15 +188,23 @@ func parseTerms(fuzzy bool, caseMode Case, str string) []term {
}
if len(text) > 0 {
terms = append(terms, term{
if switchSet {
sets = append(sets, set)
set = termSet{}
}
set = append(set, term{
typ: typ,
inv: inv,
text: []rune(text),
caseSensitive: caseSensitive,
origText: origText})
switchSet = true
}
}
return terms
if len(set) > 0 {
sets = append(sets, set)
}
return sets
}
// IsEmpty returns true if the pattern is effectively empty
@@ -189,7 +212,7 @@ func (p *Pattern) IsEmpty() bool {
if !p.extended {
return len(p.text) == 0
}
return len(p.terms) == 0
return len(p.termSets) == 0
}
// AsString returns the search query in string type
@@ -203,11 +226,10 @@ func (p *Pattern) CacheKey() string {
return p.AsString()
}
cacheableTerms := []string{}
for _, term := range p.terms {
if term.inv {
continue
for _, termSet := range p.termSets {
if len(termSet) == 1 && !termSet[0].inv {
cacheableTerms = append(cacheableTerms, string(termSet[0].origText))
}
cacheableTerms = append(cacheableTerms, string(term.origText))
}
return strings.Join(cacheableTerms, " ")
}
@@ -218,7 +240,7 @@ func (p *Pattern) Match(chunk *Chunk) []*Item {
// ChunkCache: Exact match
cacheKey := p.CacheKey()
if !p.hasInvTerm { // Because we're excluding Inv-term from cache key
if p.cacheable {
if cached, found := _cache.Find(chunk, cacheKey); found {
return cached
}
@@ -243,7 +265,7 @@ Loop:
matches := p.matchChunk(space)
if !p.hasInvTerm {
if p.cacheable {
_cache.Add(chunk, cacheKey, matches)
}
return matches
@@ -260,7 +282,7 @@ func (p *Pattern) matchChunk(chunk *Chunk) []*Item {
}
} else {
for _, item := range *chunk {
if offsets := p.extendedMatch(item); len(offsets) == len(p.terms) {
if offsets := p.extendedMatch(item); len(offsets) == len(p.termSets) {
matches = append(matches, dupItem(item, offsets))
}
}
@@ -275,7 +297,7 @@ func (p *Pattern) MatchItem(item *Item) bool {
return sidx >= 0
}
offsets := p.extendedMatch(item)
return len(offsets) == len(p.terms)
return len(offsets) == len(p.termSets)
}
func dupItem(item *Item, offsets []Offset) *Item {
@@ -301,15 +323,23 @@ func (p *Pattern) basicMatch(item *Item) (int, int, int) {
func (p *Pattern) extendedMatch(item *Item) []Offset {
input := p.prepareInput(item)
offsets := []Offset{}
for _, term := range p.terms {
pfun := p.procFun[term.typ]
if sidx, eidx, tlen := p.iter(pfun, input, term.caseSensitive, p.forward, term.text); sidx >= 0 {
if term.inv {
for _, termSet := range p.termSets {
var offset *Offset
for _, term := range termSet {
pfun := p.procFun[term.typ]
if sidx, eidx, tlen := p.iter(pfun, input, term.caseSensitive, p.forward, term.text); sidx >= 0 {
if term.inv {
continue
}
offset = &Offset{int32(sidx), int32(eidx), int32(tlen)}
break
} else if term.inv {
offset = &Offset{0, 0, 0}
continue
}
offsets = append(offsets, Offset{int32(sidx), int32(eidx), int32(tlen)})
} else if term.inv {
offsets = append(offsets, Offset{0, 0, 0})
}
if offset != nil {
offsets = append(offsets, *offset)
}
}
return offsets

View File

@@ -9,20 +9,25 @@ import (
func TestParseTermsExtended(t *testing.T) {
terms := parseTerms(true, CaseSmart,
"aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$ ^iii$")
"| aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$ | ^iii$ ^xxx | 'yyy | | zzz$ | !ZZZ |")
if len(terms) != 9 ||
terms[0].typ != termFuzzy || terms[0].inv ||
terms[1].typ != termExact || terms[1].inv ||
terms[2].typ != termPrefix || terms[2].inv ||
terms[3].typ != termSuffix || terms[3].inv ||
terms[4].typ != termFuzzy || !terms[4].inv ||
terms[5].typ != termExact || !terms[5].inv ||
terms[6].typ != termPrefix || !terms[6].inv ||
terms[7].typ != termSuffix || !terms[7].inv ||
terms[8].typ != termEqual || terms[8].inv {
terms[0][0].typ != termFuzzy || terms[0][0].inv ||
terms[1][0].typ != termExact || terms[1][0].inv ||
terms[2][0].typ != termPrefix || terms[2][0].inv ||
terms[3][0].typ != termSuffix || terms[3][0].inv ||
terms[4][0].typ != termFuzzy || !terms[4][0].inv ||
terms[5][0].typ != termExact || !terms[5][0].inv ||
terms[6][0].typ != termPrefix || !terms[6][0].inv ||
terms[7][0].typ != termSuffix || !terms[7][0].inv ||
terms[7][1].typ != termEqual || terms[7][1].inv ||
terms[8][0].typ != termPrefix || terms[8][0].inv ||
terms[8][1].typ != termExact || terms[8][1].inv ||
terms[8][2].typ != termSuffix || terms[8][2].inv ||
terms[8][3].typ != termFuzzy || !terms[8][3].inv {
t.Errorf("%s", terms)
}
for idx, term := range terms {
for idx, termSet := range terms[:8] {
term := termSet[0]
if len(term.text) != 3 {
t.Errorf("%s", term)
}
@@ -30,20 +35,25 @@ func TestParseTermsExtended(t *testing.T) {
t.Errorf("%s", term)
}
}
for _, term := range terms[8] {
if len(term.origText) != 4 {
t.Errorf("%s", term)
}
}
}
func TestParseTermsExtendedExact(t *testing.T) {
terms := parseTerms(false, CaseSmart,
"aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$")
if len(terms) != 8 ||
terms[0].typ != termExact || terms[0].inv || len(terms[0].text) != 3 ||
terms[1].typ != termFuzzy || terms[1].inv || len(terms[1].text) != 3 ||
terms[2].typ != termPrefix || terms[2].inv || len(terms[2].text) != 3 ||
terms[3].typ != termSuffix || terms[3].inv || len(terms[3].text) != 3 ||
terms[4].typ != termExact || !terms[4].inv || len(terms[4].text) != 3 ||
terms[5].typ != termFuzzy || !terms[5].inv || len(terms[5].text) != 3 ||
terms[6].typ != termPrefix || !terms[6].inv || len(terms[6].text) != 3 ||
terms[7].typ != termSuffix || !terms[7].inv || len(terms[7].text) != 3 {
terms[0][0].typ != termExact || terms[0][0].inv || len(terms[0][0].text) != 3 ||
terms[1][0].typ != termFuzzy || terms[1][0].inv || len(terms[1][0].text) != 3 ||
terms[2][0].typ != termPrefix || terms[2][0].inv || len(terms[2][0].text) != 3 ||
terms[3][0].typ != termSuffix || terms[3][0].inv || len(terms[3][0].text) != 3 ||
terms[4][0].typ != termExact || !terms[4][0].inv || len(terms[4][0].text) != 3 ||
terms[5][0].typ != termFuzzy || !terms[5][0].inv || len(terms[5][0].text) != 3 ||
terms[6][0].typ != termPrefix || !terms[6][0].inv || len(terms[6][0].text) != 3 ||
terms[7][0].typ != termSuffix || !terms[7][0].inv || len(terms[7][0].text) != 3 {
t.Errorf("%s", terms)
}
}
@@ -61,9 +71,9 @@ func TestExact(t *testing.T) {
pattern := BuildPattern(true, true, CaseSmart, true,
[]Range{}, Delimiter{}, []rune("'abc"))
sidx, eidx := algo.ExactMatchNaive(
pattern.caseSensitive, pattern.forward, []rune("aabbcc abc"), pattern.terms[0].text)
pattern.caseSensitive, pattern.forward, []rune("aabbcc abc"), pattern.termSets[0][0].text)
if sidx != 7 || eidx != 10 {
t.Errorf("%s / %d / %d", pattern.terms, sidx, eidx)
t.Errorf("%s / %d / %d", pattern.termSets, sidx, eidx)
}
}
@@ -74,9 +84,9 @@ func TestEqual(t *testing.T) {
match := func(str string, sidxExpected int, eidxExpected int) {
sidx, eidx := algo.EqualMatch(
pattern.caseSensitive, pattern.forward, []rune(str), pattern.terms[0].text)
pattern.caseSensitive, pattern.forward, []rune(str), pattern.termSets[0][0].text)
if sidx != sidxExpected || eidx != eidxExpected {
t.Errorf("%s / %d / %d", pattern.terms, sidx, eidx)
t.Errorf("%s / %d / %d", pattern.termSets, sidx, eidx)
}
}
match("ABC", -1, -1)
@@ -130,3 +140,25 @@ func TestOrigTextAndTransformed(t *testing.T) {
}
}
}
func TestCacheKey(t *testing.T) {
test := func(extended bool, patStr string, expected string, cacheable bool) {
pat := BuildPattern(true, extended, CaseSmart, true, []Range{}, Delimiter{}, []rune(patStr))
if pat.CacheKey() != expected {
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
}
if pat.cacheable != cacheable {
t.Errorf("Expected: %s, actual: %s (%s)", cacheable, pat.cacheable, patStr)
}
clearPatternCache()
}
test(false, "foo !bar", "foo !bar", true)
test(false, "foo | bar !baz", "foo | bar !baz", true)
test(true, "foo bar baz", "foo bar baz", true)
test(true, "foo !bar", "foo", false)
test(true, "foo !bar baz", "foo baz", false)
test(true, "foo | bar baz", "baz", false)
test(true, "foo | bar | baz", "", false)
test(true, "foo | bar !baz", "", false)
test(true, "| | | foo", "foo", true)
}

View File

@@ -80,6 +80,7 @@ func (a byTimeOrder) Less(i, j int) bool {
var _spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
var _runeWidths = make(map[rune]int)
var _tabStop int
const (
reqPrompt util.EventType = iota
@@ -132,6 +133,7 @@ const (
actPreviousHistory
actNextHistory
actExecute
actExecuteMulti
)
func defaultKeymap() map[int]actionType {
@@ -193,6 +195,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
} else {
header = reverseStringArray(opts.Header)
}
_tabStop = opts.Tabstop
return &Terminal{
inlineInfo: opts.InlineInfo,
prompt: opts.Prompt,
@@ -305,21 +308,25 @@ func (t *Terminal) output() bool {
found = true
}
} else {
sels := make([]selectedItem, 0, len(t.selected))
for _, sel := range t.selected {
sels = append(sels, sel)
}
sort.Sort(byTimeOrder(sels))
for _, sel := range sels {
for _, sel := range t.sortSelected() {
fmt.Println(*sel.text)
}
}
return found
}
func (t *Terminal) sortSelected() []selectedItem {
sels := make([]selectedItem, 0, len(t.selected))
for _, sel := range t.selected {
sels = append(sels, sel)
}
sort.Sort(byTimeOrder(sels))
return sels
}
func runeWidth(r rune, prefixWidth int) int {
if r == '\t' {
return 8 - prefixWidth%8
return _tabStop - prefixWidth%_tabStop
} else if w, found := _runeWidths[r]; found {
return w
} else {
@@ -391,7 +398,7 @@ func (t *Terminal) move(y int, x int, clear bool) {
}
func (t *Terminal) placeCursor() {
t.move(0, len(t.prompt)+displayWidth(t.input[:t.cx]), false)
t.move(0, displayWidth([]rune(t.prompt))+displayWidth(t.input[:t.cx]), false)
}
func (t *Terminal) printPrompt() {
@@ -402,7 +409,7 @@ func (t *Terminal) printPrompt() {
func (t *Terminal) printInfo() {
if t.inlineInfo {
t.move(0, len(t.prompt)+displayWidth(t.input)+1, true)
t.move(0, displayWidth([]rune(t.prompt))+displayWidth(t.input)+1, true)
if t.reading {
C.CPrint(C.ColSpinner, true, " < ")
} else {
@@ -698,8 +705,12 @@ func keyMatch(key int, event C.Event) bool {
return event.Type == key || event.Type == C.Rune && int(event.Char) == key-C.AltZ
}
func executeCommand(template string, current string) {
command := strings.Replace(template, "{}", fmt.Sprintf("%q", current), -1)
func quoteEntry(entry string) string {
return fmt.Sprintf("%q", entry)
}
func executeCommand(template string, replacement string) {
command := strings.Replace(template, "{}", replacement, -1)
cmd := exec.Command("sh", "-c", command)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
@@ -858,7 +869,17 @@ 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], item.AsString(t.ansi))
executeCommand(t.execmap[mapkey], quoteEntry(item.AsString(t.ansi)))
}
case actExecuteMulti:
if len(t.selected) > 0 {
sels := make([]string, len(t.selected))
for i, sel := range t.sortSelected() {
sels[i] = quoteEntry(*sel.text)
}
executeCommand(t.execmap[mapkey], strings.Join(sels, " "))
} else {
return doAction(actExecute, mapkey)
}
case actInvalid:
t.mutex.Unlock()
@@ -1023,7 +1044,7 @@ func (t *Terminal) Loop() {
my >= t.marginInt[0] && my < C.MaxY()-t.marginInt[2] {
mx -= t.marginInt[3]
my -= t.marginInt[0]
mx = util.Constrain(mx-len(t.prompt), 0, len(t.input))
mx = util.Constrain(mx-displayWidth([]rune(t.prompt)), 0, len(t.input))
if !t.reverse {
my = t.maxHeight() - my - 1
}

View File

@@ -6,6 +6,7 @@ require 'fileutils'
DEFAULT_TIMEOUT = 20
FILE = File.expand_path(__FILE__)
base = File.expand_path('../../', __FILE__)
Dir.chdir base
FZF = "FZF_DEFAULT_OPTS= FZF_DEFAULT_COMMAND= #{base}/bin/fzf"
@@ -713,6 +714,24 @@ class TestGoFZF < TestBase
File.unlink output rescue nil
end
def test_execute_multi
output = '/tmp/fzf-test-execute-multi'
opts = %[--multi --bind \\"alt-a:execute-multi(echo '[{}], @{}@' >> #{output})\\"]
tmux.send_keys "seq 100 | #{fzf opts}", :Enter
tmux.until { |lines| lines[-2].include? '100/100' }
tmux.send_keys :Escape, :a
tmux.send_keys :BTab, :BTab, :BTab
tmux.send_keys :Escape, :a
tmux.send_keys :Tab, :Tab
tmux.send_keys :Escape, :a
tmux.send_keys :Enter
readonce
assert_equal ['["1"], @"1"@', '["1" "2" "3"], @"1" "2" "3"@', '["1" "2" "4"], @"1" "2" "4"@'],
File.readlines(output).map(&:chomp)
ensure
File.unlink output rescue nil
end
def test_cycle
tmux.send_keys "seq 8 | #{fzf :cycle}", :Enter
tmux.until { |lines| lines[-2].include? '8/8' }
@@ -785,8 +804,8 @@ class TestGoFZF < TestBase
end
def test_header
tmux.send_keys "seq 100 | #{fzf "--header \\\"\\$(head -5 #{__FILE__})\\\""}", :Enter
header = File.readlines(__FILE__).take(5).map(&:strip)
tmux.send_keys "seq 100 | #{fzf "--header \\\"\\$(head -5 #{FILE})\\\""}", :Enter
header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines|
lines[-2].include?('100/100') &&
lines[-7..-3].map(&:strip) == header
@@ -794,8 +813,8 @@ class TestGoFZF < TestBase
end
def test_header_reverse
tmux.send_keys "seq 100 | #{fzf "--header=\\\"\\$(head -5 #{__FILE__})\\\" --reverse"}", :Enter
header = File.readlines(__FILE__).take(5).map(&:strip)
tmux.send_keys "seq 100 | #{fzf "--header=\\\"\\$(head -5 #{FILE})\\\" --reverse"}", :Enter
header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines|
lines[1].include?('100/100') &&
lines[2..6].map(&:strip) == header
@@ -803,8 +822,8 @@ class TestGoFZF < TestBase
end
def test_header_and_header_lines
tmux.send_keys "seq 100 | #{fzf "--header-lines 10 --header \\\"\\$(head -5 #{__FILE__})\\\""}", :Enter
header = File.readlines(__FILE__).take(5).map(&:strip)
tmux.send_keys "seq 100 | #{fzf "--header-lines 10 --header \\\"\\$(head -5 #{FILE})\\\""}", :Enter
header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines|
lines[-2].include?('90/90') &&
lines[-7...-2].map(&:strip) == header &&
@@ -813,8 +832,8 @@ class TestGoFZF < TestBase
end
def test_header_and_header_lines_reverse
tmux.send_keys "seq 100 | #{fzf "--reverse --header-lines 10 --header \\\"\\$(head -5 #{__FILE__})\\\""}", :Enter
header = File.readlines(__FILE__).take(5).map(&:strip)
tmux.send_keys "seq 100 | #{fzf "--reverse --header-lines 10 --header \\\"\\$(head -5 #{FILE})\\\""}", :Enter
header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines|
lines[1].include?('90/90') &&
lines[2...7].map(&:strip) == header &&
@@ -847,6 +866,26 @@ class TestGoFZF < TestBase
tmux.send_keys :Enter
end
def test_tabstop
writelines tempname, ["f\too\tba\tr\tbaz\tbarfooq\tux"]
{
1 => '> f oo ba r baz barfooq ux',
2 => '> f oo ba r baz barfooq ux',
3 => '> f oo ba r baz barfooq ux',
4 => '> f oo ba r baz barfooq ux',
5 => '> f oo ba r baz barfooq ux',
6 => '> f oo ba r baz barfooq ux',
7 => '> f oo ba r baz barfooq ux',
8 => '> f oo ba r baz barfooq ux',
9 => '> f oo ba r baz barfooq ux',
}.each do |ts, exp|
tmux.prepare
tmux.send_keys %[cat #{tempname} | fzf --tabstop=#{ts}], :Enter
tmux.until { |lines| lines[-3] == exp }
tmux.send_keys :Enter
end
end
def test_with_nth
writelines tempname, ['hello world ', 'byebye']
assert_equal 'hello world ', `cat #{tempname} | #{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1`.chomp
@@ -915,6 +954,12 @@ class TestGoFZF < TestBase
assert_equal 4, `seq 123 | #{FZF} -f 13 +e`.lines.length
end
def test_or_operator
assert_equal %w[1 5 10], `seq 10 | #{FZF} -f "1 | 5"`.lines.map(&:chomp)
assert_equal %w[1 10 2 3 4 5 6 7 8 9],
`seq 10 | #{FZF} -f '1 | !1'`.lines.map(&:chomp)
end
private
def writelines path, lines
File.unlink path while File.exists? path
@@ -981,6 +1026,22 @@ module TestShell
tmux.until { |lines| lines[-1].end_with?(expected) }
end
def test_alt_c_command
set_var 'FZF_ALT_C_COMMAND', 'echo /tmp'
tmux.prepare
tmux.send_keys 'cd /', :Enter
tmux.prepare
tmux.send_keys :Escape, :c, pane: 0
lines = tmux.until(1) { |lines| lines.item_count == 1 }
tmux.send_keys :Enter, pane: 1
tmux.prepare
tmux.send_keys :pwd, :Enter
tmux.until { |lines| lines[-1].end_with? '/tmp' }
end
def test_ctrl_r
tmux.prepare
tmux.send_keys 'echo 1st', :Enter; tmux.prepare