Compare commits

..

41 Commits

Author SHA1 Message Date
Junegunn Choi
e87a85a179 0.16.5 2017-02-19 01:40:25 +09:00
Junegunn Choi
11407bf656 Exclude sysfs in find commands 2017-02-19 01:33:13 +09:00
Junegunn Choi
c82fb3c9b9 Add toggle-preview-wrap action 2017-02-18 23:49:00 +09:00
Junegunn Choi
309e1d8619 Properly truncate long query string 2017-02-18 23:17:29 +09:00
Junegunn Choi
c2db67c1c0 [vim] Prepend @echo off to $FZF_DEFAULT_COMMAND on Windows (#847) 2017-02-18 21:58:03 +09:00
Junegunn Choi
9526594905 [vim] Fix FZF_DEFAULT_COMMAND on Windows
Close #847. Patch submitted by @wontoncc.
2017-02-18 18:17:37 +09:00
Junegunn Choi
3d74d277aa Use cut instead of sed in the default command 2017-02-17 13:07:45 +09:00
Junegunn Choi
fc274c2ba4 [vim] Do not escape % when using system() instead of !
Close https://github.com/junegunn/fzf.vim/issues/309
2017-02-17 10:20:39 +09:00
Pierre Neidhardt
ce43ea9f42 [shell] Replace sed with -mindepth 1 and cut (#844) 2017-02-16 17:18:01 +09:00
Junegunn Choi
21da02fac2 Fix indentation 2017-02-14 22:30:09 +09:00
Junegunn Choi
19569bd5c5 Move cursor to the top-left when returning to alternate screen
Fix broken preview border. Reported by Thomas Sattler.

    fzf --bind 'enter:execute(date)' --preview=date --reverse
2017-02-14 22:28:04 +09:00
Daniel Gray
afa25d8c57 [zsh] Do not cd when cancelling alt+c keybind (#840) 2017-02-09 14:05:02 +09:00
Junegunn Choi
1ba7acf4bd [fzf-tmux] Fix race condition when using -l/-u on zoomed panes
Using a dummy command that works as the barrier.
2017-02-08 14:55:51 +09:00
Prabir Shrestha
a847fe8754 Use "type" instead of "cat" on windows (#836) 2017-02-07 14:42:08 +09:00
Junegunn Choi
5bb18b6441 Remove Dockerfiles and clean up Makefile
Due to the recent removal of ncurses dependency, we can cross-compile
binaries for different platforms without virtual machines.
2017-02-06 21:15:29 +09:00
Junegunn Choi
876c233a26 Remove Ruby version
Related #832
2017-02-06 21:06:12 +09:00
Junegunn Choi
ee5aeb80a4 0.16.4 2017-02-05 16:17:54 +09:00
Junegunn Choi
02ceae15a2 [vim] Download instruction for Windows 2017-02-05 02:07:54 +09:00
Junegunn Choi
e514739280 Fix failing test case 2017-02-04 22:49:17 +09:00
Junegunn Choi
72265298f9 [vim] Apply --no-height when running fzf in full screen mode
To override --height option in FZF_DEFAULT_OPTS
2017-02-04 21:52:05 +09:00
Junegunn Choi
4b700192c1 Add --border option to draw horizontal lines above and below the finder
Goes well with --height
2017-02-04 21:51:22 +09:00
Junegunn Choi
fe83589ade Add test case for --tiebreak=begin 2017-02-03 02:14:14 +09:00
Junegunn Choi
fcf63c74f1 Fix --tiebreak=begin with algo v2
Due to performance consideration, FuzzyMatchV2 does not return the exact
positions of the matching characters by default. However, the ommission
caused `--tiebreak=begin` to produce inaccurate result in some cases.

  (echo baz foo bar; echo foo bar baz) | fzf --tiebreak=begin -fbar | head -1

  # Expected: foo bar baz
  # Actual:   baz foo bar

This commit fixes the problem by using the end offset which is
guaranteed to be correct.
2017-02-02 13:46:46 +09:00
Junegunn Choi
c95bb109c8 Suppress CSI codes in the output 2017-02-02 13:14:27 +09:00
Junegunn Choi
bd9c46ee34 Update ANSI processor to strip ^H along with its preceding character 2017-02-02 13:00:41 +09:00
Junegunn Choi
736aeaa1d3 Update go-runewidth
https://github.com/junegunn/go-runewidth/pull/1

/cc @joshuarubin
2017-02-02 10:08:56 +09:00
Junegunn Choi
dd1f26522c Fix caching scheme when --exact is set and '-prefix is used 2017-02-01 02:06:56 +09:00
Kassio Borges
712b7b2188 [vim] Expose buffer variable with the current fzf setup (#828)
Exposing the `b:fzf` variable will be useful to get information about
which command is being executed on the current fzf window. With that,
now, it's possible to use the current command name on the statusline:

```viml
au User FzfStatusLine call <SID>fzf_statusline()

function! s:fzf_statusline()
  let fzf_cmd_name = get(b:fzf, 'name', 'FZF')
  let &l:statusline = '> '.fzf_cmd_name
endfunction
```
2017-02-01 01:06:52 +09:00
Junegunn Choi
5b749e2d5c Update documentation 2017-01-31 21:43:41 +09:00
Junegunn Choi
d85a69a709 0.16.3 2017-01-30 01:53:17 +09:00
Junegunn Choi
a425e96fb2 [vim] g:fzf_prefer_tmux for choosing fzf-tmux over --height
https://github.com/junegunn/fzf.vim/issues/296
2017-01-30 01:47:30 +09:00
Junegunn Choi
7763fdf6ba Update man pages 2017-01-30 01:27:12 +09:00
Junegunn Choi
dd156b59fc Fix display issues with execute action
- Move cursor to the top-left corner when starting a command in
  alternate screen
- Fix cursor position when returning to alternate screen when fzf is
  running in full screen mode
2017-01-30 01:08:07 +09:00
Junegunn Choi
36dceecd58 Add support for ctrl-space key
Close #825
2017-01-28 02:54:47 +09:00
Junegunn Choi
421b9b271a Add execute-silent action
Close #823
2017-01-27 18:56:41 +09:00
Junegunn Choi
ed57dcb924 Extend placeholder expression for multiple selections
Close #788
2017-01-27 16:38:42 +09:00
Junegunn Choi
95c77bfb98 Use --bind instead of --toggle-sort
Related #822
2017-01-26 11:54:08 +09:00
Junegunn Choi
2e3e721344 Merge branch 'devel' 2017-01-26 11:52:24 +09:00
Junegunn Choi
da2c28d5c2 Add --read0 and --print0 to --help output
Close #822
2017-01-26 11:41:20 +09:00
Junegunn Choi
dbddee9de9 [fish] Add toggle-sort back to CTRL-R (#759) 2017-01-25 10:21:14 +09:00
Junegunn Choi
8731d75607 Recalculate the width of trimmed line
Close #821
2017-01-25 02:39:49 +09:00
35 changed files with 567 additions and 1916 deletions

View File

@@ -25,65 +25,22 @@ make install
# Build 32-bit and 64-bit executables and tarballs
make release
# Build executables and tarballs for Linux using Docker
make linux
# Make release archives for all supported platforms
make release-all
```
### Using `go get`
Alternatively, you can build fzf directly with `go get` command without
cloning the repository.
manually cloning the repository.
```sh
go get -u github.com/junegunn/fzf/src/fzf
```
Build options
-------------
### With ncurses 6
The official binaries of fzf are built with ncurses 5 because it's widely
supported by different platforms. However ncurses 5 is old and has a number of
limitations.
1. Does not support more than 256 color pairs (See [357][357])
2. Does not support italics
3. Does not support 24-bit color
[357]: https://github.com/junegunn/fzf/issues/357
But you can manually build fzf with ncurses 6 to overcome some of these
limitations. ncurses 6 supports up to 32767 color pairs (1), and supports
italics (2). To build fzf with ncurses 6, you have to install it first. On
macOS, you can use Homebrew to install it.
```sh
brew install homebrew/dupes/ncurses
LDFLAGS="-L/usr/local/opt/ncurses/lib" make install
```
### With tcell
[tcell][tcell] is a portable alternative to ncurses and we currently use it to
build Windows binaries. tcell has many benefits but most importantly, it
supports 24-bit colors. To build fzf with tcell:
```sh
TAGS=tcell make install
```
However, note that tcell has its own issues.
- Poor rendering performance compared to ncurses
- Does not support bracketed-paste mode
- Does not support italics unlike ncurses 6
- Some wide characters are not correctly displayed
Third-party libraries used
--------------------------
- [ncurses][ncurses]
- [mattn/go-runewidth](https://github.com/mattn/go-runewidth)
- Licensed under [MIT](http://mattn.mit-license.org)
- [mattn/go-shellwords](https://github.com/mattn/go-shellwords)
@@ -97,10 +54,3 @@ License
-------
[MIT](LICENSE)
[install]: https://github.com/junegunn/fzf#installation
[go]: https://golang.org/
[gil]: http://en.wikipedia.org/wiki/Global_Interpreter_Lock
[ncurses]: https://www.gnu.org/software/ncurses/
[req]: http://golang.org/doc/install
[tcell]: https://github.com/gdamore/tcell

View File

@@ -1,6 +1,30 @@
CHANGELOG
=========
0.16.5
------
- Minor bug fixes
- Added `toggle-preview-wrap` action
- Built with Go 1.8
0.16.4
------
- Added `--border` option to draw border above and below the finder
- Bug fixes and improvements
0.16.3
------
- Fixed a bug where fzf incorrectly display the lines when straddling tab
characters are trimmed
- Placeholder expression used in `--preview` and `execute` action can
optionally take `+` flag to be used with multiple selections
- e.g. `git log --oneline | fzf --multi --preview 'git show {+1}'`
- Added `execute-silent` action for executing a command silently without
switching to the alternate screen. This is useful when the process is
short-lived and you're not interested in its output.
- e.g. `fzf --bind 'ctrl-y:execute!(echo -n {} | pbcopy)'`
- `ctrl-space` is allowed in `--bind`
0.16.2
------
- Dropped ncurses dependency

View File

@@ -72,16 +72,6 @@ But it's recommended that you use a plugin manager like
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
```
### Upgrading fzf
fzf is being actively developed and you might want to upgrade it once in a
while. Please follow the instruction below depending on the installation
method used.
- git: `cd ~/.fzf && git pull && ./install`
- brew: `brew update; brew reinstall fzf`
- vim-plug: `:PlugUpdate fzf`
### Windows
Pre-built binaries for Windows can be downloaded [here][bin]. However, other
@@ -91,6 +81,17 @@ flawlessly.
[wsl]: https://blogs.msdn.microsoft.com/wsl/
Upgrading fzf
-------------
fzf is being actively developed and you might want to upgrade it once in a
while. Please follow the instruction below depending on the installation
method used.
- git: `cd ~/.fzf && git pull && ./install`
- brew: `brew update; brew reinstall fzf`
- vim-plug: `:PlugUpdate fzf`
Building fzf
------------
@@ -140,10 +141,10 @@ vim $(fzf --height 40% --reverse)
```
You can add these options to `$FZF_DEFAULT_OPTS` so that they're applied by
default.
default. For example,
```sh
export FZF_DEFAULT_OPTS='--height 40% --reverse'
export FZF_DEFAULT_OPTS='--height 40% --reverse --border'
```
#### Search syntax
@@ -357,7 +358,7 @@ If you have set up fzf for Vim, `:FZF` command will be added.
" With options
:FZF --no-sort --reverse --inline-info /tmp
" Bang version starts in fullscreen instead of using tmux pane or Neovim split
" Bang version starts fzf in fullscreen mode
:FZF!
```
@@ -408,20 +409,6 @@ command! -bang MyStuff
Tips
----
#### Rendering issues
If you have any rendering issues, check the following:
1. Make sure `$TERM` is correctly set. fzf will use 256-color only if it
contains `256` (e.g. `xterm-256color`)
2. If you're on screen or tmux, `$TERM` should be either `screen` or
`screen-256color`
3. Some terminal emulators (e.g. mintty) have problem displaying default
background color and make some text unable to read. In that case, try
`--black` option. And if it solves your problem, I recommend including it
in `FZF_DEFAULT_OPTS` for further convenience.
4. If you still have problem, try `--no-256` option or even `--no-color`.
#### Respecting `.gitignore`, `.hgignore`, and `svn:ignore`
[ag](https://github.com/ggreer/the_silver_searcher) or

View File

@@ -138,6 +138,7 @@ cleanup() {
# Remove temp window if we were zoomed
if [[ -n "$zoomed" ]]; then
tmux display-message -p "#{window_id}" > /dev/null
tmux swap-pane -t $original_window \; \
select-window -t $original_window \; \
kill-window -t $tmp_window \; \

1348
fzf

File diff suppressed because it is too large Load Diff

101
install
View File

@@ -2,7 +2,7 @@
set -u
version=0.16.2
version=0.16.5
auto_completion=
key_bindings=
update_config=2
@@ -88,17 +88,6 @@ check_binary() {
return 1
}
symlink() {
echo " - Creating symlink: bin/$1 -> bin/fzf"
(cd "$fzf_base"/bin &&
rm -f fzf &&
ln -sf $1 fzf)
if [ $? -ne 0 ]; then
binary_error="Failed to create symlink"
return 1
fi
}
link_fzf_in_path() {
if which_fzf="$(command -v fzf)"; then
echo " - Found in \$PATH"
@@ -124,9 +113,6 @@ download() {
echo " - Already exists"
check_binary && return
fi
if [ -x "$fzf_base"/bin/$1 ]; then
symlink $1 && check_binary && return
fi
link_fzf_in_path && return
fi
mkdir -p "$fzf_base"/bin && cd "$fzf_base"/bin
@@ -147,12 +133,12 @@ download() {
fi
set +o pipefail
if [ ! -f $1 ]; then
if [ ! -f fzf ]; then
binary_error="Failed to download ${1}"
return
fi
chmod +x $1 && symlink $1 && check_binary
chmod +x fzf && check_binary
}
# Try to download binary executable
@@ -172,80 +158,9 @@ case "$archi" in
FreeBSD\ *86) download fzf-$version-freebsd_${binary_arch:-386} ;;
OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64} ;;
OpenBSD\ *86) download fzf-$version-openbsd_${binary_arch:-386} ;;
*) binary_available=0 binary_error=1 ;;
*) binary_available=0 binary_error=1 ;;
esac
install_ruby_fzf() {
if [ -z "$allow_legacy" ]; then
ask "Do you want to install legacy Ruby version instead?" && exit 1
fi
echo "Installing legacy Ruby version ..."
# ruby executable
echo -n "Checking Ruby executable ... "
ruby=$(command -v ruby)
if [ $? -ne 0 ]; then
echo "ruby executable not found !!!"
exit 1
fi
# System ruby is preferred
system_ruby=/usr/bin/ruby
if [ -x $system_ruby ] && [ $system_ruby != "$ruby" ]; then
$system_ruby --disable-gems -rcurses -e0 2> /dev/null
[ $? -eq 0 ] && ruby=$system_ruby
fi
echo "OK ($ruby)"
# Curses-support
echo -n "Checking Curses support ... "
"$ruby" -rcurses -e0 2> /dev/null
if [ $? -eq 0 ]; then
echo "OK"
else
echo "Not found"
echo "Installing 'curses' gem ... "
if (( EUID )); then
/usr/bin/env gem install curses --user-install
else
/usr/bin/env gem install curses
fi
if [ $? -ne 0 ]; then
echo
echo "Failed to install 'curses' gem."
if [[ $(uname -r) =~ 'ARCH' ]]; then
echo "Make sure that base-devel package group is installed."
fi
exit 1
fi
fi
# Ruby version
echo -n "Checking Ruby version ... "
"$ruby" -e 'exit RUBY_VERSION >= "1.9"'
if [ $? -eq 0 ]; then
echo ">= 1.9"
"$ruby" --disable-gems -rcurses -e0 2> /dev/null
if [ $? -eq 0 ]; then
fzf_cmd="$ruby --disable-gems $fzf_base/fzf"
else
fzf_cmd="$ruby $fzf_base/fzf"
fi
else
echo "< 1.9"
fzf_cmd="$ruby $fzf_base/fzf"
fi
# Create fzf script
echo -n "Creating wrapper script for fzf ... "
rm -f "$fzf_base"/bin/fzf
echo "#!/bin/sh" > "$fzf_base"/bin/fzf
echo "$fzf_cmd \"\$@\"" >> "$fzf_base"/bin/fzf
chmod +x "$fzf_base"/bin/fzf
echo "OK"
}
cd "$fzf_base"
if [ -n "$binary_error" ]; then
if [ $binary_available -eq 0 ]; then
@@ -263,12 +178,12 @@ if [ -n "$binary_error" ]; then
echo "OK"
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
else
echo "Failed to build binary ..."
install_ruby_fzf
echo "Failed to build binary. Installation failed."
exit 1
fi
else
echo "go executable not found. Cannot build binary ..."
install_ruby_fzf
echo "go executable not found. Installation failed."
exit 1
fi
fi

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf-tmux 1 "Jan 2017" "fzf 0.16.2" "fzf-tmux - open fzf in tmux split pane"
.TH fzf-tmux 1 "Feb 2017" "fzf 0.16.5" "fzf-tmux - open fzf in tmux split pane"
.SH NAME
fzf-tmux - open fzf in tmux split pane

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf 1 "Jan 2017" "fzf 0.16.2" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Feb 2017" "fzf 0.16.5" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -156,6 +156,9 @@ Ignored when \fB--height\fR is not specified.
.B "--reverse"
Reverse orientation
.TP
.B "--border"
Draw border above and below the finder
.TP
.BI "--margin=" MARGIN
Comma-separated expression for margins around the finder.
.br
@@ -262,13 +265,21 @@ Execute the given command for the current line and display the result on the
preview window. \fB{}\fR in the command is the placeholder that is replaced to
the single-quoted string of the current line. To transform the replacement
string, specify field index expressions between the braces (See \fBFIELD INDEX
EXPRESSION\fR for the details). Also, \fB{q}\fR is replaced to the current
query string.
EXPRESSION\fR for the details).
.RS
e.g. \fBfzf --preview="head -$LINES {}"\fR
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
A placeholder expression starting with \fB+\fR flag will be replaced to the
space-separated list of the selected lines (or the current line if no selection
was made) individually quoted.
e.g. \fBfzf --multi --preview="head -10 {+}"\fR
\fBgit log --oneline | fzf --multi --preview 'git show {+1}'\fR
Also, \fB{q}\fR is replaced to the current query string.
Note that you can escape a placeholder pattern by prepending a backslash.
.RE
.TP
@@ -323,10 +334,10 @@ e.g. \fBfzf --expect=ctrl-v,ctrl-t,alt-s,f1,f2,~,@\fR
.RE
.TP
.B "--read0"
Read input delimited by ASCII NUL character instead of newline character
Read input delimited by ASCII NUL characters instead of newline characters
.TP
.B "--print0"
Print output delimited by ASCII NUL character instead of newline character
Print output delimited by ASCII NUL characters instead of newline characters
.TP
.B "--sync"
Synchronous search for multi-staged filtering. If specified, fzf will launch
@@ -418,6 +429,7 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
.B AVAILABLE KEYS: (SYNONYMS)
\fIctrl-[a-z]\fR
\fIctrl-space\fR
\fIalt-[a-z]\fR
\fIalt-[0-9]\fR
\fIf[1-12]\fR
@@ -461,7 +473,8 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
\fBdown\fR \fIctrl-j ctrl-n down\fR
\fBend-of-line\fR \fIctrl-e end\fR
\fBexecute(...)\fR (see below for the details)
\fBexecute-multi(...)\fR (see below for the details)
\fBexecute-silent(...)\fR (see below for the details)
\fRexecute-multi(...)\fR (deprecated in favor of \fB{+}\fR expression)
\fBforward-char\fR \fIctrl-f right\fR
\fBforward-word\fR \fIalt-f shift-right\fR
\fBignore\fR
@@ -487,7 +500,8 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
\fBtoggle-out\fR (\fB--reverse\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
\fBtoggle-preview\fR
\fBtoggle-sort\fR (equivalent to \fB--toggle-sort\fR)
\fBtoggle-preview-wrap\fR
\fBtoggle-sort\fR
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
\fBunix-line-discard\fR \fIctrl-u\fR
\fBunix-word-rubout\fR \fIctrl-w\fR
@@ -530,10 +544,12 @@ the closing character. The catch is that it should be the last one in the
comma-separated list of key-action pairs.
.RE
\fBexecute-multi(...)\fR is an alternative action that executes the command
with the selected entries when multi-select is enabled (\fB--multi\fR). With
this action, \fB{}\fR is replaced with the quoted strings of the selected
entries separated by spaces.
fzf switches to the alternate screen when executing a command. However, if the
command is expected to complete quickly, and you are not interested in its
output, you might want to use \fBexecute-silent\fR instead, which silently
executes the command without the switching. Note that fzf will not be
responsible until the command is complete. For asynchronous execution, start
your command as a background process (i.e. appending \fB&\fR).
.SH AUTHOR
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)

View File

@@ -1,4 +1,4 @@
" Copyright (c) 2016 Junegunn Choi
" Copyright (c) 2017 Junegunn Choi
"
" MIT License
"
@@ -28,10 +28,12 @@ let g:loaded_fzf = 1
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:install = expand('<sfile>:h:h').'/install'
let s:is_win = has('win32') || has('win64')
let s:base_dir = expand('<sfile>:h:h')
let s:fzf_go = s:base_dir.'/bin/fzf'
let s:fzf_tmux = s:base_dir.'/bin/fzf-tmux'
let s:install = s:base_dir.'/install'
let s:installed = 0
let s:fzf_tmux = expand('<sfile>:h:h').'/bin/fzf-tmux'
let s:cpo_save = &cpo
set cpo&vim
@@ -42,6 +44,11 @@ function! s:fzf_exec()
let s:exec = s:fzf_go
elseif executable('fzf')
let s:exec = 'fzf'
elseif s:is_win
call s:warn('fzf executable not found.')
call s:warn('Download fzf binary for Windows from https://github.com/junegunn/fzf-bin/releases/')
call s:warn('and place it as '.s:base_dir.'\bin\fzf.exe')
throw 'fzf executable not found'
elseif !s:installed && executable(s:install) &&
\ input('fzf executable not found. Download binary? (y/n) ') =~? '^y'
redraw
@@ -201,6 +208,10 @@ function! fzf#wrap(...)
endfor
let [name, opts, bang] = args
if len(name)
let opts.name = name
end
" Layout: g:fzf_layout (and deprecated g:fzf_height)
if bang
for key in s:layout_keys
@@ -242,7 +253,7 @@ function! fzf#wrap(...)
endfunction
function! fzf#shellescape(path)
if has('win32') || has('win64')
if s:is_win
let shellslash = &shellslash
try
set noshellslash
@@ -259,7 +270,7 @@ try
let oshell = &shell
let useshellslash = &shellslash
if has('win32') || has('win64')
if s:is_win
set shell=cmd.exe
set noshellslash
else
@@ -280,9 +291,9 @@ try
endtry
if !has_key(dict, 'source') && !empty($FZF_DEFAULT_COMMAND)
let temps.source = tempname()
call writefile(split($FZF_DEFAULT_COMMAND, "\n"), temps.source)
let dict.source = (empty($SHELL) ? &shell : $SHELL) . ' ' . s:shellesc(temps.source)
let temps.source = tempname().(s:is_win ? '.bat' : '')
call writefile((s:is_win ? ['@echo off'] : []) + split($FZF_DEFAULT_COMMAND, "\n"), temps.source)
let dict.source = (empty($SHELL) ? &shell : $SHELL) . (s:is_win ? ' /c ' : ' ') . s:shellesc(temps.source)
endif
if has_key(dict, 'source')
@@ -293,7 +304,7 @@ try
elseif type == 3
let temps.input = tempname()
call writefile(source, temps.input)
let prefix = 'cat '.s:shellesc(temps.input).'|'
let prefix = (s:is_win ? 'type ' : 'cat ').s:shellesc(temps.input).'|'
else
throw 'invalid source type'
endif
@@ -301,23 +312,28 @@ try
let prefix = ''
endif
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0)
let use_height = has_key(dict, 'down') &&
\ !(has('nvim') || has('win32') || has('win64') || s:present(dict, 'up', 'left', 'right')) &&
\ !(has('nvim') || s:is_win || s:present(dict, 'up', 'left', 'right')) &&
\ executable('tput') && filereadable('/dev/tty')
let tmux = !use_height && (!has('nvim') || get(g:, 'fzf_prefer_tmux', 0)) && s:tmux_enabled() && s:splittable(dict)
let term = has('nvim') && !tmux
let use_term = has('nvim')
let use_tmux = (!use_height && !use_term || prefer_tmux) && s:tmux_enabled() && s:splittable(dict)
if prefer_tmux && use_tmux
let use_height = 0
let use_term = 0
endif
if use_height
let optstr .= ' --height='.s:calc_size(&lines, dict.down, dict)
elseif term
elseif use_term
let optstr .= ' --no-height'
endif
let command = prefix.(tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
if term
if use_term
return s:execute_term(dict, command, temps)
endif
let lines = tmux ? s:execute_tmux(dict, command, temps)
let lines = use_tmux ? s:execute_tmux(dict, command, temps)
\ : s:execute(dict, command, use_height, temps)
call s:callback(dict, lines)
return lines
@@ -396,7 +412,7 @@ function! s:xterm_launcher()
\ &columns, &lines/2, getwinposx(), getwinposy())
endfunction
unlet! s:launcher
if has('win32') || has('win64')
if s:is_win
let s:launcher = '%s'
else
let s:launcher = function('s:xterm_launcher')
@@ -429,7 +445,7 @@ function! s:execute(dict, command, use_height, temps) abort
endif
let command = printf(fmt, escaped)
else
let command = escaped
let command = a:use_height ? a:command : escaped
endif
if a:use_height
let stdin = has_key(a:dict, 'source') ? '' : '< /dev/tty'
@@ -516,6 +532,7 @@ function! s:execute_term(dict, command, temps) abort
let winrest = winrestcmd()
let pbuf = bufnr('')
let [ppos, winopts] = s:split(a:dict)
let b:fzf = a:dict
let fzf = { 'buf': bufnr(''), 'pbuf': pbuf, 'ppos': ppos, 'dict': a:dict, 'temps': a:temps,
\ 'winopts': winopts, 'winrest': winrest, 'lines': &lines,
\ 'columns': &columns, 'command': a:command }

View File

@@ -1,7 +1,7 @@
# Key bindings
# ------------
__fzf_select__() {
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | cut -b3-"}"
@@ -46,8 +46,8 @@ fzf-file-widget() {
__fzf_cd__() {
local cmd dir
cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"}"
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | cut -b3-"}"
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
}
@@ -56,7 +56,7 @@ __fzf_history__() (
shopt -u nocaseglob nocasematch
line=$(
HISTTIMEFORMAT= history |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS +s --tac -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS +s --tac -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
command grep '^ *[0-9]') &&
if [[ $- =~ H ]]; then
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"

View File

@@ -2,7 +2,7 @@
# ------------
function fzf_key_bindings
# Store last token in $dir as root for the 'find' command
# Store current token in $dir as root for the 'find' command
function fzf-file-widget -d "List files and folders"
set -l dir (commandline -t)
# The commandline token might be escaped, we need to unescape it.
@@ -16,10 +16,10 @@ function fzf_key_bindings
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
# $dir itself, even if hidden.
set -q FZF_CTRL_T_COMMAND; or set -l FZF_CTRL_T_COMMAND "
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | sed 's#^\./##'"
-o -type l -print 2> /dev/null | cut -b3-"
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
begin
@@ -45,7 +45,7 @@ function fzf_key_bindings
function fzf-history-widget -d "Show command history"
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS +s --tiebreak=index $FZF_CTRL_R_OPTS +m"
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS +s --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
history | eval (__fzfcmd) -q '(commandline)' | read -l result
and commandline -- $result
end
@@ -54,8 +54,8 @@ function fzf_key_bindings
function fzf-cd-widget -d "Change directory"
set -q FZF_ALT_C_COMMAND; or set -l FZF_ALT_C_COMMAND "
command find -L . \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"
command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
-o -type d -print 2> /dev/null | cut -b3-"
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"

View File

@@ -4,7 +4,7 @@ if [[ $- == *i* ]]; then
# CTRL-T - Paste the selected file path(s) into the command line
__fsel() {
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | cut -b3-"}"
@@ -38,10 +38,15 @@ bindkey '^T' fzf-file-widget
# ALT-C - cd into the selected directory
fzf-cd-widget() {
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"}"
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | cut -b3-"}"
setopt localoptions pipefail 2> /dev/null
cd "${$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m):-.}"
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m)"
if [[ -z "$dir" ]]; then
zle redisplay
return 0
fi
cd "$dir"
local ret=$?
zle reset-prompt
typeset -f zle-line-init >/dev/null && zle zle-line-init
@@ -55,7 +60,7 @@ fzf-history-widget() {
local selected num
setopt localoptions noglobsubst pipefail 2> /dev/null
selected=( $(fc -l 1 |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS +s --tac -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS --query=${(q)LBUFFER} +m" $(__fzfcmd)) )
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS +s --tac -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(q)LBUFFER} +m" $(__fzfcmd)) )
local ret=$?
if [ -n "$selected" ]; then
num=$selected[1]
@@ -71,4 +76,3 @@ zle -N fzf-history-widget
bindkey '^R' fzf-history-widget
fi

View File

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

View File

@@ -1,24 +0,0 @@
FROM base/archlinux:2014.07.03
MAINTAINER Junegunn Choi <junegunn.c@gmail.com>
# apt-get
RUN pacman-key --populate archlinux && pacman-key --refresh-keys
RUN pacman-db-upgrade && pacman -Syu --noconfirm base-devel git
# Install Go 1.4
RUN cd / && curl \
https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz | \
tar -xz && mv go go1.4
ENV GOROOT /go1.4
ENV PATH /go1.4/bin:$PATH
# For i386 build
RUN echo '[multilib]' >> /etc/pacman.conf && \
echo 'Include = /etc/pacman.d/mirrorlist' >> /etc/pacman.conf && \
pacman-db-upgrade && yes | pacman -Sy gcc-multilib lib32-ncurses && \
cd $GOROOT/src && GOARCH=386 ./make.bash
# Default CMD
CMD cd /fzf/src && /bin/bash

View File

@@ -1,32 +0,0 @@
FROM centos:centos6
MAINTAINER Junegunn Choi <junegunn.c@gmail.com>
# yum
RUN yum install -y git gcc make tar glibc-devel glibc-devel.i686 \
ncurses-devel ncurses-static ncurses-devel.i686 \
gpm-devel gpm-static libgcc.i686
# Install Go 1.4
RUN cd / && curl \
https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz | \
tar -xz && mv go go1.4
# Install Go 1.7
RUN cd / && curl \
https://storage.googleapis.com/golang/go1.7.linux-amd64.tar.gz | \
tar -xz && mv go go1.7
# Install RPMs for building static 32-bit binary
RUN curl ftp://ftp.pbone.net/mirror/ftp.centos.org/6.8/os/i386/Packages/ncurses-static-5.7-4.20090207.el6.i686.rpm -o rpm && rpm -i rpm && \
curl ftp://ftp.pbone.net/mirror/ftp.centos.org/6.8/os/i386/Packages/gpm-static-1.20.6-12.el6.i686.rpm -o rpm && rpm -i rpm
ENV GOROOT_BOOTSTRAP /go1.4
ENV GOROOT /go1.7
ENV PATH /go1.7/bin:$PATH
# For i386 build
RUN cd $GOROOT/src && GOARCH=386 ./make.bash
# Default CMD
CMD cd /fzf/src && /bin/bash

View File

@@ -1,22 +0,0 @@
FROM ubuntu:14.04
MAINTAINER Junegunn Choi <junegunn.c@gmail.com>
# apt-get
RUN apt-get update && apt-get -y upgrade && \
apt-get install -y --force-yes git curl build-essential libncurses-dev libgpm-dev
# Install Go 1.4
RUN cd / && curl \
https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz | \
tar -xz && mv go go1.4
ENV GOROOT /go1.4
ENV PATH /go1.4/bin:$PATH
# For i386 build
RUN apt-get install -y lib32ncurses5-dev && \
cd $GOROOT/src && GOARCH=386 ./make.bash
# Default CMD
CMD cd /fzf/src && /bin/bash

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2016 Junegunn Choi
Copyright (c) 2017 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

View File

@@ -4,6 +4,8 @@ ifeq ($(UNAME_S),Darwin)
GOOS := darwin
else ifeq ($(UNAME_S),Linux)
GOOS := linux
else
$(error "$$GOOS is not defined.")
endif
endif
@@ -12,7 +14,6 @@ ROOTDIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
BINDIR := $(shell dirname $(ROOTDIR))/bin
GOPATH := $(shell dirname $(ROOTDIR))/gopath
SRCDIR := $(GOPATH)/src/github.com/junegunn/fzf/src
DOCKEROPTS := -i -t -v $(ROOTDIR):/fzf/src
BINARY32 := fzf-$(GOOS)_386
BINARY64 := fzf-$(GOOS)_amd64
BINARYARM5 := fzf-$(GOOS)_arm5
@@ -52,23 +53,23 @@ all: fzf/$(BINARY)
ifeq ($(GOOS),windows)
release: fzf/$(BINARY32) fzf/$(BINARY64)
cd fzf && cp $(BINARY32) $(RELEASE32).exe && zip $(RELEASE32).zip $(RELEASE32).exe
cd fzf && cp $(BINARY64) $(RELEASE64).exe && zip $(RELEASE64).zip $(RELEASE64).exe
cd fzf && rm -f $(RELEASE32).exe $(RELEASE64).exe
cd fzf && cp -f $(BINARY32) fzf.exe && zip $(RELEASE32).zip fzf.exe
cd fzf && cp -f $(BINARY64) fzf.exe && zip $(RELEASE64).zip fzf.exe
cd fzf && rm -f fzf.exe
else ifeq ($(GOOS),linux)
release: fzf/$(BINARY32) fzf/$(BINARY64) fzf/$(BINARYARM5) fzf/$(BINARYARM6) fzf/$(BINARYARM7) fzf/$(BINARYARM8)
cd fzf && cp $(BINARY32) $(RELEASE32) && tar -czf $(RELEASE32).tgz $(RELEASE32)
cd fzf && cp $(BINARY64) $(RELEASE64) && tar -czf $(RELEASE64).tgz $(RELEASE64)
cd fzf && cp $(BINARYARM5) $(RELEASEARM5) && tar -czf $(RELEASEARM5).tgz $(RELEASEARM5)
cd fzf && cp $(BINARYARM6) $(RELEASEARM6) && tar -czf $(RELEASEARM6).tgz $(RELEASEARM6)
cd fzf && cp $(BINARYARM7) $(RELEASEARM7) && tar -czf $(RELEASEARM7).tgz $(RELEASEARM7)
cd fzf && cp $(BINARYARM8) $(RELEASEARM8) && tar -czf $(RELEASEARM8).tgz $(RELEASEARM8)
cd fzf && rm -f $(RELEASE32) $(RELEASE64) $(RELEASEARM5) $(RELEASEARM6) $(RELEASEARM7) $(RELEASEARM8)
cd fzf && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf
cd fzf && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
cd fzf && cp -f $(BINARYARM5) fzf && tar -czf $(RELEASEARM5).tgz fzf
cd fzf && cp -f $(BINARYARM6) fzf && tar -czf $(RELEASEARM6).tgz fzf
cd fzf && cp -f $(BINARYARM7) fzf && tar -czf $(RELEASEARM7).tgz fzf
cd fzf && cp -f $(BINARYARM8) fzf && tar -czf $(RELEASEARM8).tgz fzf
cd fzf && rm -f fzf
else
release: fzf/$(BINARY32) fzf/$(BINARY64)
cd fzf && cp $(BINARY32) $(RELEASE32) && tar -czf $(RELEASE32).tgz $(RELEASE32)
cd fzf && cp $(BINARY64) $(RELEASE64) && tar -czf $(RELEASE64).tgz $(RELEASE64)
cd fzf && rm -f $(RELEASE32) $(RELEASE64)
cd fzf && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf
cd fzf && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
cd fzf && rm -f fzf
endif
release-all: clean test
@@ -86,12 +87,6 @@ deps: $(SRCDIR) $(SOURCES)
cd $(SRCDIR) && go get -tags "$(TAGS)"
./deps
android-build: $(SRCDIR)
cd $(SRCDIR) && GOARCH=arm GOARM=7 CGO_ENABLED=1 go get
cd $(SRCDIR)/fzf && GOARCH=arm GOARM=7 CGO_ENABLED=1 go build -a -ldflags="-w -extldflags=-pie" -o $(BINARYARM7)
cd $(SRCDIR)/fzf && cp $(BINARYARM7) $(RELEASEARM7) && tar -czf $(RELEASEARM7).tgz $(RELEASEARM7) && \
rm -f $(RELEASEARM7)
test: deps
SHELL=/bin/sh GOOS= go test -v -tags "$(TAGS)" ./...
@@ -129,42 +124,4 @@ $(BINDIR)/fzf: fzf/$(BINARY) | $(BINDIR)
$(BINDIR):
mkdir -p $@
docker-arch:
docker build -t junegunn/arch-sandbox - < Dockerfile.arch
docker-ubuntu:
docker build -t junegunn/ubuntu-sandbox - < Dockerfile.ubuntu
docker-centos:
docker build -t junegunn/centos-sandbox - < Dockerfile.centos
docker-android:
docker build -t junegunn/android-sandbox - < Dockerfile.android
arch: docker-arch
docker run $(DOCKEROPTS) junegunn/$@-sandbox \
sh -c 'cd /fzf/src; /bin/bash'
ubuntu: docker-ubuntu
docker run $(DOCKEROPTS) junegunn/$@-sandbox \
sh -c 'cd /fzf/src; /bin/bash'
centos: docker-centos
docker run $(DOCKEROPTS) junegunn/$@-sandbox \
sh -c 'cd /fzf/src; /bin/bash'
linux: docker-centos
docker run $(DOCKEROPTS) junegunn/centos-sandbox \
/bin/bash -ci 'cd /fzf/src; make TAGS=static release'
ubuntu-android: docker-android
docker run $(DOCKEROPTS) junegunn/android-sandbox \
sh -c 'cd /fzf/src; /bin/bash'
android: docker-android
docker run $(DOCKEROPTS) junegunn/android-sandbox \
/bin/bash -ci 'cd /fzf/src; GOOS=android make android-build'
.PHONY: all deps release test install uninstall clean \
linux arch ubuntu centos docker-arch docker-ubuntu docker-centos \
android-build docker-android ubuntu-android android
.PHONY: all deps release release-all test install uninstall clean

View File

@@ -44,7 +44,7 @@ func init() {
*/
// The following regular expression will include not all but most of the
// frequently used ANSI sequences
ansiRegex = regexp.MustCompile("\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b.|[\x08\x0e\x0f]")
ansiRegex = regexp.MustCompile("\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b.|[\x0e\x0f]|.\x08")
}
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {

View File

@@ -8,7 +8,7 @@ import (
const (
// Current version
version = "0.16.2"
version = "0.16.5"
// Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond
@@ -18,10 +18,9 @@ const (
readerBufferSize = 64 * 1024
// Terminal
initialDelay = 20 * time.Millisecond
initialDelayTac = 100 * time.Millisecond
spinnerDuration = 200 * time.Millisecond
maxPatternLength = 100
initialDelay = 20 * time.Millisecond
initialDelayTac = 100 * time.Millisecond
spinnerDuration = 200 * time.Millisecond
// Matcher
numPartitionsMultiplier = 8

View File

@@ -4,5 +4,5 @@ package fzf
const (
// Reader
defaultCommand = `find -L . -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | sed s/^..//`
defaultCommand = `command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
)

View File

@@ -3,7 +3,7 @@ Package fzf implements fzf, a command-line fuzzy finder.
The MIT License (MIT)
Copyright (c) 2016 Junegunn Choi
Copyright (c) 2017 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

View File

@@ -13,6 +13,6 @@ reset() (
)
reset github.com/junegunn/go-isatty 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
reset github.com/junegunn/go-runewidth 63c378b851290989b19ca955468386485f118c65
reset github.com/junegunn/go-runewidth 14207d285c6c197daabb5c9793d63e7af9ab2d50
reset github.com/junegunn/go-shellwords 33bd8f1ebe16d6e5eb688cc885749a63059e9167
reset golang.org/x/crypto abc5fa7ad02123a41f02bf1391c9760f7586e608

View File

@@ -54,6 +54,7 @@ const usage = `usage: fzf [options]
--min-height=HEIGHT Minimum height when --height is given in percent
(default: 10)
--reverse Reverse orientation
--border Draw border above and below the finder
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
--inline-info Display finder info inline with the query
--prompt=STR Input prompt (default: '> ')
@@ -82,6 +83,8 @@ const usage = `usage: fzf [options]
-f, --filter=STR Filter mode. Do not start interactive finder.
--print-query Print query as the first line
--expect=KEYS Comma-separated list of keys to complete fzf
--read0 Read input delimited by ASCII NUL characters
--print0 Print output delimited by ASCII NUL characters
--sync Synchronous search for multi-staged filtering
Environment variables
@@ -181,6 +184,7 @@ type Options struct {
Header []string
HeaderLines int
Margin [4]sizeSpec
Bordered bool
Tabstop int
Version bool
}
@@ -391,6 +395,8 @@ func parseKeyChords(str string, message string) map[int]string {
chord = tui.AltZ + int(' ')
case "bspace", "bs":
chord = tui.BSpace
case "ctrl-space":
chord = tui.CtrlSpace
case "alt-enter", "alt-return":
chord = tui.AltEnter
case "alt-space":
@@ -579,18 +585,25 @@ const (
escapedPlus = 2
)
func init() {
// Backreferences are not supported.
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
executeRegexp = regexp.MustCompile(
"(?si):(execute(?:-multi|-silent)?):.+|:(execute(?:-multi|-silent)?)(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
}
func parseKeymap(keymap map[int][]action, str string) {
if executeRegexp == nil {
// Backreferences are not supported.
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
executeRegexp = regexp.MustCompile(
"(?si):execute(-multi)?:.+|:execute(-multi)?(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
}
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
if src[len(":execute")] == '-' {
return ":execute-multi(" + strings.Repeat(" ", len(src)-len(":execute-multi()")) + ")"
prefix := ":execute"
if src[len(prefix)] == '-' {
c := src[len(prefix)+1]
if c == 's' || c == 'S' {
prefix += "-silent"
} else {
prefix += "-multi"
}
}
return ":execute(" + strings.Repeat(" ", len(src)-len(":execute()")) + ")"
return prefix + "(" + strings.Repeat(" ", len(src)-len(prefix)-2) + ")"
})
masked = strings.Replace(masked, "::", string([]rune{escapedColon, ':'}), -1)
masked = strings.Replace(masked, ",:", string([]rune{escapedComma, ':'}), -1)
@@ -710,6 +723,8 @@ func parseKeymap(keymap map[int][]action, str string) {
appendAction(actNextHistory)
case "toggle-preview":
appendAction(actTogglePreview)
case "toggle-preview-wrap":
appendAction(actTogglePreviewWrap)
case "toggle-sort":
appendAction(actToggleSort)
case "preview-up":
@@ -726,9 +741,12 @@ func parseKeymap(keymap map[int][]action, str string) {
errorExit("unknown action: " + spec)
} else {
var offset int
if t == actExecuteMulti {
switch t {
case actExecuteSilent:
offset = len("execute-silent")
case actExecuteMulti:
offset = len("execute-multi")
} else {
default:
offset = len("execute")
}
if spec[offset] == ':' {
@@ -750,23 +768,21 @@ func parseKeymap(keymap map[int][]action, str string) {
}
func isExecuteAction(str string) actionType {
t := actExecute
if !strings.HasPrefix(str, "execute") || len(str) < len("execute(") {
matches := executeRegexp.FindAllStringSubmatch(":"+str, -1)
if matches == nil || len(matches) != 1 {
return actIgnore
}
b := str[len("execute")]
if strings.HasPrefix(str, "execute-multi") {
if len(str) < len("execute-multi(") {
return actIgnore
}
t = actExecuteMulti
b = str[len("execute-multi")]
prefix := matches[0][1]
if len(prefix) == 0 {
prefix = matches[0][2]
}
e := str[len(str)-1]
if b == ':' || b == '(' && e == ')' || b == '[' && e == ']' ||
b == e && strings.ContainsAny(string(b), "~!@#$%^&*;/|") {
return t
switch prefix {
case "execute":
return actExecute
case "execute-silent":
return actExecuteSilent
case "execute-multi":
return actExecuteMulti
}
return actIgnore
}
@@ -1074,6 +1090,10 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Height = sizeSpec{}
case "--no-margin":
opts.Margin = defaultMargin()
case "--no-border":
opts.Bordered = false
case "--border":
opts.Bordered = true
case "--margin":
opts.Margin = parseMargin(
nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))

View File

@@ -101,7 +101,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
for idx, term := range termSet {
// If the query contains inverse search terms or OR operators,
// we cannot cache the search scope
if !cacheable || idx > 0 || term.inv {
if !cacheable || idx > 0 || term.inv || !fuzzy && term.typ != termExact {
cacheable = false
break Loop
}

View File

@@ -186,3 +186,21 @@ func TestCacheKey(t *testing.T) {
test(true, "foo | bar !baz", "", false)
test(true, "| | | foo", "foo", true)
}
func TestCacheable(t *testing.T) {
test := func(fuzzy bool, str string, cacheable bool) {
clearPatternCache()
pat := BuildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, true, []Range{}, Delimiter{}, []rune(str))
if cacheable != pat.cacheable {
t.Errorf("Invalid Pattern.cacheable for \"%s\": %v (expected: %v)", str, pat.cacheable, cacheable)
}
}
test(true, "foo bar", true)
test(true, "foo 'bar", true)
test(true, "foo !bar", false)
test(false, "foo bar", true)
test(false, "foo '", true)
test(false, "foo 'bar", false)
test(false, "foo !bar", false)
}

View File

@@ -37,12 +37,14 @@ func buildResult(item *Item, offsets []Offset, score int, trimLen int) *Result {
result := Result{item: item, rank: rank{index: item.index}}
numChars := item.text.Length()
minBegin := math.MaxUint16
minEnd := math.MaxUint16
maxEnd := 0
validOffsetFound := false
for _, offset := range offsets {
b, e := int(offset[0]), int(offset[1])
if b < e {
minBegin = util.Min(b, minBegin)
minEnd = util.Min(e, minEnd)
maxEnd = util.Max(e, maxEnd)
validOffsetFound = true
}
@@ -68,7 +70,7 @@ func buildResult(item *Item, offsets []Offset, score int, trimLen int) *Result {
}
}
if criterion == byBegin {
val = util.AsUint16(minBegin - whitePrefixLen)
val = util.AsUint16(minEnd - whitePrefixLen)
} else {
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/trimLen)
}

View File

@@ -24,7 +24,7 @@ import (
var placeholder *regexp.Regexp
func init() {
placeholder = regexp.MustCompile("\\\\?(?:{[0-9,-.]*}|{q})")
placeholder = regexp.MustCompile("\\\\?(?:{\\+?[0-9,-.]*}|{q})")
}
type jumpMode int
@@ -83,8 +83,10 @@ type Terminal struct {
tabstop int
margin [4]sizeSpec
strong tui.Attr
bordered bool
border tui.Window
window tui.Window
bwindow tui.Window
pborder tui.Window
pwindow tui.Window
count int
progress int
@@ -197,6 +199,7 @@ const (
actPrintQuery
actToggleSort
actTogglePreview
actTogglePreviewWrap
actPreviewUp
actPreviewDown
actPreviewPageUp
@@ -204,7 +207,8 @@ const (
actPreviousHistory
actNextHistory
actExecute
actExecuteMulti
actExecuteSilent
actExecuteMulti // Deprecated
)
func toActions(types ...actionType) []action {
@@ -294,15 +298,22 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
maxHeightFunc := func(termHeight int) int {
var maxHeight int
if opts.Height.percent {
maxHeight = util.Min(termHeight,
util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight))
maxHeight = util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight)
} else {
maxHeight = util.Min(termHeight, int(opts.Height.size))
maxHeight = int(opts.Height.size)
}
effectiveMinHeight := minHeight
if previewBox != nil && (opts.Preview.position == posUp || opts.Preview.position == posDown) {
effectiveMinHeight *= 2
}
if opts.InlineInfo {
return util.Max(maxHeight, minHeight-1)
effectiveMinHeight -= 1
}
return util.Max(maxHeight, minHeight)
if opts.Bordered {
effectiveMinHeight += 2
}
return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight))
}
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, maxHeightFunc)
} else if tui.HasFullscreenRenderer() {
@@ -342,6 +353,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
printQuery: opts.PrintQuery,
history: opts.History,
margin: opts.Margin,
bordered: opts.Bordered,
strong: strongAttr,
cycle: opts.Cycle,
header: header,
@@ -436,9 +448,9 @@ func (t *Terminal) output() bool {
}
found := len(t.selected) > 0
if !found {
cnt := t.merger.Length()
if cnt > 0 && cnt > t.cy {
t.printer(t.current())
current := t.currentItem()
if current != nil {
t.printer(current.AsString(t.ansi))
found = true
}
} else {
@@ -498,6 +510,9 @@ func (t *Terminal) resizeWindows() {
} else {
marginInt[idx] = int(sizeSpec.size)
}
if t.bordered && idx%2 == 0 {
marginInt[idx] += 1
}
}
adjust := func(idx1 int, idx2 int, max int, min int) {
if max >= min {
@@ -523,19 +538,29 @@ func (t *Terminal) resizeWindows() {
}
adjust(1, 3, screenWidth, minAreaWidth)
adjust(0, 2, screenHeight, minAreaHeight)
if t.border != nil {
t.border.Close()
}
if t.window != nil {
t.window.Close()
}
if t.bwindow != nil {
t.bwindow.Close()
if t.pborder != nil {
t.pborder.Close()
t.pwindow.Close()
}
width := screenWidth - marginInt[1] - marginInt[3]
height := screenHeight - marginInt[0] - marginInt[2]
if t.bordered {
t.border = t.tui.NewWindow(
marginInt[0]-1,
marginInt[3],
width,
height+2, tui.BorderHorizontal)
}
if previewVisible {
createPreviewWindow := func(y int, x int, w int, h int) {
t.bwindow = t.tui.NewWindow(y, x, w, h, true)
t.pborder = t.tui.NewWindow(y, x, w, h, tui.BorderAround)
pwidth := w - 4
// ncurses auto-wraps the line when the cursor reaches the right-end of
// the window. To prevent unintended line-wraps, we use the width one
@@ -543,28 +568,28 @@ func (t *Terminal) resizeWindows() {
if !t.preview.wrap && t.tui.DoesAutoWrap() {
pwidth += 1
}
t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, h-2, false)
t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, h-2, tui.BorderNone)
}
switch t.preview.position {
case posUp:
pheight := calculateSize(height, t.preview.size, minHeight, 3)
t.window = t.tui.NewWindow(
marginInt[0]+pheight, marginInt[3], width, height-pheight, false)
marginInt[0]+pheight, marginInt[3], width, height-pheight, tui.BorderNone)
createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
case posDown:
pheight := calculateSize(height, t.preview.size, minHeight, 3)
t.window = t.tui.NewWindow(
marginInt[0], marginInt[3], width, height-pheight, false)
marginInt[0], marginInt[3], width, height-pheight, tui.BorderNone)
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
case posLeft:
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
t.window = t.tui.NewWindow(
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false)
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, tui.BorderNone)
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
case posRight:
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
t.window = t.tui.NewWindow(
marginInt[0], marginInt[3], width-pwidth, height, false)
marginInt[0], marginInt[3], width-pwidth, height, tui.BorderNone)
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
}
} else {
@@ -572,13 +597,14 @@ func (t *Terminal) resizeWindows() {
marginInt[0],
marginInt[3],
width,
height, false)
height, tui.BorderNone)
}
if !t.tui.IsOptimized() && t.theme != nil && t.theme.HasBg() {
for i := 0; i < t.window.Height(); i++ {
t.window.MoveAndClear(i, 0)
}
}
t.truncateQuery()
}
func (t *Terminal) move(y int, x int, clear bool) {
@@ -604,13 +630,19 @@ func (t *Terminal) printPrompt() {
}
func (t *Terminal) printInfo() {
pos := 0
if t.inlineInfo {
t.move(0, t.displayWidth([]rune(t.prompt))+t.displayWidth(t.input)+1, true)
pos = t.displayWidth([]rune(t.prompt)) + t.displayWidth(t.input) + 1
if pos+len(" < ") > t.window.Width() {
return
}
t.move(0, pos, true)
if t.reading {
t.window.CPrint(tui.ColSpinner, t.strong, " < ")
} else {
t.window.CPrint(tui.ColPrompt, t.strong, " < ")
}
pos += len(" < ")
} else {
t.move(1, 0, true)
if t.reading {
@@ -619,6 +651,7 @@ func (t *Terminal) printInfo() {
t.window.CPrint(tui.ColSpinner, t.strong, _spinner[idx])
}
t.move(1, 2, false)
pos = 2
}
output := fmt.Sprintf("%d/%d", t.merger.Length(), t.count)
@@ -635,7 +668,9 @@ func (t *Terminal) printInfo() {
if t.progress > 0 && t.progress < 100 {
output += fmt.Sprintf(" (%d%%)", t.progress)
}
t.window.CPrint(tui.ColInfo, 0, output)
if pos+len(output) <= t.window.Width() {
t.window.CPrint(tui.ColInfo, 0, output)
}
}
func (t *Terminal) printHeader() {
@@ -847,6 +882,7 @@ func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.Colo
offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
}
}
displayWidth = t.displayWidthWithLimit(text, 0, displayWidth)
}
var index int32
@@ -976,11 +1012,15 @@ func (t *Terminal) printAll() {
func (t *Terminal) refresh() {
if !t.suppress {
if t.hasPreviewWindow() {
t.tui.RefreshWindows([]tui.Window{t.bwindow, t.pwindow, t.window})
} else {
t.tui.RefreshWindows([]tui.Window{t.window})
windows := make([]tui.Window, 0, 4)
if t.bordered {
windows = append(windows, t.border)
}
if t.hasPreviewWindow() {
windows = append(windows, t.pborder, t.pwindow)
}
windows = append(windows, t.window)
t.tui.RefreshWindows(windows)
}
}
@@ -1043,7 +1083,27 @@ func quoteEntry(entry string) string {
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
}
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, query string, items []*Item) string {
func hasPlusFlag(template string) bool {
for _, match := range placeholder.FindAllString(template, -1) {
if match[0] == '\\' {
continue
}
if match[1] == '+' {
return true
}
}
return false
}
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, forcePlus bool, query string, allItems []*Item) string {
current := allItems[:1]
selected := allItems[1:]
if current[0] == nil {
current = []*Item{}
}
if selected[0] == nil {
selected = []*Item{}
}
return placeholder.ReplaceAllStringFunc(template, func(match string) string {
// Escaped pattern
if match[0] == '\\' {
@@ -1055,6 +1115,16 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, qu
return quoteEntry(query)
}
plusFlag := forcePlus
if match[1] == '+' {
match = "{" + match[2:]
plusFlag = true
}
items := current
if plusFlag {
items = selected
}
replacements := make([]string, len(items))
if match == "{}" {
@@ -1095,18 +1165,27 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, qu
})
}
func (t *Terminal) executeCommand(template string, items []*Item) {
command := replacePlaceholder(template, t.ansi, t.delimiter, string(t.input), items)
cmd := util.ExecCommand(command)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
t.tui.Pause()
cmd.Run()
if t.tui.Resume() {
t.printAll()
func (t *Terminal) executeCommand(template string, forcePlus bool, background bool) {
valid, list := t.buildPlusList(template, forcePlus)
if !valid {
return
}
command := replacePlaceholder(template, t.ansi, t.delimiter, forcePlus, string(t.input), list)
cmd := util.ExecCommand(command)
if !background {
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
t.tui.Pause()
cmd.Run()
if t.tui.Resume() {
t.tui.Clear()
t.printAll()
}
t.refresh()
} else {
cmd.Run()
}
t.refresh()
}
func (t *Terminal) hasPreviewer() bool {
@@ -1122,11 +1201,30 @@ func (t *Terminal) hasPreviewWindow() bool {
}
func (t *Terminal) currentItem() *Item {
return t.merger.Get(t.cy).item
cnt := t.merger.Length()
if cnt > 0 && cnt > t.cy {
return t.merger.Get(t.cy).item
}
return nil
}
func (t *Terminal) current() string {
return t.currentItem().AsString(t.ansi)
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) {
current := t.currentItem()
if !forcePlus && !hasPlusFlag(template) || len(t.selected) == 0 {
return current != nil, []*Item{current, current}
}
sels := make([]*Item, len(t.selected)+1)
sels[0] = current
for i, sel := range t.sortSelected() {
sels[i+1] = sel.item
}
return true, sels
}
func (t *Terminal) truncateQuery() {
maxPatternLength := util.Max(1, t.window.Width()-t.displayWidth([]rune(t.prompt))-1)
t.input, _ = t.trimRight(t.input, maxPatternLength)
t.cx = util.Constrain(t.cx, 0, len(t.input))
}
// Loop is called to start Terminal I/O
@@ -1183,19 +1281,20 @@ func (t *Terminal) Loop() {
if t.hasPreviewer() {
go func() {
for {
var request *Item
var request []*Item
t.previewBox.Wait(func(events *util.Events) {
for req, value := range *events {
switch req {
case reqPreviewEnqueue:
request = value.(*Item)
request = value.([]*Item)
}
}
events.Clear()
})
if request != nil {
// We don't display preview window if no match
if request[0] != nil {
command := replacePlaceholder(t.preview.command,
t.ansi, t.delimiter, string(t.input), []*Item{request})
t.ansi, t.delimiter, false, string(t.input), request)
cmd := util.ExecCommand(command)
out, _ := cmd.CombinedOutput()
t.reqBox.Set(reqPreviewDisplay, string(out))
@@ -1231,17 +1330,12 @@ func (t *Terminal) Loop() {
t.printInfo()
case reqList:
t.printList()
cnt := t.merger.Length()
var currentFocus *Item
if cnt > 0 && cnt > t.cy {
currentFocus = t.currentItem()
} else {
currentFocus = nil
}
currentFocus := t.currentItem()
if currentFocus != focused {
focused = currentFocus
if t.isPreviewEnabled() {
t.previewBox.Set(reqPreviewEnqueue, focused)
_, list := t.buildPlusList(t.preview.command, false)
t.previewBox.Set(reqPreviewEnqueue, list)
}
}
case reqJump:
@@ -1346,20 +1440,10 @@ func (t *Terminal) Loop() {
doAction = func(a action, mapkey int) bool {
switch a.t {
case actIgnore:
case actExecute:
if t.cy >= 0 && t.cy < t.merger.Length() {
t.executeCommand(a.a, []*Item{t.currentItem()})
}
case actExecute, actExecuteSilent:
t.executeCommand(a.a, false, a.t == actExecuteSilent)
case actExecuteMulti:
if len(t.selected) > 0 {
sels := make([]*Item, len(t.selected))
for i, sel := range t.sortSelected() {
sels[i] = sel.item
}
t.executeCommand(a.a, sels)
} else {
return doAction(action{t: actExecute, a: a.a}, mapkey)
}
t.executeCommand(a.a, true, false)
case actInvalid:
t.mutex.Unlock()
return false
@@ -1368,12 +1452,19 @@ func (t *Terminal) Loop() {
t.previewer.enabled = !t.previewer.enabled
t.tui.Clear()
t.resizeWindows()
cnt := t.merger.Length()
if t.previewer.enabled && cnt > 0 && cnt > t.cy {
t.previewBox.Set(reqPreviewEnqueue, t.currentItem())
if t.previewer.enabled {
valid, list := t.buildPlusList(t.preview.command, false)
if valid {
t.previewBox.Set(reqPreviewEnqueue, list)
}
}
req(reqList, reqInfo, reqHeader)
}
case actTogglePreviewWrap:
if t.hasPreviewWindow() {
t.preview.wrap = !t.preview.wrap
req(reqPreviewRefresh)
}
case actToggleSort:
t.sort = !t.sort
t.eventBox.Set(EvtSearchNew, t.sort)
@@ -1619,11 +1710,7 @@ func (t *Terminal) Loop() {
if !doActions(actions, mapkey) {
continue
}
// Truncate the query if it's too long
if len(t.input) > maxPatternLength {
t.input = t.input[:maxPatternLength]
t.cx = util.Constrain(t.cx, 0, maxPatternLength)
}
t.truncateQuery()
changed = string(previousInput) != string(t.input)
} else {
if mapkey == tui.Rune {

View File

@@ -14,8 +14,10 @@ func newItem(str string) *Item {
}
func TestReplacePlaceholder(t *testing.T) {
items1 := []*Item{newItem(" foo'bar \x1b[31mbaz\x1b[m")}
item1 := newItem(" foo'bar \x1b[31mbaz\x1b[m")
items1 := []*Item{item1, item1}
items2 := []*Item{
newItem("foo'bar \x1b[31mbaz\x1b[m"),
newItem("foo'bar \x1b[31mbaz\x1b[m"),
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
@@ -27,47 +29,65 @@ func TestReplacePlaceholder(t *testing.T) {
}
// {}, preserve ansi
result = replacePlaceholder("echo {}", false, Delimiter{}, "query", items1)
result = replacePlaceholder("echo {}", false, Delimiter{}, false, "query", items1)
check("echo ' foo'\\''bar \x1b[31mbaz\x1b[m'")
// {}, strip ansi
result = replacePlaceholder("echo {}", true, Delimiter{}, "query", items1)
result = replacePlaceholder("echo {}", true, Delimiter{}, false, "query", items1)
check("echo ' foo'\\''bar baz'")
// {}, with multiple items
result = replacePlaceholder("echo {}", true, Delimiter{}, "query", items2)
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
result = replacePlaceholder("echo {}", true, Delimiter{}, false, "query", items2)
check("echo 'foo'\\''bar baz'")
// {..}, strip leading whitespaces, preserve ansi
result = replacePlaceholder("echo {..}", false, Delimiter{}, "query", items1)
result = replacePlaceholder("echo {..}", false, Delimiter{}, false, "query", items1)
check("echo 'foo'\\''bar \x1b[31mbaz\x1b[m'")
// {..}, strip leading whitespaces, strip ansi
result = replacePlaceholder("echo {..}", true, Delimiter{}, "query", items1)
result = replacePlaceholder("echo {..}", true, Delimiter{}, false, "query", items1)
check("echo 'foo'\\''bar baz'")
// {q}
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, "query", items1)
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, false, "query", items1)
check("echo ' foo'\\''bar baz' 'query'")
// {q}, multiple items
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, "query 'string'", items2)
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, false, "query 'string'", items2)
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ''query '\\''string'\\''''foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, "query", items1)
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, false, "query 'string'", items2)
check("echo 'foo'\\''bar baz''query '\\''string'\\''''foo'\\''bar baz'")
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, false, "query", items1)
check("echo 'foo'\\''bar'/'baz'/'bazfoo'\\''bar'/'baz'/'foo'\\''bar'/' foo'\\''bar baz'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, "query", items2)
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, false, "query", items2)
check("echo 'foo'\\''bar'/'baz'/'baz'/'foo'\\''bar'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, false, "query", items2)
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
// forcePlus
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, true, "query", items2)
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
// No match
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, nil})
check("echo /")
// No match, but with selections
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, item1})
check("echo /' foo'\\''bar baz'")
// String delimiter
delim := "'"
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, "query", items1)
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, false, "query", items1)
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
// Regex delimiter
regex := regexp.MustCompile("[oa]+")
// foo'bar baz
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, "query", items1)
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, false, "query", items1)
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
}

View File

@@ -40,6 +40,6 @@ func (r *FullscreenRenderer) MaxY() int { return 0 }
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, border bool) Window {
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
return nil
}

View File

@@ -95,7 +95,7 @@ type LightRenderer struct {
type LightWindow struct {
renderer *LightRenderer
colored bool
border bool
border BorderStyle
top int
left int
width int
@@ -322,6 +322,8 @@ func (r *LightRenderer) GetChar() Event {
return Event{CtrlQ, 0, nil}
case 127:
return Event{BSpace, 0, nil}
case 0:
return Event{CtrlSpace, 0, nil}
case ESC:
ev := r.escSequence(&sz)
// Second chance
@@ -532,6 +534,7 @@ func (r *LightRenderer) Pause() {
r.rmcup()
} else {
r.smcup()
r.csi("H")
}
r.flush()
}
@@ -549,6 +552,9 @@ func (r *LightRenderer) Resume() bool {
}
func (r *LightRenderer) Clear() {
if r.fullscreen {
r.csi("H")
}
// r.csi("u")
r.origin()
r.csi("J")
@@ -590,18 +596,18 @@ func (r *LightRenderer) MaxY() int {
}
func (r *LightRenderer) DoesAutoWrap() bool {
return true
return false
}
func (r *LightRenderer) IsOptimized() bool {
return false
}
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, border bool) Window {
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
w := &LightWindow{
renderer: r,
colored: r.theme != nil,
border: border,
border: borderStyle,
top: top,
left: left,
width: width,
@@ -611,13 +617,27 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
if r.theme != nil {
w.bg = r.theme.Bg
}
if w.border {
w.drawBorder()
}
w.drawBorder()
return w
}
func (w *LightWindow) drawBorder() {
switch w.border {
case BorderAround:
w.drawBorderAround()
case BorderHorizontal:
w.drawBorderHorizontal()
}
}
func (w *LightWindow) drawBorderHorizontal() {
w.Move(0, 0)
w.CPrint(ColBorder, AttrRegular, repeat("─", w.width))
w.Move(w.height-1, 0)
w.CPrint(ColBorder, AttrRegular, repeat("─", w.width))
}
func (w *LightWindow) drawBorderAround() {
w.Move(0, 0)
w.CPrint(ColBorder, AttrRegular, "┌"+repeat("─", w.width-2)+"┐")
for y := 1; y < w.height-1; y++ {
@@ -745,13 +765,17 @@ func (w *LightWindow) Print(text string) {
w.cprint2(colDefault, w.bg, AttrRegular, text)
}
func cleanse(str string) string {
return strings.Replace(str, "\x1b", "?", -1)
}
func (w *LightWindow) CPrint(pair ColorPair, attr Attr, text string) {
if !w.colored {
w.csiColor(colDefault, colDefault, attrFor(pair, attr))
} else {
w.csiColor(pair.Fg(), pair.Bg(), attr)
}
w.stderrInternal(text, false)
w.stderrInternal(cleanse(text), false)
w.csi("m")
}
@@ -759,7 +783,7 @@ func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
if w.csiColor(fg, bg, attr) {
defer w.csi("m")
}
w.stderrInternal(text, false)
w.stderrInternal(cleanse(text), false)
}
type wrappedLine struct {
@@ -847,9 +871,7 @@ func (w *LightWindow) FinishFill() {
}
func (w *LightWindow) Erase() {
if w.border {
w.drawBorder()
}
w.drawBorder()
// We don't erase the window here to avoid flickering during scroll
w.Move(0, 0)
}

View File

@@ -189,12 +189,13 @@ func (r *FullscreenRenderer) Close() {
C.delscreen(_screen)
}
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, border bool) Window {
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
if r.theme != nil {
C.wbkgd(win, C.chtype(C.COLOR_PAIR(C.int(ColNormal.index()))))
}
if border {
// FIXME Does not implement BorderHorizontal
if borderStyle != BorderNone {
pair, attr := _colorFn(ColBorder, 0)
C.wcolor_set(win, pair, nil)
C.wattron(win, attr)
@@ -475,6 +476,8 @@ func (r *FullscreenRenderer) GetChar() Event {
return escSequence()
case 127:
return Event{BSpace, 0, nil}
case 0:
return Event{CtrlSpace, 0, nil}
}
// CTRL-A ~ CTRL-Z
if c >= CtrlA && c <= CtrlZ {

View File

@@ -27,15 +27,15 @@ func (p ColorPair) style() tcell.Style {
type Attr tcell.Style
type TcellWindow struct {
color bool
top int
left int
width int
height int
lastX int
lastY int
moveCursor bool
border bool
color bool
top int
left int
width int
height int
lastX int
lastY int
moveCursor bool
borderStyle BorderStyle
}
func (w *TcellWindow) Top() int {
@@ -61,8 +61,11 @@ func (w *TcellWindow) Refresh() {
}
w.lastX = 0
w.lastY = 0
if w.border {
w.drawBorder()
switch w.borderStyle {
case BorderAround:
w.drawBorder(true)
case BorderHorizontal:
w.drawBorder(false)
}
}
@@ -270,6 +273,8 @@ func (r *FullscreenRenderer) GetChar() Event {
return Event{CtrlY, 0, nil}
case tcell.KeyCtrlZ:
return Event{CtrlZ, 0, nil}
case tcell.KeyCtrlSpace:
return Event{CtrlSpace, 0, nil}
case tcell.KeyBackspace, tcell.KeyBackspace2:
if alt {
return Event{AltBS, 0, nil}
@@ -375,15 +380,15 @@ func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
_screen.Show()
}
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, border bool) Window {
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
// TODO
return &TcellWindow{
color: r.theme != nil,
top: top,
left: left,
width: width,
height: height,
border: border}
color: r.theme != nil,
top: top,
left: left,
width: width,
height: height,
borderStyle: borderStyle}
}
func (w *TcellWindow) Close() {
@@ -534,7 +539,7 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
return w.fillString(str, ColorPair{fg, bg, -1}, a)
}
func (w *TcellWindow) drawBorder() {
func (w *TcellWindow) drawBorder(around bool) {
left := w.left
right := left + w.width
top := w.top
@@ -552,13 +557,15 @@ func (w *TcellWindow) drawBorder() {
_screen.SetContent(x, bot-1, tcell.RuneHLine, nil, style)
}
for y := top; y < bot; y++ {
_screen.SetContent(left, y, tcell.RuneVLine, nil, style)
_screen.SetContent(right-1, y, tcell.RuneVLine, nil, style)
}
if around {
for y := top; y < bot; y++ {
_screen.SetContent(left, y, tcell.RuneVLine, nil, style)
_screen.SetContent(right-1, y, tcell.RuneVLine, nil, style)
}
_screen.SetContent(left, top, tcell.RuneULCorner, nil, style)
_screen.SetContent(right-1, top, tcell.RuneURCorner, nil, style)
_screen.SetContent(left, bot-1, tcell.RuneLLCorner, nil, style)
_screen.SetContent(right-1, bot-1, tcell.RuneLRCorner, nil, style)
_screen.SetContent(left, top, tcell.RuneULCorner, nil, style)
_screen.SetContent(right-1, top, tcell.RuneURCorner, nil, style)
_screen.SetContent(left, bot-1, tcell.RuneLLCorner, nil, style)
_screen.SetContent(right-1, bot-1, tcell.RuneLRCorner, nil, style)
}
}

View File

@@ -38,6 +38,7 @@ const (
CtrlY
CtrlZ
ESC
CtrlSpace
Invalid
Resize
@@ -194,6 +195,14 @@ type MouseEvent struct {
Mod bool
}
type BorderStyle int
const (
BorderNone BorderStyle = iota
BorderAround
BorderHorizontal
)
type Renderer interface {
Init()
Pause()
@@ -210,7 +219,7 @@ type Renderer interface {
DoesAutoWrap() bool
IsOptimized() bool
NewWindow(top int, left int, width int, height int, border bool) Window
NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window
}
type Window interface {

View File

@@ -617,6 +617,17 @@ class TestGoFZF < TestBase
], `#{FZF} -foo --tiebreak=begin,length < #{tempname}`.split($/)
end
def test_tiebreak_begin_algo_v2
writelines tempname, [
'baz foo bar',
'foo bar baz',
]
assert_equal [
'foo bar baz',
'baz foo bar',
], `#{FZF} -fbar --tiebreak=begin --algo=v2 < #{tempname}`.split($/)
end
def test_tiebreak_end
writelines tempname, [
'xoxxxxxxxx',
@@ -879,7 +890,7 @@ class TestGoFZF < TestBase
def test_execute_multi
output = '/tmp/fzf-test-execute-multi'
opts = %[--multi --bind \\"alt-a:execute-multi(echo {}/{} >> #{output}; sync)\\"]
opts = %[--multi --bind \\"alt-a:execute-multi(echo {}/{+} >> #{output}; sync)\\"]
writelines tempname, %w[foo'bar foo"bar foo$bar foobar]
tmux.send_keys "cat #{tempname} | #{fzf opts}", :Enter
tmux.until { |lines| lines[-2].include? '4/4' }
@@ -902,6 +913,43 @@ class TestGoFZF < TestBase
File.unlink output rescue nil
end
def test_execute_plus_flag
output = tempname + ".tmp"
File.unlink output rescue nil
writelines tempname, ["foo bar", "123 456"]
tmux.send_keys "cat #{tempname} | #{FZF} --multi --bind 'x:execute(echo {+}/{}/{+2}/{2} >> #{output})'", :Enter
execute = lambda do
tmux.send_keys 'x', 'y'
tmux.until { |lines| lines[-2].include? '0/2' }
tmux.send_keys :BSpace
tmux.until { |lines| lines[-2].include? '2/2' }
end
tmux.until { |lines| lines[-2].include? '2/2' }
execute.call
tmux.send_keys :Up
tmux.send_keys :Tab
execute.call
tmux.send_keys :Tab
execute.call
tmux.send_keys :Enter
tmux.prepare
readonce
assert_equal [
%[foo bar/foo bar/bar/bar],
%[123 456/foo bar/456/bar],
%[123 456 foo bar/foo bar/456 bar/bar]
], File.readlines(output).map(&:chomp)
rescue
File.unlink output rescue nil
end
def test_execute_shell
# Custom script to use as $SHELL
output = tempname + '.out'
@@ -1198,7 +1246,7 @@ class TestGoFZF < TestBase
end
def test_preview
tmux.send_keys %[seq 1000 | sed s/^2$// | #{FZF} --preview 'sleep 0.2; echo {{}-{}}' --bind ?:toggle-preview], :Enter
tmux.send_keys %[seq 1000 | sed s/^2$// | #{FZF} -m --preview 'sleep 0.2; echo {{}-{+}}' --bind ?:toggle-preview], :Enter
tmux.until { |lines| lines[1].include?(' {1-1}') }
tmux.send_keys :Up
tmux.until { |lines| lines[1].include?(' {-}') }
@@ -1212,6 +1260,17 @@ class TestGoFZF < TestBase
tmux.until { |lines| lines[-2].start_with? ' 28/1000' }
tmux.send_keys 'foobar'
tmux.until { |lines| !lines[1].include?('{') }
tmux.send_keys 'C-u'
tmux.until { |lines| lines.match_count == 1000 }
tmux.until { |lines| lines[1].include?(' {1-1}') }
tmux.send_keys :BTab
tmux.until { |lines| lines[1].include?(' {-1}') }
tmux.send_keys :BTab
tmux.until { |lines| lines[1].include?(' {3-1 }') }
tmux.send_keys :BTab
tmux.until { |lines| lines[1].include?(' {4-1 3}') }
tmux.send_keys :BTab
tmux.until { |lines| lines[1].include?(' {5-1 3 4}') }
end
def test_preview_hidden
@@ -1495,7 +1554,7 @@ module CompletionTest
tmux.prepare
# Using tmux
tmux.send_keys 'unset FZFFOO**', :Tab
tmux.send_keys 'unset FZFFOOBR**', :Tab
tmux.until { |lines| lines.match_count == 1 }
tmux.send_keys :Enter
tmux.until { |lines| lines[-1].include? 'unset FZFFOOBAR' }
@@ -1503,7 +1562,7 @@ module CompletionTest
# FZF_TMUX=1
new_shell
tmux.send_keys 'unset FZFFO**', :Tab, pane: 0
tmux.send_keys 'unset FZFFOOBR**', :Tab, pane: 0
tmux.until(false, 1) { |lines| lines.match_count == 1 }
tmux.send_keys :Enter
tmux.until { |lines| lines[-1].include? 'unset FZFFOOBAR' }