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

Compare commits

...

37 Commits

Author SHA1 Message Date
Junegunn Choi
f0fe79dd3b 0.17.4 2018-06-10 10:35:52 +09:00
Akinori MUSHA
daa1958f86 Provide an option to reverse items only (#1267) 2018-06-10 01:41:50 +09:00
Junegunn Choi
2c26f02f5c Improve preview window update events
- Update preview window even if there is no match for the query string
  if any of the placeholder expressions evaluates to a non-empty string.
- Also, if the command template contains {q}, preview window will be
  updated if the query string changes even though the focus remains on
  the same item.

An example:

    git log --oneline --color=always |
       fzf --reverse --ansi --preview \
       '[ -n {1} ] && git show --color=always {1} || git show --color=always {q}'

Close #1307
2018-06-10 01:40:22 +09:00
Junegunn Choi
af87650bc4 [docker] Build binary from source 2018-06-08 19:42:29 +09:00
ptzz
2b19c0bc68 [bash/zsh] Fix missing fuzzy completions (#1303)
* [bash/zsh] Fix missing fuzzy completions

`cat foo**<TAB>` did not display the file `foobar` if there was a directory
named `foo`.

Fixes #1301

* [zsh] Evaluate completion prefix

  cat $HOME**
  cat ~username**
  cat ~username/foo**
2018-06-02 10:40:33 +09:00
Junegunn Choi
76a2dcb5a9 Add Dockerfile for running tests
make docker
make docker-test
2018-06-01 18:23:25 +09:00
Junegunn Choi
68ec3d1c10 Fix flaky test cases 2018-06-01 18:21:34 +09:00
Mark
2ff19084ca [install] Support for XDG Base Directory Specification (#1282)
Add --xdg option which makes the installer generate files under $XDG_CONFIG_HOME/fzf.
2018-06-01 11:54:58 +09:00
Daniel Gray
62f062ecfa Remove -y flag from Arch Linux installation (#1290)
https://wiki.archlinux.org/index.php/Partial_upgrades#Partial_upgrades_are_unsupported

You should never `pacman -Sy <pkg>`, Arch users are expected
to keep their system already up-to-date before installing anything.
2018-05-14 17:24:32 +09:00
Jan Edmund Lazo
cce17ad0a0 [vim] Use CRLF in batchfile for multibyte codepage (#1289)
Close #1288
2018-05-13 16:24:28 +09:00
Junegunn Choi
b8296a91b9 Clarify Vim plugin instruction
Close #1251

@amaravora
2018-05-04 16:01:42 +09:00
Junegunn Choi
6e9452b06e Add Arch Linux installation instruction
Close #1273

@codingCoffee
2018-05-04 16:00:40 +09:00
Junegunn Choi
888fd35689 [fzf-tmux] Avoid unnecessary recovery of window options
fzf-tmux temporarily turns off remain-on-exit and synchronize-panes
options. We don't have to try to restore the values of the options if
they were already turned off when fzf-tmux was started.
2018-05-04 15:38:27 +09:00
ptzz
1fb0fbca58 [bash] Do not print error when falling back to default completion (#1279)
Fixes #1278
2018-05-04 14:55:48 +09:00
Heinrich Kruger
ddd2a109e4 [fzf-tmux] Restore tmux window options (#1272)
Restore the original values of 'remain-on-exit' and 'synchronize-panes'
options when exiting 'fzf-tmux'.
2018-05-04 04:25:15 +09:00
Junegunn Choi
87504a528e [bash] Fix infinite loop on tab completion
awk may not set OFS to match FS depending on the implementation.

Close #1227
2018-04-30 12:58:10 +09:00
Junegunn Choi
6eac4af7db [vim] Ignore Vim:Interrupt when "Abort" selected on E325
Close #1268
2018-04-26 10:23:18 +09:00
Junegunn Choi
89de1340af [bash] Add --sync to the default CTRL-R options
This compensates the use of --tac. fzf will not render on the screen
until the complete list of commands are loaded.
2018-04-25 18:47:56 +09:00
Junegunn Choi
9e753a0d44 Implement ttyname() in case /dev/tty is not available
Close #1266
Close #447
2018-04-25 17:50:47 +09:00
Junegunn Choi
f57920ad90 Do not print non-displayable characters
fzf used to print non-displayable characters (ascii code < 32) as '?',
but we will simply ignore those characters with this patch, just like
our terminals do.

\n and \r are exceptions. They will be printed as a space character.

TODO: \H should delete the preceding character, but this is not implemented.

Related: #1253
2018-04-12 17:49:52 +09:00
Junegunn Choi
7dbbbef51a Add support for alt-{up,down,left,right} keys
Close #1234
2018-04-12 17:42:48 +09:00
Avindra Goolcharan
7add75126d ZSH and Bash completion: remove shebang (#1248)
Shebangs are only for files that are directly executable. In cases
where files are only sourced (such as completion scripts), these
are unneeded.
2018-04-12 17:21:56 +09:00
Akinori MUSHA
d207672bd5 Parse the output of go version to get the value of GOOS (#1260) 2018-04-12 17:09:08 +09:00
Robert Orzanna
851fa38251 Add reference to Fedora package documentation (#1255) 2018-04-06 14:10:10 +09:00
ZDNoFYVe
43345fb642 Implement flag for preserving whitespace around field (#1242) 2018-03-30 11:47:46 +09:00
xalexalex
9ff33814ea Fix typo in README (#1243) 2018-03-27 17:53:20 +09:00
Ryan Boehning
21b94d2de5 Make fzf pass go vet
Add String() methods to types, so they can be printed with %s. Change
some %s format specifiers to %v, when the default string representation
is good enough. In Go 1.10, `go test` triggers a parallel `go vet`. So
this also makes fzf pass `go test`.

Close #1236
Close #1219
2018-03-13 14:56:55 +09:00
Jesse Leite
24236860c8 Document inverse prefix exact match search syntax (#1224)
* Document inverse prefix exact match search syntax.

* Reorder search syntax table to explain basic exact match first.
2018-03-06 16:33:33 +09:00
Junegunn Choi
3f868fd792 [bash] Fix CTRL-R to preserve the latest yank
Close #1216

1. Append a single space so that step 3 won't fail
2. CTRL-E to move to the end of the line
3. CTRL-U to delete the whole line before the cursor
4. CTRL-Y to paste the deleted line
5. ESC+Y to rotate the kill ring and bring back the previous yank before step 3
6. CTRL-U to delete the whole line again
7. Paste `__fzf_history__`
8. ESC+CTRL-E to expand the command substitution
9. ESC+R to redraw the line
10. ESC+^ to expand the history entry (!NUMBER)
2018-02-16 21:55:23 +09:00
Junegunn Choi
417bca03df Add shift-up and shift-down
For now, they are respectively bound to preview-up and preview-down
by default (TBD).

Not available on tcell build.

Close #1201
2018-02-15 19:57:21 +09:00
Junegunn Choi
cce6aef557 [bash] Fix extra space issue of dynamic completion with 'nospace'
Close #1203
2018-02-15 18:21:02 +09:00
Junegunn Choi
eb3afc03b5 [vim] Make list options compatible with layout options
Fix #1205
2018-01-26 13:48:05 +09:00
Jan Edmund Lazo
7f0caf0683 Update Windows default command to print relative paths (#1200) 2018-01-17 22:02:50 +09:00
Pierre P
7f606665cb [install] Make default answer "y" (#1195) 2018-01-14 03:19:33 +09:00
Junegunn Choi
202872c2dc Remove PayPal donation button
I've decided not to take more donations.

Thanks to everyone who has supported my projects.

Edgar Hipp
Eyal Levin
Philip Stewart
James O'Beirne
Minh Triet Ly
Victor Alvarez
Max Hung
Gearoid Murphy
Aaron Taylor
Brett Bender
Phil Thompson
Anders Damsgaard
2018-01-06 02:47:42 +09:00
Junegunn Choi
93aeae1985 [bash] Trigger redraw-current-line before history-expand-line
Close #681
2017-12-07 23:33:01 +09:00
Junegunn Choi
5c34ab6692 [vim] Fix terminal buffer cleanup on Vim 8
Close #1172
2017-12-05 23:50:55 +09:00
33 changed files with 635 additions and 190 deletions

View File

@@ -22,7 +22,7 @@
### Before submitting
- Make sure that you have the latest version of fzf
- If you use tmux, make sure $TERM is set to screen or screen-256color
- If you use tmux, make sure $TERM starts with "screen"
- For more Vim stuff, check out https://github.com/junegunn/fzf.vim
Describe your problem or suggestion from here ...

View File

@@ -1,6 +1,26 @@
CHANGELOG
=========
0.17.4
------
- Added `--layout` option with a new layout called `reverse-list`.
- `--layout=reverse` is a synonym for `--reverse`
- `--layout=default` is a synonym for `--no-reverse`
- Preview window will be updated even when there is no match for the query
if any of the placeholder expressions (e.g. `{q}`, `{+}`) evaluates to
a non-empty string.
- More keys for binding: `shift-{up,down}`, `alt-{up,down,left,right}`
- fzf can now start even when `/dev/tty` is not available by making an
educated guess.
- Updated the default command for Windows.
- Fixes and improvements on bash/zsh completion
- install and uninstall scripts now supports generating files under
`XDG_CONFIG_HOME` on `--xdg` flag.
See https://github.com/junegunn/fzf/milestone/12?closed=1 for the full list of
changes.
0.17.3
------
- `$LINES` and `$COLUMNS` are exported to preview command so that the command

11
Dockerfile Normal file
View File

@@ -0,0 +1,11 @@
FROM archlinux/base:latest
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make
RUN gem install --no-ri --no-rdoc minitest
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
RUN echo '. ~/.bashrc' >> ~/.bash_profile
# Do not set default PS1
RUN rm -f /etc/bash.bashrc
COPY . /fzf
RUN cd /fzf && make install && ./install --all
CMD tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]

View File

@@ -1,12 +1,5 @@
ifndef GOOS
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
GOOS := darwin
else ifeq ($(UNAME_S),Linux)
GOOS := linux
else
$(error "$$GOOS is not defined.")
endif
GOOS := $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
endif
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
@@ -135,4 +128,12 @@ target/$(BINARYARM8): $(SOURCES) vendor
bin/fzf: target/$(BINARY) | bin
cp -f target/$(BINARY) bin/fzf
.PHONY: all release release-all test install clean
docker:
docker build -t fzf-arch .
docker run -it fzf-arch tmux
docker-test:
docker build -t fzf-arch .
docker run -it fzf-arch
.PHONY: all release release-all test install clean docker docker-test

View File

@@ -1,4 +1,4 @@
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf.png" height="170" alt="fzf - a command-line fuzzy finder"> [![travis-ci](https://travis-ci.org/junegunn/fzf.svg?branch=master)](https://travis-ci.org/junegunn/fzf) [![Donate via PayPal](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=EKYAW9PGKPD2N)
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf.png" height="170" alt="fzf - a command-line fuzzy finder"> [![travis-ci](https://travis-ci.org/junegunn/fzf.svg?branch=master)](https://travis-ci.org/junegunn/fzf)
===
fzf is a general-purpose command-line fuzzy finder.
@@ -26,6 +26,7 @@ Table of Contents
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
* [Using git](#using-git)
* [As Vim plugin](#as-vim-plugin)
* [Arch Linux](#arch-linux)
* [Fedora](#fedora)
* [Windows](#windows)
* [Upgrading fzf](#upgrading-fzf)
@@ -99,7 +100,7 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
### As Vim plugin
Once you have fzf installed, you can enable it inside Vim simply by adding the
directory to `&runtimepath` as follows:
directory to `&runtimepath` in your Vim configuration file as follows:
```vim
" If installed using Homebrew
@@ -131,6 +132,12 @@ Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
" and you don't have to run install script if you use fzf only in Vim.
```
### Arch Linux
```sh
sudo pacman -S fzf
```
### Fedora
fzf is available in Fedora 26 and above, and can be installed using the usual
@@ -142,8 +149,7 @@ sudo dnf install fzf
Shell completion and plugins for vim or neovim are enabled by default. Shell
key bindings are installed but not enabled by default. See Fedora's package
documentation for more information.
documentation (/usr/share/doc/fzf/README.Fedora) for more information.
### Windows
@@ -217,8 +223,8 @@ cursor with `--height` option.
vim $(fzf --height 40%)
```
Also check out `--reverse` option if you prefer "top-down" layout instead of
the default "bottom-up" layout.
Also check out `--reverse` and `--layout` options if you prefer
"top-down" layout instead of the default "bottom-up" layout.
```sh
vim $(fzf --height 40% --reverse)
@@ -228,7 +234,7 @@ You can add these options to `$FZF_DEFAULT_OPTS` so that they're applied by
default. For example,
```sh
export FZF_DEFAULT_OPTS='--height 40% --reverse --border'
export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'
```
#### Search syntax
@@ -237,14 +243,15 @@ Unless otherwise specified, fzf starts in "extended-search mode" where you can
type in multiple search terms delimited by spaces. e.g. `^music .mp3$ sbtrkt
!fire`
| Token | Match type | Description |
| -------- | -------------------------- | --------------------------------- |
| `sbtrkt` | fuzzy-match | Items that match `sbtrkt` |
| `^music` | prefix-exact-match | Items that start with `music` |
| `.mp3$` | suffix-exact-match | Items that end with `.mp3` |
| `'wild` | exact-match (quoted) | Items that include `wild` |
| `!fire` | inverse-exact-match | Items that do not include `fire` |
| `!.mp3$` | inverse-suffix-exact-match | Items that do not end with `.mp3` |
| Token | Match type | Description |
| --------- | -------------------------- | ------------------------------------ |
| `sbtrkt` | fuzzy-match | Items that match `sbtrkt` |
| `'wild` | exact-match (quoted) | Items that include `wild` |
| `^music` | prefix-exact-match | Items that start with `music` |
| `.mp3$` | suffix-exact-match | Items that end with `.mp3` |
| `!fire` | inverse-exact-match | Items that do not include `fire` |
| `!^music` | inverse-prefix-exact-match | Items that do not start with `music` |
| `!.mp3$` | inverse-suffix-exact-match | Items that do not end with `.mp3` |
If you don't prefer fuzzy matching and do not wish to "quote" every word,
start fzf with `-e` or `--exact` option. Note that when `--exact` is set,
@@ -265,7 +272,7 @@ or `py`.
- e.g. `export FZF_DEFAULT_COMMAND='fd --type f'`
- `FZF_DEFAULT_OPTS`
- Default options
- e.g. `export FZF_DEFAULT_OPTS="--reverse --inline-info"`
- e.g. `export FZF_DEFAULT_OPTS="--layout=reverse --inline-info"`
#### Options

View File

@@ -136,6 +136,11 @@ fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
cleanup() {
\rm -f $argsf $fifo1 $fifo2 $fifo3
# Restore tmux window options
if [[ "${#tmux_win_opts[@]}" -gt 0 ]]; then
eval "tmux ${tmux_win_opts[@]}"
fi
# Remove temp window if we were zoomed
if [[ -n "$zoomed" ]]; then
tmux display-message -p "#{window_id}" > /dev/null
@@ -174,6 +179,8 @@ pppid=$$
echo -n "trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM;" > $argsf
close="; trap - EXIT SIGINT SIGTERM $close"
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
if [[ -n "$term" ]] || [[ -t 0 ]]; then
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\

32
install
View File

@@ -2,13 +2,16 @@
set -u
version=0.17.3
version=0.17.4
auto_completion=
key_bindings=
update_config=2
binary_arch=
allow_legacy=
shells="bash zsh fish"
prefix='~/.fzf'
prefix_expand=~/.fzf
fish_dir=${XDG_CONFIG_HOME:-$HOME/.config}/fish
help() {
cat << EOF
@@ -18,6 +21,7 @@ usage: $0 [OPTIONS]
--bin Download fzf binary only; Do not generate ~/.fzf.{bash,zsh}
--all Download fzf binary and update configuration files
to enable key bindings and fuzzy completion
--xdg Generate files under \$XDG_CONFIG_HOME/fzf
--[no-]key-bindings Enable/disable key bindings (CTRL-T, CTRL-R, ALT-C)
--[no-]completion Enable/disable fuzzy completion (bash & zsh)
--[no-]update-rc Whether or not to update shell configuration files
@@ -43,6 +47,11 @@ for opt in "$@"; do
update_config=1
allow_legacy=1
;;
--xdg)
prefix='"${XDG_CONFIG_HOME:-$HOME/.config}"/fzf/fzf'
prefix_expand=${XDG_CONFIG_HOME:-$HOME/.config}/fzf/fzf
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/fzf"
;;
--key-bindings) key_bindings=1 ;;
--no-key-bindings) key_bindings=0 ;;
--completion) auto_completion=1 ;;
@@ -69,6 +78,7 @@ fzf_base="$(pwd)"
ask() {
while true; do
read -p "$1 ([y]/n) " -r
REPLY=${REPLY:-"y"}
if [[ $REPLY =~ ^[Yy]$ ]]; then
return 1
elif [[ $REPLY =~ ^[Nn]$ ]]; then
@@ -239,8 +249,8 @@ fi
echo
for shell in $shells; do
[[ "$shell" = fish ]] && continue
echo -n "Generate ~/.fzf.$shell ... "
src=~/.fzf.${shell}
src=${prefix_expand}.${shell}
echo -n "Generate $src ... "
fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null"
if [ $auto_completion -eq 0 ]; then
@@ -252,7 +262,7 @@ for shell in $shells; do
fzf_key_bindings="# $fzf_key_bindings"
fi
cat > $src << EOF
cat > "$src" << EOF
# Setup fzf
# ---------
if [[ ! "\$PATH" == *$fzf_base/bin* ]]; then
@@ -280,13 +290,13 @@ if [[ "$shells" =~ fish ]]; then
EOF
[ $? -eq 0 ] && echo "OK" || echo "Failed"
mkdir -p ~/.config/fish/functions
if [ -e ~/.config/fish/functions/fzf.fish ]; then
echo -n "Remove unnecessary ~/.config/fish/functions/fzf.fish ... "
rm -f ~/.config/fish/functions/fzf.fish && echo "OK" || echo "Failed"
mkdir -p "${fish_dir}/functions"
if [ -e "${fish_dir}/functions/fzf.fish" ]; then
echo -n "Remove unnecessary ${fish_dir}/functions/fzf.fish ... "
rm -f "${fish_dir}/functions/fzf.fish" && echo "OK" || echo "Failed"
fi
fish_binding=~/.config/fish/functions/fzf_key_bindings.fish
fish_binding="${fish_dir}/functions/fzf_key_bindings.fish"
if [ $key_bindings -ne 0 ]; then
echo -n "Symlink $fish_binding ... "
ln -sf "$fzf_base/shell/key-bindings.fish" \
@@ -352,11 +362,11 @@ echo
for shell in $shells; do
[[ "$shell" = fish ]] && continue
[ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc
append_line $update_config "[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" "$dest" "~/.fzf.${shell}"
append_line $update_config "[ -f ${prefix}.${shell} ] && source ${prefix}.${shell}" "$dest" "${prefix}.${shell}"
done
if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
if [ ! -e "$bind_file" ]; then
create_file "$bind_file" \
'function fish_user_key_bindings' \

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 "Dec 2017" "fzf 0.17.3" "fzf-tmux - open fzf in tmux split pane"
.TH fzf-tmux 1 "Jun 2018" "fzf 0.17.4" "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 "Dec 2017" "fzf 0.17.3" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Jun 2018" "fzf 0.17.4" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -155,9 +155,22 @@ the full screen.
.BI "--min-height=" "HEIGHT"
Minimum height when \fB--height\fR is given in percent (default: 10).
Ignored when \fB--height\fR is not specified.
.TP
.BI "--layout=" "LAYOUT"
Choose the layout (default: default)
.br
.BR default " Display from the bottom of the screen"
.br
.BR reverse " Display from the top of the screen"
.br
.BR reverse-list " Display from the top of the screen, prompt at the bottom"
.br
.TP
.B "--reverse"
Reverse orientation
A synonym for \fB--layout=reverse\fB
.TP
.B "--border"
Draw border above and below the finder
@@ -195,7 +208,7 @@ Input prompt (default: '> ')
.TP
.BI "--header=" "STR"
The given string will be printed as the sticky header. The lines are displayed
in the given order from top to bottom regardless of \fB--reverse\fR option, and
in the given order from top to bottom regardless of \fB--layout\fR option, and
are not affected by \fB--with-nth\fR. ANSI color codes are processed even when
\fB--ansi\fR is not set.
.TP
@@ -285,9 +298,15 @@ was made) individually quoted.
e.g. \fBfzf --multi --preview='head -10 {+}'\fR
\fBgit log --oneline | fzf --multi --preview 'git show {+1}'\fR
When using a field index expression, leading and trailing whitespace is stripped
from the replacement string. To preserve the whitespace, use the \fBs\fR flag.
Also, \fB{q}\fR is replaced to the current query string.
Note that you can escape a placeholder pattern by prepending a backslash.
Preview window will be updated even when there is no match for the current
query if any of the placeholder expressions evaluates to a non-empty string.
.RE
.TP
.BI "--preview-window=" "[POSITION][:SIZE[%]][:wrap][:hidden]"
@@ -461,6 +480,10 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
\fIspace\fR
\fIbspace\fR (\fIbs\fR)
\fIalt-up\fR
\fIalt-down\fR
\fIalt-left\fR
\fIalt-right\fR
\fIalt-enter\fR
\fIalt-space\fR
\fIalt-bspace\fR (\fIalt-bs\fR)
@@ -477,6 +500,8 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
\fIend\fR
\fIpgup\fR (\fIpage-up\fR)
\fIpgdn\fR (\fIpage-down\fR)
\fIshift-up\fR
\fIshift-down\fR
\fIshift-left\fR
\fIshift-right\fR
\fIleft-click\fR
@@ -520,8 +545,8 @@ triggered whenever the query string is changed.
\fBpage-up\fR \fIpgup\fR
\fBhalf-page-down\fR
\fBhalf-page-up\fR
\fBpreview-down\fR
\fBpreview-up\fR
\fBpreview-down\fR \fIshift-down\fR
\fBpreview-up\fR \fIshift-up\fR
\fBpreview-page-down\fR
\fBpreview-page-up\fR
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
@@ -531,8 +556,8 @@ triggered whenever the query string is changed.
\fBtoggle\fR (\fIright-click\fR)
\fBtoggle-all\fR
\fBtoggle+down\fR \fIctrl-i (tab)\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-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
\fBtoggle-preview\fR
\fBtoggle-preview-wrap\fR
\fBtoggle-sort\fR
@@ -583,7 +608,7 @@ 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
responsive until the command is complete. For asynchronous execution, start
your command as a background process (i.e. appending \fB&\fR).
.SH AUTHOR

View File

@@ -50,9 +50,9 @@ if s:is_win
" Use utf-8 for fzf.vim commands
" Return array of shell commands for cmd.exe
function! s:wrap_cmds(cmds)
return ['@echo off', 'for /f "tokens=4" %%a in (''chcp'') do set origchcp=%%a', 'chcp 65001 > nul'] +
return map(['@echo off', 'for /f "tokens=4" %%a in (''chcp'') do set origchcp=%%a', 'chcp 65001 > nul'] +
\ (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) +
\ ['chcp %origchcp% > nul']
\ ['chcp %origchcp% > nul'], 'v:val."\r"')
endfunction
else
let s:term_marker = ";#FZF"
@@ -231,6 +231,7 @@ function! s:common_sink(action, lines) abort
doautocmd BufEnter
endif
endfor
catch /^Vim:Interrupt$/
finally
let &autochdir = autochdir
silent! autocmd! fzf_swap
@@ -586,7 +587,7 @@ function! s:calc_size(max, val, dict)
let srcsz = len(a:dict.source)
endif
let opts = get(a:dict, 'options', '').$FZF_DEFAULT_OPTS
let opts = s:evaluate_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, '--header') > stridx(opts, '--no-header')
return srcsz >= 0 ? min([srcsz + margin, size]) : size
@@ -699,9 +700,9 @@ function! s:execute_term(dict, command, temps) abort
if has('nvim')
call termopen(command, fzf)
else
let t = term_start([&shell, &shellcmdflag, command], {'curwin': fzf.buf, 'exit_cb': function(fzf.on_exit)})
let fzf.buf = term_start([&shell, &shellcmdflag, command], {'curwin': 1, 'exit_cb': function(fzf.on_exit)})
if !has('patch-8.0.1261') && !has('nvim') && !s:is_win
call term_wait(t, 20)
call term_wait(fzf.buf, 20)
endif
endif
finally

View File

@@ -1,4 +1,3 @@
#!/bin/bash
# ____ ____
# / __/___ / __/
# / /_/_ / / /_
@@ -38,9 +37,9 @@ __fzfcmd_complete() {
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
}
_fzf_orig_completion_filter() {
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2";/' |
awk -F= '{gsub(/[^A-Za-z0-9_= ;]/, "_", $1); print $1"="$2}'
__fzf_orig_completion_filter() {
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2"; [[ "\1" = *" -o nospace "* ]] \&\& [[ ! "$__fzf_nospace_commands" = *" \3 "* ]] \&\& __fzf_nospace_commands="$__fzf_nospace_commands \3 ";/' |
awk -F= '{OFS = FS} {gsub(/[^A-Za-z0-9_= ;]/, "_", $1);}1'
}
_fzf_opts_completion() {
@@ -122,13 +121,17 @@ _fzf_handle_dynamic_completion() {
if [ -n "$orig" ] && type "$orig" > /dev/null 2>&1; then
$orig "$@"
elif [ -n "$_fzf_completion_loader" ]; then
orig_complete=$(complete -p "$cmd")
orig_complete=$(complete -p "$cmd" 2> /dev/null)
_completion_loader "$@"
ret=$?
# _completion_loader may not have updated completion for the command
if [ "$(complete -p "$cmd")" != "$orig_complete" ]; then
eval "$(complete | command grep "\-F.* $orig_cmd$" | _fzf_orig_completion_filter)"
eval "$orig_complete"
if [ "$(complete -p "$cmd" 2> /dev/null)" != "$orig_complete" ]; then
eval "$(complete | command grep " -F.* $orig_cmd$" | __fzf_orig_completion_filter)"
if [[ "$__fzf_nospace_commands" = *" $orig_cmd "* ]]; then
eval "${orig_complete/ -F / -o nospace -F }"
else
eval "$orig_complete"
fi
fi
return $ret
fi
@@ -145,7 +148,7 @@ __fzf_generic_path_completion() {
base=${cur:0:${#cur}-${#trigger}}
eval "base=$base"
dir="$base"
[[ $base = *"/"* ]] && dir="$base"
while true; do
if [ -z "$dir" ] || [ -d "$dir" ]; then
leftover=${base/#"$dir"}
@@ -156,6 +159,7 @@ __fzf_generic_path_completion() {
printf "%q$3 " "$item"
done)
matches=${matches% }
[[ -z "$3" ]] && [[ "$__fzf_nospace_commands" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
if [ -n "$matches" ]; then
COMPREPLY=( "$matches" )
else
@@ -278,9 +282,9 @@ a_cmds="
x_cmds="kill ssh telnet unset unalias export"
# Preserve existing completion
eval $(complete |
eval "$(complete |
sed -E '/-F/!d; / _fzf/d; '"/ ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g; s/+/\\+/g'))$/"'!d' |
_fzf_orig_completion_filter)
__fzf_orig_completion_filter)"
if type _completion_loader > /dev/null 2>&1; then
_fzf_completion_loader=1

View File

@@ -1,4 +1,3 @@
#!/bin/zsh
# ____ ____
# / __/___ / __/
# / /_/_ / / /_
@@ -37,8 +36,7 @@ __fzfcmd_complete() {
__fzf_generic_path_completion() {
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches
# (Q) flag removes a quoting level: "foo\ bar" => "foo bar"
base=${(Q)1}
base=$1
lbuf=$2
compgen=$3
fzf_opts=$4
@@ -47,14 +45,14 @@ __fzf_generic_path_completion() {
fzf="$(__fzfcmd_complete)"
setopt localoptions nonomatch
dir="$base"
eval "base=$base"
[[ $base = *"/"* ]] && dir="$base"
while [ 1 ]; do
if [[ -z "$dir" || -d ${~dir} ]]; then
if [[ -z "$dir" || -d ${dir} ]]; then
leftover=${base/#"$dir"}
leftover=${leftover/#\/}
[ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}"
dir=${~dir}
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" ${=fzf} ${=fzf_opts} -q "$leftover" | while read item; do
echo -n "${(q)item}$suffix "
done)

View File

@@ -56,7 +56,7 @@ __fzf_history__() (
shopt -u nocaseglob nocasematch
line=$(
HISTTIMEFORMAT= history |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tac -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tac --sync -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"
@@ -80,7 +80,7 @@ if [[ ! -o vi ]]; then
fi
# CTRL-R - Paste the selected command from history into the command line
bind '"\C-r": " \C-e\C-u`__fzf_history__`\e\C-e\e^\er"'
bind '"\C-r": " \C-e\C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er\e^"'
# ALT-C - cd into the selected directory
bind '"\ec": " \C-e\C-u`__fzf_cd__`\e\C-e\er\C-m"'
@@ -110,7 +110,7 @@ else
bind -m vi-command '"\C-t": "i\C-t"'
# CTRL-R - Paste the selected command from history into the command line
bind '"\C-r": "\C-x\C-addi`__fzf_history__`\C-x\C-e\C-x^\C-x\C-a$a\C-x\C-r"'
bind '"\C-r": "\C-x\C-addi`__fzf_history__`\C-x\C-e\C-x\C-r\C-x^\C-x\C-a$a"'
bind -m vi-command '"\C-r": "i\C-r"'
# ALT-C - cd into the selected directory

View File

@@ -26,7 +26,7 @@ func TestExtractColor(t *testing.T) {
output, ansiOffsets, newState := extractColor(src, state, nil)
state = newState
if output != "hello world" {
t.Errorf("Invalid output: %s %s", output, []rune(output))
t.Errorf("Invalid output: %s %v", output, []rune(output))
}
fmt.Println(src, ansiOffsets, clean)
assertion(ansiOffsets, state)

View File

@@ -9,7 +9,7 @@ import (
const (
// Current version
version = "0.17.3"
version = "0.17.4"
// Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond
@@ -59,7 +59,7 @@ func init() {
} else if os.Getenv("TERM") == "cygwin" {
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
} else {
defaultCommand = `dir /s/b`
defaultCommand = `for /r %P in (*) do @(set "_curfile=%P" & set "_curfile=!_curfile:%__CD__%=!" & echo !_curfile!)`
}
}

View File

@@ -53,7 +53,7 @@ const usage = `usage: fzf [options]
height instead of using fullscreen
--min-height=HEIGHT Minimum height when --height is given in percent
(default: 10)
--reverse Reverse orientation
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
--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
@@ -90,7 +90,8 @@ const usage = `usage: fzf [options]
Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty
FZF_DEFAULT_OPTS Default options (e.g. '--reverse --inline-info')
FZF_DEFAULT_OPTS Default options
(e.g. '--layout=reverse --inline-info')
`
@@ -132,6 +133,14 @@ const (
posRight
)
type layoutType int
const (
layoutDefault layoutType = iota
layoutReverse
layoutReverseList
)
type previewOpts struct {
command string
position windowPosition
@@ -161,7 +170,7 @@ type Options struct {
Bold bool
Height sizeSpec
MinHeight int
Reverse bool
Layout layoutType
Cycle bool
Hscroll bool
HscrollOff int
@@ -211,7 +220,7 @@ func defaultOptions() *Options {
Black: false,
Bold: true,
MinHeight: 10,
Reverse: false,
Layout: layoutDefault,
Cycle: false,
Hscroll: true,
HscrollOff: 10,
@@ -410,6 +419,14 @@ func parseKeyChords(str string, message string) map[int]string {
chord = tui.AltSlash
case "alt-bs", "alt-bspace":
chord = tui.AltBS
case "alt-up":
chord = tui.AltUp
case "alt-down":
chord = tui.AltDown
case "alt-left":
chord = tui.AltLeft
case "alt-right":
chord = tui.AltRight
case "tab":
chord = tui.Tab
case "btab", "shift-tab":
@@ -426,6 +443,10 @@ func parseKeyChords(str string, message string) map[int]string {
chord = tui.PgUp
case "pgdn", "page-down":
chord = tui.PgDn
case "shift-up":
chord = tui.SUp
case "shift-down":
chord = tui.SDown
case "shift-left":
chord = tui.SLeft
case "shift-right":
@@ -845,6 +866,20 @@ func parseHeight(str string) sizeSpec {
return size
}
func parseLayout(str string) layoutType {
switch str {
case "default":
return layoutDefault
case "reverse":
return layoutReverse
case "reverse-list":
return layoutReverseList
default:
errorExit("invalid layout (expected: default / reverse / reverse-list)")
}
return layoutDefault
}
func parsePreviewWindow(opts *previewOpts, input string) {
// Default
opts.position = posRight
@@ -1025,10 +1060,13 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Bold = true
case "--no-bold":
opts.Bold = false
case "--layout":
opts.Layout = parseLayout(
nextString(allArgs, &i, "layout required (default / reverse / reverse-list)"))
case "--reverse":
opts.Reverse = true
opts.Layout = layoutReverse
case "--no-reverse":
opts.Reverse = false
opts.Layout = layoutDefault
case "--cycle":
opts.Cycle = true
case "--no-cycle":
@@ -1144,6 +1182,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Height = parseHeight(value)
} else if match, value := optString(arg, "--min-height="); match {
opts.MinHeight = atoi(value)
} else if match, value := optString(arg, "--layout="); match {
opts.Layout = parseLayout(value)
} else if match, value := optString(arg, "--toggle-sort="); match {
parseToggleSort(opts.Keymap, value)
} else if match, value := optString(arg, "--expect="); match {

View File

@@ -50,7 +50,7 @@ func TestDelimiterRegexString(t *testing.T) {
tokens[2].text.ToString() != "---*" ||
tokens[3].text.ToString() != "*" ||
tokens[4].text.ToString() != "---" {
t.Errorf("%s %s %d", delim, tokens, len(tokens))
t.Errorf("%s %v %d", delim, tokens, len(tokens))
}
}
@@ -71,7 +71,7 @@ func TestSplitNth(t *testing.T) {
if len(ranges) != 1 ||
ranges[0].begin != rangeEllipsis ||
ranges[0].end != rangeEllipsis {
t.Errorf("%s", ranges)
t.Errorf("%v", ranges)
}
}
{
@@ -87,7 +87,7 @@ func TestSplitNth(t *testing.T) {
ranges[7].begin != -2 || ranges[7].end != -2 ||
ranges[8].begin != 2 || ranges[8].end != -2 ||
ranges[9].begin != rangeEllipsis || ranges[9].end != rangeEllipsis {
t.Errorf("%s", ranges)
t.Errorf("%v", ranges)
}
}
}
@@ -99,7 +99,7 @@ func TestIrrelevantNth(t *testing.T) {
parseOptions(opts, words)
postProcessOptions(opts)
if len(opts.Nth) != 0 {
t.Errorf("nth should be empty: %s", opts.Nth)
t.Errorf("nth should be empty: %v", opts.Nth)
}
}
for _, words := range [][]string{[]string{"--nth", "..,3", "+x"}, []string{"--nth", "3,1..", "+x"}, []string{"--nth", "..-1,1", "+x"}} {
@@ -108,7 +108,7 @@ func TestIrrelevantNth(t *testing.T) {
parseOptions(opts, words)
postProcessOptions(opts)
if len(opts.Nth) != 0 {
t.Errorf("nth should be empty: %s", opts.Nth)
t.Errorf("nth should be empty: %v", opts.Nth)
}
}
{
@@ -117,7 +117,7 @@ func TestIrrelevantNth(t *testing.T) {
parseOptions(opts, words)
postProcessOptions(opts)
if len(opts.Nth) != 2 {
t.Errorf("nth should not be empty: %s", opts.Nth)
t.Errorf("nth should not be empty: %v", opts.Nth)
}
}
}

View File

@@ -1,6 +1,7 @@
package fzf
import (
"fmt"
"regexp"
"strings"
@@ -34,6 +35,11 @@ type term struct {
caseSensitive bool
}
// String returns the string representation of a term.
func (t term) String() string {
return fmt.Sprintf("term{typ: %d, inv: %v, text: []rune(%q), caseSensitive: %v}", t.typ, t.inv, string(t.text), t.caseSensitive)
}
type termSet []term
// Pattern represents search pattern

View File

@@ -31,12 +31,12 @@ func TestParseTermsExtended(t *testing.T) {
terms[8][1].typ != termExact || terms[8][1].inv ||
terms[8][2].typ != termSuffix || terms[8][2].inv ||
terms[8][3].typ != termExact || !terms[8][3].inv {
t.Errorf("%s", terms)
t.Errorf("%v", terms)
}
for _, termSet := range terms[:8] {
term := termSet[0]
if len(term.text) != 3 {
t.Errorf("%s", term)
t.Errorf("%v", term)
}
}
}
@@ -53,14 +53,14 @@ func TestParseTermsExtendedExact(t *testing.T) {
terms[5][0].typ != termFuzzy || !terms[5][0].inv || len(terms[5][0].text) != 3 ||
terms[6][0].typ != termPrefix || !terms[6][0].inv || len(terms[6][0].text) != 3 ||
terms[7][0].typ != termSuffix || !terms[7][0].inv || len(terms[7][0].text) != 3 {
t.Errorf("%s", terms)
t.Errorf("%v", terms)
}
}
func TestParseTermsEmpty(t *testing.T) {
terms := parseTerms(true, CaseSmart, false, "' ^ !' !^")
if len(terms) != 0 {
t.Errorf("%s", terms)
t.Errorf("%v", terms)
}
}
@@ -73,7 +73,7 @@ func TestExact(t *testing.T) {
res, pos := algo.ExactMatchNaive(
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
if res.Start != 7 || res.End != 10 {
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
t.Errorf("%v / %d / %d", pattern.termSets, res.Start, res.End)
}
if pos != nil {
t.Errorf("pos is expected to be nil")
@@ -90,7 +90,7 @@ func TestEqual(t *testing.T) {
res, pos := algo.EqualMatch(
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
if res.Start != sidxExpected || res.End != eidxExpected {
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
t.Errorf("%v / %d / %d", pattern.termSets, res.Start, res.End)
}
if pos != nil {
t.Errorf("pos is expected to be nil")

View File

@@ -24,7 +24,7 @@ import (
var placeholder *regexp.Regexp
func init() {
placeholder = regexp.MustCompile("\\\\?(?:{\\+?[0-9,-.]*}|{q})")
placeholder = regexp.MustCompile("\\\\?(?:{[+s]*[0-9,-.]*}|{q})")
}
type jumpMode int
@@ -59,7 +59,7 @@ type Terminal struct {
inlineInfo bool
prompt string
promptLen int
reverse bool
layout layoutType
fullscreen bool
hscroll bool
hscrollOff int
@@ -221,6 +221,12 @@ const (
actTop
)
type placeholderFlags struct {
plus bool
preserveSpace bool
query bool
}
func toActions(types ...actionType) []action {
actions := make([]action, len(types))
for idx, t := range types {
@@ -277,6 +283,9 @@ func defaultKeymap() map[int][]action {
keymap[tui.PgUp] = toActions(actPageUp)
keymap[tui.PgDn] = toActions(actPageDown)
keymap[tui.SUp] = toActions(actPreviewUp)
keymap[tui.SDown] = toActions(actPreviewDown)
keymap[tui.Rune] = toActions(actRune)
keymap[tui.Mouse] = toActions(actMouse)
keymap[tui.DoubleClick] = toActions(actAccept)
@@ -293,10 +302,11 @@ func trimQuery(query string) []rune {
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
input := trimQuery(opts.Query)
var header []string
if opts.Reverse {
header = opts.Header
} else {
switch opts.Layout {
case layoutDefault, layoutReverseList:
header = reverseStringArray(opts.Header)
default:
header = opts.Header
}
var delay time.Duration
if opts.Tac {
@@ -354,7 +364,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
t := Terminal{
initDelay: delay,
inlineInfo: opts.InlineInfo,
reverse: opts.Reverse,
layout: opts.Layout,
fullscreen: fullscreen,
hscroll: opts.Hscroll,
hscrollOff: opts.HscrollOff,
@@ -634,8 +644,21 @@ func (t *Terminal) resizeWindows() {
}
func (t *Terminal) move(y int, x int, clear bool) {
if !t.reverse {
y = t.window.Height() - y - 1
h := t.window.Height()
switch t.layout {
case layoutDefault:
y = h - y - 1
case layoutReverseList:
n := 2 + len(t.header)
if t.inlineInfo {
n--
}
if y < n {
y = h - y - 1
} else {
y -= n
}
}
if clear {
@@ -739,7 +762,7 @@ func (t *Terminal) printList() {
count := t.merger.Length() - t.offset
for j := 0; j < maxy; j++ {
i := j
if !t.reverse {
if t.layout == layoutDefault {
i = maxy - 1 - j
}
line := i + 2 + len(t.header)
@@ -1127,16 +1150,46 @@ func quoteEntry(entry string) string {
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
}
func hasPlusFlag(template string) bool {
for _, match := range placeholder.FindAllString(template, -1) {
if match[0] == '\\' {
continue
}
if match[1] == '+' {
return true
func parsePlaceholder(match string) (bool, string, placeholderFlags) {
flags := placeholderFlags{}
if match[0] == '\\' {
// Escaped placeholder pattern
return true, match[1:], flags
}
skipChars := 1
for _, char := range match[1:] {
switch char {
case '+':
flags.plus = true
skipChars++
case 's':
flags.preserveSpace = true
skipChars++
case 'q':
flags.query = true
default:
break
}
}
return false
matchWithoutFlags := "{" + match[skipChars:]
return false, matchWithoutFlags, flags
}
func hasPreviewFlags(template string) (plus bool, query bool) {
for _, match := range placeholder.FindAllString(template, -1) {
_, _, flags := parsePlaceholder(match)
if flags.plus {
plus = true
}
if flags.query {
query = true
}
}
return
}
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, forcePlus bool, query string, allItems []*Item) string {
@@ -1149,9 +1202,10 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
selected = []*Item{}
}
return placeholder.ReplaceAllStringFunc(template, func(match string) string {
// Escaped pattern
if match[0] == '\\' {
return match[1:]
escaped, match, flags := parsePlaceholder(match)
if escaped {
return match
}
// Current query
@@ -1159,13 +1213,8 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
return quoteEntry(query)
}
plusFlag := forcePlus
if match[1] == '+' {
match = "{" + match[2:]
plusFlag = true
}
items := current
if plusFlag {
if flags.plus || forcePlus {
items = selected
}
@@ -1201,7 +1250,9 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
str = str[:delims[len(delims)-1][0]]
}
}
str = strings.TrimSpace(str)
if !flags.preserveSpace {
str = strings.TrimSpace(str)
}
replacements[idx] = quoteEntry(str)
}
return strings.Join(replacements, " ")
@@ -1257,13 +1308,28 @@ func (t *Terminal) currentItem() *Item {
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) {
current := t.currentItem()
if !forcePlus && !hasPlusFlag(template) || len(t.selected) == 0 {
plus, query := hasPreviewFlags(template)
if !(query && len(t.input) > 0 || (forcePlus || plus) && 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
// We would still want to update preview window even if there is no match if
// 1. command template contains {q} and the query string is not empty
// 2. or it contains {+} and we have more than one item already selected.
// To do so, we pass an empty Item instead of nil to trigger an update.
if current == nil {
current = &Item{}
}
var sels []*Item
if len(t.selected) == 0 {
sels = []*Item{current, current}
} else {
sels = make([]*Item, len(t.selected)+1)
sels[0] = current
for i, sel := range t.sortSelected() {
sels[i+1] = sel.item
}
}
return true, sels
}
@@ -1628,12 +1694,12 @@ func (t *Terminal) Loop() {
req(reqList, reqInfo)
}
case actToggleIn:
if t.reverse {
if t.layout != layoutDefault {
return doAction(action{t: actToggleUp}, mapkey)
}
return doAction(action{t: actToggleDown}, mapkey)
case actToggleOut:
if t.reverse {
if t.layout != layoutDefault {
return doAction(action{t: actToggleDown}, mapkey)
}
return doAction(action{t: actToggleUp}, mapkey)
@@ -1761,13 +1827,21 @@ func (t *Terminal) Loop() {
mx -= t.window.Left()
my -= t.window.Top()
mx = util.Constrain(mx-t.promptLen, 0, len(t.input))
if !t.reverse {
my = t.window.Height() - my - 1
}
min := 2 + len(t.header)
if t.inlineInfo {
min--
}
h := t.window.Height()
switch t.layout {
case layoutDefault:
my = h - my - 1
case layoutReverseList:
if my < h-min {
my += min
} else {
my = h - my - 1
}
}
if me.Double {
// Double-click
if my >= min {
@@ -1830,6 +1904,12 @@ func (t *Terminal) Loop() {
t.mutex.Unlock() // Must be unlocked before touching reqBox
if changed {
if t.isPreviewEnabled() {
_, q := hasPreviewFlags(t.preview.command)
if q {
t.version++
}
}
t.eventBox.Set(EvtSearchNew, t.sort)
}
for _, event := range events {
@@ -1854,7 +1934,7 @@ func (t *Terminal) constrain() {
}
func (t *Terminal) vmove(o int, allowCycle bool) {
if t.reverse {
if t.layout != layoutDefault {
o *= -1
}
dest := t.cy + o

View File

@@ -21,6 +21,9 @@ func TestReplacePlaceholder(t *testing.T) {
newItem("foo'bar \x1b[31mbaz\x1b[m"),
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
delim := "'"
var regex *regexp.Regexp
var result string
check := func(expected string) {
if result != expected {
@@ -72,6 +75,31 @@ func TestReplacePlaceholder(t *testing.T) {
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}/'' ''")
// Whitespace preserving flag with "'" delimiter
result = replacePlaceholder("echo {s1}", true, Delimiter{str: &delim}, false, "query", items1)
check("echo ' foo'")
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, false, "query", items1)
check("echo 'bar baz'")
result = replacePlaceholder("echo {s}", true, Delimiter{str: &delim}, false, "query", items1)
check("echo ' foo'\\''bar baz'")
result = replacePlaceholder("echo {s..}", true, Delimiter{str: &delim}, false, "query", items1)
check("echo ' foo'\\''bar baz'")
// Whitespace preserving flag with regex delimiter
regex = regexp.MustCompile("\\w+")
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, false, "query", items1)
check("echo ' '")
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, false, "query", items1)
check("echo ''\\'''")
result = replacePlaceholder("echo {s3}", true, Delimiter{regex: regex}, false, "query", items1)
check("echo ' '")
// No match
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, nil})
check("echo /")
@@ -81,12 +109,11 @@ func TestReplacePlaceholder(t *testing.T) {
check("echo /' foo'\\''bar baz'")
// String delimiter
delim := "'"
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]+")
regex = regexp.MustCompile("[oa]+")
// foo'bar baz
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

@@ -2,6 +2,7 @@ package fzf
import (
"bytes"
"fmt"
"regexp"
"strconv"
"strings"
@@ -23,12 +24,22 @@ type Token struct {
prefixLength int32
}
// String returns the string representation of a Token.
func (t Token) String() string {
return fmt.Sprintf("Token{text: %s, prefixLength: %d}", t.text, t.prefixLength)
}
// Delimiter for tokenizing the input
type Delimiter struct {
regex *regexp.Regexp
str *string
}
// String returns the string representation of a Delimeter.
func (d Delimiter) String() string {
return fmt.Sprintf("Delimiter{regex: %v, str: &%q}", d.regex, *d.str)
}
func newRange(begin int, end int) Range {
if begin == 1 {
begin = rangeEllipsis

View File

@@ -9,35 +9,35 @@ func TestParseRange(t *testing.T) {
i := ".."
r, _ := ParseRange(&i)
if r.begin != rangeEllipsis || r.end != rangeEllipsis {
t.Errorf("%s", r)
t.Errorf("%v", r)
}
}
{
i := "3.."
r, _ := ParseRange(&i)
if r.begin != 3 || r.end != rangeEllipsis {
t.Errorf("%s", r)
t.Errorf("%v", r)
}
}
{
i := "3..5"
r, _ := ParseRange(&i)
if r.begin != 3 || r.end != 5 {
t.Errorf("%s", r)
t.Errorf("%v", r)
}
}
{
i := "-3..-5"
r, _ := ParseRange(&i)
if r.begin != -3 || r.end != -5 {
t.Errorf("%s", r)
t.Errorf("%v", r)
}
}
{
i := "3"
r, _ := ParseRange(&i)
if r.begin != 3 || r.end != 3 {
t.Errorf("%s", r)
t.Errorf("%v", r)
}
}
}

View File

@@ -32,6 +32,12 @@ var offsetRegexp *regexp.Regexp = regexp.MustCompile("\x1b\\[([0-9]+);([0-9]+)R"
func openTtyIn() *os.File {
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
if err != nil {
tty := ttyname()
if len(tty) > 0 {
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
return in
}
}
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
os.Exit(2)
}
@@ -48,11 +54,13 @@ func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) {
runes := []rune{}
for len(bytes) > 0 {
r, sz := utf8.DecodeRune(bytes)
if r == utf8.RuneError || r < 32 &&
r != '\x1b' && (!allowNLCR || r != '\n' && r != '\r') {
runes = append(runes, '?')
} else {
runes = append(runes, r)
nlcr := r == '\n' || r == '\r'
if r >= 32 || r == '\x1b' || nlcr {
if r == utf8.RuneError || nlcr && !allowNLCR {
runes = append(runes, ' ')
} else {
runes = append(runes, r)
}
}
bytes = bytes[sz:]
}
@@ -360,6 +368,11 @@ func (r *LightRenderer) escSequence(sz *int) Event {
if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {
return Event{int(CtrlAltA + r.buffer[1] - 1), 0, nil}
}
alt := false
if len(r.buffer) > 2 && r.buffer[1] == ESC {
r.buffer = r.buffer[1:]
alt = true
}
switch r.buffer[1] {
case 32:
return Event{AltSpace, 0, nil}
@@ -380,12 +393,25 @@ func (r *LightRenderer) escSequence(sz *int) Event {
*sz = 3
switch r.buffer[2] {
case 68:
if alt {
return Event{AltLeft, 0, nil}
}
return Event{Left, 0, nil}
case 67:
if alt {
// Ugh..
return Event{AltRight, 0, nil}
}
return Event{Right, 0, nil}
case 66:
if alt {
return Event{AltDown, 0, nil}
}
return Event{Down, 0, nil}
case 65:
if alt {
return Event{AltUp, 0, nil}
}
return Event{Up, 0, nil}
case 90:
return Event{BTab, 0, nil}
@@ -458,25 +484,22 @@ func (r *LightRenderer) escSequence(sz *int) Event {
}
}
return Event{Invalid, 0, nil}
case 59:
case ';':
if len(r.buffer) != 6 {
return Event{Invalid, 0, nil}
}
*sz = 6
switch r.buffer[4] {
case 50:
case '2', '5':
switch r.buffer[5] {
case 68:
return Event{Home, 0, nil}
case 67:
return Event{End, 0, nil}
}
case 53:
switch r.buffer[5] {
case 68:
return Event{SLeft, 0, nil}
case 67:
case 'A':
return Event{SUp, 0, nil}
case 'B':
return Event{SDown, 0, nil}
case 'C':
return Event{SRight, 0, nil}
case 'D':
return Event{SLeft, 0, nil}
}
} // r.buffer[4]
} // r.buffer[3]
@@ -792,7 +815,7 @@ func (w *LightWindow) Print(text string) {
}
func cleanse(str string) string {
return strings.Replace(str, "\x1b", "?", -1)
return strings.Replace(str, "\x1b", "", -1)
}
func (w *LightWindow) CPrint(pair ColorPair, attr Attr, text string) {

View File

@@ -295,12 +295,24 @@ func (r *FullscreenRenderer) GetChar() Event {
return Event{BSpace, 0, nil}
case tcell.KeyUp:
if alt {
return Event{AltUp, 0, nil}
}
return Event{Up, 0, nil}
case tcell.KeyDown:
if alt {
return Event{AltDown, 0, nil}
}
return Event{Down, 0, nil}
case tcell.KeyLeft:
if alt {
return Event{AltLeft, 0, nil}
}
return Event{Left, 0, nil}
case tcell.KeyRight:
if alt {
return Event{AltRight, 0, nil}
}
return Event{Right, 0, nil}
case tcell.KeyHome:

31
src/tui/ttyname_unix.go Normal file
View File

@@ -0,0 +1,31 @@
// +build !windows
package tui
import (
"io/ioutil"
"syscall"
)
var devPrefixes = [...]string{"/dev/pts/", "/dev/"}
func ttyname() string {
var stderr syscall.Stat_t
if syscall.Fstat(2, &stderr) != nil {
return ""
}
for _, prefix := range devPrefixes {
files, err := ioutil.ReadDir(prefix)
if err != nil {
continue
}
for _, file := range files {
if stat, ok := file.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {
return prefix + file.Name()
}
}
}
return ""
}

View File

@@ -0,0 +1,7 @@
// +build windows
package tui
func ttyname() string {
return ""
}

View File

@@ -61,6 +61,8 @@ const (
Home
End
SUp
SDown
SLeft
SRight
@@ -83,6 +85,11 @@ const (
AltSlash
AltBS
AltUp
AltDown
AltLeft
AltRight
Alt0
)

View File

@@ -1,6 +1,7 @@
package util
import (
"fmt"
"unicode"
"unicode/utf8"
"unsafe"
@@ -94,6 +95,11 @@ func (chars *Chars) Length() int {
return len(chars.slice)
}
// String returns the string representation of a Chars object.
func (chars *Chars) String() string {
return fmt.Sprintf("Chars{slice: []byte(%q), inBytes: %v, trimLengthKnown: %v, trimLength: %d, Index: %d}", chars.slice, chars.inBytes, chars.trimLengthKnown, chars.trimLength, chars.Index)
}
// TrimLength returns the length after trimming leading and trailing whitespaces
func (chars *Chars) TrimLength() uint16 {
if chars.trimLengthKnown {

View File

@@ -17,11 +17,12 @@ func RuneWidth(r rune, prefixWidth int, tabstop int) int {
return tabstop - prefixWidth%tabstop
} else if w, found := _runeWidths[r]; found {
return w
} else {
w := Max(runewidth.RuneWidth(r), 1)
_runeWidths[r] = w
return w
} else if r == '\n' || r == '\r' {
return 1
}
w := runewidth.RuneWidth(r)
_runeWidths[r] = w
return w
}
// Max returns the largest integer

View File

@@ -20,7 +20,7 @@ func ExecCommandWith(_shell string, command string) *exec.Cmd {
cmd := exec.Command("cmd")
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: false,
CmdLine: fmt.Sprintf(` /s /c "%s"`, command),
CmdLine: fmt.Sprintf(` /v:on/s/c "%s"`, command),
CreationFlags: 0,
}
return cmd

98
test/test_go.rb Normal file → Executable file
View File

@@ -1060,6 +1060,21 @@ class TestGoFZF < TestBase
assert_equal '50', readonce.chomp
end
def test_header_lines_reverse_list
tmux.send_keys "seq 100 | #{fzf '--header-lines=10 -q 5 --layout=reverse-list'}", :Enter
2.times do
tmux.until do |lines|
lines[0] == '> 50' &&
lines[-4] == ' 2' &&
lines[-3] == ' 1' &&
lines[-2].include?('/90')
end
tmux.send_keys :Up
end
tmux.send_keys :Enter
assert_equal '50', readonce.chomp
end
def test_header_lines_overflow
tmux.send_keys "seq 100 | #{fzf '--header-lines=200'}", :Enter
tmux.until do |lines|
@@ -1087,7 +1102,8 @@ class TestGoFZF < TestBase
header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines|
lines[-2].include?('100/100') &&
lines[-7..-3].map(&:strip) == header
lines[-7..-3].map(&:strip) == header &&
lines[-8] == '> 1'
end
end
@@ -1096,7 +1112,18 @@ class TestGoFZF < TestBase
header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines|
lines[1].include?('100/100') &&
lines[2..6].map(&:strip) == header
lines[2..6].map(&:strip) == header &&
lines[7] == '> 1'
end
end
def test_header_reverse_list
tmux.send_keys "seq 100 | #{fzf "--header=\\\"\\$(head -5 #{FILE})\\\" --layout=reverse-list"}", :Enter
header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines|
lines[-2].include?('100/100') &&
lines[-7..-3].map(&:strip) == header &&
lines[0] == '> 1'
end
end
@@ -1120,6 +1147,16 @@ class TestGoFZF < TestBase
end
end
def test_header_and_header_lines_reverse_list
tmux.send_keys "seq 100 | #{fzf "--layout=reverse-list --header-lines 10 --header \\\"\\$(head -5 #{FILE})\\\""}", :Enter
header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines|
lines[-2].include?('90/90') &&
lines[-7...-2].map(&:strip) == header &&
lines[-17...-7].map(&:strip) == (1..10).map(&:to_s).reverse
end
end
def test_cancel
tmux.send_keys "seq 10 | #{fzf '--bind 2:cancel'}", :Enter
tmux.until { |lines| lines[-2].include?('10/10') }
@@ -1145,6 +1182,12 @@ class TestGoFZF < TestBase
tmux.send_keys :Enter
end
def test_margin_reverse_list
tmux.send_keys "yes | head -1000 | #{fzf '--margin 5,3 --layout=reverse-list'}", :Enter
tmux.until { |lines| lines[4] == '' && lines[5] == ' > y' }
tmux.send_keys :Enter
end
def test_tabstop
writelines tempname, ["f\too\tba\tr\tbaz\tbarfooq\tux"]
{
@@ -1355,6 +1398,35 @@ class TestGoFZF < TestBase
tmux.until { |_| %w[1 2 3] == File.readlines(tempname).map(&:chomp) }
end
def test_preview_flags
tmux.send_keys %(seq 10 | sed 's/^/:: /; s/$/ /' |
#{FZF} --multi --preview 'echo {{2}/{s2}/{+2}/{+s2}/{q}}'), :Enter
tmux.until { |lines| lines[1].include?('{1/1 /1/1 /}') }
tmux.send_keys '123'
tmux.until { |lines| lines[1].include?('{////123}') }
tmux.send_keys 'C-u', '1'
tmux.until { |lines| lines.match_count == 2 }
tmux.until { |lines| lines[1].include?('{1/1 /1/1 /1}') }
tmux.send_keys :BTab
tmux.until { |lines| lines[1].include?('{10/10 /1/1 /1}') }
tmux.send_keys :BTab
tmux.until { |lines| lines[1].include?('{10/10 /1 10/1 10 /1}') }
tmux.send_keys '2'
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /12}') }
tmux.send_keys '3'
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /123}') }
end
def test_preview_q_no_match
tmux.send_keys %(: | #{FZF} --preview 'echo foo {q}'), :Enter
tmux.until { |lines| lines.match_count == 0 }
tmux.until { |lines| !lines[1].include?('foo') }
tmux.send_keys 'bar'
tmux.until { |lines| lines[1].include?('foo bar') }
tmux.send_keys 'C-u'
tmux.until { |lines| !lines[1].include?('foo') }
end
def test_no_clear
tmux.send_keys "seq 10 | fzf --no-clear --inline-info --height 5 > #{tempname}", :Enter
prompt = '> < 10/10'
@@ -1514,7 +1586,7 @@ module TestShell
lines = retries do
tmux.prepare
tmux.send_keys :Escape, :c
tmux.until { |lines| lines.item_count.positive? }
tmux.until { |lines| lines.match_count.positive? }
end
expected = lines.reverse.select { |l| l.start_with? '>' }.first[2..-1]
tmux.send_keys :Enter
@@ -1551,7 +1623,7 @@ module TestShell
retries do
tmux.prepare
tmux.send_keys 'C-r'
tmux.until { |lines| lines.item_count.positive? }
tmux.until { |lines| lines.match_count.positive? }
end
tmux.send_keys 'C-r'
tmux.send_keys '3d'
@@ -1583,7 +1655,7 @@ module CompletionTest
end
tmux.prepare
tmux.send_keys 'cat /tmp/fzf-test/10**', :Tab
tmux.until { |lines| lines.item_count.positive? }
tmux.until { |lines| lines.match_count.positive? }
tmux.send_keys ' !d'
tmux.until { |lines| lines.match_count == 2 }
tmux.send_keys :Tab, :Tab
@@ -1597,7 +1669,7 @@ module CompletionTest
# ~USERNAME**<TAB>
tmux.send_keys 'C-u'
tmux.send_keys "cat ~#{ENV['USER']}**", :Tab
tmux.until { |lines| lines.item_count.positive? }
tmux.until { |lines| lines.match_count.positive? }
tmux.send_keys "'.fzf-home"
tmux.until { |lines| lines.select { |l| l.include? '.fzf-home' }.count > 1 }
tmux.send_keys :Enter
@@ -1615,7 +1687,7 @@ module CompletionTest
# /tmp/fzf\ test**<TAB>
tmux.send_keys 'C-u'
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab
tmux.until { |lines| lines.item_count.positive? }
tmux.until { |lines| lines.match_count.positive? }
tmux.send_keys 'foobar$'
tmux.until { |lines| lines.match_count == 1 }
tmux.send_keys :Enter
@@ -1635,7 +1707,7 @@ module CompletionTest
def test_file_completion_root
tmux.send_keys 'ls /**', :Tab
tmux.until { |lines| lines.item_count.positive? }
tmux.until { |lines| lines.match_count.positive? }
tmux.send_keys :Enter
end
@@ -1646,7 +1718,7 @@ module CompletionTest
FileUtils.touch '/tmp/fzf-test/d55/xxx'
tmux.prepare
tmux.send_keys 'cd /tmp/fzf-test/**', :Tab
tmux.until { |lines| lines.item_count.positive? }
tmux.until { |lines| lines.match_count.positive? }
tmux.send_keys :Tab, :Tab # Tab does not work here
tmux.send_keys 55
tmux.until { |lines| lines.match_count == 1 }
@@ -1675,7 +1747,7 @@ module CompletionTest
tmux.prepare
tmux.send_keys 'C-L'
tmux.send_keys 'kill ', :Tab
tmux.until { |lines| lines.item_count.positive? }
tmux.until { |lines| lines.match_count.positive? }
tmux.send_keys 'sleep12345'
tmux.until { |lines| lines.any_include? 'sleep 12345' }
tmux.send_keys :Enter
@@ -1694,7 +1766,7 @@ module CompletionTest
tmux.send_keys '_fzf_compgen_path() { echo "\$1"; seq 10; }', :Enter
tmux.prepare
tmux.send_keys 'ls /tmp/**', :Tab
tmux.until { |lines| lines.item_count == 11 }
tmux.until { |lines| lines.match_count == 11 }
tmux.send_keys :Tab, :Tab, :Tab
tmux.until { |lines| lines.select_count == 3 }
tmux.send_keys :Enter
@@ -1767,7 +1839,7 @@ class TestBash < TestBase
tmux.paste '_completion_loader() { complete -o default fake; }'
tmux.paste 'complete -F _fzf_path_completion -o default -o bashdefault fake'
tmux.send_keys 'fake /tmp/foo**', :Tab
tmux.until { |lines| lines.item_count.positive? }
tmux.until { |lines| lines.match_count.positive? }
tmux.send_keys 'C-c'
tmux.prepare
@@ -1776,7 +1848,7 @@ class TestBash < TestBase
tmux.prepare
tmux.send_keys 'fake /tmp/foo**', :Tab
tmux.until { |lines| lines.item_count.positive? }
tmux.until { |lines| lines.match_count.positive? }
end
end

View File

@@ -1,12 +1,45 @@
#!/usr/bin/env bash
confirm() {
while [ 1 ]; do
read -p "$1" -n 1 -r
echo
if [[ "$REPLY" =~ ^[Yy] ]]; then
xdg=0
prefix='~/.fzf'
prefix_expand=~/.fzf
fish_dir=${XDG_CONFIG_HOME:-$HOME/.config}/fish
help() {
cat << EOF
usage: $0 [OPTIONS]
--help Show this message
--xdg Remove files generated under \$XDG_CONFIG_HOME/fzf
EOF
}
for opt in "$@"; do
case $opt in
--help)
help
exit 0
;;
--xdg)
xdg=1
prefix='"${XDG_CONFIG_HOME:-$HOME/.config}"/fzf/fzf'
prefix_expand=${XDG_CONFIG_HOME:-$HOME/.config}/fzf/fzf
;;
*)
echo "unknown option: $opt"
help
exit 1
;;
esac
done
ask() {
while true; do
read -p "$1 ([y]/n) " -r
REPLY=${REPLY:-"y"}
if [[ $REPLY =~ ^[Yy]$ ]]; then
return 0
elif [[ "$REPLY" =~ ^[Nn] ]]; then
elif [[ $REPLY =~ ^[Nn]$ ]]; then
return 1
fi
done
@@ -40,7 +73,7 @@ remove_line() {
content=$(sed 's/^[0-9]*://' <<< "$line")
match=1
echo " - Line #$line_no: $content"
[ "$content" = "$1" ] || confirm " - Remove (y/n) ? "
[ "$content" = "$1" ] || ask " - Remove?"
if [ $? -eq 0 ]; then
awk -v n=$line_no 'NR == n {next} {print}' "$src" > "$src.bak" &&
mv "$src.bak" "$src" || break
@@ -55,25 +88,30 @@ remove_line() {
}
for shell in bash zsh; do
remove ~/.fzf.${shell}
shell_config=${prefix_expand}.${shell}
remove "${shell_config}"
remove_line ~/.${shell}rc \
"[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" \
"source ~/.fzf.${shell}"
"[ -f ${prefix}.${shell} ] && source ${prefix}.${shell}" \
"source ${prefix}.${shell}"
done
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
if [ -f "$bind_file" ]; then
remove_line "$bind_file" "fzf_key_bindings"
fi
if [ -d ~/.config/fish/functions ]; then
remove ~/.config/fish/functions/fzf.fish
remove ~/.config/fish/functions/fzf_key_bindings.fish
if [ -d "${fish_dir}/functions" ]; then
remove "${fish_dir}/functions/fzf.fish"
remove "${fish_dir}/functions/fzf_key_bindings.fish"
if [ "$(ls -A ~/.config/fish/functions)" ]; then
echo "Can't delete non-empty directory: \"~/.config/fish/functions\""
if [ "$(ls -A "${fish_dir}/functions")" ]; then
echo "Can't delete non-empty directory: \"${fish_dir}/functions\""
else
rmdir ~/.config/fish/functions
rmdir "${fish_dir}/functions"
fi
fi
config_dir=$(dirname "$prefix_expand")
if [[ "$xdg" = 1 ]] && [[ "$config_dir" = */fzf ]] && [[ -d "$config_dir" ]]; then
rmdir "$config_dir"
fi