m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-15 23:03:47 -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 ### Before submitting
- Make sure that you have the latest version of fzf - 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 - For more Vim stuff, check out https://github.com/junegunn/fzf.vim
Describe your problem or suggestion from here ... Describe your problem or suggestion from here ...

View File

@@ -1,6 +1,26 @@
CHANGELOG 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 0.17.3
------ ------
- `$LINES` and `$COLUMNS` are exported to preview command so that the command - `$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 ifndef GOOS
UNAME_S := $(shell uname -s) GOOS := $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
ifeq ($(UNAME_S),Darwin)
GOOS := darwin
else ifeq ($(UNAME_S),Linux)
GOOS := linux
else
$(error "$$GOOS is not defined.")
endif
endif endif
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST))) MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
@@ -135,4 +128,12 @@ target/$(BINARYARM8): $(SOURCES) vendor
bin/fzf: target/$(BINARY) | bin bin/fzf: target/$(BINARY) | bin
cp -f target/$(BINARY) bin/fzf 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. 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 Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
* [Using git](#using-git) * [Using git](#using-git)
* [As Vim plugin](#as-vim-plugin) * [As Vim plugin](#as-vim-plugin)
* [Arch Linux](#arch-linux)
* [Fedora](#fedora) * [Fedora](#fedora)
* [Windows](#windows) * [Windows](#windows)
* [Upgrading fzf](#upgrading-fzf) * [Upgrading fzf](#upgrading-fzf)
@@ -99,7 +100,7 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
### As Vim plugin ### As Vim plugin
Once you have fzf installed, you can enable it inside Vim simply by adding the 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 ```vim
" If installed using Homebrew " 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. " and you don't have to run install script if you use fzf only in Vim.
``` ```
### Arch Linux
```sh
sudo pacman -S fzf
```
### Fedora ### Fedora
fzf is available in Fedora 26 and above, and can be installed using the usual 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 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 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 ### Windows
@@ -217,8 +223,8 @@ cursor with `--height` option.
vim $(fzf --height 40%) vim $(fzf --height 40%)
``` ```
Also check out `--reverse` option if you prefer "top-down" layout instead of Also check out `--reverse` and `--layout` options if you prefer
the default "bottom-up" layout. "top-down" layout instead of the default "bottom-up" layout.
```sh ```sh
vim $(fzf --height 40% --reverse) 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, default. For example,
```sh ```sh
export FZF_DEFAULT_OPTS='--height 40% --reverse --border' export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'
``` ```
#### Search syntax #### 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 type in multiple search terms delimited by spaces. e.g. `^music .mp3$ sbtrkt
!fire` !fire`
| Token | Match type | Description | | Token | Match type | Description |
| -------- | -------------------------- | --------------------------------- | | --------- | -------------------------- | ------------------------------------ |
| `sbtrkt` | fuzzy-match | Items that match `sbtrkt` | | `sbtrkt` | fuzzy-match | Items that match `sbtrkt` |
| `^music` | prefix-exact-match | Items that start with `music` | | `'wild` | exact-match (quoted) | Items that include `wild` |
| `.mp3$` | suffix-exact-match | Items that end with `.mp3` | | `^music` | prefix-exact-match | Items that start with `music` |
| `'wild` | exact-match (quoted) | Items that include `wild` | | `.mp3$` | suffix-exact-match | Items that end with `.mp3` |
| `!fire` | inverse-exact-match | Items that do not include `fire` | | `!fire` | inverse-exact-match | Items that do not include `fire` |
| `!.mp3$` | inverse-suffix-exact-match | Items that do not end with `.mp3` | | `!^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, 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, 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'` - e.g. `export FZF_DEFAULT_COMMAND='fd --type f'`
- `FZF_DEFAULT_OPTS` - `FZF_DEFAULT_OPTS`
- Default options - Default options
- e.g. `export FZF_DEFAULT_OPTS="--reverse --inline-info"` - e.g. `export FZF_DEFAULT_OPTS="--layout=reverse --inline-info"`
#### Options #### Options

View File

