m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-14 14:23:47 -05:00

Compare commits

...

109 Commits

Author SHA1 Message Date
Junegunn Choi
9a41fd5327 0.19.0 2019-11-15 22:54:55 +09:00
Junegunn Choi
b471042037 Merge branch 'devel' 2019-11-15 22:53:08 +09:00
Junegunn Choi
2886f06977 Fix --preview-window noborder with non-default background color 2019-11-15 18:27:08 +09:00
Junegunn Choi
d630484eeb Update error message for --preview-window 2019-11-15 17:20:47 +09:00
Junegunn Choi
e24299239e Add --preview-window noborder option to disable preview border
Close #1699
2019-11-15 11:39:51 +09:00
Junegunn Choi
d2fa470165 Add --info=STYLE [default|inline|hidden]
Close #1738
2019-11-15 00:39:29 +09:00
Junegunn Choi
168453da71 More key chords for --bind
Close #1752
2019-11-14 22:39:35 +09:00
Junegunn Choi
23a06d63ac Update CHANGELOG and man pages 2019-11-13 01:27:39 +09:00
Junegunn Choi
751aa1944a Remove trailing whitespaces when using --with-nth 2019-11-12 23:20:09 +09:00
Junegunn Choi
05b5f3f845 'reload' action should reset multi-selection 2019-11-12 00:57:19 +09:00
Marco Hinz
16fc6862a8 [nvim] Handle SIGHUP in exit handler (#1749)
In recent Nvim versions, an "Error running ..." message is shown even for normal
use cases, such as:

    :Files
    <c-\><c-n>
    :close

Closing the window will :bwipeout! the terminal buffer, because fzf sets
bufhiden=wipe.

When deleting the terminal buffer while fzf is still running, Nvim sends SIGHUP.
This happens for quite some time already, but the bug only manifests since this
commit:

  https://github.com/neovim/neovim/commit/939d9053b

It's The Right Thing to do when the application exited due to a signal.

Before that commit, no "Error running ..." message was shown, because 1 (instead
of 128 + 1 == SIGHUP) was returned which the exit handler in fzf.vim treats as
"NO MATCH".
2019-11-12 00:47:05 +09:00
Junegunn Choi
7e1c0f39e7 'reload' action should reset --header-lines 2019-11-12 00:10:24 +09:00
Junegunn Choi
deccf20a35 Fix regression of select-all 2019-11-11 23:31:31 +09:00
Junegunn Choi
73c0a645e0 Remove unnecessary reader barrier on --filter mode 2019-11-11 12:53:03 +09:00
Junegunn Choi
e975bd0c8d Add test cases for --phony and reload action 2019-11-10 13:13:45 +09:00
Junegunn Choi
78da928727 Experimental implementation of "reload" action
# Reload input list with different sources
  seq 10 | fzf --bind 'ctrl-a:reload(seq 100),ctrl-b:reload(seq 1000)'

  # Reload as you type
  seq 10 | fzf --bind 'change:reload:seq {q}' --phony

  # Integration with ripgrep
  RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
  INITIAL_QUERY=""
  FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \
    fzf --bind "change:reload:$RG_PREFIX {q} || true" \
        --ansi --phony --query "$INITIAL_QUERY"

Close #751
Close #965
Close #974
Close #1736
Related #1723
2019-11-10 11:43:37 +09:00
Junegunn Choi
11962dabba Add --phony option for disabling search
With --phony, fzf becomes a simply selector interface without its own
search functionality. The query string is only used for building the
command for preview or execute action.

Close #1723
2019-11-10 11:36:55 +09:00
Junegunn Choi
dceb5d09cd RTFM, please 2019-11-08 14:09:49 +09:00
Alexandr
b4cccf23d4 Improvements to code quality and readability (#1737)
* Remove 1 unused field and 3 unused functions

unused elements fount by running
golangci-lint run --disable-all --enable unused

src/result.go:19:2: field `index` is unused (unused)
        index  int32
        ^
src/tui/light.go:716:23: func `(*LightWindow).stderr` is unused (unused)
func (w *LightWindow) stderr(str string) {
                      ^
src/terminal.go:1015:6: func `numLinesMax` is unused (unused)
func numLinesMax(str string, max int) int {
     ^
src/tui/tui.go:167:20: func `ColorPair.is24` is unused (unused)
func (p ColorPair) is24() bool {
                   ^

* Address warnings from "gosimple" linter

src/options.go:389:83: S1003: should use strings.Contains(str, ",,,") instead (gosimple)
        if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Index(str, ",,,") >= 0 {
                                                                                         ^
src/options.go:630:18: S1007: should use raw string (`...`) with regexp.MustCompile to avoid having to escape twice (gosimple)
        executeRegexp = regexp.MustCompile(
                        ^
src/terminal.go:29:16: S1007: should use raw string (`...`) with regexp.MustCompile to avoid having to escape twice (gosimple)
        placeholder = regexp.MustCompile("\\\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\\+?f?nf?})")
                      ^
src/terminal_test.go:92:10: S1007: should use raw string (`...`) with regexp.MustCompile to avoid having to escape twice (gosimple)
        regex = regexp.MustCompile("\\w+")
                ^

* Address warnings from "staticcheck" linter

src/algo/algo.go:374:2: SA4006: this value of `offset32` is never used (staticcheck)
        offset32, T := alloc32(offset32, slab, N)
        ^
src/algo/algo.go:456:2: SA4006: this value of `offset16` is never used (staticcheck)
        offset16, C := alloc16(offset16, slab, width*M)
        ^
src/tui/tui.go:119:2: SA9004: only the first constant in this group has an explicit type (staticcheck)
        colUndefined Color = -2
        ^
2019-11-05 09:46:51 +09:00
zhaoyunfeng
b911af200c [zsh-completion] Fix prefix extraction when triggers start with ';' 2019-11-03 00:33:30 +09:00
Junegunn Choi
68683c444f Fix argument parser for -m
/cc @tessus
2019-11-02 20:44:21 +09:00
Junegunn Choi
a185593d65 Remove unnecessary map lookup 2019-11-02 19:55:05 +09:00
Junegunn Choi
525040238e Fix behavior of 'deselect-all' to only deselect matches
To make it consistent with select-all and toggle-all.

Close #1364
2019-11-02 19:41:59 +09:00
Junegunn Choi
33f89a08f3 Build with Go 1.13 2019-11-02 14:59:22 +09:00
Junegunn Choi
11645e1fac Fix flaky test case 2019-11-02 14:55:13 +09:00
Junegunn Choi
6390140539 [vim/windows] Use chcp only if sed is in PATH
https://github.com/junegunn/vim-plug/pull/891
2019-11-02 14:35:21 +09:00
Junegunn Choi
072066c49c --multi to take optional argument to limit the number of selection
Close #1718
Related #688
2019-11-02 14:25:12 +09:00
Junegunn Choi
a2e9366c84 Fix flaky test case 2019-11-02 13:15:01 +09:00
Simon Fraser
391669a451 Add 'f' flag for placeholder expression (#1733)
If present the contents of the selection will be placed in a temporary file,
and the filename will be placed into the string instead.
2019-10-27 23:50:12 +09:00
Junegunn Choi
0c6c76e081 [zsh] Suppress global alias expansion in widget functions
Close #1708
2019-10-17 14:51:57 +09:00
stiletto
f1520bdde6 Support building on machines with uname -m == "aarch64" (#1710) 2019-10-11 22:11:06 +09:00
Junegunn Choi
3089880f18 [vim/windows] Fix chcp parsing for the current codepage
https://github.com/junegunn/vim-plug/pull/888
2019-10-08 09:41:22 +09:00
Junegunn Choi
ab11b74be4 [vim] Output of chcp was not parsed correctly
By @gh4w and @janlazo

See 68b31a4a66
2019-09-29 14:53:45 +09:00
Junegunn Choi
a5a97be017 [bash-completion] Properly handle exit event
Related #1704
2019-09-29 14:45:58 +09:00
Junegunn Choi
80b5bc1b68 [vim] Shell-escape --color option generated by fzf#wrap
Fix https://github.com/junegunn/fzf.vim/issues/855
2019-09-09 12:12:42 +09:00
Junegunn Choi
5c7dcaffe8 [bash-completion] _fzf_setup_completion to retain previous options 2019-08-09 11:11:29 +09:00
Junegunn Choi
5095899245 [bash-completion] Add _fzf_setup_completion to enable fuzzy completion
While we can attach `_fzf_path_completion` or `_fzf_dir_completion` to
any command using the standard bash complete command, the functionality
of the existing completion function is lost.

Use _fzf_setup_completion if you want to extend the existing function
with fuzzy completion instead of completely replacing it.

e.g. _fzf_setup_completion path kubectl
2019-08-08 15:35:52 +09:00
Ross Smith II
4800e5d2ae Add scoop mention (#1646) 2019-08-06 14:11:28 +09:00
Junegunn Choi
3b1e37f718 Fix #1657: alt-0 to alt-9 2019-08-06 14:06:58 +09:00
Christian Muehlhaeuser
6577388250 os.Kill signal cannot be trapped (#1641) 2019-07-19 13:24:46 +09:00
Christian Muehlhaeuser
3b9dbd4146 Code cleanup: remove unnecessary string conversions (#1642) 2019-07-19 13:23:18 +09:00
Christian Muehlhaeuser
a1260feeed Code cleanup (#1640)
- Replaced time.Now().Sub() with time.Since()
- Replaced unnecessary string/byte slice conversions
- Removed obsolete return and value assignment in range loop
2019-07-19 13:22:35 +09:00
Dan Čermák
7322504ad0 Add installation instructions for openSUSE (#1631) 2019-07-17 11:19:13 +09:00
miclill
de569f0052 Add Debian install instructions (#1620) 2019-07-17 11:18:07 +09:00
ssjhv
e7097a9d25 [fish] Remove perl from fish key bindings (#1635)
Perl was used to remove the trailing newline character, but fzf already
has --print0 to use null character as terminators, and fish read -z is
expecting null character as terminators. There is no reason to depend on
perl if --print0 is passed to fzf invocation.
2019-07-13 14:47:51 +09:00
charlton1
c1dbc800e5 [vim] Fix name-based colors for GVim/8.0 w/o builtin terminal (#1634)
(i.e. spawn xterm)
2019-07-09 11:08:36 +09:00
Junegunn Choi
951746297e Fix invalid layout example 2019-06-08 23:29:04 +09:00
Junegunn Choi
984304568d Remove outdated GVim instruction
The section is no longer relevant since (G)Vim 8 or above has builtin
terminal emulator.
2019-06-08 23:22:05 +09:00
Junegunn Choi
723217bdea Add fzf#run tutorial to README-VIM.md 2019-06-08 23:17:30 +09:00
Mateusz Piotrowski
0fdb71f7e4 Add FreeBSD installation instructions (#1569) 2019-06-05 09:26:04 +09:00
Junegunn Choi
12ce76b56a [bash] Make sure to execute builtin history
Fix #1592
2019-06-03 18:46:01 +09:00
Michael Kelley
0030d18448 Update sys module to newer version (#1582)
A newer version of the sys module is needed for pull request #1341
2019-05-26 11:06:46 +09:00
Junegunn Choi
0e3e6ac442 Disallow preview scroll when the content just fits the window 2019-05-21 16:35:45 +09:00
Dominik Reller
430e8193e0 [install] Remove unused variable in install script (#1571) 2019-05-07 11:34:55 +09:00
Jesus Briales
03e8ed4d88 [bash-completion] Fix custom completion with dynamic loader enabled for non-standard command names (#1564)
Related to #1170.

Fix the solution for commands with non-standard names
where `$cmd` and `$orig_cmd` differ. e.g. `s.foo` -> `s_foo`
2019-05-01 02:35:51 +09:00
Junegunn Choi
ef492f6178 Output --help message to standard output
Close #1554
2019-04-21 18:02:34 +09:00
Alexey Samoshkin
8eea45ef50 Add demo screencast video to README (#1557) 2019-04-18 18:49:30 +09:00
Junegunn Choi
ff951341c9 0.18.0 2019-03-31 11:22:38 +09:00
Junegunn Choi
df570afd52 [docker] Fix gem install option in Dockerfile 2019-03-31 11:22:12 +09:00
Junegunn Choi
07d755df11 Fix regression of prompt display 2019-03-30 02:07:09 +09:00
Junegunn Choi
37585bd5a5 Disable preview scroll if the content fits on the screen
Close #1540
2019-03-29 18:28:45 +09:00
Junegunn Choi
89e24bf8f2 Fix ineffective break statement 2019-03-29 17:29:24 +09:00
Junegunn Choi
8d2fcd3518 Avoid unnecessary redraw of the preview window 2019-03-29 15:12:46 +09:00
Junegunn Choi
f39ab3875e Redraw prompt only when necessary 2019-03-29 15:02:31 +09:00
AnakinXL
82efe6c60d [doc] Add bat for preview syntax highlighting example (#1538)
Similar to this PR from fzf.vim:
https://github.com/junegunn/fzf.vim/pull/712
2019-03-29 10:31:17 +09:00
Junegunn Choi
75972d59a8 Add --no-unicode option to draw borders in ASCII characters
Close ##1533
2019-03-29 02:11:03 +09:00
Junegunn Choi
e7d60aac9c [vim] Do not restore cwd when autochdir is set and buffer changed
Close #1539
2019-03-28 13:57:04 +09:00
Junegunn Choi
a0bfbdd49c [vim] Increase window height by 2 when --border is set
Close #1535
2019-03-26 16:42:35 +09:00
Junegunn Choi
ba594982f0 Add MacPorts instruction
Close #1521
2019-03-18 18:45:57 +09:00
Junegunn Choi
2157f4f193 Add color option for gutter
fzf --color gutter:-1

Close #1529
Close #1468
2019-03-17 15:52:38 +09:00
Junegunn Choi
309bae423c [zsh-completion] Suppress "no matches found" message 2019-03-14 01:10:51 +09:00
Junegunn Choi
4f8bf2ae78 [install] Avoid generating empty component in $PATH
Fix #1517
2019-03-08 12:38:36 +09:00
Junegunn Choi
85c1f8a9e0 Always prepend ANSI reset code before re-assembling tokens 2019-03-07 15:29:57 +09:00
Junegunn Choi
e00e7e1e56 Remove unnecessary ANSI code injection 2019-03-07 02:02:02 +09:00
Junegunn Choi
1a6defdbcc Use simple string concatenation instead of using fmt.Sprintf 2019-03-07 02:00:31 +09:00
Junegunn Choi
ef577a6509 Preserve the original color of each token when using --with-nth with --ansi
Close #1500
2019-03-06 19:05:05 +09:00
Junegunn Choi
b7c6838e45 [install] Fix symlink log
Related #1466
2019-03-05 14:45:29 +09:00
Junegunn Choi
91d04cec5c [install] Print better error message when fzf --version failed
Related #1466
2019-03-05 14:44:29 +09:00
Rui Coelho
3bd8441079 [completion] Look up on ~/.ssh/config.d/* files when doing ssh host complete (#1420) 2019-02-28 16:40:41 +09:00
Junegunn Choi
8cf45a5197 [shell] Skip loading completion code on non-interactive shell
This change is not required if you use the install script to generate
~/.fzf.bash or ~/.fzf.zsh which already has the proper guard statement.

Close #1474
2019-02-28 16:13:59 +09:00
Junegunn Choi
8dc1377efb Export FZF_PREVIEW_LINES and FZF_PREVIEW_COLUMNS to preview process
fzf will still override LINES and COLUMNS as before but they may not
hold the correct values depending on the default shell.

Close #1314
2019-02-22 14:36:30 +09:00
Junegunn Choi
6c32148f90 Add placeholder expression for zero-based item index: {n} and {+n}
Close #1482
2019-02-19 01:12:57 +09:00
Junegunn Choi
315e568de0 Update build instruction
Close #1485
2019-01-31 18:43:10 +09:00
Junegunn Choi
5d16b28869 Fix tab width after ANSI reset code in preview window
Close #1423
2018-12-22 11:52:18 +09:00
Junegunn Choi
5624a89231 Inverse-only matches should not reorder the remaining results
Fix #1458
2018-12-19 23:05:29 +09:00
Junegunn Choi
63c42b14f2 Remove trailing spaces in Makefile 2018-12-13 15:17:30 +09:00
Stefan Tatschner
6f1eaa9b39 Use go modules and simplify build (#1444)
* Update .travis.yml and use stages

This updates the .travis.yml configuration to use separate stages for
unittests and CLI tests. The output is now clearer, since for unittests
and CLI tests separate web pages are available.

* Use go modules and simplify build
2018-12-13 14:36:15 +09:00
Junegunn Choi
ca42e5e00a Avoid unnecessary redraw of preview window
Close #1455
2018-12-13 10:58:57 +09:00
Junegunn Choi
61feee690c Render preview window when the initial query fails to match
Only if preview template contains {q}

Fix #1452
Related #1307
2018-12-05 18:45:55 +09:00
Christian Muehlhaeuser
d4ed955aee Typo & grammar fixes in README (#1413) 2018-11-09 16:50:16 +09:00
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
47 changed files with 1864 additions and 852 deletions

View File

@@ -1,30 +1,22 @@
<!-- ISSUES NOT FOLLOWING THIS TEMPLATE WILL BE CLOSED AND DELETED -->
<!-- Check all that apply [x] -->
- Category
- [ ] fzf binary
- [ ] fzf-tmux script
- [ ] Key bindings
- [ ] Completion
- [ ] Vim
- [ ] Neovim
- [ ] Etc.
- [ ] I have read through the manual page (`man fzf`)
- [ ] I have the latest version of fzf
- [ ] I have searched through the existing issues
## Info
- OS
- [ ] Linux
- [ ] Mac OS X
- [ ] Windows
- [ ] Windows Subsystem for Linux
- [ ] Etc.
- Shell
- [ ] bash
- [ ] zsh
- [ ] fish
<!--
### Before submitting
- Make sure that you have the latest version of fzf
- If you use tmux, make sure $TERM starts with "screen"
- For more Vim stuff, check out https://github.com/junegunn/fzf.vim
Describe your problem or suggestion from here ...
-->
## Problem / Steps to reproduce

View File

@@ -1,20 +1,27 @@
language: ruby
dist: trusty
sudo: required
matrix:
language: go
dist: xenial
addons:
apt:
sources:
- sourceline: "ppa:pi-rho/dev"
- sourceline: "ppa:fish-shell/release-2"
packages:
- tmux
- zsh
- fish
env:
- GO111MODULE=on
jobs:
include:
- env: TAGS=
rvm: 2.3.3
# - env: TAGS=tcell
# rvm: 2.3.3
- stage: unittest
go: "1.13.x"
script: make && make test
install:
- sudo add-apt-repository -y ppa:pi-rho/dev
- sudo apt-add-repository -y ppa:fish-shell/release-2
- sudo apt-get update
- sudo apt-get install -y tmux zsh fish
- stage: cli
go: "1.13.x"
rvm: "2.5"
script: |
make install && ./install --all && tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]
script: |
make test install &&
./install --all &&
tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]

View File

@@ -6,12 +6,10 @@ Build instructions
### Prerequisites
- `go` executable in $PATH
- Go 1.11 or above
### Using Makefile
Makefile will set up and use its own `$GOPATH` under the project root.
```sh
# Build fzf binary for your platform in target
make

View File

@@ -1,6 +1,80 @@
CHANGELOG
=========
0.19.0
------
- Added "reload" action for dynamically updating the input list without
restarting fzf. See https://github.com/junegunn/fzf/issues/1750 to learn
more about it.
```sh
# Using fzf as the selector interface for ripgrep
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="foo"
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \
fzf --bind "change:reload:$RG_PREFIX {q} || true" \
--ansi --phony --query "$INITIAL_QUERY"
```
- `--multi` now takes an optional integer argument which indicates the maximum
number of items that can be selected
```sh
seq 100 | fzf --multi 3 --reverse --height 50%
```
- If a placeholder expression for `--preview` and `execute` action (and the
new `reload` action) contains `f` flag, it is replaced to the
path of a temporary file that holds the evaluated list. This is useful
when you multi-select a large number of items and the length of the
evaluated string may exceed [`ARG_MAX`][argmax].
```sh
# Press CTRL-A to select 100K items and see the sum of all the numbers
seq 100000 | fzf --multi --bind ctrl-a:select-all \
--preview "awk '{sum+=\$1} END {print sum}' {+f}"
```
- `deselect-all` no longer deselects unmatched items. It is now consistent
with `select-all` and `toggle-all` in that it only affects matched items.
- Due to the limitation of bash, fuzzy completion is enabled by default for
a fixed set of commands. A helper function for easily setting up fuzzy
completion for any command is now provided.
```sh
# usage: _fzf_setup_completion path|dir COMMANDS...
_fzf_setup_completion path git kubectl
```
- Info line style can be changed by `--info=STYLE`
- `--info=default`
- `--info=inline` (same as old `--inline-info`)
- `--info=hidden`
- Preview window border can be disabled by adding `noborder` to
`--preview-window`.
- When you transform the input with `--with-nth`, the trailing white spaces
are removed.
- `ctrl-\`, `ctrl-]`, `ctrl-^`, and `ctrl-/` can now be used with `--bind`
- See https://github.com/junegunn/fzf/milestone/15?closed=1 for more details
[argmax]: https://unix.stackexchange.com/questions/120642/what-defines-the-maximum-size-for-a-command-single-argument
0.18.0
------
- Added placeholder expression for zero-based item index: `{n}` and `{+n}`
- `fzf --preview 'echo {n}: {}'`
- Added color option for the gutter: `--color gutter:-1`
- Added `--no-unicode` option for drawing borders in non-Unicode, ASCII
characters
- `FZF_PREVIEW_LINES` and `FZF_PREVIEW_COLUMNS` are exported to preview process
- fzf still overrides `LINES` and `COLUMNS` as before, but they may be
reset by the default shell.
- Bug fixes and improvements
- See https://github.com/junegunn/fzf/milestone/14?closed=1
- Built with Go 1.12.1
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
------

View File

@@ -1,6 +1,6 @@
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 gem install --no-document minitest
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
RUN echo '. ~/.bashrc' >> ~/.bash_profile

109
Makefile
View File

@@ -1,34 +1,28 @@
ifndef GOOS
GOOS := $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
endif
GO ?= go
GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
ROOT_DIR := $(shell dirname $(MAKEFILE))
GOPATH := $(ROOT_DIR)/gopath
SRC_LINK := $(GOPATH)/src/github.com/junegunn/fzf/src
VENDOR_LINK := $(GOPATH)/src/github.com/junegunn/fzf/vendor
export GOPATH
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
ROOT_DIR := $(shell dirname $(MAKEFILE))
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(MAKEFILE)
GLIDE_YAML := glide.yaml
GLIDE_LOCK := glide.lock
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(SRC_LINK) $(VENDOR_LINK) $(GLIDE_LOCK) $(MAKEFILE)
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES))
BUILD_FLAGS := -a -ldflags "-X main.revision=$(REVISION) -w -extldflags=$(LDFLAGS)" -tags "$(TAGS)"
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES))
BUILD_FLAGS := -a -ldflags "-X main.revision=$(REVISION) -w -extldflags=$(LDFLAGS)" -tags "$(TAGS)"
BINARY32 := fzf-$(GOOS)_386
BINARY64 := fzf-$(GOOS)_amd64
BINARYARM5 := fzf-$(GOOS)_arm5
BINARYARM6 := fzf-$(GOOS)_arm6
BINARYARM7 := fzf-$(GOOS)_arm7
BINARYARM8 := fzf-$(GOOS)_arm8
VERSION := $(shell awk -F= '/version =/ {print $$2}' src/constants.go | tr -d "\" ")
RELEASE32 := fzf-$(VERSION)-$(GOOS)_386
RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
RELEASEARM5 := fzf-$(VERSION)-$(GOOS)_arm5
RELEASEARM6 := fzf-$(VERSION)-$(GOOS)_arm6
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
RELEASEARM8 := fzf-$(VERSION)-$(GOOS)_arm8
BINARY32 := fzf-$(GOOS)_386
BINARY64 := fzf-$(GOOS)_amd64
BINARYARM5 := fzf-$(GOOS)_arm5
BINARYARM6 := fzf-$(GOOS)_arm6
BINARYARM7 := fzf-$(GOOS)_arm7
BINARYARM8 := fzf-$(GOOS)_arm8
BINARYPPC64LE := fzf-$(GOOS)_ppc64le
VERSION := $(shell awk -F= '/version =/ {print $$2}' src/constants.go | tr -d "\" ")
RELEASE32 := fzf-$(VERSION)-$(GOOS)_386
RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
RELEASEARM5 := fzf-$(VERSION)-$(GOOS)_arm5
RELEASEARM6 := fzf-$(VERSION)-$(GOOS)_arm6
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
RELEASEARM8 := fzf-$(VERSION)-$(GOOS)_arm8
RELEASEPPC64LE := fzf-$(VERSION)-$(GOOS)_ppc64le
# https://en.wikipedia.org/wiki/Uname
UNAME_M := $(shell uname -m)
@@ -46,6 +40,12 @@ else ifeq ($(UNAME_M),armv6l)
BINARY := $(BINARYARM6)
else ifeq ($(UNAME_M),armv7l)
BINARY := $(BINARYARM7)
else ifeq ($(UNAME_M),armv8l)
BINARY := $(BINARYARM8)
else ifeq ($(UNAME_M),aarch64)
BINARY := $(BINARYARM8)
else ifeq ($(UNAME_M),ppc64le)
BINARY := $(BINARYPPC64LE)
else
$(error "Build on $(UNAME_M) is not supported, yet.")
endif
@@ -61,13 +61,14 @@ release: target/$(BINARY32) target/$(BINARY64)
cd target && cp -f $(BINARY64) fzf.exe && zip $(RELEASE64).zip fzf.exe
cd target && rm -f fzf.exe
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 $(BINARY64) fzf && tar -czf $(RELEASE64).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 $(BINARYARM7) fzf && tar -czf $(RELEASEARM7).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
else
release: target/$(BINARY32) target/$(BINARY64)
@@ -83,19 +84,8 @@ release-all: clean test
GOOS=openbsd make release
GOOS=windows make release
$(SRC_LINK):
mkdir -p $(shell dirname $(SRC_LINK))
ln -sf $(ROOT_DIR)/src $(SRC_LINK)
$(VENDOR_LINK):
mkdir -p $(shell dirname $(VENDOR_LINK))
ln -sf $(ROOT_DIR)/vendor $(VENDOR_LINK)
vendor: $(GLIDE_YAML)
go get -u github.com/Masterminds/glide && $(GOPATH)/bin/glide install && touch $@
test: $(SOURCES) vendor
SHELL=/bin/sh GOOS= go test -v -tags "$(TAGS)" \
test: $(SOURCES)
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
github.com/junegunn/fzf/src \
github.com/junegunn/fzf/src/algo \
github.com/junegunn/fzf/src/tui \
@@ -104,26 +94,29 @@ test: $(SOURCES) vendor
install: bin/fzf
clean:
rm -rf target
$(RM) -r target
target/$(BINARY32): $(SOURCES) vendor
GOARCH=386 go build $(BUILD_FLAGS) -o $@
target/$(BINARY32): $(SOURCES)
GOARCH=386 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARY64): $(SOURCES) vendor
GOARCH=amd64 go build $(BUILD_FLAGS) -o $@
target/$(BINARY64): $(SOURCES)
GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@
# https://github.com/golang/go/wiki/GoArm
target/$(BINARYARM5): $(SOURCES) vendor
GOARCH=arm GOARM=5 go build $(BUILD_FLAGS) -o $@
target/$(BINARYARM5): $(SOURCES)
GOARCH=arm GOARM=5 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARYARM6): $(SOURCES) vendor
GOARCH=arm GOARM=6 go build $(BUILD_FLAGS) -o $@
target/$(BINARYARM6): $(SOURCES)
GOARCH=arm GOARM=6 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARYARM7): $(SOURCES) vendor
GOARCH=arm GOARM=7 go build $(BUILD_FLAGS) -o $@
target/$(BINARYARM7): $(SOURCES)
GOARCH=arm GOARM=7 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARYARM8): $(SOURCES) vendor
GOARCH=arm64 go build $(BUILD_FLAGS) -o $@
target/$(BINARYARM8): $(SOURCES)
GOARCH=arm64 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARYPPC64LE): $(SOURCES)
GOARCH=ppc64le $(GO) build $(BUILD_FLAGS) -o $@
bin/fzf: target/$(BINARY) | bin
cp -f target/$(BINARY) bin/fzf
@@ -136,4 +129,8 @@ docker-test:
docker build -t fzf-arch .
docker run -it fzf-arch
.PHONY: all release release-all test install clean docker docker-test
update:
$(GO) get -u
$(GO) mod tidy
.PHONY: all release release-all test install clean docker docker-test update

View File

@@ -42,9 +42,6 @@ Note that the environment variables `FZF_DEFAULT_COMMAND` and
- Customizes fzf colors to match the current color scheme
- `g:fzf_history_dir`
- Enables history feature
- `g:fzf_launcher`
- (Only in GVim) Terminal emulator to open fzf with
- `g:Fzf_launcher` for function reference
#### Examples
@@ -75,7 +72,7 @@ let g:fzf_layout = { 'down': '~40%' }
" You can set up fzf window using a Vim command (Neovim or latest Vim 8 required)
let g:fzf_layout = { 'window': 'enew' }
let g:fzf_layout = { 'window': '-tabnew' }
let g:fzf_layout = { 'window': '10split enew' }
let g:fzf_layout = { 'window': '10new' }
" Customize fzf colors to match your color scheme
let g:fzf_colors =
@@ -103,8 +100,67 @@ let g:fzf_history_dir = '~/.local/share/fzf-history'
`fzf#run`
---------
For more advanced uses, you can use `fzf#run([options])` function with the
following options.
For more advanced uses, you can use `fzf#run([options])` function.
`fzf#run()` function is the core of Vim integration. It takes a single
dictionary argument. At the very least, specify `sink` option to tell what it
should do with the selected entry.
```vim
call fzf#run({'sink': 'e'})
```
Without `source`, fzf will use find command (or `$FZF_DEFAULT_COMMAND` if
defined) to list the files under the current directory. When you select one,
it will open it with `:e` command. If you want to open it in a new tab, you
can pass `:tabedit` command instead as the sink.
```vim
call fzf#run({'sink': 'tabedit'})
```
fzf allows you to select multiple entries with `--multi` (or `-m`) option, and
you can change its bottom-up layout with `--reverse` option. Such options can
be specified as `options`.
```vim
call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})
```
Instead of using the default find command, you can use any shell command as
the source. This will list the files managed by git.
```vim
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
```
Pass a layout option if you don't want fzf window to take up the entire screen.
```vim
" up / down / left / right / window are allowed
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'right': '40%'})
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vnew'})
```
`source` doesn't have to be an external shell command, you can pass a Vim
array as the source. In the following example, we use the names of the open
buffers as the source.
```vim
call fzf#run({'source': map(filter(range(1, bufnr('$')), 'buflisted(v:val)'),
\ 'bufname(v:val)'),
\ 'sink': 'e', 'down': '30%'})
```
Or the names of color schemes.
```vim
call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
\ 'fnamemodify(v:val, ":t:r")'),
\ 'sink': 'colo', 'left': '25%'})
```
The following table shows the available options.
| Option name | Type | Description |
| -------------------------- | ------------- | ---------------------------------------------------------------- |
@@ -132,14 +188,35 @@ call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
`fzf#wrap`
----------
`fzf#wrap([name string,] [opts dict,] [fullscreen boolean])` is a helper
function that decorates the options dictionary so that it understands
`g:fzf_layout`, `g:fzf_action`, `g:fzf_colors`, and `g:fzf_history_dir` like
`:FZF`.
`:FZF` command provided by default knows how to handle `CTRL-T`, `CTRL-X`, and
`CTRL-V` and opens the selected file in a new tab, in a horizontal split, or
in a vertical split respectively. And these key bindings can be configured via
`g:fzf_action`. This is implemented using `--expect` option of fzf and the
smart sink function. It also understands `g:fzf_colors`, `g:fzf_layout` and
`g:fzf_history_dir`. However, `fzf#run` doesn't know about any of these
options.
By *"wrapping"* your options dictionary with `fzf#wrap` before passing it to
`fzf#run`, you can make your command also support the options.
```vim
command! -bang MyStuff
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
" Usage:
" fzf#wrap([name string,] [opts dict,] [fullscreen boolean])
" This command now supports CTRL-T, CTRL-V, and CTRL-X key bindings
" and opens fzf according to g:fzf_layout setting.
command! Buffers call fzf#run(fzf#wrap(
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}))
" This extends the above example to open fzf in fullscreen
" when the command is run with ! suffix (Buffers!)
command! -bang Buffers call fzf#run(fzf#wrap(
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0))
" You can optionally pass the name of the command as the first argument to
" fzf#wrap to make it work with g:fzf_history_dir
command! -bang Buffers call fzf#run(fzf#wrap('buffers',
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0))
```
fzf inside terminal buffer
@@ -164,30 +241,6 @@ autocmd FileType fzf set laststatus=0 noshowmode noruler
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
```
GVim
----
With the latest version of GVim, fzf will start inside the builtin terminal
emulator of Vim. Please note that this terminal feature of Vim is still young
and unstable and you may run into some issues.
If you have an older version of GVim, you need an external terminal emulator
to start fzf with. `xterm` command is used by default, but you can customize
it with `g:fzf_launcher`.
```vim
" This is the default. %s is replaced with fzf command
let g:fzf_launcher = 'xterm -e bash -ic %s'
" Use urxvt instead
let g:fzf_launcher = 'urxvt -geometry 120x30 -e sh -c %s'
```
If you're running MacVim on OSX, I recommend you to use iTerm2 as the
launcher. Refer to the [this wiki page][macvim-iterm2] to see how to set up.
[macvim-iterm2]: https://github.com/junegunn/fzf/wiki/On-MacVim-with-iTerm2
[License](LICENSE)
------------------

View File

@@ -27,7 +27,10 @@ Table of Contents
* [Using git](#using-git)
* [As Vim plugin](#as-vim-plugin)
* [Arch Linux](#arch-linux)
* [Debian](#debian)
* [Fedora](#fedora)
* [openSUSE](#opensuse)
* [FreeBSD](#freebsd)
* [Windows](#windows)
* [Upgrading fzf](#upgrading-fzf)
* [Building fzf](#building-fzf)
@@ -37,6 +40,7 @@ Table of Contents
* [Search syntax](#search-syntax)
* [Environment variables](#environment-variables)
* [Options](#options)
* [Demo](#demo)
* [Examples](#examples)
* [fzf-tmux script](#fzf-tmux-script)
* [Key bindings for command line](#key-bindings-for-command-line)
@@ -56,6 +60,7 @@ Table of Contents
* [Respecting .gitignore](#respecting-gitignore)
* [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal)
* [Fish shell](#fish-shell)
* [Related projects](#related-projects)
* [<a href="LICENSE">License</a>](#license)
Installation
@@ -87,6 +92,10 @@ brew install fzf
$(brew --prefix)/opt/fzf/install
```
fzf is also available [via MacPorts][portfile]: `sudo port install fzf`
[portfile]: https://github.com/macports/macports-ports/blob/master/sysutils/fzf/Portfile
### Using git
Alternatively, you can "git clone" this repository to any directory and run
@@ -126,10 +135,10 @@ But instead of separately installing fzf on your system (using Homebrew or
vim-plug to do both.
```vim
" PlugInstall and PlugUpdate will clone fzf in ~/.fzf and run install script
" PlugInstall and PlugUpdate will clone fzf in ~/.fzf and run the 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.
" and you don't have to run the install script if you use fzf only in Vim.
```
### Arch Linux
@@ -138,6 +147,17 @@ Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
sudo pacman -S fzf
```
### Debian
fzf is available in Debian Buster and above, and can be installed using the usual
method:
```sh
sudo apt-get install fzf
```
Read the documentation (/usr/share/doc/fzf/README.Debian) on how to enable it.
### Fedora
fzf is available in Fedora 26 and above, and can be installed using the usual
@@ -151,10 +171,24 @@ 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.
### openSUSE
fzf is available in openSUSE Tumbleweed and can be installed via zypper:
```sh
sudo zypper install fzf
```
### FreeBSD
```sh
pkg install fzf
```
### Windows
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
available as a [Chocolatey package][choco].
available as a [Chocolatey package][choco]:
[choco]: https://chocolatey.org/packages/fzf
@@ -162,6 +196,14 @@ available as a [Chocolatey package][choco].
choco install fzf
```
or a [Scoop package][scoop]:
[scoop]: https://github.com/ScoopInstaller/Main/blob/master/bucket/fzf.json
```sh
scoop install fzf
```
However, other components of the project may not work on Windows. Known issues
and limitations can be found on [the wiki page][windows-wiki]. You might want
to consider installing fzf on [Windows Subsystem for Linux][wsl] where
@@ -278,6 +320,13 @@ or `py`.
See the man page (`man fzf`) for the full list of options.
#### Demo
If you learn by watching videos, check out this screencast by [@samoshkin](https://github.com/samoshkin) to explore `fzf` features.
<a title="fzf - command-line fuzzy finder" href="https://www.youtube.com/watch?v=qgG5Jhi_Els">
<img src="https://i.imgur.com/vtG8olE.png" width="640">
</a>
Examples
--------
@@ -311,16 +360,16 @@ fullscreen mode.
fzf --height 40%
```
Key bindings for command line
Key bindings for command-line
-----------------------------
The install script will setup the following key bindings for bash, zsh, and
fish.
- `CTRL-T` - Paste the selected files and directories onto the command line
- `CTRL-T` - Paste the selected files and directories onto the command-line
- Set `FZF_CTRL_T_COMMAND` to override the default command
- Set `FZF_CTRL_T_OPTS` to pass additional options
- `CTRL-R` - Paste the selected command from history onto the command line
- `CTRL-R` - Paste the selected command from history onto the command-line
- If you want to see the commands in chronological order, press `CTRL-R`
again which toggles sorting by relevance
- Set `FZF_CTRL_R_OPTS` to pass additional options
@@ -372,7 +421,7 @@ cd ~/github/fzf**<TAB>
#### Process IDs
Fuzzy completion for PIDs is provided for kill command. In this case
Fuzzy completion for PIDs is provided for kill command. In this case,
there is no trigger sequence, just press tab key after kill command.
```sh
@@ -425,7 +474,7 @@ _fzf_compgen_dir() {
On bash, fuzzy completion is enabled only for a predefined set of commands
(`complete | grep _fzf` to see the list). But you can enable it for other
commands as well like follows.
commands as well as follows.
```sh
complete -F _fzf_path_completion -o default -o bashdefault ag
@@ -442,7 +491,7 @@ Advanced topics
### Performance
fzf is fast, and is [getting even faster][perf]. Performance should not be
fzf is fast and is [getting even faster][perf]. Performance should not be
a problem in most use cases. However, you might want to be aware of the
options that affect the performance.
@@ -453,7 +502,7 @@ options that affect the performance.
- `--with-nth` makes fzf slower as fzf has to tokenize and reassemble each
line.
- If you absolutely need better performance, you can consider using
`--algo=v1` (the default being `v2`) to make fzf use faster greedy
`--algo=v1` (the default being `v2`) to make fzf use a faster greedy
algorithm. However, this algorithm is not guaranteed to find the optimal
ordering of the matches and is not recommended.
@@ -474,7 +523,7 @@ See *KEY BINDINGS* section of the man page for details.
### Preview window
When `--preview` option is set, fzf automatically starts external process with
When `--preview` option is set, fzf automatically starts an external process with
the current line as the argument and shows the result in the split window.
```bash
@@ -482,7 +531,7 @@ the current line as the argument and shows the result in the split window.
fzf --preview 'cat {}'
```
Since preview window is updated only after the process is complete, it's
Since the preview window is updated only after the process is complete, it's
important that the command finishes quickly.
```bash
@@ -493,15 +542,17 @@ fzf --preview 'head -100 {}'
Preview window supports ANSI colors, so you can use programs that
syntax-highlights the content of a file.
- Bat: https://github.com/sharkdp/bat
- Highlight: http://www.andre-simon.de/doku/highlight/en/highlight.php
- CodeRay: http://coderay.rubychan.de/
- Rouge: https://github.com/jneen/rouge
```bash
# Try highlight, coderay, rougify in turn, then fall back to cat
# Try bat, highlight, coderay, rougify in turn, then fall back to cat
fzf --preview '[[ $(file --mime {}) =~ binary ]] &&
echo {} is a binary file ||
(highlight -O ansi -l {} ||
(bat --style=numbers --color=always {} ||
highlight -O ansi -l {} ||
coderay {} ||
rougify {} ||
cat {}) 2> /dev/null | head -500'
@@ -514,7 +565,8 @@ You can customize the size and position of the preview window using
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/
@@ -580,9 +632,9 @@ fzf -m | while read -l r; set result $result $r; end; and vim $result
```
The globbing system is different in fish and thus `**` completion will not work.
However, the `CTRL-T` command will use the last token on the commandline as the
However, the `CTRL-T` command will use the last token on the command-line as the
root folder for the recursive search. For instance, hitting `CTRL-T` at the end
of the following commandline
of the following command-line
```sh
ls /var/
@@ -598,6 +650,11 @@ valid directory. Example:
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)
------------------

View File

@@ -1,4 +1,4 @@
fzf.txt fzf Last change: November 19 2017
fzf.txt fzf Last change: June 8 2019
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
==============================================================================
@@ -10,7 +10,6 @@ FZF - TABLE OF CONTENTS *fzf* *fzf-to
fzf#wrap
fzf inside terminal buffer
Hide statusline
GVim
License
FZF VIM INTEGRATION *fzf-vim-integration*
@@ -56,8 +55,7 @@ Note that the environment variables `FZF_DEFAULT_COMMAND` and
< Configuration >_____________________________________________________________~
*fzf-configuration*
*g:fzf_action* *g:fzf_layout* *g:fzf_colors* *g:fzf_history_dir* *g:fzf_launcher*
*g:Fzf_launcher*
*g:fzf_action* *g:fzf_layout* *g:fzf_colors* *g:fzf_history_dir*
- `g:fzf_action`
- Customizable extra key bindings for opening selected files in different
@@ -68,9 +66,6 @@ Note that the environment variables `FZF_DEFAULT_COMMAND` and
- Customizes fzf colors to match the current color scheme
- `g:fzf_history_dir`
- Enables history feature
- `g:fzf_launcher`
- (Only in GVim) Terminal emulator to open fzf with
- `g:Fzf_launcher` for function reference
Examples~
@@ -102,7 +97,7 @@ Examples~
" You can set up fzf window using a Vim command (Neovim or latest Vim 8 required)
let g:fzf_layout = { 'window': 'enew' }
let g:fzf_layout = { 'window': '-tabnew' }
let g:fzf_layout = { 'window': '10split enew' }
let g:fzf_layout = { 'window': '10new' }
" Customize fzf colors to match your color scheme
let g:fzf_colors =
@@ -130,8 +125,54 @@ Examples~
FZF#RUN *fzf#run*
==============================================================================
For more advanced uses, you can use `fzf#run([options])` function with the
following options.
For more advanced uses, you can use `fzf#run([options])` function.
`fzf#run()` function is the core of Vim integration. It takes a single
dictionary argument. At the very least, specify `sink` option to tell what it
should do with the selected entry.
>
call fzf#run({'sink': 'e'})
<
Without `source`, fzf will use find command (or `$FZF_DEFAULT_COMMAND` if
defined) to list the files under the current directory. When you select one,
it will open it with `:e` command. If you want to open it in a new tab, you
can pass `:tabedit` command instead as the sink.
>
call fzf#run({'sink': 'tabedit'})
<
fzf allows you to select multiple entries with `--multi` (or `-m`) option, and
you can change its bottom-up layout with `--reverse` option. Such options can
be specified as `options`.
>
call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})
<
Instead of using the default find command, you can use any shell command as
the source. This will list the files managed by git.
>
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
<
Pass a layout option if you don't want fzf window to take up the entire
screen.
>
" up / down / left / right / window are allowed
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'right': '40%'})
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vnew'})
<
`source` doesn't have to be an external shell command, you can pass a Vim
array as the source. In the following example, we use the names of the open
buffers as the source.
>
call fzf#run({'source': map(filter(range(1, bufnr('$')), 'buflisted(v:val)'),
\ 'bufname(v:val)'),
\ 'sink': 'e', 'down': '30%'})
<
Or the names of color schemes.
>
call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
\ 'fnamemodify(v:val, ":t:r")'),
\ 'sink': 'colo', 'left': '25%'})
<
The following table shows the available options.
---------------------------+---------------+--------------------------------------------------------------
Option name | Type | Description ~
@@ -160,13 +201,34 @@ issues on different platforms.
FZF#WRAP *fzf#wrap*
==============================================================================
`fzf#wrap([namestring,][optsdict,][fullscreenboolean])` is a helper
function that decorates the options dictionary so that it understands
`g:fzf_layout`, `g:fzf_action`, `g:fzf_colors`, and `g:fzf_history_dir` like
`:FZF`.
`:FZF` command provided by default knows how to handle CTRL-T, CTRL-X, and
CTRL-V and opens the selected file in a new tab, in a horizontal split, or in
a vertical split respectively. And these key bindings can be configured via
`g:fzf_action`. This is implemented using `--expect` option of fzf and the
smart sink function. It also understands `g:fzf_colors`, `g:fzf_layout` and
`g:fzf_history_dir`. However, `fzf#run` doesn't know about any of these
options.
By "wrapping" your options dictionary with `fzf#wrap` before passing it to
`fzf#run`, you can make your command also support the options.
>
command! -bang MyStuff
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
" Usage:
" fzf#wrap([name string,] [opts dict,] [fullscreen boolean])
" This command now supports CTRL-T, CTRL-V, and CTRL-X key bindings
" and opens fzf according to g:fzf_layout setting.
command! Buffers call fzf#run(fzf#wrap(
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}))
" This extends the above example to open fzf in fullscreen
" when the command is run with ! suffix (Buffers!)
command! -bang Buffers call fzf#run(fzf#wrap(
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0))
" You can optionally pass the name of the command as the first argument to
" fzf#wrap to make it work with g:fzf_history_dir
command! -bang Buffers call fzf#run(fzf#wrap('buffers',
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0))
<
FZF INSIDE TERMINAL BUFFER *fzf-inside-terminal-buffer*
@@ -192,29 +254,6 @@ the containing buffer.
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
<
GVIM *fzf-gvim*
==============================================================================
With the latest version of GVim, fzf will start inside the builtin terminal
emulator of Vim. Please note that this terminal feature of Vim is still young
and unstable and you may run into some issues.
If you have an older version of GVim, you need an external terminal emulator
to start fzf with. `xterm` command is used by default, but you can customize
it with `g:fzf_launcher`.
>
" This is the default. %s is replaced with fzf command
let g:fzf_launcher = 'xterm -e bash -ic %s'
" Use urxvt instead
let g:fzf_launcher = 'urxvt -geometry 120x30 -e sh -c %s'
<
If you're running MacVim on OSX, I recommend you to use iTerm2 as the
launcher. Refer to the {this wiki page}{3} to see how to set up.
{3} https://github.com/junegunn/fzf/wiki/On-MacVim-with-iTerm2
LICENSE *fzf-license*
==============================================================================

134
glide.lock generated
View File

@@ -1,134 +0,0 @@
hash: 92a208bfbaecdf8d1ccaf99a465884c49f9cd91f44f1756d7bbf3290795c781b
updated: 2017-12-03T13:37:23.420874333+09:00
imports:
- name: github.com/bjwbell/gensimd
version: 06eb18285485c0d572cc7f024050fc6cb652ed4c
subpackages:
- simd
- name: github.com/codegangsta/cli
version: c6af8847eb2b7b297d07c3ede98903e95e680ef9
- name: github.com/gdamore/encoding
version: b23993cbb6353f0e6aa98d0ee318a34728f628b9
- name: github.com/gdamore/tcell
version: 0a0db94084dfe181108c18508ebd312f12d331fb
subpackages:
- encoding
- name: github.com/lucasb-eyer/go-colorful
version: c900de9dbbc73129068f5af6a823068fc5f2308c
- name: github.com/Masterminds/semver
version: 15d8430ab86497c5c0da827b748823945e1cf1e1
- name: github.com/Masterminds/vcs
version: 6f1c6d150500e452704e9863f68c2559f58616bf
- name: github.com/mattn/go-isatty
version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
- name: github.com/mattn/go-runewidth
version: 14207d285c6c197daabb5c9793d63e7af9ab2d50
- name: github.com/mattn/go-shellwords
version: 02e3cf038dcea8290e44424da473dd12be796a8a
- name: github.com/mengzhuo/intrinsic
version: 34b800838e0bcd9c5b6abd414e3ad03dc1a686b8
subpackages:
- sse2
- name: github.com/mitchellh/go-homedir
version: b8bc1bf767474819792c23f32d8286a45736f1c6
- name: golang.org/x/crypto
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
subpackages:
- acme
- blowfish
- cast5
- chacha20poly1305/internal/chacha20
- curve25519
- ed25519
- ed25519/internal/edwards25519
- hkdf
- nacl/secretbox
- openpgp
- openpgp/armor
- openpgp/elgamal
- openpgp/errors
- openpgp/packet
- openpgp/s2k
- pbkdf2
- pkcs12/internal/rc2
- poly1305
- ripemd160
- salsa20/salsa
- ssh
- ssh/agent
- ssh/terminal
- ssh/testdata
- name: golang.org/x/net
version: a8b9294777976932365dabb6640cf1468d95c70f
subpackages:
- context
- context/ctxhttp
- name: golang.org/x/sys
version: b90f89a1e7a9c1f6b918820b3daa7f08488c8594
subpackages:
- unix
- name: golang.org/x/text
version: 4ee4af566555f5fbe026368b75596286a312663a
subpackages:
- cases
- collate
- collate/build
- currency
- encoding
- encoding/charmap
- encoding/ianaindex
- encoding/internal
- encoding/internal/identifier
- encoding/japanese
- encoding/korean
- encoding/simplifiedchinese
- encoding/traditionalchinese
- encoding/unicode
- encoding/unicode/utf32
- internal
- internal/colltab
- internal/format
- internal/gen
- internal/stringset
- internal/tag
- internal/testtext
- internal/triegen
- internal/ucd
- internal/utf8internal
- language
- language/display
- message
- runes
- secure/bidirule
- transform
- unicode/bidi
- unicode/cldr
- unicode/norm
- unicode/rangetable
- width
- name: golang.org/x/tools
version: 04447353bc504b9a5c02eb227b9ecd252e64ea20
subpackages:
- go/ast/astutil
- go/buildutil
- go/loader
- name: gopkg.in/yaml.v2
version: 287cf08546ab5e7e37d55a84f7ed3fd1db036de5
testImports:
- name: github.com/gopherjs/gopherjs
version: 444abdf920945de5d4a977b572bcc6c674d1e4eb
subpackages:
- js
- name: github.com/jtolds/gls
version: 77f18212c9c7edc9bd6a33d383a7b545ce62f064
- name: github.com/smartystreets/assertions
version: 0b37b35ec7434b77e77a4bb29b79677cced992ea
subpackages:
- internal/go-render/render
- internal/oglematchers
- name: github.com/smartystreets/goconvey
version: e5b2b7c9111590d019a696c7800593f666e1a7f4
subpackages:
- convey
- convey/gotest
- convey/reporting

View File

@@ -1,16 +0,0 @@
package: github.com/junegunn/fzf
import:
- package: github.com/mattn/go-isatty
version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
- package: github.com/mattn/go-runewidth
version: 14207d285c6c197daabb5c9793d63e7af9ab2d50
- package: github.com/mattn/go-shellwords
version: 02e3cf038dcea8290e44424da473dd12be796a8a
- package: github.com/gdamore/tcell
version: 0a0db94084dfe181108c18508ebd312f12d331fb
subpackages:
- encoding
- package: golang.org/x/crypto
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
subpackages:
- ssh/terminal

19
go.mod Normal file
View File

@@ -0,0 +1,19 @@
module github.com/junegunn/fzf
require (
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635 // indirect
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7 // indirect
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c
github.com/mattn/go-shellwords v1.0.3
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 // indirect
golang.org/x/text v0.0.0-20170530162606-4ee4af566555 // indirect
)
go 1.13

26
go.sum Normal file
View File

@@ -0,0 +1,26 @@
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635 h1:hheUEMzaOie/wKeIc1WPa7CDVuIO5hqQxjS+dwTQEnI=
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635/go.mod h1:yrQYJKKDTrHmbYxI7CYi+/hbdiDT2m4Hj+t0ikCjsrQ=
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df h1:tLS1QD2puA1USLvkEnGfOt+Zp2ijtNIK3z2YFaIZZR4=
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df/go.mod h1:tqyG50u7+Ctv1w5VX67kLzKcj9YXR/JSBZQq/+mLl1A=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7 h1:G52I+Gk/wPD4HKvKT0Vxxp9OUPxqKs3OK6rffSPtNkA=
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c h1:3nKFouDdpgGUV/uerJcYWH45ZbJzX0SiVWfTgmUeTzc=
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c h1:eFzthqtg3W6Pihj3DMTXLAF4f+ge5r5Ie5g6HLIZAF0=
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74 h1:/0jf0Cx3u07Ma4EzUA6NIGuvk9hb3Br6i9V8atthkwk=
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 h1:rM0ROo5vb9AdYJi1110yjWGMej9ITfKddS89P3Fkhug=
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170530162606-4ee4af566555 h1:pjwO9HxObpgZBurDvTLFbSinaf3+cKpTAjVfiGaHwrI=
golang.org/x/text v0.0.0-20170530162606-4ee4af566555/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

37
install
View File

@@ -2,12 +2,11 @@
set -u
version=0.17.4
version=0.19.0
auto_completion=
key_bindings=
update_config=2
binary_arch=
allow_legacy=
shells="bash zsh fish"
prefix='~/.fzf'
prefix_expand=~/.fzf
@@ -45,7 +44,6 @@ for opt in "$@"; do
auto_completion=1
key_bindings=1
update_config=1
allow_legacy=1
;;
--xdg)
prefix='"${XDG_CONFIG_HOME:-$HOME/.config}"/fzf/fzf'
@@ -73,7 +71,8 @@ for opt in "$@"; do
done
cd "$(dirname "${BASH_SOURCE[0]}")"
fzf_base="$(pwd)"
fzf_base=$(pwd)
fzf_base_esc=$(printf %q "$fzf_base")
ask() {
while true; do
@@ -90,17 +89,20 @@ ask() {
check_binary() {
echo -n " - Checking fzf executable ... "
local output
output=$("$fzf_base"/bin/fzf --version 2>&1 | awk '{print $1}')
output=$("$fzf_base"/bin/fzf --version 2>&1)
if [ $? -ne 0 ]; then
echo "Error: $output"
binary_error="Invalid binary"
elif [ "$version" != "$output" ]; then
echo "$output != $version"
binary_error="Invalid version"
else
echo "$output"
binary_error=""
return 0
output=${output/ */}
if [ "$version" != "$output" ]; then
echo "$output != $version"
binary_error="Invalid version"
else
echo "$output"
binary_error=""
return 0
fi
fi
rm -f "$fzf_base"/bin/fzf
return 1
@@ -109,7 +111,7 @@ check_binary() {
link_fzf_in_path() {
if which_fzf="$(command -v fzf)"; then
echo " - Found in \$PATH"
echo " - Creating symlink: $which_fzf -> bin/fzf"
echo " - Creating symlink: bin/fzf -> $which_fzf"
(cd "$fzf_base"/bin && rm -f fzf && ln -sf "$which_fzf" fzf)
check_binary && return
fi
@@ -192,6 +194,8 @@ case "$archi" in
CYGWIN*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
MINGW*\ *86) download fzf-$version-windows_${binary_arch:-386}.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 ;;
esac
@@ -265,8 +269,8 @@ for shell in $shells; do
cat > "$src" << EOF
# Setup fzf
# ---------
if [[ ! "\$PATH" == *$fzf_base/bin* ]]; then
export PATH="\$PATH:$fzf_base/bin"
if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then
export PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
fi
# Auto-completion
@@ -276,7 +280,6 @@ $fzf_completion
# Key bindings
# ------------
$fzf_key_bindings
EOF
echo "OK"
done
@@ -285,8 +288,8 @@ done
if [[ "$shells" =~ fish ]]; then
echo -n "Update fish_user_paths ... "
fish << EOF
echo \$fish_user_paths | \grep $fzf_base/bin > /dev/null
or set --universal fish_user_paths \$fish_user_paths $fzf_base/bin
echo \$fish_user_paths | \grep "$fzf_base"/bin > /dev/null
or set --universal fish_user_paths \$fish_user_paths "$fzf_base"/bin
EOF
[ $? -eq 0 ] && echo "OK" || echo "Failed"

View File

@@ -1,7 +1,7 @@
.ig
The MIT License (MIT)
Copyright (c) 2017 Junegunn Choi
Copyright (c) 2019 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf-tmux 1 "Jun 2018" "fzf 0.17.4" "fzf-tmux - open fzf in tmux split pane"
.TH fzf-tmux 1 "Nov 2019" "fzf 0.19.0" "fzf-tmux - open fzf in tmux split pane"
.SH NAME
fzf-tmux - open fzf in tmux split pane

View File

@@ -1,7 +1,7 @@
.ig
The MIT License (MIT)
Copyright (c) 2017 Junegunn Choi
Copyright (c) 2019 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf 1 "Jun 2018" "fzf 0.17.4" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Nov 2019" "fzf 0.19.0" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -70,6 +70,10 @@ Transform the presentation of each line using field index expressions
.TP
.BI "-d, --delimiter=" "STR"
Field delimiter regex for \fB--nth\fR and \fB--with-nth\fR (default: AWK-style)
.TP
.BI "--phony"
Do not perform search. With this option, fzf becomes a simple selector
interface rather than a "fuzzy finder".
.SS Search result
.TP
.B "+s, --no-sort"
@@ -79,7 +83,8 @@ Do not sort the result
Reverse the order of the input
.RS
e.g. \fBhistory | fzf --tac --no-sort\fR
e.g.
\fBhistory | fzf --tac --no-sort\fR
.RE
.TP
.BI "--tiebreak=" "CRI[,..]"
@@ -109,7 +114,8 @@ Comma-separated list of sort criteria to apply when the scores are tied.
.SS Interface
.TP
.B "-m, --multi"
Enable multi-select with tab/shift-tab
Enable multi-select with tab/shift-tab. It optionally takes an integer argument
which denotes the maximum number of items that can be selected.
.TP
.B "+m, --no-multi"
Disable multi-select
@@ -118,8 +124,8 @@ Disable multi-select
Disable mouse
.TP
.BI "--bind=" "KEYBINDS"
Comma-separated list of custom key bindings. See \fBKEY BINDINGS\fR for the
details.
Comma-separated list of custom key bindings. See \fBKEY/EVENT BINDINGS\fR for
the details.
.TP
.B "--cycle"
Enable cyclic scroll
@@ -174,6 +180,11 @@ A synonym for \fB--layout=reverse\fB
.TP
.B "--border"
Draw border above and below the finder
.TP
.B "--no-unicode"
Use ASCII characters instead of Unicode box drawing characters to draw border
.TP
.BI "--margin=" MARGIN
Comma-separated expression for margins around the finder.
@@ -196,12 +207,26 @@ terminal size with \fB%\fR suffix.
.br
.br
e.g. \fBfzf --margin 10%\fR
\fBfzf --margin 1,5%\fR
e.g.
\fBfzf --margin 10%
fzf --margin 1,5%\fR
.RE
.TP
.B "--inline-info"
Display finder info inline with the query
.BI "--info=" "STYLE"
Determines the display style of finder info.
.br
.BR default " Display on the next line to the prompt"
.br
.BR inline " Display on the same line"
.br
.BR hidden " Do not display finder info"
.br
.TP
.B "--no-info"
A synonym for \fB--info=hidden\fB
.TP
.BI "--prompt=" "STR"
Input prompt (default: '> ')
@@ -230,11 +255,6 @@ color mappings. Ansi color code of -1 denotes terminal default
foreground/background color. You can also specify 24-bit color in \fB#rrggbb\fR
format.
.RS
e.g. \fBfzf --color=bg+:24\fR
\fBfzf --color=light,fg:232,bg:255,bg+:116,info:27\fR
.RE
.RS
.B BASE SCHEME:
(default: dark on 256-color terminal, otherwise 16)
@@ -250,6 +270,7 @@ e.g. \fBfzf --color=bg+:24\fR
\fBhl \fRHighlighted substrings
\fBfg+ \fRText (current line)
\fBbg+ \fRBackground (current line)
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
\fBhl+ \fRHighlighted substrings (current line)
\fBinfo \fRInfo
\fBborder \fRBorder of the preview window and horizontal separators (\fB--border\fR)
@@ -258,6 +279,19 @@ e.g. \fBfzf --color=bg+:24\fR
\fBmarker \fRMulti-select marker
\fBspinner \fRStreaming input indicator
\fBheader \fRHeader
.B EXAMPLES:
\fB# Seoul256 theme with 8-bit colors
# (https://github.com/junegunn/seoul256.vim)
fzf --color='bg:237,bg+:236,info:143,border:240,spinner:108' \\
--color='hl:65,fg:252,header:65,fg+:252' \\
--color='pointer:161,marker:168,prompt:110,hl+:108'
# Seoul256 theme with 24-bit colors
fzf --color='bg:#4B4B4B,bg+:#3F3F3F,info:#BDBB72,border:#6B6B6B,spinner:#98BC99' \\
--color='hl:#719872,fg:#D9D9D9,header:#719872,fg+:#D9D9D9' \\
--color='pointer:#E12672,marker:#E17899,prompt:#98BEDE,hl+:#98BC99'\fR
.RE
.TP
.B "--no-bold"
@@ -285,23 +319,41 @@ string, specify field index expressions between the braces (See \fBFIELD INDEX
EXPRESSION\fR for the details).
.RS
e.g. \fBfzf --preview='head -$LINES {}'\fR
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
e.g.
\fBfzf --preview='head -$LINES {}'
ls -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.
fzf exports \fB$FZF_PREVIEW_LINES\fR and \fB$FZF_PREVIEW_COLUMNS\fR so that
they represent the exact size of the preview window. (It also overrides
\fB$LINES\fR and \fB$COLUMNS\fR with the same values but they can be reset
by the default shell, so prefer to refer to the ones with \fBFZF_PREVIEW_\fR
prefix.)
A placeholder expression starting with \fB+\fR flag will be replaced to the
space-separated list of the selected lines (or the current line if no selection
was made) individually quoted.
e.g. \fBfzf --multi --preview='head -10 {+}'\fR
\fBgit log --oneline | fzf --multi --preview 'git show {+1}'\fR
e.g.
\fBfzf --multi --preview='head -10 {+}'
git 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, and \fB{n}\fR is
replaced to zero-based ordinal index of the line. Use \fB{+n}\fR if you want
all index numbers when multiple lines are selected.
A placeholder expression with \fBf\fR flag is replaced to the path of
a temporary file that holds the evaluated list. This is useful when you
multi-select a large number of items and the length of the evaluated string may
exceed \fBARG_MAX\fR.
e.g.
\fB# Press CTRL-A to select 100K items and see the sum of all the numbers.
# This won't work properly without 'f' flag due to ARG_MAX limit.
seq 100000 | fzf --multi --bind ctrl-a:select-all \\
--preview "awk '{sum+=\$1} END {print sum}' {+f}"\fR
Note that you can escape a placeholder pattern by prepending a backslash.
@@ -309,8 +361,8 @@ Preview window will be updated even when there is no match for the current
query if any of the placeholder expressions evaluates to a non-empty string.
.RE
.TP
.BI "--preview-window=" "[POSITION][:SIZE[%]][:wrap][:hidden]"
Determine the layout of the preview window. If the argument ends with
.BI "--preview-window=" "[POSITION][:SIZE[%]][:noborder][:wrap][:hidden]"
Determines the layout of the preview window. If the argument contains
\fB:hidden\fR, the preview window will be hidden by default until
\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
Line wrap can be enabled with \fB:wrap\fR flag.
@@ -327,8 +379,9 @@ execute the command in the background.
.RE
.RS
e.g. \fBfzf --preview="head {}" --preview-window=up:30%\fR
\fBfzf --preview="file {}" --preview-window=down:1\fR
e.g.
\fBfzf --preview="head {}" --preview-window=up:30%
fzf --preview="file {}" --preview-window=down:1\fR
.RE
.SS Scripting
.TP
@@ -358,7 +411,8 @@ times, fzf will expect the union of the keys. \fB--no-expect\fR will clear the
list.
.RS
e.g. \fBfzf --expect=ctrl-v,ctrl-t,alt-s --expect=f1,f2,~,@\fR
e.g.
\fBfzf --expect=ctrl-v,ctrl-t,alt-s --expect=f1,f2,~,@\fR
.RE
.TP
.B "--read0"
@@ -390,7 +444,8 @@ Note that most options have the opposite versions with \fB--no-\fR prefix.
.SH ENVIRONMENT VARIABLES
.TP
.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
.B FZF_DEFAULT_OPTS
Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
@@ -463,56 +518,110 @@ query matches entries that start with \fBcore\fR and end with either \fBgo\fR,
e.g. \fB^core go$ | rb$ | py$\fR
.SH KEY BINDINGS
You can customize key bindings of fzf with \fB--bind\fR option which takes
a comma-separated list of key binding expressions. Each key binding expression
follows the following format: \fBKEY:ACTION\fR
.SH KEY/EVENT BINDINGS
\fB--bind\fR option allows you to bind \fBa key\fR or \fBan event\fR to one or
more \fBactions\fR. You can use it to customize key bindings or implement
dynamic behaviors.
e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
\fB--bind\fR takes a comma-separated list of binding expressions. Each binding
expression is \fBKEY:ACTION\fR or \fBEVENT:ACTION\fR.
.B AVAILABLE KEYS: (SYNONYMS)
\fIctrl-[a-z]\fR
\fIctrl-space\fR
\fIctrl-alt-[a-z]\fR
\fIalt-[a-z]\fR
\fIalt-[0-9]\fR
\fIf[1-12]\fR
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
\fIspace\fR
\fIbspace\fR (\fIbs\fR)
\fIalt-up\fR
\fIalt-down\fR
\fIalt-left\fR
\fIalt-right\fR
\fIalt-enter\fR
\fIalt-space\fR
\fIalt-bspace\fR (\fIalt-bs\fR)
\fIalt-/\fR
\fItab\fR
\fIbtab\fR (\fIshift-tab\fR)
\fIesc\fR
\fIdel\fR
\fIup\fR
\fIdown\fR
\fIleft\fR
\fIright\fR
\fIhome\fR
\fIend\fR
\fIpgup\fR (\fIpage-up\fR)
\fIpgdn\fR (\fIpage-down\fR)
\fIshift-up\fR
\fIshift-down\fR
\fIshift-left\fR
\fIshift-right\fR
\fIleft-click\fR
\fIright-click\fR
\fIdouble-click\fR
or any single character
e.g.
\fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
Additionally, a special event named \fIchange\fR is available which is
triggered whenever the query string is changed.
.SS AVAILABLE KEYS: (SYNONYMS)
\fIctrl-[a-z]\fR
.br
\fIctrl-space\fR
.br
\fIctrl-\\\fR
.br
\fIctrl-]\fR
.br
\fIctrl-^\fR (\fIctrl-6\fR)
.br
\fIctrl-/\fR (\fIctrl-_\fR)
.br
\fIctrl-alt-[a-z]\fR
.br
\fIalt-[a-z]\fR
.br
\fIalt-[0-9]\fR
.br
\fIf[1-12]\fR
.br
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
.br
\fIspace\fR
.br
\fIbspace\fR (\fIbs\fR)
.br
\fIalt-up\fR
.br
\fIalt-down\fR
.br
\fIalt-left\fR
.br
\fIalt-right\fR
.br
\fIalt-enter\fR
.br
\fIalt-space\fR
.br
\fIalt-bspace\fR (\fIalt-bs\fR)
.br
\fIalt-/\fR
.br
\fItab\fR
.br
\fIbtab\fR (\fIshift-tab\fR)
.br
\fIesc\fR
.br
\fIdel\fR
.br
\fIup\fR
.br
\fIdown\fR
.br
\fIleft\fR
.br
\fIright\fR
.br
\fIhome\fR
.br
\fIend\fR
.br
\fIpgup\fR (\fIpage-up\fR)
.br
\fIpgdn\fR (\fIpage-down\fR)
.br
\fIshift-up\fR
.br
\fIshift-down\fR
.br
\fIshift-left\fR
.br
\fIshift-right\fR
.br
\fIleft-click\fR
.br
\fIright-click\fR
.br
\fIdouble-click\fR
.br
or any single character
e.g. \fBfzf --bind change:top\fR
.SS AVAILABLE EVENTS:
\fIchange\fR (triggered whenever the query string is changed)
.br
e.g.
\fB# Moves cursor to the top (or bottom depending on --layout) whenever the query is changed
fzf --bind change:top\fR
.SS AVAILABLE ACTIONS:
A key or an event can be bound to one or more of the following actions.
\fBACTION: DEFAULT BINDINGS (NOTES):
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
@@ -551,6 +660,7 @@ triggered whenever the query string is changed.
\fBpreview-page-up\fR
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
\fBprint-query\fR (print query and exit)
\fBreload(...)\fR (see below for the details)
\fBreplace-query\fR (replace query string with the current selection)
\fBselect-all\fR
\fBtoggle\fR (\fIright-click\fR)
@@ -568,9 +678,14 @@ triggered whenever the query string is changed.
\fBup\fR \fIctrl-k ctrl-p up\fR
\fByank\fR \fIctrl-y\fR
.SS ACTION COMPOSITION
Multiple actions can be chained using \fB+\fR separator.
\fBfzf --bind 'ctrl-a:select-all+accept'\fR
e.g.
\fBfzf --bind 'ctrl-a:select-all+accept'\fR
.SS COMMAND EXECUTION
With \fBexecute(...)\fR action, you can execute arbitrary commands without
leaving fzf. For example, you can turn fzf into a simple file browser by
@@ -599,9 +714,9 @@ parse errors.
\fBexecute|...|\fR
\fBexecute:...\fR
.RS
This is the special form that frees you from parse errors as it does not expect
the closing character. The catch is that it should be the last one in the
comma-separated list of key-action pairs.
The last one is the special form that frees you from parse errors as it does
not expect the closing character. The catch is that it should be the last one
in the comma-separated list of key-action pairs.
.RE
fzf switches to the alternate screen when executing a command. However, if the
@@ -611,6 +726,26 @@ executes the command without the switching. Note that fzf will not be
responsive until the command is complete. For asynchronous execution, start
your command as a background process (i.e. appending \fB&\fR).
.SS RELOAD INPUT
\fBreload(...)\fR action is used to dynamically update the input list
without restarting fzf. It takes the same command template with placeholder
expressions as \fBexecute(...)\fR.
See \fIhttps://github.com/junegunn/fzf/issues/1750\fR for more info.
e.g.
\fB# Update the list of processes by pressing CTRL-R
ps -ef | fzf --bind 'ctrl-r:reload(ps -ef)' --header 'Press CTRL-R to reload' \\
--header-lines=1 --layout=reverse
# Integration with ripgrep
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="foobar"
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \\
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
--ansi --phony --query "$INITIAL_QUERY"\fR
.SH AUTHOR
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)

View File

@@ -50,9 +50,17 @@ if s:is_win
" Use utf-8 for fzf.vim commands
" Return array of shell commands for cmd.exe
function! s:wrap_cmds(cmds)
return map(['@echo off', 'for /f "tokens=4" %%a in (''chcp'') do set origchcp=%%a', 'chcp 65001 > nul'] +
\ (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) +
\ ['chcp %origchcp% > nul'], 'v:val."\r"')
let use_chcp = executable('sed')
return map([
\ '@echo off',
\ 'setlocal enabledelayedexpansion']
\ + (use_chcp ? [
\ 'for /f "usebackq" %%a in (`chcp ^| sed "s/[^0-9]//gp"`) do set origchcp=%%a',
\ 'chcp 65001 > nul'] : [])
\ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
\ + (use_chcp ? ['chcp !origchcp! > nul'] : [])
\ + ['endlocal'],
\ 'v:val."\r"')
endfunction
else
let s:term_marker = ";#FZF"
@@ -254,7 +262,7 @@ endfunction
function! s:defaults()
let rules = copy(get(g:, 'fzf_colors', {}))
let colors = join(map(items(filter(map(rules, 'call("s:get_color", v:val)'), '!empty(v:val)')), 'join(v:val, ":")'), ',')
return empty(colors) ? '' : ('--color='.colors)
return empty(colors) ? '' : fzf#shellescape('--color='.colors)
endfunction
function! s:validate_layout(layout)
@@ -357,7 +365,7 @@ try
throw v:exception
endtry
if has('nvim') && !has_key(dict, 'dir')
if !has_key(dict, 'dir')
let dict.dir = s:fzf_getcwd()
endif
if has('win32unix') && has_key(dict, 'dir')
@@ -455,15 +463,18 @@ endfunction
function! s:pushd(dict)
if s:present(a:dict, 'dir')
let cwd = s:fzf_getcwd()
if get(a:dict, 'prev_dir', '') ==# cwd
return 1
endif
let a:dict.prev_dir = cwd
let w:fzf_pushd = {
\ 'command': haslocaldir() ? 'lcd' : (exists(':tcd') && haslocaldir(-1) ? 'tcd' : 'cd'),
\ 'origin': cwd,
\ 'bufname': bufname('')
\ }
execute 'lcd' s:escape(a:dict.dir)
let a:dict.dir = s:fzf_getcwd()
return 1
let cwd = s:fzf_getcwd()
let w:fzf_pushd.dir = cwd
let a:dict.pushd = w:fzf_pushd
return cwd
endif
return 0
return ''
endfunction
augroup fzf_popd
@@ -472,20 +483,38 @@ augroup fzf_popd
augroup END
function! s:dopopd()
if !exists('w:fzf_dir') || s:fzf_getcwd() != w:fzf_dir[1]
if !exists('w:fzf_pushd')
return
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 && (!&autochdir || w:fzf_pushd.bufname ==# bufname(''))
execute w:fzf_pushd.command s:escape(w:fzf_pushd.origin)
endif
unlet w:fzf_pushd
endfunction
function! s:xterm_launcher()
let fmt = 'xterm -T "[fzf]" -bg "\%s" -fg "\%s" -geometry %dx%d+%d+%d -e bash -ic %%s'
let fmt = 'xterm -T "[fzf]" -bg "%s" -fg "%s" -geometry %dx%d+%d+%d -e bash -ic %%s'
if has('gui_macvim')
let fmt .= '&& osascript -e "tell application \"MacVim\" to activate"'
endif
return printf(fmt,
\ synIDattr(hlID("Normal"), "bg"), synIDattr(hlID("Normal"), "fg"),
\ escape(synIDattr(hlID("Normal"), "bg"), '#'), escape(synIDattr(hlID("Normal"), "fg"), '#'),
\ &columns, &lines/2, getwinposx(), getwinposy())
endfunction
unlet! s:launcher
@@ -498,6 +527,10 @@ endif
function! s:exit_handler(code, command, ...)
if a:code == 130
return 0
elseif has('nvim') && a:code == 129
" When deleting the terminal buffer while fzf is still running,
" Nvim sends SIGHUP.
return 0
elseif a:code > 1
call s:error('Error running ' . a:command)
if !empty(a:000)
@@ -534,9 +567,7 @@ function! s:execute(dict, command, use_height, temps) abort
let fzf.dict = a:dict
let fzf.temps = a:temps
function! fzf.on_exit(job_id, exit_status, event) dict
if s:present(self.dict, 'dir')
execute 'lcd' s:escape(self.dict.dir)
endif
call s:pushd(self.dict)
let lines = s:collect(self.temps)
call s:callback(self.dict, lines)
endfunction
@@ -563,9 +594,10 @@ endfunction
function! s:execute_tmux(dict, command, temps) abort
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
let command = join(['cd', fzf#shellescape(a:dict.dir), '&&', command])
let command = join(['cd', fzf#shellescape(cwd), '&&', command])
endif
call system(command)
@@ -587,8 +619,9 @@ function! s:calc_size(max, val, dict)
let srcsz = len(a:dict.source)
endif
let opts = s:evaluate_opts(get(a:dict, 'options', '')).$FZF_DEFAULT_OPTS
let opts = $FZF_DEFAULT_OPTS.' '.s:evaluate_opts(get(a:dict, 'options', ''))
let margin = stridx(opts, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2
let margin += stridx(opts, '--border') > stridx(opts, '--no-border') ? 2 : 0
let margin += stridx(opts, '--header') > stridx(opts, '--no-header')
return srcsz >= 0 ? min([srcsz + margin, size]) : size
endfunction
@@ -686,9 +719,7 @@ function! s:execute_term(dict, command, temps) abort
endfunction
try
if s:present(a:dict, 'dir')
execute 'lcd' s:escape(a:dict.dir)
endif
call s:pushd(a:dict)
if s:is_win
let fzf.temps.batchfile = s:fzf_tempname().'.bat'
call writefile(s:wrap_cmds(a:command), fzf.temps.batchfile)
@@ -706,9 +737,7 @@ function! s:execute_term(dict, command, temps) abort
endif
endif
finally
if s:present(a:dict, 'dir')
lcd -
endif
call s:dopopd()
endtry
setlocal nospell bufhidden=wipe nobuflisted nonumber
setf fzf
@@ -727,21 +756,9 @@ function! s:collect(temps) abort
endfunction
function! s:callback(dict, lines) abort
" Since anything can be done in the sink function, there is no telling that
" the change of the working directory was made by &autochdir setting.
"
" We use the following heuristic to determine whether to restore CWD:
" - Always restore the current directory when &autochdir is disabled.
" FIXME This makes it impossible to change directory from inside the sink
" function when &autochdir is not used.
" - In case of an error or an interrupt, a:lines will be empty.
" And it will be an array of a single empty string when fzf was finished
" without a match. In these cases, we presume that the change of the
" directory is not expected and should be undone.
let popd = has_key(a:dict, 'prev_dir') &&
\ (!&autochdir || (empty(a:lines) || len(a:lines) == 1 && empty(a:lines[0])))
let popd = has_key(a:dict, 'pushd')
if popd
let w:fzf_dir = [a:dict.prev_dir, a:dict.dir]
let w:fzf_pushd = a:dict.pushd
endif
try
@@ -765,7 +782,7 @@ function! s:callback(dict, lines) abort
" We may have opened a new window or tab
if popd
let w:fzf_dir = [a:dict.prev_dir, a:dict.dir]
let w:fzf_pushd = a:dict.pushd
call s:dopopd()
endif
endfunction

View File

@@ -9,6 +9,8 @@
# - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty)
if [[ $- =~ i ]]; then
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() {
@@ -121,11 +123,11 @@ _fzf_handle_dynamic_completion() {
if [ -n "$orig" ] && type "$orig" > /dev/null 2>&1; then
$orig "$@"
elif [ -n "$_fzf_completion_loader" ]; then
orig_complete=$(complete -p "$cmd" 2> /dev/null)
orig_complete=$(complete -p "$orig_cmd" 2> /dev/null)
_completion_loader "$@"
ret=$?
# _completion_loader may not have updated completion for the command
if [ "$(complete -p "$cmd" 2> /dev/null)" != "$orig_complete" ]; then
if [ "$(complete -p "$orig_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 }"
@@ -193,12 +195,13 @@ _fzf_complete() {
selected=$(cat | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" $fzf $1 -q "$cur" | $post | tr '\n' ' ')
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
printf '\e[5n'
if [ -n "$selected" ]; then
COMPREPLY=("$selected")
return 0
else
COMPREPLY=("$cur")
fi
printf '\e[5n'
return 0
else
shift
_fzf_handle_dynamic_completion "$cmd" "$@"
@@ -241,7 +244,7 @@ _fzf_complete_telnet() {
_fzf_complete_ssh() {
_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 ~/.ssh/config.d/* /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' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u
@@ -290,7 +293,7 @@ if type _completion_loader > /dev/null 2>&1; then
_fzf_completion_loader=1
fi
_fzf_defc() {
__fzf_defc() {
local cmd func opts orig_var orig def
cmd="$1"
func="$2"
@@ -307,16 +310,14 @@ _fzf_defc() {
# Anything
for cmd in $a_cmds; do
_fzf_defc "$cmd" _fzf_path_completion "-o default -o bashdefault"
__fzf_defc "$cmd" _fzf_path_completion "-o default -o bashdefault"
done
# Directory
for cmd in $d_cmds; do
_fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
done
unset _fzf_defc
# Kill completion
complete -F _fzf_complete_kill -o nospace -o default -o bashdefault kill
@@ -330,3 +331,23 @@ complete -F _fzf_complete_export -o default -o bashdefault export
complete -F _fzf_complete_unalias -o default -o bashdefault unalias
unset cmd d_cmds a_cmds x_cmds
_fzf_setup_completion() {
local kind fn cmd
kind=$1
fn=_fzf_${1}_completion
if [[ $# -lt 2 ]] || ! type -t "$fn" > /dev/null; then
echo "usage: ${FUNCNAME[0]} path|dir COMMANDS..."
return 1
fi
shift
for cmd in "$@"; do
eval "$(complete -p "$cmd" 2> /dev/null | grep -v "$fn" | __fzf_orig_completion_filter)"
case "$kind" in
dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;;
*) __fzf_defc "$cmd" "$fn" "-o default -o bashdefault" ;;
esac
done
}
fi

View File

@@ -9,6 +9,8 @@
# - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty)
if [[ $- =~ i ]]; then
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() {
@@ -60,8 +62,7 @@ __fzf_generic_path_completion() {
if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches$tail"
fi
zle redisplay
typeset -f zle-line-init >/dev/null && zle zle-line-init
zle reset-prompt
break
fi
dir=$(dirname "$dir")
@@ -100,8 +101,7 @@ _fzf_complete() {
if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches"
fi
zle redisplay
typeset -f zle-line-init >/dev/null && zle zle-line-init
zle reset-prompt
command rm -f "$fifo"
}
@@ -114,7 +114,8 @@ _fzf_complete_telnet() {
_fzf_complete_ssh() {
_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}') \
setopt localoptions nonomatch
command cat <(cat ~/.ssh/config ~/.ssh/config.d/* /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' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u
@@ -157,6 +158,12 @@ fzf-completion() {
trigger=${FZF_COMPLETION_TRIGGER-'**'}
[ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("")
# When the trigger starts with ';', it becomes a separate token
if [[ ${LBUFFER} = *"${tokens[-2]}${tokens[-1]}" ]]; then
tokens[-2]="${tokens[-2]}${tokens[-1]}"
tokens=(${tokens[0,-2]})
fi
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
# Kill completion (do not require trigger sequence)
if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then
@@ -165,8 +172,7 @@ fzf-completion() {
if [ -n "$matches" ]; then
LBUFFER="$LBUFFER$matches"
fi
zle redisplay
typeset -f zle-line-init >/dev/null && zle zle-line-init
zle reset-prompt
# Trigger sequence given
elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
@@ -195,3 +201,5 @@ fzf-completion() {
zle -N fzf-completion
bindkey '^I' fzf-completion
fi

View File

@@ -55,7 +55,7 @@ __fzf_history__() (
local line
shopt -u nocaseglob nocasematch
line=$(
HISTTIMEFORMAT= history |
HISTTIMEFORMAT= builtin history |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tac --sync -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
command grep '^ *[0-9]') &&
if [[ $- =~ H ]]; then

View File

@@ -40,14 +40,14 @@ function fzf_key_bindings
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 -l FISH_MAJOR (echo $FISH_VERSION | cut -f1 -d.)
set -l FISH_MINOR (echo $FISH_VERSION | cut -f2 -d.)
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
set -l FISH_MINOR (echo $version | cut -f2 -d.)
# 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
# before 2.4.0.
if [ "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \) ];
history -z | eval (__fzfcmd) --read0 -q '(commandline)' | perl -pe 'chomp if eof' | read -lz result
history -z | eval (__fzfcmd) --read0 --print0 -q '(commandline)' | read -lz result
and commandline -- $result
else
history | eval (__fzfcmd) -q '(commandline)' | read -l result

View File

@@ -8,7 +8,7 @@ __fsel() {
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | cut -b3-"}"
setopt localoptions pipefail 2> /dev/null
setopt localoptions pipefail no_aliases 2> /dev/null
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do
echo -n "${(q)item} "
done
@@ -29,8 +29,7 @@ __fzfcmd() {
fzf-file-widget() {
LBUFFER="${LBUFFER}$(__fsel)"
local ret=$?
zle redisplay
typeset -f zle-line-init >/dev/null && zle zle-line-init
zle reset-prompt
return $ret
}
zle -N fzf-file-widget
@@ -50,7 +49,7 @@ zle -N fzf-redraw-prompt
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 \
-o -type d -print 2> /dev/null | cut -b3-"}"
setopt localoptions pipefail 2> /dev/null
setopt localoptions pipefail no_aliases 2> /dev/null
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m)"
if [[ -z "$dir" ]]; then
zle redisplay
@@ -59,7 +58,6 @@ fzf-cd-widget() {
cd "$dir"
local ret=$?
zle fzf-redraw-prompt
typeset -f zle-line-init >/dev/null && zle zle-line-init
return $ret
}
zle -N fzf-cd-widget
@@ -68,7 +66,7 @@ bindkey '\ec' fzf-cd-widget
# CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() {
local selected num
setopt localoptions noglobsubst noposixbuiltins pipefail 2> /dev/null
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
selected=( $(fc -rl 1 |
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=$?
@@ -78,8 +76,7 @@ fzf-history-widget() {
zle vi-fetch-history -n $num
fi
fi
zle redisplay
typeset -f zle-line-init >/dev/null && zle zle-line-init
zle reset-prompt
return $ret
}
zle -N fzf-history-widget

View File

@@ -371,7 +371,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
// The first occurrence of each character in the pattern
offset32, F := alloc32(offset32, slab, M)
// Rune array
offset32, T := alloc32(offset32, slab, N)
_, T := alloc32(offset32, slab, N)
input.CopyRunes(T)
// Phase 2. Calculate bonus for each point
@@ -453,7 +453,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
copy(H, H0[f0:lastIdx+1])
// Possible length of consecutive chunk at each position.
offset16, C := alloc16(offset16, slab, width*M)
_, C := alloc16(offset16, slab, width*M)
copy(C, C0[f0:lastIdx+1])
Fsub := F[1:]

View File

@@ -32,6 +32,55 @@ func (s *ansiState) equals(t *ansiState) bool {
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr
}
func (s *ansiState) ToString() string {
if !s.colored() {
return ""
}
ret := ""
if s.attr&tui.Bold > 0 {
ret += "1;"
}
if s.attr&tui.Dim > 0 {
ret += "2;"
}
if s.attr&tui.Italic > 0 {
ret += "3;"
}
if s.attr&tui.Underline > 0 {
ret += "4;"
}
if s.attr&tui.Blink > 0 {
ret += "5;"
}
if s.attr&tui.Reverse > 0 {
ret += "7;"
}
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
return "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
}
func toAnsiString(color tui.Color, offset int) string {
col := int(color)
ret := ""
if col == -1 {
ret += strconv.Itoa(offset + 9)
} else if col < 8 {
ret += strconv.Itoa(offset + col)
} else if col < 16 {
ret += strconv.Itoa(offset - 30 + 90 + col - 8)
} else if col < 256 {
ret += strconv.Itoa(offset+8) + ";5;" + strconv.Itoa(col)
} else if col >= (1 << 24) {
r := strconv.Itoa((col >> 16) & 0xff)
g := strconv.Itoa((col >> 8) & 0xff)
b := strconv.Itoa(col & 0xff)
ret += strconv.Itoa(offset+8) + ";2;" + r + ";" + g + ";" + b
}
return ret + ";"
}
var ansiRegex *regexp.Regexp
func init() {

View File

@@ -2,6 +2,7 @@ package fzf
import (
"fmt"
"strings"
"testing"
"github.com/junegunn/fzf/src/tui"
@@ -156,3 +157,31 @@ func TestExtractColor(t *testing.T) {
assert((*offsets)[1], 6, 11, 200, 100, false)
})
}
func TestAnsiCodeStringConversion(t *testing.T) {
assert := func(code string, prevState *ansiState, expected string) {
state := interpretCode(code, prevState)
if expected != state.ToString() {
t.Errorf("expected: %s, actual: %s",
strings.Replace(expected, "\x1b[", "\\x1b[", -1),
strings.Replace(state.ToString(), "\x1b[", "\\x1b[", -1))
}
}
assert("\x1b[m", nil, "")
assert("\x1b[m", &ansiState{attr: tui.Blink}, "")
assert("\x1b[31m", nil, "\x1b[31;49m")
assert("\x1b[41m", nil, "\x1b[39;41m")
assert("\x1b[92m", nil, "\x1b[92;49m")
assert("\x1b[102m", nil, "\x1b[39;102m")
assert("\x1b[31m", &ansiState{fg: 4, bg: 4}, "\x1b[31;44m")
assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse}, "\x1b[1;2;7;31;49m")
assert("\x1b[38;5;100;48;5;200m", nil, "\x1b[38;5;100;48;5;200m")
assert("\x1b[48;5;100;38;5;200m", nil, "\x1b[38;5;200;48;5;100m")
assert("\x1b[48;5;100;38;2;10;20;30;1m", nil, "\x1b[1;38;2;10;20;30;48;5;100m")
assert("\x1b[48;5;100;38;2;10;20;30;7m",
&ansiState{attr: tui.Dim | tui.Italic, fg: 1, bg: 1},
"\x1b[2;3;7;38;2;10;20;30;48;5;100m")
}

View File

@@ -64,6 +64,13 @@ func (cl *ChunkList) Push(data []byte) bool {
return ret
}
// Clear clears the data
func (cl *ChunkList) Clear() {
cl.mutex.Lock()
cl.chunks = nil
cl.mutex.Unlock()
}
// Snapshot returns immutable snapshot of the ChunkList
func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
cl.mutex.Lock()

View File

@@ -1,6 +1,7 @@
package fzf
import (
"math"
"os"
"time"
@@ -9,7 +10,7 @@ import (
const (
// Current version
version = "0.17.4"
version = "0.19.0"
// Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond
@@ -22,9 +23,12 @@ const (
readerPollIntervalMax = 50 * time.Millisecond
// Terminal
initialDelay = 20 * time.Millisecond
initialDelayTac = 100 * time.Millisecond
spinnerDuration = 200 * time.Millisecond
initialDelay = 20 * time.Millisecond
initialDelayTac = 100 * time.Millisecond
spinnerDuration = 200 * time.Millisecond
previewCancelWait = 500 * time.Millisecond
maxPatternLength = 300
maxMulti = math.MaxInt32
// Matcher
numPartitionsMultiplier = 8
@@ -75,6 +79,7 @@ const (
)
const (
exitCancel = -1
exitOk = 0
exitNoMatch = 1
exitError = 2

View File

@@ -63,12 +63,14 @@ func Run(opts *Options, revision string) {
ansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) {
return util.ToChars(data), nil
}
var lineAnsiState, prevLineAnsiState *ansiState
if opts.Ansi {
if opts.Theme != nil {
var state *ansiState
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
trimmed, offsets, newState := extractColor(string(data), state, nil)
state = newState
prevLineAnsiState = lineAnsiState
trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
lineAnsiState = newState
return util.ToChars([]byte(trimmed)), offsets
}
} else {
@@ -100,6 +102,22 @@ func Run(opts *Options, revision string) {
} else {
chunkList = NewChunkList(func(item *Item, data []byte) bool {
tokens := Tokenize(string(data), opts.Delimiter)
if opts.Ansi && opts.Theme != nil && len(tokens) > 1 {
var ansiState *ansiState
if prevLineAnsiState != nil {
ansiStateDup := *prevLineAnsiState
ansiState = &ansiStateDup
}
for _, token := range tokens {
prevAnsiState := ansiState
_, _, ansiState = extractColor(token.text.ToString(), ansiState, nil)
if prevAnsiState != nil {
token.text.Prepend("\x1b[m" + prevAnsiState.ToString())
} else {
token.text.Prepend("\x1b[m")
}
}
}
trans := Transform(tokens, opts.WithNth)
transformed := joinTokens(trans)
if len(header) < opts.HeaderLines {
@@ -108,6 +126,7 @@ func Run(opts *Options, revision string) {
return false
}
item.text, item.colors = ansiProcessor([]byte(transformed))
item.text.TrimTrailingWhitespaces()
item.text.Index = itemIndex
item.origText = &data
itemIndex++
@@ -117,10 +136,11 @@ func Run(opts *Options, revision string) {
// Reader
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
var reader *Reader
if !streamingFilter {
reader := NewReader(func(data []byte) bool {
reader = NewReader(func(data []byte) bool {
return chunkList.Push(data)
}, eventBox, opts.ReadZero)
}, eventBox, opts.ReadZero, opts.Filter == nil)
go reader.ReadSource()
}
@@ -149,6 +169,7 @@ func Run(opts *Options, revision string) {
}
pattern := patternBuilder([]rune(*opts.Filter))
matcher.sort = pattern.sortable
found := false
if streamingFilter {
@@ -163,7 +184,7 @@ func Run(opts *Options, revision string) {
}
}
return false
}, eventBox, opts.ReadZero)
}, eventBox, opts.ReadZero, false)
reader.ReadSource()
} else {
eventBox.Unwatch(EvtReadNew)
@@ -204,10 +225,23 @@ func Run(opts *Options, revision string) {
// Event coordination
reading := true
ticks := 0
var nextCommand *string
restart := func(command string) {
reading = true
chunkList.Clear()
header = make([]string, 0, opts.HeaderLines)
go reader.restart(command)
}
eventBox.Watch(EvtReadNew)
for {
delay := true
ticks++
input := func() []rune {
if opts.Phony {
return []rune{}
}
return []rune(terminal.Input())
}
eventBox.Wait(func(events *util.Events) {
if _, fin := (*events)[EvtReadFin]; fin {
delete(*events, EvtReadNew)
@@ -216,21 +250,38 @@ func Run(opts *Options, revision string) {
switch evt {
case EvtReadNew, EvtReadFin:
reading = reading && evt == EvtReadNew
clearCache := false
if evt == EvtReadFin && nextCommand != nil {
clearCache = true
restart(*nextCommand)
nextCommand = nil
} else {
reading = reading && evt == EvtReadNew
}
snapshot, count := chunkList.Snapshot()
terminal.UpdateCount(count, !reading, value.(bool))
terminal.UpdateCount(count, !reading, value.(*string))
if opts.Sync {
terminal.UpdateList(PassMerger(&snapshot, opts.Tac))
}
matcher.Reset(snapshot, terminal.Input(), false, !reading, sort)
matcher.Reset(snapshot, input(), false, !reading, sort, clearCache)
case EvtSearchNew:
var command *string
switch val := value.(type) {
case bool:
sort = val
case searchRequest:
sort = val.sort
command = val.command
}
if command != nil {
if reading {
reader.terminate()
nextCommand = command
} else {
restart(*command)
}
}
snapshot, _ := chunkList.Snapshot()
matcher.Reset(snapshot, terminal.Input(), true, !reading, sort)
matcher.Reset(snapshot, input(), true, !reading, sort, command != nil)
delay = false
case EvtSearchProgress:

View File

@@ -59,7 +59,7 @@ func (h *History) append(line string) error {
lines := append(h.lines[:len(h.lines)-1], line)
if len(lines) > h.maxSize {
lines = lines[len(lines)-h.maxSize : len(lines)]
lines = lines[len(lines)-h.maxSize:]
}
h.lines = append(lines, "")
return ioutil.WriteFile(h.path, []byte(strings.Join(h.lines, "\n")), 0600)

View File

@@ -12,10 +12,11 @@ import (
// MatchRequest represents a search request
type MatchRequest struct {
chunks []*Chunk
pattern *Pattern
final bool
sort bool
chunks []*Chunk
pattern *Pattern
final bool
sort bool
clearCache bool
}
// Matcher is responsible for performing search
@@ -69,7 +70,7 @@ func (m *Matcher) Loop() {
events.Clear()
})
if request.sort != m.sort {
if request.sort != m.sort || request.clearCache {
m.sort = request.sort
m.mergerCache = make(map[string]*Merger)
clearChunkCache()
@@ -207,13 +208,13 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
return nil, wait()
}
if time.Now().Sub(startedAt) > progressMinDuration {
if time.Since(startedAt) > progressMinDuration {
m.eventBox.Set(EvtSearchProgress, float32(count)/float32(numChunks))
}
}
partialResults := make([][]Result, numSlices)
for _ = range slices {
for range slices {
partialResult := <-resultChan
partialResults[partialResult.index] = partialResult.matches
}
@@ -221,7 +222,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
}
// Reset is called to interrupt/signal the ongoing search
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool) {
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, clearCache bool) {
pattern := m.patternBuilder(patternRunes)
var event util.EventType
@@ -230,5 +231,5 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
} else {
event = reqRetry
}
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort})
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, clearCache})
}

View File

@@ -33,12 +33,13 @@ const usage = `usage: fzf [options]
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
+s, --no-sort Do not sort the result
--tac Reverse the order of the input
--phony Do not perform search
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
when the scores are tied [length|begin|end|index]
(default: length)
Interface
-m, --multi Enable multi-select with tab/shift-tab
-m, --multi[=MAX] Enable multi-select with tab/shift-tab
--no-mouse Disable mouse
--bind=KEYBINDS Custom key bindings. Refer to the man page.
--cycle Enable cyclic scroll
@@ -56,7 +57,7 @@ const usage = `usage: fzf [options]
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
--border Draw border above and below the finder
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
--inline-info Display finder info inline with the query
--info=STYLE Finder info style [default|inline|hidden]
--prompt=STR Input prompt (default: '> ')
--header=STR String to print as header
--header-lines=N The first N lines of the input are treated as header
@@ -141,12 +142,21 @@ const (
layoutReverseList
)
type infoStyle int
const (
infoDefault infoStyle = iota
infoInline
infoHidden
)
type previewOpts struct {
command string
position windowPosition
size sizeSpec
hidden bool
wrap bool
border bool
}
// Options stores the values of command-line options
@@ -154,6 +164,7 @@ type Options struct {
Fuzzy bool
FuzzyAlgo algo.Algo
Extended bool
Phony bool
Case Case
Normalize bool
Nth []Range
@@ -162,7 +173,7 @@ type Options struct {
Sort int
Tac bool
Criteria []criterion
Multi bool
Multi int
Ansi bool
Mouse bool
Theme *tui.ColorTheme
@@ -175,7 +186,7 @@ type Options struct {
Hscroll bool
HscrollOff int
FileWord bool
InlineInfo bool
InfoStyle infoStyle
JumpLabels string
Prompt string
Query string
@@ -189,12 +200,14 @@ type Options struct {
PrintQuery bool
ReadZero bool
Printer func(string)
PrintSep string
Sync bool
History *History
Header []string
HeaderLines int
Margin [4]sizeSpec
Bordered bool
Unicode bool
Tabstop int
ClearOnExit bool
Version bool
@@ -205,6 +218,7 @@ func defaultOptions() *Options {
Fuzzy: true,
FuzzyAlgo: algo.FuzzyMatchV2,
Extended: true,
Phony: false,
Case: CaseSmart,
Normalize: true,
Nth: make([]Range, 0),
@@ -213,7 +227,7 @@ func defaultOptions() *Options {
Sort: 1000,
Tac: false,
Criteria: []criterion{byScore, byLength},
Multi: false,
Multi: 0,
Ansi: false,
Mouse: true,
Theme: tui.EmptyTheme(),
@@ -225,7 +239,7 @@ func defaultOptions() *Options {
Hscroll: true,
HscrollOff: 10,
FileWord: false,
InlineInfo: false,
InfoStyle: infoDefault,
JumpLabels: defaultJumpLabels,
Prompt: "> ",
Query: "",
@@ -235,22 +249,24 @@ func defaultOptions() *Options {
ToggleSort: false,
Expect: make(map[int]string),
Keymap: make(map[int][]action),
Preview: previewOpts{"", posRight, sizeSpec{50, true}, false, false},
Preview: previewOpts{"", posRight, sizeSpec{50, true}, false, false, true},
PrintQuery: false,
ReadZero: false,
Printer: func(str string) { fmt.Println(str) },
PrintSep: "\n",
Sync: false,
History: nil,
Header: make([]string, 0),
HeaderLines: 0,
Margin: defaultMargin(),
Unicode: true,
Tabstop: 8,
ClearOnExit: true,
Version: false}
}
func help(code int) {
os.Stderr.WriteString(usage)
os.Stdout.WriteString(usage)
os.Exit(code)
}
@@ -310,13 +326,14 @@ func nextInt(args []string, i *int, message string) int {
return atoi(args[*i])
}
func optionalNumeric(args []string, i *int) int {
func optionalNumeric(args []string, i *int, defaultValue int) int {
if len(args) > *i+1 {
if strings.IndexAny(args[*i+1], "0123456789") == 0 {
*i++
return atoi(args[*i])
}
}
return 1 // Don't care
return defaultValue
}
func splitNth(str string) []Range {
@@ -381,7 +398,7 @@ func parseKeyChords(str string, message string) map[int]string {
}
tokens := strings.Split(str, ",")
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Index(str, ",,,") >= 0 {
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Contains(str, ",,,") {
tokens = append(tokens, ",")
}
@@ -409,6 +426,14 @@ func parseKeyChords(str string, message string) map[int]string {
chord = tui.BSpace
case "ctrl-space":
chord = tui.CtrlSpace
case "ctrl-^", "ctrl-6":
chord = tui.CtrlCaret
case "ctrl-/", "ctrl-_":
chord = tui.CtrlSlash
case "ctrl-\\":
chord = tui.CtrlBackSlash
case "ctrl-]":
chord = tui.CtrlRightBracket
case "change":
chord = tui.Change
case "alt-enter", "alt-return":
@@ -576,6 +601,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
theme.Current = ansi
case "bg+":
theme.DarkBg = ansi
case "gutter":
theme.Gutter = ansi
case "hl":
theme.Match = ansi
case "hl+":
@@ -621,13 +648,15 @@ func init() {
// Backreferences are not supported.
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
executeRegexp = regexp.MustCompile(
"(?si):(execute(?:-multi|-silent)?):.+|:(execute(?:-multi|-silent)?)(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
`(?si):(execute(?:-multi|-silent)?|reload):.+|:(execute(?:-multi|-silent)?|reload)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
}
func parseKeymap(keymap map[int][]action, str string) {
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
prefix := ":execute"
if src[len(prefix)] == '-' {
if strings.HasPrefix(src, ":reload") {
prefix = ":reload"
} else if src[len(prefix)] == '-' {
c := src[len(prefix)+1]
if c == 's' || c == 'S' {
prefix += "-silent"
@@ -780,6 +809,8 @@ func parseKeymap(keymap map[int][]action, str string) {
} else {
var offset int
switch t {
case actReload:
offset = len("reload")
case actExecuteSilent:
offset = len("execute-silent")
case actExecuteMulti:
@@ -815,6 +846,8 @@ func isExecuteAction(str string) actionType {
prefix = matches[0][2]
}
switch prefix {
case "reload":
return actReload
case "execute":
return actExecute
case "execute-silent":
@@ -880,6 +913,20 @@ func parseLayout(str string) layoutType {
return layoutDefault
}
func parseInfoStyle(str string) infoStyle {
switch str {
case "default":
return infoDefault
case "inline":
return infoInline
case "hidden":
return infoHidden
default:
errorExit("invalid info style (expected: default / inline / hidden)")
}
return infoDefault
}
func parsePreviewWindow(opts *previewOpts, input string) {
// Default
opts.position = posRight
@@ -891,6 +938,7 @@ func parsePreviewWindow(opts *previewOpts, input string) {
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
for _, token := range tokens {
switch token {
case "":
case "hidden":
opts.hidden = true
case "wrap":
@@ -903,6 +951,10 @@ func parsePreviewWindow(opts *previewOpts, input string) {
opts.position = posLeft
case "right":
opts.position = posRight
case "border":
opts.border = true
case "noborder":
opts.border = false
default:
if sizeRegex.MatchString(token) {
opts.size = parseSize(token, 99, "window size")
@@ -1007,6 +1059,10 @@ func parseOptions(opts *Options, allArgs []string) {
}
case "--no-expect":
opts.Expect = make(map[int]string)
case "--no-phony":
opts.Phony = false
case "--phony":
opts.Phony = true
case "--tiebreak":
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
case "--bind":
@@ -1027,7 +1083,7 @@ func parseOptions(opts *Options, allArgs []string) {
case "--with-nth":
opts.WithNth = splitNth(nextString(allArgs, &i, "nth expression required"))
case "-s", "--sort":
opts.Sort = optionalNumeric(allArgs, &i)
opts.Sort = optionalNumeric(allArgs, &i, 1)
case "+s", "--no-sort":
opts.Sort = 0
case "--tac":
@@ -1039,9 +1095,9 @@ func parseOptions(opts *Options, allArgs []string) {
case "+i":
opts.Case = CaseRespect
case "-m", "--multi":
opts.Multi = true
opts.Multi = optionalNumeric(allArgs, &i, maxMulti)
case "+m", "--no-multi":
opts.Multi = false
opts.Multi = 0
case "--ansi":
opts.Ansi = true
case "--no-ansi":
@@ -1081,10 +1137,15 @@ func parseOptions(opts *Options, allArgs []string) {
opts.FileWord = true
case "--no-filepath-word":
opts.FileWord = false
case "--info":
opts.InfoStyle = parseInfoStyle(
nextString(allArgs, &i, "info style required"))
case "--no-info":
opts.InfoStyle = infoHidden
case "--inline-info":
opts.InlineInfo = true
opts.InfoStyle = infoInline
case "--no-inline-info":
opts.InlineInfo = false
opts.InfoStyle = infoDefault
case "--jump-labels":
opts.JumpLabels = nextString(allArgs, &i, "label characters required")
validateJumpLabels = true
@@ -1102,8 +1163,10 @@ func parseOptions(opts *Options, allArgs []string) {
opts.ReadZero = false
case "--print0":
opts.Printer = func(str string) { fmt.Print(str, "\x00") }
opts.PrintSep = "\x00"
case "--no-print0":
opts.Printer = func(str string) { fmt.Println(str) }
opts.PrintSep = "\n"
case "--print-query":
opts.PrintQuery = true
case "--no-print-query":
@@ -1137,7 +1200,7 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Preview.command = ""
case "--preview-window":
parsePreviewWindow(&opts.Preview,
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:wrap][:hidden]"))
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:noborder][:wrap][:hidden]"))
case "--height":
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
case "--min-height":
@@ -1150,6 +1213,10 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Bordered = false
case "--border":
opts.Bordered = true
case "--no-unicode":
opts.Unicode = false
case "--unicode":
opts.Unicode = true
case "--margin":
opts.Margin = parseMargin(
nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
@@ -1178,12 +1245,16 @@ func parseOptions(opts *Options, allArgs []string) {
opts.WithNth = splitNth(value)
} else if match, _ := optString(arg, "-s", "--sort="); match {
opts.Sort = 1 // Don't care
} else if match, value := optString(arg, "-m", "--multi="); match {
opts.Multi = atoi(value)
} else if match, value := optString(arg, "--height="); match {
opts.Height = parseHeight(value)
} else if match, value := optString(arg, "--min-height="); match {
opts.MinHeight = atoi(value)
} else if match, value := optString(arg, "--layout="); match {
opts.Layout = parseLayout(value)
} else if match, value := optString(arg, "--info="); match {
opts.InfoStyle = parseInfoStyle(value)
} else if match, value := optString(arg, "--toggle-sort="); match {
parseToggleSort(opts.Keymap, value)
} else if match, value := optString(arg, "--expect="); match {

View File

@@ -52,6 +52,7 @@ type Pattern struct {
forward bool
text []rune
termSets []termSet
sortable bool
cacheable bool
cacheKey string
delimiter Delimiter
@@ -101,18 +102,27 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
}
caseSensitive := true
sortable := true
termSets := []termSet{}
if extended {
termSets = parseTerms(fuzzy, caseMode, normalize, asString)
// We should not sort the result if there are only inverse search terms
sortable = false
Loop:
for _, termSet := range termSets {
for idx, term := range termSet {
if !term.inv {
sortable = true
}
// If the query contains inverse search terms or OR operators,
// we cannot cache the search scope
if !cacheable || idx > 0 || term.inv || fuzzy && term.typ != termFuzzy || !fuzzy && term.typ != termExact {
cacheable = false
break Loop
if sortable {
// Can't break until we see at least one non-inverse term
break Loop
}
}
}
}
@@ -134,6 +144,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
forward: forward,
text: []rune(asString),
termSets: termSets,
sortable: sortable,
cacheable: cacheable,
nth: nth,
delimiter: delimiter,

View File

@@ -4,6 +4,8 @@ import (
"bufio"
"io"
"os"
"os/exec"
"sync"
"sync/atomic"
"time"
@@ -16,11 +18,17 @@ type Reader struct {
eventBox *util.EventBox
delimNil bool
event int32
finChan chan bool
mutex sync.Mutex
exec *exec.Cmd
command *string
killed bool
wait bool
}
// NewReader returns new Reader object
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, delimNil bool) *Reader {
return &Reader{pusher, eventBox, delimNil, int32(EvtReady)}
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, delimNil bool, wait bool) *Reader {
return &Reader{pusher, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, false, wait}
}
func (r *Reader) startEventPoller() {
@@ -29,9 +37,12 @@ func (r *Reader) startEventPoller() {
pollInterval := readerPollIntervalMin
for {
if atomic.CompareAndSwapInt32(ptr, int32(EvtReadNew), int32(EvtReady)) {
r.eventBox.Set(EvtReadNew, true)
r.eventBox.Set(EvtReadNew, (*string)(nil))
pollInterval = readerPollIntervalMin
} else if atomic.LoadInt32(ptr) == int32(EvtReadFin) {
if r.wait {
r.finChan <- true
}
return
} else {
pollInterval += readerPollIntervalStep
@@ -46,7 +57,37 @@ func (r *Reader) startEventPoller() {
func (r *Reader) fin(success bool) {
atomic.StoreInt32(&r.event, int32(EvtReadFin))
r.eventBox.Set(EvtReadFin, success)
if r.wait {
<-r.finChan
}
r.mutex.Lock()
ret := r.command
if success || r.killed {
ret = nil
}
r.mutex.Unlock()
r.eventBox.Set(EvtReadFin, ret)
}
func (r *Reader) terminate() {
r.mutex.Lock()
defer func() { r.mutex.Unlock() }()
r.killed = true
if r.exec != nil && r.exec.Process != nil {
util.KillCommand(r.exec)
} else {
os.Stdin.Close()
}
}
func (r *Reader) restart(command string) {
r.event = int32(EvtReady)
r.startEventPoller()
success := r.readFromCommand(nil, command)
r.fin(success)
}
// ReadSource reads data from the default command or from standard input
@@ -54,12 +95,13 @@ func (r *Reader) ReadSource() {
r.startEventPoller()
var success bool
if util.IsTty() {
// The default command for *nix requires bash
shell := "bash"
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
if len(cmd) == 0 {
// The default command for *nix requires bash
success = r.readFromCommand("bash", defaultCommand)
success = r.readFromCommand(&shell, defaultCommand)
} else {
success = r.readFromCommand("sh", cmd)
success = r.readFromCommand(nil, cmd)
}
} else {
success = r.readFromStdin()
@@ -102,16 +144,25 @@ func (r *Reader) readFromStdin() bool {
return true
}
func (r *Reader) readFromCommand(shell string, cmd string) bool {
listCommand := util.ExecCommandWith(shell, cmd)
out, err := listCommand.StdoutPipe()
func (r *Reader) readFromCommand(shell *string, command string) bool {
r.mutex.Lock()
r.killed = false
r.command = &command
if shell != nil {
r.exec = util.ExecCommandWith(*shell, command, true)
} else {
r.exec = util.ExecCommand(command, true)
}
out, err := r.exec.StdoutPipe()
if err != nil {
r.mutex.Unlock()
return false
}
err = listCommand.Start()
err = r.exec.Start()
r.mutex.Unlock()
if err != nil {
return false
}
r.feed(out)
return listCommand.Wait() == nil
return r.exec.Wait() == nil
}

View File

@@ -10,10 +10,9 @@ import (
func TestReadFromCommand(t *testing.T) {
strs := []string{}
eb := util.NewEventBox()
reader := Reader{
pusher: func(s []byte) bool { strs = append(strs, string(s)); return true },
eventBox: eb,
event: int32(EvtReady)}
reader := NewReader(
func(s []byte) bool { strs = append(strs, string(s)); return true },
eb, false, true)
reader.startEventPoller()
@@ -23,7 +22,7 @@ func TestReadFromCommand(t *testing.T) {
}
// Normal command
reader.fin(reader.readFromCommand("sh", `echo abc && echo def`))
reader.fin(reader.readFromCommand(nil, `echo abc && echo def`))
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
t.Errorf("%s", strs)
}
@@ -48,7 +47,7 @@ func TestReadFromCommand(t *testing.T) {
reader.startEventPoller()
// Failing command
reader.fin(reader.readFromCommand("sh", `no-such-command`))
reader.fin(reader.readFromCommand(nil, `no-such-command`))
strs = []string{}
if len(strs) > 0 {
t.Errorf("%s", strs)

View File

@@ -16,7 +16,6 @@ type colorOffset struct {
offset [2]int32
color tui.ColorPair
attr tui.Attr
index int32
}
type Result struct {

File diff suppressed because it is too large Load Diff

View File

@@ -30,92 +30,92 @@ func TestReplacePlaceholder(t *testing.T) {
t.Errorf("expected: %s, actual: %s", expected, result)
}
}
printsep := "\n"
// {}, preserve ansi
result = replacePlaceholder("echo {}", false, Delimiter{}, false, "query", items1)
result = replacePlaceholder("echo {}", false, Delimiter{}, printsep, false, "query", items1)
check("echo ' foo'\\''bar \x1b[31mbaz\x1b[m'")
// {}, strip ansi
result = replacePlaceholder("echo {}", true, Delimiter{}, false, "query", items1)
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items1)
check("echo ' foo'\\''bar baz'")
// {}, with multiple items
result = replacePlaceholder("echo {}", true, Delimiter{}, false, "query", items2)
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items2)
check("echo 'foo'\\''bar baz'")
// {..}, strip leading whitespaces, preserve ansi
result = replacePlaceholder("echo {..}", false, Delimiter{}, false, "query", items1)
result = replacePlaceholder("echo {..}", false, Delimiter{}, printsep, false, "query", items1)
check("echo 'foo'\\''bar \x1b[31mbaz\x1b[m'")
// {..}, strip leading whitespaces, strip ansi
result = replacePlaceholder("echo {..}", true, Delimiter{}, false, "query", items1)
result = replacePlaceholder("echo {..}", true, Delimiter{}, printsep, false, "query", items1)
check("echo 'foo'\\''bar baz'")
// {q}
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, false, "query", items1)
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, printsep, false, "query", items1)
check("echo ' foo'\\''bar baz' 'query'")
// {q}, multiple items
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, false, "query 'string'", items2)
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, printsep, false, "query 'string'", items2)
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ''query '\\''string'\\''''foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, false, "query 'string'", items2)
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, printsep, false, "query 'string'", items2)
check("echo 'foo'\\''bar baz''query '\\''string'\\''''foo'\\''bar baz'")
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, false, "query", items1)
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items1)
check("echo 'foo'\\''bar'/'baz'/'bazfoo'\\''bar'/'baz'/'foo'\\''bar'/' foo'\\''bar baz'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, false, "query", items2)
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items2)
check("echo 'foo'\\''bar'/'baz'/'baz'/'foo'\\''bar'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, false, "query", items2)
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, printsep, false, "query", items2)
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
// forcePlus
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, true, "query", items2)
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, true, "query", items2)
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
// Whitespace preserving flag with "'" delimiter
result = replacePlaceholder("echo {s1}", true, Delimiter{str: &delim}, false, "query", items1)
result = replacePlaceholder("echo {s1}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
check("echo ' foo'")
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, false, "query", items1)
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
check("echo 'bar baz'")
result = replacePlaceholder("echo {s}", true, Delimiter{str: &delim}, false, "query", items1)
result = replacePlaceholder("echo {s}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
check("echo ' foo'\\''bar baz'")
result = replacePlaceholder("echo {s..}", true, Delimiter{str: &delim}, false, "query", items1)
result = replacePlaceholder("echo {s..}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
check("echo ' foo'\\''bar baz'")
// Whitespace preserving flag with regex delimiter
regex = regexp.MustCompile("\\w+")
regex = regexp.MustCompile(`\w+`)
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, false, "query", items1)
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
check("echo ' '")
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, false, "query", items1)
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
check("echo ''\\'''")
result = replacePlaceholder("echo {s3}", true, Delimiter{regex: regex}, false, "query", items1)
result = replacePlaceholder("echo {s3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
check("echo ' '")
// No match
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, nil})
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, nil})
check("echo /")
// No match, but with selections
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, item1})
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, item1})
check("echo /' foo'\\''bar baz'")
// String delimiter
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, false, "query", items1)
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
// Regex delimiter
regex = regexp.MustCompile("[oa]+")
// foo'bar baz
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, false, "query", items1)
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
}

View File

@@ -238,7 +238,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
for _, part := range parts {
output.WriteString(part.ToString())
}
merged = util.ToChars([]byte(output.String()))
merged = util.ToChars(output.Bytes())
}
var prefixLength int32

View File

@@ -73,7 +73,7 @@ func TestTransform(t *testing.T) {
{
ranges := splitNth("1,2,3")
tx := Transform(tokens, ranges)
if string(joinTokens(tx)) != "abc: def: ghi: " {
if joinTokens(tx) != "abc: def: ghi: " {
t.Errorf("%s", tx)
}
}
@@ -95,7 +95,7 @@ func TestTransform(t *testing.T) {
{
ranges := splitNth("1..2,3,2..,1")
tx := Transform(tokens, ranges)
if string(joinTokens(tx)) != " abc: def: ghi: def: ghi: jkl abc:" ||
if joinTokens(tx) != " abc: def: ghi: def: ghi: jkl abc:" ||
len(tx) != 4 ||
tx[0].text.ToString() != " abc: def:" || tx[0].prefixLength != 0 ||
tx[1].text.ToString() != " ghi:" || tx[1].prefixLength != 12 ||

View File

@@ -27,7 +27,7 @@ const (
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 {
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
@@ -154,16 +154,18 @@ func (r *LightRenderer) findOffset() (row int, col int) {
for tries := 0; tries < offsetPollTries; tries++ {
bytes = r.getBytesInternal(bytes, tries > 0)
offsets := offsetRegexp.FindSubmatch(bytes)
if len(offsets) > 2 {
return atoi(string(offsets[1]), 0) - 1, atoi(string(offsets[2]), 0) - 1
if len(offsets) > 3 {
// 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
}
func repeat(s string, times int) string {
func repeat(r rune, times int) string {
if times > 0 {
return strings.Repeat(s, times)
return strings.Repeat(string(r), times)
}
return ""
}
@@ -297,6 +299,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
}
buffer = append(buffer, byte(c))
pc := c
for {
c, ok = r.getch(true)
if !ok {
@@ -306,9 +309,13 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
continue
}
break
} else if c == ESC && pc != c {
retries = r.escDelay / escPollInterval
} else {
retries = 0
}
retries = 0
buffer = append(buffer, byte(c))
pc = c
}
return buffer
@@ -338,6 +345,14 @@ func (r *LightRenderer) GetChar() Event {
return Event{BSpace, 0, nil}
case 0:
return Event{CtrlSpace, 0, nil}
case 28:
return Event{CtrlBackSlash, 0, nil}
case 29:
return Event{CtrlRightBracket, 0, nil}
case 30:
return Event{CtrlCaret, 0, nil}
case 31:
return Event{CtrlSlash, 0, nil}
case ESC:
ev := r.escSequence(&sz)
// Second chance
@@ -374,6 +389,8 @@ func (r *LightRenderer) escSequence(sz *int) Event {
alt = true
}
switch r.buffer[1] {
case ESC:
return Event{ESC, 0, nil}
case 32:
return Event{AltSpace, 0, nil}
case 47:
@@ -509,6 +526,9 @@ func (r *LightRenderer) escSequence(sz *int) Event {
if r.buffer[1] >= 'a' && r.buffer[1] <= 'z' {
return Event{AltA + int(r.buffer[1]) - 'a', 0, nil}
}
if r.buffer[1] >= '0' && r.buffer[1] <= '9' {
return Event{Alt0 + int(r.buffer[1]) - '0', 0, nil}
}
return Event{Invalid, 0, nil}
}
@@ -538,7 +558,7 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
r.prevDownTime = now
} else {
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
time.Now().Sub(r.prevDownTime) < doubleClickDuration {
time.Since(r.prevDownTime) < doubleClickDuration {
double = true
}
}
@@ -667,7 +687,7 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
}
func (w *LightWindow) drawBorder() {
switch w.border {
switch w.border.shape {
case BorderAround:
w.drawBorderAround()
case BorderHorizontal:
@@ -677,32 +697,30 @@ func (w *LightWindow) drawBorder() {
func (w *LightWindow) drawBorderHorizontal() {
w.Move(0, 0)
w.CPrint(ColBorder, AttrRegular, repeat("─", w.width))
w.CPrint(ColBorder, AttrRegular, repeat(w.border.horizontal, w.width))
w.Move(w.height-1, 0)
w.CPrint(ColBorder, AttrRegular, repeat("─", w.width))
w.CPrint(ColBorder, AttrRegular, repeat(w.border.horizontal, w.width))
}
func (w *LightWindow) drawBorderAround() {
w.Move(0, 0)
w.CPrint(ColBorder, AttrRegular, "┌"+repeat("─", w.width-2)+"┐")
w.CPrint(ColBorder, AttrRegular,
string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight))
for y := 1; y < w.height-1; y++ {
w.Move(y, 0)
w.CPrint(ColBorder, AttrRegular, "│")
w.cprint2(colDefault, w.bg, AttrRegular, repeat(" ", w.width-2))
w.CPrint(ColBorder, AttrRegular, "│")
w.CPrint(ColBorder, AttrRegular, string(w.border.vertical))
w.cprint2(colDefault, w.bg, AttrRegular, repeat(' ', w.width-2))
w.CPrint(ColBorder, AttrRegular, string(w.border.vertical))
}
w.Move(w.height-1, 0)
w.CPrint(ColBorder, AttrRegular, "└"+repeat("─", w.width-2)+"┘")
w.CPrint(ColBorder, AttrRegular,
string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight))
}
func (w *LightWindow) csi(code string) {
w.renderer.csi(code)
}
func (w *LightWindow) stderr(str string) {
w.renderer.stderr(str)
}
func (w *LightWindow) stderrInternal(str string, allowNLCR bool) {
w.renderer.stderrInternal(str, allowNLCR)
}
@@ -753,7 +771,7 @@ func (w *LightWindow) MoveAndClear(y int, x int) {
w.Move(y, x)
// We should not delete preview window on the right
// csi("K")
w.Print(repeat(" ", w.width-x))
w.Print(repeat(' ', w.width-x))
w.Move(y, x)
}
@@ -849,7 +867,7 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
width += w
str := string(r)
if r == '\t' {
str = repeat(" ", w)
str = repeat(' ', w)
}
if prefixLength+width <= max {
line += str

View File

@@ -61,12 +61,8 @@ func (w *TcellWindow) Refresh() {
}
w.lastX = 0
w.lastY = 0
switch w.borderStyle {
case BorderAround:
w.drawBorder(true)
case BorderHorizontal:
w.drawBorder(false)
}
w.drawBorder()
}
func (w *TcellWindow) FinishFill() {
@@ -288,6 +284,12 @@ func (r *FullscreenRenderer) GetChar() Event {
return Event{keyfn('z'), 0, nil}
case tcell.KeyCtrlSpace:
return Event{CtrlSpace, 0, nil}
case tcell.KeyCtrlBackslash:
return Event{CtrlBackSlash, 0, nil}
case tcell.KeyCtrlRightSq:
return Event{CtrlRightBracket, 0, nil}
case tcell.KeyCtrlUnderscore:
return Event{CtrlSlash, 0, nil}
case tcell.KeyBackspace2:
if alt {
return Event{AltBS, 0, nil}
@@ -382,12 +384,16 @@ func (r *FullscreenRenderer) GetChar() Event {
return Event{Invalid, 0, nil}
}
func (r *FullscreenRenderer) Pause(bool) {
_screen.Fini()
func (r *FullscreenRenderer) Pause(clear bool) {
if clear {
_screen.Fini()
}
}
func (r *FullscreenRenderer) Resume(bool) {
r.initScreen()
func (r *FullscreenRenderer) Resume(clear bool) {
if clear {
r.initScreen()
}
}
func (r *FullscreenRenderer) Close() {
@@ -566,7 +572,11 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
return w.fillString(str, NewColorPair(fg, bg), a)
}
func (w *TcellWindow) drawBorder(around bool) {
func (w *TcellWindow) drawBorder() {
if w.borderStyle.shape == BorderNone {
return
}
left := w.left
right := left + w.width
top := w.top
@@ -580,19 +590,19 @@ func (w *TcellWindow) drawBorder(around bool) {
}
for x := left; x < right; x++ {
_screen.SetContent(x, top, tcell.RuneHLine, nil, style)
_screen.SetContent(x, bot-1, tcell.RuneHLine, nil, style)
_screen.SetContent(x, top, w.borderStyle.horizontal, nil, style)
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
}
if around {
if w.borderStyle.shape == BorderAround {
for y := top; y < bot; y++ {
_screen.SetContent(left, y, tcell.RuneVLine, nil, style)
_screen.SetContent(right-1, y, tcell.RuneVLine, nil, style)
_screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
}
_screen.SetContent(left, top, tcell.RuneULCorner, nil, style)
_screen.SetContent(right-1, top, tcell.RuneURCorner, nil, style)
_screen.SetContent(left, bot-1, tcell.RuneLLCorner, nil, style)
_screen.SetContent(right-1, bot-1, tcell.RuneLRCorner, nil, style)
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
_screen.SetContent(right-1, top, w.borderStyle.topRight, nil, style)
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
_screen.SetContent(right-1, bot-1, w.borderStyle.bottomRight, nil, style)
}
}

View File

@@ -40,6 +40,12 @@ const (
ESC
CtrlSpace
// https://apple.stackexchange.com/questions/24261/how-do-i-send-c-that-is-control-slash-to-the-terminal
CtrlBackSlash
CtrlRightBracket
CtrlCaret
CtrlSlash
Invalid
Resize
Mouse
@@ -117,7 +123,7 @@ func (c Color) is24() bool {
const (
colUndefined Color = -2
colDefault = -1
colDefault Color = -1
)
const (
@@ -164,14 +170,11 @@ func (p ColorPair) Bg() Color {
return p.bg
}
func (p ColorPair) is24() bool {
return p.fg.is24() || p.bg.is24()
}
type ColorTheme struct {
Fg Color
Bg Color
DarkBg Color
Gutter Color
Prompt Color
Match Color
Current Color
@@ -200,14 +203,60 @@ type MouseEvent struct {
Mod bool
}
type BorderStyle int
type BorderShape int
const (
BorderNone BorderStyle = iota
BorderNone BorderShape = iota
BorderAround
BorderHorizontal
)
type BorderStyle struct {
shape BorderShape
horizontal rune
vertical rune
topLeft rune
topRight rune
bottomLeft rune
bottomRight rune
}
type BorderCharacter int
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
if unicode {
return BorderStyle{
shape: shape,
horizontal: '─',
vertical: '│',
topLeft: '┌',
topRight: '┐',
bottomLeft: '└',
bottomRight: '┘',
}
}
return BorderStyle{
shape: shape,
horizontal: '-',
vertical: '|',
topLeft: '+',
topRight: '+',
bottomLeft: '+',
bottomRight: '+',
}
}
func MakeTransparentBorder() BorderStyle {
return BorderStyle{
shape: BorderAround,
horizontal: ' ',
vertical: ' ',
topLeft: ' ',
topRight: ' ',
bottomLeft: ' ',
bottomRight: ' '}
}
type Renderer interface {
Init()
Pause(clear bool)
@@ -272,17 +321,19 @@ var (
Dark256 *ColorTheme
Light256 *ColorTheme
ColNormal ColorPair
ColPrompt ColorPair
ColMatch ColorPair
ColCurrent ColorPair
ColCurrentMatch ColorPair
ColSpinner ColorPair
ColInfo ColorPair
ColCursor ColorPair
ColSelected ColorPair
ColHeader ColorPair
ColBorder ColorPair
ColPrompt ColorPair
ColNormal ColorPair
ColMatch ColorPair
ColCursor ColorPair
ColSelected ColorPair
ColCurrent ColorPair
ColCurrentMatch ColorPair
ColCurrentCursor ColorPair
ColCurrentSelected ColorPair
ColSpinner ColorPair
ColInfo ColorPair
ColHeader ColorPair
ColBorder ColorPair
)
func EmptyTheme() *ColorTheme {
@@ -290,6 +341,7 @@ func EmptyTheme() *ColorTheme {
Fg: colUndefined,
Bg: colUndefined,
DarkBg: colUndefined,
Gutter: colUndefined,
Prompt: colUndefined,
Match: colUndefined,
Current: colUndefined,
@@ -312,6 +364,7 @@ func init() {
Fg: colDefault,
Bg: colDefault,
DarkBg: colBlack,
Gutter: colBlack,
Prompt: colBlue,
Match: colGreen,
Current: colYellow,
@@ -326,6 +379,7 @@ func init() {
Fg: colDefault,
Bg: colDefault,
DarkBg: 236,
Gutter: colUndefined,
Prompt: 110,
Match: 108,
Current: 254,
@@ -340,6 +394,7 @@ func init() {
Fg: colDefault,
Bg: colDefault,
DarkBg: 251,
Gutter: colUndefined,
Prompt: 25,
Match: 66,
Current: 237,
@@ -371,6 +426,7 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
theme.Fg = o(baseTheme.Fg, theme.Fg)
theme.Bg = o(baseTheme.Bg, theme.Bg)
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
theme.Gutter = o(theme.DarkBg, o(baseTheme.Gutter, theme.Gutter))
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
theme.Match = o(baseTheme.Match, theme.Match)
theme.Current = o(baseTheme.Current, theme.Current)
@@ -392,27 +448,31 @@ func initPalette(theme *ColorTheme) {
return ColorPair{fg, bg, idx}
}
if theme != nil {
ColNormal = pair(theme.Fg, theme.Bg)
ColPrompt = pair(theme.Prompt, theme.Bg)
ColNormal = pair(theme.Fg, theme.Bg)
ColMatch = pair(theme.Match, theme.Bg)
ColCursor = pair(theme.Cursor, theme.Gutter)
ColSelected = pair(theme.Selected, theme.Gutter)
ColCurrent = pair(theme.Current, theme.DarkBg)
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
ColCurrentSelected = pair(theme.Selected, theme.DarkBg)
ColSpinner = pair(theme.Spinner, theme.Bg)
ColInfo = pair(theme.Info, theme.Bg)
ColCursor = pair(theme.Cursor, theme.DarkBg)
ColSelected = pair(theme.Selected, theme.DarkBg)
ColHeader = pair(theme.Header, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg)
} else {
ColNormal = pair(colDefault, colDefault)
ColPrompt = pair(colDefault, colDefault)
ColNormal = pair(colDefault, colDefault)
ColMatch = pair(colDefault, colDefault)
ColCurrent = pair(colDefault, colDefault)
ColCurrentMatch = pair(colDefault, colDefault)
ColSpinner = pair(colDefault, colDefault)
ColInfo = pair(colDefault, colDefault)
ColCursor = pair(colDefault, colDefault)
ColSelected = pair(colDefault, colDefault)
ColCurrent = pair(colDefault, colDefault)
ColCurrentMatch = pair(colDefault, colDefault)
ColCurrentCursor = pair(colDefault, colDefault)
ColCurrentSelected = pair(colDefault, colDefault)
ColSpinner = pair(colDefault, colDefault)
ColInfo = pair(colDefault, colDefault)
ColHeader = pair(colDefault, colDefault)
ColBorder = pair(colDefault, colDefault)
}

View File

@@ -142,6 +142,11 @@ func (chars *Chars) TrailingWhitespaces() int {
return whitespaces
}
func (chars *Chars) TrimTrailingWhitespaces() {
whitespaces := chars.TrailingWhitespaces()
chars.slice = chars.slice[0 : len(chars.slice)-whitespaces]
}
func (chars *Chars) ToString() string {
if runes := chars.optionalRunes(); runes != nil {
return string(runes)
@@ -169,5 +174,13 @@ func (chars *Chars) CopyRunes(dest []rune) {
for idx, b := range chars.slice[:len(dest)] {
dest[idx] = rune(b)
}
return
}
func (chars *Chars) Prepend(prefix string) {
if runes := chars.optionalRunes(); runes != nil {
runes = append([]rune(prefix), runes...)
chars.slice = *(*[]byte)(unsafe.Pointer(&runes))
} else {
chars.slice = append([]byte(prefix), chars.slice...)
}
}

View File

@@ -9,17 +9,26 @@ import (
)
// 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")
if len(shell) == 0 {
shell = "sh"
}
return ExecCommandWith(shell, command)
return ExecCommandWith(shell, command, setpgid)
}
// ExecCommandWith executes the given command with the specified shell
func ExecCommandWith(shell string, command string) *exec.Cmd {
return exec.Command(shell, "-c", command)
func ExecCommandWith(shell string, command string, setpgid bool) *exec.Cmd {
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
@@ -27,7 +36,7 @@ func IsWindows() bool {
return false
}
// SetNonBlock executes syscall.SetNonblock on file descriptor
// SetNonblock executes syscall.SetNonblock on file descriptor
func SetNonblock(file *os.File, nonblock bool) {
syscall.SetNonblock(int(file.Fd()), nonblock)
}

View File

@@ -10,13 +10,15 @@ import (
)
// ExecCommand executes the given command with cmd
func ExecCommand(command string) *exec.Cmd {
return ExecCommandWith("cmd", command)
func ExecCommand(command string, setpgid bool) *exec.Cmd {
return ExecCommandWith("cmd", command, setpgid)
}
// ExecCommandWith executes the given command with cmd. _shell parameter is
// 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.SysProcAttr = &syscall.SysProcAttr{
HideWindow: false,
@@ -26,12 +28,17 @@ func ExecCommandWith(_shell string, command string) *exec.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
func IsWindows() bool {
return true
}
// SetNonBlock executes syscall.SetNonblock on file descriptor
// SetNonblock executes syscall.SetNonblock on file descriptor
func SetNonblock(file *os.File, nonblock bool) {
syscall.SetNonblock(syscall.Handle(file.Fd()), nonblock)
}

View File

@@ -8,10 +8,13 @@ Execute (fzf#run with dir option):
let cwd = getcwd()
let result = fzf#run({ 'source': 'git ls-files', 'options': '--filter=vdr', 'dir': g:dir })
AssertEqual ['fzf.vader'], result
AssertEqual 0, haslocaldir()
AssertEqual getcwd(), cwd
execute 'lcd' fnameescape(cwd)
let result = sort(fzf#run({ 'source': 'git ls-files', 'options': '--filter e', 'dir': g:dir }))
AssertEqual ['fzf.vader', 'test_go.rb'], result
AssertEqual 1, haslocaldir()
AssertEqual getcwd(), cwd
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
AssertEqual cwd, getcwd()
Execute (fzf#run with dir option and autochdir):
Execute (FIXME: fzf#run with dir option and autochdir):
set acd
let cwd = getcwd()
call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'})
" Working directory changed due to &acd
AssertEqual '/foobar', expand('%')
AssertEqual '/', getcwd()
Execute (fzf#run with dir option and autochdir when final cwd is same as dir):

View File

@@ -256,12 +256,12 @@ class TestGoFZF < TestBase
# Testing basic key bindings
tmux.send_keys '99', 'C-a', '1', 'C-f', '3', 'C-b', 'C-h', 'C-u', 'C-e', 'C-y', 'C-k', 'Tab', 'BTab'
tmux.until { |lines| lines[-2] == ' 856/100000' }
lines = tmux.capture
assert_equal '> 3910', lines[-4]
assert_equal ' 391', lines[-3]
assert_equal ' 856/100000', lines[-2]
assert_equal '> 391', lines[-1]
tmux.until do |lines|
'> 3910' == lines[-4] &&
' 391' == lines[-3] &&
' 856/100000' == lines[-2] &&
'> 391' == lines[-1]
end
tmux.send_keys :Enter
assert_equal '3910', readonce.chomp
@@ -277,7 +277,7 @@ class TestGoFZF < TestBase
def test_fzf_default_command_failure
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', 'FZF_DEFAULT_COMMAND=false'), :Enter
tmux.until { |lines| lines[-2].include?('FZF_DEFAULT_COMMAND failed') }
tmux.until { |lines| lines[-2].include?('Command failed: false') }
tmux.send_keys :Enter
end
@@ -371,6 +371,65 @@ class TestGoFZF < TestBase
assert_equal %w[3 2 5 6 8 7], readonce.split($INPUT_RECORD_SEPARATOR)
end
def test_multi_max
tmux.send_keys "seq 1 10 | #{FZF} -m 3 --bind A:select-all,T:toggle-all --preview 'echo [{+}]/{}'", :Enter
tmux.until { |lines| lines.item_count == 10 }
tmux.send_keys '1'
tmux.until do |lines|
lines[1].include?('[1]/1') && lines[-2].include?('2/10')
end
tmux.send_keys 'A'
tmux.until do |lines|
lines[1].include?('[1 10]/1') && lines[-2].include?('2/10 (2/3)')
end
tmux.send_keys :BSpace
tmux.until { |lines| lines[-2].include?('10/10 (2/3)') }
tmux.send_keys 'T'
tmux.until do |lines|
lines[1].include?('[2 3 4]/1') && lines[-2].include?('10/10 (3/3)')
end
%w[T A].each do |key|
tmux.send_keys key
tmux.until do |lines|
lines[1].include?('[1 5 6]/1') && lines[-2].include?('10/10 (3/3)')
end
end
tmux.send_keys :BTab
tmux.until do |lines|
lines[1].include?('[5 6]/2') && lines[-2].include?('10/10 (2/3)')
end
[:BTab, :BTab, 'A'].each do |key|
tmux.send_keys key
tmux.until do |lines|
lines[1].include?('[5 6 2]/3') && lines[-2].include?('10/10 (3/3)')
end
end
tmux.send_keys '2'
tmux.until { |lines| lines[-2].include?('1/10 (3/3)') }
tmux.send_keys 'T'
tmux.until do |lines|
lines[1].include?('[5 6]/2') && lines[-2].include?('1/10 (2/3)')
end
tmux.send_keys :BSpace
tmux.until { |lines| lines[-2].include?('10/10 (2/3)') }
tmux.send_keys 'A'
tmux.until do |lines|
lines[1].include?('[5 6 1]/1') && lines[-2].include?('10/10 (3/3)')
end
end
def test_with_nth
[true, false].each do |multi|
tmux.send_keys "(echo ' 1st 2nd 3rd/';
@@ -806,16 +865,22 @@ class TestGoFZF < TestBase
tmux.until { |lines| lines[-2].include? '(100)' }
tmux.send_keys :Tab, :Tab
tmux.until { |lines| lines[-2].include? '(98)' }
tmux.send_keys '100'
tmux.until { |lines| lines.match_count == 1 }
tmux.send_keys 'C-d'
tmux.until { |lines| lines[-2].include? '(97)' }
tmux.send_keys 'C-u'
tmux.until { |lines| lines.match_count == 100 }
tmux.send_keys 'C-d'
tmux.until { |lines| !lines[-2].include? '(' }
tmux.send_keys :Tab, :Tab
tmux.send_keys :BTab, :BTab
tmux.until { |lines| lines[-2].include? '(2)' }
tmux.send_keys 0
tmux.until { |lines| lines[-2].include? '10/100' }
tmux.send_keys 'C-a'
tmux.until { |lines| lines[-2].include? '(12)' }
tmux.send_keys :Enter
assert_equal %w[2 1 10 20 30 40 50 60 70 80 90 100],
assert_equal %w[1 2 10 20 30 40 50 60 70 80 90 100],
readonce.split($INPUT_RECORD_SEPARATOR)
end
@@ -873,7 +938,7 @@ class TestGoFZF < TestBase
def test_execute
output = '/tmp/fzf-test-execute'
opts = %[--bind \\"alt-a:execute(echo [{}] >> #{output}),alt-b:execute[echo /{}{}/ >> #{output}],C:execute:echo /{}{}{}/ >> #{output}\\"]
opts = %[--bind \\"alt-a:execute(echo /{}/ >> #{output}),alt-b:execute[echo /{}{}/ >> #{output}],C:execute:echo /{}{}{}/ >> #{output}\\"]
wait = ->(exp) { tmux.until { |lines| lines[-2].include? exp } }
writelines tempname, %w[foo'bar foo"bar foo$bar]
tmux.send_keys "cat #{tempname} | #{fzf opts}; sync", :Enter
@@ -898,7 +963,7 @@ class TestGoFZF < TestBase
wait['/3']
tmux.send_keys :Enter
readonce
assert_equal %w[[foo'bar] [foo'bar]
assert_equal %w[/foo'bar/ /foo'bar/
/foo"barfoo"bar/ /foo"barfoo"bar/
/foo$barfoo$barfoo$bar/],
File.readlines(output).map(&:chomp)
@@ -1371,7 +1436,7 @@ class TestGoFZF < TestBase
end
def test_preview_hidden
tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-\\$LINES-\\$COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter
tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-\\$FZF_PREVIEW_LINES-\\$FZF_PREVIEW_COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter
tmux.until { |lines| lines[-1] == '>' }
tmux.send_keys '?'
tmux.until { |lines| lines[-2] =~ / {1-1-1-[0-9]+}/ }
@@ -1400,21 +1465,30 @@ class TestGoFZF < TestBase
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 /}') }
#{FZF} --multi --preview 'echo {{2}/{s2}/{+2}/{+s2}/{q}/{n}/{+n}}'), :Enter
tmux.until { |lines| lines[1].include?('{1/1 /1/1 //0/0}') }
tmux.send_keys '123'
tmux.until { |lines| lines[1].include?('{////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.until { |lines| lines[1].include?('{1/1 /1/1 /1/0/0}') }
tmux.send_keys :BTab
tmux.until { |lines| lines[1].include?('{10/10 /1/1 /1}') }
tmux.until { |lines| lines[1].include?('{10/10 /1/1 /1/9/0}') }
tmux.send_keys :BTab
tmux.until { |lines| lines[1].include?('{10/10 /1 10/1 10 /1}') }
tmux.until { |lines| lines[1].include?('{10/10 /1 10/1 10 /1/9/0 9}') }
tmux.send_keys '2'
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /12}') }
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /12//0 9}') }
tmux.send_keys '3'
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /123}') }
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /123//0 9}') }
end
def test_preview_file
tmux.send_keys %[(echo foo bar; echo bar foo) | #{FZF} --multi --preview 'cat {+f} {+f2} {+nf} {+fn}' --print0], :Enter
tmux.until { |lines| lines[1].include?('foo barbar00') }
tmux.send_keys :BTab
tmux.until { |lines| lines[1].include?('foo barbar00') }
tmux.send_keys :BTab
tmux.until { |lines| lines[1].include?('foo barbar foobarfoo0101') }
end
def test_preview_q_no_match
@@ -1427,6 +1501,12 @@ class TestGoFZF < TestBase
tmux.until { |lines| !lines[1].include?('foo') }
end
def test_preview_q_no_match_with_initial_query
tmux.send_keys %(: | #{FZF} --preview 'echo foo {q}{q}' --query foo), :Enter
tmux.until { |lines| lines.match_count == 0 }
tmux.until { |lines| lines[1].include?('foofoo') }
end
def test_no_clear
tmux.send_keys "seq 10 | fzf --no-clear --inline-info --height 5 > #{tempname}", :Enter
prompt = '> < 10/10'
@@ -1436,6 +1516,11 @@ class TestGoFZF < TestBase
tmux.until { |lines| lines[-1] == prompt }
end
def test_info_hidden
tmux.send_keys 'seq 10 | fzf --info=hidden', :Enter
tmux.until { |lines| lines[-2] == '> 1' }
end
def test_change_top
tmux.send_keys %(seq 1000 | #{FZF} --bind change:top), :Enter
tmux.until { |lines| lines.match_count == 1000 }
@@ -1513,6 +1598,48 @@ class TestGoFZF < TestBase
assert_equal ['foo bar'], `#{FZF} -f'^foo\\ bar$' < #{tempname}`.lines.map(&:chomp)
assert_equal input.lines.count - 1, `#{FZF} -f'!^foo\\ bar$' < #{tempname}`.lines.count
end
def test_inverse_only_search_should_not_sort_the_result
# Filter
assert_equal(%w[aaaaa b ccc],
`printf '%s\n' aaaaa b ccc BAD | #{FZF} -f '!bad'`.lines.map(&:chomp))
# Interactive
tmux.send_keys(%[printf '%s\n' aaaaa b ccc BAD | #{FZF} -q '!bad'], :Enter)
tmux.until { |lines| lines.item_count == 4 && lines.match_count == 3 }
tmux.until { |lines| lines[-3] == '> aaaaa' }
tmux.until { |lines| lines[-4] == ' b' }
tmux.until { |lines| lines[-5] == ' ccc' }
end
def test_preview_correct_tab_width_after_ansi_reset_code
writelines tempname, ["\x1b[31m+\x1b[m\t\x1b[32mgreen"]
tmux.send_keys "#{FZF} --preview 'cat #{tempname}'", :Enter
tmux.until { |lines| lines[1].include?('+ green') }
end
def test_phony
tmux.send_keys %(seq 1000 | #{FZF} --query 333 --phony --preview 'echo {} {q}'), :Enter
tmux.until { |lines| lines.match_count == 1000 }
tmux.until { |lines| lines[1].include?('1 333') }
tmux.send_keys 'foo'
tmux.until { |lines| lines.match_count == 1000 }
tmux.until { |lines| lines[1].include?('1 333foo') }
end
def test_reload
tmux.send_keys %(seq 1000 | #{FZF} --bind 'change:reload(seq {q}),a:reload(seq 100),b:reload:seq 200' --header-lines 2 --multi 2), :Enter
tmux.until { |lines| lines.match_count == 998 }
tmux.send_keys 'a'
tmux.until { |lines| lines.item_count == 98 && lines.match_count == 98 }
tmux.send_keys 'b'
tmux.until { |lines| lines.item_count == 198 && lines.match_count == 198 }
tmux.send_keys :Tab
tmux.until { |lines| lines[-2].include?('(1/2)') }
tmux.send_keys '555'
tmux.until { |lines| lines.item_count == 553 && lines.match_count == 1 }
tmux.until { |lines| !lines[-2].include?('(1/2)') }
end
end
module TestShell