Compare commits

...

18 Commits

Author SHA1 Message Date
Junegunn Choi
b3010a4624 0.10.8 2015-10-09 12:42:07 +09:00
Junegunn Choi
7d53051ec8 Merge pull request #371 from wilywampa/edit_directory
Trigger netrw autocommand when opening directory
2015-10-09 12:36:08 +09:00
Jacob Niehus
ed893c5f47 Trigger netrw autocommand when opening directory 2015-10-08 20:28:07 -07:00
Junegunn Choi
a4eb3323da Fix #370 - Panic when trying to set colors when colors are disabled 2015-10-09 12:16:47 +09:00
Junegunn Choi
1da065e50e 0.10.7 2015-10-05 23:28:24 +09:00
Junegunn Choi
86bc9d506f Fix invalid interrupt handler during execute action
Interrupt handling during execute action was not serialized and often
caused crash, failed to restore the terminal state.
2015-10-05 23:19:26 +09:00
Junegunn Choi
eee45a9578 [completion] Revamp completion API
* _fzf_complete is the helper function for custom completion
    * _fzf_complete FZF_OPTS ARGS
    * Reads the output of the source command instead of the command string
    * In zsh, you can use pipe to feed the data into the function, but
      it's not possible in bash as by doing so COMPREPLY is set from the
      subshell and thus nullified
* Change the naming convention for consistency:
    * _fzf_complete_COMMAND

e.g.

  # pass completion suggested by @d4ndo (#362)
  _fzf_complete_pass() {
    _fzf_complete '+m' "$@" < <(
      local pwdir=${PASSWORD_STORE_DIR-~/.password-store/}
      local stringsize="${#pwdir}"
      find "$pwdir" -name "*.gpg" -print |
          cut -c "$((stringsize + 1))"-  |
          sed -e 's/\(.*\)\.gpg/\1/'
    )
  }

  # Only in bash
  complete -F _fzf_complete_pass -o default -o bashdefault pass
