mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-02 21:22:01 -07:00
Compare commits
41 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
c4c92142a6 | ||
|
d4b6338102 | ||
|
8df7d962e6 | ||
|
41e916a511 | ||
|
d9c8a9a880 | ||
|
ddc7bb9064 | ||
|
1d4057c209 | ||
|
822b86942c | ||
|
1e74dbb937 | ||
|
7cef92fffe | ||
|
42e4992f06 | ||
|
a6066175c6 | ||
|
27444d6b1e | ||
|
d6a99c0391 | ||
|
f787f7e651 | ||
|
a7c9c08371 | ||
|
fccc93176b | ||
|
6439a138fe | ||
|
a9a29dff4f | ||
|
6a52f8b8dd | ||
|
a1049328d6 | ||
|
5c2b96bd00 | ||
|
c36413fdf6 | ||
|
52cf5af91c | ||
|
3a4e053af7 | ||
|
049bc9ec68 | ||
|
b461a555b8 | ||
|
0f87b2d1e1 | ||
|
0fb5b76c0d | ||
|
0c918dd87a | ||
|
05299a0fee | ||
|
b36b0a91f5 | ||
|
6081eac58a | ||
|
942ba749c7 | ||
|
f941012687 | ||
|
fed5e5d5af | ||
|
b864885753 | ||
|
64747c2324 | ||
|
34965edcda | ||
|
bd4377084d | ||
|
38a2076b89 |
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,6 +1,18 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.13.4
|
||||||
|
------
|
||||||
|
- Performance optimization
|
||||||
|
- Memory footprint for ascii string is reduced by 60%
|
||||||
|
- 15 to 20% improvement of query performance
|
||||||
|
- Up to 45% better performance of `--nth` with non-regex delimiters
|
||||||
|
- Fixed invalid handling of `hidden` property of `--preview-window`
|
||||||
|
|
||||||
|
0.13.3
|
||||||
|
------
|
||||||
|
- Fixed duplicate rendering of the last line in preview window
|
||||||
|
|
||||||
0.13.2
|
0.13.2
|
||||||
------
|
------
|
||||||
- Fixed race condition where preview window is not properly cleared
|
- Fixed race condition where preview window is not properly cleared
|
||||||
|
17
README.md
17
README.md
@@ -320,10 +320,10 @@ customization.
|
|||||||
|
|
||||||
[fzf-config]: https://github.com/junegunn/fzf/wiki/Configuring-FZF-command-(vim)
|
[fzf-config]: https://github.com/junegunn/fzf/wiki/Configuring-FZF-command-(vim)
|
||||||
|
|
||||||
#### `fzf#run([options])`
|
#### `fzf#run`
|
||||||
|
|
||||||
For more advanced uses, you can use `fzf#run()` function with the following
|
For more advanced uses, you can use `fzf#run([options])` function with the
|
||||||
options.
|
following options.
|
||||||
|
|
||||||
| Option name | Type | Description |
|
| Option name | Type | Description |
|
||||||
| -------------------------- | ------------- | ---------------------------------------------------------------- |
|
| -------------------------- | ------------- | ---------------------------------------------------------------- |
|
||||||
@@ -342,6 +342,17 @@ options.
|
|||||||
Examples can be found on [the wiki
|
Examples can be found on [the wiki
|
||||||
page](https://github.com/junegunn/fzf/wiki/Examples-(vim)).
|
page](https://github.com/junegunn/fzf/wiki/Examples-(vim)).
|
||||||
|
|
||||||
|
#### `fzf#wrap`
|
||||||
|
|
||||||
|
`fzf#wrap(name string, [opts dict, [fullscreen boolean]])` is a helper
|
||||||
|
function that decorates the options dictionary so that it understands
|
||||||
|
`g:fzf_layout`, `g:fzf_action`, and `g:fzf_history_dir` like `:FZF`.
|
||||||
|
|
||||||
|
```vim
|
||||||
|
command! -bang MyStuff
|
||||||
|
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
|
||||||
|
```
|
||||||
|
|
||||||
Tips
|
Tips
|
||||||
----
|
----
|
||||||
|
|
||||||
|
80
bin/fzf-tmux
80
bin/fzf-tmux
@@ -2,25 +2,51 @@
|
|||||||
# fzf-tmux: starts fzf in a tmux pane
|
# fzf-tmux: starts fzf in a tmux pane
|
||||||
# usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
|
# usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
>&2 echo "$1"
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
fzf="$(command -v fzf 2> /dev/null)" || fzf="$(dirname "$0")/fzf"
|
||||||
|
[[ -x "$fzf" ]] || fail 'fzf executable not found'
|
||||||
|
|
||||||
args=()
|
args=()
|
||||||
opt=""
|
opt=""
|
||||||
skip=""
|
skip=""
|
||||||
swap=""
|
swap=""
|
||||||
close=""
|
close=""
|
||||||
term=""
|
term=""
|
||||||
[ -n "$LINES" ] && lines=$LINES || lines=$(tput lines)
|
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines)
|
||||||
while [ $# -gt 0 ]; do
|
|
||||||
|
help() {
|
||||||
|
>&2 echo 'usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
|
||||||
|
|
||||||
|
Layout
|
||||||
|
-u [HEIGHT[%]] Split above (up)
|
||||||
|
-d [HEIGHT[%]] Split below (down)
|
||||||
|
-l [WIDTH[%]] Split left
|
||||||
|
-r [WIDTH[%]] Split right
|
||||||
|
|
||||||
|
(default: -d 50%)
|
||||||
|
'
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
arg="$1"
|
arg="$1"
|
||||||
case "$arg" in
|
shift
|
||||||
|
[[ -z "$skip" ]] && case "$arg" in
|
||||||
-)
|
-)
|
||||||
term=1
|
term=1
|
||||||
;;
|
;;
|
||||||
|
--help)
|
||||||
|
help
|
||||||
|
;;
|
||||||
|
--version)
|
||||||
|
echo "fzf-tmux (with fzf $("$fzf" --version))"
|
||||||
|
exit
|
||||||
|
;;
|
||||||
-w*|-h*|-d*|-u*|-r*|-l*)
|
-w*|-h*|-d*|-u*|-r*|-l*)
|
||||||
if [ -n "$skip" ]; then
|
|
||||||
args+=("$1")
|
|
||||||
shift
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
if [[ "$arg" =~ ^.[lrw] ]]; then
|
if [[ "$arg" =~ ^.[lrw] ]]; then
|
||||||
opt="-h"
|
opt="-h"
|
||||||
if [[ "$arg" =~ ^.l ]]; then
|
if [[ "$arg" =~ ^.l ]]; then
|
||||||
@@ -36,35 +62,33 @@ while [ $# -gt 0 ]; do
|
|||||||
close="; tmux swap-pane -D"
|
close="; tmux swap-pane -D"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [ ${#arg} -gt 2 ]; then
|
if [[ ${#arg} -gt 2 ]]; then
|
||||||
size="${arg:2}"
|
size="${arg:2}"
|
||||||
else
|
else
|
||||||
shift
|
|
||||||
if [[ "$1" =~ ^[0-9]+%?$ ]]; then
|
if [[ "$1" =~ ^[0-9]+%?$ ]]; then
|
||||||
size="$1"
|
size="$1"
|
||||||
else
|
|
||||||
[ -n "$1" -a "$1" != "--" ] && args+=("$1")
|
|
||||||
shift
|
shift
|
||||||
|
else
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$size" =~ %$ ]]; then
|
if [[ "$size" =~ %$ ]]; then
|
||||||
size=${size:0:((${#size}-1))}
|
size=${size:0:((${#size}-1))}
|
||||||
if [ -n "$swap" ]; then
|
if [[ -n "$swap" ]]; then
|
||||||
opt="$opt -p $(( 100 - size ))"
|
opt="$opt -p $(( 100 - size ))"
|
||||||
else
|
else
|
||||||
opt="$opt -p $size"
|
opt="$opt -p $size"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [ -n "$swap" ]; then
|
if [[ -n "$swap" ]]; then
|
||||||
if [[ "$arg" =~ ^.l ]]; then
|
if [[ "$arg" =~ ^.l ]]; then
|
||||||
[ -n "$COLUMNS" ] && max=$COLUMNS || max=$(tput cols)
|
[[ -n "$COLUMNS" ]] && max=$COLUMNS || max=$(tput cols)
|
||||||
else
|
else
|
||||||
max=$lines
|
max=$lines
|
||||||
fi
|
fi
|
||||||
size=$(( max - size ))
|
size=$(( max - size ))
|
||||||
[ $size -lt 0 ] && size=0
|
[[ $size -lt 0 ]] && size=0
|
||||||
opt="$opt -l $size"
|
opt="$opt -l $size"
|
||||||
else
|
else
|
||||||
opt="$opt -l $size"
|
opt="$opt -l $size"
|
||||||
@@ -75,16 +99,17 @@ while [ $# -gt 0 ]; do
|
|||||||
# "--" can be used to separate fzf-tmux options from fzf options to
|
# "--" can be used to separate fzf-tmux options from fzf options to
|
||||||
# avoid conflicts
|
# avoid conflicts
|
||||||
skip=1
|
skip=1
|
||||||
|
continue
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
args+=("$1")
|
args+=("$arg")
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
shift
|
[[ -n "$skip" ]] && args+=("$arg")
|
||||||
done
|
done
|
||||||
|
|
||||||
if ! [ -n "$TMUX" -a "$lines" -gt 15 ]; then
|
if [[ -z "$TMUX" ]] || [[ "$lines" -le 15 ]]; then
|
||||||
fzf "${args[@]}"
|
"$fzf" "${args[@]}"
|
||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -108,7 +133,7 @@ cleanup() {
|
|||||||
rm -f $argsf $fifo1 $fifo2 $fifo3
|
rm -f $argsf $fifo1 $fifo2 $fifo3
|
||||||
|
|
||||||
# Remove temp window if we were zoomed
|
# Remove temp window if we were zoomed
|
||||||
if [ -n "$zoomed" ]; then
|
if [[ -n "$zoomed" ]]; then
|
||||||
tmux swap-pane -t $original_window \; \
|
tmux swap-pane -t $original_window \; \
|
||||||
select-window -t $original_window \; \
|
select-window -t $original_window \; \
|
||||||
kill-window -t $tmp_window \; \
|
kill-window -t $tmp_window \; \
|
||||||
@@ -117,16 +142,9 @@ cleanup() {
|
|||||||
}
|
}
|
||||||
trap cleanup EXIT SIGINT SIGTERM
|
trap cleanup EXIT SIGINT SIGTERM
|
||||||
|
|
||||||
fail() {
|
|
||||||
>&2 echo "$1"
|
|
||||||
exit 2
|
|
||||||
}
|
|
||||||
fzf="$(which fzf 2> /dev/null)" || fzf="$(dirname "$0")/fzf"
|
|
||||||
[ -x "$fzf" ] || fail "fzf executable not found"
|
|
||||||
|
|
||||||
envs="env TERM=$TERM "
|
envs="env TERM=$TERM "
|
||||||
[ -n "$FZF_DEFAULT_OPTS" ] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
||||||
[ -n "$FZF_DEFAULT_COMMAND" ] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
||||||
|
|
||||||
mkfifo -m o+w $fifo2
|
mkfifo -m o+w $fifo2
|
||||||
mkfifo -m o+w $fifo3
|
mkfifo -m o+w $fifo3
|
||||||
@@ -141,7 +159,7 @@ for arg in "${args[@]}"; do
|
|||||||
opts="$opts \"$arg\""
|
opts="$opts \"$arg\""
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ -n "$term" -o -t 0 ]; then
|
if [[ -n "$term" ]] || [[ -t 0 ]]; then
|
||||||
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" > $argsf
|
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" > $argsf
|
||||||
tmux set-window-option synchronize-panes off \;\
|
tmux set-window-option synchronize-panes off \;\
|
||||||
set-window-option remain-on-exit off \;\
|
set-window-option remain-on-exit off \;\
|
||||||
|
35
install
35
install
@@ -2,13 +2,14 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
[[ "$@" =~ --pre ]] && version=0.13.2 pre=1 ||
|
[[ "$@" =~ --pre ]] && version=0.13.4 pre=1 ||
|
||||||
version=0.13.2 pre=0
|
version=0.13.4 pre=0
|
||||||
|
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
binary_arch=
|
binary_arch=
|
||||||
|
allow_legacy=
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
cat << EOF
|
cat << EOF
|
||||||
@@ -37,6 +38,7 @@ for opt in "$@"; do
|
|||||||
auto_completion=1
|
auto_completion=1
|
||||||
key_bindings=1
|
key_bindings=1
|
||||||
update_config=1
|
update_config=1
|
||||||
|
allow_legacy=1
|
||||||
;;
|
;;
|
||||||
--key-bindings) key_bindings=1 ;;
|
--key-bindings) key_bindings=1 ;;
|
||||||
--no-key-bindings) key_bindings=0 ;;
|
--no-key-bindings) key_bindings=0 ;;
|
||||||
@@ -109,6 +111,14 @@ link_fzf_in_path() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try_curl() {
|
||||||
|
command -v curl > /dev/null && curl -fL $1 | tar -xz
|
||||||
|
}
|
||||||
|
|
||||||
|
try_wget() {
|
||||||
|
command -v wget > /dev/null && wget -O - $1 | tar -xz
|
||||||
|
}
|
||||||
|
|
||||||
download() {
|
download() {
|
||||||
echo "Downloading bin/fzf ..."
|
echo "Downloading bin/fzf ..."
|
||||||
if [ $pre = 0 ]; then
|
if [ $pre = 0 ]; then
|
||||||
@@ -128,14 +138,13 @@ download() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local url=https://github.com/junegunn/fzf-bin/releases/download/$version/${1}.tgz
|
local url=https://github.com/junegunn/fzf-bin/releases/download/$version/${1}.tgz
|
||||||
if command -v curl > /dev/null; then
|
set -o pipefail
|
||||||
curl -fL $url | tar -xz
|
if ! (try_curl $url || try_wget $url); then
|
||||||
elif command -v wget > /dev/null; then
|
set +o pipefail
|
||||||
wget -O - $url | tar -xz
|
binary_error="Failed to download with curl and wget"
|
||||||
else
|
|
||||||
binary_error="curl or wget not found"
|
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
set +o pipefail
|
||||||
|
|
||||||
if [ ! -f $1 ]; then
|
if [ ! -f $1 ]; then
|
||||||
binary_error="Failed to download ${1}"
|
binary_error="Failed to download ${1}"
|
||||||
@@ -158,6 +167,9 @@ case "$archi" in
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
install_ruby_fzf() {
|
install_ruby_fzf() {
|
||||||
|
if [ -z "$allow_legacy" ]; then
|
||||||
|
ask "Do you want to install legacy Ruby version instead?" && exit 1
|
||||||
|
fi
|
||||||
echo "Installing legacy Ruby version ..."
|
echo "Installing legacy Ruby version ..."
|
||||||
|
|
||||||
# ruby executable
|
# ruby executable
|
||||||
@@ -229,6 +241,9 @@ cd "$fzf_base"
|
|||||||
if [ -n "$binary_error" ]; then
|
if [ -n "$binary_error" ]; then
|
||||||
if [ $binary_available -eq 0 ]; then
|
if [ $binary_available -eq 0 ]; then
|
||||||
echo "No prebuilt binary for $archi ..."
|
echo "No prebuilt binary for $archi ..."
|
||||||
|
else
|
||||||
|
echo " - $binary_error !!!"
|
||||||
|
fi
|
||||||
if command -v go > /dev/null; then
|
if command -v go > /dev/null; then
|
||||||
echo -n "Building binary (go get -u github.com/junegunn/fzf/src/fzf) ... "
|
echo -n "Building binary (go get -u github.com/junegunn/fzf/src/fzf) ... "
|
||||||
if [ -z "${GOPATH-}" ]; then
|
if [ -z "${GOPATH-}" ]; then
|
||||||
@@ -246,10 +261,6 @@ if [ -n "$binary_error" ]; then
|
|||||||
echo "go executable not found. Cannot build binary ..."
|
echo "go executable not found. Cannot build binary ..."
|
||||||
install_ruby_fzf
|
install_ruby_fzf
|
||||||
fi
|
fi
|
||||||
else
|
|
||||||
echo " - $binary_error !!!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[[ "$*" =~ "--bin" ]] && exit 0
|
[[ "$*" =~ "--bin" ]] && exit 0
|
||||||
|
54
man/man1/fzf-tmux.1
Normal file
54
man/man1/fzf-tmux.1
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
.ig
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Junegunn Choi
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
..
|
||||||
|
.TH fzf-tmux 1 "Aug 2016" "fzf 0.13.4" "fzf-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
|
.SH NAME
|
||||||
|
fzf-tmux - open fzf in tmux split pane
|
||||||
|
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
|
||||||
|
|
||||||
|
.SH DESCRIPTION
|
||||||
|
fzf-tmux is a wrapper script for fzf that opens fzf in a tmux split pane. It is
|
||||||
|
designed to work just like fzf except that it does not take up the whole
|
||||||
|
screen. You can safely use fzf-tmux instead of fzf in your scripts as the extra
|
||||||
|
options will be silently ignored if you're not on tmux.
|
||||||
|
|
||||||
|
.SH OPTIONS
|
||||||
|
.SS Layout
|
||||||
|
|
||||||
|
(default: \fB-d 50%\fR)
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B "-u [height[%]]"
|
||||||
|
Split above (up)
|
||||||
|
.TP
|
||||||
|
.B "-d [height[%]]"
|
||||||
|
Split below (down)
|
||||||
|
.TP
|
||||||
|
.B "-l [width[%]]"
|
||||||
|
Split left
|
||||||
|
.TP
|
||||||
|
.B "-r [width[%]]"
|
||||||
|
Split right
|
@@ -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 "Jun 2016" "fzf 0.13.2" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Aug 2016" "fzf 0.13.4" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
|
271
plugin/fzf.vim
271
plugin/fzf.vim
@@ -21,7 +21,8 @@
|
|||||||
" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
let s:default_height = '40%'
|
let s:default_layout = { 'down': '~40%' }
|
||||||
|
let s:layout_keys = ['window', 'up', 'down', 'left', 'right']
|
||||||
let s:fzf_go = expand('<sfile>:h:h').'/bin/fzf'
|
let s:fzf_go = expand('<sfile>:h:h').'/bin/fzf'
|
||||||
let s:install = expand('<sfile>:h:h').'/install'
|
let s:install = expand('<sfile>:h:h').'/install'
|
||||||
let s:installed = 0
|
let s:installed = 0
|
||||||
@@ -104,11 +105,106 @@ function! s:warn(msg)
|
|||||||
echohl None
|
echohl None
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! s:has_any(dict, keys)
|
||||||
|
for key in a:keys
|
||||||
|
if has_key(a:dict, key)
|
||||||
|
return 1
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
return 0
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:open(cmd, target)
|
||||||
|
if stridx('edit', a:cmd) == 0 && fnamemodify(a:target, ':p') ==# expand('%:p')
|
||||||
|
return
|
||||||
|
endif
|
||||||
|
execute a:cmd s:escape(a:target)
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:common_sink(action, lines) abort
|
||||||
|
if len(a:lines) < 2
|
||||||
|
return
|
||||||
|
endif
|
||||||
|
let key = remove(a:lines, 0)
|
||||||
|
let cmd = get(a: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
|
||||||
|
let empty = empty(expand('%')) && line('$') == 1 && empty(getline(1)) && !&modified
|
||||||
|
let autochdir = &autochdir
|
||||||
|
set noautochdir
|
||||||
|
for item in a:lines
|
||||||
|
if empty
|
||||||
|
execute 'e' s:escape(item)
|
||||||
|
let empty = 0
|
||||||
|
else
|
||||||
|
call s:open(cmd, item)
|
||||||
|
endif
|
||||||
|
if exists('#BufEnter') && isdirectory(item)
|
||||||
|
doautocmd BufEnter
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
finally
|
||||||
|
let &autochdir = autochdir
|
||||||
|
silent! autocmd! fzf_swap
|
||||||
|
endtry
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" name string, [opts dict, [fullscreen boolean]]
|
||||||
|
function! fzf#wrap(name, ...)
|
||||||
|
if type(a:name) != type('')
|
||||||
|
throw 'invalid name type: string expected'
|
||||||
|
endif
|
||||||
|
let opts = copy(get(a:000, 0, {}))
|
||||||
|
let bang = get(a:000, 1, 0)
|
||||||
|
|
||||||
|
" Layout: g:fzf_layout (and deprecated g:fzf_height)
|
||||||
|
if bang
|
||||||
|
for key in s:layout_keys
|
||||||
|
if has_key(opts, key)
|
||||||
|
call remove(opts, key)
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
elseif !s:has_any(opts, s:layout_keys)
|
||||||
|
if !exists('g:fzf_layout') && exists('g:fzf_height')
|
||||||
|
let opts.down = g:fzf_height
|
||||||
|
else
|
||||||
|
let opts = extend(opts, get(g:, 'fzf_layout', s:default_layout))
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
" History: g:fzf_history_dir
|
||||||
|
let opts.options = get(opts, 'options', '')
|
||||||
|
if len(get(g:, 'fzf_history_dir', ''))
|
||||||
|
let dir = expand(g:fzf_history_dir)
|
||||||
|
if !isdirectory(dir)
|
||||||
|
call mkdir(dir, 'p')
|
||||||
|
endif
|
||||||
|
let opts.options = join(['--history', s:escape(dir.'/'.a:name), opts.options])
|
||||||
|
endif
|
||||||
|
|
||||||
|
" Action: g:fzf_action
|
||||||
|
if !s:has_any(opts, ['sink', 'sink*'])
|
||||||
|
let opts._action = get(g:, 'fzf_action', s:default_action)
|
||||||
|
let opts.options .= ' --expect='.join(keys(opts._action), ',')
|
||||||
|
function! opts.sink(lines) abort
|
||||||
|
return s:common_sink(self._action, a:lines)
|
||||||
|
endfunction
|
||||||
|
let opts['sink*'] = remove(opts, 'sink')
|
||||||
|
endif
|
||||||
|
|
||||||
|
return opts
|
||||||
|
endfunction
|
||||||
|
|
||||||
function! fzf#run(...) abort
|
function! fzf#run(...) abort
|
||||||
try
|
try
|
||||||
let oshell = &shell
|
let oshell = &shell
|
||||||
set shell=sh
|
set shell=sh
|
||||||
if has('nvim') && bufexists('term://*:FZF')
|
if has('nvim') && len(filter(range(1, bufnr('$')), 'bufname(v:val) =~# ";#FZF"'))
|
||||||
call s:warn('FZF is already running!')
|
call s:warn('FZF is already running!')
|
||||||
return []
|
return []
|
||||||
endif
|
endif
|
||||||
@@ -122,7 +218,9 @@ try
|
|||||||
endtry
|
endtry
|
||||||
|
|
||||||
if !has_key(dict, 'source') && !empty($FZF_DEFAULT_COMMAND)
|
if !has_key(dict, 'source') && !empty($FZF_DEFAULT_COMMAND)
|
||||||
let dict.source = $FZF_DEFAULT_COMMAND
|
let temps.source = tempname()
|
||||||
|
call writefile(split($FZF_DEFAULT_COMMAND, "\n"), temps.source)
|
||||||
|
let dict.source = (empty($SHELL) ? 'sh' : $SHELL) . ' ' . s:shellesc(temps.source)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if has_key(dict, 'source')
|
if has_key(dict, 'source')
|
||||||
@@ -135,7 +233,7 @@ try
|
|||||||
call writefile(source, temps.input)
|
call writefile(source, temps.input)
|
||||||
let prefix = 'cat '.s:shellesc(temps.input).'|'
|
let prefix = 'cat '.s:shellesc(temps.input).'|'
|
||||||
else
|
else
|
||||||
throw 'Invalid source type'
|
throw 'invalid source type'
|
||||||
endif
|
endif
|
||||||
else
|
else
|
||||||
let prefix = ''
|
let prefix = ''
|
||||||
@@ -147,9 +245,9 @@ try
|
|||||||
return s:execute_term(dict, command, temps)
|
return s:execute_term(dict, command, temps)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
let ret = tmux ? s:execute_tmux(dict, command, temps) : s:execute(dict, command, temps)
|
let lines = tmux ? s:execute_tmux(dict, command, temps) : s:execute(dict, command, temps)
|
||||||
call s:popd(dict, ret)
|
call s:callback(dict, lines)
|
||||||
return ret
|
return lines
|
||||||
finally
|
finally
|
||||||
let &shell = oshell
|
let &shell = oshell
|
||||||
endtry
|
endtry
|
||||||
@@ -200,22 +298,17 @@ function! s:pushd(dict)
|
|||||||
return 0
|
return 0
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:popd(dict, lines)
|
augroup fzf_popd
|
||||||
" Since anything can be done in the sink function, there is no telling that
|
autocmd!
|
||||||
" the change of the working directory was made by &autochdir setting.
|
autocmd WinEnter * call s:dopopd()
|
||||||
"
|
augroup END
|
||||||
" We use the following heuristic to determine whether to restore CWD:
|
|
||||||
" - Always restore the current directory when &autochdir is disabled.
|
function! s:dopopd()
|
||||||
" FIXME This makes it impossible to change directory from inside the sink
|
if !exists('w:fzf_prev_dir') || exists('*haslocaldir') && !haslocaldir()
|
||||||
" function when &autochdir is not used.
|
return
|
||||||
" - In case of an error or an interrupt, a:lines will be empty.
|
|
||||||
" And it will be an array of a single empty string when fzf was finished
|
|
||||||
" without a match. In these cases, we presume that the change of the
|
|
||||||
" directory is not expected and should be undone.
|
|
||||||
if has_key(a:dict, 'prev_dir') &&
|
|
||||||
\ (!&autochdir || (empty(a:lines) || len(a:lines) == 1 && empty(a:lines[0])))
|
|
||||||
execute 'lcd' s:escape(remove(a:dict, 'prev_dir'))
|
|
||||||
endif
|
endif
|
||||||
|
execute 'lcd' s:escape(w:fzf_prev_dir)
|
||||||
|
unlet w:fzf_prev_dir
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:xterm_launcher()
|
function! s:xterm_launcher()
|
||||||
@@ -255,8 +348,9 @@ function! s:execute(dict, command, temps) abort
|
|||||||
let command = escaped
|
let command = escaped
|
||||||
endif
|
endif
|
||||||
execute 'silent !'.command
|
execute 'silent !'.command
|
||||||
|
let exit_status = v:shell_error
|
||||||
redraw!
|
redraw!
|
||||||
return s:exit_handler(v:shell_error, command) ? s:callback(a:dict, a:temps) : []
|
return s:exit_handler(exit_status, command) ? s:collect(a:temps) : []
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:execute_tmux(dict, command, temps) abort
|
function! s:execute_tmux(dict, command, temps) abort
|
||||||
@@ -267,8 +361,9 @@ function! s:execute_tmux(dict, command, temps) abort
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
call system(command)
|
call system(command)
|
||||||
|
let exit_status = v:shell_error
|
||||||
redraw!
|
redraw!
|
||||||
return s:exit_handler(v:shell_error, command) ? s:callback(a:dict, a:temps) : []
|
return s:exit_handler(exit_status, command) ? s:collect(a:temps) : []
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:calc_size(max, val, dict)
|
function! s:calc_size(max, val, dict)
|
||||||
@@ -285,6 +380,7 @@ function! s:calc_size(max, val, dict)
|
|||||||
|
|
||||||
let opts = get(a:dict, 'options', '').$FZF_DEFAULT_OPTS
|
let opts = get(a:dict, 'options', '').$FZF_DEFAULT_OPTS
|
||||||
let margin = stridx(opts, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2
|
let margin = stridx(opts, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2
|
||||||
|
let margin += stridx(opts, '--header') > stridx(opts, '--no-header')
|
||||||
return srcsz >= 0 ? min([srcsz + margin, size]) : size
|
return srcsz >= 0 ? min([srcsz + margin, size]) : size
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
@@ -328,7 +424,7 @@ endfunction
|
|||||||
function! s:execute_term(dict, command, temps) abort
|
function! s:execute_term(dict, command, temps) abort
|
||||||
let [ppos, winopts] = s:split(a:dict)
|
let [ppos, winopts] = s:split(a:dict)
|
||||||
let fzf = { 'buf': bufnr('%'), 'ppos': ppos, 'dict': a:dict, 'temps': a:temps,
|
let fzf = { 'buf': bufnr('%'), 'ppos': ppos, 'dict': a:dict, 'temps': a:temps,
|
||||||
\ 'name': 'FZF', 'winopts': winopts, 'command': a:command }
|
\ 'winopts': winopts, 'command': a:command }
|
||||||
function! fzf.switch_back(inplace)
|
function! fzf.switch_back(inplace)
|
||||||
if a:inplace && bufnr('') == self.buf
|
if a:inplace && bufnr('') == self.buf
|
||||||
" FIXME: Can't re-enter normal mode from terminal mode
|
" FIXME: Can't re-enter normal mode from terminal mode
|
||||||
@@ -356,36 +452,67 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
execute self.ppos.win.'wincmd w'
|
execute self.ppos.win.'wincmd w'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if bufexists(self.buf)
|
||||||
|
execute 'bd!' self.buf
|
||||||
|
endif
|
||||||
|
|
||||||
if !s:exit_handler(a:code, self.command, 1)
|
if !s:exit_handler(a:code, self.command, 1)
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
|
|
||||||
call s:pushd(self.dict)
|
call s:pushd(self.dict)
|
||||||
let ret = []
|
let lines = s:collect(self.temps)
|
||||||
try
|
call s:callback(self.dict, lines)
|
||||||
let ret = s:callback(self.dict, self.temps)
|
|
||||||
call self.switch_back(s:getpos() == self.ppos)
|
call self.switch_back(s:getpos() == self.ppos)
|
||||||
finally
|
|
||||||
call s:popd(self.dict, ret)
|
|
||||||
endtry
|
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
call s:pushd(a:dict)
|
try
|
||||||
call termopen(a:command, fzf)
|
if s:present(a:dict, 'dir')
|
||||||
call s:popd(a:dict, [])
|
execute 'lcd' s:escape(a:dict.dir)
|
||||||
|
endif
|
||||||
|
call termopen(a:command . ';#FZF', fzf)
|
||||||
|
finally
|
||||||
|
if s:present(a:dict, 'dir')
|
||||||
|
lcd -
|
||||||
|
endif
|
||||||
|
endtry
|
||||||
setlocal nospell bufhidden=wipe nobuflisted
|
setlocal nospell bufhidden=wipe nobuflisted
|
||||||
setf fzf
|
setf fzf
|
||||||
startinsert
|
startinsert
|
||||||
return []
|
return []
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:callback(dict, temps) abort
|
function! s:collect(temps) abort
|
||||||
let lines = []
|
try
|
||||||
try
|
return filereadable(a:temps.result) ? readfile(a:temps.result) : []
|
||||||
if filereadable(a:temps.result)
|
finally
|
||||||
let lines = readfile(a:temps.result)
|
for tf in values(a:temps)
|
||||||
|
silent! call delete(tf)
|
||||||
|
endfor
|
||||||
|
endtry
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:callback(dict, lines) abort
|
||||||
|
" Since anything can be done in the sink function, there is no telling that
|
||||||
|
" the change of the working directory was made by &autochdir setting.
|
||||||
|
"
|
||||||
|
" We use the following heuristic to determine whether to restore CWD:
|
||||||
|
" - Always restore the current directory when &autochdir is disabled.
|
||||||
|
" FIXME This makes it impossible to change directory from inside the sink
|
||||||
|
" function when &autochdir is not used.
|
||||||
|
" - In case of an error or an interrupt, a:lines will be empty.
|
||||||
|
" And it will be an array of a single empty string when fzf was finished
|
||||||
|
" without a match. In these cases, we presume that the change of the
|
||||||
|
" directory is not expected and should be undone.
|
||||||
|
let popd = has_key(a:dict, 'prev_dir') &&
|
||||||
|
\ (!&autochdir || (empty(a:lines) || len(a:lines) == 1 && empty(a:lines[0])))
|
||||||
|
if popd
|
||||||
|
let w:fzf_prev_dir = a:dict.prev_dir
|
||||||
|
endif
|
||||||
|
|
||||||
|
try
|
||||||
if has_key(a:dict, 'sink')
|
if has_key(a:dict, 'sink')
|
||||||
for line in lines
|
for line in a:lines
|
||||||
if type(a:dict.sink) == 2
|
if type(a:dict.sink) == 2
|
||||||
call a:dict.sink(line)
|
call a:dict.sink(line)
|
||||||
else
|
else
|
||||||
@@ -394,76 +521,36 @@ try
|
|||||||
endfor
|
endfor
|
||||||
endif
|
endif
|
||||||
if has_key(a:dict, 'sink*')
|
if has_key(a:dict, 'sink*')
|
||||||
call a:dict['sink*'](lines)
|
call a:dict['sink*'](a:lines)
|
||||||
endif
|
endif
|
||||||
endif
|
catch
|
||||||
|
|
||||||
for tf in values(a:temps)
|
|
||||||
silent! call delete(tf)
|
|
||||||
endfor
|
|
||||||
catch
|
|
||||||
if stridx(v:exception, ':E325:') < 0
|
if stridx(v:exception, ':E325:') < 0
|
||||||
echoerr v:exception
|
echoerr v:exception
|
||||||
endif
|
endif
|
||||||
finally
|
endtry
|
||||||
return lines
|
|
||||||
endtry
|
" We may have opened a new window or tab
|
||||||
|
if popd
|
||||||
|
let w:fzf_prev_dir = a:dict.prev_dir
|
||||||
|
call s:dopopd()
|
||||||
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
let s:default_action = {
|
let s:default_action = {
|
||||||
\ 'ctrl-m': 'e',
|
|
||||||
\ 'ctrl-t': 'tab split',
|
\ 'ctrl-t': 'tab split',
|
||||||
\ 'ctrl-x': 'split',
|
\ 'ctrl-x': 'split',
|
||||||
\ 'ctrl-v': 'vsplit' }
|
\ 'ctrl-v': 'vsplit' }
|
||||||
|
|
||||||
function! s:cmd_callback(lines) abort
|
|
||||||
if empty(a:lines)
|
|
||||||
return
|
|
||||||
endif
|
|
||||||
let key = remove(a:lines, 0)
|
|
||||||
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
|
|
||||||
let empty = empty(expand('%')) && line('$') == 1 && empty(getline(1)) && !&modified
|
|
||||||
let autochdir = &autochdir
|
|
||||||
set noautochdir
|
|
||||||
for item in a:lines
|
|
||||||
if empty
|
|
||||||
execute 'e' s:escape(item)
|
|
||||||
let empty = 0
|
|
||||||
else
|
|
||||||
execute cmd s:escape(item)
|
|
||||||
endif
|
|
||||||
if exists('#BufEnter') && isdirectory(item)
|
|
||||||
doautocmd BufEnter
|
|
||||||
endif
|
|
||||||
endfor
|
|
||||||
finally
|
|
||||||
let &autochdir = autochdir
|
|
||||||
silent! autocmd! fzf_swap
|
|
||||||
endtry
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
function! s:cmd(bang, ...) abort
|
function! s:cmd(bang, ...) abort
|
||||||
let s:action = get(g:, 'fzf_action', s:default_action)
|
let args = copy(a:000)
|
||||||
let args = extend(['--expect='.join(keys(s:action), ',')], a:000)
|
|
||||||
let opts = {}
|
let opts = {}
|
||||||
if len(args) > 0 && isdirectory(expand(args[-1]))
|
if len(args) && isdirectory(expand(args[-1]))
|
||||||
let opts.dir = substitute(remove(args, -1), '\\\(["'']\)', '\1', 'g')
|
let opts.dir = substitute(remove(args, -1), '\\\(["'']\)', '\1', 'g')
|
||||||
endif
|
endif
|
||||||
if !a:bang
|
call fzf#run(fzf#wrap('FZF', extend({'options': join(args)}, opts), a:bang))
|
||||||
let opts.down = get(g:, 'fzf_height', get(g:, 'fzf_tmux_height', s:default_height))
|
|
||||||
endif
|
|
||||||
call fzf#run(extend({'options': join(args), 'sink*': function('<sid>cmd_callback')}, opts))
|
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
command! -nargs=* -complete=dir -bang FZF call s:cmd(<bang>0, <f-args>)
|
command! -nargs=* -complete=dir -bang FZF call s:cmd(<bang>0, <f-args>)
|
||||||
|
|
||||||
let &cpo = s:cpo_save
|
let &cpo = s:cpo_save
|
||||||
unlet s:cpo_save
|
unlet s:cpo_save
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||||
_fzf_compgen_path() {
|
_fzf_compgen_path() {
|
||||||
echo "$1"
|
echo "$1"
|
||||||
\find -L "$1" \
|
command find -L "$1" \
|
||||||
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ fi
|
|||||||
|
|
||||||
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
||||||
_fzf_compgen_dir() {
|
_fzf_compgen_dir() {
|
||||||
\find -L "$1" \
|
command find -L "$1" \
|
||||||
-name .git -prune -o -name .svn -prune -o -type d \
|
-name .git -prune -o -name .svn -prune -o -type d \
|
||||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ _fzf_handle_dynamic_completion() {
|
|||||||
elif [ -n "$_fzf_completion_loader" ]; then
|
elif [ -n "$_fzf_completion_loader" ]; then
|
||||||
_completion_loader "$@"
|
_completion_loader "$@"
|
||||||
ret=$?
|
ret=$?
|
||||||
eval "$(complete | \grep "\-F.* $orig_cmd$" | _fzf_orig_completion_filter)"
|
eval "$(complete | command grep "\-F.* $orig_cmd$" | _fzf_orig_completion_filter)"
|
||||||
source "${BASH_SOURCE[0]}"
|
source "${BASH_SOURCE[0]}"
|
||||||
return $ret
|
return $ret
|
||||||
fi
|
fi
|
||||||
@@ -213,16 +213,16 @@ _fzf_complete_kill() {
|
|||||||
|
|
||||||
_fzf_complete_telnet() {
|
_fzf_complete_telnet() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete '+m' "$@" < <(
|
||||||
\grep -v '^\s*\(#\|$\)' /etc/hosts | \grep -Fv '0.0.0.0' |
|
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_ssh() {
|
_fzf_complete_ssh() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete '+m' "$@" < <(
|
||||||
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | \grep -i '^host' | \grep -v '*') \
|
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*') \
|
||||||
<(\grep -oE '^[^ ]+' ~/.ssh/known_hosts | tr ',' '\n' | awk '{ print $1 " " $1 }') \
|
<(command grep -oE '^[^ ]+' ~/.ssh/known_hosts | tr ',' '\n' | awk '{ print $1 " " $1 }') \
|
||||||
<(\grep -v '^\s*\(#\|$\)' /etc/hosts | \grep -Fv '0.0.0.0') |
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -263,8 +263,8 @@ x_cmds="kill ssh telnet unset unalias export"
|
|||||||
# Preserve existing completion
|
# Preserve existing completion
|
||||||
if [ "$_fzf_completion_loaded" != '0.11.3' ]; then
|
if [ "$_fzf_completion_loaded" != '0.11.3' ]; then
|
||||||
# Really wish I could use associative array but OSX comes with bash 3.2 :(
|
# Really wish I could use associative array but OSX comes with bash 3.2 :(
|
||||||
eval $(complete | \grep '\-F' | \grep -v _fzf_ |
|
eval $(complete | command grep '\-F' | command grep -v _fzf_ |
|
||||||
\grep -E " ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g' | sed 's/+/\\+/g'))$" | _fzf_orig_completion_filter)
|
command grep -E " ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g' | sed 's/+/\\+/g'))$" | _fzf_orig_completion_filter)
|
||||||
export _fzf_completion_loaded=0.11.3
|
export _fzf_completion_loaded=0.11.3
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||||
_fzf_compgen_path() {
|
_fzf_compgen_path() {
|
||||||
echo "$1"
|
echo "$1"
|
||||||
\find -L "$1" \
|
command find -L "$1" \
|
||||||
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ fi
|
|||||||
|
|
||||||
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
||||||
_fzf_compgen_dir() {
|
_fzf_compgen_dir() {
|
||||||
\find -L "$1" \
|
command find -L "$1" \
|
||||||
-name .git -prune -o -name .svn -prune -o -type d \
|
-name .git -prune -o -name .svn -prune -o -type d \
|
||||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||||
}
|
}
|
||||||
@@ -58,6 +58,7 @@ __fzf_generic_path_completion() {
|
|||||||
LBUFFER="$lbuf$matches$tail"
|
LBUFFER="$lbuf$matches$tail"
|
||||||
fi
|
fi
|
||||||
zle redisplay
|
zle redisplay
|
||||||
|
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
dir=$(dirname "$dir")
|
dir=$(dirname "$dir")
|
||||||
@@ -76,7 +77,7 @@ _fzf_dir_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_feed_fifo() (
|
_fzf_feed_fifo() (
|
||||||
rm -f "$1"
|
command rm -f "$1"
|
||||||
mkfifo "$1"
|
mkfifo "$1"
|
||||||
cat <&0 > "$1" &
|
cat <&0 > "$1" &
|
||||||
)
|
)
|
||||||
@@ -97,21 +98,22 @@ _fzf_complete() {
|
|||||||
LBUFFER="$lbuf$matches"
|
LBUFFER="$lbuf$matches"
|
||||||
fi
|
fi
|
||||||
zle redisplay
|
zle redisplay
|
||||||
rm -f "$fifo"
|
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
||||||
|
command rm -f "$fifo"
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_telnet() {
|
_fzf_complete_telnet() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete '+m' "$@" < <(
|
||||||
\grep -v '^\s*\(#\|$\)' /etc/hosts | \grep -Fv '0.0.0.0' |
|
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_ssh() {
|
_fzf_complete_ssh() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete '+m' "$@" < <(
|
||||||
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | \grep -i '^host' | \grep -v '*') \
|
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*') \
|
||||||
<(\grep -oE '^[^ ]+' ~/.ssh/known_hosts | tr ',' '\n' | awk '{ print $1 " " $1 }') \
|
<(command grep -oE '^[^ ]+' ~/.ssh/known_hosts | tr ',' '\n' | awk '{ print $1 " " $1 }') \
|
||||||
<(\grep -v '^\s*\(#\|$\)' /etc/hosts | \grep -Fv '0.0.0.0') |
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -136,7 +138,7 @@ _fzf_complete_unalias() {
|
|||||||
|
|
||||||
fzf-completion() {
|
fzf-completion() {
|
||||||
local tokens cmd prefix trigger tail fzf matches lbuf d_cmds
|
local tokens cmd prefix trigger tail fzf matches lbuf d_cmds
|
||||||
setopt localoptions noshwordsplit
|
setopt localoptions noshwordsplit noksh_arrays
|
||||||
|
|
||||||
# http://zsh.sourceforge.net/FAQ/zshfaq03.html
|
# http://zsh.sourceforge.net/FAQ/zshfaq03.html
|
||||||
# http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags
|
# http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags
|
||||||
@@ -161,6 +163,7 @@ fzf-completion() {
|
|||||||
LBUFFER="$LBUFFER$matches"
|
LBUFFER="$LBUFFER$matches"
|
||||||
fi
|
fi
|
||||||
zle redisplay
|
zle redisplay
|
||||||
|
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
||||||
# Trigger sequence given
|
# Trigger sequence given
|
||||||
elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
||||||
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
|
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
|
||||||
|
@@ -26,7 +26,7 @@ __fzf_select_tmux__() {
|
|||||||
height="-l $height"
|
height="-l $height"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
tmux split-window $height "cd $(printf %q "$PWD"); FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS") PATH=$(printf %q "$PATH") FZF_CTRL_T_COMMAND=$(printf %q "$FZF_CTRL_T_COMMAND") FZF_CTRL_T_OPTS=$(printf %q "$FZF_CTRL_T_OPTS") bash -c 'source \"${BASH_SOURCE[0]}\"; tmux send-keys -t $TMUX_PANE \"\$(__fzf_select__)\"'"
|
tmux split-window $height "cd $(printf %q "$PWD"); FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS") PATH=$(printf %q "$PATH") FZF_CTRL_T_COMMAND=$(printf %q "$FZF_CTRL_T_COMMAND") FZF_CTRL_T_OPTS=$(printf %q "$FZF_CTRL_T_OPTS") bash -c 'source \"${BASH_SOURCE[0]}\"; RESULT=\"\$(__fzf_select__)\"; tmux setb -b fzf \"\$RESULT\" \\; pasteb -b fzf -t $TMUX_PANE \\; deleteb -b fzf || tmux send-keys -t $TMUX_PANE \"\$RESULT\"'"
|
||||||
}
|
}
|
||||||
|
|
||||||
fzf-file-widget() {
|
fzf-file-widget() {
|
||||||
@@ -52,7 +52,7 @@ __fzf_history__() (
|
|||||||
line=$(
|
line=$(
|
||||||
HISTTIMEFORMAT= history |
|
HISTTIMEFORMAT= history |
|
||||||
eval "$(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS" |
|
eval "$(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS" |
|
||||||
\grep '^ *[0-9]') &&
|
command grep '^ *[0-9]') &&
|
||||||
if [[ $- =~ H ]]; then
|
if [[ $- =~ H ]]; then
|
||||||
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
||||||
else
|
else
|
||||||
|
@@ -8,10 +8,13 @@ __fsel() {
|
|||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"}"
|
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"}"
|
||||||
|
setopt localoptions pipefail 2> /dev/null
|
||||||
eval "$cmd | $(__fzfcmd) -m $FZF_CTRL_T_OPTS" | while read item; do
|
eval "$cmd | $(__fzfcmd) -m $FZF_CTRL_T_OPTS" | while read item; do
|
||||||
echo -n "${(q)item} "
|
echo -n "${(q)item} "
|
||||||
done
|
done
|
||||||
|
local ret=$?
|
||||||
echo
|
echo
|
||||||
|
return $ret
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzfcmd() {
|
__fzfcmd() {
|
||||||
@@ -20,7 +23,10 @@ __fzfcmd() {
|
|||||||
|
|
||||||
fzf-file-widget() {
|
fzf-file-widget() {
|
||||||
LBUFFER="${LBUFFER}$(__fsel)"
|
LBUFFER="${LBUFFER}$(__fsel)"
|
||||||
|
local ret=$?
|
||||||
zle redisplay
|
zle redisplay
|
||||||
|
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
||||||
|
return $ret
|
||||||
}
|
}
|
||||||
zle -N fzf-file-widget
|
zle -N fzf-file-widget
|
||||||
bindkey '^T' fzf-file-widget
|
bindkey '^T' fzf-file-widget
|
||||||
@@ -29,8 +35,12 @@ bindkey '^T' fzf-file-widget
|
|||||||
fzf-cd-widget() {
|
fzf-cd-widget() {
|
||||||
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
|
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-"}"
|
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"}"
|
||||||
|
setopt localoptions pipefail 2> /dev/null
|
||||||
cd "${$(eval "$cmd | $(__fzfcmd) +m $FZF_ALT_C_OPTS"):-.}"
|
cd "${$(eval "$cmd | $(__fzfcmd) +m $FZF_ALT_C_OPTS"):-.}"
|
||||||
|
local ret=$?
|
||||||
zle reset-prompt
|
zle reset-prompt
|
||||||
|
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
||||||
|
return $ret
|
||||||
}
|
}
|
||||||
zle -N fzf-cd-widget
|
zle -N fzf-cd-widget
|
||||||
bindkey '\ec' fzf-cd-widget
|
bindkey '\ec' fzf-cd-widget
|
||||||
@@ -38,8 +48,9 @@ bindkey '\ec' fzf-cd-widget
|
|||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
fzf-history-widget() {
|
fzf-history-widget() {
|
||||||
local selected num
|
local selected num
|
||||||
setopt localoptions noglobsubst
|
setopt localoptions noglobsubst pipefail 2> /dev/null
|
||||||
selected=( $(fc -l 1 | eval "$(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS -q ${(q)LBUFFER}") )
|
selected=( $(fc -l 1 | eval "$(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS -q ${(q)LBUFFER}") )
|
||||||
|
local ret=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
num=$selected[1]
|
num=$selected[1]
|
||||||
if [ -n "$num" ]; then
|
if [ -n "$num" ]; then
|
||||||
@@ -47,6 +58,8 @@ fzf-history-widget() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
zle redisplay
|
zle redisplay
|
||||||
|
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
||||||
|
return $ret
|
||||||
}
|
}
|
||||||
zle -N fzf-history-widget
|
zle -N fzf-history-widget
|
||||||
bindkey '^R' fzf-history-widget
|
bindkey '^R' fzf-history-widget
|
||||||
|
@@ -15,11 +15,18 @@ import (
|
|||||||
* In short: They try to do as little work as possible.
|
* In short: They try to do as little work as possible.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func runeAt(runes []rune, index int, max int, forward bool) rune {
|
func indexAt(index int, max int, forward bool) int {
|
||||||
if forward {
|
if forward {
|
||||||
return runes[index]
|
return index
|
||||||
}
|
}
|
||||||
return runes[max-index-1]
|
return max - index - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func runeAt(text util.Chars, index int, max int, forward bool) rune {
|
||||||
|
if forward {
|
||||||
|
return text.Get(index)
|
||||||
|
}
|
||||||
|
return text.Get(max - index - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Result conatins the results of running a match function.
|
// Result conatins the results of running a match function.
|
||||||
@@ -42,14 +49,14 @@ const (
|
|||||||
charNumber
|
charNumber
|
||||||
)
|
)
|
||||||
|
|
||||||
func evaluateBonus(caseSensitive bool, runes []rune, pattern []rune, sidx int, eidx int) int32 {
|
func evaluateBonus(caseSensitive bool, text util.Chars, pattern []rune, sidx int, eidx int) int32 {
|
||||||
var bonus int32
|
var bonus int32
|
||||||
pidx := 0
|
pidx := 0
|
||||||
lenPattern := len(pattern)
|
lenPattern := len(pattern)
|
||||||
consecutive := false
|
consecutive := false
|
||||||
prevClass := charNonWord
|
prevClass := charNonWord
|
||||||
for index := 0; index < eidx; index++ {
|
for index := util.Max(0, sidx-1); index < eidx; index++ {
|
||||||
char := runes[index]
|
char := text.Get(index)
|
||||||
var class charClass
|
var class charClass
|
||||||
if unicode.IsLower(char) {
|
if unicode.IsLower(char) {
|
||||||
class = charLower
|
class = charLower
|
||||||
@@ -107,7 +114,7 @@ func evaluateBonus(caseSensitive bool, runes []rune, pattern []rune, sidx int, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FuzzyMatch performs fuzzy-match
|
// FuzzyMatch performs fuzzy-match
|
||||||
func FuzzyMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune) Result {
|
func FuzzyMatch(caseSensitive bool, forward bool, text util.Chars, pattern []rune) Result {
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
return Result{0, 0, 0}
|
return Result{0, 0, 0}
|
||||||
}
|
}
|
||||||
@@ -125,11 +132,11 @@ func FuzzyMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune)
|
|||||||
sidx := -1
|
sidx := -1
|
||||||
eidx := -1
|
eidx := -1
|
||||||
|
|
||||||
lenRunes := len(runes)
|
lenRunes := text.Length()
|
||||||
lenPattern := len(pattern)
|
lenPattern := len(pattern)
|
||||||
|
|
||||||
for index := range runes {
|
for index := 0; index < lenRunes; index++ {
|
||||||
char := runeAt(runes, index, lenRunes, forward)
|
char := runeAt(text, index, lenRunes, forward)
|
||||||
// This is considerably faster than blindly applying strings.ToLower to the
|
// This is considerably faster than blindly applying strings.ToLower to the
|
||||||
// whole string
|
// whole string
|
||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
@@ -142,7 +149,7 @@ func FuzzyMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune)
|
|||||||
char = unicode.To(unicode.LowerCase, char)
|
char = unicode.To(unicode.LowerCase, char)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pchar := runeAt(pattern, pidx, lenPattern, forward)
|
pchar := pattern[indexAt(pidx, lenPattern, forward)]
|
||||||
if char == pchar {
|
if char == pchar {
|
||||||
if sidx < 0 {
|
if sidx < 0 {
|
||||||
sidx = index
|
sidx = index
|
||||||
@@ -157,7 +164,7 @@ func FuzzyMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune)
|
|||||||
if sidx >= 0 && eidx >= 0 {
|
if sidx >= 0 && eidx >= 0 {
|
||||||
pidx--
|
pidx--
|
||||||
for index := eidx - 1; index >= sidx; index-- {
|
for index := eidx - 1; index >= sidx; index-- {
|
||||||
char := runeAt(runes, index, lenRunes, forward)
|
char := runeAt(text, index, lenRunes, forward)
|
||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
if char >= 'A' && char <= 'Z' {
|
if char >= 'A' && char <= 'Z' {
|
||||||
char += 32
|
char += 32
|
||||||
@@ -166,7 +173,7 @@ func FuzzyMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pchar := runeAt(pattern, pidx, lenPattern, forward)
|
pchar := pattern[indexAt(pidx, lenPattern, forward)]
|
||||||
if char == pchar {
|
if char == pchar {
|
||||||
if pidx--; pidx < 0 {
|
if pidx--; pidx < 0 {
|
||||||
sidx = index
|
sidx = index
|
||||||
@@ -182,7 +189,7 @@ func FuzzyMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Result{int32(sidx), int32(eidx),
|
return Result{int32(sidx), int32(eidx),
|
||||||
evaluateBonus(caseSensitive, runes, pattern, sidx, eidx)}
|
evaluateBonus(caseSensitive, text, pattern, sidx, eidx)}
|
||||||
}
|
}
|
||||||
return Result{-1, -1, 0}
|
return Result{-1, -1, 0}
|
||||||
}
|
}
|
||||||
@@ -194,12 +201,12 @@ func FuzzyMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune)
|
|||||||
//
|
//
|
||||||
// We might try to implement better algorithms in the future:
|
// We might try to implement better algorithms in the future:
|
||||||
// http://en.wikipedia.org/wiki/String_searching_algorithm
|
// http://en.wikipedia.org/wiki/String_searching_algorithm
|
||||||
func ExactMatchNaive(caseSensitive bool, forward bool, runes []rune, pattern []rune) Result {
|
func ExactMatchNaive(caseSensitive bool, forward bool, text util.Chars, pattern []rune) Result {
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
return Result{0, 0, 0}
|
return Result{0, 0, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
lenRunes := len(runes)
|
lenRunes := text.Length()
|
||||||
lenPattern := len(pattern)
|
lenPattern := len(pattern)
|
||||||
|
|
||||||
if lenRunes < lenPattern {
|
if lenRunes < lenPattern {
|
||||||
@@ -208,7 +215,7 @@ func ExactMatchNaive(caseSensitive bool, forward bool, runes []rune, pattern []r
|
|||||||
|
|
||||||
pidx := 0
|
pidx := 0
|
||||||
for index := 0; index < lenRunes; index++ {
|
for index := 0; index < lenRunes; index++ {
|
||||||
char := runeAt(runes, index, lenRunes, forward)
|
char := runeAt(text, index, lenRunes, forward)
|
||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
if char >= 'A' && char <= 'Z' {
|
if char >= 'A' && char <= 'Z' {
|
||||||
char += 32
|
char += 32
|
||||||
@@ -216,7 +223,7 @@ func ExactMatchNaive(caseSensitive bool, forward bool, runes []rune, pattern []r
|
|||||||
char = unicode.To(unicode.LowerCase, char)
|
char = unicode.To(unicode.LowerCase, char)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pchar := runeAt(pattern, pidx, lenPattern, forward)
|
pchar := pattern[indexAt(pidx, lenPattern, forward)]
|
||||||
if pchar == char {
|
if pchar == char {
|
||||||
pidx++
|
pidx++
|
||||||
if pidx == lenPattern {
|
if pidx == lenPattern {
|
||||||
@@ -229,7 +236,7 @@ func ExactMatchNaive(caseSensitive bool, forward bool, runes []rune, pattern []r
|
|||||||
eidx = lenRunes - (index - lenPattern + 1)
|
eidx = lenRunes - (index - lenPattern + 1)
|
||||||
}
|
}
|
||||||
return Result{int32(sidx), int32(eidx),
|
return Result{int32(sidx), int32(eidx),
|
||||||
evaluateBonus(caseSensitive, runes, pattern, sidx, eidx)}
|
evaluateBonus(caseSensitive, text, pattern, sidx, eidx)}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
index -= pidx
|
index -= pidx
|
||||||
@@ -240,13 +247,13 @@ func ExactMatchNaive(caseSensitive bool, forward bool, runes []rune, pattern []r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PrefixMatch performs prefix-match
|
// PrefixMatch performs prefix-match
|
||||||
func PrefixMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune) Result {
|
func PrefixMatch(caseSensitive bool, forward bool, text util.Chars, pattern []rune) Result {
|
||||||
if len(runes) < len(pattern) {
|
if text.Length() < len(pattern) {
|
||||||
return Result{-1, -1, 0}
|
return Result{-1, -1, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
for index, r := range pattern {
|
for index, r := range pattern {
|
||||||
char := runes[index]
|
char := text.Get(index)
|
||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
char = unicode.ToLower(char)
|
char = unicode.ToLower(char)
|
||||||
}
|
}
|
||||||
@@ -256,20 +263,19 @@ func PrefixMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune)
|
|||||||
}
|
}
|
||||||
lenPattern := len(pattern)
|
lenPattern := len(pattern)
|
||||||
return Result{0, int32(lenPattern),
|
return Result{0, int32(lenPattern),
|
||||||
evaluateBonus(caseSensitive, runes, pattern, 0, lenPattern)}
|
evaluateBonus(caseSensitive, text, pattern, 0, lenPattern)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SuffixMatch performs suffix-match
|
// SuffixMatch performs suffix-match
|
||||||
func SuffixMatch(caseSensitive bool, forward bool, input []rune, pattern []rune) Result {
|
func SuffixMatch(caseSensitive bool, forward bool, text util.Chars, pattern []rune) Result {
|
||||||
runes := util.TrimRight(input)
|
trimmedLen := text.Length() - text.TrailingWhitespaces()
|
||||||
trimmedLen := len(runes)
|
|
||||||
diff := trimmedLen - len(pattern)
|
diff := trimmedLen - len(pattern)
|
||||||
if diff < 0 {
|
if diff < 0 {
|
||||||
return Result{-1, -1, 0}
|
return Result{-1, -1, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
for index, r := range pattern {
|
for index, r := range pattern {
|
||||||
char := runes[index+diff]
|
char := text.Get(index + diff)
|
||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
char = unicode.ToLower(char)
|
char = unicode.ToLower(char)
|
||||||
}
|
}
|
||||||
@@ -281,16 +287,16 @@ func SuffixMatch(caseSensitive bool, forward bool, input []rune, pattern []rune)
|
|||||||
sidx := trimmedLen - lenPattern
|
sidx := trimmedLen - lenPattern
|
||||||
eidx := trimmedLen
|
eidx := trimmedLen
|
||||||
return Result{int32(sidx), int32(eidx),
|
return Result{int32(sidx), int32(eidx),
|
||||||
evaluateBonus(caseSensitive, runes, pattern, sidx, eidx)}
|
evaluateBonus(caseSensitive, text, pattern, sidx, eidx)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EqualMatch performs equal-match
|
// EqualMatch performs equal-match
|
||||||
func EqualMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune) Result {
|
func EqualMatch(caseSensitive bool, forward bool, text util.Chars, pattern []rune) Result {
|
||||||
// Note: EqualMatch always return a zero bonus.
|
// Note: EqualMatch always return a zero bonus.
|
||||||
if len(runes) != len(pattern) {
|
if text.Length() != len(pattern) {
|
||||||
return Result{-1, -1, 0}
|
return Result{-1, -1, 0}
|
||||||
}
|
}
|
||||||
runesStr := string(runes)
|
runesStr := text.ToString()
|
||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
runesStr = strings.ToLower(runesStr)
|
runesStr = strings.ToLower(runesStr)
|
||||||
}
|
}
|
||||||
|
@@ -3,13 +3,15 @@ package algo
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func assertMatch(t *testing.T, fun func(bool, bool, []rune, []rune) Result, caseSensitive, forward bool, input, pattern string, sidx int32, eidx int32, bonus int32) {
|
func assertMatch(t *testing.T, fun func(bool, bool, util.Chars, []rune) Result, caseSensitive, forward bool, input, pattern string, sidx int32, eidx int32, bonus int32) {
|
||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
pattern = strings.ToLower(pattern)
|
pattern = strings.ToLower(pattern)
|
||||||
}
|
}
|
||||||
res := fun(caseSensitive, forward, []rune(input), []rune(pattern))
|
res := fun(caseSensitive, forward, util.RunesToChars([]rune(input)), []rune(pattern))
|
||||||
if res.Start != sidx {
|
if res.Start != sidx {
|
||||||
t.Errorf("Invalid start index: %d (expected: %d, %s / %s)", res.Start, sidx, input, pattern)
|
t.Errorf("Invalid start index: %d (expected: %d, %s / %s)", res.Start, sidx, input, pattern)
|
||||||
}
|
}
|
||||||
|
@@ -49,7 +49,7 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
|||||||
prev := str[idx:offset[0]]
|
prev := str[idx:offset[0]]
|
||||||
output.WriteString(prev)
|
output.WriteString(prev)
|
||||||
if proc != nil && !proc(prev, state) {
|
if proc != nil && !proc(prev, state) {
|
||||||
break
|
return "", nil, nil
|
||||||
}
|
}
|
||||||
newState := interpretCode(str[offset[0]:offset[1]], state)
|
newState := interpretCode(str[offset[0]:offset[1]], state)
|
||||||
|
|
||||||
|
@@ -3,6 +3,8 @@ package fzf
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestChunkList(t *testing.T) {
|
func TestChunkList(t *testing.T) {
|
||||||
@@ -10,7 +12,7 @@ func TestChunkList(t *testing.T) {
|
|||||||
sortCriteria = []criterion{byMatchLen, byLength}
|
sortCriteria = []criterion{byMatchLen, byLength}
|
||||||
|
|
||||||
cl := NewChunkList(func(s []byte, i int) *Item {
|
cl := NewChunkList(func(s []byte, i int) *Item {
|
||||||
return &Item{text: []rune(string(s)), rank: buildEmptyRank(int32(i * 2))}
|
return &Item{text: util.ToChars(s), rank: buildEmptyRank(int32(i * 2))}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Snapshot
|
// Snapshot
|
||||||
@@ -42,8 +44,8 @@ func TestChunkList(t *testing.T) {
|
|||||||
last := func(arr [5]int32) int32 {
|
last := func(arr [5]int32) int32 {
|
||||||
return arr[len(arr)-1]
|
return arr[len(arr)-1]
|
||||||
}
|
}
|
||||||
if string((*chunk1)[0].text) != "hello" || last((*chunk1)[0].rank) != 0 ||
|
if (*chunk1)[0].text.ToString() != "hello" || last((*chunk1)[0].rank) != 0 ||
|
||||||
string((*chunk1)[1].text) != "world" || last((*chunk1)[1].rank) != 2 {
|
(*chunk1)[1].text.ToString() != "world" || last((*chunk1)[1].rank) != 2 {
|
||||||
t.Error("Invalid data")
|
t.Error("Invalid data")
|
||||||
}
|
}
|
||||||
if chunk1.IsFull() {
|
if chunk1.IsFull() {
|
||||||
|
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Current version
|
// Current version
|
||||||
version = "0.13.2"
|
version = "0.13.4"
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||||
|
35
src/core.go
35
src/core.go
@@ -63,29 +63,29 @@ func Run(opts *Options) {
|
|||||||
eventBox := util.NewEventBox()
|
eventBox := util.NewEventBox()
|
||||||
|
|
||||||
// ANSI code processor
|
// ANSI code processor
|
||||||
ansiProcessor := func(data []byte) ([]rune, []ansiOffset) {
|
ansiProcessor := func(data []byte) (util.Chars, []ansiOffset) {
|
||||||
return util.BytesToRunes(data), nil
|
return util.ToChars(data), nil
|
||||||
}
|
}
|
||||||
ansiProcessorRunes := func(data []rune) ([]rune, []ansiOffset) {
|
ansiProcessorRunes := func(data []rune) (util.Chars, []ansiOffset) {
|
||||||
return data, nil
|
return util.RunesToChars(data), nil
|
||||||
}
|
}
|
||||||
if opts.Ansi {
|
if opts.Ansi {
|
||||||
if opts.Theme != nil {
|
if opts.Theme != nil {
|
||||||
var state *ansiState
|
var state *ansiState
|
||||||
ansiProcessor = func(data []byte) ([]rune, []ansiOffset) {
|
ansiProcessor = func(data []byte) (util.Chars, []ansiOffset) {
|
||||||
trimmed, offsets, newState := extractColor(string(data), state, nil)
|
trimmed, offsets, newState := extractColor(string(data), state, nil)
|
||||||
state = newState
|
state = newState
|
||||||
return []rune(trimmed), offsets
|
return util.RunesToChars([]rune(trimmed)), offsets
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// When color is disabled but ansi option is given,
|
// When color is disabled but ansi option is given,
|
||||||
// we simply strip out ANSI codes from the input
|
// we simply strip out ANSI codes from the input
|
||||||
ansiProcessor = func(data []byte) ([]rune, []ansiOffset) {
|
ansiProcessor = func(data []byte) (util.Chars, []ansiOffset) {
|
||||||
trimmed, _, _ := extractColor(string(data), nil, nil)
|
trimmed, _, _ := extractColor(string(data), nil, nil)
|
||||||
return []rune(trimmed), nil
|
return util.RunesToChars([]rune(trimmed)), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ansiProcessorRunes = func(data []rune) ([]rune, []ansiOffset) {
|
ansiProcessorRunes = func(data []rune) (util.Chars, []ansiOffset) {
|
||||||
return ansiProcessor([]byte(string(data)))
|
return ansiProcessor([]byte(string(data)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,29 +100,30 @@ func Run(opts *Options) {
|
|||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
runes, colors := ansiProcessor(data)
|
chars, colors := ansiProcessor(data)
|
||||||
return &Item{
|
return &Item{
|
||||||
text: runes,
|
text: chars,
|
||||||
colors: colors,
|
colors: colors,
|
||||||
rank: buildEmptyRank(int32(index))}
|
rank: buildEmptyRank(int32(index))}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
chunkList = NewChunkList(func(data []byte, index int) *Item {
|
chunkList = NewChunkList(func(data []byte, index int) *Item {
|
||||||
runes := util.BytesToRunes(data)
|
chars := util.ToChars(data)
|
||||||
tokens := Tokenize(runes, opts.Delimiter)
|
tokens := Tokenize(chars, opts.Delimiter)
|
||||||
trans := Transform(tokens, opts.WithNth)
|
trans := Transform(tokens, opts.WithNth)
|
||||||
if len(header) < opts.HeaderLines {
|
if len(header) < opts.HeaderLines {
|
||||||
header = append(header, string(joinTokens(trans)))
|
header = append(header, string(joinTokens(trans)))
|
||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
textRunes := joinTokens(trans)
|
||||||
item := Item{
|
item := Item{
|
||||||
text: joinTokens(trans),
|
text: util.RunesToChars(textRunes),
|
||||||
origText: &runes,
|
origText: &data,
|
||||||
colors: nil,
|
colors: nil,
|
||||||
rank: buildEmptyRank(int32(index))}
|
rank: buildEmptyRank(int32(index))}
|
||||||
|
|
||||||
trimmed, colors := ansiProcessorRunes(item.text)
|
trimmed, colors := ansiProcessorRunes(textRunes)
|
||||||
item.text = trimmed
|
item.text = trimmed
|
||||||
item.colors = colors
|
item.colors = colors
|
||||||
return &item
|
return &item
|
||||||
@@ -170,7 +171,7 @@ func Run(opts *Options) {
|
|||||||
func(runes []byte) bool {
|
func(runes []byte) bool {
|
||||||
item := chunkList.trans(runes, 0)
|
item := chunkList.trans(runes, 0)
|
||||||
if item != nil && pattern.MatchItem(item) {
|
if item != nil && pattern.MatchItem(item) {
|
||||||
fmt.Println(string(item.text))
|
fmt.Println(item.text.ToString())
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
16
src/item.go
16
src/item.go
@@ -4,6 +4,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/curses"
|
"github.com/junegunn/fzf/src/curses"
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Offset holds three 32-bit integers denoting the offsets of a matched substring
|
// Offset holds three 32-bit integers denoting the offsets of a matched substring
|
||||||
@@ -17,8 +18,8 @@ type colorOffset struct {
|
|||||||
|
|
||||||
// Item represents each input line
|
// Item represents each input line
|
||||||
type Item struct {
|
type Item struct {
|
||||||
text []rune
|
text util.Chars
|
||||||
origText *[]rune
|
origText *[]byte
|
||||||
transformed []Token
|
transformed []Token
|
||||||
offsets []Offset
|
offsets []Offset
|
||||||
colors []ansiOffset
|
colors []ansiOffset
|
||||||
@@ -43,6 +44,7 @@ func buildEmptyRank(index int32) [5]int32 {
|
|||||||
return [5]int32{0, 0, 0, 0, index}
|
return [5]int32{0, 0, 0, 0, index}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Index returns ordinal index of the Item
|
||||||
func (item *Item) Index() int32 {
|
func (item *Item) Index() int32 {
|
||||||
return item.rank[4]
|
return item.rank[4]
|
||||||
}
|
}
|
||||||
@@ -91,12 +93,14 @@ func (item *Item) Rank(cache bool) [5]int32 {
|
|||||||
// If offsets is empty, lenSum will be 0, but we don't care
|
// If offsets is empty, lenSum will be 0, but we don't care
|
||||||
val = int32(lenSum)
|
val = int32(lenSum)
|
||||||
} else {
|
} else {
|
||||||
val = int32(len(item.text))
|
val = int32(item.text.Length())
|
||||||
}
|
}
|
||||||
case byBegin:
|
case byBegin:
|
||||||
// We can't just look at item.offsets[0][0] because it can be an inverse term
|
// We can't just look at item.offsets[0][0] because it can be an inverse term
|
||||||
whitePrefixLen := 0
|
whitePrefixLen := 0
|
||||||
for idx, r := range item.text {
|
numChars := item.text.Length()
|
||||||
|
for idx := 0; idx < numChars; idx++ {
|
||||||
|
r := item.text.Get(idx)
|
||||||
whitePrefixLen = idx
|
whitePrefixLen = idx
|
||||||
if idx == minBegin || r != ' ' && r != '\t' {
|
if idx == minBegin || r != ' ' && r != '\t' {
|
||||||
break
|
break
|
||||||
@@ -105,7 +109,7 @@ func (item *Item) Rank(cache bool) [5]int32 {
|
|||||||
val = int32(minBegin - whitePrefixLen)
|
val = int32(minBegin - whitePrefixLen)
|
||||||
case byEnd:
|
case byEnd:
|
||||||
if prevEnd > 0 {
|
if prevEnd > 0 {
|
||||||
val = int32(1 + len(item.text) - prevEnd)
|
val = int32(1 + item.text.Length() - prevEnd)
|
||||||
} else {
|
} else {
|
||||||
// Empty offsets due to inverse terms.
|
// Empty offsets due to inverse terms.
|
||||||
val = 1
|
val = 1
|
||||||
@@ -134,7 +138,7 @@ func (item *Item) StringPtr(stripAnsi bool) *string {
|
|||||||
orig := string(*item.origText)
|
orig := string(*item.origText)
|
||||||
return &orig
|
return &orig
|
||||||
}
|
}
|
||||||
str := string(item.text)
|
str := item.text.ToString()
|
||||||
return &str
|
return &str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/curses"
|
"github.com/junegunn/fzf/src/curses"
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOffsetSort(t *testing.T) {
|
func TestOffsetSort(t *testing.T) {
|
||||||
@@ -44,13 +45,13 @@ func TestItemRank(t *testing.T) {
|
|||||||
sortCriteria = []criterion{byMatchLen, byLength}
|
sortCriteria = []criterion{byMatchLen, byLength}
|
||||||
|
|
||||||
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
|
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
|
||||||
item1 := Item{text: strs[0], offsets: []Offset{}, rank: [5]int32{0, 0, 0, 0, 1}}
|
item1 := Item{text: util.RunesToChars(strs[0]), offsets: []Offset{}, rank: [5]int32{0, 0, 0, 0, 1}}
|
||||||
rank1 := item1.Rank(true)
|
rank1 := item1.Rank(true)
|
||||||
if rank1[0] != math.MaxInt32 || rank1[1] != 3 || rank1[4] != 1 {
|
if rank1[0] != math.MaxInt32 || rank1[1] != 3 || rank1[4] != 1 {
|
||||||
t.Error(item1.Rank(true))
|
t.Error(item1.Rank(true))
|
||||||
}
|
}
|
||||||
// Only differ in index
|
// Only differ in index
|
||||||
item2 := Item{text: strs[0], offsets: []Offset{}}
|
item2 := Item{text: util.RunesToChars(strs[0]), offsets: []Offset{}}
|
||||||
|
|
||||||
items := []*Item{&item1, &item2}
|
items := []*Item{&item1, &item2}
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
@@ -66,10 +67,10 @@ func TestItemRank(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort by relevance
|
// Sort by relevance
|
||||||
item3 := Item{text: strs[1], rank: [5]int32{0, 0, 0, 0, 2}, offsets: []Offset{Offset{1, 3}, Offset{5, 7}}}
|
item3 := Item{text: util.RunesToChars(strs[1]), rank: [5]int32{0, 0, 0, 0, 2}, offsets: []Offset{Offset{1, 3}, Offset{5, 7}}}
|
||||||
item4 := Item{text: strs[1], rank: [5]int32{0, 0, 0, 0, 2}, offsets: []Offset{Offset{1, 2}, Offset{6, 7}}}
|
item4 := Item{text: util.RunesToChars(strs[1]), rank: [5]int32{0, 0, 0, 0, 2}, offsets: []Offset{Offset{1, 2}, Offset{6, 7}}}
|
||||||
item5 := Item{text: strs[2], rank: [5]int32{0, 0, 0, 0, 2}, offsets: []Offset{Offset{1, 3}, Offset{5, 7}}}
|
item5 := Item{text: util.RunesToChars(strs[2]), rank: [5]int32{0, 0, 0, 0, 2}, offsets: []Offset{Offset{1, 3}, Offset{5, 7}}}
|
||||||
item6 := Item{text: strs[2], rank: [5]int32{0, 0, 0, 0, 2}, offsets: []Offset{Offset{1, 2}, Offset{6, 7}}}
|
item6 := Item{text: util.RunesToChars(strs[2]), rank: [5]int32{0, 0, 0, 0, 2}, offsets: []Offset{Offset{1, 2}, Offset{6, 7}}}
|
||||||
items = []*Item{&item1, &item2, &item3, &item4, &item5, &item6}
|
items = []*Item{&item1, &item2, &item3, &item4, &item5, &item6}
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
if items[0] != &item6 || items[1] != &item4 ||
|
if items[0] != &item6 || items[1] != &item4 ||
|
||||||
|
@@ -200,7 +200,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
partialResults := make([][]*Item, numSlices)
|
partialResults := make([][]*Item, numSlices)
|
||||||
for _, _ = range slices {
|
for _ = range slices {
|
||||||
partialResult := <-resultChan
|
partialResult := <-resultChan
|
||||||
partialResults[partialResult.index] = partialResult.matches
|
partialResults[partialResult.index] = partialResult.matches
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@ package fzf
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// Merger with no data
|
// EmptyMerger is a Merger with no data
|
||||||
var EmptyMerger = NewMerger([][]*Item{}, false, false)
|
var EmptyMerger = NewMerger([][]*Item{}, false, false)
|
||||||
|
|
||||||
// Merger holds a set of locally sorted lists of items and provides the view of
|
// Merger holds a set of locally sorted lists of items and provides the view of
|
||||||
|
@@ -5,6 +5,8 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func assert(t *testing.T, cond bool, msg ...string) {
|
func assert(t *testing.T, cond bool, msg ...string) {
|
||||||
@@ -22,7 +24,7 @@ func randItem() *Item {
|
|||||||
offsets[idx] = Offset{sidx, eidx}
|
offsets[idx] = Offset{sidx, eidx}
|
||||||
}
|
}
|
||||||
return &Item{
|
return &Item{
|
||||||
text: []rune(str),
|
text: util.RunesToChars([]rune(str)),
|
||||||
rank: buildEmptyRank(rand.Int31()),
|
rank: buildEmptyRank(rand.Int31()),
|
||||||
offsets: offsets}
|
offsets: offsets}
|
||||||
}
|
}
|
||||||
|
@@ -724,6 +724,7 @@ func parseSize(str string, maxPercent float64, label string) sizeSpec {
|
|||||||
|
|
||||||
func parsePreviewWindow(opts *previewOpts, input string) {
|
func parsePreviewWindow(opts *previewOpts, input string) {
|
||||||
layout := input
|
layout := input
|
||||||
|
opts.hidden = false
|
||||||
if strings.HasSuffix(layout, ":hidden") {
|
if strings.HasSuffix(layout, ":hidden") {
|
||||||
opts.hidden = true
|
opts.hidden = true
|
||||||
layout = strings.TrimSuffix(layout, ":hidden")
|
layout = strings.TrimSuffix(layout, ":hidden")
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/curses"
|
"github.com/junegunn/fzf/src/curses"
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDelimiterRegex(t *testing.T) {
|
func TestDelimiterRegex(t *testing.T) {
|
||||||
@@ -42,24 +43,24 @@ func TestDelimiterRegex(t *testing.T) {
|
|||||||
|
|
||||||
func TestDelimiterRegexString(t *testing.T) {
|
func TestDelimiterRegexString(t *testing.T) {
|
||||||
delim := delimiterRegexp("*")
|
delim := delimiterRegexp("*")
|
||||||
tokens := Tokenize([]rune("-*--*---**---"), delim)
|
tokens := Tokenize(util.RunesToChars([]rune("-*--*---**---")), delim)
|
||||||
if delim.regex != nil ||
|
if delim.regex != nil ||
|
||||||
string(tokens[0].text) != "-*" ||
|
tokens[0].text.ToString() != "-*" ||
|
||||||
string(tokens[1].text) != "--*" ||
|
tokens[1].text.ToString() != "--*" ||
|
||||||
string(tokens[2].text) != "---*" ||
|
tokens[2].text.ToString() != "---*" ||
|
||||||
string(tokens[3].text) != "*" ||
|
tokens[3].text.ToString() != "*" ||
|
||||||
string(tokens[4].text) != "---" {
|
tokens[4].text.ToString() != "---" {
|
||||||
t.Errorf("%s %s %d", delim, tokens, len(tokens))
|
t.Errorf("%s %s %d", delim, tokens, len(tokens))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDelimiterRegexRegex(t *testing.T) {
|
func TestDelimiterRegexRegex(t *testing.T) {
|
||||||
delim := delimiterRegexp("--\\*")
|
delim := delimiterRegexp("--\\*")
|
||||||
tokens := Tokenize([]rune("-*--*---**---"), delim)
|
tokens := Tokenize(util.RunesToChars([]rune("-*--*---**---")), delim)
|
||||||
if delim.str != nil ||
|
if delim.str != nil ||
|
||||||
string(tokens[0].text) != "-*--*" ||
|
tokens[0].text.ToString() != "-*--*" ||
|
||||||
string(tokens[1].text) != "---*" ||
|
tokens[1].text.ToString() != "---*" ||
|
||||||
string(tokens[2].text) != "*---" {
|
tokens[2].text.ToString() != "*---" {
|
||||||
t.Errorf("%s %d", tokens, len(tokens))
|
t.Errorf("%s %d", tokens, len(tokens))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -352,14 +353,14 @@ func TestDefaultCtrlNP(t *testing.T) {
|
|||||||
check([]string{hist, "--bind=ctrl-p:accept"}, curses.CtrlP, actAccept)
|
check([]string{hist, "--bind=ctrl-p:accept"}, curses.CtrlP, actAccept)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToggle(t *testing.T) {
|
func optsFor(words ...string) *Options {
|
||||||
optsFor := func(words ...string) *Options {
|
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
parseOptions(opts, words)
|
parseOptions(opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToggle(t *testing.T) {
|
||||||
opts := optsFor()
|
opts := optsFor()
|
||||||
if opts.ToggleSort {
|
if opts.ToggleSort {
|
||||||
t.Error()
|
t.Error()
|
||||||
@@ -375,3 +376,31 @@ func TestToggle(t *testing.T) {
|
|||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPreviewOpts(t *testing.T) {
|
||||||
|
opts := optsFor()
|
||||||
|
if !(opts.Preview.command == "" &&
|
||||||
|
opts.Preview.hidden == false &&
|
||||||
|
opts.Preview.position == posRight &&
|
||||||
|
opts.Preview.size.percent == true &&
|
||||||
|
opts.Preview.size.size == 50) {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
opts = optsFor("--preview", "cat {}", "--preview-window=left:15:hidden")
|
||||||
|
if !(opts.Preview.command == "cat {}" &&
|
||||||
|
opts.Preview.hidden == true &&
|
||||||
|
opts.Preview.position == posLeft &&
|
||||||
|
opts.Preview.size.percent == false &&
|
||||||
|
opts.Preview.size.size == 15+2) {
|
||||||
|
t.Error(opts.Preview)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts = optsFor("--preview-window=left:15:hidden", "--preview-window=down")
|
||||||
|
if !(opts.Preview.command == "" &&
|
||||||
|
opts.Preview.hidden == false &&
|
||||||
|
opts.Preview.position == posDown &&
|
||||||
|
opts.Preview.size.percent == true &&
|
||||||
|
opts.Preview.size.size == 50) {
|
||||||
|
t.Error(opts.Preview)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -49,7 +49,7 @@ type Pattern struct {
|
|||||||
cacheable bool
|
cacheable bool
|
||||||
delimiter Delimiter
|
delimiter Delimiter
|
||||||
nth []Range
|
nth []Range
|
||||||
procFun map[termType]func(bool, bool, []rune, []rune) algo.Result
|
procFun map[termType]func(bool, bool, util.Chars, []rune) algo.Result
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -125,7 +125,7 @@ func BuildPattern(fuzzy bool, extended bool, caseMode Case, forward bool,
|
|||||||
cacheable: cacheable,
|
cacheable: cacheable,
|
||||||
nth: nth,
|
nth: nth,
|
||||||
delimiter: delimiter,
|
delimiter: delimiter,
|
||||||
procFun: make(map[termType]func(bool, bool, []rune, []rune) algo.Result)}
|
procFun: make(map[termType]func(bool, bool, util.Chars, []rune) algo.Result)}
|
||||||
|
|
||||||
ptr.procFun[termFuzzy] = algo.FuzzyMatch
|
ptr.procFun[termFuzzy] = algo.FuzzyMatch
|
||||||
ptr.procFun[termEqual] = algo.EqualMatch
|
ptr.procFun[termEqual] = algo.EqualMatch
|
||||||
@@ -361,19 +361,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, trimLength: util.TrimLen(item.text)}}
|
ret = []Token{Token{text: item.text, prefixLength: 0, trimLength: item.text.TrimLength()}}
|
||||||
}
|
}
|
||||||
item.transformed = ret
|
item.transformed = ret
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) iter(pfun func(bool, bool, []rune, []rune) algo.Result,
|
func (p *Pattern) iter(pfun func(bool, bool, util.Chars, []rune) algo.Result,
|
||||||
tokens []Token, caseSensitive bool, forward bool, pattern []rune) (Offset, int32) {
|
tokens []Token, caseSensitive bool, forward bool, pattern []rune) (Offset, int32) {
|
||||||
for _, part := range tokens {
|
for _, part := range tokens {
|
||||||
prefixLength := int32(part.prefixLength)
|
prefixLength := int32(part.prefixLength)
|
||||||
if res := pfun(caseSensitive, forward, part.text, pattern); res.Start >= 0 {
|
if res := pfun(caseSensitive, forward, part.text, pattern); res.Start >= 0 {
|
||||||
var sidx int32 = res.Start + prefixLength
|
sidx := res.Start + prefixLength
|
||||||
var eidx int32 = res.End + prefixLength
|
eidx := res.End + prefixLength
|
||||||
return Offset{sidx, eidx, int32(part.trimLength)}, res.Bonus
|
return Offset{sidx, eidx, int32(part.trimLength)}, res.Bonus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/algo"
|
"github.com/junegunn/fzf/src/algo"
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseTermsExtended(t *testing.T) {
|
func TestParseTermsExtended(t *testing.T) {
|
||||||
@@ -71,7 +72,7 @@ func TestExact(t *testing.T) {
|
|||||||
pattern := BuildPattern(true, true, CaseSmart, true,
|
pattern := BuildPattern(true, true, CaseSmart, true,
|
||||||
[]Range{}, Delimiter{}, []rune("'abc"))
|
[]Range{}, Delimiter{}, []rune("'abc"))
|
||||||
res := algo.ExactMatchNaive(
|
res := algo.ExactMatchNaive(
|
||||||
pattern.caseSensitive, pattern.forward, []rune("aabbcc abc"), pattern.termSets[0][0].text)
|
pattern.caseSensitive, pattern.forward, util.RunesToChars([]rune("aabbcc abc")), pattern.termSets[0][0].text)
|
||||||
if res.Start != 7 || res.End != 10 {
|
if res.Start != 7 || res.End != 10 {
|
||||||
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
||||||
}
|
}
|
||||||
@@ -84,7 +85,7 @@ func TestEqual(t *testing.T) {
|
|||||||
|
|
||||||
match := func(str string, sidxExpected int32, eidxExpected int32) {
|
match := func(str string, sidxExpected int32, eidxExpected int32) {
|
||||||
res := algo.EqualMatch(
|
res := algo.EqualMatch(
|
||||||
pattern.caseSensitive, pattern.forward, []rune(str), pattern.termSets[0][0].text)
|
pattern.caseSensitive, pattern.forward, util.RunesToChars([]rune(str)), pattern.termSets[0][0].text)
|
||||||
if res.Start != sidxExpected || res.End != eidxExpected {
|
if res.Start != sidxExpected || res.End != eidxExpected {
|
||||||
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
||||||
}
|
}
|
||||||
@@ -120,20 +121,20 @@ func TestCaseSensitivity(t *testing.T) {
|
|||||||
|
|
||||||
func TestOrigTextAndTransformed(t *testing.T) {
|
func TestOrigTextAndTransformed(t *testing.T) {
|
||||||
pattern := BuildPattern(true, true, CaseSmart, true, []Range{}, Delimiter{}, []rune("jg"))
|
pattern := BuildPattern(true, true, CaseSmart, true, []Range{}, Delimiter{}, []rune("jg"))
|
||||||
tokens := Tokenize([]rune("junegunn"), Delimiter{})
|
tokens := Tokenize(util.RunesToChars([]rune("junegunn")), Delimiter{})
|
||||||
trans := Transform(tokens, []Range{Range{1, 1}})
|
trans := Transform(tokens, []Range{Range{1, 1}})
|
||||||
|
|
||||||
origRunes := []rune("junegunn.choi")
|
origBytes := []byte("junegunn.choi")
|
||||||
for _, extended := range []bool{false, true} {
|
for _, extended := range []bool{false, true} {
|
||||||
chunk := Chunk{
|
chunk := Chunk{
|
||||||
&Item{
|
&Item{
|
||||||
text: []rune("junegunn"),
|
text: util.RunesToChars([]rune("junegunn")),
|
||||||
origText: &origRunes,
|
origText: &origBytes,
|
||||||
transformed: trans},
|
transformed: trans},
|
||||||
}
|
}
|
||||||
pattern.extended = extended
|
pattern.extended = extended
|
||||||
matches := pattern.matchChunk(&chunk)
|
matches := pattern.matchChunk(&chunk)
|
||||||
if string(matches[0].text) != "junegunn" || string(*matches[0].origText) != "junegunn.choi" ||
|
if matches[0].text.ToString() != "junegunn" || string(*matches[0].origText) != "junegunn.choi" ||
|
||||||
matches[0].offsets[0][0] != 0 || matches[0].offsets[0][1] != 5 ||
|
matches[0].offsets[0][0] != 0 || matches[0].offsets[0][1] != 5 ||
|
||||||
!reflect.DeepEqual(matches[0].transformed, trans) {
|
!reflect.DeepEqual(matches[0].transformed, trans) {
|
||||||
t.Error("Invalid match result", matches)
|
t.Error("Invalid match result", matches)
|
||||||
|
@@ -564,7 +564,7 @@ func (t *Terminal) printHeader() {
|
|||||||
trimmed, colors, newState := extractColor(lineStr, state, nil)
|
trimmed, colors, newState := extractColor(lineStr, state, nil)
|
||||||
state = newState
|
state = newState
|
||||||
item := &Item{
|
item := &Item{
|
||||||
text: []rune(trimmed),
|
text: util.RunesToChars([]rune(trimmed)),
|
||||||
colors: colors,
|
colors: colors,
|
||||||
rank: buildEmptyRank(0)}
|
rank: buildEmptyRank(0)}
|
||||||
|
|
||||||
@@ -656,6 +656,17 @@ func trimLeft(runes []rune, width int) ([]rune, int32) {
|
|||||||
return runes, trimmed
|
return runes, trimmed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func overflow(runes []rune, max int) bool {
|
||||||
|
l := 0
|
||||||
|
for _, r := range runes {
|
||||||
|
l += runeWidth(r, l)
|
||||||
|
if l > max {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int, current bool) {
|
func (t *Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int, current bool) {
|
||||||
var maxe int
|
var maxe int
|
||||||
for _, offset := range item.offsets {
|
for _, offset := range item.offsets {
|
||||||
@@ -663,22 +674,20 @@ func (t *Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Overflow
|
// Overflow
|
||||||
text := make([]rune, len(item.text))
|
text := make([]rune, item.text.Length())
|
||||||
copy(text, item.text)
|
copy(text, item.text.ToRunes())
|
||||||
offsets := item.colorOffsets(col2, bold, current)
|
offsets := item.colorOffsets(col2, bold, current)
|
||||||
maxWidth := t.window.Width - 3
|
maxWidth := t.window.Width - 3
|
||||||
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
|
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
|
||||||
fullWidth := displayWidth(text)
|
if overflow(text, maxWidth) {
|
||||||
if fullWidth > maxWidth {
|
|
||||||
if t.hscroll {
|
if t.hscroll {
|
||||||
// Stri..
|
// Stri..
|
||||||
matchEndWidth := displayWidth(text[:maxe])
|
if !overflow(text[:maxe], maxWidth-2) {
|
||||||
if matchEndWidth <= maxWidth-2 {
|
|
||||||
text, _ = trimRight(text, maxWidth-2)
|
text, _ = trimRight(text, maxWidth-2)
|
||||||
text = append(text, []rune("..")...)
|
text = append(text, []rune("..")...)
|
||||||
} else {
|
} else {
|
||||||
// Stri..
|
// Stri..
|
||||||
if matchEndWidth < fullWidth-2 {
|
if overflow(text[maxe:], 2) {
|
||||||
text = append(text[:maxe], []rune("..")...)
|
text = append(text[:maxe], []rune("..")...)
|
||||||
}
|
}
|
||||||
// ..ri..
|
// ..ri..
|
||||||
|
@@ -18,7 +18,7 @@ type Range struct {
|
|||||||
|
|
||||||
// Token contains the tokenized part of the strings and its prefix length
|
// Token contains the tokenized part of the strings and its prefix length
|
||||||
type Token struct {
|
type Token struct {
|
||||||
text []rune
|
text util.Chars
|
||||||
prefixLength int
|
prefixLength int
|
||||||
trimLength int
|
trimLength int
|
||||||
}
|
}
|
||||||
@@ -75,15 +75,15 @@ func ParseRange(str *string) (Range, bool) {
|
|||||||
return newRange(n, n), true
|
return newRange(n, n), true
|
||||||
}
|
}
|
||||||
|
|
||||||
func withPrefixLengths(tokens [][]rune, begin int) []Token {
|
func withPrefixLengths(tokens []util.Chars, begin int) []Token {
|
||||||
ret := make([]Token, len(tokens))
|
ret := make([]Token, len(tokens))
|
||||||
|
|
||||||
prefixLength := begin
|
prefixLength := begin
|
||||||
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{token, prefixLength, util.TrimLen(token)}
|
ret[idx] = Token{token, prefixLength, token.TrimLength()}
|
||||||
prefixLength += len(token)
|
prefixLength += token.Length()
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
@@ -94,59 +94,60 @@ const (
|
|||||||
awkWhite
|
awkWhite
|
||||||
)
|
)
|
||||||
|
|
||||||
func awkTokenizer(input []rune) ([][]rune, int) {
|
func awkTokenizer(input util.Chars) ([]util.Chars, int) {
|
||||||
// 9, 32
|
// 9, 32
|
||||||
ret := [][]rune{}
|
ret := []util.Chars{}
|
||||||
str := []rune{}
|
|
||||||
prefixLength := 0
|
prefixLength := 0
|
||||||
state := awkNil
|
state := awkNil
|
||||||
for _, r := range input {
|
numChars := input.Length()
|
||||||
|
begin := 0
|
||||||
|
end := 0
|
||||||
|
for idx := 0; idx < numChars; idx++ {
|
||||||
|
r := input.Get(idx)
|
||||||
white := r == 9 || r == 32
|
white := r == 9 || r == 32
|
||||||
switch state {
|
switch state {
|
||||||
case awkNil:
|
case awkNil:
|
||||||
if white {
|
if white {
|
||||||
prefixLength++
|
prefixLength++
|
||||||
} else {
|
} else {
|
||||||
state = awkBlack
|
state, begin, end = awkBlack, idx, idx+1
|
||||||
str = append(str, r)
|
|
||||||
}
|
}
|
||||||
case awkBlack:
|
case awkBlack:
|
||||||
str = append(str, r)
|
end = idx + 1
|
||||||
if white {
|
if white {
|
||||||
state = awkWhite
|
state = awkWhite
|
||||||
}
|
}
|
||||||
case awkWhite:
|
case awkWhite:
|
||||||
if white {
|
if white {
|
||||||
str = append(str, r)
|
end = idx + 1
|
||||||
} else {
|
} else {
|
||||||
ret = append(ret, str)
|
ret = append(ret, input.Slice(begin, end))
|
||||||
state = awkBlack
|
state, begin, end = awkBlack, idx, idx+1
|
||||||
str = []rune{r}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(str) > 0 {
|
if begin < end {
|
||||||
ret = append(ret, str)
|
ret = append(ret, input.Slice(begin, end))
|
||||||
}
|
}
|
||||||
return ret, prefixLength
|
return ret, prefixLength
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tokenize tokenizes the given string with the delimiter
|
// Tokenize tokenizes the given string with the delimiter
|
||||||
func Tokenize(runes []rune, delimiter Delimiter) []Token {
|
func Tokenize(text util.Chars, delimiter Delimiter) []Token {
|
||||||
if delimiter.str == nil && delimiter.regex == nil {
|
if delimiter.str == nil && delimiter.regex == nil {
|
||||||
// AWK-style (\S+\s*)
|
// AWK-style (\S+\s*)
|
||||||
tokens, prefixLength := awkTokenizer(runes)
|
tokens, prefixLength := awkTokenizer(text)
|
||||||
return withPrefixLengths(tokens, prefixLength)
|
return withPrefixLengths(tokens, prefixLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokens []string
|
|
||||||
if delimiter.str != nil {
|
if delimiter.str != nil {
|
||||||
tokens = strings.Split(string(runes), *delimiter.str)
|
return withPrefixLengths(text.Split(*delimiter.str), 0)
|
||||||
for i := 0; i < len(tokens)-1; i++ {
|
|
||||||
tokens[i] = tokens[i] + *delimiter.str
|
|
||||||
}
|
}
|
||||||
} else if delimiter.regex != nil {
|
|
||||||
str := string(runes)
|
// FIXME performance
|
||||||
|
var tokens []string
|
||||||
|
if delimiter.regex != nil {
|
||||||
|
str := text.ToString()
|
||||||
for len(str) > 0 {
|
for len(str) > 0 {
|
||||||
loc := delimiter.regex.FindStringIndex(str)
|
loc := delimiter.regex.FindStringIndex(str)
|
||||||
if loc == nil {
|
if loc == nil {
|
||||||
@@ -157,9 +158,9 @@ func Tokenize(runes []rune, delimiter Delimiter) []Token {
|
|||||||
str = str[last:]
|
str = str[last:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
asRunes := make([][]rune, len(tokens))
|
asRunes := make([]util.Chars, len(tokens))
|
||||||
for i, token := range tokens {
|
for i, token := range tokens {
|
||||||
asRunes[i] = []rune(token)
|
asRunes[i] = util.RunesToChars([]rune(token))
|
||||||
}
|
}
|
||||||
return withPrefixLengths(asRunes, 0)
|
return withPrefixLengths(asRunes, 0)
|
||||||
}
|
}
|
||||||
@@ -167,7 +168,7 @@ func Tokenize(runes []rune, delimiter Delimiter) []Token {
|
|||||||
func joinTokens(tokens []Token) []rune {
|
func joinTokens(tokens []Token) []rune {
|
||||||
ret := []rune{}
|
ret := []rune{}
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
ret = append(ret, token.text...)
|
ret = append(ret, token.text.ToRunes()...)
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
@@ -175,7 +176,7 @@ func joinTokens(tokens []Token) []rune {
|
|||||||
func joinTokensAsRunes(tokens []Token) []rune {
|
func joinTokensAsRunes(tokens []Token) []rune {
|
||||||
ret := []rune{}
|
ret := []rune{}
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
ret = append(ret, token.text...)
|
ret = append(ret, token.text.ToRunes()...)
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
@@ -185,19 +186,19 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
|||||||
transTokens := make([]Token, len(withNth))
|
transTokens := make([]Token, len(withNth))
|
||||||
numTokens := len(tokens)
|
numTokens := len(tokens)
|
||||||
for idx, r := range withNth {
|
for idx, r := range withNth {
|
||||||
part := []rune{}
|
parts := []util.Chars{}
|
||||||
minIdx := 0
|
minIdx := 0
|
||||||
if r.begin == r.end {
|
if r.begin == r.end {
|
||||||
idx := r.begin
|
idx := r.begin
|
||||||
if idx == rangeEllipsis {
|
if idx == rangeEllipsis {
|
||||||
part = append(part, joinTokensAsRunes(tokens)...)
|
parts = append(parts, util.RunesToChars(joinTokensAsRunes(tokens)))
|
||||||
} else {
|
} else {
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
idx += numTokens + 1
|
idx += numTokens + 1
|
||||||
}
|
}
|
||||||
if idx >= 1 && idx <= numTokens {
|
if idx >= 1 && idx <= numTokens {
|
||||||
minIdx = idx - 1
|
minIdx = idx - 1
|
||||||
part = append(part, tokens[idx-1].text...)
|
parts = append(parts, tokens[idx-1].text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -224,17 +225,32 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
|||||||
minIdx = util.Max(0, begin-1)
|
minIdx = util.Max(0, begin-1)
|
||||||
for idx := begin; idx <= end; idx++ {
|
for idx := begin; idx <= end; idx++ {
|
||||||
if idx >= 1 && idx <= numTokens {
|
if idx >= 1 && idx <= numTokens {
|
||||||
part = append(part, tokens[idx-1].text...)
|
parts = append(parts, tokens[idx-1].text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Merge multiple parts
|
||||||
|
var merged util.Chars
|
||||||
|
switch len(parts) {
|
||||||
|
case 0:
|
||||||
|
merged = util.RunesToChars([]rune{})
|
||||||
|
case 1:
|
||||||
|
merged = parts[0]
|
||||||
|
default:
|
||||||
|
runes := []rune{}
|
||||||
|
for _, part := range parts {
|
||||||
|
runes = append(runes, part.ToRunes()...)
|
||||||
|
}
|
||||||
|
merged = util.RunesToChars(runes)
|
||||||
|
}
|
||||||
|
|
||||||
var prefixLength int
|
var prefixLength int
|
||||||
if minIdx < numTokens {
|
if minIdx < numTokens {
|
||||||
prefixLength = tokens[minIdx].prefixLength
|
prefixLength = tokens[minIdx].prefixLength
|
||||||
} else {
|
} else {
|
||||||
prefixLength = 0
|
prefixLength = 0
|
||||||
}
|
}
|
||||||
transTokens[idx] = Token{part, prefixLength, util.TrimLen(part)}
|
transTokens[idx] = Token{merged, prefixLength, merged.TrimLength()}
|
||||||
}
|
}
|
||||||
return transTokens
|
return transTokens
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
)
|
||||||
|
|
||||||
func TestParseRange(t *testing.T) {
|
func TestParseRange(t *testing.T) {
|
||||||
{
|
{
|
||||||
@@ -43,23 +47,23 @@ func TestParseRange(t *testing.T) {
|
|||||||
func TestTokenize(t *testing.T) {
|
func TestTokenize(t *testing.T) {
|
||||||
// AWK-style
|
// AWK-style
|
||||||
input := " abc: def: ghi "
|
input := " abc: def: ghi "
|
||||||
tokens := Tokenize([]rune(input), Delimiter{})
|
tokens := Tokenize(util.RunesToChars([]rune(input)), Delimiter{})
|
||||||
if string(tokens[0].text) != "abc: " || tokens[0].prefixLength != 2 || tokens[0].trimLength != 4 {
|
if tokens[0].text.ToString() != "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(util.RunesToChars([]rune(input)), delimiterRegexp(":"))
|
||||||
if string(tokens[0].text) != " abc:" || tokens[0].prefixLength != 0 || tokens[0].trimLength != 4 {
|
if tokens[0].text.ToString() != " 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(util.RunesToChars([]rune(input)), delimiterRegexp("\\s+"))
|
||||||
if string(tokens[0].text) != " " || tokens[0].prefixLength != 0 || tokens[0].trimLength != 0 ||
|
if tokens[0].text.ToString() != " " || tokens[0].prefixLength != 0 || tokens[0].trimLength != 0 ||
|
||||||
string(tokens[1].text) != "abc: " || tokens[1].prefixLength != 2 || tokens[1].trimLength != 4 ||
|
tokens[1].text.ToString() != "abc: " || tokens[1].prefixLength != 2 || tokens[1].trimLength != 4 ||
|
||||||
string(tokens[2].text) != "def: " || tokens[2].prefixLength != 8 || tokens[2].trimLength != 4 ||
|
tokens[2].text.ToString() != "def: " || tokens[2].prefixLength != 8 || tokens[2].trimLength != 4 ||
|
||||||
string(tokens[3].text) != "ghi " || tokens[3].prefixLength != 14 || tokens[3].trimLength != 3 {
|
tokens[3].text.ToString() != "ghi " || tokens[3].prefixLength != 14 || tokens[3].trimLength != 3 {
|
||||||
t.Errorf("%s", tokens)
|
t.Errorf("%s", tokens)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +71,7 @@ func TestTokenize(t *testing.T) {
|
|||||||
func TestTransform(t *testing.T) {
|
func TestTransform(t *testing.T) {
|
||||||
input := " abc: def: ghi: jkl"
|
input := " abc: def: ghi: jkl"
|
||||||
{
|
{
|
||||||
tokens := Tokenize([]rune(input), Delimiter{})
|
tokens := Tokenize(util.RunesToChars([]rune(input)), Delimiter{})
|
||||||
{
|
{
|
||||||
ranges := splitNth("1,2,3")
|
ranges := splitNth("1,2,3")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
@@ -80,25 +84,25 @@ func TestTransform(t *testing.T) {
|
|||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
if string(joinTokens(tx)) != "abc: def: ghi: def: ghi: jklabc: " ||
|
if string(joinTokens(tx)) != "abc: def: ghi: def: ghi: jklabc: " ||
|
||||||
len(tx) != 4 ||
|
len(tx) != 4 ||
|
||||||
string(tx[0].text) != "abc: def: " || tx[0].prefixLength != 2 ||
|
tx[0].text.ToString() != "abc: def: " || tx[0].prefixLength != 2 ||
|
||||||
string(tx[1].text) != "ghi: " || tx[1].prefixLength != 14 ||
|
tx[1].text.ToString() != "ghi: " || tx[1].prefixLength != 14 ||
|
||||||
string(tx[2].text) != "def: ghi: jkl" || tx[2].prefixLength != 8 ||
|
tx[2].text.ToString() != "def: ghi: jkl" || tx[2].prefixLength != 8 ||
|
||||||
string(tx[3].text) != "abc: " || tx[3].prefixLength != 2 {
|
tx[3].text.ToString() != "abc: " || tx[3].prefixLength != 2 {
|
||||||
t.Errorf("%s", tx)
|
t.Errorf("%s", tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
tokens := Tokenize([]rune(input), delimiterRegexp(":"))
|
tokens := Tokenize(util.RunesToChars([]rune(input)), delimiterRegexp(":"))
|
||||||
{
|
{
|
||||||
ranges := splitNth("1..2,3,2..,1")
|
ranges := splitNth("1..2,3,2..,1")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
if string(joinTokens(tx)) != " abc: def: ghi: def: ghi: jkl abc:" ||
|
if string(joinTokens(tx)) != " abc: def: ghi: def: ghi: jkl abc:" ||
|
||||||
len(tx) != 4 ||
|
len(tx) != 4 ||
|
||||||
string(tx[0].text) != " abc: def:" || tx[0].prefixLength != 0 ||
|
tx[0].text.ToString() != " abc: def:" || tx[0].prefixLength != 0 ||
|
||||||
string(tx[1].text) != " ghi:" || tx[1].prefixLength != 12 ||
|
tx[1].text.ToString() != " ghi:" || tx[1].prefixLength != 12 ||
|
||||||
string(tx[2].text) != " def: ghi: jkl" || tx[2].prefixLength != 6 ||
|
tx[2].text.ToString() != " def: ghi: jkl" || tx[2].prefixLength != 6 ||
|
||||||
string(tx[3].text) != " abc:" || tx[3].prefixLength != 0 {
|
tx[3].text.ToString() != " abc:" || tx[3].prefixLength != 0 {
|
||||||
t.Errorf("%s", tx)
|
t.Errorf("%s", tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
156
src/util/chars.go
Normal file
156
src/util/chars.go
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Chars struct {
|
||||||
|
runes []rune
|
||||||
|
bytes []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToChars converts byte array into rune array
|
||||||
|
func ToChars(bytea []byte) Chars {
|
||||||
|
var runes []rune
|
||||||
|
ascii := true
|
||||||
|
numBytes := len(bytea)
|
||||||
|
for i := 0; i < numBytes; {
|
||||||
|
if bytea[i] < utf8.RuneSelf {
|
||||||
|
if !ascii {
|
||||||
|
runes = append(runes, rune(bytea[i]))
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
if ascii {
|
||||||
|
ascii = false
|
||||||
|
runes = make([]rune, i, numBytes)
|
||||||
|
for j := 0; j < i; j++ {
|
||||||
|
runes[j] = rune(bytea[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r, sz := utf8.DecodeRune(bytea[i:])
|
||||||
|
i += sz
|
||||||
|
runes = append(runes, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ascii {
|
||||||
|
return Chars{bytes: bytea}
|
||||||
|
}
|
||||||
|
return Chars{runes: runes}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunesToChars(runes []rune) Chars {
|
||||||
|
return Chars{runes: runes}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) Get(i int) rune {
|
||||||
|
if chars.runes != nil {
|
||||||
|
return chars.runes[i]
|
||||||
|
}
|
||||||
|
return rune(chars.bytes[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) Length() int {
|
||||||
|
if chars.runes != nil {
|
||||||
|
return len(chars.runes)
|
||||||
|
}
|
||||||
|
return len(chars.bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrimLength returns the length after trimming leading and trailing whitespaces
|
||||||
|
func (chars *Chars) TrimLength() int {
|
||||||
|
var i int
|
||||||
|
len := chars.Length()
|
||||||
|
for i = len - 1; i >= 0; i-- {
|
||||||
|
char := chars.Get(i)
|
||||||
|
if char != ' ' && char != '\t' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Completely empty
|
||||||
|
if i < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var j int
|
||||||
|
for j = 0; j < len; j++ {
|
||||||
|
char := chars.Get(j)
|
||||||
|
if char != ' ' && char != '\t' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i - j + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) TrailingWhitespaces() int {
|
||||||
|
whitespaces := 0
|
||||||
|
for i := chars.Length() - 1; i >= 0; i-- {
|
||||||
|
char := chars.Get(i)
|
||||||
|
if char != ' ' && char != '\t' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
whitespaces++
|
||||||
|
}
|
||||||
|
return whitespaces
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) ToString() string {
|
||||||
|
if chars.runes != nil {
|
||||||
|
return string(chars.runes)
|
||||||
|
}
|
||||||
|
return string(chars.bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) ToRunes() []rune {
|
||||||
|
if chars.runes != nil {
|
||||||
|
return chars.runes
|
||||||
|
}
|
||||||
|
runes := make([]rune, len(chars.bytes))
|
||||||
|
for idx, b := range chars.bytes {
|
||||||
|
runes[idx] = rune(b)
|
||||||
|
}
|
||||||
|
return runes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) Slice(b int, e int) Chars {
|
||||||
|
if chars.runes != nil {
|
||||||
|
return Chars{runes: chars.runes[b:e]}
|
||||||
|
}
|
||||||
|
return Chars{bytes: chars.bytes[b:e]}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) Split(delimiter string) []Chars {
|
||||||
|
delim := []rune(delimiter)
|
||||||
|
numChars := chars.Length()
|
||||||
|
numDelim := len(delim)
|
||||||
|
begin := 0
|
||||||
|
ret := make([]Chars, 0, 1)
|
||||||
|
|
||||||
|
for index := 0; index < numChars; {
|
||||||
|
if index+numDelim <= numChars {
|
||||||
|
match := true
|
||||||
|
for off, d := range delim {
|
||||||
|
if chars.Get(index+off) != d {
|
||||||
|
match = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Found the delimiter
|
||||||
|
if match {
|
||||||
|
incr := Max(numDelim, 1)
|
||||||
|
ret = append(ret, chars.Slice(begin, index+incr))
|
||||||
|
index += incr
|
||||||
|
begin = index
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Impossible to find the delimiter in the remaining substring
|
||||||
|
break
|
||||||
|
}
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
if begin < numChars || len(ret) == 0 {
|
||||||
|
ret = append(ret, chars.Slice(begin, numChars))
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
82
src/util/chars_test.go
Normal file
82
src/util/chars_test.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestToCharsNil(t *testing.T) {
|
||||||
|
bs := Chars{bytes: []byte{}}
|
||||||
|
if bs.bytes == nil || bs.runes != nil {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
rs := RunesToChars([]rune{})
|
||||||
|
if rs.bytes != nil || rs.runes == nil {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToCharsAscii(t *testing.T) {
|
||||||
|
chars := ToChars([]byte("foobar"))
|
||||||
|
if chars.ToString() != "foobar" || chars.runes != nil {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCharsLength(t *testing.T) {
|
||||||
|
chars := ToChars([]byte("\tabc한글 "))
|
||||||
|
if chars.Length() != 8 || chars.TrimLength() != 5 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCharsToString(t *testing.T) {
|
||||||
|
text := "\tabc한글 "
|
||||||
|
chars := ToChars([]byte(text))
|
||||||
|
if chars.ToString() != text {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrimLength(t *testing.T) {
|
||||||
|
check := func(str string, exp int) {
|
||||||
|
chars := ToChars([]byte(str))
|
||||||
|
trimmed := chars.TrimLength()
|
||||||
|
if trimmed != exp {
|
||||||
|
t.Errorf("Invalid TrimLength 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplit(t *testing.T) {
|
||||||
|
check := func(str string, delim string, tokens ...string) {
|
||||||
|
input := ToChars([]byte(str))
|
||||||
|
result := input.Split(delim)
|
||||||
|
if len(result) != len(tokens) {
|
||||||
|
t.Errorf("Invalid Split result for '%s': %d tokens found (expected %d): %s",
|
||||||
|
str, len(result), len(tokens), result)
|
||||||
|
}
|
||||||
|
for idx, token := range tokens {
|
||||||
|
if result[idx].ToString() != token {
|
||||||
|
t.Errorf("Invalid Split result for '%s': %s (expected %s)",
|
||||||
|
str, result[idx].ToString(), token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check("abc:def::", ":", "abc:", "def:", ":")
|
||||||
|
check("abc:def::", "-", "abc:def::")
|
||||||
|
check("abc", "", "a", "b", "c")
|
||||||
|
check("abc", "a", "a", "bc")
|
||||||
|
check("abc", "ab", "ab", "c")
|
||||||
|
check("abc", "abc", "abc")
|
||||||
|
check("abc", "abcd", "abc")
|
||||||
|
check("", "abcd", "")
|
||||||
|
}
|
@@ -7,18 +7,14 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Max returns the largest integer
|
// Max returns the largest integer
|
||||||
func Max(first int, items ...int) int {
|
func Max(first int, second int) int {
|
||||||
max := first
|
if first >= second {
|
||||||
for _, item := range items {
|
return first
|
||||||
if item > max {
|
|
||||||
max = item
|
|
||||||
}
|
}
|
||||||
}
|
return second
|
||||||
return max
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Min returns the smallest integer
|
// Min returns the smallest integer
|
||||||
@@ -84,58 +80,6 @@ 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 {
|
|
||||||
var i int
|
|
||||||
for i = len(runes) - 1; i >= 0; i-- {
|
|
||||||
char := runes[i]
|
|
||||||
if char != ' ' && char != '\t' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return runes[0 : i+1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// BytesToRunes converts byte array into rune array
|
|
||||||
func BytesToRunes(bytea []byte) []rune {
|
|
||||||
runes := make([]rune, 0, len(bytea))
|
|
||||||
for i := 0; i < len(bytea); {
|
|
||||||
if bytea[i] < utf8.RuneSelf {
|
|
||||||
runes = append(runes, rune(bytea[i]))
|
|
||||||
i++
|
|
||||||
} else {
|
|
||||||
r, sz := utf8.DecodeRune(bytea[i:])
|
|
||||||
i += sz
|
|
||||||
runes = append(runes, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecCommand executes the given command with $SHELL
|
// ExecCommand executes the given command with $SHELL
|
||||||
func ExecCommand(command string) *exec.Cmd {
|
func ExecCommand(command string) *exec.Cmd {
|
||||||
shell := os.Getenv("SHELL")
|
shell := os.Getenv("SHELL")
|
||||||
|
@@ -3,7 +3,7 @@ package util
|
|||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestMax(t *testing.T) {
|
func TestMax(t *testing.T) {
|
||||||
if Max(-2, 5, 1, 4, 3) != 5 {
|
if Max(-2, 5) != 5 {
|
||||||
t.Error("Invalid result")
|
t.Error("Invalid result")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,23 +20,3 @@ 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)
|
|
||||||
}
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
Execute (Setup):
|
Execute (Setup):
|
||||||
let g:dir = fnamemodify(g:vader_file, ':p:h')
|
let g:dir = fnamemodify(g:vader_file, ':p:h')
|
||||||
|
unlet! g:fzf_layout g:fzf_action g:fzf_history_dir
|
||||||
Log 'Test directory: ' . g:dir
|
Log 'Test directory: ' . g:dir
|
||||||
Save &acd
|
Save &acd
|
||||||
|
|
||||||
@@ -43,6 +44,11 @@ Execute (fzf#run with dir option and noautochdir):
|
|||||||
" No change in working directory
|
" No change in working directory
|
||||||
AssertEqual cwd, getcwd()
|
AssertEqual cwd, getcwd()
|
||||||
|
|
||||||
|
call fzf#run({'source': ['/foobar'], 'sink': 'tabe', 'dir': '/tmp', 'options': '-1'})
|
||||||
|
AssertEqual cwd, getcwd()
|
||||||
|
tabclose
|
||||||
|
AssertEqual cwd, getcwd()
|
||||||
|
|
||||||
Execute (Incomplete fzf#run with dir option and autochdir):
|
Execute (Incomplete fzf#run with dir option and autochdir):
|
||||||
set acd
|
set acd
|
||||||
let cwd = getcwd()
|
let cwd = getcwd()
|
||||||
@@ -64,6 +70,79 @@ Execute (fzf#run with dir option and autochdir when final cwd is same as dir):
|
|||||||
" Working directory changed due to &acd
|
" Working directory changed due to &acd
|
||||||
AssertEqual '/', getcwd()
|
AssertEqual '/', getcwd()
|
||||||
|
|
||||||
|
Execute (fzf#wrap):
|
||||||
|
AssertThrows fzf#wrap({'foo': 'bar'})
|
||||||
|
|
||||||
|
let opts = fzf#wrap('foobar')
|
||||||
|
Log opts
|
||||||
|
AssertEqual '~40%', opts.down
|
||||||
|
Assert opts.options =~ '--expect='
|
||||||
|
Assert !has_key(opts, 'sink')
|
||||||
|
Assert has_key(opts, 'sink*')
|
||||||
|
|
||||||
|
let opts = fzf#wrap('foobar', {}, 0)
|
||||||
|
Log opts
|
||||||
|
AssertEqual '~40%', opts.down
|
||||||
|
|
||||||
|
let opts = fzf#wrap('foobar', {}, 1)
|
||||||
|
Log opts
|
||||||
|
Assert !has_key(opts, 'down')
|
||||||
|
|
||||||
|
let opts = fzf#wrap('foobar', {'down': '50%'})
|
||||||
|
Log opts
|
||||||
|
AssertEqual '50%', opts.down
|
||||||
|
|
||||||
|
let opts = fzf#wrap('foobar', {'down': '50%'}, 1)
|
||||||
|
Log opts
|
||||||
|
Assert !has_key(opts, 'down')
|
||||||
|
|
||||||
|
let opts = fzf#wrap('foobar', {'sink': 'e'})
|
||||||
|
Log opts
|
||||||
|
AssertEqual 'e', opts.sink
|
||||||
|
Assert !has_key(opts, 'sink*')
|
||||||
|
|
||||||
|
let opts = fzf#wrap('foobar', {'options': '--reverse'})
|
||||||
|
Log opts
|
||||||
|
Assert opts.options =~ '--expect='
|
||||||
|
Assert opts.options =~ '--reverse'
|
||||||
|
|
||||||
|
let g:fzf_layout = {'window': 'enew'}
|
||||||
|
let opts = fzf#wrap('foobar')
|
||||||
|
Log opts
|
||||||
|
AssertEqual 'enew', opts.window
|
||||||
|
|
||||||
|
let opts = fzf#wrap('foobar', {}, 1)
|
||||||
|
Log opts
|
||||||
|
Assert !has_key(opts, 'window')
|
||||||
|
|
||||||
|
let opts = fzf#wrap('foobar', {'right': '50%'})
|
||||||
|
Log opts
|
||||||
|
Assert !has_key(opts, 'window')
|
||||||
|
AssertEqual '50%', opts.right
|
||||||
|
|
||||||
|
let opts = fzf#wrap('foobar', {'right': '50%'}, 1)
|
||||||
|
Log opts
|
||||||
|
Assert !has_key(opts, 'window')
|
||||||
|
Assert !has_key(opts, 'right')
|
||||||
|
|
||||||
|
let g:fzf_action = {'a': 'tabe'}
|
||||||
|
let opts = fzf#wrap('foobar')
|
||||||
|
Log opts
|
||||||
|
Assert opts.options =~ '--expect=a'
|
||||||
|
Assert !has_key(opts, 'sink')
|
||||||
|
Assert has_key(opts, 'sink*')
|
||||||
|
|
||||||
|
let opts = fzf#wrap('foobar', {'sink': 'e'})
|
||||||
|
Log opts
|
||||||
|
AssertEqual 'e', opts.sink
|
||||||
|
Assert !has_key(opts, 'sink*')
|
||||||
|
|
||||||
|
let g:fzf_history_dir = '/tmp'
|
||||||
|
let opts = fzf#wrap('foobar', {'options': '--color light'})
|
||||||
|
Log opts
|
||||||
|
Assert opts.options =~ '--history /tmp/foobar'
|
||||||
|
Assert opts.options =~ '--color light'
|
||||||
|
|
||||||
Execute (Cleanup):
|
Execute (Cleanup):
|
||||||
unlet g:dir
|
unlet g:dir
|
||||||
Restore
|
Restore
|
||||||
|
@@ -36,6 +36,10 @@ end
|
|||||||
|
|
||||||
class Shell
|
class Shell
|
||||||
class << self
|
class << self
|
||||||
|
def unsets
|
||||||
|
'unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS FZF_ALT_C_COMMAND FZF_ALT_C_OPTS FZF_CTRL_R_OPTS;'
|
||||||
|
end
|
||||||
|
|
||||||
def bash
|
def bash
|
||||||
'PS1= PROMPT_COMMAND= bash --rcfile ~/.fzf.bash'
|
'PS1= PROMPT_COMMAND= bash --rcfile ~/.fzf.bash'
|
||||||
end
|
end
|
||||||
@@ -45,6 +49,10 @@ class Shell
|
|||||||
FileUtils.cp File.expand_path('~/.fzf.zsh'), '/tmp/fzf-zsh/.zshrc'
|
FileUtils.cp File.expand_path('~/.fzf.zsh'), '/tmp/fzf-zsh/.zshrc'
|
||||||
'PS1= PROMPT_COMMAND= HISTSIZE=100 ZDOTDIR=/tmp/fzf-zsh zsh'
|
'PS1= PROMPT_COMMAND= HISTSIZE=100 ZDOTDIR=/tmp/fzf-zsh zsh'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fish
|
||||||
|
'fish'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -57,11 +65,11 @@ class Tmux
|
|||||||
@win =
|
@win =
|
||||||
case shell
|
case shell
|
||||||
when :bash
|
when :bash
|
||||||
go("new-window -d -P -F '#I' '#{Shell.bash}'").first
|
go("new-window -d -P -F '#I' '#{Shell.unsets + Shell.bash}'").first
|
||||||
when :zsh
|
when :zsh
|
||||||
go("new-window -d -P -F '#I' '#{Shell.zsh}'").first
|
go("new-window -d -P -F '#I' '#{Shell.unsets + Shell.zsh}'").first
|
||||||
when :fish
|
when :fish
|
||||||
go("new-window -d -P -F '#I' 'fish'").first
|
go("new-window -d -P -F '#I' '#{Shell.unsets + Shell.fish}'").first
|
||||||
else
|
else
|
||||||
raise "Unknown shell: #{shell}"
|
raise "Unknown shell: #{shell}"
|
||||||
end
|
end
|
||||||
@@ -90,6 +98,10 @@ class Tmux
|
|||||||
go("send-keys -t #{target} #{args}")
|
go("send-keys -t #{target} #{args}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def paste str
|
||||||
|
%x[tmux setb '#{str.gsub("'", "'\\''")}' \\; pasteb -t #{win} \\; send-keys -t #{win} Enter]
|
||||||
|
end
|
||||||
|
|
||||||
def capture pane = 0
|
def capture pane = 0
|
||||||
File.unlink TEMPNAME while File.exists? TEMPNAME
|
File.unlink TEMPNAME while File.exists? TEMPNAME
|
||||||
wait do
|
wait do
|
||||||
@@ -149,12 +161,6 @@ class TestBase < Minitest::Test
|
|||||||
@temp_suffix].join '-'
|
@temp_suffix].join '-'
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup
|
|
||||||
ENV.delete 'FZF_DEFAULT_OPTS'
|
|
||||||
ENV.delete 'FZF_CTRL_T_COMMAND'
|
|
||||||
ENV.delete 'FZF_DEFAULT_COMMAND'
|
|
||||||
end
|
|
||||||
|
|
||||||
def readonce
|
def readonce
|
||||||
wait { File.exists?(tempname) }
|
wait { File.exists?(tempname) }
|
||||||
File.read(tempname)
|
File.read(tempname)
|
||||||
@@ -362,7 +368,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_query_unicode
|
def test_query_unicode
|
||||||
tmux.send_keys "(echo abc; echo 가나다) | #{fzf :query, '가다'}", :Enter
|
tmux.paste "(echo abc; echo 가나다) | #{fzf :query, '가다'}"
|
||||||
tmux.until { |lines| lines[-2].include? '1/2' }
|
tmux.until { |lines| lines[-2].include? '1/2' }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal ['가나다'], readonce.split($/)
|
assert_equal ['가나다'], readonce.split($/)
|
||||||
@@ -1313,16 +1319,29 @@ module TestShell
|
|||||||
|
|
||||||
def test_ctrl_t_unicode
|
def test_ctrl_t_unicode
|
||||||
FileUtils.mkdir_p '/tmp/fzf-test'
|
FileUtils.mkdir_p '/tmp/fzf-test'
|
||||||
tmux.send_keys 'cd /tmp/fzf-test; echo -n test1 > "fzf-unicode 테스트1"; echo -n test2 > "fzf-unicode 테스트2"', :Enter
|
tmux.paste 'cd /tmp/fzf-test; echo -n test1 > "fzf-unicode 테스트1"; echo -n test2 > "fzf-unicode 테스트2"'
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'cat ', 'C-t', pane: 0
|
tmux.send_keys 'cat ', 'C-t', pane: 0
|
||||||
tmux.until(1) { |lines| lines.item_count >= 1 }
|
tmux.until(1) { |lines| lines.item_count >= 1 }
|
||||||
tmux.send_keys 'fzf-unicode', pane: 1
|
tmux.send_keys 'fzf-unicode', pane: 1
|
||||||
tmux.until(1) { |lines| lines[-2].start_with? ' 2/' }
|
tmux.until(1) { |lines| lines[-2].start_with? ' 2/' }
|
||||||
tmux.send_keys :BTab, :BTab, pane: 1
|
|
||||||
|
tmux.send_keys '1', pane: 1
|
||||||
|
tmux.until(1) { |lines| lines[-2].start_with? ' 1/' }
|
||||||
|
tmux.send_keys :BTab, pane: 1
|
||||||
|
tmux.until(1) { |lines| lines[-2].include? '(1)' }
|
||||||
|
|
||||||
|
tmux.send_keys :BSpace, pane: 1
|
||||||
|
tmux.until(1) { |lines| lines[-2].start_with? ' 2/' }
|
||||||
|
|
||||||
|
tmux.send_keys '2', pane: 1
|
||||||
|
tmux.until(1) { |lines| lines[-2].start_with? ' 1/' }
|
||||||
|
tmux.send_keys :BTab, pane: 1
|
||||||
tmux.until(1) { |lines| lines[-2].include? '(2)' }
|
tmux.until(1) { |lines| lines[-2].include? '(2)' }
|
||||||
|
|
||||||
tmux.send_keys :Enter, pane: 1
|
tmux.send_keys :Enter, pane: 1
|
||||||
tmux.until { |lines| lines[-1].include?('cat') || lines[-2].include?('cat') }
|
tmux.until { |lines| lines[-1].include?('cat') || lines[-2].include?('cat') }
|
||||||
|
tmux.until { |lines| lines[-1].include?('fzf-unicode') || lines[-2].include?('fzf-unicode') }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines[-1].include? 'test1test2' }
|
tmux.until { |lines| lines[-1].include? 'test1test2' }
|
||||||
end
|
end
|
||||||
@@ -1530,12 +1549,24 @@ module CompletionTest
|
|||||||
|
|
||||||
def test_file_completion_unicode
|
def test_file_completion_unicode
|
||||||
FileUtils.mkdir_p '/tmp/fzf-test'
|
FileUtils.mkdir_p '/tmp/fzf-test'
|
||||||
tmux.send_keys 'cd /tmp/fzf-test; echo -n test3 > "fzf-unicode 테스트1"; echo -n test4 > "fzf-unicode 테스트2"', :Enter
|
tmux.paste 'cd /tmp/fzf-test; echo -n test3 > "fzf-unicode 테스트1"; echo -n test4 > "fzf-unicode 테스트2"'
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'cat fzf-unicode**', :Tab, pane: 0
|
tmux.send_keys 'cat fzf-unicode**', :Tab, pane: 0
|
||||||
tmux.until(1) { |lines| lines[-2].start_with? ' 2/' }
|
tmux.until(1) { |lines| lines[-2].start_with? ' 2/' }
|
||||||
tmux.send_keys :BTab, :BTab, pane: 1
|
|
||||||
|
tmux.send_keys '1', pane: 1
|
||||||
|
tmux.until(1) { |lines| lines[-2].start_with? ' 1/' }
|
||||||
|
tmux.send_keys :BTab, pane: 1
|
||||||
|
tmux.until(1) { |lines| lines[-2].include? '(1)' }
|
||||||
|
|
||||||
|
tmux.send_keys :BSpace, pane: 1
|
||||||
|
tmux.until(1) { |lines| lines[-2].start_with? ' 2/' }
|
||||||
|
|
||||||
|
tmux.send_keys '2', pane: 1
|
||||||
|
tmux.until(1) { |lines| lines[-2].start_with? ' 1/' }
|
||||||
|
tmux.send_keys :BTab, pane: 1
|
||||||
tmux.until(1) { |lines| lines[-2].include? '(2)' }
|
tmux.until(1) { |lines| lines[-2].include? '(2)' }
|
||||||
|
|
||||||
tmux.send_keys :Enter, pane: 1
|
tmux.send_keys :Enter, pane: 1
|
||||||
tmux.until { |lines| lines[-1].include?('cat') || lines[-2].include?('cat') }
|
tmux.until { |lines| lines[-1].include?('cat') || lines[-2].include?('cat') }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
|
Reference in New Issue
Block a user