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

Compare commits

...

85 Commits

Author SHA1 Message Date
Junegunn Choi
b46227dcb6 0.17.5 2018-10-07 01:46:29 +09:00
Paul Frybarger
fd8d371ac7 [zsh] Fix multiline prompt issue with 'zle reset-prompt' (#1397)
Close #867 
Close #1256
2018-10-05 10:56:26 +09:00
Junegunn Choi
0e06e298d4 [man] Document that FZF_DEFAULT_COMMAND should be POSIX-compliant
Close #1379
2018-09-30 22:20:46 +09:00
Junegunn Choi
72df905902 Do not wait for more keystrokes after double escape characters
Close #1393
2018-09-28 10:33:53 +09:00
Junegunn Choi
0d748a0699 Kill running preview process after 500ms when focus has changed
Close #1383
Close #1384
2018-09-28 10:33:52 +09:00
Junegunn Choi
27c40dc6b0 Restore STDIN during execute-silent
This allows users to terminate the process with CTRL-C when it hangs.
2018-09-27 15:54:13 +09:00
Junegunn Choi
8e34e6fbb4 [install] Escape spaces in installation directory
Close #1390
2018-09-27 10:15:22 +09:00
Junegunn Choi
3bc98ed623 Add link to related projects page 2018-09-27 02:52:52 +09:00
Tim Cuthbertson
70a92a858a Don't drop buffered input data in findOffset() (#1392) 2018-09-27 02:35:44 +09:00
Jan Edmund Lazo
49d04374a4 [install] Detect MSYS on Windows (#1391) 2018-09-25 23:03:36 +09:00
Junegunn Choi
8540902a35 Add link to git key bindings gist 2018-09-04 12:24:46 +09:00
Junegunn Choi
8c6fcee3ca [vim] Fix directory switching around sink function
Close #1356

Related:
- #612
- https://github.com/junegunn/fzf.vim/issues/308
2018-08-20 15:31:41 +09:00
Junegunn Choi
13803d0dbb [vim] Clear temporary window-local working directory
Close #1085
Close #1086
Close https://github.com/junegunn/fzf.vim/issues/678
2018-08-10 18:24:18 +09:00
Michael Kelley
423986996a Handle incomplete ESC sequence in typeahead buffer (#1350)
If an ESC char is found while processing characters,
continue to check for characters. This prevents fzf from
prematurely exiting.

Close #1349
2018-08-08 15:43:55 +09:00
Younes Manton
1c9e7b7ea6 Update Makefile to build ppc64le binary (#1326)
* Add ppc64le support to Makefile

* Update crypt libs to fix tty ioctls on ppc64le

The hardcoded tty ioctl commands in the terminal package were not
correct for ppc64le and caused the ioctls to return ENOTTY for
commands like TCGETS and so on. The bug is fixed in later versions.
2018-07-16 18:55:06 +09:00
Jay
6de1ad9d3d [completion] Filter out non-hostnames in SSH config file (#1329)
* Correctly exclude SSH config options with Host

SSH config files have 14 options containing 'Host'.
Previously The zsh and bash completion scripts would include lines
containing these options when doing command-line completion of SSH hosts
with `ssh **`.

This commit fixes that problem by only including lines with 'host '.

* Don't autocomplete SSH hostnames using ?

SSH config files support ? as well as * for wildcards in Host lines.
This commit excludes lines containing ? for zsh/bash command line
completeion using `ssh **`
2018-07-06 11:29:39 +09:00
Oliver Schrenk
5004ae3457 [fish] Use $version instead of $FISH_VERSION (#1100)
$FISH_VERSION is dropped in 2.7, but every version has $version

- https://github.com/fish-shell/fish-shell/issues/4414
- fb8ae04f80

Comment from @faho in #1316:

Unfortunately, $FISH_VERSION was only ever a thing from fish 2.0 to fish 2.7.1.

All fish versions from the very beginning though used a variable called simply "$version" to store their version, so that is the one that should be used.
2018-06-27 19:02:16 +09:00
做梦专业户
e67cc75063 Update Makefile to support armv8l (#1321) 2018-06-27 18:56:02 +09:00
Junegunn Choi
0edbcbdf19 Allow search query longer than the screen width
By implementing horizontal scrolling of the prompt line.
Maximum length is hard-coded to 300-chars.

Close #1312
Fix #1225
2018-06-25 19:07:47 +09:00
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
Junegunn Choi
390b49653b 0.17.3 2017-12-03 23:55:24 +09:00
Junegunn Choi
b877c385f0 Fix assertions in test_dynamic_completion_loader 2017-12-03 23:54:58 +09:00
Junegunn Choi
9c47739c0e Fix panic when replace-query is triggered on empty result set 2017-12-03 23:48:59 +09:00
Junegunn Choi
04aa2992e7 Revert "0.17.2"
This reverts commit 2f1edeff78.
2017-12-03 23:42:38 +09:00
Junegunn Choi
2f1edeff78 0.17.2 2017-12-03 23:34:37 +09:00
Junegunn Choi
306d51cdcf Update tcell to fix double-enter problem on Windows GVim
- Close #1169
- https://github.com/gdamore/tcell/pull/159
2017-12-03 23:32:45 +09:00
Junegunn Choi
54a026525a [vim] Remove unnecessary term_wait workaround
The issue is fixed in 1232624ae5
2017-12-03 23:32:43 +09:00
Junegunn Choi
d6588fc835 [bash-completion] Fix custom completion with dynamic loader enabled
After _completion_loader is called, instead of loading the entire
completion.bash file, just restore the fzf completion for the current
command. `_fzf_orig_completion_$cmd` is only set if _completion_loader
actually changed the completion options to avoid infinite loop.

Close #1170
2017-12-03 23:32:41 +09:00
Junegunn Choi
5a7b41a2cf Add accept-non-empty action
'accept-non-empty' is similar to 'accept' (which is bound to 'enter' and
'double-click' by default) but it prevents fzf from exiting without any
selection.

Close #1162
2017-12-02 02:28:36 +09:00
Junegunn Choi
338a73d764 [man] Describe 'cancel' action 2017-12-01 19:13:51 +09:00
Junegunn Choi
c20954f020 Add replace-query action
replace-query action replaces the query string with the current
selection. If the selection is too long, it will be truncated.

If the line contains meta-characters of fzf search syntax, it is
possible that the line is no longer included in the updated result.

e.g.

  echo '!hello' | fzf --bind ctrl-v:replace-query

Close #1137
2017-12-01 13:08:55 +09:00
Junegunn Choi
1e8e1d3c9d Fix test case on older versions of Ruby 2017-12-01 13:03:02 +09:00
Junegunn Choi
f6b1962056 Inject $LINES and $COLUMNS when running preview command
Close #1168
2017-12-01 03:28:10 +09:00
Junegunn Choi
b3b101a89c Support binding of left-click and right-click
left-click and right-click are respectively bound to "ignore" and
"toggle" (after implicitly moving the cursor) by default.

Close #1130
2017-12-01 03:28:08 +09:00
Junegunn Choi
9615c4edf1 Fix test case for invalid FZF_DEFAULT_COMMAND 2017-12-01 02:22:36 +09:00
Junegunn Choi
85a75ee035 Revert default command: find with -fstype required
In #1061 we changed the default command to retry with a simpler find
command with fewer arguments if the first find command failed. This was
to support stripped-down verions of find that do not support -fstype
argument.

However, this caused an unwanted side-effect of yielding duplicate
entries when the first command failed after producing some lines.

We revert the change in this commit, so the default command will not
work with find without -fstype support. But we now print better error
message in that case so that the user can set up a working
$FZF_DEFAULT_COMMAND.

Close #1120 #1167
2017-12-01 01:40:42 +09:00
Junegunn Choi
1e5bd55672 [install] Change the order of case patterns for $archi (#1060)
/cc @ehandal
2017-11-27 15:44:19 +09:00
Jan Edmund Lazo
37d4015d56 [vim] Don't use :terminal on msys2 or Cygwin (#1155)
Close #1152

msys2 terminal Vim assumes that it runs in mintty
so `:terminal` uses `TERM=xterm`.
fzf doesn't support `TERM=xterm` on Windows.
2017-11-22 13:34:02 +09:00
Junegunn Choi
6b27554cdb Clarify installation instructions 2017-11-22 03:10:20 +09:00
Junegunn Choi
fc1b119159 [vim] Add instruction to hide statusline of terminal buffer (#1143) 2017-11-19 12:07:54 +09:00
Aaron Jensen
2cd0d4a9f7 [zsh] Fire zsh precmd functions after cd (#1136)
Fixes #915
2017-11-14 12:43:52 +09:00
Elliott Sales de Andrade
fd03aabeb2 Add Fedora installation information (#1141) 2017-11-14 12:40:17 +09:00
Justin Toniazzo
8068c975c2 Fix broken link in readme TOC (#1131)
The `Respecting .gitignore` link pointed to a section of the readme which no longer exists.
2017-11-08 23:54:46 +09:00
Junegunn Choi
a6d2ab3360 Update README: Examples using fd
- https://github.com/sharkdp/fd
- https://mike.place/2017/fzf-fd/

/cc @williamsmj
2017-10-29 23:48:55 +09:00
Adam Dinwoodie
fe7b91dfd9 Add bin/fzf.exe to .gitignore (#1111)
On Cygwin and MinGW, the fzf binary will have a .exe extension, so
ignore that binary if it exists as well as the bare binary.
2017-10-27 09:12:12 +09:00
Junegunn Choi
5784101bea Suggest ripgrep instead of the silver searcher
Since https://github.com/BurntSushi/ripgrep/issues/200 is fixed in
0.7.1, we can safely suggest ripgrep as the candidate generator as it
has a more precise implementation of gitignore filtering than the silver
searcher.
2017-10-23 13:19:11 +09:00
Igor Urazov
eaf6eb8978 [completion] Ensure ps called as command (#1098)
When `ps` is aliased for something uncommon, like `alias ps=grc ps` which colorizes ps output, the output of `ps` can be unexpected and/or undesired.

This change makes ps to be always executed as command, even if it's aliased.
2017-10-21 10:31:34 +09:00
Daniel Schaffrath
3af63bcf1f [zsh] Use fc -r instead of fzf --tac to speed up loadtime (#1097)
Reference: http://zsh.sourceforge.net/Doc/Release/Shell-Builtin-Commands.html

> The flag -r reverses the order of the events
2017-10-20 12:56:02 +09:00
Andrey Chernih
80a21f7a75 [completion] Fix known_hosts completion for custom port number (#1092)
Handles records like "[20.20.7.168]:9722 ssh-rsa ..."

This is a standard format for servers running on custom port according to http://man.openbsd.org/sshd.8#SSH_KNOWN_HOSTS_FILE_FORMAT

    A hostname or address may optionally be enclosed within ‘[’ and ‘]’
    brackets then followed by ‘:’ and a non-standard port number.
2017-10-19 22:04:32 +09:00
43 changed files with 1196 additions and 391 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 ...

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
bin/fzf bin/fzf
bin/fzf.exe
target target
pkg pkg
Gemfile.lock Gemfile.lock

View File

@@ -1,6 +1,52 @@
CHANGELOG CHANGELOG
========= =========
0.17.5
------
- Bug fixes and improvements
- See https://github.com/junegunn/fzf/milestone/13?closed=1
- Search query longer than the screen width is allowed (up to 300 chars)
- Built with Go 1.11.1
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
knows the exact size of the preview window.
- Better error messages when the default command or `$FZF_DEFAULT_COMMAND`
fails.
- Reverted #1061 to avoid having duplicate entries in the list when find
command detected a file system loop (#1120). The default command now
requires that find supports `-fstype` option.
- fzf now distinguishes mouse left click and right click (#1130)
- Right click is now bound to `toggle` action by default
- `--bind` understands `left-click` and `right-click`
- Added `replace-query` action (#1137)
- Replaces query string with the current selection
- Added `accept-non-empty` action (#1162)
- Same as accept, except that it prevents fzf from exiting without any
selection
0.17.1 0.17.1
------ ------

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)))
@@ -29,6 +22,7 @@ BINARYARM5 := fzf-$(GOOS)_arm5
BINARYARM6 := fzf-$(GOOS)_arm6 BINARYARM6 := fzf-$(GOOS)_arm6
BINARYARM7 := fzf-$(GOOS)_arm7 BINARYARM7 := fzf-$(GOOS)_arm7
BINARYARM8 := fzf-$(GOOS)_arm8 BINARYARM8 := fzf-$(GOOS)_arm8
BINARYPPC64LE := fzf-$(GOOS)_ppc64le
VERSION := $(shell awk -F= '/version =/ {print $$2}' src/constants.go | tr -d "\" ") VERSION := $(shell awk -F= '/version =/ {print $$2}' src/constants.go | tr -d "\" ")
RELEASE32 := fzf-$(VERSION)-$(GOOS)_386 RELEASE32 := fzf-$(VERSION)-$(GOOS)_386
RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64 RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
@@ -36,6 +30,7 @@ RELEASEARM5 := fzf-$(VERSION)-$(GOOS)_arm5
RELEASEARM6 := fzf-$(VERSION)-$(GOOS)_arm6 RELEASEARM6 := fzf-$(VERSION)-$(GOOS)_arm6
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7 RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
RELEASEARM8 := fzf-$(VERSION)-$(GOOS)_arm8 RELEASEARM8 := fzf-$(VERSION)-$(GOOS)_arm8
RELEASEPPC64LE := fzf-$(VERSION)-$(GOOS)_ppc64le
# https://en.wikipedia.org/wiki/Uname # https://en.wikipedia.org/wiki/Uname
UNAME_M := $(shell uname -m) UNAME_M := $(shell uname -m)
@@ -53,6 +48,10 @@ else ifeq ($(UNAME_M),armv6l)
BINARY := $(BINARYARM6) BINARY := $(BINARYARM6)
else ifeq ($(UNAME_M),armv7l) else ifeq ($(UNAME_M),armv7l)
BINARY := $(BINARYARM7) BINARY := $(BINARYARM7)
else ifeq ($(UNAME_M),armv8l)
BINARY := $(BINARYARM8)
else ifeq ($(UNAME_M),ppc64le)
BINARY := $(BINARYPPC64LE)
else else
$(error "Build on $(UNAME_M) is not supported, yet.") $(error "Build on $(UNAME_M) is not supported, yet.")
endif endif
@@ -68,13 +67,14 @@ release: target/$(BINARY32) target/$(BINARY64)
cd target && cp -f $(BINARY64) fzf.exe && zip $(RELEASE64).zip fzf.exe cd target && cp -f $(BINARY64) fzf.exe && zip $(RELEASE64).zip fzf.exe
cd target && rm -f fzf.exe cd target && rm -f fzf.exe
else ifeq ($(GOOS),linux) else ifeq ($(GOOS),linux)
release: target/$(BINARY32) target/$(BINARY64) target/$(BINARYARM5) target/$(BINARYARM6) target/$(BINARYARM7) target/$(BINARYARM8) release: target/$(BINARY32) target/$(BINARY64) target/$(BINARYARM5) target/$(BINARYARM6) target/$(BINARYARM7) target/$(BINARYARM8) target/$(BINARYPPC64LE)
cd target && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf cd target && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf
cd target && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf cd target && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
cd target && cp -f $(BINARYARM5) fzf && tar -czf $(RELEASEARM5).tgz fzf cd target && cp -f $(BINARYARM5) fzf && tar -czf $(RELEASEARM5).tgz fzf
cd target && cp -f $(BINARYARM6) fzf && tar -czf $(RELEASEARM6).tgz fzf cd target && cp -f $(BINARYARM6) fzf && tar -czf $(RELEASEARM6).tgz fzf
cd target && cp -f $(BINARYARM7) fzf && tar -czf $(RELEASEARM7).tgz fzf cd target && cp -f $(BINARYARM7) fzf && tar -czf $(RELEASEARM7).tgz fzf
cd target && cp -f $(BINARYARM8) fzf && tar -czf $(RELEASEARM8).tgz fzf cd target && cp -f $(BINARYARM8) fzf && tar -czf $(RELEASEARM8).tgz fzf
cd target && cp -f $(BINARYPPC64LE) fzf && tar -czf $(RELEASEPPC64LE).tgz fzf
cd target && rm -f fzf cd target && rm -f fzf
else else
release: target/$(BINARY32) target/$(BINARY64) release: target/$(BINARY32) target/$(BINARY64)
@@ -132,7 +132,18 @@ target/$(BINARYARM7): $(SOURCES) vendor
target/$(BINARYARM8): $(SOURCES) vendor target/$(BINARYARM8): $(SOURCES) vendor
GOARCH=arm64 go build $(BUILD_FLAGS) -o $@ GOARCH=arm64 go build $(BUILD_FLAGS) -o $@
target/$(BINARYPPC64LE): $(SOURCES) vendor
GOARCH=ppc64le go build $(BUILD_FLAGS) -o $@
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

@@ -142,6 +142,28 @@ command! -bang MyStuff
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0)) \ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
``` ```
fzf inside terminal buffer
--------------------------
The latest versions of Vim and Neovim include builtin terminal emulator
(`:terminal`) and fzf will start in a terminal buffer in the following cases:
- On Neovim
- On GVim
- On Terminal Vim with the non-default layout
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
### Hide statusline
When fzf starts in a terminal buffer, you may want to hide the statusline of
the containing buffer.
```vim
autocmd! FileType fzf
autocmd FileType fzf set laststatus=0 noshowmode noruler
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
```
GVim GVim
---- ----

148
README.md
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.
@@ -23,9 +23,11 @@ Table of Contents
----------------- -----------------
* [Installation](#installation) * [Installation](#installation)
* [Using git](#using-git)
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew) * [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
* [Using git](#using-git)
* [As Vim plugin](#as-vim-plugin) * [As Vim plugin](#as-vim-plugin)
* [Arch Linux](#arch-linux)
* [Fedora](#fedora)
* [Windows](#windows) * [Windows](#windows)
* [Upgrading fzf](#upgrading-fzf) * [Upgrading fzf](#upgrading-fzf)
* [Building fzf](#building-fzf) * [Building fzf](#building-fzf)
@@ -51,9 +53,10 @@ Table of Contents
* [Executing external programs](#executing-external-programs) * [Executing external programs](#executing-external-programs)
* [Preview window](#preview-window) * [Preview window](#preview-window)
* [Tips](#tips) * [Tips](#tips)
* [Respecting .gitignore, <code>.hgignore</code>, and <code>svn:ignore</code>](#respecting-gitignore-hgignore-and-svnignore) * [Respecting .gitignore](#respecting-gitignore)
* [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal) * [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal)
* [Fish shell](#fish-shell) * [Fish shell](#fish-shell)
* [Related projects](#related-projects)
* [<a href="LICENSE">License</a>](#license) * [<a href="LICENSE">License</a>](#license)
Installation Installation
@@ -73,20 +76,10 @@ stuff.
[bin]: https://github.com/junegunn/fzf-bin/releases [bin]: https://github.com/junegunn/fzf-bin/releases
### Using git
Clone this repository and run
[install](https://github.com/junegunn/fzf/blob/master/install) script.
```sh
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install
```
### Using Homebrew or Linuxbrew ### Using Homebrew or Linuxbrew
Alternatively, you can use [Homebrew](http://brew.sh/) or You can use [Homebrew](http://brew.sh/) or [Linuxbrew](http://linuxbrew.sh/)
[Linuxbrew](http://linuxbrew.sh/) to install fzf. to install fzf.
```sh ```sh
brew install fzf brew install fzf
@@ -95,25 +88,70 @@ brew install fzf
$(brew --prefix)/opt/fzf/install $(brew --prefix)/opt/fzf/install
``` ```
### Using git
Alternatively, you can "git clone" this repository to any directory and run
[install](https://github.com/junegunn/fzf/blob/master/install) script.
```sh
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install
```
### As Vim plugin ### As Vim plugin
You can manually add the directory to `&runtimepath` as follows, Once you have fzf installed, you can enable it inside Vim simply by adding the
directory to `&runtimepath` in your Vim configuration file as follows:
```vim ```vim
" If installed using git
set rtp+=~/.fzf
" If installed using Homebrew " If installed using Homebrew
set rtp+=/usr/local/opt/fzf set rtp+=/usr/local/opt/fzf
" If installed using git
set rtp+=~/.fzf
``` ```
But it's recommended that you use a plugin manager like If you use [vim-plug](https://github.com/junegunn/vim-plug), the same can be
[vim-plug](https://github.com/junegunn/vim-plug). written as:
```vim ```vim
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' } " If installed using Homebrew
Plug '/usr/local/opt/fzf'
" If installed using git
Plug '~/.fzf'
``` ```
But instead of separately installing fzf on your system (using Homebrew or
"git clone") and enabling it on Vim (adding it to `&runtimepath`), you can use
vim-plug to do both.
```vim
" PlugInstall and PlugUpdate will clone fzf in ~/.fzf and run install script
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
" Both options are optional. You don't have to install fzf in ~/.fzf
" 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
method:
```sh
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 (/usr/share/doc/fzf/README.Fedora) for more information.
### Windows ### Windows
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
@@ -186,8 +224,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)
@@ -197,7 +235,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
@@ -207,12 +245,13 @@ 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` |
| `'wild` | exact-match (quoted) | Items that include `wild` |
| `^music` | prefix-exact-match | Items that start with `music` | | `^music` | prefix-exact-match | Items that start with `music` |
| `.mp3$` | suffix-exact-match | Items that end with `.mp3` | | `.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` | | `!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` | | `!.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,
@@ -231,10 +270,10 @@ or `py`.
- `FZF_DEFAULT_COMMAND` - `FZF_DEFAULT_COMMAND`
- Default command to use when input is tty - Default command to use when input is tty
- e.g. `export FZF_DEFAULT_COMMAND='ag -g ""'` - 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
@@ -369,26 +408,20 @@ export FZF_COMPLETION_TRIGGER='~~'
# Options to fzf command # Options to fzf command
export FZF_COMPLETION_OPTS='+c -x' export FZF_COMPLETION_OPTS='+c -x'
# Use ag instead of the default find command for listing path candidates. # Use fd (https://github.com/sharkdp/fd) instead of the default find
# - The first argument to the function is the base path to start traversal # command for listing path candidates.
# - The first argument to the function ($1) is the base path to start traversal
# - See the source code (completion.{bash,zsh}) for the details. # - See the source code (completion.{bash,zsh}) for the details.
# - ag only lists files, so we use with-dir script to augment the output
_fzf_compgen_path() { _fzf_compgen_path() {
ag -g "" "$1" | with-dir "$1" fd --hidden --follow --exclude ".git" . "$1"
} }
# Use ag to generate the list for directory completion # Use fd to generate the list for directory completion
_fzf_compgen_dir() { _fzf_compgen_dir() {
ag -g "" "$1" | only-dir "$1" fd --type d --hidden --follow --exclude ".git" . "$1"
} }
``` ```
`only-dir` and `with-dir` scripts can be found [here][dir-scripts]. They are
written in Ruby, but you should be able to rewrite them in any language you
prefer.
[dir-scripts]: https://gist.github.com/junegunn/8c3796a965f22e6a803fe53096ad7a75
#### Supported commands #### Supported commands
On bash, fuzzy completion is enabled only for a predefined set of commands On bash, fuzzy completion is enabled only for a predefined set of commands
@@ -482,37 +515,41 @@ You can customize the size and position of the preview window using
fzf --height 40% --reverse --preview 'file {}' --preview-window down:1 fzf --height 40% --reverse --preview 'file {}' --preview-window down:1
``` ```
For more advanced examples, see [Key bindings for git with fzf][fzf-git]. For more advanced examples, see [Key bindings for git with fzf][fzf-git]
([code](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236)).
[fzf-git]: https://junegunn.kr/2016/07/fzf-git/ [fzf-git]: https://junegunn.kr/2016/07/fzf-git/
Tips Tips
---- ----
#### Respecting `.gitignore`, `.hgignore`, and `svn:ignore` #### Respecting `.gitignore`
[ag](https://github.com/ggreer/the_silver_searcher) or You can use [fd](https://github.com/sharkdp/fd),
[rg](https://github.com/BurntSushi/ripgrep) will do the [ripgrep](https://github.com/BurntSushi/ripgrep), or [the silver
filtering: searcher](https://github.com/ggreer/the_silver_searcher) instead of the
default find command to traverse the file system while respecting
`.gitignore`.
```sh ```sh
# Feed the output of ag into fzf # Feed the output of fd into fzf
ag -g "" | fzf fd --type f | fzf
# Setting ag as the default source for fzf # Setting fd as the default source for fzf
export FZF_DEFAULT_COMMAND='ag -g ""' export FZF_DEFAULT_COMMAND='fd --type f'
# Now fzf (w/o pipe) will use ag instead of find # Now fzf (w/o pipe) will use fd instead of find
fzf fzf
# To apply the command to CTRL-T as well # To apply the command to CTRL-T as well
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND" export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
``` ```
If you don't want to exclude hidden files, use the following command: If you want the command to follow symbolic links, and don't want it to exclude
hidden files, use the following command:
```sh ```sh
export FZF_DEFAULT_COMMAND='ag --hidden --ignore .git -g ""' export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
``` ```
#### `git ls-tree` for fast traversal #### `git ls-tree` for fast traversal
@@ -563,6 +600,11 @@ valid directory. Example:
set -g FZF_CTRL_T_COMMAND "command find -L \$dir -type f 2> /dev/null | sed '1d; s#^\./##'" set -g FZF_CTRL_T_COMMAND "command find -L \$dir -type f 2> /dev/null | sed '1d; s#^\./##'"
``` ```
Related projects
----------------
https://github.com/junegunn/fzf/wiki/Related-projects
[License](LICENSE) [License](LICENSE)
------------------ ------------------

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 \;\

View File

@@ -1,4 +1,4 @@
fzf.txt fzf Last change: September 29 2017 fzf.txt fzf Last change: November 19 2017
FZF - TABLE OF CONTENTS *fzf* *fzf-toc* FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
============================================================================== ==============================================================================
@@ -8,6 +8,8 @@ FZF - TABLE OF CONTENTS *fzf* *fzf-to
Examples Examples
fzf#run fzf#run
fzf#wrap fzf#wrap
fzf inside terminal buffer
Hide statusline
GVim GVim
License License
@@ -167,6 +169,29 @@ function that decorates the options dictionary so that it understands
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0)) \ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
< <
FZF INSIDE TERMINAL BUFFER *fzf-inside-terminal-buffer*
==============================================================================
The latest versions of Vim and Neovim include builtin terminal emulator
(`:terminal`) and fzf will start in a terminal buffer in the following cases:
- On Neovim
- On GVim
- On Terminal Vim with the non-default layout
- `callfzf#run({'left':'30%'})` or `letg:fzf_layout={'left':'30%'}`
< Hide statusline >___________________________________________________________~
*fzf-hide-statusline*
When fzf starts in a terminal buffer, you may want to hide the statusline of
the containing buffer.
>
autocmd! FileType fzf
autocmd FileType fzf set laststatus=0 noshowmode noruler
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
<
GVIM *fzf-gvim* GVIM *fzf-gvim*
============================================================================== ==============================================================================

18
glide.lock generated
View File

@@ -1,22 +1,30 @@
hash: d68dd0bd779ac4ffca1e0c49ca38d85f90d5d68fa8e2d5d7db70a8ce8c662ec1 hash: b617c76661b399f586276767bb93ee67b65dd03cfd1348ecad409e372ea97b3e
updated: 2017-06-01T15:48:41.653745249-07:00 updated: 2018-06-27T18:37:20.189962-07:00
imports: imports:
- name: github.com/codegangsta/cli
version: c6af8847eb2b7b297d07c3ede98903e95e680ef9
- name: github.com/gdamore/encoding - name: github.com/gdamore/encoding
version: b23993cbb6353f0e6aa98d0ee318a34728f628b9 version: b23993cbb6353f0e6aa98d0ee318a34728f628b9
- name: github.com/gdamore/tcell - name: github.com/gdamore/tcell
version: 44772c121bb7838819d3ba4a7e84c0c2d617328e version: 0a0db94084dfe181108c18508ebd312f12d331fb
subpackages: subpackages:
- encoding - encoding
- name: github.com/lucasb-eyer/go-colorful - name: github.com/lucasb-eyer/go-colorful
version: c900de9dbbc73129068f5af6a823068fc5f2308c version: c900de9dbbc73129068f5af6a823068fc5f2308c
- name: github.com/Masterminds/semver
version: 15d8430ab86497c5c0da827b748823945e1cf1e1
- name: github.com/Masterminds/vcs
version: 6f1c6d150500e452704e9863f68c2559f58616bf
- name: github.com/mattn/go-isatty - name: github.com/mattn/go-isatty
version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8 version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
- name: github.com/mattn/go-runewidth - name: github.com/mattn/go-runewidth
version: 14207d285c6c197daabb5c9793d63e7af9ab2d50 version: 14207d285c6c197daabb5c9793d63e7af9ab2d50
- name: github.com/mattn/go-shellwords - name: github.com/mattn/go-shellwords
version: 02e3cf038dcea8290e44424da473dd12be796a8a version: 02e3cf038dcea8290e44424da473dd12be796a8a
- name: github.com/mitchellh/go-homedir
version: b8bc1bf767474819792c23f32d8286a45736f1c6
- name: golang.org/x/crypto - name: golang.org/x/crypto
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e version: 558b6879de74bc843225cde5686419267ff707ca
subpackages: subpackages:
- ssh/terminal - ssh/terminal
- name: golang.org/x/sys - name: golang.org/x/sys
@@ -35,4 +43,6 @@ imports:
- encoding/simplifiedchinese - encoding/simplifiedchinese
- encoding/traditionalchinese - encoding/traditionalchinese
- transform - transform
- name: gopkg.in/yaml.v2
version: 287cf08546ab5e7e37d55a84f7ed3fd1db036de5
testImports: [] testImports: []

View File

@@ -7,10 +7,10 @@ import:
- package: github.com/mattn/go-shellwords - package: github.com/mattn/go-shellwords
version: 02e3cf038dcea8290e44424da473dd12be796a8a version: 02e3cf038dcea8290e44424da473dd12be796a8a
- package: github.com/gdamore/tcell - package: github.com/gdamore/tcell
version: 44772c121bb7838819d3ba4a7e84c0c2d617328e version: 0a0db94084dfe181108c18508ebd312f12d331fb
subpackages: subpackages:
- encoding - encoding
- package: golang.org/x/crypto - package: golang.org/x/crypto
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e version: 558b6879de74bc843225cde5686419267ff707ca
subpackages: subpackages:
- ssh/terminal - ssh/terminal

47
install
View File

@@ -2,13 +2,16 @@
set -u set -u
version=0.17.1 version=0.17.5
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 ;;
@@ -64,11 +73,13 @@ for opt in "$@"; do
done done
cd "$(dirname "${BASH_SOURCE[0]}")" cd "$(dirname "${BASH_SOURCE[0]}")"
fzf_base="$(pwd)" fzf_base=$(pwd)
fzf_base_esc=$(printf %q "$fzf_base")
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
@@ -168,13 +179,13 @@ binary_error=""
case "$archi" in case "$archi" in
Darwin\ *64) download fzf-$version-darwin_${binary_arch:-amd64}.tgz ;; Darwin\ *64) download fzf-$version-darwin_${binary_arch:-amd64}.tgz ;;
Darwin\ *86) download fzf-$version-darwin_${binary_arch:-386}.tgz ;; Darwin\ *86) download fzf-$version-darwin_${binary_arch:-386}.tgz ;;
Linux\ *64) download fzf-$version-linux_${binary_arch:-amd64}.tgz ;;
Linux\ *86) download fzf-$version-linux_${binary_arch:-386}.tgz ;;
Linux\ armv5*) download fzf-$version-linux_${binary_arch:-arm5}.tgz ;; Linux\ armv5*) download fzf-$version-linux_${binary_arch:-arm5}.tgz ;;
Linux\ armv6*) download fzf-$version-linux_${binary_arch:-arm6}.tgz ;; Linux\ armv6*) download fzf-$version-linux_${binary_arch:-arm6}.tgz ;;
Linux\ armv7*) download fzf-$version-linux_${binary_arch:-arm7}.tgz ;; Linux\ armv7*) download fzf-$version-linux_${binary_arch:-arm7}.tgz ;;
Linux\ armv8*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;; Linux\ armv8*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;;
Linux\ aarch64*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;; Linux\ aarch64*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;;
Linux\ *64) download fzf-$version-linux_${binary_arch:-amd64}.tgz ;;
Linux\ *86) download fzf-$version-linux_${binary_arch:-386}.tgz ;;
FreeBSD\ *64) download fzf-$version-freebsd_${binary_arch:-amd64}.tgz ;; FreeBSD\ *64) download fzf-$version-freebsd_${binary_arch:-amd64}.tgz ;;
FreeBSD\ *86) download fzf-$version-freebsd_${binary_arch:-386}.tgz ;; FreeBSD\ *86) download fzf-$version-freebsd_${binary_arch:-386}.tgz ;;
OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64}.tgz ;; OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64}.tgz ;;
@@ -182,6 +193,8 @@ case "$archi" in
CYGWIN*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;; CYGWIN*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
MINGW*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;; MINGW*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
MINGW*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;; MINGW*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
MSYS*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
MSYS*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
*) binary_available=0 binary_error=1 ;; *) binary_available=0 binary_error=1 ;;
esac esac
@@ -239,8 +252,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,10 +265,10 @@ 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_esc/bin* ]]; then
export PATH="\$PATH:$fzf_base/bin" export PATH="\$PATH:$fzf_base/bin"
fi fi
@@ -275,18 +288,18 @@ done
if [[ "$shells" =~ fish ]]; then if [[ "$shells" =~ fish ]]; then
echo -n "Update fish_user_paths ... " echo -n "Update fish_user_paths ... "
fish << EOF fish << EOF
echo \$fish_user_paths | \grep $fzf_base/bin > /dev/null echo \$fish_user_paths | \grep "$fzf_base"/bin > /dev/null
or set --universal fish_user_paths \$fish_user_paths $fzf_base/bin or set --universal fish_user_paths \$fish_user_paths "$fzf_base"/bin
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 +365,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 "Oct 2017" "fzf 0.17.1" "fzf-tmux - open fzf in tmux split pane" .TH fzf-tmux 1 "Oct 2018" "fzf 0.17.5" "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 "Oct 2017" "fzf 0.17.1" "fzf - a command-line fuzzy finder" .TH fzf 1 "Oct 2018" "fzf 0.17.5" "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
@@ -272,19 +285,28 @@ string, specify field index expressions between the braces (See \fBFIELD INDEX
EXPRESSION\fR for the details). EXPRESSION\fR for the details).
.RS .RS
e.g. \fBfzf --preview="head -$LINES {}"\fR e.g. \fBfzf --preview='head -$LINES {}'\fR
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR \fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
fzf overrides \fB$LINES\fR and \fB$COLUMNS\fR so that they represent the exact
size of the preview window.
A placeholder expression starting with \fB+\fR flag will be replaced to the A placeholder expression starting with \fB+\fR flag will be replaced to the
space-separated list of the selected lines (or the current line if no selection space-separated list of the selected lines (or the current line if no selection
was made) individually quoted. 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]"
@@ -368,7 +390,8 @@ Note that most options have the opposite versions with \fB--no-\fR prefix.
.SH ENVIRONMENT VARIABLES .SH ENVIRONMENT VARIABLES
.TP .TP
.B FZF_DEFAULT_COMMAND .B FZF_DEFAULT_COMMAND
Default command to use when input is tty Default command to use when input is tty. On *nix systems, fzf runs the command
with \fBsh -c\fR, so make sure that it's POSIX-compliant.
.TP .TP
.B FZF_DEFAULT_OPTS .B FZF_DEFAULT_OPTS
Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
@@ -458,6 +481,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)
@@ -474,8 +501,12 @@ 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
\fIright-click\fR
\fIdouble-click\fR \fIdouble-click\fR
or any single character or any single character
@@ -487,12 +518,13 @@ triggered whenever the query string is changed.
\fBACTION: DEFAULT BINDINGS (NOTES): \fBACTION: DEFAULT BINDINGS (NOTES):
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR \fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
\fBaccept\fR \fIenter double-click\fR \fBaccept\fR \fIenter double-click\fR
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
\fBbackward-char\fR \fIctrl-b left\fR \fBbackward-char\fR \fIctrl-b left\fR
\fBbackward-delete-char\fR \fIctrl-h bspace\fR \fBbackward-delete-char\fR \fIctrl-h bspace\fR
\fBbackward-kill-word\fR \fIalt-bs\fR \fBbackward-kill-word\fR \fIalt-bs\fR
\fBbackward-word\fR \fIalt-b shift-left\fR \fBbackward-word\fR \fIalt-b shift-left\fR
\fBbeginning-of-line\fR \fIctrl-a home\fR \fBbeginning-of-line\fR \fIctrl-a home\fR
\fBcancel\fR \fBcancel\fR (clears query string if not empty, aborts fzf otherwise)
\fBclear-screen\fR \fIctrl-l\fR \fBclear-screen\fR \fIctrl-l\fR
\fBdelete-char\fR \fIdel\fR \fBdelete-char\fR \fIdel\fR
\fBdelete-char/eof\fR \fIctrl-d\fR \fBdelete-char/eof\fR \fIctrl-d\fR
@@ -514,18 +546,19 @@ 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)
\fBprint-query\fR (print query and exit) \fBprint-query\fR (print query and exit)
\fBreplace-query\fR (replace query string with the current selection)
\fBselect-all\fR \fBselect-all\fR
\fBtoggle\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
@@ -576,7 +609,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
@@ -356,7 +357,7 @@ try
throw v:exception throw v:exception
endtry endtry
if has('nvim') && !has_key(dict, 'dir') if !has_key(dict, 'dir')
let dict.dir = s:fzf_getcwd() let dict.dir = s:fzf_getcwd()
endif endif
if has('win32unix') && has_key(dict, 'dir') if has('win32unix') && has_key(dict, 'dir')
@@ -392,7 +393,7 @@ try
let has_vim8_term = has('terminal') && has('patch-8.0.995') let has_vim8_term = has('terminal') && has('patch-8.0.995')
let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win
let use_term = has_nvim_term || let use_term = has_nvim_term ||
\ has_vim8_term && (has('gui_running') || s:is_win || !use_height && s:present(dict, 'down', 'up', 'left', 'right', 'window')) \ has_vim8_term && !has('win32unix') && (has('gui_running') || s:is_win || !use_height && s:present(dict, 'down', 'up', 'left', 'right', 'window'))
let use_tmux = (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:tmux_enabled() && s:splittable(dict) let use_tmux = (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:tmux_enabled() && s:splittable(dict)
if prefer_tmux && use_tmux if prefer_tmux && use_tmux
let use_height = 0 let use_height = 0
@@ -454,15 +455,17 @@ endfunction
function! s:pushd(dict) function! s:pushd(dict)
if s:present(a:dict, 'dir') if s:present(a:dict, 'dir')
let cwd = s:fzf_getcwd() let cwd = s:fzf_getcwd()
if get(a:dict, 'prev_dir', '') ==# cwd let w:fzf_pushd = {
return 1 \ 'command': haslocaldir() ? 'lcd' : (exists(':tcd') && haslocaldir(-1) ? 'tcd' : 'cd'),
endif \ 'origin': cwd
let a:dict.prev_dir = cwd \ }
execute 'lcd' s:escape(a:dict.dir) execute 'lcd' s:escape(a:dict.dir)
let a:dict.dir = s:fzf_getcwd() let cwd = s:fzf_getcwd()
return 1 let w:fzf_pushd.dir = cwd
let a:dict.pushd = w:fzf_pushd
return cwd
endif endif
return 0 return ''
endfunction endfunction
augroup fzf_popd augroup fzf_popd
@@ -471,11 +474,29 @@ augroup fzf_popd
augroup END augroup END
function! s:dopopd() function! s:dopopd()
if !exists('w:fzf_dir') || s:fzf_getcwd() != w:fzf_dir[1] if !exists('w:fzf_pushd')
return return
endif endif
execute 'lcd' s:escape(w:fzf_dir[0])
unlet w:fzf_dir " FIXME: We temporarily change the working directory to 'dir' entry
" of options dictionary (set to the current working directory if not given)
" before running fzf.
"
" e.g. call fzf#run({'dir': '/tmp', 'source': 'ls', 'sink': 'e'})
"
" After processing the sink function, we have to restore the current working
" directory. But doing so may not be desirable if the function changed the
" working directory on purpose.
"
" So how can we tell if we should do it or not? A simple heuristic we use
" here is that we change directory only if the current working directory
" matches 'dir' entry. However, it is possible that the sink function did
" change the directory to 'dir'. In that case, the user will have an
" unexpected result.
if s:fzf_getcwd() ==# w:fzf_pushd.dir
execute w:fzf_pushd.command s:escape(w:fzf_pushd.origin)
endif
unlet w:fzf_pushd
endfunction endfunction
function! s:xterm_launcher() function! s:xterm_launcher()
@@ -533,9 +554,7 @@ function! s:execute(dict, command, use_height, temps) abort
let fzf.dict = a:dict let fzf.dict = a:dict
let fzf.temps = a:temps let fzf.temps = a:temps
function! fzf.on_exit(job_id, exit_status, event) dict function! fzf.on_exit(job_id, exit_status, event) dict
if s:present(self.dict, 'dir') call s:pushd(self.dict)
execute 'lcd' s:escape(self.dict.dir)
endif
let lines = s:collect(self.temps) let lines = s:collect(self.temps)
call s:callback(self.dict, lines) call s:callback(self.dict, lines)
endfunction endfunction
@@ -562,9 +581,10 @@ endfunction
function! s:execute_tmux(dict, command, temps) abort function! s:execute_tmux(dict, command, temps) abort
let command = a:command let command = a:command
if s:pushd(a:dict) let cwd = s:pushd(a:dict)
if len(cwd)
" -c '#{pane_current_path}' is only available on tmux 1.9 or above " -c '#{pane_current_path}' is only available on tmux 1.9 or above
let command = join(['cd', fzf#shellescape(a:dict.dir), '&&', command]) let command = join(['cd', fzf#shellescape(cwd), '&&', command])
endif endif
call system(command) call system(command)
@@ -586,7 +606,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
@@ -685,9 +705,7 @@ function! s:execute_term(dict, command, temps) abort
endfunction endfunction
try try
if s:present(a:dict, 'dir') call s:pushd(a:dict)
execute 'lcd' s:escape(a:dict.dir)
endif
if s:is_win if s:is_win
let fzf.temps.batchfile = s:fzf_tempname().'.bat' let fzf.temps.batchfile = s:fzf_tempname().'.bat'
call writefile(s:wrap_cmds(a:command), fzf.temps.batchfile) call writefile(s:wrap_cmds(a:command), fzf.temps.batchfile)
@@ -699,16 +717,13 @@ 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)})
" FIXME: https://github.com/vim/vim/issues/1998 if !has('patch-8.0.1261') && !has('nvim') && !s:is_win
if !has('nvim') && !s:is_win call term_wait(fzf.buf, 20)
call term_wait(t, 20)
endif endif
endif endif
finally finally
if s:present(a:dict, 'dir') call s:dopopd()
lcd -
endif
endtry endtry
setlocal nospell bufhidden=wipe nobuflisted nonumber setlocal nospell bufhidden=wipe nobuflisted nonumber
setf fzf setf fzf
@@ -727,21 +742,9 @@ function! s:collect(temps) abort
endfunction endfunction
function! s:callback(dict, lines) abort function! s:callback(dict, lines) abort
" Since anything can be done in the sink function, there is no telling that let popd = has_key(a:dict, 'pushd')
" the change of the working directory was made by &autochdir setting.
"
" We use the following heuristic to determine whether to restore CWD:
" - Always restore the current directory when &autochdir is disabled.
" FIXME This makes it impossible to change directory from inside the sink
" function when &autochdir is not used.
" - In case of an error or an interrupt, a:lines will be empty.
" And it will be an array of a single empty string when fzf was finished
" without a match. In these cases, we presume that the change of the
" directory is not expected and should be undone.
let popd = has_key(a:dict, 'prev_dir') &&
\ (!&autochdir || (empty(a:lines) || len(a:lines) == 1 && empty(a:lines[0])))
if popd if popd
let w:fzf_dir = [a:dict.prev_dir, a:dict.dir] let w:fzf_pushd = a:dict.pushd
endif endif
try try
@@ -765,7 +768,7 @@ function! s:callback(dict, lines) abort
" We may have opened a new window or tab " We may have opened a new window or tab
if popd if popd
let w:fzf_dir = [a:dict.prev_dir, a:dict.dir] let w:fzf_pushd = a:dict.pushd
call s:dopopd() call s:dopopd()
endif endif
endfunction endfunction

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() {
@@ -113,7 +112,7 @@ _fzf_opts_completion() {
} }
_fzf_handle_dynamic_completion() { _fzf_handle_dynamic_completion() {
local cmd orig_var orig ret orig_cmd local cmd orig_var orig ret orig_cmd orig_complete
cmd="$1" cmd="$1"
shift shift
orig_cmd="$1" orig_cmd="$1"
@@ -122,10 +121,18 @@ _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" 2> /dev/null)
_completion_loader "$@" _completion_loader "$@"
ret=$? ret=$?
eval "$(complete | command grep "\-F.* $orig_cmd$" | _fzf_orig_completion_filter)" # _completion_loader may not have updated completion for the command
source "${BASH_SOURCE[0]}" 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 return $ret
fi fi
} }
@@ -141,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"}
@@ -152,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
@@ -215,7 +223,7 @@ _fzf_complete_kill() {
local selected fzf local selected fzf
fzf="$(__fzfcmd_complete)" fzf="$(__fzfcmd_complete)"
selected=$(ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" $fzf -m | awk '{print $2}' | tr '\n' ' ') selected=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" $fzf -m | awk '{print $2}' | tr '\n' ' ')
printf '\e[5n' printf '\e[5n'
if [ -n "$selected" ]; then if [ -n "$selected" ]; then
@@ -233,8 +241,8 @@ _fzf_complete_telnet() {
_fzf_complete_ssh() { _fzf_complete_ssh() {
_fzf_complete '+m' "$@" < <( _fzf_complete '+m' "$@" < <(
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \ cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host ' | command grep -v '[*?]' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \
<(command grep -oE '^[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | awk '{ print $1 " " $1 }') \ <(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') | <(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u awk '{if (length($2) > 0) {print $2}}' | sort -u
) )
@@ -274,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)
@@ -62,8 +60,7 @@ __fzf_generic_path_completion() {
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches$tail" LBUFFER="$lbuf$matches$tail"
fi fi
zle redisplay zle reset-prompt
typeset -f zle-line-init >/dev/null && zle zle-line-init
break break
fi fi
dir=$(dirname "$dir") dir=$(dirname "$dir")
@@ -102,8 +99,7 @@ _fzf_complete() {
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches" LBUFFER="$lbuf$matches"
fi fi
zle redisplay zle reset-prompt
typeset -f zle-line-init >/dev/null && zle zle-line-init
command rm -f "$fifo" command rm -f "$fifo"
} }
@@ -116,8 +112,8 @@ _fzf_complete_telnet() {
_fzf_complete_ssh() { _fzf_complete_ssh() {
_fzf_complete '+m' "$@" < <( _fzf_complete '+m' "$@" < <(
command cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \ command cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host ' | command grep -v '[*?]' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \
<(command grep -oE '^[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | awk '{ print $1 " " $1 }') \ <(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') | <(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u awk '{if (length($2) > 0) {print $2}}' | sort -u
) )
@@ -163,12 +159,11 @@ fzf-completion() {
# Kill completion (do not require trigger sequence) # Kill completion (do not require trigger sequence)
if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then
fzf="$(__fzfcmd_complete)" fzf="$(__fzfcmd_complete)"
matches=$(ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" ${=fzf} -m | awk '{print $2}' | tr '\n' ' ') matches=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" ${=fzf} -m | awk '{print $2}' | tr '\n' ' ')
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
LBUFFER="$LBUFFER$matches" LBUFFER="$LBUFFER$matches"
fi fi
zle redisplay zle reset-prompt
typeset -f zle-line-init >/dev/null && zle zle-line-init
# Trigger sequence given # Trigger sequence given
elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}) d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})

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

@@ -40,8 +40,8 @@ function fzf_key_bindings
begin begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
set -l FISH_MAJOR (echo $FISH_VERSION | cut -f1 -d.) set -l FISH_MAJOR (echo $version | cut -f1 -d.)
set -l FISH_MINOR (echo $FISH_VERSION | cut -f2 -d.) set -l FISH_MINOR (echo $version | cut -f2 -d.)
# history's -z flag is needed for multi-line support. # history's -z flag is needed for multi-line support.
# history's -z flag was added in fish 2.4.0, so don't use it for versions # history's -z flag was added in fish 2.4.0, so don't use it for versions

View File

@@ -29,13 +29,22 @@ __fzfcmd() {
fzf-file-widget() { fzf-file-widget() {
LBUFFER="${LBUFFER}$(__fsel)" LBUFFER="${LBUFFER}$(__fsel)"
local ret=$? local ret=$?
zle redisplay zle reset-prompt
typeset -f zle-line-init >/dev/null && zle zle-line-init
return $ret return $ret
} }
zle -N fzf-file-widget zle -N fzf-file-widget
bindkey '^T' fzf-file-widget bindkey '^T' fzf-file-widget
# Ensure precmds are run after cd
fzf-redraw-prompt() {
local precmd
for precmd in $precmd_functions; do
$precmd
done
zle reset-prompt
}
zle -N fzf-redraw-prompt
# ALT-C - cd into the selected directory # ALT-C - cd into the selected directory
fzf-cd-widget() { fzf-cd-widget() {
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \ local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
@@ -48,8 +57,7 @@ fzf-cd-widget() {
fi fi
cd "$dir" cd "$dir"
local ret=$? local ret=$?
zle reset-prompt zle fzf-redraw-prompt
typeset -f zle-line-init >/dev/null && zle zle-line-init
return $ret return $ret
} }
zle -N fzf-cd-widget zle -N fzf-cd-widget
@@ -59,8 +67,8 @@ bindkey '\ec' fzf-cd-widget
fzf-history-widget() { fzf-history-widget() {
local selected num local selected num
setopt localoptions noglobsubst noposixbuiltins pipefail 2> /dev/null setopt localoptions noglobsubst noposixbuiltins pipefail 2> /dev/null
selected=( $(fc -l 1 | selected=( $(fc -rl 1 |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tac -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) ) FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
local ret=$? local ret=$?
if [ -n "$selected" ]; then if [ -n "$selected" ]; then
num=$selected[1] num=$selected[1]
@@ -68,8 +76,7 @@ fzf-history-widget() {
zle vi-fetch-history -n $num zle vi-fetch-history -n $num
fi fi
fi fi
zle redisplay zle reset-prompt
typeset -f zle-line-init >/dev/null && zle zle-line-init
return $ret return $ret
} }
zle -N fzf-history-widget zle -N fzf-history-widget

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.1" version = "0.17.5"
// Core // Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond coordinatorDelayMax time.Duration = 100 * time.Millisecond
@@ -25,6 +25,8 @@ const (
initialDelay = 20 * time.Millisecond initialDelay = 20 * time.Millisecond
initialDelayTac = 100 * time.Millisecond initialDelayTac = 100 * time.Millisecond
spinnerDuration = 200 * time.Millisecond spinnerDuration = 200 * time.Millisecond
previewCancelWait = 500 * time.Millisecond
maxPatternLength = 300
// Matcher // Matcher
numPartitionsMultiplier = 8 numPartitionsMultiplier = 8
@@ -55,11 +57,11 @@ var defaultCommand string
func init() { func init() {
if !util.IsWindows() { if !util.IsWindows() {
defaultCommand = `set -o pipefail; (command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print || command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print) 2> /dev/null | cut -b3-` defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
} 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!)`
} }
} }
@@ -75,6 +77,7 @@ const (
) )
const ( const (
exitCancel = -1
exitOk = 0 exitOk = 0
exitNoMatch = 1 exitNoMatch = 1
exitError = 2 exitError = 2

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,10 +443,18 @@ 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":
chord = tui.SRight chord = tui.SRight
case "left-click":
chord = tui.LeftClick
case "right-click":
chord = tui.RightClick
case "double-click": case "double-click":
chord = tui.DoubleClick chord = tui.DoubleClick
case "f10": case "f10":
@@ -658,8 +683,12 @@ func parseKeymap(keymap map[int][]action, str string) {
appendAction(actAbort) appendAction(actAbort)
case "accept": case "accept":
appendAction(actAccept) appendAction(actAccept)
case "accept-non-empty":
appendAction(actAcceptNonEmpty)
case "print-query": case "print-query":
appendAction(actPrintQuery) appendAction(actPrintQuery)
case "replace-query":
appendAction(actReplaceQuery)
case "backward-char": case "backward-char":
appendAction(actBackwardChar) appendAction(actBackwardChar)
case "backward-delete-char": case "backward-delete-char":
@@ -837,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
@@ -1017,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":
@@ -1136,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

@@ -103,7 +103,7 @@ func (r *Reader) readFromStdin() bool {
} }
func (r *Reader) readFromCommand(shell string, cmd string) bool { func (r *Reader) readFromCommand(shell string, cmd string) bool {
listCommand := util.ExecCommandWith(shell, cmd) listCommand := util.ExecCommandWith(shell, cmd, false)
out, err := listCommand.StdoutPipe() out, err := listCommand.StdoutPipe()
if err != nil { if err != nil {
return false return false

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,8 @@ type Terminal struct {
inlineInfo bool inlineInfo bool
prompt string prompt string
promptLen int promptLen int
reverse bool queryLen [2]int
layout layoutType
fullscreen bool fullscreen bool
hscroll bool hscroll bool
hscrollOff int hscrollOff int
@@ -68,6 +69,7 @@ type Terminal struct {
cx int cx int
cy int cy int
offset int offset int
xoffset int
yanked []rune yanked []rune
input []rune input []rune
multi bool multi bool
@@ -112,6 +114,7 @@ type Terminal struct {
prevLines []itemLine prevLines []itemLine
suppress bool suppress bool
startChan chan bool startChan chan bool
killChan chan int
slab *util.Slab slab *util.Slab
theme *tui.ColorTheme theme *tui.ColorTheme
tui tui.Renderer tui tui.Renderer
@@ -170,6 +173,7 @@ const (
actBeginningOfLine actBeginningOfLine
actAbort actAbort
actAccept actAccept
actAcceptNonEmpty
actBackwardChar actBackwardChar
actBackwardDeleteChar actBackwardDeleteChar
actBackwardWord actBackwardWord
@@ -203,6 +207,7 @@ const (
actJump actJump
actJumpAccept actJumpAccept
actPrintQuery actPrintQuery
actReplaceQuery
actToggleSort actToggleSort
actTogglePreview actTogglePreview
actTogglePreviewWrap actTogglePreviewWrap
@@ -219,6 +224,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 {
@@ -275,9 +286,14 @@ 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)
keymap[tui.LeftClick] = toActions(actIgnore)
keymap[tui.RightClick] = toActions(actToggle)
return keymap return keymap
} }
@@ -289,10 +305,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 {
@@ -350,7 +367,8 @@ 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, queryLen: [2]int{0, 0},
layout: opts.Layout,
fullscreen: fullscreen, fullscreen: fullscreen,
hscroll: opts.Hscroll, hscroll: opts.Hscroll,
hscrollOff: opts.HscrollOff, hscrollOff: opts.HscrollOff,
@@ -359,6 +377,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
cx: len(input), cx: len(input),
cy: 0, cy: 0,
offset: 0, offset: 0,
xoffset: 0,
yanked: []rune{}, yanked: []rune{},
input: input, input: input,
multi: opts.Multi, multi: opts.Multi,
@@ -396,6 +415,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
slab: util.MakeSlab(slab16Size, slab32Size), slab: util.MakeSlab(slab16Size, slab32Size),
theme: opts.Theme, theme: opts.Theme,
startChan: make(chan bool, 1), startChan: make(chan bool, 1),
killChan: make(chan int),
tui: renderer, tui: renderer,
initFunc: func() { renderer.Init() }} initFunc: func() { renderer.Init() }}
t.prompt, t.promptLen = t.processTabs([]rune(opts.Prompt), 0) t.prompt, t.promptLen = t.processTabs([]rune(opts.Prompt), 0)
@@ -626,12 +646,24 @@ func (t *Terminal) resizeWindows() {
for i := 0; i < t.window.Height(); i++ { for i := 0; i < t.window.Height(); i++ {
t.window.MoveAndClear(i, 0) t.window.MoveAndClear(i, 0)
} }
t.truncateQuery()
} }
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 {
@@ -641,20 +673,44 @@ func (t *Terminal) move(y int, x int, clear bool) {
} }
} }
func (t *Terminal) truncateQuery() {
t.input, _ = t.trimRight(t.input, maxPatternLength)
t.cx = util.Constrain(t.cx, 0, len(t.input))
}
func (t *Terminal) updatePromptOffset() ([]rune, []rune) {
maxWidth := util.Max(1, t.window.Width()-t.promptLen-1)
_, overflow := t.trimLeft(t.input[:t.cx], maxWidth)
minOffset := int(overflow)
maxOffset := util.Min(util.Min(len(t.input), minOffset+maxWidth), t.cx)
t.xoffset = util.Constrain(t.xoffset, minOffset, maxOffset)
before, _ := t.trimLeft(t.input[t.xoffset:t.cx], maxWidth)
beforeLen := t.displayWidth(before)
after, _ := t.trimRight(t.input[t.cx:], maxWidth-beforeLen)
afterLen := t.displayWidth(after)
t.queryLen = [2]int{beforeLen, afterLen}
return before, after
}
func (t *Terminal) placeCursor() { func (t *Terminal) placeCursor() {
t.move(0, t.promptLen+t.displayWidth(t.input[:t.cx]), false) t.move(0, t.promptLen+t.queryLen[0], false)
} }
func (t *Terminal) printPrompt() { func (t *Terminal) printPrompt() {
t.move(0, 0, true) t.move(0, 0, true)
t.window.CPrint(tui.ColPrompt, t.strong, t.prompt) t.window.CPrint(tui.ColPrompt, t.strong, t.prompt)
t.window.CPrint(tui.ColNormal, t.strong, string(t.input))
before, after := t.updatePromptOffset()
t.window.CPrint(tui.ColNormal, t.strong, string(before))
t.window.CPrint(tui.ColNormal, t.strong, string(after))
} }
func (t *Terminal) printInfo() { func (t *Terminal) printInfo() {
pos := 0 pos := 0
if t.inlineInfo { if t.inlineInfo {
pos = t.promptLen + t.displayWidth(t.input) + 1 pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
if pos+len(" < ") > t.window.Width() { if pos+len(" < ") > t.window.Width() {
return return
} }
@@ -691,7 +747,11 @@ func (t *Terminal) printInfo() {
output += fmt.Sprintf(" (%d%%)", t.progress) output += fmt.Sprintf(" (%d%%)", t.progress)
} }
if !t.success && t.count == 0 { if !t.success && t.count == 0 {
output += " [ERROR]" if len(os.Getenv("FZF_DEFAULT_COMMAND")) > 0 {
output = "[$FZF_DEFAULT_COMMAND failed]"
} else {
output = "[default command failed - $FZF_DEFAULT_COMMAND required]"
}
} }
if pos+len(output) <= t.window.Width() { if pos+len(output) <= t.window.Width() {
t.window.CPrint(tui.ColInfo, 0, output) t.window.CPrint(tui.ColInfo, 0, output)
@@ -731,7 +791,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)
@@ -1119,16 +1179,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] == '\\' { if match[0] == '\\' {
continue // Escaped placeholder pattern
return true, match[1:], flags
} }
if match[1] == '+' {
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 {
@@ -1141,9 +1231,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
@@ -1151,13 +1242,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
} }
@@ -1193,7 +1279,9 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
str = str[:delims[len(delims)-1][0]] str = str[:delims[len(delims)-1][0]]
} }
} }
if !flags.preserveSpace {
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
}
replacements[idx] = quoteEntry(str) replacements[idx] = quoteEntry(str)
} }
return strings.Join(replacements, " ") return strings.Join(replacements, " ")
@@ -1212,7 +1300,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
return return
} }
command := replacePlaceholder(template, t.ansi, t.delimiter, forcePlus, string(t.input), list) command := replacePlaceholder(template, t.ansi, t.delimiter, forcePlus, string(t.input), list)
cmd := util.ExecCommand(command) cmd := util.ExecCommand(command, false)
if !background { if !background {
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
@@ -1223,7 +1311,9 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
t.redraw() t.redraw()
t.refresh() t.refresh()
} else { } else {
t.tui.Pause(false)
cmd.Run() cmd.Run()
t.tui.Resume(false)
} }
} }
@@ -1249,21 +1339,30 @@ 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)
// 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 sels[0] = current
for i, sel := range t.sortSelected() { for i, sel := range t.sortSelected() {
sels[i+1] = sel.item sels[i+1] = sel.item
} }
return true, sels
} }
return true, sels
func (t *Terminal) truncateQuery() {
maxPatternLength := util.Max(1, t.window.Width()-t.promptLen-1)
t.input, _ = t.trimRight(t.input, maxPatternLength)
t.cx = util.Constrain(t.cx, 0, len(t.input))
} }
func (t *Terminal) selectItem(item *Item) { func (t *Terminal) selectItem(item *Item) {
@@ -1284,6 +1383,20 @@ func (t *Terminal) toggleItem(item *Item) {
} }
} }
func (t *Terminal) killPreview(code int) {
select {
case t.killChan <- code:
default:
if code != exitCancel {
os.Exit(code)
}
}
}
func (t *Terminal) cancelPreview() {
t.killPreview(exitCancel)
}
// Loop is called to start Terminal I/O // Loop is called to start Terminal I/O
func (t *Terminal) Loop() { func (t *Terminal) Loop() {
// prof := profile.Start(profile.ProfilePath("/tmp/")) // prof := profile.Start(profile.ProfilePath("/tmp/"))
@@ -1318,10 +1431,10 @@ func (t *Terminal) Loop() {
t.initFunc() t.initFunc()
t.resizeWindows() t.resizeWindows()
t.printPrompt() t.printPrompt()
t.placeCursor()
t.refresh()
t.printInfo() t.printInfo()
t.printHeader() t.printHeader()
t.placeCursor()
t.refresh()
t.mutex.Unlock() t.mutex.Unlock()
go func() { go func() {
timer := time.NewTimer(t.initDelay) timer := time.NewTimer(t.initDelay)
@@ -1361,9 +1474,43 @@ func (t *Terminal) Loop() {
if request[0] != nil { if request[0] != nil {
command := replacePlaceholder(t.preview.command, command := replacePlaceholder(t.preview.command,
t.ansi, t.delimiter, false, string(t.input), request) t.ansi, t.delimiter, false, string(t.input), request)
cmd := util.ExecCommand(command) cmd := util.ExecCommand(command, true)
out, _ := cmd.CombinedOutput() if t.pwindow != nil {
t.reqBox.Set(reqPreviewDisplay, string(out)) env := os.Environ()
env = append(env, fmt.Sprintf("LINES=%d", t.pwindow.Height()))
env = append(env, fmt.Sprintf("COLUMNS=%d", t.pwindow.Width()))
cmd.Env = env
}
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &out
cmd.Start()
finishChan := make(chan bool, 1)
updateChan := make(chan bool)
go func() {
select {
case code := <-t.killChan:
if code != exitCancel {
util.KillCommand(cmd)
os.Exit(code)
} else {
select {
case <-time.After(previewCancelWait):
util.KillCommand(cmd)
updateChan <- true
case <-finishChan:
updateChan <- false
}
}
case <-finishChan:
updateChan <- false
}
}()
cmd.Wait()
finishChan <- true
if out.Len() > 0 || !<-updateChan {
t.reqBox.Set(reqPreviewDisplay, string(out.Bytes()))
}
} else { } else {
t.reqBox.Set(reqPreviewDisplay, "") t.reqBox.Set(reqPreviewDisplay, "")
} }
@@ -1381,7 +1528,7 @@ func (t *Terminal) Loop() {
t.history.append(string(t.input)) t.history.append(string(t.input))
} }
// prof.Stop() // prof.Stop()
os.Exit(code) t.killPreview(code)
} }
go func() { go func() {
@@ -1408,6 +1555,7 @@ func (t *Terminal) Loop() {
focused = currentFocus focused = currentFocus
if t.isPreviewEnabled() { if t.isPreviewEnabled() {
_, list := t.buildPlusList(t.preview.command, false) _, list := t.buildPlusList(t.preview.command, false)
t.cancelPreview()
t.previewBox.Set(reqPreviewEnqueue, list) t.previewBox.Set(reqPreviewEnqueue, list)
} }
} }
@@ -1449,9 +1597,9 @@ func (t *Terminal) Loop() {
} }
} }
t.placeCursor() t.placeCursor()
t.refresh()
t.mutex.Unlock() t.mutex.Unlock()
}) })
t.refresh()
} }
}() }()
@@ -1517,6 +1665,7 @@ func (t *Terminal) Loop() {
if t.previewer.enabled { if t.previewer.enabled {
valid, list := t.buildPlusList(t.preview.command, false) valid, list := t.buildPlusList(t.preview.command, false)
if valid { if valid {
t.cancelPreview()
t.previewBox.Set(reqPreviewEnqueue, list) t.previewBox.Set(reqPreviewEnqueue, list)
} }
} }
@@ -1556,6 +1705,11 @@ func (t *Terminal) Loop() {
} }
case actPrintQuery: case actPrintQuery:
req(reqPrintQuery) req(reqPrintQuery)
case actReplaceQuery:
if t.cy >= 0 && t.cy < t.merger.Length() {
t.input = t.merger.Get(t.cy).item.text.ToRunes()
t.cx = len(t.input)
}
case actAbort: case actAbort:
req(reqQuit) req(reqQuit)
case actDeleteChar: case actDeleteChar:
@@ -1609,12 +1763,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)
@@ -1638,6 +1792,10 @@ func (t *Terminal) Loop() {
req(reqList) req(reqList)
case actAccept: case actAccept:
req(reqClose) req(reqClose)
case actAcceptNonEmpty:
if len(t.selected) > 0 || t.merger.Length() > 0 || !t.reading && t.count == 0 {
req(reqClose)
}
case actClearScreen: case actClearScreen:
req(reqRedraw) req(reqRedraw)
case actTop: case actTop:
@@ -1738,13 +1896,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 {
@@ -1755,13 +1921,17 @@ func (t *Terminal) Loop() {
} else if me.Down { } else if me.Down {
if my == 0 && mx >= 0 { if my == 0 && mx >= 0 {
// Prompt // Prompt
t.cx = mx t.cx = mx + t.xoffset
} else if my >= min { } else if my >= min {
// List // List
if t.vset(t.offset+my-min) && t.multi && me.Mod { if t.vset(t.offset+my-min) && t.multi && me.Mod {
toggle() toggle()
} }
req(reqList) req(reqList)
if me.Left {
return doActions(t.keymap[tui.LeftClick], tui.LeftClick)
}
return doActions(t.keymap[tui.RightClick], tui.RightClick)
} }
} }
} }
@@ -1803,6 +1973,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 {
@@ -1827,7 +2003,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

@@ -27,11 +27,17 @@ const (
const consoleDevice string = "/dev/tty" const consoleDevice string = "/dev/tty"
var offsetRegexp *regexp.Regexp = regexp.MustCompile("\x1b\\[([0-9]+);([0-9]+)R") 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,12 +54,14 @@ 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 {
runes = append(runes, ' ')
} else { } else {
runes = append(runes, r) runes = append(runes, r)
} }
}
bytes = bytes[sz:] bytes = bytes[sz:]
} }
r.queued += string(runes) r.queued += string(runes)
@@ -146,8 +154,10 @@ func (r *LightRenderer) findOffset() (row int, col int) {
for tries := 0; tries < offsetPollTries; tries++ { for tries := 0; tries < offsetPollTries; tries++ {
bytes = r.getBytesInternal(bytes, tries > 0) bytes = r.getBytesInternal(bytes, tries > 0)
offsets := offsetRegexp.FindSubmatch(bytes) offsets := offsetRegexp.FindSubmatch(bytes)
if len(offsets) > 2 { if len(offsets) > 3 {
return atoi(string(offsets[1]), 0) - 1, atoi(string(offsets[2]), 0) - 1 // add anything we skipped over to the input buffer
r.buffer = append(r.buffer, offsets[1]...)
return atoi(string(offsets[2]), 0) - 1, atoi(string(offsets[3]), 0) - 1
} }
} }
return -1, -1 return -1, -1
@@ -289,6 +299,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
} }
buffer = append(buffer, byte(c)) buffer = append(buffer, byte(c))
pc := c
for { for {
c, ok = r.getch(true) c, ok = r.getch(true)
if !ok { if !ok {
@@ -298,9 +309,13 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
continue continue
} }
break break
} } else if c == ESC && pc != c {
retries = r.escDelay / escPollInterval
} else {
retries = 0 retries = 0
}
buffer = append(buffer, byte(c)) buffer = append(buffer, byte(c))
pc = c
} }
return buffer return buffer
@@ -360,7 +375,14 @@ 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 ESC:
return Event{ESC, 0, nil}
case 32: case 32:
return Event{AltSpace, 0, nil} return Event{AltSpace, 0, nil}
case 47: case 47:
@@ -380,12 +402,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 +493,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]
@@ -495,16 +527,19 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
} }
*sz = 6 *sz = 6
switch r.buffer[3] { switch r.buffer[3] {
case 32, 36, 40, 48, // mouse-down / shift / cmd / ctrl case 32, 34, 36, 40, 48, // mouse-down / shift / cmd / ctrl
35, 39, 43, 51: // mouse-up / shift / cmd / ctrl 35, 39, 43, 51: // mouse-up / shift / cmd / ctrl
mod := r.buffer[3] >= 36 mod := r.buffer[3] >= 36
left := r.buffer[3] == 32
down := r.buffer[3]%2 == 0 down := r.buffer[3]%2 == 0
x := int(r.buffer[4] - 33) x := int(r.buffer[4] - 33)
y := int(r.buffer[5]-33) - r.yoffset y := int(r.buffer[5]-33) - r.yoffset
double := false double := false
if down { if down {
now := time.Now() now := time.Now()
if now.Sub(r.prevDownTime) < doubleClickDuration { if !left { // Right double click is not allowed
r.clickY = []int{}
} else if now.Sub(r.prevDownTime) < doubleClickDuration {
r.clickY = append(r.clickY, y) r.clickY = append(r.clickY, y)
} else { } else {
r.clickY = []int{y} r.clickY = []int{y}
@@ -517,14 +552,14 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
} }
} }
return Event{Mouse, 0, &MouseEvent{y, x, 0, down, double, mod}} return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
case 96, 100, 104, 112, // scroll-up / shift / cmd / ctrl case 96, 100, 104, 112, // scroll-up / shift / cmd / ctrl
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl 97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
mod := r.buffer[3] >= 100 mod := r.buffer[3] >= 100
s := 1 - int(r.buffer[3]%2)*2 s := 1 - int(r.buffer[3]%2)*2
x := int(r.buffer[4] - 33) x := int(r.buffer[4] - 33)
y := int(r.buffer[5]-33) - r.yoffset y := int(r.buffer[5]-33) - r.yoffset
return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, mod}} return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, false, mod}}
} }
return Event{Invalid, 0, nil} return Event{Invalid, 0, nil}
} }
@@ -789,7 +824,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

@@ -193,19 +193,22 @@ func (r *FullscreenRenderer) GetChar() Event {
button := ev.Buttons() button := ev.Buttons()
mod := ev.Modifiers() != 0 mod := ev.Modifiers() != 0
if button&tcell.WheelDown != 0 { if button&tcell.WheelDown != 0 {
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, mod}} return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, false, mod}}
} else if button&tcell.WheelUp != 0 { } else if button&tcell.WheelUp != 0 {
return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, mod}} return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, false, mod}}
} else if runtime.GOOS != "windows" { } else if runtime.GOOS != "windows" {
// double and single taps on Windows don't quite work due to // double and single taps on Windows don't quite work due to
// the console acting on the events and not allowing us // the console acting on the events and not allowing us
// to consume them. // to consume them.
down := button&tcell.Button1 != 0 // left left := button&tcell.Button1 != 0
down := left || button&tcell.Button3 != 0
double := false double := false
if down { if down {
now := time.Now() now := time.Now()
if now.Sub(r.prevDownTime) < doubleClickDuration { if !left {
r.clickY = []int{}
} else if now.Sub(r.prevDownTime) < doubleClickDuration {
r.clickY = append(r.clickY, x) r.clickY = append(r.clickY, x)
} else { } else {
r.clickY = []int{x} r.clickY = []int{x}
@@ -218,7 +221,7 @@ func (r *FullscreenRenderer) GetChar() Event {
} }
} }
return Event{Mouse, 0, &MouseEvent{y, x, 0, down, double, mod}} return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
} }
// process keyboard: // process keyboard:
@@ -292,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:
@@ -367,13 +382,17 @@ func (r *FullscreenRenderer) GetChar() Event {
return Event{Invalid, 0, nil} return Event{Invalid, 0, nil}
} }
func (r *FullscreenRenderer) Pause(bool) { func (r *FullscreenRenderer) Pause(clear bool) {
if clear {
_screen.Fini() _screen.Fini()
} }
}
func (r *FullscreenRenderer) Resume(bool) { func (r *FullscreenRenderer) Resume(clear bool) {
if clear {
r.initScreen() r.initScreen()
} }
}
func (r *FullscreenRenderer) Close() { func (r *FullscreenRenderer) Close() {
_screen.Fini() _screen.Fini()

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

@@ -44,6 +44,8 @@ const (
Resize Resize
Mouse Mouse
DoubleClick DoubleClick
LeftClick
RightClick
BTab BTab
BSpace BSpace
@@ -59,6 +61,8 @@ const (
Home Home
End End
SUp
SDown
SLeft SLeft
SRight SRight
@@ -81,6 +85,11 @@ const (
AltSlash AltSlash
AltBS AltBS
AltUp
AltDown
AltLeft
AltRight
Alt0 Alt0
) )
@@ -185,6 +194,7 @@ type MouseEvent struct {
Y int Y int
X int X int
S int S int
Left bool
Down bool Down bool
Double bool Double bool
Mod bool Mod bool

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,12 +17,13 @@ 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
}
w := runewidth.RuneWidth(r)
_runeWidths[r] = w _runeWidths[r] = w
return w return w
} }
}
// Max returns the largest integer // Max returns the largest integer
func Max(first int, second int) int { func Max(first int, second int) int {

View File

@@ -9,17 +9,26 @@ import (
) )
// ExecCommand executes the given command with $SHELL // ExecCommand executes the given command with $SHELL
func ExecCommand(command string) *exec.Cmd { func ExecCommand(command string, setpgid bool) *exec.Cmd {
shell := os.Getenv("SHELL") shell := os.Getenv("SHELL")
if len(shell) == 0 { if len(shell) == 0 {
shell = "sh" shell = "sh"
} }
return ExecCommandWith(shell, command) return ExecCommandWith(shell, command, setpgid)
} }
// ExecCommandWith executes the given command with the specified shell // ExecCommandWith executes the given command with the specified shell
func ExecCommandWith(shell string, command string) *exec.Cmd { func ExecCommandWith(shell string, command string, setpgid bool) *exec.Cmd {
return exec.Command(shell, "-c", command) cmd := exec.Command(shell, "-c", command)
if setpgid {
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
}
return cmd
}
// KillCommand kills the process for the given command
func KillCommand(cmd *exec.Cmd) error {
return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
} }
// IsWindows returns true on Windows // IsWindows returns true on Windows
@@ -27,7 +36,7 @@ func IsWindows() bool {
return false return false
} }
// SetNonBlock executes syscall.SetNonblock on file descriptor // SetNonblock executes syscall.SetNonblock on file descriptor
func SetNonblock(file *os.File, nonblock bool) { func SetNonblock(file *os.File, nonblock bool) {
syscall.SetNonblock(int(file.Fd()), nonblock) syscall.SetNonblock(int(file.Fd()), nonblock)
} }

View File

@@ -10,28 +10,35 @@ import (
) )
// ExecCommand executes the given command with cmd // ExecCommand executes the given command with cmd
func ExecCommand(command string) *exec.Cmd { func ExecCommand(command string, setpgid bool) *exec.Cmd {
return ExecCommandWith("cmd", command) return ExecCommandWith("cmd", command, setpgid)
} }
// ExecCommandWith executes the given command with cmd. _shell parameter is // ExecCommandWith executes the given command with cmd. _shell parameter is
// ignored on Windows. // ignored on Windows.
func ExecCommandWith(_shell string, command string) *exec.Cmd { // FIXME: setpgid is unused. We set it in the Unix implementation so that we
// can kill preview process with its child processes at once.
func ExecCommandWith(_shell string, command string, setpgid bool) *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
} }
// KillCommand kills the process for the given command
func KillCommand(cmd *exec.Cmd) error {
return cmd.Process.Kill()
}
// IsWindows returns true on Windows // IsWindows returns true on Windows
func IsWindows() bool { func IsWindows() bool {
return true return true
} }
// SetNonBlock executes syscall.SetNonblock on file descriptor // SetNonblock executes syscall.SetNonblock on file descriptor
func SetNonblock(file *os.File, nonblock bool) { func SetNonblock(file *os.File, nonblock bool) {
syscall.SetNonblock(syscall.Handle(file.Fd()), nonblock) syscall.SetNonblock(syscall.Handle(file.Fd()), nonblock)
} }

View File

@@ -8,10 +8,13 @@ Execute (fzf#run with dir option):
let cwd = getcwd() let cwd = getcwd()
let result = fzf#run({ 'source': 'git ls-files', 'options': '--filter=vdr', 'dir': g:dir }) let result = fzf#run({ 'source': 'git ls-files', 'options': '--filter=vdr', 'dir': g:dir })
AssertEqual ['fzf.vader'], result AssertEqual ['fzf.vader'], result
AssertEqual 0, haslocaldir()
AssertEqual getcwd(), cwd AssertEqual getcwd(), cwd
execute 'lcd' fnameescape(cwd)
let result = sort(fzf#run({ 'source': 'git ls-files', 'options': '--filter e', 'dir': g:dir })) let result = sort(fzf#run({ 'source': 'git ls-files', 'options': '--filter e', 'dir': g:dir }))
AssertEqual ['fzf.vader', 'test_go.rb'], result AssertEqual ['fzf.vader', 'test_go.rb'], result
AssertEqual 1, haslocaldir()
AssertEqual getcwd(), cwd AssertEqual getcwd(), cwd
Execute (fzf#run with Funcref command): Execute (fzf#run with Funcref command):
@@ -56,11 +59,11 @@ Execute (Incomplete fzf#run with dir option and autochdir):
" No change in working directory even if &acd is set " No change in working directory even if &acd is set
AssertEqual cwd, getcwd() AssertEqual cwd, getcwd()
Execute (fzf#run with dir option and autochdir): Execute (FIXME: fzf#run with dir option and autochdir):
set acd set acd
let cwd = getcwd()
call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'}) call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'})
" Working directory changed due to &acd " Working directory changed due to &acd
AssertEqual '/foobar', expand('%')
AssertEqual '/', getcwd() AssertEqual '/', getcwd()
Execute (fzf#run with dir option and autochdir when final cwd is same as dir): Execute (fzf#run with dir option and autochdir when final cwd is same as dir):

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

@@ -277,7 +277,7 @@ class TestGoFZF < TestBase
def test_fzf_default_command_failure def test_fzf_default_command_failure
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', 'FZF_DEFAULT_COMMAND=false'), :Enter tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', 'FZF_DEFAULT_COMMAND=false'), :Enter
tmux.until { |lines| lines[-2].include?('ERROR') } tmux.until { |lines| lines[-2].include?('FZF_DEFAULT_COMMAND failed') }
tmux.send_keys :Enter tmux.send_keys :Enter
end end
@@ -769,6 +769,15 @@ class TestGoFZF < TestBase
assert_equal %w[print-my-query], readonce.split($INPUT_RECORD_SEPARATOR) assert_equal %w[print-my-query], readonce.split($INPUT_RECORD_SEPARATOR)
end end
def test_bind_replace_query
tmux.send_keys "seq 1 1000 | #{fzf '--print-query --bind=ctrl-j:replace-query'}", :Enter
tmux.send_keys '1'
tmux.until { |lines| lines[-2].end_with? '272/1000' }
tmux.send_keys 'C-k', 'C-j'
tmux.until { |lines| lines[-2].end_with? '29/1000' }
tmux.until { |lines| lines[-1].end_with? '> 10' }
end
def test_long_line def test_long_line
data = '.' * 256 * 1024 data = '.' * 256 * 1024
File.open(tempname, 'w') do |f| File.open(tempname, 'w') do |f|
@@ -1051,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|
@@ -1078,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
@@ -1087,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
@@ -1111,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') }
@@ -1136,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"]
{ {
@@ -1319,12 +1371,12 @@ class TestGoFZF < TestBase
end end
def test_preview_hidden def test_preview_hidden
tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-\\$LINES-\\$COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter
tmux.until { |lines| lines[-1] == '>' } tmux.until { |lines| lines[-1] == '>' }
tmux.send_keys '?' tmux.send_keys '?'
tmux.until { |lines| lines[-2].include?(' {1-1}') } tmux.until { |lines| lines[-2] =~ / {1-1-1-[0-9]+}/ }
tmux.send_keys '555' tmux.send_keys '555'
tmux.until { |lines| lines[-2].include?(' {555-555}') } tmux.until { |lines| lines[-2] =~ / {555-555-1-[0-9]+}/ }
tmux.send_keys '?' tmux.send_keys '?'
tmux.until { |lines| lines[-1] == '> 555' } tmux.until { |lines| lines[-1] == '> 555' }
end end
@@ -1346,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'
@@ -1369,6 +1450,42 @@ class TestGoFZF < TestBase
tmux.send_keys :Enter tmux.send_keys :Enter
end end
def test_accept_non_empty
tmux.send_keys %(seq 1000 | #{fzf '--print-query --bind enter:accept-non-empty'}), :Enter
tmux.until { |lines| lines.match_count == 1000 }
tmux.send_keys 'foo'
tmux.until { |lines| lines[-2].include? '0/1000' }
# fzf doesn't exit since there's no selection
tmux.send_keys :Enter
tmux.until { |lines| lines[-2].include? '0/1000' }
tmux.send_keys 'C-u'
tmux.until { |lines| lines[-2].include? '1000/1000' }
tmux.send_keys '999'
tmux.until { |lines| lines[-2].include? '1/1000' }
tmux.send_keys :Enter
assert_equal %w[999 999], readonce.split($INPUT_RECORD_SEPARATOR)
end
def test_accept_non_empty_with_multi_selection
tmux.send_keys %(seq 1000 | #{fzf '-m --print-query --bind enter:accept-non-empty'}), :Enter
tmux.until { |lines| lines.match_count == 1000 }
tmux.send_keys :Tab
tmux.until { |lines| lines[-2].include? '1000/1000 (1)' }
tmux.send_keys 'foo'
tmux.until { |lines| lines[-2].include? '0/1000' }
# fzf will exit in this case even though there's no match for the current query
tmux.send_keys :Enter
assert_equal %w[foo 1], readonce.split($INPUT_RECORD_SEPARATOR)
end
def test_accept_non_empty_with_empty_list
tmux.send_keys %(: | #{fzf '-q foo --print-query --bind enter:accept-non-empty'}), :Enter
tmux.until { |lines| lines[-2].strip == '0/0' }
tmux.send_keys :Enter
# fzf will exit anyway since input list is empty
assert_equal %w[foo], readonce.split($INPUT_RECORD_SEPARATOR)
end
def test_preview_update_on_select def test_preview_update_on_select
tmux.send_keys(%(seq 10 | fzf -m --preview 'echo {+}' --bind a:toggle-all), tmux.send_keys(%(seq 10 | fzf -m --preview 'echo {+}' --bind a:toggle-all),
:Enter) :Enter)
@@ -1469,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
@@ -1506,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'
@@ -1538,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
@@ -1552,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
@@ -1570,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
@@ -1590,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
@@ -1601,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 }
@@ -1630,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
@@ -1649,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
@@ -1716,6 +1833,23 @@ class TestBash < TestBase
super super
@tmux = Tmux.new :bash @tmux = Tmux.new :bash
end end
def test_dynamic_completion_loader
tmux.paste 'touch /tmp/foo; _fzf_completion_loader=1'
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.match_count.positive? }
tmux.send_keys 'C-c'
tmux.prepare
tmux.send_keys 'fake /tmp/foo'
tmux.send_keys :Tab , 'C-u'
tmux.prepare
tmux.send_keys 'fake /tmp/foo**', :Tab
tmux.until { |lines| lines.match_count.positive? }
end
end end
class TestZsh < TestBase class TestZsh < TestBase

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