@@ -136,6 +136,11 @@ fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
cleanup() { cleanup() {
\rm -f $argsf $fifo1 $fifo2 $fifo3 \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 # Remove temp window if we were zoomed
if [[ -n "$zoomed" ]]; then if [[ -n "$zoomed" ]]; then
tmux display-message -p "#{window_id}" > /dev/null tmux display-message -p "#{window_id}" > /dev/null
@@ -174,6 +179,8 @@ pppid=$$
echo -n "trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM;" > $argsf echo -n "trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM;" > $argsf
close="; trap - EXIT SIGINT SIGTERM $close" 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 if [[ -n "$term" ]] || [[ -t 0 ]]; then
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\ 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 set -u
version=0.17.3 version=0.17.4
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2
binary_arch= binary_arch=
allow_legacy= allow_legacy=
shells="bash zsh fish" shells="bash zsh fish"
prefix='~/.fzf'
prefix_expand=~/.fzf
fish_dir=${XDG_CONFIG_HOME:-$HOME/.config}/fish
help() { help() {
cat << EOF cat << EOF
@@ -18,6 +21,7 @@ usage: $0 [OPTIONS]
--bin Download fzf binary only; Do not generate ~/.fzf.{bash,zsh} --bin Download fzf binary only; Do not generate ~/.fzf.{bash,zsh}
--all Download fzf binary and update configuration files --all Download fzf binary and update configuration files
to enable key bindings and fuzzy completion 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-]key-bindings Enable/disable key bindings (CTRL-T, CTRL-R, ALT-C)
--[no-]completion Enable/disable fuzzy completion (bash & zsh) --[no-]completion Enable/disable fuzzy completion (bash & zsh)
--[no-]update-rc Whether or not to update shell configuration files --[no-]update-rc Whether or not to update shell configuration files
@@ -43,6 +47,11 @@ for opt in "$@"; do
update_config=1 update_config=1
allow_legacy=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 ;; --key-bindings) key_bindings=1 ;;
--no-key-bindings) key_bindings=0 ;; --no-key-bindings) key_bindings=0 ;;
--completion) auto_completion=1 ;; --completion) auto_completion=1 ;;
@@ -69,6 +78,7 @@ fzf_base="$(pwd)"
ask() { ask() {
while true; do while true; do
read -p "$1 ([y]/n) " -r read -p "$1 ([y]/n) " -r
REPLY=${REPLY:-"y"}
if [[ $REPLY =~ ^[Yy]$ ]]; then if [[ $REPLY =~ ^[Yy]$ ]]; then
return 1 return 1
elif [[ $REPLY =~ ^[Nn]$ ]]; then elif [[ $REPLY =~ ^[Nn]$ ]]; then
@@ -239,8 +249,8 @@ fi
echo echo
for shell in $shells; do for shell in $shells; do
[[ "$shell" = fish ]] && continue [[ "$shell" = fish ]] && continue
echo -n "Generate ~/.fzf.$shell ... " src=${prefix_expand}.${shell}
src=~/.fzf.${shell} echo -n "Generate $src ... "
fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null" fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null"
if [ $auto_completion -eq 0 ]; then if [ $auto_completion -eq 0 ]; then
@@ -252,7 +262,7 @@ for shell in $shells; do
fzf_key_bindings="# $fzf_key_bindings" fzf_key_bindings="# $fzf_key_bindings"
fi fi
cat > $src << EOF cat > "$src" << EOF
# Setup fzf # Setup fzf
# --------- # ---------
if [[ ! "\$PATH" == *$fzf_base/bin* ]]; then if [[ ! "\$PATH" == *$fzf_base/bin* ]]; then
@@ -280,13 +290,13 @@ if [[ "$shells" =~ fish ]]; then
EOF EOF
[ $? -eq 0 ] && echo "OK" || echo "Failed" [ $? -eq 0 ] && echo "OK" || echo "Failed"
mkdir -p ~/.config/fish/functions mkdir -p "${fish_dir}/functions"
if [ -e ~/.config/fish/functions/fzf.fish ]; then if [ -e "${fish_dir}/functions/fzf.fish" ]; then
echo -n "Remove unnecessary ~/.config/fish/functions/fzf.fish ... " echo -n "Remove unnecessary ${fish_dir}/functions/fzf.fish ... "
rm -f ~/.config/fish/functions/fzf.fish && echo "OK" || echo "Failed" rm -f "${fish_dir}/functions/fzf.fish" && echo "OK" || echo "Failed"
fi 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 if [ $key_bindings -ne 0 ]; then
echo -n "Symlink $fish_binding ... " echo -n "Symlink $fish_binding ... "
ln -sf "$fzf_base/shell/key-bindings.fish" \ ln -sf "$fzf_base/shell/key-bindings.fish" \
@@ -352,11 +362,11 @@ echo
for shell in $shells; do for shell in $shells; do
[[ "$shell" = fish ]] && continue [[ "$shell" = fish ]] && continue
[ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc [ $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 done
if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then 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 if [ ! -e "$bind_file" ]; then
create_file "$bind_file" \ create_file "$bind_file" \
'function fish_user_key_bindings' \ '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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. 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 .SH NAME
fzf-tmux - open fzf in tmux split pane 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. 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 .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -155,9 +155,22 @@ the full screen.
.BI "--min-height=" "HEIGHT" .BI "--min-height=" "HEIGHT"
Minimum height when \fB--height\fR is given in percent (default: 10). Minimum height when \fB--height\fR is given in percent (default: 10).
Ignored when \fB--height\fR is not specified. 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 .TP
.B "--reverse" .B "--reverse"
Reverse orientation A synonym for \fB--layout=reverse\fB
.TP .TP
.B "--border" .B "--border"
Draw border above and below the finder Draw border above and below the finder
@@ -195,7 +208,7 @@ Input prompt (default: '> ')
.TP .TP
.BI "--header=" "STR" .BI "--header=" "STR"
The given string will be printed as the sticky header. The lines are displayed 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 are not affected by \fB--with-nth\fR. ANSI color codes are processed even when
\fB--ansi\fR is not set. \fB--ansi\fR is not set.
.TP .TP
@@ -285,9 +298,15 @@ was made) individually quoted.
e.g. \fBfzf --multi --preview='head -10 {+}'\fR e.g. \fBfzf --multi --preview='head -10 {+}'\fR
\fBgit log --oneline | fzf --multi --preview 'git show {+1}'\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. Also, \fB{q}\fR is replaced to the current query string.
Note that you can escape a placeholder pattern by prepending a backslash. 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 .RE
.TP .TP
.BI "--preview-window=" "[POSITION][:SIZE[%]][:wrap][:hidden]" .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) \fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
\fIspace\fR \fIspace\fR
\fIbspace\fR (\fIbs\fR) \fIbspace\fR (\fIbs\fR)
\fIalt-up\fR
\fIalt-down\fR
\fIalt-left\fR
\fIalt-right\fR
\fIalt-enter\fR \fIalt-enter\fR
\fIalt-space\fR \fIalt-space\fR
\fIalt-bspace\fR (\fIalt-bs\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 \fIend\fR
\fIpgup\fR (\fIpage-up\fR) \fIpgup\fR (\fIpage-up\fR)
\fIpgdn\fR (\fIpage-down\fR) \fIpgdn\fR (\fIpage-down\fR)
\fIshift-up\fR
\fIshift-down\fR
\fIshift-left\fR \fIshift-left\fR
\fIshift-right\fR \fIshift-right\fR
\fIleft-click\fR \fIleft-click\fR
@@ -520,8 +545,8 @@ triggered whenever the query string is changed.
\fBpage-up\fR \fIpgup\fR \fBpage-up\fR \fIpgup\fR
\fBhalf-page-down\fR \fBhalf-page-down\fR
\fBhalf-page-up\fR \fBhalf-page-up\fR
\fBpreview-down\fR \fBpreview-down\fR \fIshift-down\fR
\fBpreview-up\fR \fBpreview-up\fR \fIshift-up\fR
\fBpreview-page-down\fR \fBpreview-page-down\fR
\fBpreview-page-up\fR \fBpreview-page-up\fR
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\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\fR (\fIright-click\fR)
\fBtoggle-all\fR \fBtoggle-all\fR
\fBtoggle+down\fR \fIctrl-i (tab)\fR \fBtoggle+down\fR \fIctrl-i (tab)\fR
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR) \fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
\fBtoggle-out\fR (\fB--reverse\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR) \fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
\fBtoggle-preview\fR \fBtoggle-preview\fR
\fBtoggle-preview-wrap\fR \fBtoggle-preview-wrap\fR
\fBtoggle-sort\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 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 output, you might want to use \fBexecute-silent\fR instead, which silently
executes the command without the switching. Note that fzf will not be 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). your command as a background process (i.e. appending \fB&\fR).
.SH AUTHOR .SH AUTHOR

View File

@@ -50,9 +50,9 @@ if s:is_win
" Use utf-8 for fzf.vim commands " Use utf-8 for fzf.vim commands
" Return array of shell commands for cmd.exe " Return array of shell commands for cmd.exe
function! s:wrap_cmds(cmds) 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]) + \ (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) +
\ ['chcp %origchcp% > nul'] \ ['chcp %origchcp% > nul'], 'v:val."\r"')
endfunction endfunction
else else
let s:term_marker = ";#FZF" let s:term_marker = ";#FZF"
@@ -231,6 +231,7 @@ function! s:common_sink(action, lines) abort
doautocmd BufEnter doautocmd BufEnter
endif endif
endfor endfor
catch /^Vim:Interrupt$/
finally finally
let &autochdir = autochdir let &autochdir = autochdir
silent! autocmd! fzf_swap silent! autocmd! fzf_swap
@@ -586,7 +587,7 @@ function! s:calc_size(max, val, dict)
let srcsz = len(a:dict.source) let srcsz = len(a:dict.source)
endif 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, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2
let margin += stridx(opts, '--header') > stridx(opts, '--no-header') let margin += stridx(opts, '--header') > stridx(opts, '--no-header')
return srcsz >= 0 ? min([srcsz + margin, size]) : size return srcsz >= 0 ? min([srcsz + margin, size]) : size
@@ -699,9 +700,9 @@ function! s:execute_term(dict, command, temps) abort
if has('nvim') if has('nvim')
call termopen(command, fzf) call termopen(command, fzf)
else 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 if !has('patch-8.0.1261') && !has('nvim') && !s:is_win
call term_wait(t, 20) call term_wait(fzf.buf, 20)
endif endif
endif endif
finally 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" echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
} }
_fzf_orig_completion_filter() { __fzf_orig_completion_filter() {
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2";/' | 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= '{gsub(/[^A-Za-z0-9_= ;]/, "_", $1); print $1"="$2}' awk -F= '{OFS = FS} {gsub(/[^A-Za-z0-9_= ;]/, "_", $1);}1'
} }
_fzf_opts_completion() { _fzf_opts_completion() {
@@ -122,13 +121,17 @@ _fzf_handle_dynamic_completion() {
if [ -n "$orig" ] && type "$orig" > /dev/null 2>&1; then if [ -n "$orig" ] && type "$orig" > /dev/null 2>&1; then
$orig "$@" $orig "$@"
elif [ -n "$_fzf_completion_loader" ]; then elif [ -n "$_fzf_completion_loader" ]; then
orig_complete=$(complete -p "$cmd") orig_complete=$(complete -p "$cmd" 2> /dev/null)
_completion_loader "$@" _completion_loader "$@"
ret=$? ret=$?
# _completion_loader may not have updated completion for the command # _completion_loader may not have updated completion for the command
if [ "$(complete -p "$cmd")" != "$orig_complete" ]; then if [ "$(complete -p "$cmd" 2> /dev/null)" != "$orig_complete" ]; then
eval "$(complete | command grep "\-F.* $orig_cmd$" | _fzf_orig_completion_filter)" eval "$(complete | command grep " -F.* $orig_cmd$" | __fzf_orig_completion_filter)"
eval "$orig_complete" if [[ "$__fzf_nospace_commands" = *" $orig_cmd "* ]]; then
eval "${orig_complete/ -F / -o nospace -F }"
else
eval "$orig_complete"
fi
fi fi
return $ret return $ret
fi fi
@@ -145,7 +148,7 @@ __fzf_generic_path_completion() {
base=${cur:0:${#cur}-${#trigger}} base=${cur:0:${#cur}-${#trigger}}
eval "base=$base" eval "base=$base"
dir="$base" [[ $base = *"/"* ]] && dir="$base"
while true; do while true; do
if [ -z "$dir" ] || [ -d "$dir" ]; then if [ -z "$dir" ] || [ -d "$dir" ]; then
leftover=${base/#"$dir"} leftover=${base/#"$dir"}
@@ -156,6 +159,7 @@ __fzf_generic_path_completion() {
printf "%q$3 " "$item" printf "%q$3 " "$item"
done) done)
matches=${matches% } matches=${matches% }
[[ -z "$3" ]] && [[ "$__fzf_nospace_commands" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
COMPREPLY=( "$matches" ) COMPREPLY=( "$matches" )
else else
@@ -278,9 +282,9 @@ a_cmds="
x_cmds="kill ssh telnet unset unalias export" x_cmds="kill ssh telnet unset unalias export"
# Preserve existing completion # 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' | 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 if type _completion_loader > /dev/null 2>&1; then
_fzf_completion_loader=1 _fzf_completion_loader=1

View File

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

View File

@@ -56,7 +56,7 @@ __fzf_history__() (
shopt -u nocaseglob nocasematch shopt -u nocaseglob nocasematch
line=$( line=$(
HISTTIMEFORMAT= history | 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]') && command grep '^ *[0-9]') &&
if [[ $- =~ H ]]; then if [[ $- =~ H ]]; then
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line" sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
@@ -80,7 +80,7 @@ if [[ ! -o vi ]]; then
fi fi
# CTRL-R - Paste the selected command from history into the command line # 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 # ALT-C - cd into the selected directory
bind '"\ec": " \C-e\C-u`__fzf_cd__`\e\C-e\er\C-m"' 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"' bind -m vi-command '"\C-t": "i\C-t"'
# CTRL-R - Paste the selected command from history into the command line # 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"' bind -m vi-command '"\C-r": "i\C-r"'
# ALT-C - cd into the selected directory # 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) output, ansiOffsets, newState := extractColor(src, state, nil)
state = newState state = newState
if output != "hello world" { 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) fmt.Println(src, ansiOffsets, clean)
assertion(ansiOffsets, state) assertion(ansiOffsets, state)

View File

@@ -9,7 +9,7 @@ import (
const ( const (
// Current version // Current version
version = "0.17.3" version = "0.17.4"
// Core // Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond coordinatorDelayMax time.Duration = 100 * time.Millisecond
@@ -59,7 +59,7 @@ func init() {
} else if os.Getenv("TERM") == "cygwin" { } 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-"` defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
} else { } 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 height instead of using fullscreen
--min-height=HEIGHT Minimum height when --height is given in percent --min-height=HEIGHT Minimum height when --height is given in percent
(default: 10) (default: 10)
--reverse Reverse orientation --layout=LAYOUT Choose layout: [default|reverse|reverse-list]
--border Draw border above and below the finder --border Draw border above and below the finder
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L) --margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
--inline-info Display finder info inline with the query --inline-info Display finder info inline with the query
@@ -90,7 +90,8 @@ const usage = `usage: fzf [options]
Environment variables Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty 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 posRight
) )
type layoutType int
const (
layoutDefault layoutType = iota
layoutReverse
layoutReverseList
)
type previewOpts struct { type previewOpts struct {
command string command string
position windowPosition position windowPosition
@@ -161,7 +170,7 @@ type Options struct {
Bold bool Bold bool
Height sizeSpec Height sizeSpec
MinHeight int MinHeight int
Reverse bool Layout layoutType
Cycle bool Cycle bool
Hscroll bool Hscroll bool
HscrollOff int HscrollOff int
@@ -211,7 +220,7 @@ func defaultOptions() *Options {
Black: false, Black: false,
Bold: true, Bold: true,
MinHeight: 10, MinHeight: 10,
Reverse: false, Layout: layoutDefault,
Cycle: false, Cycle: false,
Hscroll: true, Hscroll: true,
HscrollOff: 10, HscrollOff: 10,
@@ -410,6 +419,14 @@ func parseKeyChords(str string, message string) map[int]string {
chord = tui.AltSlash chord = tui.AltSlash
case "alt-bs", "alt-bspace": case "alt-bs", "alt-bspace":
chord = tui.AltBS 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": case "tab":
chord = tui.Tab chord = tui.Tab
case "btab", "shift-tab": case "btab", "shift-tab":
@@ -426,6 +443,10 @@ func parseKeyChords(str string, message string) map[int]string {
chord = tui.PgUp chord = tui.PgUp
case "pgdn", "page-down": case "pgdn", "page-down":
chord = tui.PgDn chord = tui.PgDn
case "shift-up":
chord = tui.SUp
case "shift-down":
chord = tui.SDown
case "shift-left": case "shift-left":
chord = tui.SLeft chord = tui.SLeft
case "shift-right": case "shift-right":
@@ -845,6 +866,20 @@ func parseHeight(str string) sizeSpec {
return size 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) { func parsePreviewWindow(opts *previewOpts, input string) {
// Default // Default
opts.position = posRight opts.position = posRight
@@ -1025,10 +1060,13 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Bold = true opts.Bold = true
case "--no-bold": case "--no-bold":
opts.Bold = false opts.Bold = false
case "--layout":
opts.Layout = parseLayout(
nextString(allArgs, &i, "layout required (default / reverse / reverse-list)"))
case "--reverse": case "--reverse":
opts.Reverse = true opts.Layout = layoutReverse
case "--no-reverse": case "--no-reverse":
opts.Reverse = false opts.Layout = layoutDefault
case "--cycle": case "--cycle":
opts.Cycle = true opts.Cycle = true
case "--no-cycle": case "--no-cycle":
@@ -1144,6 +1182,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Height = parseHeight(value) opts.Height = parseHeight(value)
} else if match, value := optString(arg, "--min-height="); match { } else if match, value := optString(arg, "--min-height="); match {
opts.MinHeight = atoi(value) 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 { } else if match, value := optString(arg, "--toggle-sort="); match {
parseToggleSort(opts.Keymap, value) parseToggleSort(opts.Keymap, value)
} else if match, value := optString(arg, "--expect="); match { } else if match, value := optString(arg, "--expect="); match {

View File

@@ -50,7 +50,7 @@ func TestDelimiterRegexString(t *testing.T) {
tokens[2].text.ToString() != "---*" || tokens[2].text.ToString() != "---*" ||
tokens[3].text.ToString() != "*" || tokens[3].text.ToString() != "*" ||
tokens[4].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 || if len(ranges) != 1 ||
ranges[0].begin != rangeEllipsis || ranges[0].begin != rangeEllipsis ||
ranges[0].end != 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[7].begin != -2 || ranges[7].end != -2 ||
ranges[8].begin != 2 || ranges[8].end != -2 || ranges[8].begin != 2 || ranges[8].end != -2 ||
ranges[9].begin != rangeEllipsis || ranges[9].end != rangeEllipsis { 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) parseOptions(opts, words)
postProcessOptions(opts) postProcessOptions(opts)
if len(opts.Nth) != 0 { 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"}} { 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) parseOptions(opts, words)
postProcessOptions(opts) postProcessOptions(opts)
if len(opts.Nth) != 0 { 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) parseOptions(opts, words)
postProcessOptions(opts) postProcessOptions(opts)
if len(opts.Nth) != 2 { 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 package fzf
import ( import (
"fmt"
"regexp" "regexp"
"strings" "strings"
@@ -34,6 +35,11 @@ type term struct {
caseSensitive bool 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 type termSet []term
// Pattern represents search pattern // 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][1].typ != termExact || terms[8][1].inv ||
terms[8][2].typ != termSuffix || terms[8][2].inv || terms[8][2].typ != termSuffix || terms[8][2].inv ||
terms[8][3].typ != termExact || !terms[8][3].inv { terms[8][3].typ != termExact || !terms[8][3].inv {
t.Errorf("%s", terms) t.Errorf("%v", terms)
} }
for _, termSet := range terms[:8] { for _, termSet := range terms[:8] {
term := termSet[0] term := termSet[0]
if len(term.text) != 3 { 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[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[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 { 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) { func TestParseTermsEmpty(t *testing.T) {
terms := parseTerms(true, CaseSmart, false, "' ^ !' !^") terms := parseTerms(true, CaseSmart, false, "' ^ !' !^")
if len(terms) != 0 { 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( res, pos := algo.ExactMatchNaive(
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil) pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
if res.Start != 7 || res.End != 10 { if res.Start != 7 || res.End != 10 {
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End) t.Errorf("%v / %d / %d", pattern.termSets, res.Start, res.End)
} }
if pos != nil { if pos != nil {
t.Errorf("pos is expected to be nil") t.Errorf("pos is expected to be nil")
@@ -90,7 +90,7 @@ func TestEqual(t *testing.T) {
res, pos := algo.EqualMatch( res, pos := algo.EqualMatch(
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil) pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
if res.Start != sidxExpected || res.End != eidxExpected { if res.Start != sidxExpected || res.End != eidxExpected {
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End) t.Errorf("%v / %d / %d", pattern.termSets, res.Start, res.End)
} }
if pos != nil { if pos != nil {
t.Errorf("pos is expected to be nil") t.Errorf("pos is expected to be nil")

View File

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

View File

@@ -2,6 +2,7 @@ package fzf
import ( import (
"bytes" "bytes"
"fmt"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@@ -23,12 +24,22 @@ type Token struct {
prefixLength int32 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 // Delimiter for tokenizing the input
type Delimiter struct { type Delimiter struct {
regex *regexp.Regexp regex *regexp.Regexp
str *string 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 { func newRange(begin int, end int) Range {
if begin == 1 { if begin == 1 {
begin = rangeEllipsis begin = rangeEllipsis

View File

@@ -9,35 +9,35 @@ func TestParseRange(t *testing.T) {
i := ".." i := ".."
r, _ := ParseRange(&i) r, _ := ParseRange(&i)
if r.begin != rangeEllipsis || r.end != rangeEllipsis { if r.begin != rangeEllipsis || r.end != rangeEllipsis {
t.Errorf("%s", r) t.Errorf("%v", r)
} }
} }
{ {
i := "3.." i := "3.."
r, _ := ParseRange(&i) r, _ := ParseRange(&i)
if r.begin != 3 || r.end != rangeEllipsis { if r.begin != 3 || r.end != rangeEllipsis {
t.Errorf("%s", r) t.Errorf("%v", r)
} }
} }
{ {
i := "3..5" i := "3..5"
r, _ := ParseRange(&i) r, _ := ParseRange(&i)
if r.begin != 3 || r.end != 5 { if r.begin != 3 || r.end != 5 {
t.Errorf("%s", r) t.Errorf("%v", r)
} }
} }
{ {
i := "-3..-5" i := "-3..-5"
r, _ := ParseRange(&i) r, _ := ParseRange(&i)
if r.begin != -3 || r.end != -5 { if r.begin != -3 || r.end != -5 {
t.Errorf("%s", r) t.Errorf("%v", r)
} }
} }
{ {
i := "3" i := "3"
r, _ := ParseRange(&i) r, _ := ParseRange(&i)
if r.begin != 3 || r.end != 3 { 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 { func openTtyIn() *os.File {
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0) in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
if err != nil { 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) fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
os.Exit(2) os.Exit(2)
} }
@@ -48,11 +54,13 @@ func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) {
runes := []rune{} runes := []rune{}
for len(bytes) > 0 { for len(bytes) > 0 {
r, sz := utf8.DecodeRune(bytes) r, sz := utf8.DecodeRune(bytes)
if r == utf8.RuneError || r < 32 && nlcr := r == '\n' || r == '\r'
r != '\x1b' && (!allowNLCR || r != '\n' && r != '\r') { if r >= 32 || r == '\x1b' || nlcr {
runes = append(runes, '?') if r == utf8.RuneError || nlcr && !allowNLCR {
} else { runes = append(runes, ' ')
runes = append(runes, r) } else {
runes = append(runes, r)
}
} }
bytes = bytes[sz:] 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 { if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {
return Event{int(CtrlAltA + r.buffer[1] - 1), 0, nil} 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] { switch r.buffer[1] {
case 32: case 32:
return Event{AltSpace, 0, nil} return Event{AltSpace, 0, nil}
@@ -380,12 +393,25 @@ func (r *LightRenderer) escSequence(sz *int) Event {
*sz = 3 *sz = 3
switch r.buffer[2] { switch r.buffer[2] {
case 68: case 68:
if alt {
return Event{AltLeft, 0, nil}
}
return Event{Left, 0, nil} return Event{Left, 0, nil}
case 67: case 67:
if alt {
// Ugh..
return Event{AltRight, 0, nil}
}
return Event{Right, 0, nil} return Event{Right, 0, nil}
case 66: case 66:
if alt {
return Event{AltDown, 0, nil}
}
return Event{Down, 0, nil} return Event{Down, 0, nil}
case 65: case 65:
if alt {
return Event{AltUp, 0, nil}
}
return Event{Up, 0, nil} return Event{Up, 0, nil}
case 90: case 90:
return Event{BTab, 0, nil} return Event{BTab, 0, nil}
@@ -458,25 +484,22 @@ func (r *LightRenderer) escSequence(sz *int) Event {
} }
} }
return Event{Invalid, 0, nil} return Event{Invalid, 0, nil}
case 59: case ';':
if len(r.buffer) != 6 { if len(r.buffer) != 6 {
return Event{Invalid, 0, nil} return Event{Invalid, 0, nil}
} }
*sz = 6 *sz = 6
switch r.buffer[4] { switch r.buffer[4] {
case 50: case '2', '5':
switch r.buffer[5] { switch r.buffer[5] {
case 68: case 'A':
return Event{Home, 0, nil} return Event{SUp, 0, nil}
case 67: case 'B':
return Event{End, 0, nil} return Event{SDown, 0, nil}
} case 'C':
case 53:
switch r.buffer[5] {
case 68:
return Event{SLeft, 0, nil}
case 67:
return Event{SRight, 0, nil} return Event{SRight, 0, nil}
case 'D':
return Event{SLeft, 0, nil}
} }
} // r.buffer[4] } // r.buffer[4]
} // r.buffer[3] } // r.buffer[3]
@@ -792,7 +815,7 @@ func (w *LightWindow) Print(text string) {
} }
func cleanse(str string) 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) { 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} return Event{BSpace, 0, nil}
case tcell.KeyUp: case tcell.KeyUp:
if alt {
return Event{AltUp, 0, nil}
}
return Event{Up, 0, nil} return Event{Up, 0, nil}
case tcell.KeyDown: case tcell.KeyDown:
if alt {
return Event{AltDown, 0, nil}
}
return Event{Down, 0, nil} return Event{Down, 0, nil}
case tcell.KeyLeft: case tcell.KeyLeft:
if alt {
return Event{AltLeft, 0, nil}
}
return Event{Left, 0, nil} return Event{Left, 0, nil}
case tcell.KeyRight: case tcell.KeyRight:
if alt {
return Event{AltRight, 0, nil}
}
return Event{Right, 0, nil} return Event{Right, 0, nil}
case tcell.KeyHome: 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 Home
End End
SUp
SDown
SLeft SLeft
SRight SRight
@@ -83,6 +85,11 @@ const (
AltSlash AltSlash
AltBS AltBS
AltUp
AltDown
AltLeft
AltRight
Alt0 Alt0
) )

View File

@@ -1,6 +1,7 @@
package util package util
import ( import (
"fmt"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
"unsafe" "unsafe"
@@ -94,6 +95,11 @@ func (chars *Chars) Length() int {
return len(chars.slice) 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 // TrimLength returns the length after trimming leading and trailing whitespaces
func (chars *Chars) TrimLength() uint16 { func (chars *Chars) TrimLength() uint16 {
if chars.trimLengthKnown { if chars.trimLengthKnown {

View File

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

View File

@@ -20,7 +20,7 @@ func ExecCommandWith(_shell string, command string) *exec.Cmd {
cmd := exec.Command("cmd") cmd := exec.Command("cmd")
cmd.SysProcAttr = &syscall.SysProcAttr{ cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: false, HideWindow: false,
CmdLine: fmt.Sprintf(` /s /c "%s"`, command), CmdLine: fmt.Sprintf(` /v:on/s/c "%s"`, command),
CreationFlags: 0, CreationFlags: 0,
} }
return cmd 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 assert_equal '50', readonce.chomp
end 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 def test_header_lines_overflow
tmux.send_keys "seq 100 | #{fzf '--header-lines=200'}", :Enter tmux.send_keys "seq 100 | #{fzf '--header-lines=200'}", :Enter
tmux.until do |lines| tmux.until do |lines|
@@ -1087,7 +1102,8 @@ class TestGoFZF < TestBase
header = File.readlines(FILE).take(5).map(&:strip) header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines| tmux.until do |lines|
lines[-2].include?('100/100') && lines[-2].include?('100/100') &&
lines[-7..-3].map(&:strip) == header lines[-7..-3].map(&:strip) == header &&
lines[-8] == '> 1'
end end
end end
@@ -1096,7 +1112,18 @@ class TestGoFZF < TestBase
header = File.readlines(FILE).take(5).map(&:strip) header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines| tmux.until do |lines|
lines[1].include?('100/100') && 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
end end
@@ -1120,6 +1147,16 @@ class TestGoFZF < TestBase
end end
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 def test_cancel
tmux.send_keys "seq 10 | #{fzf '--bind 2:cancel'}", :Enter tmux.send_keys "seq 10 | #{fzf '--bind 2:cancel'}", :Enter
tmux.until { |lines| lines[-2].include?('10/10') } tmux.until { |lines| lines[-2].include?('10/10') }
@@ -1145,6 +1182,12 @@ class TestGoFZF < TestBase
tmux.send_keys :Enter tmux.send_keys :Enter
end 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 def test_tabstop
writelines tempname, ["f\too\tba\tr\tbaz\tbarfooq\tux"] 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) } tmux.until { |_| %w[1 2 3] == File.readlines(tempname).map(&:chomp) }
end 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 def test_no_clear
tmux.send_keys "seq 10 | fzf --no-clear --inline-info --height 5 > #{tempname}", :Enter tmux.send_keys "seq 10 | fzf --no-clear --inline-info --height 5 > #{tempname}", :Enter
prompt = '> < 10/10' prompt = '> < 10/10'
@@ -1514,7 +1586,7 @@ module TestShell
lines = retries do lines = retries do
tmux.prepare tmux.prepare
tmux.send_keys :Escape, :c tmux.send_keys :Escape, :c
tmux.until { |lines| lines.item_count.positive? } tmux.until { |lines| lines.match_count.positive? }
end end
expected = lines.reverse.select { |l| l.start_with? '>' }.first[2..-1] expected = lines.reverse.select { |l| l.start_with? '>' }.first[2..-1]
tmux.send_keys :Enter tmux.send_keys :Enter
@@ -1551,7 +1623,7 @@ module TestShell
retries do retries do
tmux.prepare tmux.prepare
tmux.send_keys 'C-r' tmux.send_keys 'C-r'
tmux.until { |lines| lines.item_count.positive? } tmux.until { |lines| lines.match_count.positive? }
end end
tmux.send_keys 'C-r' tmux.send_keys 'C-r'
tmux.send_keys '3d' tmux.send_keys '3d'
@@ -1583,7 +1655,7 @@ module CompletionTest
end end
tmux.prepare tmux.prepare
tmux.send_keys 'cat /tmp/fzf-test/10**', :Tab 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.send_keys ' !d'
tmux.until { |lines| lines.match_count == 2 } tmux.until { |lines| lines.match_count == 2 }
tmux.send_keys :Tab, :Tab tmux.send_keys :Tab, :Tab
@@ -1597,7 +1669,7 @@ module CompletionTest
# ~USERNAME**<TAB> # ~USERNAME**<TAB>
tmux.send_keys 'C-u' tmux.send_keys 'C-u'
tmux.send_keys "cat ~#{ENV['USER']}**", :Tab 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.send_keys "'.fzf-home"
tmux.until { |lines| lines.select { |l| l.include? '.fzf-home' }.count > 1 } tmux.until { |lines| lines.select { |l| l.include? '.fzf-home' }.count > 1 }
tmux.send_keys :Enter tmux.send_keys :Enter
@@ -1615,7 +1687,7 @@ module CompletionTest
# /tmp/fzf\ test**<TAB> # /tmp/fzf\ test**<TAB>
tmux.send_keys 'C-u' tmux.send_keys 'C-u'
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab 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.send_keys 'foobar$'
tmux.until { |lines| lines.match_count == 1 } tmux.until { |lines| lines.match_count == 1 }
tmux.send_keys :Enter tmux.send_keys :Enter
@@ -1635,7 +1707,7 @@ module CompletionTest
def test_file_completion_root def test_file_completion_root
tmux.send_keys 'ls /**', :Tab tmux.send_keys 'ls /**', :Tab
tmux.until { |lines| lines.item_count.positive? } tmux.until { |lines| lines.match_count.positive? }
tmux.send_keys :Enter tmux.send_keys :Enter
end end
@@ -1646,7 +1718,7 @@ module CompletionTest
FileUtils.touch '/tmp/fzf-test/d55/xxx' FileUtils.touch '/tmp/fzf-test/d55/xxx'
tmux.prepare tmux.prepare
tmux.send_keys 'cd /tmp/fzf-test/**', :Tab 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 :Tab, :Tab # Tab does not work here
tmux.send_keys 55 tmux.send_keys 55
tmux.until { |lines| lines.match_count == 1 } tmux.until { |lines| lines.match_count == 1 }
@@ -1675,7 +1747,7 @@ module CompletionTest
tmux.prepare tmux.prepare
tmux.send_keys 'C-L' tmux.send_keys 'C-L'
tmux.send_keys 'kill ', :Tab tmux.send_keys 'kill ', :Tab
tmux.until { |lines| lines.item_count.positive? } tmux.until { |lines| lines.match_count.positive? }
tmux.send_keys 'sleep12345' tmux.send_keys 'sleep12345'
tmux.until { |lines| lines.any_include? 'sleep 12345' } tmux.until { |lines| lines.any_include? 'sleep 12345' }
tmux.send_keys :Enter tmux.send_keys :Enter
@@ -1694,7 +1766,7 @@ module CompletionTest
tmux.send_keys '_fzf_compgen_path() { echo "\$1"; seq 10; }', :Enter tmux.send_keys '_fzf_compgen_path() { echo "\$1"; seq 10; }', :Enter
tmux.prepare tmux.prepare
tmux.send_keys 'ls /tmp/**', :Tab 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.send_keys :Tab, :Tab, :Tab
tmux.until { |lines| lines.select_count == 3 } tmux.until { |lines| lines.select_count == 3 }
tmux.send_keys :Enter tmux.send_keys :Enter
@@ -1767,7 +1839,7 @@ class TestBash < TestBase
tmux.paste '_completion_loader() { complete -o default fake; }' tmux.paste '_completion_loader() { complete -o default fake; }'
tmux.paste 'complete -F _fzf_path_completion -o default -o bashdefault fake' tmux.paste 'complete -F _fzf_path_completion -o default -o bashdefault fake'
tmux.send_keys 'fake /tmp/foo**', :Tab 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.send_keys 'C-c'
tmux.prepare tmux.prepare
@@ -1776,7 +1848,7 @@ class TestBash < TestBase
tmux.prepare tmux.prepare
tmux.send_keys 'fake /tmp/foo**', :Tab tmux.send_keys 'fake /tmp/foo**', :Tab
tmux.until { |lines| lines.item_count.positive? } tmux.until { |lines| lines.match_count.positive? }
end end
end end

View File

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