2015-10-05 19:34:38 +09:00
Junegunn Choi
659f49a09a [fzf-tmux] Create temp files in $TMPDIR if defined 2015-10-05 13:01:09 +09:00
Junegunn Choi
8fa9e85980 [zsh-completion] Allow custom completion function
While in bash you can externally register custom completion functions
using `complete` command, it was not possible to do so in zsh without
changing completion.zsh as the name of the supported commands are
hard-coded within the code (See #362). With this commit, fzf-completion
of zsh will first look if `_fzf_COMMAND_completion` exists and calls the
function, so one can externally define completion functions for specific
commands.

This commit also tries to make the interface of (yet undocumented)
_fzf_list_completion helper function consistent across bash and zsh.

So the following code works both on bash and zsh.

    _fzf_pass_completion() {
      local pwdir=${PASSWORD_STORE_DIR-~/.password-store/}
      local stringsize="${#pwdir}"
      let "stringsize+=1"
      _fzf_list_completion '+m' "$@" << "EOF"
        find "$pwdir" -name "*.gpg" -print | cut -c "$stringsize"- | sed -e 's/\(.*\)\.gpg/\1/'
    EOF
    }

    # Only on bash
    complete -F _fzf_pass_completion -o default -o bashdefault pass

Note that the suggested convention and the interface are not yet final
and subject to change.

/cc @d4ndo
2015-10-05 01:48:45 +09:00
Junegunn Choi
92a75c9563 Use trimmed length when --nth is used with --tiebreak=length
This change improves sort ordering for aligned tabular input.
Given the following input:

    apple   juice   100
    apple   pie     200

fzf --nth=2 will now prefer the one with pie. Before this change fzf
compared "juice   " and "pie     ", both of which have the same length.
2015-10-02 18:40:20 +09:00
Junegunn Choi
7c7a30c472 Merge pull request #364 from halostatue/use-zsh-regex-module
Remove dependency on zsh/pcre module
2015-10-02 11:02:19 +09:00
Austin Ziegler
ea271cd4e2 Remove dependency on zsh/pcre module
Fixes #363.
2015-10-01 15:18:10 -04:00
Junegunn Choi
6a38d07a4c Merge pull request #361 from justinmk/swapexists
[vim] handle SwapExists
2015-09-30 16:16:18 +09:00
Justin M. Keyes
c4e5ee63bb [vim] handle SwapExists
The SwapExists dialog prevents multiple files from being opening if the
dialog occurs before all files are opened. Opening the files is more
important than showing the dialog, so choose "readonly" automatically
and continue opening files.
2015-09-30 02:48:12 -04:00
Junegunn Choi
862da2c0b1 [vim] Consistent exit status handling 2015-09-27 16:26:40 +09:00
Junegunn Choi
545370d2b3 Merge branch 'jebaum-master' 2015-09-27 15:59:04 +09:00
James Baumgarten
59220c63a6 [vim] handle exit status 1 properly (#359) 2015-09-26 16:56:52 -07:00
Junegunn Choi
86306dd45a [vim] Display proper error message when GVim launcher failed
Related: https://github.com/junegunn/fzf.vim/issues/16
2015-09-26 21:04:44 +09:00
20 changed files with 302 additions and 142 deletions

View File

@@ -1,6 +1,19 @@
CHANGELOG CHANGELOG
========= =========
0.10.8
------
- Fixed panic when trying to set colors after colors are disabled (#370)
0.10.7
------
- Fixed unserialized interrupt handling during execute action which often
caused invalid memory access and crash
- Changed `--tiebreak=length` (default) to use trimmed length when `--nth` is
used
0.10.6 0.10.6
------ ------

View File

@@ -91,10 +91,10 @@ set -e
# Clean up named pipes on exit # Clean up named pipes on exit
id=$RANDOM id=$RANDOM
argsf=/tmp/fzf-args-$id argsf="${TMPDIR:-/tmp}/fzf-args-$id"
fifo1=/tmp/fzf-fifo1-$id fifo1="${TMPDIR:-/tmp}/fzf-fifo1-$id"
fifo2=/tmp/fzf-fifo2-$id fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id"
fifo3=/tmp/fzf-fifo3-$id fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
cleanup() { cleanup() {
rm -f $argsf $fifo1 $fifo2 $fifo3 rm -f $argsf $fifo1 $fifo2 $fifo3
} }

10
install
View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
[[ "$@" =~ --pre ]] && version=0.10.6 pre=1 || [[ "$@" =~ --pre ]] && version=0.10.8 pre=1 ||
version=0.10.6 pre=0 version=0.10.8 pre=0
cd $(dirname $BASH_SOURCE) cd $(dirname $BASH_SOURCE)
fzf_base=$(pwd) fzf_base=$(pwd)
@@ -185,7 +185,7 @@ for shell in bash zsh; do
echo -n "Generate ~/.fzf.$shell ... " echo -n "Generate ~/.fzf.$shell ... "
src=~/.fzf.${shell} src=~/.fzf.${shell}
fzf_completion="[[ \$- =~ i ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null" fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null"
if [ $auto_completion -ne 0 ]; then if [ $auto_completion -ne 0 ]; then
fzf_completion="# $fzf_completion" fzf_completion="# $fzf_completion"
fi fi
@@ -198,13 +198,13 @@ for shell in bash zsh; do
cat > $src << EOF cat > $src << EOF
# Setup fzf # Setup fzf
# --------- # ---------
if [[ ! "\$PATH" =~ "$fzf_base/bin" ]]; then if [[ ! "\$PATH" == *$fzf_base/bin* ]]; then
export PATH="\$PATH:$fzf_base/bin" export PATH="\$PATH:$fzf_base/bin"
fi fi
# Man path # Man path
# -------- # --------
if [[ ! "\$MANPATH" =~ "$fzf_base/man" && -d "$fzf_base/man" ]]; then if [[ ! "\$MANPATH" == *$fzf_base/man* && -d "$fzf_base/man" ]]; then
export MANPATH="\$MANPATH:$fzf_base/man" export MANPATH="\$MANPATH:$fzf_base/man"
fi fi

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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Sep 2015" "fzf 0.10.6" "fzf - a command-line fuzzy finder" .TH fzf 1 "Oct 2015" "fzf 0.10.8" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder

View File

@@ -213,7 +213,7 @@ endfunction
function! s:xterm_launcher() function! s:xterm_launcher()
let fmt = 'xterm -T "[fzf]" -bg "\%s" -fg "\%s" -geometry %dx%d+%d+%d -e bash -ic %%s' let fmt = 'xterm -T "[fzf]" -bg "\%s" -fg "\%s" -geometry %dx%d+%d+%d -e bash -ic %%s'
if has('gui_macvim') if has('gui_macvim')
let fmt .= '; osascript -e "tell application \"MacVim\" to activate"' let fmt .= '&& osascript -e "tell application \"MacVim\" to activate"'
endif endif
return printf(fmt, return printf(fmt,
\ synIDattr(hlID("Normal"), "bg"), synIDattr(hlID("Normal"), "fg"), \ synIDattr(hlID("Normal"), "bg"), synIDattr(hlID("Normal"), "fg"),
@@ -222,6 +222,19 @@ endfunction
unlet! s:launcher unlet! s:launcher
let s:launcher = function('s:xterm_launcher') let s:launcher = function('s:xterm_launcher')
function! s:exit_handler(code, command, ...)
if a:code == 130
return 0
elseif a:code > 1
call s:error('Error running ' . a:command)
if !empty(a:000)
sleep
endif
return 0
endif
return 1
endfunction
function! s:execute(dict, command, temps) function! s:execute(dict, command, temps)
call s:pushd(a:dict) call s:pushd(a:dict)
silent! !clear 2> /dev/null silent! !clear 2> /dev/null
@@ -235,15 +248,7 @@ function! s:execute(dict, command, temps)
endif endif
execute 'silent !'.command execute 'silent !'.command
redraw! redraw!
if v:shell_error return s:exit_handler(v:shell_error, command) ? s:callback(a:dict, a:temps) : []
" Do not print error message on exit status 1 (no match) or 130 (interrupt)
if v:shell_error == 2
call s:error('Error running ' . command)
endif
return []
else
return s:callback(a:dict, a:temps)
endif
endfunction endfunction
function! s:execute_tmux(dict, command, temps) function! s:execute_tmux(dict, command, temps)
@@ -255,11 +260,7 @@ function! s:execute_tmux(dict, command, temps)
call system(command) call system(command)
redraw! redraw!
if v:shell_error == 2 return s:exit_handler(v:shell_error, command) ? s:callback(a:dict, a:temps) : []
call s:error('Error running ' . command)
return []
endif
return s:callback(a:dict, a:temps)
endfunction endfunction
function! s:calc_size(max, val, dict) function! s:calc_size(max, val, dict)
@@ -335,9 +336,7 @@ function! s:execute_term(dict, command, temps)
endif endif
endif endif
if a:code == 2 if !s:exit_handler(a:code, s:command, 1)
call s:error('Error running ' . s:command)
sleep
return return
endif endif
@@ -406,14 +405,24 @@ function! s:cmd_callback(lines) abort
endif endif
let key = remove(a:lines, 0) let key = remove(a:lines, 0)
let cmd = get(s:action, key, 'e') let cmd = get(s:action, key, 'e')
if len(a:lines) > 1
augroup fzf_swap
autocmd SwapExists * let v:swapchoice='o'
\| call s:warn('fzf: E325: swap file exists: '.expand('<afile>'))
augroup END
endif
try try
let autochdir = &autochdir let autochdir = &autochdir
set noautochdir set noautochdir
for item in a:lines for item in a:lines
execute cmd s:escape(item) execute cmd s:escape(item)
if exists('#BufEnter') && isdirectory(item)
doautocmd BufEnter
endif
endfor endfor
finally finally
let &autochdir = autochdir let &autochdir = autochdir
silent! autocmd! fzf_swap
endtry endtry
endfunction endfunction

View File

@@ -94,7 +94,7 @@ _fzf_handle_dynamic_completion() {
fi fi
} }
_fzf_path_completion() { __fzf_generic_path_completion() {
local cur base dir leftover matches trigger cmd fzf local cur base dir leftover matches trigger cmd fzf
[ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf" [ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g') cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g')
@@ -135,20 +135,29 @@ _fzf_path_completion() {
fi fi
} }
_fzf_list_completion() { _fzf_feed_fifo() (
local cur selected trigger cmd src fzf rm -f "$fifo"
mkfifo "$fifo"
cat <&0 > "$fifo" &
)
_fzf_complete() {
local fifo cur selected trigger cmd fzf
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
[ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf" [ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
read -r src
cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g') cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g')
trigger=${FZF_COMPLETION_TRIGGER-'**'} trigger=${FZF_COMPLETION_TRIGGER-'**'}
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
if [[ ${cur} == *"$trigger" ]]; then if [[ ${cur} == *"$trigger" ]]; then
cur=${cur:0:${#cur}-${#trigger}} cur=${cur:0:${#cur}-${#trigger}}
_fzf_feed_fifo "$fifo"
tput sc tput sc
selected=$(eval "$src | $fzf $FZF_COMPLETION_OPTS $1 -q '$cur'" | tr '\n' ' ') selected=$(eval "cat '$fifo' | $fzf $FZF_COMPLETION_OPTS $1 -q '$cur'" | tr '\n' ' ')
selected=${selected% } selected=${selected% } # Strip trailing space not to repeat "-o nospace"
tput rc tput rc
rm -f "$fifo"
if [ -n "$selected" ]; then if [ -n "$selected" ]; then
COMPREPLY=("$selected") COMPREPLY=("$selected")
@@ -160,25 +169,25 @@ _fzf_list_completion() {
fi fi
} }
_fzf_all_completion() { _fzf_path_completion() {
_fzf_path_completion \ __fzf_generic_path_completion \
"-name .git -prune -o -name .svn -prune -o -type d -print -o -type f -print -o -type l -print" \ "-name .git -prune -o -name .svn -prune -o -type d -print -o -type f -print -o -type l -print" \
"-m" "" "$@" "-m" "" "$@"
} }
_fzf_file_completion() { _fzf_file_completion() {
_fzf_path_completion \ __fzf_generic_path_completion \
"-name .git -prune -o -name .svn -prune -o -type f -print -o -type l -print" \ "-name .git -prune -o -name .svn -prune -o -type f -print -o -type l -print" \
"-m" "" "$@" "-m" "" "$@"
} }
_fzf_dir_completion() { _fzf_dir_completion() {
_fzf_path_completion \ __fzf_generic_path_completion \
"-name .git -prune -o -name .svn -prune -o -type d -print" \ "-name .git -prune -o -name .svn -prune -o -type d -print" \
"" "/" "$@" "" "/" "$@"
} }
_fzf_kill_completion() { _fzf_complete_kill() {
[ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1 [ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1
local selected fzf local selected fzf
@@ -193,28 +202,37 @@ _fzf_kill_completion() {
fi fi
} }
_fzf_telnet_completion() { _fzf_complete_telnet() {
_fzf_list_completion '+m' "$@" << "EOF" _fzf_complete '+m' "$@" < <(
\grep -v '^\s*\(#\|$\)' /etc/hosts | \grep -Fv '0.0.0.0' | awk '{if (length($2) > 0) {print $2}}' | sort -u \grep -v '^\s*\(#\|$\)' /etc/hosts | \grep -Fv '0.0.0.0' |
EOF awk '{if (length($2) > 0) {print $2}}' | sort -u
)
} }
_fzf_ssh_completion() { _fzf_complete_ssh() {
_fzf_list_completion '+m' "$@" << "EOF" _fzf_complete '+m' "$@" < <(
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | \grep -i '^host' | \grep -v '*') <(\grep -v '^\s*\(#\|$\)' /etc/hosts | \grep -Fv '0.0.0.0') | awk '{if (length($2) > 0) {print $2}}' | sort -u cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | \grep -i '^host' | \grep -v '*') \
EOF <(\grep -v '^\s*\(#\|$\)' /etc/hosts | \grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
} }
_fzf_env_var_completion() { _fzf_complete_unset() {
_fzf_list_completion '-m' "$@" << "EOF" _fzf_complete '-m' "$@" < <(
declare -xp | sed 's/=.*//' | sed 's/.* //' declare -xp | sed 's/=.*//' | sed 's/.* //'
EOF )
} }
_fzf_alias_completion() { _fzf_complete_export() {
_fzf_list_completion '-m' "$@" << "EOF" _fzf_complete '-m' "$@" < <(
declare -xp | sed 's/=.*//' | sed 's/.* //'
)
}
_fzf_complete_unalias() {
_fzf_complete '-m' "$@" < <(
alias | sed 's/=.*//' | sed 's/.* //' alias | sed 's/=.*//' | sed 's/.* //'
EOF )
} }
# fzf options # fzf options
@@ -257,19 +275,19 @@ done
# Anything # Anything
for cmd in $a_cmds; do for cmd in $a_cmds; do
complete -F _fzf_all_completion -o default -o bashdefault $cmd complete -F _fzf_path_completion -o default -o bashdefault $cmd
done done
# Kill completion # Kill completion
complete -F _fzf_kill_completion -o nospace -o default -o bashdefault kill complete -F _fzf_complete_kill -o nospace -o default -o bashdefault kill
# Host completion # Host completion
complete -F _fzf_ssh_completion -o default -o bashdefault ssh complete -F _fzf_complete_ssh -o default -o bashdefault ssh
complete -F _fzf_telnet_completion -o default -o bashdefault telnet complete -F _fzf_complete_telnet -o default -o bashdefault telnet
# Environment variables / Aliases # Environment variables / Aliases
complete -F _fzf_env_var_completion -o default -o bashdefault unset complete -F _fzf_complete_unset -o default -o bashdefault unset
complete -F _fzf_env_var_completion -o default -o bashdefault export complete -F _fzf_complete_export -o default -o bashdefault export
complete -F _fzf_alias_completion -o default -o bashdefault unalias complete -F _fzf_complete_unalias -o default -o bashdefault unalias
unset cmd d_cmds f_cmds a_cmds x_cmds unset cmd d_cmds f_cmds a_cmds x_cmds

View File

@@ -10,8 +10,9 @@
# - $FZF_COMPLETION_TRIGGER (default: '**') # - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty) # - $FZF_COMPLETION_OPTS (default: empty)
_fzf_path_completion() { __fzf_generic_path_completion() {
local base lbuf find_opts fzf_opts suffix tail fzf dir leftover matches nnm local base lbuf find_opts fzf_opts suffix tail fzf dir leftover matches nnm
# (Q) flag removes a quoting level: "foo\ bar" => "foo bar"
base=${(Q)1} base=${(Q)1}
lbuf=$2 lbuf=$2
find_opts=$3 find_opts=$3
@@ -47,55 +48,71 @@ _fzf_path_completion() {
[ -n "$nnm" ] && unsetopt nonomatch [ -n "$nnm" ] && unsetopt nonomatch
} }
_fzf_all_completion() { _fzf_path_completion() {
_fzf_path_completion "$1" "$2" \ __fzf_generic_path_completion "$1" "$2" \
"-name .git -prune -o -name .svn -prune -o -type d -print -o -type f -print -o -type l -print" \ "-name .git -prune -o -name .svn -prune -o -type d -print -o -type f -print -o -type l -print" \
"-m" "" " " "-m" "" " "
} }
_fzf_dir_completion() { _fzf_dir_completion() {
_fzf_path_completion "$1" "$2" \ __fzf_generic_path_completion "$1" "$2" \
"-name .git -prune -o -name .svn -prune -o -type d -print" \ "-name .git -prune -o -name .svn -prune -o -type d -print" \
"" "/" "" "" "/" ""
} }
_fzf_list_completion() { _fzf_feed_fifo() (
local prefix lbuf fzf_opts src fzf matches rm -f "$fifo"
prefix=$1 mkfifo "$fifo"
cat <&0 > "$fifo" &
)
_fzf_complete() {
local fifo fzf_opts lbuf fzf matches
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
fzf_opts=$1
lbuf=$2 lbuf=$2
fzf_opts=$3
read -r src
[ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf" [ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
matches=$(eval "$src" | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=fzf_opts} -q "$prefix") _fzf_feed_fifo "$fifo"
matches=$(cat "$fifo" | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=fzf_opts} -q "${(Q)prefix}" | tr '\n' ' ')
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches" LBUFFER="$lbuf$matches"
fi fi
zle redisplay zle redisplay
rm -f "$fifo"
} }
_fzf_telnet_completion() { _fzf_complete_telnet() {
_fzf_list_completion "$1" "$2" '+m' << "EOF" _fzf_complete '+m' "$@" < <(
\grep -v '^\s*\(#\|$\)' /etc/hosts | \grep -Fv '0.0.0.0' | awk '{if (length($2) > 0) {print $2}}' | sort -u \grep -v '^\s*\(#\|$\)' /etc/hosts | \grep -Fv '0.0.0.0' |
EOF awk '{if (length($2) > 0) {print $2}}' | sort -u
)
} }
_fzf_ssh_completion() { _fzf_complete_ssh() {
_fzf_list_completion "$1" "$2" '+m' << "EOF" _fzf_complete '+m' "$@" < <(
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | \grep -i '^host' | \grep -v '*') <(\grep -v '^\s*\(#\|$\)' /etc/hosts | \grep -Fv '0.0.0.0') | awk '{if (length($2) > 0) {print $2}}' | sort -u cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | \grep -i '^host' | \grep -v '*') \
EOF <(\grep -v '^\s*\(#\|$\)' /etc/hosts | \grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
} }
_fzf_env_var_completion() { _fzf_complete_export() {
_fzf_list_completion "$1" "$2" '+m' << "EOF" _fzf_complete '-m' "$@" < <(
declare -xp | sed 's/=.*//' | sed 's/.* //' declare -xp | sed 's/=.*//' | sed 's/.* //'
EOF )
} }
_fzf_alias_completion() { _fzf_complete_unset() {
_fzf_list_completion "$1" "$2" '+m' << "EOF" _fzf_complete '-m' "$@" < <(
declare -xp | sed 's/=.*//' | sed 's/.* //'
)
}
_fzf_complete_unalias() {
_fzf_complete '+m' "$@" < <(
alias | sed 's/=.*//' alias | sed 's/=.*//'
EOF )
} }
fzf-completion() { fzf-completion() {
@@ -135,18 +152,12 @@ fzf-completion() {
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}} [ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
[ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}} [ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}}
if [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then if eval "type _fzf_complete_${cmd} > /dev/null"; then
_fzf_dir_completion "$prefix" $lbuf eval "prefix=\"$prefix\" _fzf_complete_${cmd} \"$lbuf\""
elif [ $cmd = telnet ]; then elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then
_fzf_telnet_completion "$prefix" $lbuf _fzf_dir_completion "$prefix" "$lbuf"
elif [ $cmd = ssh ]; then
_fzf_ssh_completion "$prefix" $lbuf
elif [ $cmd = unset -o $cmd = export ]; then
_fzf_env_var_completion "$prefix" $lbuf
elif [ $cmd = unalias ]; then
_fzf_alias_completion "$prefix" $lbuf
else else
_fzf_all_completion "$prefix" $lbuf _fzf_path_completion "$prefix" "$lbuf"
fi fi
# Fall back to default completion # Fall back to default completion
else else

View File

@@ -1,6 +1,6 @@
# Key bindings # Key bindings
# ------------ # ------------
if [[ $- =~ i ]]; then if [[ $- == *i* ]]; then
# CTRL-T - Paste the selected file path(s) into the command line # CTRL-T - Paste the selected file path(s) into the command line
__fsel() { __fsel() {

View File

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

View File

@@ -11,7 +11,6 @@ import "C"
import ( import (
"fmt" "fmt"
"os" "os"
"os/signal"
"syscall" "syscall"
"time" "time"
"unicode/utf8" "unicode/utf8"
@@ -271,14 +270,6 @@ func Init(theme *ColorTheme, black bool, mouse bool) {
C.noecho() C.noecho()
C.raw() // stty dsusp undef C.raw() // stty dsusp undef
intChan := make(chan os.Signal, 1)
signal.Notify(intChan, os.Interrupt, os.Kill)
go func() {
<-intChan
Close()
os.Exit(2)
}()
if theme != nil { if theme != nil {
C.start_color() C.start_color()
initPairs(theme, black) initPairs(theme, black)

View File

@@ -6,8 +6,8 @@ import (
"github.com/junegunn/fzf/src/curses" "github.com/junegunn/fzf/src/curses"
) )
// Offset holds two 32-bit integers denoting the offsets of a matched substring // Offset holds three 32-bit integers denoting the offsets of a matched substring
type Offset [2]int32 type Offset [3]int32
type colorOffset struct { type colorOffset struct {
offset [2]int32 offset [2]int32
@@ -43,10 +43,13 @@ func (item *Item) Rank(cache bool) Rank {
} }
matchlen := 0 matchlen := 0
prevEnd := 0 prevEnd := 0
lenSum := 0
minBegin := math.MaxUint16 minBegin := math.MaxUint16
for _, offset := range item.offsets { for _, offset := range item.offsets {
begin := int(offset[0]) begin := int(offset[0])
end := int(offset[1]) end := int(offset[1])
trimLen := int(offset[2])
lenSum += trimLen
if prevEnd > begin { if prevEnd > begin {
begin = prevEnd begin = prevEnd
} }
@@ -65,10 +68,7 @@ func (item *Item) Rank(cache bool) Rank {
case byLength: case byLength:
// It is guaranteed that .transformed in not null in normal execution // It is guaranteed that .transformed in not null in normal execution
if item.transformed != nil { if item.transformed != nil {
lenSum := 0 // If offsets is empty, lenSum will be 0, but we don't care
for _, token := range item.transformed {
lenSum += len(token.text)
}
tiebreak = uint16(lenSum) tiebreak = uint16(lenSum)
} else { } else {
tiebreak = uint16(len(item.text)) tiebreak = uint16(len(item.text))
@@ -116,7 +116,8 @@ func (item *Item) colorOffsets(color int, bold bool, current bool) []colorOffset
if len(item.colors) == 0 { if len(item.colors) == 0 {
var offsets []colorOffset var offsets []colorOffset
for _, off := range item.offsets { for _, off := range item.offsets {
offsets = append(offsets, colorOffset{offset: off, color: color, bold: bold})
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: color, bold: bold})
} }
return offsets return offsets
} }
@@ -160,7 +161,7 @@ func (item *Item) colorOffsets(color int, bold bool, current bool) []colorOffset
if curr != 0 && idx > start { if curr != 0 && idx > start {
if curr == -1 { if curr == -1 {
offsets = append(offsets, colorOffset{ offsets = append(offsets, colorOffset{
offset: Offset{int32(start), int32(idx)}, color: color, bold: bold}) offset: [2]int32{int32(start), int32(idx)}, color: color, bold: bold})
} else { } else {
ansi := item.colors[curr-1] ansi := item.colors[curr-1]
fg := ansi.color.fg fg := ansi.color.fg
@@ -180,7 +181,7 @@ func (item *Item) colorOffsets(color int, bold bool, current bool) []colorOffset
} }
} }
offsets = append(offsets, colorOffset{ offsets = append(offsets, colorOffset{
offset: Offset{int32(start), int32(idx)}, offset: [2]int32{int32(start), int32(idx)},
color: curses.PairFor(fg, bg), color: curses.PairFor(fg, bg),
bold: ansi.color.bold || bold}) bold: ansi.color.bold || bold})
} }

View File

@@ -380,9 +380,12 @@ func parseTiebreak(str string) tiebreak {
} }
func dupeTheme(theme *curses.ColorTheme) *curses.ColorTheme { func dupeTheme(theme *curses.ColorTheme) *curses.ColorTheme {
if theme != nil {
dupe := *theme dupe := *theme
return &dupe return &dupe
} }
return nil
}
func parseTheme(defaultTheme *curses.ColorTheme, str string) *curses.ColorTheme { func parseTheme(defaultTheme *curses.ColorTheme, str string) *curses.ColorTheme {
theme := dupeTheme(defaultTheme) theme := dupeTheme(defaultTheme)
@@ -402,7 +405,7 @@ func parseTheme(defaultTheme *curses.ColorTheme, str string) *curses.ColorTheme
} }
// Color is disabled // Color is disabled
if theme == nil { if theme == nil {
errorExit("colors disabled; cannot customize colors") continue
} }
pair := strings.Split(str, ":") pair := strings.Split(str, ":")

View File

@@ -316,3 +316,15 @@ func TestColorSpec(t *testing.T) {
t.Errorf("using default colors") t.Errorf("using default colors")
} }
} }
func TestParseNilTheme(t *testing.T) {
var theme *curses.ColorTheme
newTheme := parseTheme(theme, "prompt:12")
if newTheme != nil {
t.Errorf("color is disabled. keep it that way.")
}
newTheme = parseTheme(theme, "prompt:12,dark,prompt:13")
if newTheme.Prompt != 13 {
t.Errorf("color should now be enabled and customized")
}
}

View File

@@ -6,6 +6,7 @@ import (
"strings" "strings"
"github.com/junegunn/fzf/src/algo" "github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/util"
) )
// fuzzy // fuzzy
@@ -251,9 +252,9 @@ func (p *Pattern) matchChunk(chunk *Chunk) []*Item {
matches := []*Item{} matches := []*Item{}
if p.mode == ModeFuzzy { if p.mode == ModeFuzzy {
for _, item := range *chunk { for _, item := range *chunk {
if sidx, eidx := p.fuzzyMatch(item); sidx >= 0 { if sidx, eidx, tlen := p.fuzzyMatch(item); sidx >= 0 {
matches = append(matches, matches = append(matches,
dupItem(item, []Offset{Offset{int32(sidx), int32(eidx)}})) dupItem(item, []Offset{Offset{int32(sidx), int32(eidx), int32(tlen)}}))
} }
} }
} else { } else {
@@ -269,7 +270,7 @@ func (p *Pattern) matchChunk(chunk *Chunk) []*Item {
// MatchItem returns true if the Item is a match // MatchItem returns true if the Item is a match
func (p *Pattern) MatchItem(item *Item) bool { func (p *Pattern) MatchItem(item *Item) bool {
if p.mode == ModeFuzzy { if p.mode == ModeFuzzy {
sidx, _ := p.fuzzyMatch(item) sidx, _, _ := p.fuzzyMatch(item)
return sidx >= 0 return sidx >= 0
} }
offsets := p.extendedMatch(item) offsets := p.extendedMatch(item)
@@ -288,7 +289,7 @@ func dupItem(item *Item, offsets []Offset) *Item {
rank: Rank{0, 0, item.index}} rank: Rank{0, 0, item.index}}
} }
func (p *Pattern) fuzzyMatch(item *Item) (int, int) { func (p *Pattern) fuzzyMatch(item *Item) (int, int, int) {
input := p.prepareInput(item) input := p.prepareInput(item)
return p.iter(algo.FuzzyMatch, input, p.caseSensitive, p.forward, p.text) return p.iter(algo.FuzzyMatch, input, p.caseSensitive, p.forward, p.text)
} }
@@ -298,13 +299,13 @@ func (p *Pattern) extendedMatch(item *Item) []Offset {
offsets := []Offset{} offsets := []Offset{}
for _, term := range p.terms { for _, term := range p.terms {
pfun := p.procFun[term.typ] pfun := p.procFun[term.typ]
if sidx, eidx := p.iter(pfun, input, term.caseSensitive, p.forward, term.text); sidx >= 0 { if sidx, eidx, tlen := p.iter(pfun, input, term.caseSensitive, p.forward, term.text); sidx >= 0 {
if term.inv { if term.inv {
break break
} }
offsets = append(offsets, Offset{int32(sidx), int32(eidx)}) offsets = append(offsets, Offset{int32(sidx), int32(eidx), int32(tlen)})
} else if term.inv { } else if term.inv {
offsets = append(offsets, Offset{0, 0}) offsets = append(offsets, Offset{0, 0, 0})
} }
} }
return offsets return offsets
@@ -320,19 +321,19 @@ func (p *Pattern) prepareInput(item *Item) []Token {
tokens := Tokenize(item.text, p.delimiter) tokens := Tokenize(item.text, p.delimiter)
ret = Transform(tokens, p.nth) ret = Transform(tokens, p.nth)
} else { } else {
ret = []Token{Token{text: item.text, prefixLength: 0}} ret = []Token{Token{text: item.text, prefixLength: 0, trimLength: util.TrimLen(item.text)}}
} }
item.transformed = ret item.transformed = ret
return ret return ret
} }
func (p *Pattern) iter(pfun func(bool, bool, []rune, []rune) (int, int), func (p *Pattern) iter(pfun func(bool, bool, []rune, []rune) (int, int),
tokens []Token, caseSensitive bool, forward bool, pattern []rune) (int, int) { tokens []Token, caseSensitive bool, forward bool, pattern []rune) (int, int, int) {
for _, part := range tokens { for _, part := range tokens {
prefixLength := part.prefixLength prefixLength := part.prefixLength
if sidx, eidx := pfun(caseSensitive, forward, part.text, pattern); sidx >= 0 { if sidx, eidx := pfun(caseSensitive, forward, part.text, pattern); sidx >= 0 {
return sidx + prefixLength, eidx + prefixLength return sidx + prefixLength, eidx + prefixLength, part.trimLength
} }
} }
return -1, -1 return -1, -1, -1 // math.MaxUint16
} }

View File

@@ -727,6 +727,13 @@ func (t *Terminal) Loop() {
t.reqBox.Set(reqRefresh, nil) t.reqBox.Set(reqRefresh, nil)
}() }()
intChan := make(chan os.Signal, 1)
signal.Notify(intChan, os.Interrupt, os.Kill)
go func() {
<-intChan
t.reqBox.Set(reqQuit, nil)
}()
resizeChan := make(chan os.Signal, 1) resizeChan := make(chan os.Signal, 1)
signal.Notify(resizeChan, syscall.SIGWINCH) signal.Notify(resizeChan, syscall.SIGWINCH)
go func() { go func() {

View File

@@ -20,6 +20,7 @@ type Range struct {
type Token struct { type Token struct {
text []rune text []rune
prefixLength int prefixLength int
trimLength int
} }
// Delimiter for tokenizing the input // Delimiter for tokenizing the input
@@ -81,7 +82,7 @@ func withPrefixLengths(tokens [][]rune, begin int) []Token {
for idx, token := range tokens { for idx, token := range tokens {
// Need to define a new local variable instead of the reused token to take // Need to define a new local variable instead of the reused token to take
// the pointer to it // the pointer to it
ret[idx] = Token{text: token, prefixLength: prefixLength} ret[idx] = Token{token, prefixLength, util.TrimLen(token)}
prefixLength += len(token) prefixLength += len(token)
} }
return ret return ret
@@ -233,7 +234,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
} else { } else {
prefixLength = 0 prefixLength = 0
} }
transTokens[idx] = Token{part, prefixLength} transTokens[idx] = Token{part, prefixLength, util.TrimLen(part)}
} }
return transTokens return transTokens
} }

View File

@@ -44,22 +44,22 @@ func TestTokenize(t *testing.T) {
// AWK-style // AWK-style
input := " abc: def: ghi " input := " abc: def: ghi "
tokens := Tokenize([]rune(input), Delimiter{}) tokens := Tokenize([]rune(input), Delimiter{})
if string(tokens[0].text) != "abc: " || tokens[0].prefixLength != 2 { if string(tokens[0].text) != "abc: " || tokens[0].prefixLength != 2 || tokens[0].trimLength != 4 {
t.Errorf("%s", tokens) t.Errorf("%s", tokens)
} }
// With delimiter // With delimiter
tokens = Tokenize([]rune(input), delimiterRegexp(":")) tokens = Tokenize([]rune(input), delimiterRegexp(":"))
if string(tokens[0].text) != " abc:" || tokens[0].prefixLength != 0 { if string(tokens[0].text) != " abc:" || tokens[0].prefixLength != 0 || tokens[0].trimLength != 4 {
t.Errorf("%s", tokens) t.Errorf("%s", tokens)
} }
// With delimiter regex // With delimiter regex
tokens = Tokenize([]rune(input), delimiterRegexp("\\s+")) tokens = Tokenize([]rune(input), delimiterRegexp("\\s+"))
if string(tokens[0].text) != " " || tokens[0].prefixLength != 0 || if string(tokens[0].text) != " " || tokens[0].prefixLength != 0 || tokens[0].trimLength != 0 ||
string(tokens[1].text) != "abc: " || tokens[1].prefixLength != 2 || string(tokens[1].text) != "abc: " || tokens[1].prefixLength != 2 || tokens[1].trimLength != 4 ||
string(tokens[2].text) != "def: " || tokens[2].prefixLength != 8 || string(tokens[2].text) != "def: " || tokens[2].prefixLength != 8 || tokens[2].trimLength != 4 ||
string(tokens[3].text) != "ghi " || tokens[3].prefixLength != 14 { string(tokens[3].text) != "ghi " || tokens[3].prefixLength != 14 || tokens[3].trimLength != 3 {
t.Errorf("%s", tokens) t.Errorf("%s", tokens)
} }
} }

View File

@@ -75,6 +75,7 @@ func IsTty() bool {
return int(C.isatty(C.int(os.Stdin.Fd()))) != 0 return int(C.isatty(C.int(os.Stdin.Fd()))) != 0
} }
// TrimRight returns rune array with trailing white spaces cut off
func TrimRight(runes []rune) []rune { func TrimRight(runes []rune) []rune {
var i int var i int
for i = len(runes) - 1; i >= 0; i-- { for i = len(runes) - 1; i >= 0; i-- {
@@ -86,6 +87,7 @@ func TrimRight(runes []rune) []rune {
return runes[0 : i+1] return runes[0 : i+1]
} }
// BytesToRunes converts byte array into rune array
func BytesToRunes(bytea []byte) []rune { func BytesToRunes(bytea []byte) []rune {
runes := make([]rune, 0, len(bytea)) runes := make([]rune, 0, len(bytea))
for i := 0; i < len(bytea); { for i := 0; i < len(bytea); {
@@ -100,3 +102,27 @@ func BytesToRunes(bytea []byte) []rune {
} }
return runes return runes
} }
// TrimLen returns the length of trimmed rune array
func TrimLen(runes []rune) int {
var i int
for i = len(runes) - 1; i >= 0; i-- {
char := runes[i]
if char != ' ' && char != '\t' {
break
}
}
// Completely empty
if i < 0 {
return 0
}
var j int
for j = 0; j < len(runes); j++ {
char := runes[j]
if char != ' ' && char != '\t' {
break
}
}
return i - j + 1
}

View File

@@ -20,3 +20,23 @@ func TestContrain(t *testing.T) {
t.Error("Expected", 3) t.Error("Expected", 3)
} }
} }
func TestTrimLen(t *testing.T) {
check := func(str string, exp int) {
trimmed := TrimLen([]rune(str))
if trimmed != exp {
t.Errorf("Invalid TrimLen result for '%s': %d (expected %d)",
str, trimmed, exp)
}
}
check("hello", 5)
check("hello ", 5)
check("hello ", 5)
check(" hello", 5)
check(" hello", 5)
check(" hello ", 5)
check(" hello ", 5)
check("h o", 5)
check(" h o ", 5)
check(" ", 0)
}

View File

@@ -527,6 +527,53 @@ class TestGoFZF < TestBase
assert_equal output, `cat #{tempname} | #{FZF} -fh -n2 -d:`.split($/) assert_equal output, `cat #{tempname} | #{FZF} -fh -n2 -d:`.split($/)
end end
def test_tiebreak_length_with_nth_trim_length
input = [
"apple juice bottle 1",
"apple ui bottle 2",
"app ice bottle 3",
"app ic bottle 4",
]
writelines tempname, input
# len(1)
output = [
"app ice bottle 3",
"app ic bottle 4",
"apple juice bottle 1",
"apple ui bottle 2",
]
assert_equal output, `cat #{tempname} | #{FZF} -fa -n1`.split($/)
# len(1 ~ 2)
output = [
"apple ui bottle 2",
"app ic bottle 4",
"apple juice bottle 1",
"app ice bottle 3",
]
assert_equal output, `cat #{tempname} | #{FZF} -fai -n1..2`.split($/)
# len(1) + len(2)
output = [
"app ic bottle 4",
"app ice bottle 3",
"apple ui bottle 2",
"apple juice bottle 1",
]
assert_equal output, `cat #{tempname} | #{FZF} -x -f"a i" -n1,2`.split($/)
# len(2)
output = [
"apple ui bottle 2",
"app ic bottle 4",
"app ice bottle 3",
"apple juice bottle 1",
]
assert_equal output, `cat #{tempname} | #{FZF} -fi -n2`.split($/)
assert_equal output, `cat #{tempname} | #{FZF} -fi -n2,1..2`.split($/)
end
def test_tiebreak_end_backward_scan def test_tiebreak_end_backward_scan
input = %w[ input = %w[
foobar-fb foobar-fb