m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-14 22:33:47 -05:00

Compare commits

...

40 Commits

Author SHA1 Message Date
Junegunn Choi
2f364c62f4 0.12.2 2016-05-19 01:55:54 +09:00
Junegunn Choi
7ed9f83662 Validate jump label characters
Also extend default jump labels
2016-05-19 01:46:22 +09:00
Junegunn Choi
f498a9b3fb Revert version number 2016-05-18 22:47:57 +09:00
Junegunn Choi
13330738b8 Do not match jump labels beyond the screen limit 2016-05-18 22:45:34 +09:00
Junegunn Choi
e53535cc61 Update default jump labels 2016-05-18 22:44:31 +09:00
Junegunn Choi
c62fc5e75c More named keys: F5 ~ F10, ALT-/ 2016-05-18 22:25:09 +09:00
Junegunn Choi
70245ad98c [make] Reduce the size of the binaries with -ldflags -w
Related: #555
2016-05-18 13:29:27 +09:00
Junegunn Choi
6d235bceee Add jump and jump-accept actions for --bind
jump and jump-accept implement EasyMotion-like movement in fzf.
Suggested by @mhrebenyuk. Close #569.
2016-05-18 02:10:03 +09:00
Junegunn Choi
4adebfc856 [install] go get -u github.com/junegunn/fzf/src/fzf 2016-05-17 01:41:59 +09:00
Junegunn Choi
faccc0a410 [fzf-tmux] Escape backslash in command-line arguments 2016-05-15 17:07:34 +09:00
Junegunn Choi
9078688baf Add print-query action for --bind
Close #571
2016-05-13 00:51:15 +09:00
Junegunn Choi
9bd8b1d25f Fix typo 2016-05-13 00:44:33 +09:00
Junegunn Choi
dd4be1da38 Allow alt-enter and alt-space for --bind (#571) 2016-05-13 00:43:50 +09:00
Junegunn Choi
66f86e1870 [fzf-tmux] Fix #562 - Check $TMUX instead of $TMUX_PANE 2016-05-11 22:08:14 +09:00
Junegunn Choi
4ab75b68dc Fix flaky test case: test_execute
Should wait until execute action completes
2016-05-11 01:40:49 +09:00
Junegunn Choi
73cb70dbb3 Fix flaky test case: test_file_completion_unicode 2016-05-11 01:25:17 +09:00
Junegunn Choi
d082cccb6d Fix flaky test case: test_ctrl_t_unicode
The width of the pseudo-terminal on Travis CI environment can be small
and cause the line to be wrapped.
2016-05-11 01:18:26 +09:00
Junegunn Choi
88a80e3c2c Determine 256-color capability using tigetnum("colors")
Close #570
2016-05-11 01:07:06 +09:00
Junegunn Choi
24516bcf4d [install] Set a temporary GOPATH 2016-05-09 02:03:08 +09:00
Junegunn Choi
b4c4a642ed Update README
Close #560, #561
2016-05-03 00:07:53 +09:00
Junegunn Choi
0231617857 [neovim] Fix issues with enew and tabnew layouts
Related: #559
2016-04-28 01:25:24 +09:00
Junegunn Choi
7f64fba80f Update Makefile to allow build on i686 (#555) 2016-04-26 01:49:02 +09:00
Junegunn Choi
633aec38f5 Merge pull request #554 from gene-pavlovsky/patch-1
Fix missing reference to UNAME_M
2016-04-25 23:04:29 +09:00
Gene Pavlovsky
d1b402a23c Fix missing reference to UNAME_M
The `Build on $(UNAME_M) is not supported, yet` message was referencing an undefined UNAME_M. Fixed that.
2016-04-24 21:24:10 +03:00
Junegunn Choi
35a9aff8e1 0.12.1 2016-04-25 01:23:52 +09:00
Junegunn Choi
988c9bd9be [zsh] Fix issues with unicode characters 2016-04-25 01:04:35 +09:00
Junegunn Choi
095f31b316 [vim] Explicitly set source to FZF_DEFAULT_COMMAND
Helps when your `$SHELL` is slow.

Close #552.
2016-04-24 18:02:01 +09:00
Junegunn Choi
d86cee2a69 [bash] Export fzf-file-widget function for bash 4+ (#546)
e.g. Remapping fzf-file-widget to CTRL-X CTRL-T intead of CTRL-T

    bind -x '"\C-x\C-t": fzf-file-widget'
    bind '"\C-t": transpose-chars'
2016-04-24 14:04:15 +09:00
Junegunn Choi
e986f20a85 [fish] Use consistent function names for key bindings (#546)
- fzf-file-widget
- fzf-history-widget
- fzf-cd-widget
2016-04-24 13:56:50 +09:00
Junegunn Choi
c727ba1d99 [fzf-tmux] Do not split pane if the height is too small 2016-04-24 13:32:33 +09:00
Junegunn Choi
bb70923cd8 Fix flaky test cases 2016-04-24 04:52:01 +09:00
Junegunn Choi
772fa42dcb [fish] Fix intermittent errors on CTRL-T
Related: 23244bb
2016-04-24 04:51:35 +09:00
Junegunn Choi
85ef3263fc Fix incorrect cache reference in --exact mode (#547)
When we prepend a single quote to our query in --exact mode, we are not
supposed to limit the scope of the new search to the previous
exact-match result.
2016-04-24 03:43:24 +09:00
Junegunn Choi
4bde8de63f Apply new ranking algorithm to exact match as well 2016-04-23 19:48:06 +09:00
Junegunn Choi
654a7df9b0 [neovim] Set bufhidden and nobuflisted after opening terminal 2016-04-23 17:53:54 +09:00
Junegunn Choi
c3aa836ec0 [bash] Update completion.bash
[bash] Update completion.bash
2016-04-23 11:36:50 +09:00
Junegunn Choi
95764bef6f Merge pull request #550 from gene-pavlovsky/gene-pavlovsky-patch-2
[bash] Update key-bindings.bash
2016-04-23 11:35:27 +09:00
Gene Pavlovsky
63dbf48546 Update key-bindings.bash
Faster startup. Use internal bash globbing instead of external grep binary (adapter from Gentoo's `/etc/bash/bashrc` TERM checking). Insignificant on Linux, but on Cygwin this cuts startup time by 40 ms on my Core i7 laptop.
2016-04-23 03:44:41 +03:00
Gene Pavlovsky
e2401350a3 Update completion.bash
Fixes #548. Avoid using a subshell in _fzf_defc().
2016-04-23 03:12:15 +03:00
Junegunn Choi
e867355b2a [neovim] Restore winfixwidth and winfixheight
Fix https://github.com/junegunn/fzf.vim/issues/128
2016-04-21 00:33:30 +09:00
22 changed files with 567 additions and 237 deletions

View File

@@ -1,6 +1,27 @@
CHANGELOG
=========
0.12.2
------
- 256-color capability detection does not require `256` in `$TERM`
- Added `print-query` action
- More named keys for binding; <kbd>F1</kbd> ~ <kbd>F10</kbd>,
<kbd>ALT-/</kbd>, <kbd>ALT-space</kbd>, and <kbd>ALT-enter</kbd>
- Added `jump` and `jump-accept` actions that implement [EasyMotion][em]-like
movement
![][jump]
[em]: https://github.com/easymotion/vim-easymotion
[jump]: https://cloud.githubusercontent.com/assets/700826/15367574/b3999dc4-1d64-11e6-85da-28ceeb1a9bc2.png
0.12.1
------
- Ranking algorithm introduced in 0.12.0 is now universally applied
- Fixed invalid cache reference in exact mode
- Fixes and improvements in Vim plugin and shell extensions
0.12.0
------

View File

@@ -151,27 +151,6 @@ Many useful examples can be found on [the wiki
page](https://github.com/junegunn/fzf/wiki/examples). Feel free to add your
own as well.
Key bindings for command line
-----------------------------
The install script will setup the following key bindings for bash, zsh, and
fish.
- `CTRL-T` - Paste the selected files and directories onto the command line
- Set `FZF_CTRL_T_COMMAND` to override the default command
- `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
- `ALT-C` - cd into the selected directory
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
pane with `FZF_TMUX_HEIGHT` (e.g. `20`, `50%`).
If you use vi mode on bash, you need to add `set -o vi` *before* `source
~/.fzf.bash` in your .bashrc, so that it correctly sets up key bindings for vi
mode.
`fzf-tmux` script
-----------------
@@ -191,6 +170,28 @@ cat /usr/share/dict/words | fzf-tmux -l 20% --multi --reverse
It will still work even when you're not on tmux, silently ignoring `-[udlr]`
options, so you can invariably use `fzf-tmux` in your scripts.
Key bindings for command line
-----------------------------
The install script will setup the following key bindings for bash, zsh, and
fish.
- `CTRL-T` - Paste the selected files and directories onto the command line
- Set `FZF_CTRL_T_COMMAND` to override the default command
- `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
- `ALT-C` - cd into the selected directory
- Set `FZF_ALT_C_COMMAND` to override the default command
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
pane with `FZF_TMUX_HEIGHT` (e.g. `20`, `50%`).
If you use vi mode on bash, you need to add `set -o vi` *before* `source
~/.fzf.bash` in your .bashrc, so that it correctly sets up key bindings for vi
mode.
Fuzzy completion for bash and zsh
---------------------------------

View File

@@ -8,6 +8,7 @@ skip=""
swap=""
close=""
term=""
[ -n "$LINES" ] && lines=$LINES || lines=$(tput lines)
while [ $# -gt 0 ]; do
arg="$1"
case "$arg" in
@@ -60,7 +61,7 @@ while [ $# -gt 0 ]; do
if [[ "$arg" =~ ^.l ]]; then
[ -n "$COLUMNS" ] && max=$COLUMNS || max=$(tput cols)
else
[ -n "$LINES" ] && max=$LINES || max=$(tput lines)
max=$lines
fi
size=$(( max - size ))
[ $size -lt 0 ] && size=0
@@ -82,7 +83,7 @@ while [ $# -gt 0 ]; do
shift
done
if [ -z "$TMUX" ]; then
if ! [ -n "$TMUX" -a "$lines" -gt 15 ]; then
fzf "${args[@]}"
exit $?
fi
@@ -133,6 +134,7 @@ mkfifo -m o+w $fifo3
# Build arguments to fzf
opts=""
for arg in "${args[@]}"; do
arg="${arg//\\/\\\\}"
arg="${arg//\"/\\\"}"
arg="${arg//\`/\\\`}"
opts="$opts \"$arg\""

14
install
View File

@@ -2,8 +2,8 @@
set -u
[[ "$@" =~ --pre ]] && version=0.12.0 pre=1 ||
version=0.12.0 pre=0
[[ "$@" =~ --pre ]] && version=0.12.2 pre=1 ||
version=0.12.2 pre=0
auto_completion=
key_bindings=
@@ -230,10 +230,14 @@ 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 github.com/junegunn/fzf/src/fzf) ... "
if go get github.com/junegunn/fzf/src/fzf; 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"
link_fzf_in_path
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
else
echo "Failed to build binary ..."
install_ruby_fzf

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 "Apr 2016" "fzf 0.12.0" "fzf - a command-line fuzzy finder"
.TH fzf 1 "May 2016" "fzf 0.12.2" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -183,6 +183,9 @@ on the center of the screen.
.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
@@ -200,11 +203,14 @@ 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-4]\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
@@ -244,12 +250,15 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
\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

View File

@@ -121,6 +121,10 @@ try
throw v:exception
endtry
if !has_key(dict, 'source') && !empty($FZF_DEFAULT_COMMAND)
let dict.source = $FZF_DEFAULT_COMMAND
endif
if has_key(dict, 'source')
let source = dict.source
let type = type(source)
@@ -294,7 +298,7 @@ function! s:split(dict)
\ 'down': ['botright', 'resize', &lines],
\ 'left': ['vertical topleft', 'vertical resize', &columns],
\ 'right': ['vertical botright', 'vertical resize', &columns] }
let s:ppos = s:getpos()
let ppos = s:getpos()
try
for [dir, triple] in items(directions)
let val = get(a:dict, dir, '')
@@ -307,7 +311,7 @@ function! s:split(dict)
endif
execute cmd sz.'new'
execute resz sz
return
return [ppos, {}]
endif
endfor
if s:present(a:dict, 'window')
@@ -315,31 +319,44 @@ function! s:split(dict)
else
execute (tabpagenr()-1).'tabnew'
endif
return [ppos, { '&l:wfw': &l:wfw, '&l:wfh': &l:wfh }]
finally
setlocal winfixwidth winfixheight buftype=nofile bufhidden=wipe nobuflisted
setlocal winfixwidth winfixheight
endtry
endfunction
function! s:execute_term(dict, command, temps) abort
call s:split(a:dict)
let fzf = { 'buf': bufnr('%'), 'dict': a:dict, 'temps': a:temps, 'name': 'FZF' }
let s:command = a:command
let [ppos, winopts] = s:split(a:dict)
let fzf = { 'buf': bufnr('%'), 'ppos': ppos, 'dict': a:dict, 'temps': a:temps,
\ 'name': 'FZF', 'winopts': winopts, 'command': a:command }
function! fzf.switch_back(inplace)
if a:inplace && bufnr('') == self.buf
" FIXME: Can't re-enter normal mode from terminal mode
" execute "normal! \<c-^>"
b #
" No other listed buffer
if bufnr('') == self.buf
enew
endif
endif
endfunction
function! fzf.on_exit(id, code)
let pos = s:getpos()
let inplace = pos == s:ppos " {'window': 'enew'}
if !inplace
if s:getpos() == self.ppos " {'window': 'enew'}
for [opt, val] in items(self.winopts)
execute 'let' opt '=' val
endfor
call self.switch_back(1)
else
if bufnr('') == self.buf
" We use close instead of bd! since Vim does not close the split when
" there's no other listed buffer (nvim +'set nobuflisted')
close
endif
if pos.tab == s:ppos.tab
wincmd p
endif
execute 'tabnext' self.ppos.tab
execute self.ppos.win.'wincmd w'
endif
if !s:exit_handler(a:code, s:command, 1)
if !s:exit_handler(a:code, self.command, 1)
return
endif
@@ -347,14 +364,7 @@ function! s:execute_term(dict, command, temps) abort
let ret = []
try
let ret = s:callback(self.dict, self.temps)
if inplace && bufnr('') == self.buf
execute "normal! \<c-^>"
" No other listed buffer
if bufnr('') == self.buf
bd!
endif
endif
call self.switch_back(s:getpos() == self.ppos)
finally
call s:popd(self.dict, ret)
endtry
@@ -363,7 +373,7 @@ function! s:execute_term(dict, command, temps) abort
call s:pushd(a:dict)
call termopen(a:command, fzf)
call s:popd(a:dict, [])
setlocal nospell
setlocal nospell bufhidden=wipe nobuflisted
setf fzf
startinsert
return []

View File

@@ -272,14 +272,15 @@ if type _completion_loader > /dev/null 2>&1; then
fi
_fzf_defc() {
local cmd func opts orig_var orig
local cmd func opts orig_var orig def
cmd="$1"
func="$2"
opts="$3"
orig_var="_fzf_orig_completion_$cmd"
orig="${!orig_var}"
if [ -n "$orig" ]; then
eval "$(printf "$orig" "$func")"
printf -v def "$orig" "$func"
eval "$def"
else
complete -F "$func" $opts "$cmd"
fi

View File

@@ -54,7 +54,7 @@ __fzf_generic_path_completion() {
[ "$dir" != "/" ] && dir="${dir/%\//}"
dir=${~dir}
matches=$(eval "$compgen $(printf %q "$dir")" | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=fzf_opts} -q "$leftover" | while read item; do
printf "%q$suffix " "$item"
echo -n "${(q)item}$suffix "
done)
matches=${matches% }
if [ -n "$matches" ]; then

View File

@@ -29,11 +29,13 @@ __fzf_select_tmux__() {
tmux split-window $height "cd $(printf %q "$PWD"); FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS") PATH=$(printf %q "$PATH") FZF_CTRL_T_COMMAND=$(printf %q "$FZF_CTRL_T_COMMAND") bash -c 'source \"${BASH_SOURCE[0]}\"; tmux send-keys -t $TMUX_PANE \"\$(__fzf_select__)\"'"
}
__fzf_select_tmux_auto__() {
if [ "${FZF_TMUX:-1}" != 0 ] && [ ${LINES:-40} -gt 15 ]; then
fzf-file-widget() {
if __fzf_use_tmux__; then
__fzf_select_tmux__
else
tmux send-keys -t "$TMUX_PANE" "$(__fzf_select__)"
local selected="$(__fzf_select__)"
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
fi
}
@@ -58,21 +60,21 @@ __fzf_history__() (
fi
)
__use_tmux=0
__use_tmux_auto=0
if [ -n "$TMUX_PANE" ]; then
[ "${FZF_TMUX:-1}" != 0 ] && [ ${LINES:-40} -gt 15 ] && __use_tmux=1
[ $BASH_VERSINFO -gt 3 ] && __use_tmux_auto=1
fi
__fzf_use_tmux__() {
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-1}" != 0 ] && [ ${LINES:-40} -gt 15 ]
}
if [ -z "$(set -o | \grep '^vi.*on')" ]; then
[ $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
# Required to refresh the prompt after fzf
bind '"\er": redraw-current-line'
bind '"\e^": history-expand-line'
# CTRL-T - Paste the selected file path into the command line
if [ $__use_tmux_auto -eq 1 ]; then
bind -x '"\C-t": "__fzf_select_tmux_auto__"'
if [ $__use_bind_x -eq 1 ]; then
bind -x '"\C-t": "fzf-file-widget"'
elif [ $__use_tmux -eq 1 ]; then
bind '"\C-t": " \C-u \C-a\C-k$(__fzf_select_tmux__)\e\C-e\C-y\C-a\C-d\C-y\ey\C-h"'
else
@@ -100,8 +102,8 @@ else
# CTRL-T - Paste the selected file path into the command line
# - FIXME: Selected items are attached to the end regardless of cursor position
if [ $__use_tmux_auto -eq 1 ]; then
bind -x '"\C-t": "__fzf_select_tmux_auto__"'
if [ $__use_bind_x -eq 1 ]; then
bind -x '"\C-t": "fzf-file-widget"'
elif [ $__use_tmux -eq 1 ]; then
bind '"\C-t": "\C-x\C-a$a \C-x\C-addi$(__fzf_select_tmux__)\C-x\C-e\C-x\C-a0P$xa"'
else
@@ -118,6 +120,6 @@ else
bind -m vi-command '"\ec": "ddi$(__fzf_cd__)\C-x\C-e\C-x\C-r\C-m"'
fi
unset -v __use_tmux __use_tmux_auto
unset -v __use_tmux __use_bind_x
fi

View File

@@ -13,27 +13,26 @@ function fzf_key_bindings
end
end
function __fzf_ctrl_t
function fzf-file-widget
set -q FZF_CTRL_T_COMMAND; or set -l 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-"
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)" -m > $TMPDIR/fzf.result"
and sleep 0
and commandline -i (cat $TMPDIR/fzf.result | __fzf_escape)
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
end
function __fzf_ctrl_r
function fzf-history-widget
history | eval (__fzfcmd) +s +m --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS > $TMPDIR/fzf.result
and commandline (cat $TMPDIR/fzf.result)
commandline -f repaint
rm -f $TMPDIR/fzf.result
end
function __fzf_alt_c
function fzf-cd-widget
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-"
@@ -59,14 +58,14 @@ function fzf_key_bindings
end
end
bind \ct '__fzf_ctrl_t'
bind \cr '__fzf_ctrl_r'
bind \ec '__fzf_alt_c'
bind \ct fzf-file-widget
bind \cr fzf-history-widget
bind \ec fzf-cd-widget
if bind -M insert > /dev/null 2>&1
bind -M insert \ct '__fzf_ctrl_t'
bind -M insert \cr '__fzf_ctrl_r'
bind -M insert \ec '__fzf_alt_c'
bind -M insert \ct fzf-file-widget
bind -M insert \cr fzf-history-widget
bind -M insert \ec fzf-cd-widget
end
end

View File

@@ -9,7 +9,7 @@ __fsel() {
-o -type d -print \
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"}"
eval "$cmd" | $(__fzfcmd) -m | while read item; do
printf '%q ' "$item"
echo -n "${(q)item} "
done
echo
}

View File

@@ -7,10 +7,6 @@ else ifeq ($(UNAME_S),Linux)
endif
endif
ifneq ($(shell uname -m),x86_64)
$(error "Build on $(UNAME_M) is not supported, yet.")
endif
SOURCES := $(wildcard *.go */*.go)
ROOTDIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
BINDIR := $(shell dirname $(ROOTDIR))/bin
@@ -26,15 +22,22 @@ RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
export GOPATH
all: release
UNAME_M := $(shell uname -m)
ifeq ($(UNAME_M),x86_64)
BINARY := $(BINARY64)
else ifeq ($(UNAME_M),i686)
BINARY := $(BINARY32)
else
$(error "Build on $(UNAME_M) is not supported, yet.")
endif
release: test build
all: fzf/$(BINARY)
release: test fzf/$(BINARY32) fzf/$(BINARY64)
-cd fzf && cp $(BINARY32) $(RELEASE32) && tar -czf $(RELEASE32).tgz $(RELEASE32)
cd fzf && cp $(BINARY64) $(RELEASE64) && tar -czf $(RELEASE64).tgz $(RELEASE64) && \
rm -f $(RELEASE32) $(RELEASE64)
build: fzf/$(BINARY32) fzf/$(BINARY64)
$(SRCDIR):
mkdir -p $(shell dirname $(SRCDIR))
ln -s $(ROOTDIR) $(SRCDIR)
@@ -44,7 +47,7 @@ deps: $(SRCDIR) $(SOURCES)
android-build: $(SRCDIR)
cd $(SRCDIR) && GOARCH=arm GOARM=7 CGO_ENABLED=1 go get
cd $(SRCDIR)/fzf && GOARCH=arm GOARM=7 CGO_ENABLED=1 go build -a -ldflags="-extldflags=-pie" -o $(BINARYARM7)
cd $(SRCDIR)/fzf && GOARCH=arm GOARM=7 CGO_ENABLED=1 go build -a -ldflags="-w -extldflags=-pie" -o $(BINARYARM7)
cd $(SRCDIR)/fzf && cp $(BINARYARM7) $(RELEASEARM7) && tar -czf $(RELEASEARM7).tgz $(RELEASEARM7) && \
rm -f $(RELEASEARM7)
@@ -54,20 +57,20 @@ test: deps
install: $(BINDIR)/fzf
uninstall:
rm -f $(BINDIR)/fzf $(BINDIR)/$(BINARY64)
rm -f $(BINDIR)/fzf $(BINDIR)/$(BINARY)
clean:
cd fzf && rm -f fzf-*
fzf/$(BINARY32): deps
cd fzf && GOARCH=386 CGO_ENABLED=1 go build -a -tags "$(TAGS)" -o $(BINARY32)
cd fzf && GOARCH=386 CGO_ENABLED=1 go build -a -ldflags -w -tags "$(TAGS)" -o $(BINARY32)
fzf/$(BINARY64): deps
cd fzf && go build -a -tags "$(TAGS)" -o $(BINARY64)
cd fzf && go build -a -ldflags -w -tags "$(TAGS)" -o $(BINARY64)
$(BINDIR)/fzf: fzf/$(BINARY64) | $(BINDIR)
cp -f fzf/$(BINARY64) $(BINDIR)
cd $(BINDIR) && ln -sf $(BINARY64) fzf
$(BINDIR)/fzf: fzf/$(BINARY) | $(BINDIR)
cp -f fzf/$(BINARY) $(BINDIR)
cd $(BINDIR) && ln -sf $(BINARY) fzf
$(BINDIR):
mkdir -p $@
@@ -98,7 +101,7 @@ centos: docker-centos
linux: docker-centos
docker run $(DOCKEROPTS) junegunn/centos-sandbox \
/bin/bash -ci 'cd /fzf/src; make TAGS=static'
/bin/bash -ci 'cd /fzf/src; make TAGS=static release'
ubuntu-android: docker-android
docker run $(DOCKEROPTS) junegunn/android-sandbox \
@@ -108,6 +111,6 @@ android: docker-android
docker run $(DOCKEROPTS) junegunn/android-sandbox \
/bin/bash -ci 'cd /fzf/src; GOOS=android make android-build'
.PHONY: all build deps release test install uninstall clean \
.PHONY: all deps release test install uninstall clean \
linux arch ubuntu centos docker-arch docker-ubuntu docker-centos \
android-build docker-android ubuntu-android android

View File

@@ -79,7 +79,7 @@ Build
```sh
# Build fzf executables and tarballs
make
make release
# Install the executable to ../bin directory
make install

View File

@@ -42,6 +42,70 @@ const (
charNumber
)
func evaluateBonus(caseSensitive bool, runes []rune, pattern []rune, sidx int, eidx int) int32 {
var bonus int32
pidx := 0
lenPattern := len(pattern)
consecutive := false
prevClass := charNonWord
for index := 0; index < eidx; index++ {
char := runes[index]
var class charClass
if unicode.IsLower(char) {
class = charLower
} else if unicode.IsUpper(char) {
class = charUpper
} else if unicode.IsLetter(char) {
class = charLetter
} else if unicode.IsNumber(char) {
class = charNumber
} else {
class = charNonWord
}
var point int32
if prevClass == charNonWord && class != charNonWord {
// Word boundary
point = 2
} else if prevClass == charLower && class == charUpper ||
prevClass != charNumber && class == charNumber {
// camelCase letter123
point = 1
}
prevClass = class
if index >= sidx {
if !caseSensitive {
if char >= 'A' && char <= 'Z' {
char += 32
} else if char > unicode.MaxASCII {
char = unicode.To(unicode.LowerCase, char)
}
}
pchar := pattern[pidx]
if pchar == char {
// Boost bonus for the first character in the pattern
if pidx == 0 {
point *= 2
}
// Bonus to consecutive matching chars
if consecutive {
point++
}
bonus += point
if pidx++; pidx == lenPattern {
break
}
consecutive = true
} else {
consecutive = false
}
}
}
return bonus
}
// FuzzyMatch performs fuzzy-match
func FuzzyMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune) Result {
if len(pattern) == 0 {
@@ -117,67 +181,8 @@ func FuzzyMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune)
sidx, eidx = lenRunes-eidx, lenRunes-sidx
}
var bonus int32
pidx := 0
consecutive := false
prevClass := charNonWord
for index := 0; index < eidx; index++ {
char := runes[index]
var class charClass
if unicode.IsLower(char) {
class = charLower
} else if unicode.IsUpper(char) {
class = charUpper
} else if unicode.IsLetter(char) {
class = charLetter
} else if unicode.IsNumber(char) {
class = charNumber
} else {
class = charNonWord
}
var point int32
if prevClass == charNonWord && class != charNonWord {
// Word boundary
point = 2
} else if prevClass == charLower && class == charUpper ||
prevClass != charNumber && class == charNumber {
// camelCase letter123
point = 1
}
prevClass = class
if index >= sidx {
if !caseSensitive {
if char >= 'A' && char <= 'Z' {
char += 32
} else if char > unicode.MaxASCII {
char = unicode.To(unicode.LowerCase, char)
}
}
pchar := pattern[pidx]
if pchar == char {
// Boost bonus for the first character in the pattern
if pidx == 0 {
point *= 2
}
// Bonus to consecutive matching chars
if consecutive {
point++
}
bonus += point
if pidx++; pidx == lenPattern {
break
}
consecutive = true
} else {
consecutive = false
}
}
}
return Result{int32(sidx), int32(eidx), bonus}
return Result{int32(sidx), int32(eidx),
evaluateBonus(caseSensitive, runes, pattern, sidx, eidx)}
}
return Result{-1, -1, 0}
}
@@ -190,7 +195,6 @@ func FuzzyMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune)
// We might try to implement better algorithms in the future:
// http://en.wikipedia.org/wiki/String_searching_algorithm
func ExactMatchNaive(caseSensitive bool, forward bool, runes []rune, pattern []rune) Result {
// Note: ExactMatchNaive always return a zero bonus.
if len(pattern) == 0 {
return Result{0, 0, 0}
}
@@ -216,10 +220,16 @@ func ExactMatchNaive(caseSensitive bool, forward bool, runes []rune, pattern []r
if pchar == char {
pidx++
if pidx == lenPattern {
var sidx, eidx int
if forward {
return Result{int32(index - lenPattern + 1), int32(index + 1), 0}
sidx = index - lenPattern + 1
eidx = index + 1
} else {
sidx = lenRunes - (index + 1)
eidx = lenRunes - (index - lenPattern + 1)
}
return Result{int32(lenRunes - (index + 1)), int32(lenRunes - (index - lenPattern + 1)), 0}
return Result{int32(sidx), int32(eidx),
evaluateBonus(caseSensitive, runes, pattern, sidx, eidx)}
}
} else {
index -= pidx
@@ -231,7 +241,6 @@ func ExactMatchNaive(caseSensitive bool, forward bool, runes []rune, pattern []r
// PrefixMatch performs prefix-match
func PrefixMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune) Result {
// Note: PrefixMatch always return a zero bonus.
if len(runes) < len(pattern) {
return Result{-1, -1, 0}
}
@@ -245,12 +254,13 @@ func PrefixMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune)
return Result{-1, -1, 0}
}
}
return Result{0, int32(len(pattern)), 0}
lenPattern := len(pattern)
return Result{0, int32(lenPattern),
evaluateBonus(caseSensitive, runes, pattern, 0, lenPattern)}
}
// SuffixMatch performs suffix-match
func SuffixMatch(caseSensitive bool, forward bool, input []rune, pattern []rune) Result {
// Note: SuffixMatch always return a zero bonus.
runes := util.TrimRight(input)
trimmedLen := len(runes)
diff := trimmedLen - len(pattern)
@@ -267,7 +277,11 @@ func SuffixMatch(caseSensitive bool, forward bool, input []rune, pattern []rune)
return Result{-1, -1, 0}
}
}
return Result{int32(trimmedLen - len(pattern)), int32(trimmedLen), 0}
lenPattern := len(pattern)
sidx := trimmedLen - lenPattern
eidx := trimmedLen
return Result{int32(sidx), int32(eidx),
evaluateBonus(caseSensitive, runes, pattern, sidx, eidx)}
}
// EqualMatch performs equal-match

View File

@@ -51,29 +51,36 @@ func TestFuzzyMatchBackward(t *testing.T) {
func TestExactMatchNaive(t *testing.T) {
for _, dir := range []bool{true, false} {
assertMatch(t, ExactMatchNaive, false, dir, "fooBarbaz", "oBA", 2, 5, 0)
assertMatch(t, ExactMatchNaive, false, dir, "fooBarbaz", "oBA", 2, 5, 3)
assertMatch(t, ExactMatchNaive, true, dir, "fooBarbaz", "oBA", -1, -1, 0)
assertMatch(t, ExactMatchNaive, true, dir, "fooBarbaz", "fooBarbazz", -1, -1, 0)
assertMatch(t, ExactMatchNaive, false, dir, "/AutomatorDocument.icns", "rdoc", 9, 13, 4)
assertMatch(t, ExactMatchNaive, false, dir, "/man1/zshcompctl.1", "zshc", 6, 10, 7)
assertMatch(t, ExactMatchNaive, false, dir, "/.oh-my-zsh/cache", "zsh/c", 8, 13, 10)
}
}
func TestExactMatchNaiveBackward(t *testing.T) {
assertMatch(t, ExactMatchNaive, false, true, "foobar foob", "oo", 1, 3, 0)
assertMatch(t, ExactMatchNaive, false, false, "foobar foob", "oo", 8, 10, 0)
assertMatch(t, ExactMatchNaive, false, true, "foobar foob", "oo", 1, 3, 1)
assertMatch(t, ExactMatchNaive, false, false, "foobar foob", "oo", 8, 10, 1)
}
func TestPrefixMatch(t *testing.T) {
for _, dir := range []bool{true, false} {
assertMatch(t, PrefixMatch, false, dir, "fooBarbaz", "Foo", 0, 3, 0)
assertMatch(t, PrefixMatch, true, dir, "fooBarbaz", "Foo", -1, -1, 0)
assertMatch(t, PrefixMatch, false, dir, "fooBarbaz", "baz", -1, -1, 0)
assertMatch(t, PrefixMatch, false, dir, "fooBarBaz", "baz", -1, -1, 0)
assertMatch(t, PrefixMatch, false, dir, "fooBarbaz", "Foo", 0, 3, 6)
assertMatch(t, PrefixMatch, false, dir, "foOBarBaZ", "foo", 0, 3, 7)
assertMatch(t, PrefixMatch, false, dir, "f-oBarbaz", "f-o", 0, 3, 8)
}
}
func TestSuffixMatch(t *testing.T) {
for _, dir := range []bool{true, false} {
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz", "Foo", -1, -1, 0)
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz", "baz", 6, 9, 0)
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz", "baz", 6, 9, 2)
assertMatch(t, SuffixMatch, false, dir, "fooBarBaZ", "baz", 6, 9, 5)
assertMatch(t, SuffixMatch, true, dir, "fooBarbaz", "Baz", -1, -1, 0)
}
}

View File

@@ -8,7 +8,7 @@ import (
const (
// Current version
version = "0.12.0"
version = "0.12.2"
// Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond
@@ -36,6 +36,9 @@ const (
// History
defaultHistoryMax int = 1000
// Jump labels
defaultJumpLabels string = "asdfghjklqwertyuiopzxcvbnm1234567890ASDFGHJKLQWERTYUIOPZXCVBNM`~;:,<.>/?'\"!@#$%^&*()[{]}-_=+"
)
// fzf events

View File

@@ -80,7 +80,16 @@ const (
F2
F3
F4
F5
F6
F7
F8
F9
F10
AltEnter
AltSpace
AltSlash
AltBS
AltA
AltB
@@ -109,6 +118,8 @@ const (
const (
doubleClickDuration = 500 * time.Millisecond
colDefault = -1
colUndefined = -2
)
type ColorTheme struct {
@@ -159,6 +170,23 @@ var (
DarkBG int
)
func EmptyTheme() *ColorTheme {
return &ColorTheme{
UseDefault: true,
Fg: colUndefined,
Bg: colUndefined,
DarkBg: colUndefined,
Prompt: colUndefined,
Match: colUndefined,
Current: colUndefined,
CurrentMatch: colUndefined,
Spinner: colUndefined,
Info: colUndefined,
Cursor: colUndefined,
Selected: colUndefined,
Header: colUndefined}
}
func init() {
_prevDownTime = time.Unix(0, 0)
_clickY = []int{}
@@ -280,44 +308,58 @@ func Init(theme *ColorTheme, black bool, mouse bool) {
if theme != nil {
C.start_color()
initPairs(theme, black)
var baseTheme *ColorTheme
if C.tigetnum(C.CString("colors")) >= 256 {
baseTheme = Dark256
} else {
baseTheme = Default16
}
initPairs(baseTheme, theme, black)
_color = attrColored
} else {
_color = attrMono
}
}
func initPairs(theme *ColorTheme, black bool) {
fg := C.short(theme.Fg)
bg := C.short(theme.Bg)
func override(a int16, b int16) C.short {
if b == colUndefined {
return C.short(a)
}
return C.short(b)
}
func initPairs(baseTheme *ColorTheme, theme *ColorTheme, black bool) {
fg := override(baseTheme.Fg, theme.Fg)
bg := override(baseTheme.Bg, theme.Bg)
if black {
bg = C.COLOR_BLACK
} else if theme.UseDefault {
fg = -1
bg = -1
fg = colDefault
bg = colDefault
C.use_default_colors()
}
if theme.UseDefault {
FG = -1
BG = -1
FG = colDefault
BG = colDefault
} else {
FG = int(fg)
BG = int(bg)
C.assume_default_colors(C.int(theme.Fg), C.int(bg))
C.assume_default_colors(C.int(override(baseTheme.Fg, theme.Fg)), C.int(bg))
}
CurrentFG = int(theme.Current)
DarkBG = int(theme.DarkBg)
darkBG := C.short(DarkBG)
C.init_pair(ColPrompt, C.short(theme.Prompt), bg)
C.init_pair(ColMatch, C.short(theme.Match), bg)
C.init_pair(ColCurrent, C.short(theme.Current), darkBG)
C.init_pair(ColCurrentMatch, C.short(theme.CurrentMatch), darkBG)
C.init_pair(ColSpinner, C.short(theme.Spinner), bg)
C.init_pair(ColInfo, C.short(theme.Info), bg)
C.init_pair(ColCursor, C.short(theme.Cursor), darkBG)
C.init_pair(ColSelected, C.short(theme.Selected), darkBG)
C.init_pair(ColHeader, C.short(theme.Header), bg)
currentFG := override(baseTheme.Current, theme.Current)
darkBG := override(baseTheme.DarkBg, theme.DarkBg)
CurrentFG = int(currentFG)
DarkBG = int(darkBG)
C.init_pair(ColPrompt, override(baseTheme.Prompt, theme.Prompt), bg)
C.init_pair(ColMatch, override(baseTheme.Match, theme.Match), bg)
C.init_pair(ColCurrent, currentFG, darkBG)
C.init_pair(ColCurrentMatch, override(baseTheme.CurrentMatch, theme.CurrentMatch), darkBG)
C.init_pair(ColSpinner, override(baseTheme.Spinner, theme.Spinner), bg)
C.init_pair(ColInfo, override(baseTheme.Info, theme.Info), bg)
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)
}
func Close() {
@@ -384,6 +426,12 @@ func escSequence(sz *int) Event {
}
*sz = 2
switch _buf[1] {
case 13:
return Event{AltEnter, 0, nil}
case 32:
return Event{AltSpace, 0, nil}
case 47:
return Event{AltSlash, 0, nil}
case 98:
return Event{AltB, 0, nil}
case 100:
@@ -429,6 +477,15 @@ func escSequence(sz *int) Event {
*sz = 4
switch _buf[2] {
case 50:
if len(_buf) == 5 && _buf[4] == 126 {
*sz = 5
switch _buf[3] {
case 48:
return Event{F9, 0, nil}
case 49:
return Event{F10, 0, nil}
}
}
return Event{Invalid, 0, nil} // INS
case 51:
return Event{Del, 0, nil}
@@ -442,6 +499,21 @@ func escSequence(sz *int) Event {
switch _buf[3] {
case 126:
return Event{Home, 0, nil}
case 53, 55, 56, 57:
if len(_buf) == 5 && _buf[4] == 126 {
*sz = 5
switch _buf[3] {
case 53:
return Event{F5, 0, nil}
case 55:
return Event{F6, 0, nil}
case 56:
return Event{F7, 0, nil}
case 57:
return Event{F8, 0, nil}
}
}
return Event{Invalid, 0, nil}
case 59:
if len(_buf) != 6 {
return Event{Invalid, 0, nil}

View File

@@ -45,6 +45,7 @@ const usage = `usage: fzf [options]
--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
--prompt=STR Input prompt (default: '> ')
--bind=KEYBINDS Custom key bindings. Refer to the man page.
--history=FILE History file
@@ -112,6 +113,7 @@ type Options struct {
Hscroll bool
HscrollOff int
InlineInfo bool
JumpLabels string
Prompt string
Query string
Select1 bool
@@ -132,13 +134,6 @@ type Options struct {
Version bool
}
func defaultTheme() *curses.ColorTheme {
if strings.Contains(os.Getenv("TERM"), "256") {
return curses.Dark256
}
return curses.Default16
}
func defaultOptions() *Options {
return &Options{
Fuzzy: true,
@@ -153,13 +148,14 @@ func defaultOptions() *Options {
Multi: false,
Ansi: false,
Mouse: true,
Theme: defaultTheme(),
Theme: curses.EmptyTheme(),
Black: false,
Reverse: false,
Cycle: false,
Hscroll: true,
HscrollOff: 10,
InlineInfo: false,
JumpLabels: defaultJumpLabels,
Prompt: "> ",
Query: "",
Select1: false,
@@ -322,6 +318,12 @@ func parseKeyChords(str string, message string) map[int]string {
chord = curses.AltZ + int(' ')
case "bspace", "bs":
chord = curses.BSpace
case "alt-enter", "alt-return":
chord = curses.AltEnter
case "alt-space":
chord = curses.AltSpace
case "alt-/":
chord = curses.AltSlash
case "alt-bs", "alt-bspace":
chord = curses.AltBS
case "tab":
@@ -346,12 +348,14 @@ func parseKeyChords(str string, message string) map[int]string {
chord = curses.SRight
case "double-click":
chord = curses.DoubleClick
case "f10":
chord = curses.F10
default:
if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
chord = curses.CtrlA + int(lkey[5]) - 'a'
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) {
chord = curses.AltA + int(lkey[4]) - 'a'
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '4' {
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
chord = curses.F1 + int(key[1]) - '1'
} else if utf8.RuneCountInString(key) == 1 {
chord = curses.AltZ + int([]rune(key)[0])
@@ -534,6 +538,8 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, str string)
keymap[key] = actAbort
case "accept":
keymap[key] = actAccept
case "print-query":
keymap[key] = actPrintQuery
case "backward-char":
keymap[key] = actBackwardChar
case "backward-delete-char":
@@ -554,6 +560,10 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, str string)
keymap[key] = actForwardChar
case "forward-word":
keymap[key] = actForwardWord
case "jump":
keymap[key] = actJump
case "jump-accept":
keymap[key] = actJumpAccept
case "kill-line":
keymap[key] = actKillLine
case "kill-word":
@@ -714,6 +724,7 @@ func parseOptions(opts *Options, allArgs []string) {
opts.History.maxSize = historyMax
}
}
validateJumpLabels := false
for i := 0; i < len(allArgs); i++ {
arg := allArgs[i]
switch arg {
@@ -745,7 +756,7 @@ func parseOptions(opts *Options, allArgs []string) {
case "--color":
spec := optionalNextString(allArgs, &i)
if len(spec) == 0 {
opts.Theme = defaultTheme()
opts.Theme = curses.EmptyTheme()
} else {
opts.Theme = parseTheme(opts.Theme, spec)
}
@@ -805,6 +816,9 @@ func parseOptions(opts *Options, allArgs []string) {
opts.InlineInfo = true
case "--no-inline-info":
opts.InlineInfo = false
case "--jump-labels":
opts.JumpLabels = nextString(allArgs, &i, "label characters required")
validateJumpLabels = true
case "-1", "--select-1":
opts.Select1 = true
case "+1", "--no-select-1":
@@ -892,6 +906,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Tabstop = atoi(value)
} else if match, value := optString(arg, "--hscroll-off="); match {
opts.HscrollOff = atoi(value)
} else if match, value := optString(arg, "--jump-labels="); match {
opts.JumpLabels = value
} else {
errorExit("unknown option: " + arg)
}
@@ -909,6 +925,18 @@ func parseOptions(opts *Options, allArgs []string) {
if opts.Tabstop < 1 {
errorExit("tab stop must be a positive integer")
}
if len(opts.JumpLabels) == 0 {
errorExit("empty jump labels")
}
if validateJumpLabels {
for _, r := range opts.JumpLabels {
if r < 32 || r > 126 {
errorExit("non-ascii jump labels are not allowed")
}
}
}
}
func postProcessOptions(opts *Options) {

View File

@@ -123,14 +123,14 @@ func TestIrrelevantNth(t *testing.T) {
}
func TestParseKeys(t *testing.T) {
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g", "")
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ALT-enter,alt-SPACE", "")
check := func(i int, s string) {
if pairs[i] != s {
t.Errorf("%s != %s", pairs[i], s)
}
}
if len(pairs) != 9 {
t.Error(9)
if len(pairs) != 11 {
t.Error(11)
}
check(curses.CtrlZ, "ctrl-z")
check(curses.AltZ, "alt-z")
@@ -141,6 +141,8 @@ func TestParseKeys(t *testing.T) {
check(curses.CtrlA+'g'-'a', "ctrl-G")
check(curses.AltZ+'J', "J")
check(curses.AltZ+'g', "g")
check(curses.AltEnter, "ALT-enter")
check(curses.AltSpace, "alt-SPACE")
// Synonyms
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")

View File

@@ -227,7 +227,7 @@ func (p *Pattern) CacheKey() string {
}
cacheableTerms := []string{}
for _, termSet := range p.termSets {
if len(termSet) == 1 && !termSet[0].inv {
if len(termSet) == 1 && !termSet[0].inv && (p.fuzzy || termSet[0].typ == termExact) {
cacheableTerms = append(cacheableTerms, string(termSet[0].origText))
}
}

View File

@@ -19,6 +19,14 @@ import (
"github.com/junegunn/go-runewidth"
)
type jumpMode int
const (
jumpDisabled jumpMode = iota
jumpEnabled
jumpAcceptEnabled
)
// Terminal represents terminal input/output
type Terminal struct {
initDelay time.Duration
@@ -50,6 +58,8 @@ type Terminal struct {
count int
progress int
reading bool
jumping jumpMode
jumpLabels string
merger *Merger
selected map[int32]selectedItem
reqBox *util.EventBox
@@ -88,9 +98,11 @@ const (
reqInfo
reqHeader
reqList
reqJump
reqRefresh
reqRedraw
reqClose
reqPrintQuery
reqQuit
)
@@ -132,6 +144,9 @@ const (
actUp
actPageUp
actPageDown
actJump
actJumpAccept
actPrintQuery
actToggleSort
actPreviousHistory
actNextHistory
@@ -233,6 +248,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
header0: header,
ansi: opts.Ansi,
reading: true,
jumping: jumpDisabled,
jumpLabels: opts.JumpLabels,
merger: EmptyMerger,
selected: make(map[int32]selectedItem),
reqBox: util.NewEventBox(),
@@ -495,15 +512,25 @@ func (t *Terminal) printList() {
}
t.move(line, 0, true)
if i < count {
t.printItem(t.merger.Get(i+t.offset), i == t.cy-t.offset)
t.printItem(t.merger.Get(i+t.offset), i, i == t.cy-t.offset)
}
}
}
func (t *Terminal) printItem(item *Item, current bool) {
func (t *Terminal) printItem(item *Item, i int, current bool) {
_, selected := t.selected[item.Index()]
label := " "
if t.jumping != jumpDisabled {
if i < len(t.jumpLabels) {
// Striped
current = i%2 == 0
label = t.jumpLabels[i : i+1]
}
} else if current {
label = ">"
}
C.CPrint(C.ColCursor, true, label)
if current {
C.CPrint(C.ColCursor, true, ">")
if selected {
C.CPrint(C.ColSelected, true, ">")
} else {
@@ -511,7 +538,6 @@ func (t *Terminal) printItem(item *Item, current bool) {
}
t.printHighlighted(item, true, C.ColCurrent, C.ColCurrentMatch, true)
} else {
C.CPrint(C.ColCursor, true, " ")
if selected {
C.CPrint(C.ColSelected, true, ">")
} else {
@@ -804,6 +830,11 @@ func (t *Terminal) Loop() {
t.printInfo()
case reqList:
t.printList()
case reqJump:
if t.merger.Length() == 0 {
t.jumping = jumpDisabled
}
t.printList()
case reqHeader:
t.printHeader()
case reqRefresh:
@@ -819,6 +850,10 @@ func (t *Terminal) Loop() {
exit(exitOk)
}
exit(exitNoMatch)
case reqPrintQuery:
C.Close()
fmt.Println(string(t.input))
exit(exitOk)
case reqQuit:
C.Close()
exit(exitInterrupt)
@@ -906,6 +941,8 @@ func (t *Terminal) Loop() {
if t.cx > 0 {
t.cx--
}
case actPrintQuery:
req(reqPrintQuery)
case actAbort:
req(reqQuit)
case actDeleteChar:
@@ -1017,6 +1054,12 @@ func (t *Terminal) Loop() {
case actPageDown:
t.vmove(-(t.maxItems() - 1))
req(reqList)
case actJump:
t.jumping = jumpEnabled
req(reqJump)
case actJumpAccept:
t.jumping = jumpAcceptEnabled
req(reqJump)
case actBackwardWord:
t.cx = findLastMatch("[^[:alnum:]][[:alnum:]]", string(t.input[:t.cx])) + 1
case actForwardWord:
@@ -1096,9 +1139,11 @@ func (t *Terminal) Loop() {
}
return true
}
action := t.keymap[event.Type]
changed := false
mapkey := event.Type
if event.Type == C.Rune {
if t.jumping == jumpDisabled {
action := t.keymap[mapkey]
if mapkey == C.Rune {
mapkey = int(event.Char) + int(C.AltZ)
if act, prs := t.keymap[mapkey]; prs {
action = act
@@ -1107,7 +1152,19 @@ func (t *Terminal) Loop() {
if !doAction(action, mapkey) {
continue
}
changed := string(previousInput) != string(t.input)
changed = string(previousInput) != string(t.input)
} else {
if mapkey == C.Rune {
if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
t.cy = idx + t.offset
if t.jumping == jumpAcceptEnabled {
req(reqClose)
}
}
}
t.jumping = jumpDisabled
req(reqList)
}
t.mutex.Unlock() // Must be unlocked before touching reqBox
if changed {

View File

@@ -31,7 +31,7 @@ def wait
return if yield
sleep 0.05
end
throw 'timeout'
raise 'timeout'
end
class Shell
@@ -430,6 +430,10 @@ class TestGoFZF < TestBase
test.call 'f3', 'f3'
test.call 'f2,f4', 'f2', 'f2'
test.call 'f2,f4', 'f4', 'f4'
test.call 'alt-/', [:Escape, :/]
%w[f5 f6 f7 f8 f9 f10].each do |key|
test.call 'f5,f6,f7,f8,f9,f10', key, key
end
test.call '@', '@'
end
@@ -771,6 +775,13 @@ class TestGoFZF < TestBase
assert_equal %w[4 5 6 9], readonce.split($/)
end
def test_bind_print_query
tmux.send_keys "seq 1 1000 | #{fzf '-m --bind=ctrl-j:print-query'}", :Enter
tmux.until { |lines| lines[-2].end_with? '/1000' }
tmux.send_keys 'print-my-query', 'C-j'
assert_equal %w[print-my-query], readonce.split($/)
end
def test_long_line
data = '.' * 256 * 1024
File.open(tempname, 'w') do |f|
@@ -859,16 +870,27 @@ 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}\\"]
tmux.send_keys "seq 100 | #{fzf opts}", :Enter
tmux.until { |lines| lines[-2].include? '100/100' }
tmux.send_keys :Escape, :a, :Escape, :a
wait = lambda { |exp| tmux.until { |lines| lines[-2].include? exp } }
tmux.send_keys "seq 100 | #{fzf opts}; sync", :Enter
wait['100/100']
tmux.send_keys :Escape, :a
wait['/100']
tmux.send_keys :Escape, :a
wait['/100']
tmux.send_keys :Up
tmux.send_keys :Escape, :b, :Escape, :b
tmux.send_keys :Escape, :b
wait['/100']
tmux.send_keys :Escape, :b
wait['/100']
tmux.send_keys :Up
tmux.send_keys :C
wait['100/100']
tmux.send_keys 'foobar'
tmux.until { |lines| lines[-2].include? '0/100' }
tmux.send_keys :Escape, :a, :Escape, :b, :Escape, :c
wait['0/100']
tmux.send_keys :Escape, :a
wait['/100']
tmux.send_keys :Escape, :b
wait['/100']
tmux.send_keys :Enter
readonce
assert_equal ['["1"]', '["1"]', '("2"), ("2")', '("2"), ("2")', '("3"), ["3"], @"3"@'],
@@ -1026,7 +1048,7 @@ class TestGoFZF < TestBase
end
end
def test_canel
def test_cancel
tmux.send_keys "seq 10 | #{fzf "--bind 2:cancel"}", :Enter
tmux.until { |lines| lines[-2].include?('10/10') }
tmux.send_keys '123'
@@ -1117,13 +1139,9 @@ class TestGoFZF < TestBase
def test_exitstatus_empty
{ '99' => '0', '999' => '1' }.each do |query, status|
tmux.send_keys "seq 100 | #{FZF} -q #{query}", :Enter
tmux.send_keys "seq 100 | #{FZF} -q #{query}; echo --\\$?--", :Enter
tmux.until { |lines| lines[-2] =~ %r{ [10]/100} }
tmux.send_keys :Enter
tmux.send_keys 'echo --\$?--'
tmux.until { |lines| lines.last.include? "echo --$?--" }
tmux.send_keys :Enter
tmux.until { |lines| lines.last.include? "--#{status}--" }
end
end
@@ -1157,6 +1175,53 @@ class TestGoFZF < TestBase
end
end
def test_partial_caching
tmux.send_keys 'seq 1000 | fzf -e', :Enter
tmux.until { |lines| lines[-2] == ' 1000/1000' }
tmux.send_keys 11
tmux.until { |lines| lines[-2] == ' 19/1000' }
tmux.send_keys 'C-a', "'"
tmux.until { |lines| lines[-2] == ' 28/1000' }
tmux.send_keys :Enter
end
def test_jump
tmux.send_keys "seq 1000 | #{fzf "--multi --jump-labels 12345 --bind 'ctrl-j:jump'"}", :Enter
tmux.until { |lines| lines[-2] == ' 1000/1000' }
tmux.send_keys 'C-j'
tmux.until { |lines| lines[-7] == '5 5' }
tmux.until { |lines| lines[-8] == ' 6' }
tmux.send_keys '5'
tmux.until { |lines| lines[-7] == '> 5' }
tmux.send_keys :Tab
tmux.until { |lines| lines[-7] == ' >5' }
tmux.send_keys 'C-j'
tmux.until { |lines| lines[-7] == '5>5' }
tmux.send_keys '2'
tmux.until { |lines| lines[-4] == '> 2' }
tmux.send_keys :Tab
tmux.until { |lines| lines[-4] == ' >2' }
tmux.send_keys 'C-j'
tmux.until { |lines| lines[-7] == '5>5' }
# Press any key other than jump labels to cancel jump
tmux.send_keys '6'
tmux.until { |lines| lines[-3] == '> 1' }
tmux.send_keys :Tab
tmux.until { |lines| lines[-3] == '>>1' }
tmux.send_keys :Enter
assert_equal %w[5 2 1], readonce.split($/)
end
def test_jump_accept
tmux.send_keys "seq 1000 | #{fzf "--multi --jump-labels 12345 --bind 'ctrl-j:jump-accept'"}", :Enter
tmux.until { |lines| lines[-2] == ' 1000/1000' }
tmux.send_keys 'C-j'
tmux.until { |lines| lines[-7] == '5 5' }
tmux.send_keys '3'
assert_equal '3', readonce.chomp
end
private
def writelines path, lines
File.unlink path while File.exists? path
@@ -1212,6 +1277,22 @@ module TestShell
tmux.until(0) { |lines| lines[-1].include? '1 2 3' }
end
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.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.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
tmux.until { |lines| lines[-1].include? 'test1test2' }
end
def test_alt_c
tmux.prepare
tmux.send_keys :Escape, :c, pane: 0
@@ -1412,6 +1493,20 @@ module CompletionTest
tmux.send_keys :Enter
tmux.until { |lines| lines[-1] == 'unset FOO' }
end
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.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.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
tmux.until { |lines| lines[-1].include? 'test3test4' }
end
end
class TestBash < TestBase