Compare commits

...

113 Commits

Author SHA1 Message Date
Junegunn Choi
34fe5ab143 0.26.0 2021-03-13 15:13:31 +09:00
Junegunn Choi
1b08f43f82 Advanced preview scroll offset expression to better support fixed header 2021-03-13 02:26:41 +09:00
Junegunn Choi
b24a2e2fdc Fix regression in preview window rendering 2021-03-12 21:23:16 +09:00
Junegunn Choi
4c4c6e626e Add support for preview window header
Fix #2373

  # Display top 3 lines as the fixed header
  fzf --preview 'bat --style=header,grid --color=always {}' --preview-window '~3'
2021-03-12 20:32:27 +09:00
Junegunn Choi
7310370a31 Fix truncation of colored line when --preview-window wrap is set
Fix #2346
2021-03-12 20:31:27 +09:00
Junegunn Choi
8ae94f0059 Fix premature truncation of colored line when --preview-window wrap is set
Fix #2346
2021-03-12 11:05:51 +09:00
Junegunn Choi
8fccf20892 Fix incorrect tab character handling
Fix #2372
2021-03-12 10:08:18 +09:00
Charlie Vieth
5a874ae241 Speed up ANSI code processing (#2368)
This commit speeds up the parsing/processing of ANSI escape codes by
roughly 7.5x. The speedup is mostly accomplished by replacing the regex
with dedicated parsing logic (nextAnsiEscapeSequence()) and reducing the
number of allocations in extractColor().

#### Benchmarks
```
name             old time/op    new time/op     delta
ExtractColor-16    4.89µs ± 5%     0.64µs ± 2%   -86.87%  (p=0.000 n=9+9)

name             old speed      new speed       delta
ExtractColor-16  25.6MB/s ± 5%  194.6MB/s ± 2%  +661.43%  (p=0.000 n=9+9)

name             old alloc/op   new alloc/op    delta
ExtractColor-16    1.37kB ± 0%     0.31kB ± 0%   -77.31%  (p=0.000 n=10+10)

name             old allocs/op  new allocs/op   delta
ExtractColor-16      48.0 ± 0%        4.0 ± 0%   -91.67%  (p=0.000 n=10+10)
```
2021-03-11 19:34:50 +09:00
Jannik Vieten
f4e1ed25f2 [fish] Make widgets work with --option= prefix (#2383)
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2021-03-08 22:59:56 +09:00
Junegunn Choi
cbfbb49ab4 [vim] Vim 8.0 compatibility
Fix #2367
2021-03-08 12:56:06 +09:00
solarizedalias
489b16efce [fzf-tmux] Adapt to tmux latest changes (#2379) 2021-03-08 12:44:36 +09:00
Junegunn Choi
b82c1693c0 Fix deadlocks 2021-03-08 00:08:10 +09:00
Junegunn Choi
019bfc4e35 Fix yet another deadlock
EventBox.Set should not be called while holding the terminal mutex

  goroutine 1 [semacquire]:
  sync.runtime_SemacquireMutex(0xc0001923bc, 0x1000001066200, 0x1)
          /usr/local/Cellar/go/1.16/libexec/src/runtime/sema.go:71 +0x47
  sync.(*Mutex).lockSlow(0xc0001923b8)
          /usr/local/Cellar/go/1.16/libexec/src/sync/mutex.go:138 +0x105
  sync.(*Mutex).Lock(...)
          /usr/local/Cellar/go/1.16/libexec/src/sync/mutex.go:81
  github.com/junegunn/fzf/src.(*Terminal).Input(0xc000192000, 0x0, 0x0, 0x0, 0x0)
          /fzf/src/terminal.go:581 +0x145
  github.com/junegunn/fzf/src.Run.func10(0xc00010c8a0, 0xc000092050, 0xa)
          /fzf/src/core.go:245 +0x37
  github.com/junegunn/fzf/src.Run.func11(0xc00011a4e0)
          /fzf/src/core.go:295 +0x5ce
  github.com/junegunn/fzf/src/util.(*EventBox).Wait(0xc00011a4e0, 0xc000127ec8)
          /fzf/src/util/eventbox.go:34 +0x5e
  github.com/junegunn/fzf/src.Run(0xc000180000, 0x11ac014, 0x6, 0x11ac158, 0x7)
          /fzf/src/core.go:251 +0xdac
  main.main()
          /fzf/main.go:13 +0x5a

  goroutine 11 [semacquire]:
  sync.runtime_SemacquireMutex(0xc00012c31c, 0xc00010e800, 0x1)
          /usr/local/Cellar/go/1.16/libexec/src/runtime/sema.go:71 +0x47
  sync.(*Mutex).lockSlow(0xc00012c318)
          /usr/local/Cellar/go/1.16/libexec/src/sync/mutex.go:138 +0x105
  sync.(*Mutex).Lock(0xc00012c318)
          /usr/local/Cellar/go/1.16/libexec/src/sync/mutex.go:81 +0x47
  github.com/junegunn/fzf/src/util.(*EventBox).Set(0xc00011a4e0, 0x7, 0x114eb40, 0x1265460)
          /fzf/src/util/eventbox.go:40 +0x3b
  github.com/junegunn/fzf/src.(*Terminal).killPreview(0xc000192000, 0x0)
          /fzf/src/terminal.go:1831 +0xa5
  github.com/junegunn/fzf/src.(*Terminal).exit(0xc000192000, 0xc000106e58)
          /fzf/src/terminal.go:1847 +0x75
  github.com/junegunn/fzf/src.(*Terminal).Loop.func8.1(0xc00011a540)
          /fzf/src/terminal.go:2148 +0x38f
  github.com/junegunn/fzf/src/util.(*EventBox).Wait(0xc00011a540, 0xc000106f90)
          /fzf/src/util/eventbox.go:34 +0x5e
  github.com/junegunn/fzf/src.(*Terminal).Loop.func8(0xc000192000, 0xc00010a2c0)
          /fzf/src/terminal.go:2077 +0xa5
  created by github.com/junegunn/fzf/src.(*Terminal).Loop
          /fzf/src/terminal.go:2072 +0x3e8
2021-03-07 23:35:19 +09:00
Junegunn Choi
dfda5c054a [actions] Install fish using apt-get
For some reason, `test_ctrl_r` and `test_ctrl_r_abort` are not passing
on GitHub Action runner with Fish 3.2.0.
2021-03-07 22:41:27 +09:00
Junegunn Choi
f657169616 Fix deadlock on exit 2021-03-07 21:44:08 +09:00
Junegunn Choi
4c06da8b70 Fix GitHub Action build
$USER is missing
2021-03-07 18:05:39 +09:00
yoshida.shinya
9fe2393a00 Add test cases for killing input command on terminate (#2381 #2382) 2021-03-07 11:36:00 +09:00
Junegunn Choi
e2e8d94b14 Kill input command on terminate
Fix #2381
Close #2382
2021-03-07 11:30:26 +09:00
bitterfox
4f9a7f8c87 Don't exit fzf by SIGINT while executing command (#2375)
Fix #2374

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2021-02-28 21:01:03 +09:00
Junegunn Choi
bb0502ff44 Check gofmt in make test 2021-02-28 18:28:21 +09:00
Junegunn Choi
c256442245 Fix typo 2021-02-28 18:27:21 +09:00
Jonathan Müller
1137404190 [vim] Add keepjump to switch_back() function (#2363)
Otherwise, the jump list will contain a (hidden) entry for the FZF buffer if `window: enew` is used.
2021-02-25 21:58:13 +09:00
Junegunn Choi
d57c6d0284 Update build script to build macOS arm64 binary
Close #2361
2021-02-25 21:31:15 +09:00
Junegunn Choi
76bbf57b3d Add select and deselect actions
Close #2358
2021-02-25 21:23:05 +09:00
Hiroki Konishi
806a47a7cc [vim] Remove unnecessary border management in nvim floating window (#2370) 2021-02-25 14:41:23 +09:00
Junegunn Choi
29851c18aa [vim] Force redraw by exiting and re-entering terminal mode
Workaround for Neovim v0.5.0-dev

https://github.com/junegunn/fzf/issues/2352#issuecomment-782894123
2021-02-22 21:46:28 +09:00
Junegunn Choi
dea950c2c8 [vim] Call feedkeys only when the destination buffer is a terminal
Fix #2352
Fix https://github.com/junegunn/fzf.vim/issues/1216

Close #2364
2021-02-22 00:22:12 +09:00
Junegunn Choi
a367dfb22e README.md: Better example 2021-02-17 16:44:54 +09:00
odeson24
9fe1a7b373 Remove redundant assignment (#2356)
Co-authored-by: Ryan Ou <ryanou@aetherai.com>
2021-02-17 10:28:43 +09:00
Hussein Esmail
8e2d21c548 Update README.md (#2353)
Remove Linuxbrew links since Linuxbrew has been merged into Homebrew

* https://brew.sh/2019/02/02/homebrew-2.0.0/
2021-02-17 10:24:35 +09:00
Junegunn Choi
bedf1cd357 [vim] Use tnoremap only when it's available
Fix #2357
2021-02-17 10:04:38 +09:00
Junegunn Choi
13f180a70c [vim] Stay in terminal mode if fzf#run is called from sink
Fix #2352
2021-02-15 13:58:49 +09:00
Junegunn Choi
6654239c94 0.25.1 2021-02-03 22:32:52 +09:00
Junegunn Choi
1b61e5e9e9 Clarification on FZF_DEFAULT_COMMAND 2021-02-03 19:40:05 +09:00
Marlon Richert
43b3b907f8 [zsh] Don't run precmd hooks in cd widget (#2340)
`precmd` hooks expect the Zsh Line Editor to not be active.
Running these when the ZLE is active can lead to unpredictable results.
See https://github.com/marlonrichert/zsh-autocomplete/issues/180
2021-02-03 19:26:17 +09:00
Junegunn Choi
fcd896508b [vim] fzf#run should ignore empty 'dir' argument
Fix #2343
2021-02-03 13:51:56 +09:00
Junegunn Choi
f55c990e86 Add close action
Close #2331
2021-02-02 00:11:05 +09:00
Naveen
d110372f99 Create codeql-analysis.yml (#2338) 2021-02-01 23:54:45 +09:00
Junegunn Choi
c862af09f2 Fix toggle-preview-wrap action
Fix #2336
2021-02-01 23:14:21 +09:00
Junegunn Choi
1cfeec0ca3 Fix segmentation fault on \x1b[0K
Fix #2339
2021-02-01 22:59:11 +09:00
step
a0649edc1e [man] Clarify that $SHELL is used to run commands (#2334)
SHELL is used for execute actions and the preview and default commands.
2021-02-01 20:07:42 +09:00
Kovarththanan Rajaratnam
0e0bcb3e10 Update README.md (#2337)
--phony was renamed to --disabled in d779ff7e6d
2021-02-01 20:02:20 +09:00
Nanda Lopes
686528d627 BUILD.md: Update Go version requirement (#2332)
src/options.go:463:9: undefined: strings.ReplaceAll
    note: module requires Go 1.13make: *** [Makefile:122: target/fzf-linux_amd64] Erro 2
2021-01-28 11:43:15 +09:00
jiangjianshan
3afa920151 [install.ps1] Change permission of the downloaded binary (#2308)
Fix #2256
2021-01-28 11:41:23 +09:00
Junegunn Choi
32c493e994 [Makefile] Restore 32-bit targets
Close #2328
2021-01-20 18:40:17 +09:00
Olivier Roques
1a76bdf891 [vim] Exit terminal mode before closing FZF window (#2326)
Fix https://github.com/junegunn/fzf.vim/issues/1216
2021-01-17 22:39:40 +09:00
Junegunn Choi
af48b3df29 Replace Travis CI badge 2021-01-15 10:21:36 +09:00
freddii
58ac1fb2fa Fix typos in source code (#2322) 2021-01-15 10:10:09 +09:00
Junegunn Choi
e922704f72 Migrate to GitHub Actions 2021-01-13 19:10:24 +09:00
Vlad Doster
c6115735c7 Update README.md (#2321)
- Correct spelling/grammar
2021-01-13 04:26:10 +09:00
Ruslan Sayfutdinov
9ddf5c72be [zsh] Properly reset prompt after completion (#2318) 2021-01-13 04:09:34 +09:00
Junegunn Choi
cc5640326b [man] Fix typo 2021-01-10 04:46:31 +09:00
calvin ardi
bf447d7703 Update default number version (#2307) 2021-01-08 13:35:46 +09:00
nicolasbarra
cbb008c938 Update README to upgrade using brew upgrade (#2309) 2021-01-07 16:50:08 +09:00
E.L.K
eaa0c52b45 Fix selection changed on terminal resize (#2306) 2021-01-04 04:20:31 +09:00
Elliott Sales de Andrade
82791f7efc Use more explicit int-to-string conversion.
This fixes the following errors with Go 1.15:
```
src/options.go:452:69: conversion from untyped int to string yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?)
src/options.go:463:33: conversion from untyped int to string yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?)
```
2021-01-03 14:38:09 +09:00
Junegunn Choi
8c533e34ea 0.25.0 2021-01-03 00:56:11 +09:00
Junegunn Choi
37708ad9cd Revert "[zsh] Use shell redirection (#2281)"
This reverts commit e9bc7331bd.

The change is no longer necessary since 090dee8.
2021-01-03 00:48:40 +09:00
Junegunn Choi
090dee857f Do not disable mouse on SIGCONT before SIGSTOP
Fix #2161
2021-01-03 00:43:56 +09:00
Junegunn Choi
d779ff7e6d Make search toggleable
- `--phony` renamed to `--disabled` for consistency
    - `--no-phony` is now `--enabled`
- Added `enable-search`, `disable-search`, and `toggle-search` actions
  for `--bind`
- Added `--color` options: `query` and `disabled`

Close #2303
2021-01-03 00:15:00 +09:00
Junegunn Choi
fd8858f8c9 [fzf-tmux] Disable CTRL-Z 2021-01-01 22:43:36 +09:00
Junegunn Choi
b234647a63 [shell] Disable CTRL-Z
Fix #2289
2021-01-01 22:36:45 +09:00
Junegunn Choi
6e93eefc82 Update vimdoc 2020-12-31 19:12:57 +09:00
yhu266
38fca30125 Add tag links for "doc/fzf.txt" 2020-12-31 19:12:57 +09:00
Junegunn Choi
012ee9ca85 [Makefile] Make sure to use bash 2020-12-31 12:57:57 +09:00
Junegunn Choi
151252e33a Add preview-top and preview-bottom actions 2020-12-31 12:57:57 +09:00
Junegunn Choi
7136cfc68b Fix alt-, for --expect 2020-12-31 03:38:46 +09:00
Junegunn Choi
408c04f25f Update test case for 'first' and 'last' 2020-12-30 18:43:16 +09:00
Junegunn Choi
7f8e0dbc40 Extend support for alt key chords
"alt-" with any case-sensitive character is allowed
2020-12-30 18:39:17 +09:00
Junegunn Choi
0de7ab18f6 Add "last" action to move the cursor to the last match
This is the opposite of "first" (previously known as "top").
2020-12-30 18:39:17 +09:00
林千里
e9bc7331bd [zsh] Use shell redirection (#2281)
zsh sends SIGCONT when running fzf in a pipe in certain cases,
causing mouse mode to become disabled

Fix #2101
2020-12-23 14:38:37 +09:00
Loic Nageleisen
797dd7c449 [Makefile] Support building on machines with uname -m == "arm64" (#2291) 2020-12-23 14:27:46 +09:00
Junegunn Choi
f37ccaa64f Prevent index out of range error
Fix #2293
2020-12-23 10:34:31 +09:00
Junegunn Choi
ab3937ee5a [vim] Allow closing Vim running fzf without confirmation
Close #2287
2020-12-16 21:44:28 +09:00
Junegunn Choi
00f4551a7b Revert "[zsh] Reload shared history before searching (#2251)"
This reverts commit b62a74b315.

https://github.com/junegunn/fzf/pull/2251#issuecomment-740551383
2020-12-09 00:02:35 +09:00
Junegunn Choi
257ddd028d Update CHANGELOG 2020-12-07 19:35:48 +09:00
Junegunn Choi
e0a22e76f8 Make --color attributes mergeable
So you can override the colors and still have the text attributes

    # Default colors and attributes
    fzf

    export FZF_DEFAULT_OPTS='--color hl👎underline,hl+👎underline:reverse'

    # Default colors with underline+reverse attributes
    fzf

    # Different colors with underline+reverse attributes
    fzf --color hl:176,hl+:177

Related: https://github.com/junegunn/fzf.vim/issues/1197#issuecomment-739804363
2020-12-07 19:11:00 +09:00
Junegunn Choi
00a3610331 0.24.4 2020-12-05 23:24:55 +09:00
Junegunn Choi
f502725120 Fix slice bound error on extremely narrow screen 2020-12-05 22:00:42 +09:00
Martin Polden
b62a74b315 [zsh] Reload shared history before searching (#2251) 2020-12-05 21:48:54 +09:00
Junegunn Choi
2ec382ae0e Add --preview-window follow option 2020-12-05 21:16:35 +09:00
Junegunn Choi
cbfee31593 Fix typo in test case 2020-12-04 20:39:52 +09:00
Junegunn Choi
6d647e13ff Add change-prompt action
Close #2270
2020-12-04 20:34:41 +09:00
Junegunn Choi
d2af3ff98d Change how hl:-1 or hl+:-1 is applied to text with background color 2020-12-04 19:27:43 +09:00
Junegunn Choi
052d17e66a Fix Travis OSX build 2020-12-03 10:33:45 +09:00
Junegunn Choi
a9bc954e17 Do not update Homebrew on Travis OSX build
https://docs.travis-ci.com/user/installing-dependencies/#installing-packages-on-macos

> By default, the Homebrew addon will not run brew update before
> installing packages. brew update can take a long time and slow down your
> builds.
2020-12-03 10:24:06 +09:00
Junegunn Choi
2983426771 Fix unit tests 2020-11-25 13:08:28 +09:00
ratijas
c61eb94b3f [zsh] Declare variable as local before assignment (#2266) 2020-11-25 11:21:30 +09:00
Junegunn Choi
3829eab1cf Support ANSI code for clearing the rest of the line (ESC[0K)
Some programs use it to set the background color for the whole line.

  fzf --preview "printf 'normal \x1b[42mgreen\x1b[0K \x1b[43myellow\x1b[m\nnormal again'"

  fzf --preview 'delta <(echo foo) <(echo bar) < /dev/tty'

Fix #2249
2020-11-25 01:49:48 +09:00
Junegunn Choi
3fe8eeedc5 Fix handling of arrow keys with alt and/or shift modifier
Fix #2254

- Properly handle extra chars in the buffer. Patch suggested by @mckelly2833.
- Support alt-arrow sequences in \e[1;3A format
- Support shift-alt-arrow sequences in \e[1;10A format
2020-11-24 19:51:19 +09:00
Junegunn Choi
1efef88b6e Improve trim function to handle longer strings
Fix #2258
2020-11-24 19:03:59 +09:00
Junegunn Choi
7acdaf0b43 [vim] &termwinkey may not be available
/cc @Caid11
2020-11-19 09:55:57 +09:00
Michal Domonkos
1ed25d76ba [vim] Clean up temp files on interrupt (#2252)
The clean-up is done in s:collect(), so let's make sure it's run before
we may terminate due to CTRL-C or ESC (or some other error code) in
s:exit_handler().
2020-11-17 14:37:14 +09:00
Junegunn Choi
474c1f5e32 [vim] Map CTRL-Z to <nop> 2020-11-15 21:28:07 +09:00
Junegunn Choi
8b71fea5dc [vim] Set termwinkey to allow CTRL-W
Fix https://github.com/junegunn/fzf.vim/issues/1176
2020-11-15 21:27:57 +09:00
Tomas Janousek
7bd99a22ee [bash-completion] Fix endless loop when completion.bash sourced twice
I forgot to add the "not _fzf" check into __fzf_orig_completion, so
invoking it twice would rewrite the _fzf_orig_completion_xxx variables
and then cause an endless loop when completion is requested.

Fixes: ef2c29d5d4 ("[bash-completion] Optimize __fzf_orig_completion_filter")
2020-11-14 10:29:05 +09:00
Tomas Janousek
75b8cca3b3 [bash-completion] Unexport _fzf_orig_completion_* variables 2020-11-13 02:16:54 +09:00
Tomas Janousek
ef2c29d5d4 [bash-completion] Optimize __fzf_orig_completion_filter
Commit d4ad4a25 slowed loading of completion.bash significantly (on my
laptop from 10 ms to 30 ms), then 54891d11 improved that (to 20 ms) but
it still stands out as the heavy part of my .bashrc.

Rewriting __fzf_orig_completion_filter to pure bash without forking to
sed/awk brings this back under 10 ms.

before:

    $ HISTFILE=/tmp/bashhist hyperfine 'bash --rcfile shell/completion.bash -i'
    Benchmark #1: bash --rcfile shell/completion.bash -i
      Time (mean ± σ):      21.2 ms ±   0.3 ms    [User: 24.9 ms, System: 6.4 ms]
      Range (min … max):    20.7 ms …  23.3 ms    132 runs

after:

    $ HISTFILE=/tmp/bashhist hyperfine 'bash --rcfile shell/completion.bash -i'
    Benchmark #1: bash --rcfile shell/completion.bash -i
      Time (mean ± σ):       9.6 ms ±   0.3 ms    [User: 8.0 ms, System: 2.2 ms]
      Range (min … max):     9.3 ms …  11.4 ms    298 runs

Fixes: d4ad4a25db ("[bash-completion] Fix default alias/variable completion")
Fixes: 54891d11e0 ("[bash-completion] Minor optimization")
2020-11-13 02:16:54 +09:00
Tomas Janousek
218b3c8274 [bash-completion] Move -F/_fzf filter to __fzf_orig_completion_filter
This prevents mistakes like the one fixed by the previous commit, and
also speeds bash startup a tiny bit:

before:

    $ HISTFILE=/tmp/bashhist hyperfine 'bash --rcfile shell/completion.bash -i'
    Benchmark #1: bash --rcfile shell/completion.bash -i
      Time (mean ± σ):      22.4 ms ±   0.6 ms    [User: 28.7 ms, System: 7.8 ms]
      Range (min … max):    21.7 ms …  25.2 ms    123 runs

after:

    $ HISTFILE=/tmp/bashhist hyperfine 'bash --rcfile shell/completion.bash -i'
    Benchmark #1: bash --rcfile shell/completion.bash -i
      Time (mean ± σ):      21.2 ms ±   0.3 ms    [User: 24.9 ms, System: 6.4 ms]
      Range (min … max):    20.7 ms …  23.3 ms    132 runs
2020-11-13 02:16:54 +09:00
Tomas Janousek
db9cb2ddda [bash-completion] Avoid empty _a, _v completions
This doesn't look right:

    $ complete | grep ' _.$'
    complete _a
    complete _v

The __fzf_orig_completion_filter invocation in _fzf_setup_completion
needs the /-F/ filter, just like all the other invocations.

Fixes: d4ad4a25db ("[bash-completion] Fix default alias/variable completion")
2020-11-13 02:16:54 +09:00
Junegunn Choi
722d66e85a 0.24.3 2020-11-09 20:41:59 +09:00
Junegunn Choi
f6269f0193 Add --padding option
Close #2241
2020-11-09 20:37:17 +09:00
Junegunn Choi
520eae817a Remove print statement for debugging 2020-11-09 19:17:33 +09:00
Junegunn Choi
d099941360 [vim] Fix double path separator issue on Windows
Fix https://github.com/junegunn/fzf.vim/issues/1141
2020-11-05 18:14:45 +09:00
Junegunn Choi
e3e76fa8c5 0.24.2 2020-11-03 23:32:24 +09:00
Junegunn Choi
2553806e79 Allow preview window height shorter than 3
Fix #2231
2020-11-03 22:04:01 +09:00
Junegunn Choi
1bcbc5a353 Fix regression where lines are skipped in the preview window
Fix #2239
2020-11-03 21:31:19 +09:00
Junegunn Choi
15d351b0f0 Use default bg color when fg is set to -1 with reverse attribute 2020-11-03 20:51:44 +09:00
Junegunn Choi
c144c95cda [vim] Set maxwidth and maxheight when creating a popup
For me, this fixes invalid popup size problem on Windows GVim
2020-10-31 22:27:58 +09:00
Junegunn Choi
f08f4fd87d [vim] Remove dead code 2020-10-31 22:21:06 +09:00
Junegunn Choi
f8aaeef218 Revert "Prefer LightRenderer on Windows if it's available"
This reverts commit 7915e365b3
due to https://github.com/junegunn/fzf.vim/issues/1152#issuecomment-719696495.
2020-10-31 02:53:10 +09:00
Junegunn Choi
7915e365b3 Prefer LightRenderer on Windows if it's available
Fix #1766
2020-10-31 01:41:57 +09:00
Junegunn Choi
1c68f81c37 [vim] See the last line of "fzf --version" output
The output may contain some unexpected warning messages from the shell
if it's not properly configured. While such extra messages should be
properly addressed by the user, we can ignore them by checking the
last line of the output instead of the first line.

Related: bd3a021ec1
2020-10-30 21:57:09 +09:00
47 changed files with 2140 additions and 892 deletions

36
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning
name: CodeQL
on:
push:
branches: [ master, devel ]
pull_request:
branches: [ master ]
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: ['go']
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
fetch-depth: 0
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

44
.github/workflows/linux.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
---
name: Test fzf on Linux
on:
push:
branches: [ master, devel ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
go: [1.14, 1.15]
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}
- name: Setup Ruby
uses: ruby/setup-ruby@v1.62.0
with:
ruby-version: 3.0.0
- name: Install packages
run: sudo apt-get install --yes zsh fish tmux
- name: Install Ruby gems
run: sudo gem install --no-document minitest:5.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1
- name: Rubocop
run: rubocop --require rubocop-minitest --require rubocop-performance
- name: Unit test
run: make test
- name: Integration test
run: make install && ./install --all && LC_ALL=C tmux new-session -d && ruby test/test_go.rb --verbose

44
.github/workflows/macos.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
---
name: Test fzf on macOS
on:
push:
branches: [ master, devel ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: macos-latest
strategy:
matrix:
go: [1.14, 1.15]
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}
- name: Setup Ruby
uses: ruby/setup-ruby@v1.62.0
with:
ruby-version: 3.0.0
- name: Install packages
run: HOMEBREW_NO_INSTALL_CLEANUP=1 brew install fish zsh tmux
- name: Install Ruby gems
run: gem install --no-document minitest:5.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1
- name: Rubocop
run: rubocop --require rubocop-minitest --require rubocop-performance
- name: Unit test
run: make test
- name: Integration test
run: make install && ./install --all && LC_ALL=C tmux new-session -d && ruby test/test_go.rb --verbose

View File

@@ -1,11 +0,0 @@
source = ["./dist/fzf-macos_darwin_amd64/fzf"]
bundle_id = "kr.junegunn.fzf"
apple_id {
username = "junegunn.c@gmail.com"
password = "@env:AC_PASSWORD"
}
sign {
application_identity = "Apple Development: junegunn.c@gmail.com"
}

View File

@@ -12,10 +12,25 @@ builds:
- darwin
goarch:
- amd64
- arm64
ldflags:
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
hooks:
post: gon .gon.hcl
post: |-
sh -c '
cat > /tmp/fzf-gon.hcl << EOF
source = ["./dist/fzf-macos_darwin_{{ .Arch }}/fzf"]
bundle_id = "kr.junegunn.fzf"
apple_id {
username = "junegunn.c@gmail.com"
password = "@env:AC_PASSWORD"
}
sign {
application_identity = "Apple Development: junegunn.c@gmail.com"
}
EOF
gon /tmp/fzf-gon.hcl
'
- goos:
- linux

View File

@@ -1,28 +0,0 @@
language: go
go:
- "1.14"
env: GO111MODULE=on
os:
- linux
- osx
dist: bionic
addons:
apt:
packages:
- fish
- zsh
sources:
sourceline: ppa:fish-shell/release-3
homebrew:
packages:
- fish
- tmux
update: true
install: gem install --no-document minitest:5.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1
script:
- make test
# LC_ALL=C to avoid escape codes in
# printf %q $'\355\205\214\354\212\244\355\212\270' on macOS. Bash on
# macOS is built without HANDLE_MULTIBYTE?
- make install && ./install --all && LC_ALL=C tmux new-session -d && ruby test/test_go.rb --verbose
- rubocop --require rubocop-minitest --require rubocop-performance

View File

@@ -6,7 +6,7 @@ Build instructions
### Prerequisites
- Go 1.11 or above
- Go 1.13 or above
### Using Makefile

View File

@@ -1,6 +1,107 @@
CHANGELOG
=========
0.26.0
------
- Added support for fixed header in preview window
```sh
# Display top 3 lines as the fixed header
fzf --preview 'bat --style=header,grid --color=always {}' --preview-window '~3'
```
- More advanced preview offset expression to better support the fixed header
```sh
# Preview with bat, matching line in the middle of the window below
# the fixed header of the top 3 lines
#
# ~3 Top 3 lines as the fixed header
# +{2} Base scroll offset extracted from the second field
# +3 Extra offset to compensate for the 3-line header
# /2 Put in the middle of the preview area
#
git grep --line-number '' |
fzf --delimiter : \
--preview 'bat --style=full --color=always --highlight-line {2} {1}' \
--preview-window '~3:+{2}+3/2'
```
- Added `select` and `deselect` action for unconditionally selecting or
deselecting a single item in `--multi` mode. Complements `toggle` action.
- Sigificant performance improvement in ANSI code processing
- Bug fixes and improvements
- Built with Go 1.16
0.25.1
------
- Added `close` action
- Close preview window if open, abort fzf otherwise
- Bug fixes and improvements
0.25.0
------
- Text attributes set in `--color` are not reset when fzf sees another
`--color` option for the same element. This allows you to put custom text
attributes in your `$FZF_DEFAULT_OPTS` and still have those attributes
even when you override the colors.
```sh
# Default colors and attributes
fzf
# Apply custom text attributes
export FZF_DEFAULT_OPTS='--color fg+:italic,hl:-1:underline,hl+:-1:reverse:underline'
fzf
# Different colors but you still have the attributes
fzf --color hl:176,hl+:177
# Write "regular" if you want to clear the attributes
fzf --color hl:176:regular,hl+:177:regular
```
- Renamed `--phony` to `--disabled`
- You can dynamically enable and disable the search functionality using the
new `enable-search`, `disable-search`, and `toggle-search` actions
- You can assign a different color to the query string for when search is disabled
```sh
fzf --color query:#ffffff,disabled:#999999 --bind space:toggle-search
```
- Added `last` action to move the cursor to the last match
- The opposite action `top` is renamed to `first`, but `top` is still
recognized as a synonym for backward compatibility
- Added `preview-top` and `preview-bottom` actions
- Extended support for alt key chords: alt with any case-sensitive single character
```sh
fzf --bind alt-,:first,alt-.:last
```
0.24.4
------
- Added `--preview-window` option `follow`
```sh
# Preview window will automatically scroll to the bottom
fzf --preview-window follow --preview 'for i in $(seq 100000); do
echo "$i"
sleep 0.01
(( i % 300 == 0 )) && printf "\033[2J"
done'
```
- Added `change-prompt` action
```sh
fzf --prompt 'foo> ' --bind $'a:change-prompt:\x1b[31mbar> '
```
- Bug fixes and improvements
0.24.3
------
- Added `--padding` option
```sh
fzf --margin 5% --padding 5% --border --preview 'cat {}' \
--color bg:#222222,preview-bg:#333333
```
0.24.2
------
- Bug fixes and improvements
0.24.1
------
- Fixed broken `--color=[bw|no]` option

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi
Copyright (c) 2013-2021 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,3 +1,4 @@
SHELL := bash
GO ?= go
GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
@@ -26,6 +27,7 @@ $(error Not on git repository; cannot determine $$FZF_REVISION)
endif
BUILD_FLAGS := -a -ldflags "-s -w -X main.version=$(VERSION) -X main.revision=$(REVISION)" -tags "$(TAGS)"
BINARY32 := fzf-$(GOOS)_386
BINARY64 := fzf-$(GOOS)_amd64
BINARYARM5 := fzf-$(GOOS)_arm5
BINARYARM6 := fzf-$(GOOS)_arm6
@@ -39,6 +41,10 @@ ifeq ($(UNAME_M),x86_64)
BINARY := $(BINARY64)
else ifeq ($(UNAME_M),amd64)
BINARY := $(BINARY64)
else ifeq ($(UNAME_M),i686)
BINARY := $(BINARY32)
else ifeq ($(UNAME_M),i386)
BINARY := $(BINARY32)
else ifeq ($(UNAME_M),armv5l)
BINARY := $(BINARYARM5)
else ifeq ($(UNAME_M),armv6l)
@@ -47,6 +53,8 @@ else ifeq ($(UNAME_M),armv7l)
BINARY := $(BINARYARM7)
else ifeq ($(UNAME_M),armv8l)
BINARY := $(BINARYARM8)
else ifeq ($(UNAME_M),arm64)
BINARY := $(BINARYARM8)
else ifeq ($(UNAME_M),aarch64)
BINARY := $(BINARYARM8)
else ifeq ($(UNAME_M),ppc64le)
@@ -58,12 +66,16 @@ endif
all: target/$(BINARY)
test: $(SOURCES)
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
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 \
github.com/junegunn/fzf/src/util
bench:
cd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" -run=Bench -bench=. -benchmem
install: bin/fzf
build:
@@ -107,6 +119,9 @@ endif
clean:
$(RM) -r dist target
target/$(BINARY32): $(SOURCES)
GOARCH=386 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARY64): $(SOURCES)
GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@
@@ -141,4 +156,4 @@ update:
$(GO) get -u
$(GO) mod tidy
.PHONY: all build release test install clean docker docker-test update
.PHONY: all build release test bench install clean docker docker-test update

View File

@@ -171,19 +171,23 @@ list:
- `element` is an fzf element to apply a color to:
| Element | Description |
| --- | --- |
| `fg` / `bg` / `hl` | Item (foreground / background / highlight) |
| `fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight) |
| `hl` / `hl+` | Highlighted substrings (normal / current) |
| `gutter` | Background of the gutter on the left |
| `pointer` | Pointer to the current line (`>`) |
| `marker` | Multi-select marker (`>`) |
| `border` | Border around the window (`--border` and `--preview`) |
| `header` | Header (`--header` or `--header-lines`) |
| `info` | Info line (match counters) |
| `spinner` | Streaming input indicator |
| `prompt` | Prompt before query (`> `) |
| Element | Description |
| --- | --- |
| `fg` / `bg` / `hl` | Item (foreground / background / highlight) |
| `fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight) |
| `preview-fg` / `preview-bg` | Preview window text and background |
| `hl` / `hl+` | Highlighted substrings (normal / current) |
| `gutter` | Background of the gutter on the left |
| `pointer` | Pointer to the current line (`>`) |
| `marker` | Multi-select marker (`>`) |
| `border` | Border around the window (`--border` and `--preview`) |
| `header` | Header (`--header` or `--header-lines`) |
| `info` | Info line (match counters) |
| `spinner` | Streaming input indicator |
| `query` | Query string |
| `disabled` | Query string when search is disabled |
| `prompt` | Prompt before query (`> `) |
| `pointer` | Pointer to the current line (`>`) |
- `component` specifies the component (`fg` / `bg`) from which to extract the
color when considering each of the following highlight groups
@@ -358,7 +362,7 @@ Our `:LS` command will be much more useful if we can pass a directory argument
to it, so that something like `:LS /tmp` is possible.
```vim
command! -bang -complete=dir -nargs=* LS
command! -bang -complete=dir -nargs=? LS
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
```
@@ -368,7 +372,7 @@ a unique name to our command and pass it as the first argument to `fzf#wrap`.
```vim
" The query history for this command will be stored as 'ls' inside g:fzf_history_dir.
" The name is ignored if g:fzf_history_dir is not defined.
command! -bang -complete=dir -nargs=* LS
command! -bang -complete=dir -nargs=? LS
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
```
@@ -406,7 +410,6 @@ The latest versions of Vim and Neovim include builtin terminal emulator
" Optional:
" - xoffset [float default 0.5 range [0 ~ 1]]
" - yoffset [float default 0.5 range [0 ~ 1]]
" - highlight [string default 'Comment']: Highlight group for border
" - border [string default 'rounded']: Border style
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
@@ -446,4 +449,4 @@ endif
The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi
Copyright (c) 2013-2021 Junegunn Choi

View File

@@ -1,4 +1,4 @@
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf.png" height="170" alt="fzf - a command-line fuzzy finder"> [![travis-ci](https://travis-ci.org/junegunn/fzf.svg?branch=master)](https://travis-ci.org/junegunn/fzf)
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf.png" height="170" alt="fzf - a command-line fuzzy finder"> [![github-actions](https://github.com/junegunn/fzf/workflows/Test%20fzf%20on%20Linux/badge.svg)](https://github.com/junegunn/fzf/actions)
===
fzf is a general-purpose command-line fuzzy finder.
@@ -17,7 +17,7 @@ Pros
- The most comprehensive feature set
- Flexible layout
- Batteries included
- Vim/Neovim plugin, key bindings and fuzzy auto-completion
- Vim/Neovim plugin, key bindings, and fuzzy auto-completion
Table of Contents
-----------------
@@ -25,7 +25,7 @@ Table of Contents
<!-- vim-markdown-toc GFM -->
* [Installation](#installation)
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
* [Using Homebrew](#using-homebrew)
* [Using git](#using-git)
* [Using Linux package managers](#using-linux-package-managers)
* [Windows](#windows)
@@ -84,9 +84,9 @@ stuff.
[bin]: https://github.com/junegunn/fzf/releases
### Using Homebrew or Linuxbrew
### Using Homebrew
You can use [Homebrew](http://brew.sh/) or [Linuxbrew](http://linuxbrew.sh/)
You can use [Homebrew](http://brew.sh/) (on macOS or Linux)
to install fzf.
```sh
@@ -166,12 +166,12 @@ For more installation options, see [README-VIM.md](README-VIM.md).
Upgrading fzf
-------------
fzf is being actively developed and you might want to upgrade it once in a
fzf is being actively developed, and you might want to upgrade it once in a
while. Please follow the instruction below depending on the installation
method used.
- git: `cd ~/.fzf && git pull && ./install`
- brew: `brew update; brew reinstall fzf`
- brew: `brew update; brew upgrade fzf`
- macports: `sudo port upgrade fzf`
- chocolatey: `choco upgrade fzf`
- vim-plug: `:PlugUpdate fzf`
@@ -217,7 +217,7 @@ cursor with `--height` option.
vim $(fzf --height 40%)
```
Also check out `--reverse` and `--layout` options if you prefer
Also, check out `--reverse` and `--layout` options if you prefer
"top-down" layout instead of the default "bottom-up" layout.
```sh
@@ -264,6 +264,13 @@ or `py`.
- `FZF_DEFAULT_COMMAND`
- Default command to use when input is tty
- e.g. `export FZF_DEFAULT_COMMAND='fd --type f'`
- > :warning: This variable is not used by shell extensions due to the
> slight difference in requirements.
>
> (e.g. `CTRL-T` runs `$FZF_CTRL_T_COMMAND` instead, `vim **<tab>` runs
> `_fzf_compgen_path()`, and `cd **<tab>` runs `_fzf_compgen_dir()`)
>
> The available options are described later in this document.
- `FZF_DEFAULT_OPTS`
- Default options
- e.g. `export FZF_DEFAULT_OPTS="--layout=reverse --inline-info"`
@@ -331,7 +338,7 @@ fish.
- Set `FZF_ALT_C_COMMAND` to override the default command
- Set `FZF_ALT_C_OPTS` to pass additional options
If you're on a tmux session, you can start fzf in a tmux split pane or in
If you're on a tmux session, you can start fzf in a tmux split-pane or in
a tmux popup window by setting `FZF_TMUX_OPTS` (e.g. `-d 40%`).
See `fzf-tmux --help` for available options.
@@ -343,12 +350,12 @@ Fuzzy completion for bash and zsh
#### Files and directories
Fuzzy completion for files and directories can be triggered if the word before
the cursor ends with the trigger sequence which is by default `**`.
the cursor ends with the trigger sequence, which is by default `**`.
- `COMMAND [DIRECTORY/][FUZZY_PATTERN]**<TAB>`
```sh
# Files under current directory
# Files under the current directory
# - You can select multiple items with TAB key
vim **<TAB>
@@ -372,7 +379,7 @@ cd ~/github/fzf**<TAB>
#### Process IDs
Fuzzy completion for PIDs is provided for kill command. In this case,
there is no trigger sequence, just press tab key after kill command.
there is no trigger sequence; just press the tab key after the kill command.
```sh
# Can select multiple processes with <TAB> or <Shift-TAB> keys
@@ -381,7 +388,7 @@ kill -9 <TAB>
#### Host names
For ssh and telnet commands, fuzzy completion for host names is provided. The
For ssh and telnet commands, fuzzy completion for hostnames is provided. The
names are extracted from /etc/hosts and ~/.ssh/config.
```sh
@@ -404,7 +411,7 @@ unalias **<TAB>
export FZF_COMPLETION_TRIGGER='~~'
# Options to fzf command
export FZF_COMPLETION_OPTS='+c -x'
export FZF_COMPLETION_OPTS='--border --info=inline'
# Use fd (https://github.com/sharkdp/fd) instead of the default find
# command for listing path candidates.
@@ -469,11 +476,11 @@ _fzf_complete_doge() {
- The arguments before `--` are the options to fzf.
- After `--`, simply pass the original completion arguments unchanged (`"$@"`).
- Then write a set of commands that generates the completion candidates and
- Then, write a set of commands that generates the completion candidates and
feed its output to the function using process substitution (`< <(...)`).
zsh will automatically pick up the function using the naming convention but in
bash you have to manually associate the function with the command using
bash you have to manually associate the function with the command using the
`complete` command.
```sh
@@ -509,12 +516,12 @@ Advanced topics
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.
options that affect performance.
- `--ansi` tells fzf to extract and parse ANSI color codes in the input and it
- `--ansi` tells fzf to extract and parse ANSI color codes in the input, and it
makes the initial scanning slower. So it's not recommended that you add it
to your `$FZF_DEFAULT_OPTS`.
- `--nth` makes fzf slower as fzf has to tokenize each line.
- `--nth` makes fzf slower because it has to tokenize each line.
- `--with-nth` makes fzf slower as fzf has to tokenize and reassemble each
line.
- If you absolutely need better performance, you can consider using
@@ -563,7 +570,7 @@ FZF_DEFAULT_COMMAND='find . -type f' \
#### 3. Interactive ripgrep integration
The following example uses fzf as the selector interface for ripgrep. We bound
`reload` action to `change` event, so every time you type on fzf, ripgrep
`reload` action to `change` event, so every time you type on fzf, the ripgrep
process will restart with the updated query string denoted by the placeholder
expression `{q}`. Also, note that we used `--phony` option so that fzf doesn't
perform any secondary filtering.
@@ -573,7 +580,7 @@ INITIAL_QUERY=""
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \
fzf --bind "change:reload:$RG_PREFIX {q} || true" \
--ansi --phony --query "$INITIAL_QUERY" \
--ansi --disabled --query "$INITIAL_QUERY" \
--height=50% --layout=reverse
```
@@ -589,7 +596,7 @@ Your `$SHELL` is used to execute the command with `$SHELL -c COMMAND`.
The window can be scrolled using the mouse or custom key bindings.
```bash
# {} is replaced to the single-quoted string of the focused line
# {} is replaced with the single-quoted string of the focused line
fzf --preview 'cat {}'
```
@@ -661,7 +668,7 @@ fzf
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
```
If you want the command to follow symbolic links, and don't want it to exclude
If you want the command to follow symbolic links and don't want it to exclude
hidden files, use the following command:
```sh
@@ -698,4 +705,4 @@ https://github.com/junegunn/fzf/wiki/Related-projects
The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi
Copyright (c) 2013-2021 Junegunn Choi

View File

@@ -135,8 +135,8 @@ if [[ -z "$TMUX" ]]; then
exit $?
fi
# --height option is not allowed
args=("${args[@]}" "--no-height")
# --height option is not allowed. CTRL-Z is also disabled.
args=("${args[@]}" "--no-height" "--bind=ctrl-z:ignore")
# Handle zoomed tmux pane without popup options by moving it to a temp window
if [[ ! "$opt" =~ "-K -E" ]] && tmux list-panes -F '#F' | grep -q Z; then
@@ -204,7 +204,16 @@ if [[ "$opt" =~ "-K -E" ]]; then
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; out=\$? $close; exit \$out" >> $argsf
cat <&0 > $fifo1 &
fi
tmux popup -d "$PWD" "${tmux_args[@]}" $opt -R "bash $argsf" > /dev/null 2>&1
# tmux dropped the support for `-K`, `-R` to popup command
# TODO: We can remove this once tmux 3.2 is released
if [[ ! "$(tmux popup --help 2>&1)" =~ '-R shell-command' ]]; then
opt="${opt/-K/}"
else
opt="${opt} -R"
fi
tmux popup -d "$PWD" "${tmux_args[@]}" $opt "bash $argsf" > /dev/null 2>&1
exit $?
fi

View File

@@ -1,22 +1,22 @@
fzf.txt fzf Last change: October 18 2020
fzf.txt fzf Last change: January 3 2021
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
==============================================================================
FZF Vim integration
Installation
Summary
:FZF[!]
Configuration
Examples
Explanation of g:fzf_colors
fzf#run
fzf#wrap
Global options supported by fzf#wrap
Tips
fzf inside terminal buffer
Starting fzf in a popup window
Hide statusline
License
FZF Vim integration |fzf-vim-integration|
Installation |fzf-installation|
Summary |fzf-summary|
:FZF[!] |:FZF|
Configuration |fzf-configuration|
Examples |fzf-examples|
Explanation of g:fzf_colors |fzf-explanation-of-gfzfcolors|
fzf#run |fzf#run|
fzf#wrap |fzf#wrap|
Global options supported by fzf#wrap |fzf-global-options-supported-by-fzf#wrap|
Tips |fzf-tips|
fzf inside terminal buffer |fzf-inside-terminal-buffer|
Starting fzf in a popup window |fzf-starting-fzf-in-a-popup-window|
Hide statusline |fzf-hide-statusline|
License |fzf-license|
FZF VIM INTEGRATION *fzf-vim-integration*
==============================================================================
@@ -200,21 +200,25 @@ list:
<
- `element` is an fzf element to apply a color to:
----------------------+------------------------------------------------------
Element | Description ~
----------------------+------------------------------------------------------
`fg` / `bg` / `hl` | Item (foreground / background / highlight)
`fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight)
`hl` / `hl+` | Highlighted substrings (normal / current)
`gutter` | Background of the gutter on the left
`pointer` | Pointer to the current line ( `>` )
`marker` | Multi-select marker ( `>` )
`border` | Border around the window ( `--border` and `--preview` )
`header` | Header ( `--header` or `--header-lines` )
`info` | Info line (match counters)
`spinner` | Streaming input indicator
`prompt` | Prompt before query ( `>` )
----------------------+------------------------------------------------------
----------------------------+------------------------------------------------------
Element | Description ~
----------------------------+------------------------------------------------------
`fg` / `bg` / `hl` | Item (foreground / background / highlight)
`fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight)
`preview-fg` / `preview-bg` | Preview window text and background
`hl` / `hl+` | Highlighted substrings (normal / current)
`gutter` | Background of the gutter on the left
`pointer` | Pointer to the current line ( `>` )
`marker` | Multi-select marker ( `>` )
`border` | Border around the window ( `--border` and `--preview` )
`header` | Header ( `--header` or `--header-lines` )
`info` | Info line (match counters)
`spinner` | Streaming input indicator
`query` | Query string
`disabled` | Query string when search is disabled
`prompt` | Prompt before query ( `>` )
`pointer` | Pointer to the current line ( `>` )
----------------------------+------------------------------------------------------
- `component` specifies the component (`fg` / `bg`) from which to extract the
color when considering each of the following highlight groups
- `group1[,group2,...]` is a list of highlight groups that are searched (in
@@ -314,9 +318,8 @@ following options are allowed:
- Optional:
- `yoffset` [float default 0.5 range [0 ~ 1]]
- `xoffset` [float default 0.5 range [0 ~ 1]]
- `highlight` [string default `'Comment'`]: Highlight group for border
- `border` [string default `rounded`]: Border style
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right`
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`
FZF#WRAP
@@ -420,7 +423,6 @@ Starting fzf in a popup window~
" Optional:
" - xoffset [float default 0.5 range [0 ~ 1]]
" - yoffset [float default 0.5 range [0 ~ 1]]
" - highlight [string default 'Comment']: Highlight group for border
" - border [string default 'rounded']: Border style
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
@@ -458,7 +460,7 @@ LICENSE *fzf-license*
The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi
Copyright (c) 2013-2021 Junegunn Choi
==============================================================================
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:

2
go.sum
View File

@@ -17,6 +17,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90Pveol
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
@@ -32,4 +33,5 @@ golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@@ -2,7 +2,7 @@
set -u
version=0.24.1
version=0.26.0
auto_completion=
key_bindings=
update_config=2
@@ -168,7 +168,8 @@ archi=$(uname -sm)
binary_available=1
binary_error=""
case "$archi" in
Darwin\ *64) download fzf-$version-darwin_amd64.tar.gz ;;
# Darwin\ arm64) download fzf-$version-darwin_arm64.tar.gz ;; # TODO
Darwin\ x86_64) download fzf-$version-darwin_amd64.tar.gz ;;
Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;

View File

@@ -1,4 +1,4 @@
$version="0.24.1"
$version="0.26.0"
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
@@ -43,7 +43,11 @@ function download {
$url="https://github.com/junegunn/fzf/releases/download/$version/$file"
$temp=$env:TMP + "\fzf.zip"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
(New-Object Net.WebClient).DownloadFile($url, $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$temp"))
if ($PSVersionTable.PSVersion.Major -ge 3) {
Invoke-WebRequest -Uri $url -OutFile $temp
} else {
(New-Object Net.WebClient).DownloadFile($url, $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$temp"))
}
if ($?) {
(Microsoft.PowerShell.Archive\Expand-Archive -Path $temp -DestinationPath .); (Remove-Item $temp)
} else {
@@ -53,7 +57,7 @@ function download {
$binary_error="Failed to download $file"
return
}
check_binary >$null
echo y | icacls $fzf_base\bin\fzf.exe /grant Administrator:F ; check_binary >$null
}
download "fzf-$version-windows_amd64.zip"

View File

@@ -5,7 +5,7 @@ import (
"github.com/junegunn/fzf/src/protector"
)
var version string = "0.24"
var version string = "0.26"
var revision string = "devel"
func main() {

View File

@@ -1,7 +1,7 @@
.ig
The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi
Copyright (c) 2013-2021 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 "Oct 2020" "fzf 0.24.1" "fzf-tmux - open fzf in tmux split pane"
.TH fzf-tmux 1 "Mar 2021" "fzf 0.26.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) 2013-2020 Junegunn Choi
Copyright (c) 2013-2021 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 "Oct 2020" "fzf 0.24.1" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Mar 2021" "fzf 0.26.0" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -71,9 +71,10 @@ Transform the presentation of each line using field index expressions
.BI "-d, --delimiter=" "STR"
Field delimiter regex for \fB--nth\fR and \fB--with-nth\fR (default: AWK-style)
.TP
.BI "--phony"
.BI "--disabled"
Do not perform search. With this option, fzf becomes a simple selector
interface rather than a "fuzzy finder".
interface rather than a "fuzzy finder". You can later enable the search using
\fBenable-search\fR or \fBtoggle-search\fR action.
.SS Search result
.TP
.B "+s, --no-sort"
@@ -233,6 +234,29 @@ e.g.
\fBfzf --margin 10%
fzf --margin 1,5%\fR
.RE
.TP
.BI "--padding=" PADDING
Comma-separated expression for padding inside the border. Padding is
distinguishable from margin only when \fB--border\fR option is used.
.br
.br
e.g.
\fBfzf --margin 5% --padding 5% --border --preview 'cat {}' \\
--color bg:#222222,preview-bg:#333333\fR
.br
.RS
.BR TRBL " Same padding for top, right, bottom, and left"
.br
.BR TB,RL " Vertical, horizontal padding"
.br
.BR T,RL,B " Top, horizontal, bottom padding"
.br
.BR T,R,B,L " Top, right, bottom, left padding"
.br
.RE
.TP
.BI "--info=" "STYLE"
Determines the display style of finder info.
@@ -300,6 +324,8 @@ color mappings.
\fBbg+ \fRBackground (current line)
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
\fBhl+ \fRHighlighted substrings (current line)
\fBquery \fRQuery string
\fBdisabled \fRQuery string when search is disabled
\fBinfo \fRInfo line (match counters)
\fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
\fBprompt \fRPrompt
@@ -416,7 +442,7 @@ e.g.
done'\fR
.RE
.TP
.BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]cycle][:[no]hidden][:+SCROLL[-OFFSET]][:default]"
.BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]follow][:[no]cycle][:[no]hidden][:+SCROLL[OFFSETS][/DENOM]][:~HEADER_LINES][:default]"
.RS
.B POSITION: (default: right)
@@ -425,27 +451,48 @@ e.g.
\fBleft
\fBright
\fRDetermines 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. Cyclic scrolling is enabled
with \fB:cycle\fR flag.
\fRDetermines the layout of the preview window.
If size is given as 0, preview window will not be visible, but fzf will still
* If the argument contains \fB:hidden\fR, the preview window will be hidden by
default until \fBtoggle-preview\fR action is triggered.
* If size is given as 0, preview window will not be visible, but fzf will still
execute the command in the background.
To change the style of the border of the preview window, specify one of
* Long lines are truncated by default. Line wrap can be enabled with
\fB:wrap\fR flag.
* Preview window will automatically scroll to the bottom when \fB:follow\fR
flag is set, similarly to how \fBtail -f\fR works.
.RS
e.g.
\fBfzf --preview-window follow --preview 'for i in $(seq 100000); do
echo "$i"
sleep 0.01
(( i % 300 == 0 )) && printf "\\033[2J"
done'\fR
.RE
* Cyclic scrolling is enabled with \fB:cycle\fR flag.
* To change the style of the border of the preview window, specify one of
\fBrounded\fR (border with rounded edges, default), \fBsharp\fR (border with
sharp edges), or \fBnoborder\fR (no border).
\fB+SCROLL[-OFFSET]\fR determines the initial scroll offset of the preview
window. \fBSCROLL\fR can be either a numeric integer or a single-field index
expression that refers to a numeric integer. The optional \fB-OFFSET\fR part is
for adjusting the base offset so that you can see the text above it. It should
be given as a numeric integer (\fB-INTEGER\fR), or as a denominator form
(\fB-/INTEGER\fR) for specifying a fraction of the preview window height.
* \fB[:+SCROLL[OFFSETS][/DENOM]]\fR determines the initial scroll offset of the
preview window.
\fBdefault\fR resets all options previously set to the default.
- \fBSCROLL\fR can be either a numeric integer or a single-field index expression that refers to a numeric integer.
- The optional \fBOFFSETS\fR part is for adjusting the base offset. It should be given as a series of signed integers (\fB-INTEGER\fR or \fB+INTEGER\fR).
- The final \fB/DENOM\fR part is for specifying a fraction of the preview window height.
* \fB~HEADER_LINES\fR keeps the top N lines as the fixed header so that they
are always visible.
* \fBdefault\fR resets all options previously set to the default.
.RS
e.g.
@@ -456,14 +503,23 @@ e.g.
# Initial scroll offset is set to the line number of each line of
# git grep output *minus* 5 lines (-5)
git grep --line-number '' |
fzf --delimiter : --preview 'nl {1}' --preview-window +{2}-5
fzf --delimiter : --preview 'nl {1}' --preview-window '+{2}-5'
# Preview with bat, matching line in the middle of the window (-/2)
# Preview with bat, matching line in the middle of the window below
# the fixed header of the top 3 lines
#
# ~3 Top 3 lines as the fixed header
# +{2} Base scroll offset extracted from the second field
# +3 Extra offset to compensate for the 3-line header
# /2 Put in the middle of the preview area
#
git grep --line-number '' |
fzf --delimiter : \\
--preview 'bat --style=numbers --color=always --highlight-line {2} {1}' \\
--preview-window +{2}-/2\fR
--preview 'bat --style=full --color=always --highlight-line {2} {1}' \\
--preview-window '~3:+{2}+3/2'
# Display top 3 lines as the fixed header
fzf --preview 'bat --style=full --color=always {}' --preview-window '~3'\fR
.RE
.SS Scripting
@@ -528,7 +584,8 @@ Note that most options have the opposite versions with \fB--no-\fR prefix.
.TP
.B FZF_DEFAULT_COMMAND
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.
with \fB$SHELL -c\fR if \fBSHELL\fR is set, otherwise with \fBsh -c\fR, so in
this case make sure that the command is POSIX-compliant.
.TP
.B FZF_DEFAULT_OPTS
Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
@@ -627,9 +684,7 @@ e.g.
.br
\fIctrl-alt-[a-z]\fR
.br
\fIalt-[a-z]\fR
.br
\fIalt-[0-9]\fR
\fIalt-[*]\fR (Any case-sensitive single character is allowed)
.br
\fIf[1-12]\fR
.br
@@ -653,8 +708,6 @@ e.g.
.br
\fIalt-bspace\fR (\fIalt-bs\fR)
.br
\fIalt-/\fR
.br
\fItab\fR
.br
\fIbtab\fR (\fIshift-tab\fR)
@@ -689,6 +742,14 @@ e.g.
.br
\fIshift-right\fR
.br
\fIalt-shift-up\fR
.br
\fIalt-shift-down\fR
.br
\fIalt-shift-left\fR
.br
\fIalt-shift-right\fR
.br
\fIleft-click\fR
.br
\fIright-click\fR
@@ -703,8 +764,8 @@ or any single character
Triggered whenever the query string is changed
e.g.
\fB# Moves cursor to the top (or bottom depending on --layout) whenever the query is changed
fzf --bind change:top\fR
\fB# Move cursor to the first entry whenever the query is changed
fzf --bind change:first\fR
.RE
\fIbackward-eof\fR
@@ -730,16 +791,22 @@ A key or an event can be bound to one or more of the following actions.
\fBbackward-word\fR \fIalt-b shift-left\fR
\fBbeginning-of-line\fR \fIctrl-a home\fR
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
\fBchange-prompt(...)\fR (change prompt to the given string)
\fBclear-screen\fR \fIctrl-l\fR
\fBclear-selection\fR (clear multi-selection)
\fBclose\fR (close preview window if open, abort fzf otherwise)
\fBclear-query\fR (clear query string)
\fBdelete-char\fR \fIdel\fR
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
\fBdeselect\fR
\fBdeselect-all\fR (deselect all matches)
\fBdisable-search\fR (disable search functionality)
\fBdown\fR \fIctrl-j ctrl-n down\fR
\fBenable-search\fR (enable search functionality)
\fBend-of-line\fR \fIctrl-e end\fR
\fBexecute(...)\fR (see below for the details)
\fBexecute-silent(...)\fR (see below for the details)
\fBfirst\fR (move to the first match)
\fBforward-char\fR \fIctrl-f right\fR
\fBforward-word\fR \fIalt-f shift-right\fR
\fBignore\fR
@@ -747,6 +814,7 @@ A key or an event can be bound to one or more of the following actions.
\fBjump-accept\fR (jump and accept)
\fBkill-line\fR
\fBkill-word\fR \fIalt-d\fR
\fBlast\fR (move to the last match)
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
\fBpage-down\fR \fIpgdn\fR
\fBpage-up\fR \fIpgup\fR
@@ -759,11 +827,14 @@ A key or an event can be bound to one or more of the following actions.
\fBpreview-page-up\fR
\fBpreview-half-page-down\fR
\fBpreview-half-page-up\fR
\fBpreview-bottom\fR
\fBpreview-top\fR
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
\fBprint-query\fR (print query and exit)
\fBrefresh-preview\fR
\fBreload(...)\fR (see below for the details)
\fBreplace-query\fR (replace query string with the current selection)
\fBselect\fR
\fBselect-all\fR (select all matches)
\fBtoggle\fR (\fIright-click\fR)
\fBtoggle-all\fR (toggle all matches)
@@ -772,9 +843,9 @@ A key or an event can be bound to one or more of the following actions.
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
\fBtoggle-preview\fR
\fBtoggle-preview-wrap\fR
\fBtoggle-search\fR (toggle search functionality)
\fBtoggle-sort\fR
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
\fBtop\fR (move to the top result)
\fBunix-line-discard\fR \fIctrl-u\fR
\fBunix-word-rubout\fR \fIctrl-w\fR
\fBup\fR \fIctrl-k ctrl-p up\fR
@@ -785,7 +856,40 @@ A key or an event can be bound to one or more of the following actions.
Multiple actions can be chained using \fB+\fR separator.
e.g.
\fBfzf --bind 'ctrl-a:select-all+accept'\fR
\fBfzf --multi --bind 'ctrl-a:select-all+accept'\fR
\fBfzf --multi --bind 'ctrl-a:select-all' --bind 'ctrl-a:+accept'\fR
.SS ACTION ARGUMENT
An action denoted with \fB(...)\fR suffix takes an argument.
e.g.
\fBfzf --bind 'ctrl-a:change-prompt(NewPrompt> )'\fR
\fBfzf --bind 'ctrl-v:preview(cat {})' --preview-window hidden\fR
If the argument contains parentheses, fzf may fail to parse the expression. In
that case, you can use any of the following alternative notations to avoid
parse errors.
\fBaction-name[...]\fR
\fBaction-name~...~\fR
\fBaction-name!...!\fR
\fBaction-name@...@\fR
\fBaction-name#...#\fR
\fBaction-name$...$\fR
\fBaction-name%...%\fR
\fBaction-name^...^\fR
\fBaction-name&...&\fR
\fBaction-name*...*\fR
\fBaction-name;...;\fR
\fBaction-name/.../\fR
\fBaction-name|...|\fR
\fBaction-name:...\fR
.RS
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
.SS COMMAND EXECUTION
@@ -797,30 +901,6 @@ binding \fBenter\fR key to \fBless\fR command like follows.
You can use the same placeholder expressions as in \fB--preview\fR.
If the command contains parentheses, fzf may fail to parse the expression. In
that case, you can use any of the following alternative notations to avoid
parse errors.
\fBexecute[...]\fR
\fBexecute~...~\fR
\fBexecute!...!\fR
\fBexecute@...@\fR
\fBexecute#...#\fR
\fBexecute$...$\fR
\fBexecute%...%\fR
\fBexecute^...^\fR
\fBexecute&...&\fR
\fBexecute*...*\fR
\fBexecute;...;\fR
\fBexecute/.../\fR
\fBexecute|...|\fR
\fBexecute:...\fR
.RS
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
command is expected to complete quickly, and you are not interested in its
output, you might want to use \fBexecute-silent\fR instead, which silently
@@ -828,6 +908,10 @@ 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).
On *nix systems, fzf runs the command with \fB$SHELL -c\fR if \fBSHELL\fR is
set, otherwise with \fBsh -c\fR, so in this case make sure that the command is
POSIX-compliant.
.SS RELOAD INPUT
\fBreload(...)\fR action is used to dynamically update the input list
@@ -846,7 +930,7 @@ e.g.
INITIAL_QUERY="foobar"
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \\
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
--ansi --phony --query "$INITIAL_QUERY"\fR
--ansi --disabled --query "$INITIAL_QUERY"\fR
.SS PREVIEW BINDING

View File

@@ -189,7 +189,7 @@ function! fzf#exec(...)
if v:shell_error || empty(output)
throw printf('Failed to run "%s": %s', command, output)
endif
let fzf_version = matchstr(output[0], '[0-9.]\+')
let fzf_version = matchstr(output[-1], '[0-9.]\+')
if s:version_requirement(fzf_version, a:1)
let s:checked[a:1] = 1
return s:exec
@@ -283,7 +283,8 @@ function! s:common_sink(action, lines) abort
let cwd = exists('w:fzf_pushd') ? w:fzf_pushd.dir : expand('%:p:h')
for item in a:lines
if item[0] != '~' && item !~ (s:is_win ? '^[A-Z]:\' : '^/')
let item = join([cwd, item], (s:is_win ? '\' : '/'))
let sep = s:is_win ? '\' : '/'
let item = join([cwd, item], cwd[len(cwd)-1] == sep ? '' : sep)
endif
if empty
execute 'e' s:escape(item)
@@ -423,10 +424,10 @@ try
throw v:exception
endtry
if !has_key(dict, 'dir')
if !s:present(dict, 'dir')
let dict.dir = s:fzf_getcwd()
endif
if has('win32unix') && has_key(dict, 'dir')
if has('win32unix') && s:present(dict, 'dir')
let dict.dir = fnamemodify(dict.dir, ':p')
endif
@@ -644,7 +645,8 @@ function! s:execute(dict, command, use_height, temps) abort
endif
let exit_status = v:shell_error
redraw!
return s:exit_handler(exit_status, command) ? s:collect(a:temps) : []
let lines = s:collect(a:temps)
return s:exit_handler(exit_status, command) ? lines : []
endfunction
function! s:execute_tmux(dict, command, temps) abort
@@ -658,7 +660,8 @@ function! s:execute_tmux(dict, command, temps) abort
call system(command)
let exit_status = v:shell_error
redraw!
return s:exit_handler(exit_status, command) ? s:collect(a:temps) : []
let lines = s:collect(a:temps)
return s:exit_handler(exit_status, command) ? lines : []
endfunction
function! s:calc_size(max, val, dict)
@@ -761,6 +764,13 @@ function! s:split(dict)
endtry
endfunction
nnoremap <silent> <Plug>(fzf-insert) i
nnoremap <silent> <Plug>(fzf-normal) <Nop>
if exists(':tnoremap')
tnoremap <silent> <Plug>(fzf-insert) <C-\><C-n>i
tnoremap <silent> <Plug>(fzf-normal) <C-\><C-n>
endif
function! s:execute_term(dict, command, temps) abort
let winrest = winrestcmd()
let pbuf = bufnr('')
@@ -773,7 +783,7 @@ function! s:execute_term(dict, command, temps) abort
function! fzf.switch_back(inplace)
if a:inplace && bufnr('') == self.buf
if bufexists(self.pbuf)
execute 'keepalt b' self.pbuf
execute 'keepalt keepjumps b' self.pbuf
endif
" No other listed buffer
if bufnr('') == self.buf
@@ -805,14 +815,18 @@ function! s:execute_term(dict, command, temps) abort
execute self.winrest
endif
let lines = s:collect(self.temps)
if !s:exit_handler(a:code, self.command, 1)
return
endif
call s:pushd(self.dict)
let lines = s:collect(self.temps)
call s:callback(self.dict, lines)
call self.switch_back(s:getpos() == self.ppos)
if &buftype == 'terminal'
call feedkeys(&filetype == 'fzf' ? "\<Plug>(fzf-insert)" : "\<Plug>(fzf-normal)")
endif
endfunction
try
@@ -829,12 +843,18 @@ function! s:execute_term(dict, command, temps) abort
call termopen(command, fzf)
else
let term_opts = {'exit_cb': function(fzf.on_exit)}
if v:version >= 802
let term_opts.term_kill = 'term'
endif
if is_popup
let term_opts.hidden = 1
else
let term_opts.curwin = 1
endif
let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)
if exists('&termwinkey')
call setbufvar(fzf.buf, '&termwinkey', '<c-z>')
endif
if is_popup && exists('#TerminalWinOpen')
doautocmd <nomodeline> TerminalWinOpen
endif
@@ -842,6 +862,7 @@ function! s:execute_term(dict, command, temps) abort
call term_wait(fzf.buf, 20)
endif
endif
tnoremap <buffer> <c-z> <nop>
finally
call s:dopopd()
endtry
@@ -897,34 +918,23 @@ if has('nvim')
function s:create_popup(hl, opts) abort
let buf = nvim_create_buf(v:false, v:true)
let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts)
let border = has_key(opts, 'border') ? remove(opts, 'border') : []
let win = nvim_open_win(buf, v:true, opts)
call setwinvar(win, '&winhighlight', 'NormalFloat:'..a:hl)
call setwinvar(win, '&colorcolumn', '')
if !empty(border)
call nvim_buf_set_lines(buf, 0, -1, v:true, border)
endif
return buf
endfunction
else
function! s:create_popup(hl, opts) abort
let is_frame = has_key(a:opts, 'border')
let s:popup_create = {buf -> popup_create(buf, #{
\ line: a:opts.row,
\ col: a:opts.col,
\ minwidth: a:opts.width,
\ maxwidth: a:opts.width,
\ minheight: a:opts.height,
\ zindex: 50 - is_frame,
\ maxheight: a:opts.height,
\ zindex: 1000,
\ })}
if is_frame
let id = s:popup_create('')
call setwinvar(id, '&wincolor', a:hl)
call setbufline(winbufnr(id), 1, a:opts.border)
execute 'autocmd BufWipeout * ++once call popup_close('..id..')'
return winbufnr(id)
else
autocmd TerminalOpen * ++once call s:popup_create(str2nr(expand('<abuf>')))
endif
autocmd TerminalOpen * ++once call s:popup_create(str2nr(expand('<abuf>')))
endfunction
endif

View File

@@ -46,9 +46,20 @@ __fzf_comprun() {
fi
}
__fzf_orig_completion_filter() {
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2"; [[ "\1" = *" -o nospace "* ]] \&\& [[ ! "$__fzf_nospace_commands" = *" \3 "* ]] \&\& __fzf_nospace_commands="$__fzf_nospace_commands \3 ";/' |
awk -F= '{OFS = FS} {gsub(/[^A-Za-z0-9_= ;]/, "_", $1);}1'
__fzf_orig_completion() {
local l comp f cmd
while read -r l; do
if [[ "$l" =~ ^(.*\ -F)\ *([^ ]*).*\ ([^ ]*)$ ]]; then
comp="${BASH_REMATCH[1]}"
f="${BASH_REMATCH[2]}"
cmd="${BASH_REMATCH[3]}"
[[ "$f" = _fzf_* ]] && continue
printf -v "_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}" "%s" "${comp} %s ${cmd} #${f}"
if [[ "$l" = *" -o nospace "* ]] && [[ ! "$__fzf_nospace_commands" = *" $cmd "* ]]; then
__fzf_nospace_commands="$__fzf_nospace_commands $cmd "
fi
fi
done
}
_fzf_opts_completion() {
@@ -137,7 +148,7 @@ _fzf_handle_dynamic_completion() {
ret=$?
# _completion_loader may not have updated completion for the command
if [ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]; then
eval "$(complete | command grep " -F.* $orig_cmd$" | __fzf_orig_completion_filter)"
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
if [[ "$__fzf_nospace_commands" = *" $orig_cmd "* ]]; then
eval "${orig_complete/ -F / -o nospace -F }"
else
@@ -165,7 +176,7 @@ __fzf_generic_path_completion() {
leftover=${leftover/#\/}
[ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}"
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
printf "%q$3 " "$item"
done)
matches=${matches% }
@@ -221,7 +232,7 @@ _fzf_complete() {
if [[ "$cur" == *"$trigger" ]]; then
cur=${cur:0:${#cur}-${#trigger}}
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
if [ -n "$selected" ]; then
COMPREPLY=("$selected")
@@ -306,9 +317,7 @@ a_cmds="
svn tar unzip zip"
# Preserve existing completion
eval "$(complete |
sed -E '/-F/!d; / _fzf/d; '"/ ($(echo $d_cmds $a_cmds | sed 's/ /|/g; s/+/\\+/g'))$/"'!d' |
__fzf_orig_completion_filter)"
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds 2> /dev/null)
if type _completion_loader > /dev/null 2>&1; then
_fzf_completion_loader=1
@@ -353,7 +362,7 @@ _fzf_setup_completion() {
return 1
fi
shift
eval "$(complete -p "$@" 2> /dev/null | grep -v "$fn" | __fzf_orig_completion_filter)"
__fzf_orig_completion < <(complete -p "$@" 2> /dev/null)
for cmd in "$@"; do
case "$kind" in
dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;;

View File

@@ -145,7 +145,7 @@ __fzf_generic_path_completion() {
leftover=${leftover/#\/}
[ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}"
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
echo -n "${(q)item}$suffix "
done)
matches=${matches% }
@@ -207,11 +207,10 @@ _fzf_complete() {
type $post > /dev/null 2>&1 || post=cat
_fzf_feed_fifo "$fifo"
matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches"
fi
zle reset-prompt
command rm -f "$fifo"
}
@@ -302,6 +301,7 @@ fzf-completion() {
if eval "type _fzf_complete_${cmd} > /dev/null"; then
prefix="$prefix" eval _fzf_complete_${cmd} ${(q)lbuf}
zle reset-prompt
elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then
_fzf_dir_completion "$prefix" "$lbuf"
else

View File

@@ -18,7 +18,7 @@ __fzf_select__() {
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | cut -b3-"}"
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read -r item; do
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read -r item; do
printf '%q ' "$item"
done
echo
@@ -41,7 +41,7 @@ __fzf_cd__() {
local cmd dir
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | cut -b3-"}"
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
}
__fzf_history__() {
@@ -49,7 +49,7 @@ __fzf_history__() {
output=$(
builtin fc -lnr -2147483648 |
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e 'BEGIN { getc; $/ = "\n\t"; $HISTCMD = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCMD - $. . "\t$_" if !$seen{$_}++' |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m --read0" $(__fzfcmd) --query "$READLINE_LINE"
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m --read0" $(__fzfcmd) --query "$READLINE_LINE"
) || return
READLINE_LINE=${output#*$'\t'}
if [ -z "$READLINE_POINT" ]; then

View File

@@ -20,6 +20,7 @@ function fzf_key_bindings
set -l commandline (__fzf_parse_commandline)
set -l dir $commandline[1]
set -l fzf_query $commandline[2]
set -l prefix $commandline[3]
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
# $dir itself, even if hidden.
@@ -31,7 +32,7 @@ function fzf_key_bindings
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
end
if [ -z "$result" ]
@@ -42,6 +43,7 @@ function fzf_key_bindings
commandline -t ""
end
for i in $result
commandline -it -- $prefix
commandline -it -- (string escape $i)
commandline -it -- ' '
end
@@ -51,7 +53,7 @@ function fzf_key_bindings
function fzf-history-widget -d "Show command history"
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m"
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
set -l FISH_MINOR (echo $version | cut -f2 -d.)
@@ -74,13 +76,14 @@ function fzf_key_bindings
set -l commandline (__fzf_parse_commandline)
set -l dir $commandline[1]
set -l fzf_query $commandline[2]
set -l prefix $commandline[3]
test -n "$FZF_ALT_C_COMMAND"; or set -l FZF_ALT_C_COMMAND "
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
if [ -n "$result" ]
@@ -88,6 +91,7 @@ function fzf_key_bindings
# Remove last token from commandline.
commandline -t ""
commandline -it -- $prefix
end
end
@@ -116,9 +120,15 @@ function fzf_key_bindings
bind -M insert \ec fzf-cd-widget
end
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath and rest of token'
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
set -l commandline (commandline -t)
# strip -option= from token if present
set -l prefix (string match -r -- '^-[^\s=]+=' $commandline)
set commandline (string replace -- "$prefix" '' $commandline)
# eval is used to do shell expansion on paths
set -l commandline (eval "printf '%s' "(commandline -t))
eval set commandline $commandline
if [ -z $commandline ]
# Default to current directory with no --query
@@ -138,6 +148,7 @@ function fzf_key_bindings
echo $dir
echo $fzf_query
echo $prefix
end
function __fzf_get_dir -d 'Find the longest existing filepath from input string'

View File

@@ -45,7 +45,8 @@ __fsel() {
-o -type d -print \
-o -type l -print 2> /dev/null | cut -b3-"}"
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
local item
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do
echo -n "${(q)item} "
done
local ret=$?
@@ -67,36 +68,22 @@ fzf-file-widget() {
zle -N fzf-file-widget
bindkey '^T' fzf-file-widget
# Ensure precmds are run after cd
fzf-redraw-prompt() {
local precmd
for precmd in $precmd_functions; do
$precmd
done
zle reset-prompt
}
zle -N fzf-redraw-prompt
# ALT-C - cd into the selected directory
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 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)"
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m)"
if [[ -z "$dir" ]]; then
zle redisplay
return 0
fi
if [ -z "$BUFFER" ]; then
BUFFER="cd ${(q)dir}"
zle accept-line
else
print -sr "cd ${(q)dir}"
cd "$dir"
fi
zle push-line # Clear buffer. Auto-restored on next prompt.
BUFFER="cd ${(q)dir}"
zle accept-line
local ret=$?
unset dir # ensure this doesn't end up appearing in prompt expansion
zle fzf-redraw-prompt
zle reset-prompt
return $ret
}
zle -N fzf-cd-widget
@@ -107,7 +94,7 @@ fzf-history-widget() {
local selected num
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
selected=( $(fc -rl 1 | perl -ne 'print if !$seen{(/^\s*[0-9]+\**\s+(.*)/, $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)) )
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
local ret=$?
if [ -n "$selected" ]; then
num=$selected[1]

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi
Copyright (c) 2013-2021 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,8 +1,6 @@
package fzf
import (
"bytes"
"regexp"
"strconv"
"strings"
"unicode/utf8"
@@ -19,17 +17,18 @@ type ansiState struct {
fg tui.Color
bg tui.Color
attr tui.Attr
lbg tui.Color
}
func (s *ansiState) colored() bool {
return s.fg != -1 || s.bg != -1 || s.attr > 0
return s.fg != -1 || s.bg != -1 || s.attr > 0 || s.lbg >= 0
}
func (s *ansiState) equals(t *ansiState) bool {
if t == nil {
return !s.colored()
}
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr && s.lbg == t.lbg
}
func (s *ansiState) ToString() string {
@@ -81,73 +80,154 @@ func toAnsiString(color tui.Color, offset int) string {
return ret + ";"
}
var ansiRegex *regexp.Regexp
func init() {
/*
References:
- https://github.com/gnachman/iTerm2
- http://ascii-table.com/ansi-escape-sequences.php
- http://ascii-table.com/ansi-escape-sequences-vt-100.php
- http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
- https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
*/
// The following regular expression will include not all but most of the
// frequently used ANSI sequences
ansiRegex = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
func isPrint(c uint8) bool {
return '\x20' <= c && c <= '\x7e'
}
func findAnsiStart(str string) int {
idx := 0
for ; idx < len(str); idx++ {
b := str[idx]
if b == 0x1b || b == 0x0e || b == 0x0f {
return idx
func matchOperatingSystemCommand(s string) int {
// `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)`
// ^ match starting here
//
i := 5 // prefix matched in nextAnsiEscapeSequence()
for ; i < len(s) && isPrint(s[i]); i++ {
}
if i < len(s) {
if s[i] == '\x07' {
return i + 1
}
if b == 0x08 && idx > 0 {
return idx - 1
if s[i] == '\x1b' && i < len(s)-1 && s[i+1] == '\\' {
return i + 2
}
}
return idx
return -1
}
func matchControlSequence(s string) int {
// `\x1b[\\[()][0-9;]*[a-zA-Z@]`
// ^ match starting here
//
i := 2 // prefix matched in nextAnsiEscapeSequence()
for ; i < len(s) && (isNumeric(s[i]) || s[i] == ';'); i++ {
}
if i < len(s) {
c := s[i]
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '@' {
return i + 1
}
}
return -1
}
func isCtrlSeqStart(c uint8) bool {
return c == '\\' || c == '[' || c == '(' || c == ')'
}
// nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to
// calling FindStringIndex() on the below regex (which was originally used):
//
// "(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)"
//
func nextAnsiEscapeSequence(s string) (int, int) {
// fast check for ANSI escape sequences
i := 0
for ; i < len(s); i++ {
switch s[i] {
case '\x0e', '\x0f', '\x1b', '\x08':
// We ignore the fact that '\x08' cannot be the first char
// in the string and be an escape sequence for the sake of
// speed and simplicity.
goto Loop
}
}
return -1, -1
Loop:
for ; i < len(s); i++ {
switch s[i] {
case '\x08':
// backtrack to match: `.\x08`
if i > 0 && s[i-1] != '\n' {
if s[i-1] < utf8.RuneSelf {
return i - 1, i + 1
}
_, n := utf8.DecodeLastRuneInString(s[:i])
return i - n, i + 1
}
case '\x1b':
// match: `\x1b[\\[()][0-9;]*[a-zA-Z@]`
if i+2 < len(s) && isCtrlSeqStart(s[i+1]) {
if j := matchControlSequence(s[i:]); j != -1 {
return i, i + j
}
}
// match: `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)`
if i+5 < len(s) && s[i+1] == ']' && isNumeric(s[i+2]) &&
s[i+3] == ';' && isPrint(s[i+4]) {
if j := matchOperatingSystemCommand(s[i:]); j != -1 {
return i, i + j
}
}
// match: `\x1b.`
if i+1 < len(s) && s[i+1] != '\n' {
if s[i+1] < utf8.RuneSelf {
return i, i + 2
}
_, n := utf8.DecodeRuneInString(s[i+1:])
return i, i + n + 1
}
case '\x0e', '\x0f':
// match: `[\x0e\x0f]`
return i, i + 1
}
}
return -1, -1
}
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
var offsets []ansiOffset
var output bytes.Buffer
// We append to a stack allocated variable that we'll
// later copy and return, to save on allocations.
offsets := make([]ansiOffset, 0, 32)
if state != nil {
offsets = append(offsets, ansiOffset{[2]int32{0, 0}, *state})
}
prevIdx := 0
runeCount := 0
var (
pstate *ansiState // lazily allocated
output strings.Builder
prevIdx int
runeCount int
)
for idx := 0; idx < len(str); {
idx += findAnsiStart(str[idx:])
if idx == len(str) {
// Make sure that we found an ANSI code
start, end := nextAnsiEscapeSequence(str[idx:])
if start == -1 {
break
}
// Make sure that we found an ANSI code
offset := ansiRegex.FindStringIndex(str[idx:])
if len(offset) < 2 {
idx++
continue
}
offset[0] += idx
offset[1] += idx
idx = offset[1]
start += idx
idx += end
// Check if we should continue
prev := str[prevIdx:offset[0]]
prev := str[prevIdx:start]
if proc != nil && !proc(prev, state) {
return "", nil, nil
}
prevIdx = idx
prevIdx = offset[1]
runeCount += utf8.RuneCountInString(prev)
output.WriteString(prev)
if len(prev) != 0 {
runeCount += utf8.RuneCountInString(prev)
// Grow the buffer size to the maximum possible length (string length
// containing ansi codes) to avoid repetitive allocation
if output.Cap() == 0 {
output.Grow(len(str))
}
output.WriteString(prev)
}
newState := interpretCode(str[offset[0]:offset[1]], state)
newState := interpretCode(str[start:idx], state)
if !newState.equals(state) {
if state != nil {
// Update last offset
@@ -156,8 +236,15 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
if newState.colored() {
// Append new offset
state = newState
offsets = append(offsets, ansiOffset{[2]int32{int32(runeCount), int32(runeCount)}, *state})
if pstate == nil {
pstate = &ansiState{}
}
*pstate = newState
state = pstate
offsets = append(offsets, ansiOffset{
[2]int32{int32(runeCount), int32(runeCount)},
newState,
})
} else {
// Discard state
state = nil
@@ -167,7 +254,6 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
var rest string
var trimmed string
if prevIdx == 0 {
// No ANSI code found
rest = str
@@ -177,48 +263,75 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
output.WriteString(rest)
trimmed = output.String()
}
if len(rest) > 0 && state != nil {
// Update last offset
runeCount += utf8.RuneCountInString(rest)
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
}
if proc != nil {
proc(rest, state)
}
if len(offsets) == 0 {
return trimmed, nil, state
if len(offsets) > 0 {
if len(rest) > 0 && state != nil {
// Update last offset
runeCount += utf8.RuneCountInString(rest)
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
}
// Return a copy of the offsets slice
a := make([]ansiOffset, len(offsets))
copy(a, offsets)
return trimmed, &a, state
}
return trimmed, &offsets, state
return trimmed, nil, state
}
func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
// State
var state *ansiState
func parseAnsiCode(s string) (int, string) {
var remaining string
if i := strings.IndexByte(s, ';'); i >= 0 {
remaining = s[i+1:]
s = s[:i]
}
if len(s) > 0 {
// Inlined version of strconv.Atoi() that only handles positive
// integers and does not allocate on error.
code := 0
for _, ch := range []byte(s) {
ch -= '0'
if ch > 9 {
return -1, remaining
}
code = code*10 + int(ch)
}
return code, remaining
}
return -1, remaining
}
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
var state ansiState
if prevState == nil {
state = &ansiState{-1, -1, 0}
state = ansiState{-1, -1, 0, -1}
} else {
state = &ansiState{prevState.fg, prevState.bg, prevState.attr}
state = ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg}
}
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
if prevState != nil && strings.HasSuffix(ansiCode, "0K") {
state.lbg = prevState.bg
}
return state
}
ptr := &state.fg
state256 := 0
init := func() {
if len(ansiCode) <= 3 {
state.fg = -1
state.bg = -1
state.attr = 0
state256 = 0
return state
}
ansiCode = ansiCode[2 : len(ansiCode)-1]
if len(ansiCode) == 0 {
init()
}
for _, code := range strings.Split(ansiCode, ";") {
if num, err := strconv.Atoi(code); err == nil {
state256 := 0
ptr := &state.fg
for len(ansiCode) != 0 {
var num int
if num, ansiCode = parseAnsiCode(ansiCode); num != -1 {
switch state256 {
case 0:
switch num {
@@ -249,7 +362,10 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
case 24: // tput rmul
state.attr = state.attr &^ tui.Underline
case 0:
init()
state.fg = -1
state.bg = -1
state.attr = 0
state256 = 0
default:
if num >= 30 && num <= 37 {
state.fg = tui.Color(num - 30)
@@ -285,6 +401,7 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
}
}
}
if state256 > 0 {
*ptr = -1
}

View File

@@ -2,12 +2,190 @@ package fzf
import (
"fmt"
"math/rand"
"regexp"
"strings"
"testing"
"unicode/utf8"
"github.com/junegunn/fzf/src/tui"
)
// The following regular expression will include not all but most of the
// frequently used ANSI sequences. This regex is used as a reference for
// testing nextAnsiEscapeSequence().
//
// References:
// - https://github.com/gnachman/iTerm2
// - http://ascii-table.com/ansi-escape-sequences.php
// - http://ascii-table.com/ansi-escape-sequences-vt-100.php
// - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
// - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
var ansiRegexRefence = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
func testParserReference(t testing.TB, str string) {
t.Helper()
toSlice := func(start, end int) []int {
if start == -1 {
return nil
}
return []int{start, end}
}
s := str
for i := 0; ; i++ {
got := toSlice(nextAnsiEscapeSequence(s))
exp := ansiRegexRefence.FindStringIndex(s)
equal := len(got) == len(exp)
if equal {
for i := 0; i < len(got); i++ {
if got[i] != exp[i] {
equal = false
break
}
}
}
if !equal {
var exps, gots []rune
if len(got) == 2 {
gots = []rune(s[got[0]:got[1]])
}
if len(exp) == 2 {
exps = []rune(s[exp[0]:exp[1]])
}
t.Errorf("%d: %q: got: %v (%q) want: %v (%q)", i, s, got, gots, exp, exps)
return
}
if len(exp) == 0 {
return
}
s = s[exp[1]:]
}
}
func TestNextAnsiEscapeSequence(t *testing.T) {
testStrs := []string{
"\x1b[0mhello world",
"\x1b[1mhello world",
"椙\x1b[1m椙",
"椙\x1b[1椙m椙",
"\x1b[1mhello \x1b[mw\x1b7o\x1b8r\x1b(Bl\x1b[2@d",
"\x1b[1mhello \x1b[Kworld",
"hello \x1b[34;45;1mworld",
"hello \x1b[34;45;1mwor\x1b[34;45;1mld",
"hello \x1b[34;45;1mwor\x1b[0mld",
"hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md",
"hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md",
"hello \x1b[32;1mworld",
"hello world",
"hello \x1b[0;38;5;200;48;5;100mworld",
"\x1b椙",
"椙\x08",
"\n\x08",
"X\x08",
"",
"\x1b]4;3;rgb:aa/bb/cc\x07 ",
"\x1b]4;3;rgb:aa/bb/cc\x1b\\ ",
ansiBenchmarkString,
}
for _, s := range testStrs {
testParserReference(t, s)
}
}
func TestNextAnsiEscapeSequence_Fuzz_Modified(t *testing.T) {
t.Parallel()
if testing.Short() {
t.Skip("short test")
}
testStrs := []string{
"\x1b[0mhello world",
"\x1b[1mhello world",
"椙\x1b[1m椙",
"椙\x1b[1椙m椙",
"\x1b[1mhello \x1b[mw\x1b7o\x1b8r\x1b(Bl\x1b[2@d",
"\x1b[1mhello \x1b[Kworld",
"hello \x1b[34;45;1mworld",
"hello \x1b[34;45;1mwor\x1b[34;45;1mld",
"hello \x1b[34;45;1mwor\x1b[0mld",
"hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md",
"hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md",
"hello \x1b[32;1mworld",
"hello world",
"hello \x1b[0;38;5;200;48;5;100mworld",
ansiBenchmarkString,
}
replacementBytes := [...]rune{'\x0e', '\x0f', '\x1b', '\x08'}
modifyString := func(s string, rr *rand.Rand) string {
n := rr.Intn(len(s))
b := []rune(s)
for ; n >= 0 && len(b) != 0; n-- {
i := rr.Intn(len(b))
switch x := rr.Intn(4); x {
case 0:
b = append(b[:i], b[i+1:]...)
case 1:
j := rr.Intn(len(replacementBytes) - 1)
b[i] = replacementBytes[j]
case 2:
x := rune(rr.Intn(utf8.MaxRune))
for !utf8.ValidRune(x) {
x = rune(rr.Intn(utf8.MaxRune))
}
b[i] = x
case 3:
b[i] = rune(rr.Intn(utf8.MaxRune)) // potentially invalid
default:
t.Fatalf("unsupported value: %d", x)
}
}
return string(b)
}
rr := rand.New(rand.NewSource(1))
for _, s := range testStrs {
for i := 1_000; i >= 0; i-- {
testParserReference(t, modifyString(s, rr))
}
}
}
func TestNextAnsiEscapeSequence_Fuzz_Random(t *testing.T) {
t.Parallel()
if testing.Short() {
t.Skip("short test")
}
randomString := func(rr *rand.Rand) string {
numChars := rand.Intn(50)
codePoints := make([]rune, numChars)
for i := 0; i < len(codePoints); i++ {
var r rune
for n := 0; n < 1000; n++ {
r = rune(rr.Intn(utf8.MaxRune))
// Allow 10% of runes to be invalid
if utf8.ValidRune(r) || rr.Float64() < 0.10 {
break
}
}
codePoints[i] = r
}
return string(codePoints)
}
rr := rand.New(rand.NewSource(1))
for i := 0; i < 100_000; i++ {
testParserReference(t, randomString(rr))
}
}
func TestExtractColor(t *testing.T) {
assert := func(offset ansiOffset, b int32, e int32, fg tui.Color, bg tui.Color, bold bool) {
var attr tui.Attr
@@ -168,7 +346,7 @@ func TestAnsiCodeStringConversion(t *testing.T) {
}
}
assert("\x1b[m", nil, "")
assert("\x1b[m", &ansiState{attr: tui.Blink}, "")
assert("\x1b[m", &ansiState{attr: tui.Blink, lbg: -1}, "")
assert("\x1b[31m", nil, "\x1b[31;49m")
assert("\x1b[41m", nil, "\x1b[39;41m")
@@ -176,8 +354,8 @@ func TestAnsiCodeStringConversion(t *testing.T) {
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[31m", &ansiState{fg: 4, bg: 4, lbg: -1}, "\x1b[31;44m")
assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse, lbg: -1}, "\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")
@@ -185,3 +363,64 @@ func TestAnsiCodeStringConversion(t *testing.T) {
&ansiState{attr: tui.Dim | tui.Italic, fg: 1, bg: 1},
"\x1b[2;3;7;38;2;10;20;30;48;5;100m")
}
func TestParseAnsiCode(t *testing.T) {
tests := []struct {
In, Exp string
N int
}{
{"123", "", 123},
{"1a", "", -1},
{"1a;12", "12", -1},
{"12;a", "a", 12},
{"-2", "", -1},
}
for _, x := range tests {
n, s := parseAnsiCode(x.In)
if n != x.N || s != x.Exp {
t.Fatalf("%q: got: (%d %q) want: (%d %q)", x.In, n, s, x.N, x.Exp)
}
}
}
// kernel/bpf/preload/iterators/README
const ansiBenchmarkString = "\x1b[38;5;81m\x1b[01;31m\x1b[Kkernel/\x1b[0m\x1b[38;5;81mbpf/" +
"\x1b[0m\x1b[38;5;81mpreload/\x1b[0m\x1b[38;5;81miterators/" +
"\x1b[0m\x1b[38;5;149mMakefile\x1b[m\x1b[K\x1b[0m"
func BenchmarkNextAnsiEscapeSequence(b *testing.B) {
b.SetBytes(int64(len(ansiBenchmarkString)))
for i := 0; i < b.N; i++ {
s := ansiBenchmarkString
for {
_, o := nextAnsiEscapeSequence(s)
if o == -1 {
break
}
s = s[o:]
}
}
}
// Baseline test to compare the speed of nextAnsiEscapeSequence() to the
// previously used regex based implementation.
func BenchmarkNextAnsiEscapeSequence_Regex(b *testing.B) {
b.SetBytes(int64(len(ansiBenchmarkString)))
for i := 0; i < b.N; i++ {
s := ansiBenchmarkString
for {
a := ansiRegexRefence.FindStringIndex(s)
if len(a) == 0 {
break
}
s = s[a[1]:]
}
}
}
func BenchmarkExtractColor(b *testing.B) {
b.SetBytes(int64(len(ansiBenchmarkString)))
for i := 0; i < b.N; i++ {
extractColor(ansiBenchmarkString, nil, nil)
}
}

View File

@@ -6,8 +6,8 @@ func TestChunkCache(t *testing.T) {
cache := NewChunkCache()
chunk1p := &Chunk{}
chunk2p := &Chunk{count: chunkSize}
items1 := []Result{Result{}}
items2 := []Result{Result{}, Result{}}
items1 := []Result{{}}
items2 := []Result{{}, {}}
cache.Add(chunk1p, "foo", items1)
cache.Add(chunk2p, "foo", items1)
cache.Add(chunk2p, "bar", items2)

View File

@@ -73,6 +73,7 @@ const (
EvtSearchFin
EvtHeader
EvtReady
EvtQuit
)
const (

View File

@@ -3,7 +3,7 @@ Package fzf implements fzf, a command-line fuzzy finder.
The MIT License (MIT)
Copyright (c) 2017 Junegunn Choi
Copyright (c) 2013-2021 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
@@ -237,14 +237,16 @@ func Run(opts *Options, version string, revision string) {
go reader.restart(command)
}
eventBox.Watch(EvtReadNew)
query := []rune{}
for {
delay := true
ticks++
input := func() []rune {
if opts.Phony {
return []rune{}
paused, input := terminal.Input()
if !paused {
query = input
}
return []rune(terminal.Input())
return query
}
eventBox.Wait(func(events *util.Events) {
if _, fin := (*events)[EvtReadFin]; fin {
@@ -252,7 +254,11 @@ func Run(opts *Options, version string, revision string) {
}
for evt, value := range *events {
switch evt {
case EvtQuit:
if reading {
reader.terminate()
}
os.Exit(value.(int))
case EvtReadNew, EvtReadFin:
if evt == EvtReadFin && nextCommand != nil {
restart(*nextCommand)

View File

@@ -3,7 +3,6 @@ package fzf
import (
"io/ioutil"
"os"
"os/user"
"runtime"
"testing"
)
@@ -12,16 +11,12 @@ func TestHistory(t *testing.T) {
maxHistory := 50
// Invalid arguments
user, _ := user.Current()
var paths []string
if runtime.GOOS == "windows" {
// GOPATH should exist, so we shouldn't be able to override it
paths = []string{os.Getenv("GOPATH")}
} else {
paths = []string{"/etc", "/proc"}
if user.Name != "root" {
paths = append(paths, "/etc/sudoers")
}
}
for _, path := range paths {

View File

@@ -106,7 +106,6 @@ func (mg *Merger) mergedGet(idx int) Result {
minIdx = listIdx
}
}
mg.cursors[listIdx] = cursor
}
if minIdx >= 0 {

View File

@@ -7,7 +7,6 @@ import (
"strconv"
"strings"
"unicode"
"unicode/utf8"
"github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/tui"
@@ -34,7 +33,7 @@ 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
--disabled 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)
@@ -60,7 +59,8 @@ const usage = `usage: fzf [options]
--border[=STYLE] Draw border around the finder
[rounded|sharp|horizontal|vertical|
top|bottom|left|right] (default: rounded)
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
--info=STYLE Finder info style [default|inline|hidden]
--prompt=STR Input prompt (default: '> ')
--pointer=STR Pointer to the current line (default: '>')
@@ -82,9 +82,9 @@ const usage = `usage: fzf [options]
--preview=COMMAND Command to preview highlighted line ({})
--preview-window=OPT Preview window layout (default: right:50%)
[up|down|left|right][:SIZE[%]]
[:[no]wrap][:[no]cycle][:[no]hidden]
[:[no]wrap][:[no]cycle][:[no]follow][:[no]hidden]
[:rounded|sharp|noborder]
[:+SCROLL[-OFFSET]]
[:+SCROLL[OFFSETS][/DENOM]][:~HEADER_LINES]
[:default]
Scripting
@@ -161,14 +161,16 @@ const (
)
type previewOpts struct {
command string
position windowPosition
size sizeSpec
scroll string
hidden bool
wrap bool
cycle bool
border tui.BorderShape
command string
position windowPosition
size sizeSpec
scroll string
hidden bool
wrap bool
cycle bool
follow bool
border tui.BorderShape
headerLines int
}
// Options stores the values of command-line options
@@ -209,8 +211,8 @@ type Options struct {
Exit0 bool
Filter *string
ToggleSort bool
Expect map[int]string
Keymap map[int][]action
Expect map[tui.Event]string
Keymap map[tui.Event][]action
Preview previewOpts
PrintQuery bool
ReadZero bool
@@ -221,6 +223,7 @@ type Options struct {
Header []string
HeaderLines int
Margin [4]sizeSpec
Padding [4]sizeSpec
BorderShape tui.BorderShape
Unicode bool
Tabstop int
@@ -229,7 +232,7 @@ type Options struct {
}
func defaultPreviewOpts(command string) previewOpts {
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, tui.BorderRounded}
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded, 0}
}
func defaultOptions() *Options {
@@ -269,8 +272,8 @@ func defaultOptions() *Options {
Exit0: false,
Filter: nil,
ToggleSort: false,
Expect: make(map[int]string),
Keymap: make(map[int][]action),
Expect: make(map[tui.Event]string),
Keymap: make(map[tui.Event][]action),
Preview: defaultPreviewOpts(""),
PrintQuery: false,
ReadZero: false,
@@ -281,6 +284,7 @@ func defaultOptions() *Options {
Header: make([]string, 0),
HeaderLines: 0,
Margin: defaultMargin(),
Padding: defaultMargin(),
Unicode: true,
Tabstop: 8,
ClearOnExit: true,
@@ -441,126 +445,141 @@ func parseBorder(str string, optional bool) tui.BorderShape {
return tui.BorderNone
}
func parseKeyChords(str string, message string) map[int]string {
func parseKeyChords(str string, message string) map[tui.Event]string {
if len(str) == 0 {
errorExit(message)
}
str = regexp.MustCompile("(?i)(alt-),").ReplaceAllString(str, "$1"+string([]rune{escapedComma}))
tokens := strings.Split(str, ",")
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Contains(str, ",,,") {
tokens = append(tokens, ",")
}
chords := make(map[int]string)
chords := make(map[tui.Event]string)
for _, key := range tokens {
if len(key) == 0 {
continue // ignore
}
key = strings.ReplaceAll(key, string([]rune{escapedComma}), ",")
lkey := strings.ToLower(key)
chord := 0
add := func(e tui.EventType) {
chords[e.AsEvent()] = key
}
switch lkey {
case "up":
chord = tui.Up
add(tui.Up)
case "down":
chord = tui.Down
add(tui.Down)
case "left":
chord = tui.Left
add(tui.Left)
case "right":
chord = tui.Right
add(tui.Right)
case "enter", "return":
chord = tui.CtrlM
add(tui.CtrlM)
case "space":
chord = tui.AltZ + int(' ')
chords[tui.Key(' ')] = key
case "bspace", "bs":
chord = tui.BSpace
add(tui.BSpace)
case "ctrl-space":
chord = tui.CtrlSpace
add(tui.CtrlSpace)
case "ctrl-^", "ctrl-6":
chord = tui.CtrlCaret
add(tui.CtrlCaret)
case "ctrl-/", "ctrl-_":
chord = tui.CtrlSlash
add(tui.CtrlSlash)
case "ctrl-\\":
chord = tui.CtrlBackSlash
add(tui.CtrlBackSlash)
case "ctrl-]":
chord = tui.CtrlRightBracket
add(tui.CtrlRightBracket)
case "change":
chord = tui.Change
add(tui.Change)
case "backward-eof":
chord = tui.BackwardEOF
add(tui.BackwardEOF)
case "alt-enter", "alt-return":
chord = tui.CtrlAltM
chords[tui.CtrlAltKey('m')] = key
case "alt-space":
chord = tui.AltSpace
case "alt-/":
chord = tui.AltSlash
chords[tui.AltKey(' ')] = key
case "alt-bs", "alt-bspace":
chord = tui.AltBS
add(tui.AltBS)
case "alt-up":
chord = tui.AltUp
add(tui.AltUp)
case "alt-down":
chord = tui.AltDown
add(tui.AltDown)
case "alt-left":
chord = tui.AltLeft
add(tui.AltLeft)
case "alt-right":
chord = tui.AltRight
add(tui.AltRight)
case "tab":
chord = tui.Tab
add(tui.Tab)
case "btab", "shift-tab":
chord = tui.BTab
add(tui.BTab)
case "esc":
chord = tui.ESC
add(tui.ESC)
case "del":
chord = tui.Del
add(tui.Del)
case "home":
chord = tui.Home
add(tui.Home)
case "end":
chord = tui.End
add(tui.End)
case "insert":
chord = tui.Insert
add(tui.Insert)
case "pgup", "page-up":
chord = tui.PgUp
add(tui.PgUp)
case "pgdn", "page-down":
chord = tui.PgDn
add(tui.PgDn)
case "alt-shift-up", "shift-alt-up":
add(tui.AltSUp)
case "alt-shift-down", "shift-alt-down":
add(tui.AltSDown)
case "alt-shift-left", "shift-alt-left":
add(tui.AltSLeft)
case "alt-shift-right", "shift-alt-right":
add(tui.AltSRight)
case "shift-up":
chord = tui.SUp
add(tui.SUp)
case "shift-down":
chord = tui.SDown
add(tui.SDown)
case "shift-left":
chord = tui.SLeft
add(tui.SLeft)
case "shift-right":
chord = tui.SRight
add(tui.SRight)
case "left-click":
chord = tui.LeftClick
add(tui.LeftClick)
case "right-click":
chord = tui.RightClick
add(tui.RightClick)
case "double-click":
chord = tui.DoubleClick
add(tui.DoubleClick)
case "f10":
chord = tui.F10
add(tui.F10)
case "f11":
chord = tui.F11
add(tui.F11)
case "f12":
chord = tui.F12
add(tui.F12)
default:
runes := []rune(key)
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
chord = tui.CtrlAltA + int(lkey[9]) - 'a'
chords[tui.CtrlAltKey(rune(key[9]))] = key
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
chord = tui.CtrlA + int(lkey[5]) - 'a'
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) {
chord = tui.AltA + int(lkey[4]) - 'a'
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isNumeric(lkey[4]) {
chord = tui.Alt0 + int(lkey[4]) - '0'
add(tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a'))
} else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") {
r := runes[4]
switch r {
case escapedColon:
r = ':'
case escapedComma:
r = ','
case escapedPlus:
r = '+'
}
chords[tui.AltKey(r)] = key
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
chord = tui.F1 + int(key[1]) - '1'
} else if utf8.RuneCountInString(key) == 1 {
chord = tui.AltZ + int([]rune(key)[0])
add(tui.EventType(tui.F1.Int() + int(key[1]) - '1'))
} else if len(runes) == 1 {
chords[tui.Key(runes[0])] = key
} else {
errorExit("unsupported key: " + key)
}
}
if chord > 0 {
chords[chord] = key
}
}
return chords
}
@@ -632,71 +651,74 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
fail()
}
cattr := tui.NewColorAttr()
for _, component := range components[1:] {
switch component {
case "regular":
cattr.Attr = tui.AttrRegular
case "bold", "strong":
cattr.Attr |= tui.Bold
case "dim":
cattr.Attr |= tui.Dim
case "italic":
cattr.Attr |= tui.Italic
case "underline":
cattr.Attr |= tui.Underline
case "blink":
cattr.Attr |= tui.Blink
case "reverse":
cattr.Attr |= tui.Reverse
case "":
default:
if rrggbb.MatchString(component) {
cattr.Color = tui.HexToColor(component)
} else {
ansi32, err := strconv.Atoi(component)
if err != nil || ansi32 < -1 || ansi32 > 255 {
fail()
mergeAttr := func(cattr *tui.ColorAttr) {
for _, component := range components[1:] {
switch component {
case "regular":
cattr.Attr = tui.AttrRegular
case "bold", "strong":
cattr.Attr |= tui.Bold
case "dim":
cattr.Attr |= tui.Dim
case "italic":
cattr.Attr |= tui.Italic
case "underline":
cattr.Attr |= tui.Underline
case "blink":
cattr.Attr |= tui.Blink
case "reverse":
cattr.Attr |= tui.Reverse
case "":
default:
if rrggbb.MatchString(component) {
cattr.Color = tui.HexToColor(component)
} else {
ansi32, err := strconv.Atoi(component)
if err != nil || ansi32 < -1 || ansi32 > 255 {
fail()
}
cattr.Color = tui.Color(ansi32)
}
cattr.Color = tui.Color(ansi32)
}
}
}
switch components[0] {
case "input":
theme.Input = cattr
case "query", "input":
mergeAttr(&theme.Input)
case "disabled":
mergeAttr(&theme.Disabled)
case "fg":
theme.Fg = cattr
mergeAttr(&theme.Fg)
case "bg":
theme.Bg = cattr
mergeAttr(&theme.Bg)
case "preview-fg":
theme.PreviewFg = cattr
mergeAttr(&theme.PreviewFg)
case "preview-bg":
theme.PreviewBg = cattr
mergeAttr(&theme.PreviewBg)
case "fg+":
theme.Current = cattr
mergeAttr(&theme.Current)
case "bg+":
theme.DarkBg = cattr
mergeAttr(&theme.DarkBg)
case "gutter":
theme.Gutter = cattr
mergeAttr(&theme.Gutter)
case "hl":
theme.Match = cattr
mergeAttr(&theme.Match)
case "hl+":
theme.CurrentMatch = cattr
mergeAttr(&theme.CurrentMatch)
case "border":
theme.Border = cattr
mergeAttr(&theme.Border)
case "prompt":
theme.Prompt = cattr
mergeAttr(&theme.Prompt)
case "spinner":
theme.Spinner = cattr
mergeAttr(&theme.Spinner)
case "info":
theme.Info = cattr
mergeAttr(&theme.Info)
case "pointer":
theme.Cursor = cattr
mergeAttr(&theme.Cursor)
case "marker":
theme.Selected = cattr
mergeAttr(&theme.Selected)
case "header":
theme.Header = cattr
mergeAttr(&theme.Header)
default:
fail()
}
@@ -707,11 +729,11 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
var executeRegexp *regexp.Regexp
func firstKey(keymap map[int]string) int {
func firstKey(keymap map[tui.Event]string) tui.Event {
for k := range keymap {
return k
}
return 0
return tui.EventType(0).AsEvent()
}
const (
@@ -724,10 +746,10 @@ 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)?|reload|preview):.+|[:+](execute(?:-multi|-silent)?|reload|preview)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
}
func parseKeymap(keymap map[int][]action, str string) {
func parseKeymap(keymap map[tui.Event][]action, str string) {
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
symbol := ":"
if strings.HasPrefix(src, "+") {
@@ -738,6 +760,8 @@ func parseKeymap(keymap map[int][]action, str string) {
prefix = symbol + "reload"
} else if strings.HasPrefix(src[1:], "preview") {
prefix = symbol + "preview"
} else if strings.HasPrefix(src[1:], "change-prompt") {
prefix = symbol + "change-prompt"
} else if src[len(prefix)] == '-' {
c := src[len(prefix)+1]
if c == 's' || c == 'S' {
@@ -761,13 +785,13 @@ func parseKeymap(keymap map[int][]action, str string) {
if len(pair) < 2 {
errorExit("bind action not specified: " + origPairStr)
}
var key int
var key tui.Event
if len(pair[0]) == 1 && pair[0][0] == escapedColon {
key = ':' + tui.AltZ
key = tui.Key(':')
} else if len(pair[0]) == 1 && pair[0][0] == escapedComma {
key = ',' + tui.AltZ
key = tui.Key(',')
} else if len(pair[0]) == 1 && pair[0][0] == escapedPlus {
key = '+' + tui.AltZ
key = tui.Key('+')
} else {
keys := parseKeyChords(pair[0], "key name required")
key = firstKey(keys)
@@ -816,6 +840,8 @@ func parseKeymap(keymap map[int][]action, str string) {
appendAction(actDeleteChar)
case "delete-char/eof":
appendAction(actDeleteCharEOF)
case "deselect":
appendAction(actDeselect)
case "end-of-line":
appendAction(actEndOfLine)
case "cancel":
@@ -854,18 +880,26 @@ func parseKeymap(keymap map[int][]action, str string) {
appendAction(actToggleOut)
case "toggle-all":
appendAction(actToggleAll)
case "toggle-search":
appendAction(actToggleSearch)
case "select":
appendAction(actSelect)
case "select-all":
appendAction(actSelectAll)
case "deselect-all":
appendAction(actDeselectAll)
case "close":
appendAction(actClose)
case "toggle":
appendAction(actToggle)
case "down":
appendAction(actDown)
case "up":
appendAction(actUp)
case "top":
appendAction(actTop)
case "first", "top":
appendAction(actFirst)
case "last":
appendAction(actLast)
case "page-up":
appendAction(actPageUp)
case "page-down":
@@ -884,6 +918,10 @@ func parseKeymap(keymap map[int][]action, str string) {
appendAction(actTogglePreviewWrap)
case "toggle-sort":
appendAction(actToggleSort)
case "preview-top":
appendAction(actPreviewTop)
case "preview-bottom":
appendAction(actPreviewBottom)
case "preview-up":
appendAction(actPreviewUp)
case "preview-down":
@@ -896,6 +934,10 @@ func parseKeymap(keymap map[int][]action, str string) {
appendAction(actPreviewHalfPageUp)
case "preview-half-page-down":
appendAction(actPreviewHalfPageDown)
case "enable-search":
appendAction(actEnableSearch)
case "disable-search":
appendAction(actDisableSearch)
default:
t := isExecuteAction(specLower)
if t == actIgnore {
@@ -911,6 +953,8 @@ func parseKeymap(keymap map[int][]action, str string) {
offset = len("reload")
case actPreview:
offset = len("preview")
case actChangePrompt:
offset = len("change-prompt")
case actExecuteSilent:
offset = len("execute-silent")
case actExecuteMulti:
@@ -950,6 +994,8 @@ func isExecuteAction(str string) actionType {
return actReload
case "preview":
return actPreview
case "change-prompt":
return actChangePrompt
case "execute":
return actExecute
case "execute-silent":
@@ -960,7 +1006,7 @@ func isExecuteAction(str string) actionType {
return actIgnore
}
func parseToggleSort(keymap map[int][]action, str string) {
func parseToggleSort(keymap map[tui.Event][]action, str string) {
keys := parseKeyChords(str, "key name required")
if len(keys) != 1 {
errorExit("multiple keys specified")
@@ -1032,7 +1078,8 @@ func parseInfoStyle(str string) infoStyle {
func parsePreviewWindow(opts *previewOpts, input string) {
tokens := strings.Split(input, ":")
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
offsetRegex := regexp.MustCompile("^\\+([0-9]+|{-?[0-9]+})(-[0-9]+|-/[1-9][0-9]*)?$")
offsetRegex := regexp.MustCompile(`^(\+{-?[0-9]+})?([+-][0-9]+)*(-?/[1-9][0-9]*)?$`)
headerRegex := regexp.MustCompile("^~(0|[1-9][0-9]*)$")
for _, token := range tokens {
switch token {
case "":
@@ -1064,11 +1111,17 @@ func parsePreviewWindow(opts *previewOpts, input string) {
opts.border = tui.BorderSharp
case "noborder":
opts.border = tui.BorderNone
case "follow":
opts.follow = true
case "nofollow":
opts.follow = false
default:
if sizeRegex.MatchString(token) {
if headerRegex.MatchString(token) {
opts.headerLines = atoi(token[1:])
} else if sizeRegex.MatchString(token) {
opts.size = parseSize(token, 99, "window size")
} else if offsetRegex.MatchString(token) {
opts.scroll = token[1:]
opts.scroll = token
} else {
errorExit("invalid preview window option: " + token)
}
@@ -1076,10 +1129,10 @@ func parsePreviewWindow(opts *previewOpts, input string) {
}
}
func parseMargin(margin string) [4]sizeSpec {
func parseMargin(opt string, margin string) [4]sizeSpec {
margins := strings.Split(margin, ",")
checked := func(str string) sizeSpec {
return parseSize(str, 49, "margin")
return parseSize(str, 49, opt)
}
switch len(margins) {
case 1:
@@ -1099,7 +1152,7 @@ func parseMargin(margin string) [4]sizeSpec {
checked(margins[0]), checked(margins[1]),
checked(margins[2]), checked(margins[3])}
default:
errorExit("invalid margin: " + margin)
errorExit("invalid " + opt + ": " + margin)
}
return defaultMargin()
}
@@ -1163,10 +1216,10 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Expect[k] = v
}
case "--no-expect":
opts.Expect = make(map[int]string)
case "--no-phony":
opts.Expect = make(map[tui.Event]string)
case "--enabled", "--no-phony":
opts.Phony = false
case "--phony":
case "--disabled", "--phony":
opts.Phony = true
case "--tiebreak":
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
@@ -1315,7 +1368,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[%]][:rounded|sharp|noborder][:wrap][:cycle][:hidden][:+SCROLL[-OFFSET]][:default]"))
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:rounded|sharp|noborder][:wrap][:cycle][:hidden][:+SCROLL[OFFSETS][/DENOM]][:~HEADER_LINES][:default]"))
case "--height":
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
case "--min-height":
@@ -1324,6 +1377,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Height = sizeSpec{}
case "--no-margin":
opts.Margin = defaultMargin()
case "--no-padding":
opts.Padding = defaultMargin()
case "--no-border":
opts.BorderShape = tui.BorderNone
case "--border":
@@ -1335,7 +1390,12 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Unicode = true
case "--margin":
opts.Margin = parseMargin(
"margin",
nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
case "--padding":
opts.Padding = parseMargin(
"padding",
nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
case "--tabstop":
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
case "--clear":
@@ -1404,7 +1464,9 @@ func parseOptions(opts *Options, allArgs []string) {
} else if match, value := optString(arg, "--preview-window="); match {
parsePreviewWindow(&opts.Preview, value)
} else if match, value := optString(arg, "--margin="); match {
opts.Margin = parseMargin(value)
opts.Margin = parseMargin("margin", value)
} else if match, value := optString(arg, "--padding="); match {
opts.Padding = parseMargin("padding", value)
} else if match, value := optString(arg, "--tabstop="); match {
opts.Tabstop = atoi(value)
} else if match, value := optString(arg, "--hscroll-off="); match {
@@ -1478,11 +1540,11 @@ func postProcessOptions(opts *Options) {
}
// Default actions for CTRL-N / CTRL-P when --history is set
if opts.History != nil {
if _, prs := opts.Keymap[tui.CtrlP]; !prs {
opts.Keymap[tui.CtrlP] = toActions(actPreviousHistory)
if _, prs := opts.Keymap[tui.CtrlP.AsEvent()]; !prs {
opts.Keymap[tui.CtrlP.AsEvent()] = toActions(actPreviousHistory)
}
if _, prs := opts.Keymap[tui.CtrlN]; !prs {
opts.Keymap[tui.CtrlN] = toActions(actNextHistory)
if _, prs := opts.Keymap[tui.CtrlN.AsEvent()]; !prs {
opts.Keymap[tui.CtrlN.AsEvent()] = toActions(actNextHistory)
}
}

View File

@@ -102,7 +102,7 @@ func TestIrrelevantNth(t *testing.T) {
t.Errorf("nth should be empty: %v", opts.Nth)
}
}
for _, words := range [][]string{[]string{"--nth", "..,3", "+x"}, []string{"--nth", "3,1..", "+x"}, []string{"--nth", "..-1,1", "+x"}} {
for _, words := range [][]string{{"--nth", "..,3", "+x"}, {"--nth", "3,1..", "+x"}, {"--nth", "..-1,1", "+x"}} {
{
opts := defaultOptions()
parseOptions(opts, words)
@@ -125,26 +125,29 @@ func TestIrrelevantNth(t *testing.T) {
func TestParseKeys(t *testing.T) {
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
check := func(i int, s string) {
if pairs[i] != s {
t.Errorf("%s != %s", pairs[i], s)
checkEvent := func(e tui.Event, s string) {
if pairs[e] != s {
t.Errorf("%s != %s", pairs[e], s)
}
}
check := func(et tui.EventType, s string) {
checkEvent(et.AsEvent(), s)
}
if len(pairs) != 12 {
t.Error(12)
}
check(tui.CtrlZ, "ctrl-z")
check(tui.AltZ, "alt-z")
check(tui.F2, "f2")
check(tui.AltZ+'@', "@")
check(tui.AltA, "Alt-a")
check(tui.AltZ+'!', "!")
check(tui.CtrlA+'g'-'a', "ctrl-G")
check(tui.AltZ+'J', "J")
check(tui.AltZ+'g', "g")
check(tui.CtrlAltA, "ctrl-alt-a")
check(tui.CtrlAltM, "ALT-enter")
check(tui.AltSpace, "alt-SPACE")
check(tui.CtrlG, "ctrl-G")
checkEvent(tui.AltKey('z'), "alt-z")
checkEvent(tui.Key('@'), "@")
checkEvent(tui.AltKey('a'), "Alt-a")
checkEvent(tui.Key('!'), "!")
checkEvent(tui.Key('J'), "J")
checkEvent(tui.Key('g'), "g")
checkEvent(tui.CtrlAltKey('a'), "ctrl-alt-a")
checkEvent(tui.CtrlAltKey('m'), "ALT-enter")
checkEvent(tui.AltKey(' '), "alt-SPACE")
// Synonyms
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
@@ -152,7 +155,7 @@ func TestParseKeys(t *testing.T) {
t.Error(9)
}
check(tui.CtrlM, "Return")
check(tui.AltZ+' ', "space")
checkEvent(tui.Key(' '), "space")
check(tui.Tab, "tab")
check(tui.BTab, "btab")
check(tui.ESC, "esc")
@@ -184,93 +187,98 @@ func TestParseKeysWithComma(t *testing.T) {
t.Errorf("%d != %d", a, b)
}
}
check := func(pairs map[int]string, i int, s string) {
if pairs[i] != s {
t.Errorf("%s != %s", pairs[i], s)
check := func(pairs map[tui.Event]string, e tui.Event, s string) {
if pairs[e] != s {
t.Errorf("%s != %s", pairs[e], s)
}
}
pairs := parseKeyChords(",", "")
checkN(len(pairs), 1)
check(pairs, tui.AltZ+',', ",")
check(pairs, tui.Key(','), ",")
pairs = parseKeyChords(",,a,b", "")
checkN(len(pairs), 3)
check(pairs, tui.AltZ+'a', "a")
check(pairs, tui.AltZ+'b', "b")
check(pairs, tui.AltZ+',', ",")
check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key(','), ",")
pairs = parseKeyChords("a,b,,", "")
checkN(len(pairs), 3)
check(pairs, tui.AltZ+'a', "a")
check(pairs, tui.AltZ+'b', "b")
check(pairs, tui.AltZ+',', ",")
check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key(','), ",")
pairs = parseKeyChords("a,,,b", "")
checkN(len(pairs), 3)
check(pairs, tui.AltZ+'a', "a")
check(pairs, tui.AltZ+'b', "b")
check(pairs, tui.AltZ+',', ",")
check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key(','), ",")
pairs = parseKeyChords("a,,,b,c", "")
checkN(len(pairs), 4)
check(pairs, tui.AltZ+'a', "a")
check(pairs, tui.AltZ+'b', "b")
check(pairs, tui.AltZ+'c', "c")
check(pairs, tui.AltZ+',', ",")
check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key('c'), "c")
check(pairs, tui.Key(','), ",")
pairs = parseKeyChords(",,,", "")
checkN(len(pairs), 1)
check(pairs, tui.AltZ+',', ",")
check(pairs, tui.Key(','), ",")
pairs = parseKeyChords(",ALT-,,", "")
checkN(len(pairs), 1)
check(pairs, tui.AltKey(','), "ALT-,")
}
func TestBind(t *testing.T) {
keymap := defaultKeymap()
check := func(keyName int, arg1 string, types ...actionType) {
if len(keymap[keyName]) != len(types) {
t.Errorf("invalid number of actions (%d != %d)", len(types), len(keymap[keyName]))
check := func(event tui.Event, arg1 string, types ...actionType) {
if len(keymap[event]) != len(types) {
t.Errorf("invalid number of actions for %v (%d != %d)",
event, len(types), len(keymap[event]))
return
}
for idx, action := range keymap[keyName] {
for idx, action := range keymap[event] {
if types[idx] != action.t {
t.Errorf("invalid action type (%d != %d)", types[idx], action.t)
}
}
if len(arg1) > 0 && keymap[keyName][0].a != arg1 {
t.Errorf("invalid action argument: (%s != %s)", arg1, keymap[keyName][0].a)
if len(arg1) > 0 && keymap[event][0].a != arg1 {
t.Errorf("invalid action argument: (%s != %s)", arg1, keymap[event][0].a)
}
}
check(tui.CtrlA, "", actBeginningOfLine)
check(tui.CtrlA.AsEvent(), "", actBeginningOfLine)
parseKeymap(keymap,
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
"f1:execute(ls {+})+abort+execute(echo {+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
"x:Execute(foo+bar),X:execute/bar+baz/"+
",f1:+top,f1:+top"+
",f1:+first,f1:+top"+
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
check(tui.CtrlA, "", actKillLine)
check(tui.CtrlB, "", actToggleSort, actUp, actDown)
check(tui.AltZ+'c', "", actPageUp)
check(tui.AltZ+',', "", actAbort)
check(tui.AltZ+':', "", actAccept)
check(tui.AltZ, "", actPageDown)
check(tui.F1, "ls {+}", actExecute, actAbort, actExecute, actSelectAll, actTop, actTop)
check(tui.F2, "echo {}, {}, {}", actExecute)
check(tui.F3, "echo '({})'", actExecute)
check(tui.F4, "less {}", actExecute)
check(tui.AltZ+'x', "foo+bar", actExecute)
check(tui.AltZ+'X', "bar+baz", actExecute)
check(tui.AltA, "echo (,),[,],/,:,;,%,{}", actExecuteMulti)
check(tui.AltB, "echo (,),[,],/,:,@,%,{}", actExecute)
check(tui.AltZ+'+', "++\nfoobar,Y:execute(baz)+up", actExecute)
check(tui.CtrlA.AsEvent(), "", actKillLine)
check(tui.CtrlB.AsEvent(), "", actToggleSort, actUp, actDown)
check(tui.Key('c'), "", actPageUp)
check(tui.Key(','), "", actAbort)
check(tui.Key(':'), "", actAccept)
check(tui.AltKey('z'), "", actPageDown)
check(tui.F1.AsEvent(), "ls {+}", actExecute, actAbort, actExecute, actSelectAll, actFirst, actFirst)
check(tui.F2.AsEvent(), "echo {}, {}, {}", actExecute)
check(tui.F3.AsEvent(), "echo '({})'", actExecute)
check(tui.F4.AsEvent(), "less {}", actExecute)
check(tui.Key('x'), "foo+bar", actExecute)
check(tui.Key('X'), "bar+baz", actExecute)
check(tui.AltKey('a'), "echo (,),[,],/,:,;,%,{}", actExecuteMulti)
check(tui.AltKey('b'), "echo (,),[,],/,:,@,%,{}", actExecute)
check(tui.Key('+'), "++\nfoobar,Y:execute(baz)+up", actExecute)
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
check(tui.AltZ+int([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
check(tui.Key([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
}
parseKeymap(keymap, "f1:abort")
check(tui.F1, "", actAbort)
check(tui.F1.AsEvent(), "", actAbort)
}
func TestColorSpec(t *testing.T) {
@@ -314,11 +322,12 @@ func TestColorSpec(t *testing.T) {
}
func TestDefaultCtrlNP(t *testing.T) {
check := func(words []string, key int, expected actionType) {
check := func(words []string, et tui.EventType, expected actionType) {
e := et.AsEvent()
opts := defaultOptions()
parseOptions(opts, words)
postProcessOptions(opts)
if opts.Keymap[key][0].t != expected {
if opts.Keymap[e][0].t != expected {
t.Error()
}
}
@@ -380,18 +389,18 @@ func TestPreviewOpts(t *testing.T) {
opts.Preview.hidden == true &&
opts.Preview.wrap == true &&
opts.Preview.position == posLeft &&
opts.Preview.scroll == "{1}-/2" &&
opts.Preview.scroll == "+{1}-/2" &&
opts.Preview.size.percent == false &&
opts.Preview.size.size == 15) {
t.Error(opts.Preview)
}
opts = optsFor("--preview-window=up:15:wrap:hidden:+{1}-/2", "--preview-window=down", "--preview-window=cycle")
opts = optsFor("--preview-window=up:15:wrap:hidden:+{1}+3-1-2/2", "--preview-window=down", "--preview-window=cycle")
if !(opts.Preview.command == "" &&
opts.Preview.hidden == true &&
opts.Preview.wrap == true &&
opts.Preview.cycle == true &&
opts.Preview.position == posDown &&
opts.Preview.scroll == "{1}-/2" &&
opts.Preview.scroll == "+{1}+3-1-2/2" &&
opts.Preview.size.percent == false &&
opts.Preview.size.size == 15) {
t.Error(opts.Preview.size.size)

View File

@@ -337,7 +337,7 @@ func (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (*Result,
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
var input []Token
if len(p.nth) == 0 {
input = []Token{Token{text: &item.text, prefixLength: 0}}
input = []Token{{text: &item.text, prefixLength: 0}}
} else {
input = p.transformInput(item)
}
@@ -350,7 +350,7 @@ func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset,
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, *[]int) {
var input []Token
if len(p.nth) == 0 {
input = []Token{Token{text: &item.text, prefixLength: 0}}
input = []Token{{text: &item.text, prefixLength: 0}}
} else {
input = p.transformInput(item)
}

View File

@@ -131,7 +131,7 @@ func TestCaseSensitivity(t *testing.T) {
func TestOrigTextAndTransformed(t *testing.T) {
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("jg"))
tokens := Tokenize("junegunn", Delimiter{})
trans := Transform(tokens, []Range{Range{1, 1}})
trans := Transform(tokens, []Range{{1, 1}})
origBytes := []byte("junegunn.choi")
for _, extended := range []bool{false, true} {

View File

@@ -160,7 +160,19 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
color := colMatch
if curr < -1 && theme.Colored {
origColor := ansiToColorPair(itemColors[-curr-2], colMatch)
color = origColor.MergeNonDefault(color)
// hl or hl+ only sets the foreground color, so colMatch is the
// combination of either [hl and bg] or [hl+ and bg+].
//
// If the original text already has background color, and the
// foreground color of colMatch is -1, we shouldn't only apply the
// background color of colMatch.
// e.g. echo -e "\x1b[32;7mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline
// echo -e "\x1b[42mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline
if color.Fg().IsDefault() && origColor.HasBg() {
color = origColor
} else {
color = origColor.MergeNonDefault(color)
}
}
colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, color: color})

View File

@@ -18,8 +18,8 @@ func withIndex(i *Item, index int) *Item {
func TestOffsetSort(t *testing.T) {
offsets := []Offset{
Offset{3, 5}, Offset{2, 7},
Offset{1, 3}, Offset{2, 9}}
{3, 5}, {2, 7},
{1, 3}, {2, 9}}
sort.Sort(ByOrder(offsets))
if offsets[0][0] != 1 || offsets[0][1] != 3 ||
@@ -84,13 +84,13 @@ func TestResultRank(t *testing.T) {
// Sort by relevance
item3 := buildResult(
withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 3)
withIndex(&Item{}, 2), []Offset{{1, 3}, {5, 7}}, 3)
item4 := buildResult(
withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 4)
withIndex(&Item{}, 2), []Offset{{1, 2}, {6, 7}}, 4)
item5 := buildResult(
withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 5)
withIndex(&Item{}, 2), []Offset{{1, 3}, {5, 7}}, 5)
item6 := buildResult(
withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 6)
withIndex(&Item{}, 2), []Offset{{1, 2}, {6, 7}}, 6)
items = []Result{item1, item2, item3, item4, item5, item6}
sort.Sort(ByRelevance(items))
if !(items[0] == item6 && items[1] == item5 &&
@@ -109,10 +109,10 @@ func TestColorOffset(t *testing.T) {
item := Result{
item: &Item{
colors: &[]ansiOffset{
{[2]int32{0, 20}, ansiState{1, 5, 0}},
{[2]int32{22, 27}, ansiState{2, 6, tui.Bold}},
{[2]int32{30, 32}, ansiState{3, 7, 0}},
{[2]int32{33, 40}, ansiState{4, 8, tui.Bold}}}}}
{[2]int32{0, 20}, ansiState{1, 5, 0, -1}},
{[2]int32{22, 27}, ansiState{2, 6, tui.Bold, -1}},
{[2]int32{30, 32}, ansiState{3, 7, 0, -1}},
{[2]int32{33, 40}, ansiState{4, 8, tui.Bold, -1}}}}}
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)

File diff suppressed because it is too large Load Diff

View File

@@ -35,7 +35,7 @@ type Delimiter struct {
str *string
}
// String returns the string representation of a Delimeter.
// String returns the string representation of a Delimiter.
func (d Delimiter) String() string {
return fmt.Sprintf("Delimiter{regex: %v, str: &%q}", d.regex, *d.str)
}

View File

@@ -4,7 +4,7 @@
package tui
type Attr int
type Attr int32
func HasFullscreenRenderer() bool {
return false

View File

@@ -1,6 +1,7 @@
package tui
import (
"bytes"
"fmt"
"os"
"regexp"
@@ -230,7 +231,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
}
retries := 0
if c == ESC || nonblock {
if c == ESC.Int() || nonblock {
retries = r.escDelay / escPollInterval
}
buffer = append(buffer, byte(c))
@@ -245,7 +246,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
continue
}
break
} else if c == ESC && pc != c {
} else if c == ESC.Int() && pc != c {
retries = r.escDelay / escPollInterval
} else {
retries = 0
@@ -278,11 +279,11 @@ func (r *LightRenderer) GetChar() Event {
}()
switch r.buffer[0] {
case CtrlC:
case CtrlC.Byte():
return Event{CtrlC, 0, nil}
case CtrlG:
case CtrlG.Byte():
return Event{CtrlG, 0, nil}
case CtrlQ:
case CtrlQ.Byte():
return Event{CtrlQ, 0, nil}
case 127:
return Event{BSpace, 0, nil}
@@ -296,7 +297,7 @@ func (r *LightRenderer) GetChar() Event {
return Event{CtrlCaret, 0, nil}
case 31:
return Event{CtrlSlash, 0, nil}
case ESC:
case ESC.Byte():
ev := r.escSequence(&sz)
// Second chance
if ev.Type == Invalid {
@@ -307,8 +308,8 @@ func (r *LightRenderer) GetChar() Event {
}
// CTRL-A ~ CTRL-Z
if r.buffer[0] <= CtrlZ {
return Event{int(r.buffer[0]), 0, nil}
if r.buffer[0] <= CtrlZ.Byte() {
return Event{EventType(r.buffer[0]), 0, nil}
}
char, rsz := utf8.DecodeRune(r.buffer)
if char == utf8.RuneError {
@@ -331,26 +332,16 @@ func (r *LightRenderer) escSequence(sz *int) Event {
*sz = 2
if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {
return Event{int(CtrlAltA + r.buffer[1] - 1), 0, nil}
return CtrlAltKey(rune(r.buffer[1] + 'a' - 1))
}
alt := false
if len(r.buffer) > 2 && r.buffer[1] == ESC {
if len(r.buffer) > 2 && r.buffer[1] == ESC.Byte() {
r.buffer = r.buffer[1:]
alt = true
}
switch r.buffer[1] {
case ESC:
case ESC.Byte():
return Event{ESC, 0, nil}
case ' ':
return Event{AltSpace, 0, nil}
case '/':
return Event{AltSlash, 0, nil}
case 'b':
return Event{AltB, 0, nil}
case 'd':
return Event{AltD, 0, nil}
case 'f':
return Event{AltF, 0, nil}
case 127:
return Event{AltBS, 0, nil}
case '[', 'O':
@@ -463,20 +454,54 @@ func (r *LightRenderer) escSequence(sz *int) Event {
}
return Event{Invalid, 0, nil}
case ';':
if len(r.buffer) != 6 {
if len(r.buffer) < 6 {
return Event{Invalid, 0, nil}
}
*sz = 6
switch r.buffer[4] {
case '2', '5':
switch r.buffer[5] {
case '1', '2', '3', '5':
alt := r.buffer[4] == '3'
altShift := r.buffer[4] == '1' && r.buffer[5] == '0'
char := r.buffer[5]
if altShift {
if len(r.buffer) < 7 {
return Event{Invalid, 0, nil}
}
*sz = 7
char = r.buffer[6]
}
switch char {
case 'A':
if alt {
return Event{AltUp, 0, nil}
}
if altShift {
return Event{AltSUp, 0, nil}
}
return Event{SUp, 0, nil}
case 'B':
if alt {
return Event{AltDown, 0, nil}
}
if altShift {
return Event{AltSDown, 0, nil}
}
return Event{SDown, 0, nil}
case 'C':
if alt {
return Event{AltRight, 0, nil}
}
if altShift {
return Event{AltSRight, 0, nil}
}
return Event{SRight, 0, nil}
case 'D':
if alt {
return Event{AltLeft, 0, nil}
}
if altShift {
return Event{AltSLeft, 0, nil}
}
return Event{SLeft, 0, nil}
}
} // r.buffer[4]
@@ -484,11 +509,11 @@ func (r *LightRenderer) escSequence(sz *int) Event {
} // r.buffer[2]
} // r.buffer[2]
} // r.buffer[1]
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}
rest := bytes.NewBuffer(r.buffer[1:])
c, size, err := rest.ReadRune()
if err == nil {
*sz = 1 + size
return AltKey(c)
}
return Event{Invalid, 0, nil}
}
@@ -881,12 +906,6 @@ func (w *LightWindow) fill(str string, onMove func()) FillReturn {
for i, line := range allLines {
lines := wrapLine(line, w.posx, w.width, w.tabstop)
for j, wl := range lines {
if w.posx >= w.Width()-1 && wl.displayWidth == 0 {
if w.posy < w.height-1 {
w.Move(w.posy+1, 0)
}
return FillNextLine
}
w.stderrInternal(wl.text, false)
w.posx += wl.displayWidth
@@ -901,6 +920,14 @@ func (w *LightWindow) fill(str string, onMove func()) FillReturn {
}
}
}
if w.posx+1 >= w.Width() {
if w.posy+1 >= w.height {
return FillSuspend
}
w.Move(w.posy+1, 0)
onMove()
return FillNextLine
}
return FillContinue
}

View File

@@ -222,66 +222,69 @@ func (r *FullscreenRenderer) GetChar() Event {
// process keyboard:
case *tcell.EventKey:
alt := (ev.Modifiers() & tcell.ModAlt) > 0
keyfn := func(r rune) int {
mods := ev.Modifiers()
alt := (mods & tcell.ModAlt) > 0
shift := (mods & tcell.ModShift) > 0
altShift := alt && shift
keyfn := func(r rune) Event {
if alt {
return CtrlAltA - 'a' + int(r)
return CtrlAltKey(r)
}
return CtrlA - 'a' + int(r)
return EventType(CtrlA.Int() - 'a' + int(r)).AsEvent()
}
switch ev.Key() {
case tcell.KeyCtrlA:
return Event{keyfn('a'), 0, nil}
return keyfn('a')
case tcell.KeyCtrlB:
return Event{keyfn('b'), 0, nil}
return keyfn('b')
case tcell.KeyCtrlC:
return Event{keyfn('c'), 0, nil}
return keyfn('c')
case tcell.KeyCtrlD:
return Event{keyfn('d'), 0, nil}
return keyfn('d')
case tcell.KeyCtrlE:
return Event{keyfn('e'), 0, nil}
return keyfn('e')
case tcell.KeyCtrlF:
return Event{keyfn('f'), 0, nil}
return keyfn('f')
case tcell.KeyCtrlG:
return Event{keyfn('g'), 0, nil}
return keyfn('g')
case tcell.KeyCtrlH:
return Event{keyfn('h'), 0, nil}
return keyfn('h')
case tcell.KeyCtrlI:
return Event{keyfn('i'), 0, nil}
return keyfn('i')
case tcell.KeyCtrlJ:
return Event{keyfn('j'), 0, nil}
return keyfn('j')
case tcell.KeyCtrlK:
return Event{keyfn('k'), 0, nil}
return keyfn('k')
case tcell.KeyCtrlL:
return Event{keyfn('l'), 0, nil}
return keyfn('l')
case tcell.KeyCtrlM:
return Event{keyfn('m'), 0, nil}
return keyfn('m')
case tcell.KeyCtrlN:
return Event{keyfn('n'), 0, nil}
return keyfn('n')
case tcell.KeyCtrlO:
return Event{keyfn('o'), 0, nil}
return keyfn('o')
case tcell.KeyCtrlP:
return Event{keyfn('p'), 0, nil}
return keyfn('p')
case tcell.KeyCtrlQ:
return Event{keyfn('q'), 0, nil}
return keyfn('q')
case tcell.KeyCtrlR:
return Event{keyfn('r'), 0, nil}
return keyfn('r')
case tcell.KeyCtrlS:
return Event{keyfn('s'), 0, nil}
return keyfn('s')
case tcell.KeyCtrlT:
return Event{keyfn('t'), 0, nil}
return keyfn('t')
case tcell.KeyCtrlU:
return Event{keyfn('u'), 0, nil}
return keyfn('u')
case tcell.KeyCtrlV:
return Event{keyfn('v'), 0, nil}
return keyfn('v')
case tcell.KeyCtrlW:
return Event{keyfn('w'), 0, nil}
return keyfn('w')
case tcell.KeyCtrlX:
return Event{keyfn('x'), 0, nil}
return keyfn('x')
case tcell.KeyCtrlY:
return Event{keyfn('y'), 0, nil}
return keyfn('y')
case tcell.KeyCtrlZ:
return Event{keyfn('z'), 0, nil}
return keyfn('z')
case tcell.KeyCtrlSpace:
return Event{CtrlSpace, 0, nil}
case tcell.KeyCtrlBackslash:
@@ -297,21 +300,45 @@ func (r *FullscreenRenderer) GetChar() Event {
return Event{BSpace, 0, nil}
case tcell.KeyUp:
if altShift {
return Event{AltSUp, 0, nil}
}
if shift {
return Event{SUp, 0, nil}
}
if alt {
return Event{AltUp, 0, nil}
}
return Event{Up, 0, nil}
case tcell.KeyDown:
if altShift {
return Event{AltSDown, 0, nil}
}
if shift {
return Event{SDown, 0, nil}
}
if alt {
return Event{AltDown, 0, nil}
}
return Event{Down, 0, nil}
case tcell.KeyLeft:
if altShift {
return Event{AltSLeft, 0, nil}
}
if shift {
return Event{SLeft, 0, nil}
}
if alt {
return Event{AltLeft, 0, nil}
}
return Event{Left, 0, nil}
case tcell.KeyRight:
if altShift {
return Event{AltSRight, 0, nil}
}
if shift {
return Event{SRight, 0, nil}
}
if alt {
return Event{AltRight, 0, nil}
}
@@ -362,18 +389,7 @@ func (r *FullscreenRenderer) GetChar() Event {
case tcell.KeyRune:
r := ev.Rune()
if alt {
switch r {
case ' ':
return Event{AltSpace, 0, nil}
case '/':
return Event{AltSlash, 0, nil}
}
if r >= 'a' && r <= 'z' {
return Event{AltA + int(r) - 'a', 0, nil}
}
if r >= '0' && r <= '9' {
return Event{Alt0 + int(r) - '0', 0, nil}
}
return AltKey(r)
}
return Event{Rune, r, nil}

View File

@@ -8,8 +8,10 @@ import (
)
// Types of user action
type EventType int
const (
Rune = iota
Rune EventType = iota
CtrlA
CtrlB
@@ -89,8 +91,6 @@ const (
Change
BackwardEOF
AltSpace
AltSlash
AltBS
AltUp
@@ -98,20 +98,43 @@ const (
AltLeft
AltRight
Alt0
AltSUp
AltSDown
AltSLeft
AltSRight
Alt
CtrlAlt
)
const ( // Reset iota
AltA = Alt0 + 'a' - '0' + iota
AltB
AltC
AltD
AltE
AltF
AltZ = AltA + 'z' - 'a'
CtrlAltA = AltZ + 1
CtrlAltM = CtrlAltA + 'm' - 'a'
)
func (t EventType) AsEvent() Event {
return Event{t, 0, nil}
}
func (t EventType) Int() int {
return int(t)
}
func (t EventType) Byte() byte {
return byte(t)
}
func (e Event) Comparable() Event {
// Ignore MouseEvent pointer
return Event{e.Type, e.Char, nil}
}
func Key(r rune) Event {
return Event{Rune, r, nil}
}
func AltKey(r rune) Event {
return Event{Alt, r, nil}
}
func CtrlAltKey(r rune) Event {
return Event{CtrlAlt, r, nil}
}
const (
doubleClickDuration = 500 * time.Millisecond
@@ -119,6 +142,10 @@ const (
type Color int32
func (c Color) IsDefault() bool {
return c == colDefault
}
func (c Color) is24() bool {
return c > 0 && (c&(1<<24)) > 0
}
@@ -185,6 +212,11 @@ func (p ColorPair) Attr() Attr {
return p.attr
}
func (p ColorPair) HasBg() bool {
return p.attr&Reverse == 0 && p.bg != colDefault ||
p.attr&Reverse > 0 && p.fg != colDefault
}
func (p ColorPair) merge(other ColorPair, except Color) ColorPair {
dup := p
dup.attr = dup.attr.Merge(other.attr)
@@ -218,6 +250,7 @@ func (p ColorPair) MergeNonDefault(other ColorPair) ColorPair {
type ColorTheme struct {
Colored bool
Input ColorAttr
Disabled ColorAttr
Fg ColorAttr
Bg ColorAttr
PreviewFg ColorAttr
@@ -237,7 +270,7 @@ type ColorTheme struct {
}
type Event struct {
Type int
Type EventType
Char rune
MouseEvent *MouseEvent
}
@@ -389,6 +422,7 @@ var (
ColPrompt ColorPair
ColNormal ColorPair
ColInput ColorPair
ColDisabled ColorPair
ColMatch ColorPair
ColCursor ColorPair
ColCursorEmpty ColorPair
@@ -411,6 +445,7 @@ func EmptyTheme() *ColorTheme {
return &ColorTheme{
Colored: true,
Input: ColorAttr{colUndefined, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colUndefined, AttrUndefined},
Bg: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
@@ -433,6 +468,7 @@ func NoColorTheme() *ColorTheme {
return &ColorTheme{
Colored: false,
Input: ColorAttr{colDefault, AttrRegular},
Disabled: ColorAttr{colDefault, AttrRegular},
Fg: ColorAttr{colDefault, AttrRegular},
Bg: ColorAttr{colDefault, AttrRegular},
PreviewFg: ColorAttr{colDefault, AttrRegular},
@@ -460,6 +496,7 @@ func init() {
Default16 = &ColorTheme{
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
@@ -479,6 +516,7 @@ func init() {
Dark256 = &ColorTheme{
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
@@ -498,6 +536,7 @@ func init() {
Light256 = &ColorTheme{
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
@@ -532,6 +571,7 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
return c
}
theme.Input = o(baseTheme.Input, theme.Input)
theme.Disabled = o(theme.Input, o(baseTheme.Disabled, theme.Disabled))
theme.Fg = o(baseTheme.Fg, theme.Fg)
theme.Bg = o(baseTheme.Bg, theme.Bg)
theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg))
@@ -554,6 +594,9 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
func initPalette(theme *ColorTheme) {
pair := func(fg, bg ColorAttr) ColorPair {
if fg.Color == colDefault && (fg.Attr&Reverse) > 0 {
bg.Color = colDefault
}
return ColorPair{fg.Color, bg.Color, fg.Attr}
}
blank := theme.Fg
@@ -562,6 +605,7 @@ func initPalette(theme *ColorTheme) {
ColPrompt = pair(theme.Prompt, theme.Bg)
ColNormal = pair(theme.Fg, theme.Bg)
ColInput = pair(theme.Input, theme.Bg)
ColDisabled = pair(theme.Disabled, theme.Bg)
ColMatch = pair(theme.Match, theme.Bg)
ColCursor = pair(theme.Cursor, theme.Gutter)
ColCursorEmpty = pair(blank, theme.Gutter)

View File

@@ -148,14 +148,15 @@ class Tmux
def prepare
tries = 0
begin
self.until do |lines|
send_keys ' ', 'C-u', :Enter, 'hello', :Left, :Right
lines[-1] == 'hello'
self.until(true) do |lines|
message = "Prepare[#{tries}]"
send_keys ' ', 'C-u', :Enter, message, :Left, :Right
lines[-1] == message
end
rescue Minitest::Assertion
(tries += 1) < 5 ? retry : raise
end
send_keys 'C-u'
send_keys 'C-u', 'C-l'
end
private
@@ -1554,8 +1555,8 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_equal '> 1', lines[-2] }
end
def test_change_top
tmux.send_keys %(seq 1000 | #{FZF} --bind change:top), :Enter
def test_change_first_last
tmux.send_keys %(seq 1000 | #{FZF} --bind change:first,alt-Z:last), :Enter
tmux.until { |lines| assert_equal 1000, lines.match_count }
tmux.send_keys :Up
tmux.until { |lines| assert_equal '> 2', lines[-4] }
@@ -1565,6 +1566,10 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_equal '> 10', lines[-4] }
tmux.send_keys 1
tmux.until { |lines| assert_equal '> 11', lines[-3] }
tmux.send_keys 'C-u'
tmux.until { |lines| assert_equal '> 1', lines[-3] }
tmux.send_keys :Escape, 'Z'
tmux.until { |lines| assert_equal '> 1000', lines[0] }
tmux.send_keys :Enter
end
@@ -1654,13 +1659,35 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_includes lines[1], ' + green ' }
end
def test_phony
tmux.send_keys %(seq 1000 | #{FZF} --query 333 --phony --preview 'echo {} {q}'), :Enter
def test_disabled
tmux.send_keys %(seq 1000 | #{FZF} --query 333 --disabled --bind a:enable-search,b:disable-search,c:toggle-search --preview 'echo {} {q}'), :Enter
tmux.until { |lines| assert_equal 1000, lines.match_count }
tmux.until { |lines| assert_includes lines[1], ' 1 333 ' }
tmux.send_keys 'foo'
tmux.until { |lines| assert_equal 1000, lines.match_count }
tmux.until { |lines| assert_includes lines[1], ' 1 333foo ' }
# Already disabled, no change
tmux.send_keys 'b'
tmux.until { |lines| assert_equal 1000, lines.match_count }
# Enable search
tmux.send_keys 'a'
tmux.until { |lines| assert_equal 0, lines.match_count }
tmux.send_keys :BSpace, :BSpace, :BSpace
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.until { |lines| assert_includes lines[1], ' 333 333 ' }
# Toggle search -> disabled again, but retains the previous result
tmux.send_keys 'c'
tmux.send_keys 'foo'
tmux.until { |lines| assert_includes lines[1], ' 333 333foo ' }
tmux.until { |lines| assert_equal 1, lines.match_count }
# Enabled, no match
tmux.send_keys 'c'
tmux.until { |lines| assert_equal 0, lines.match_count }
tmux.until { |lines| assert_includes lines[1], ' 333foo ' }
end
def test_reload
@@ -1823,6 +1850,198 @@ class TestGoFZF < TestBase
tmux.until { |lines| lines.item_count == 100 }
tmux.until { |lines| lines[1]&.include?('[200]') }
end
def test_change_prompt
tmux.send_keys "#{FZF} --bind 'a:change-prompt(a> ),b:change-prompt:b> ' --query foo", :Enter
tmux.until { |lines| assert_equal '> foo', lines[-1] }
tmux.send_keys 'a'
tmux.until { |lines| assert_equal 'a> foo', lines[-1] }
tmux.send_keys 'b'
tmux.until { |lines| assert_equal 'b> foo', lines[-1] }
end
def test_preview_window_follow
tmux.send_keys "#{FZF} --preview 'seq 1000 | nl' --preview-window down:noborder:follow", :Enter
tmux.until { |lines| assert_equal '1000 1000', lines[-1].strip }
end
def test_toggle_preview_wrap
tmux.send_keys "#{FZF} --preview 'for i in $(seq $FZF_PREVIEW_COLUMNS); do echo -n .; done; echo wrapped; echo 2nd line' --bind ctrl-w:toggle-preview-wrap", :Enter
2.times do
tmux.until { |lines| assert_includes lines[2], '2nd line' }
tmux.send_keys 'C-w'
tmux.until do |lines|
assert_includes lines[2], 'wrapped'
assert_includes lines[3], '2nd line'
end
tmux.send_keys 'C-w'
end
end
def test_close
tmux.send_keys "seq 100 | #{FZF} --preview 'echo foo' --bind ctrl-c:close", :Enter
tmux.until { |lines| assert_equal 100, lines.match_count }
tmux.until { |lines| assert_includes lines[1], 'foo' }
tmux.send_keys 'C-c'
tmux.until { |lines| refute_includes lines[1], 'foo' }
tmux.send_keys '10'
tmux.until { |lines| assert_equal 2, lines.match_count }
tmux.send_keys 'C-c'
tmux.send_keys 'C-l', 'closed'
tmux.until { |lines| assert_includes lines[0], 'closed' }
end
def test_select_deselect
tmux.send_keys "seq 3 | #{FZF} --multi --bind up:deselect+up,down:select+down", :Enter
tmux.until { |lines| assert_equal 3, lines.match_count }
tmux.send_keys :Tab
tmux.until { |lines| assert_equal 1, lines.select_count }
tmux.send_keys :Up
tmux.until { |lines| assert_equal 0, lines.select_count }
tmux.send_keys :Down, :Down
tmux.until { |lines| assert_equal 2, lines.select_count }
tmux.send_keys :Tab
tmux.until { |lines| assert_equal 1, lines.select_count }
tmux.send_keys :Down, :Down
tmux.until { |lines| assert_equal 2, lines.select_count }
tmux.send_keys :Up
tmux.until { |lines| assert_equal 1, lines.select_count }
tmux.send_keys :Down
tmux.until { |lines| assert_equal 1, lines.select_count }
tmux.send_keys :Down
tmux.until { |lines| assert_equal 2, lines.select_count }
end
def test_interrupt_execute
tmux.send_keys "seq 100 | #{FZF} --bind 'ctrl-l:execute:echo executing {}; sleep 100'", :Enter
tmux.until { |lines| assert_equal 100, lines.item_count }
tmux.send_keys 'C-l'
tmux.until { |lines| assert lines.any_include?('executing 1') }
tmux.send_keys 'C-c'
tmux.until { |lines| assert_equal 100, lines.item_count }
tmux.send_keys 99
tmux.until { |lines| assert_equal 1, lines.match_count }
end
def test_kill_default_command_on_abort
script = tempname + '.sh'
writelines(script,
['#!/usr/bin/env bash',
"echo 'Started'",
'while :; do sleep 1; done'])
system("chmod +x #{script}")
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', "FZF_DEFAULT_COMMAND=#{script}"), :Enter
tmux.until { |lines| assert_equal 1, lines.item_count }
tmux.send_keys 'C-c'
tmux.send_keys 'C-l', 'closed'
tmux.until { |lines| assert_includes lines[0], 'closed' }
wait { refute system("pgrep -f #{script}") }
ensure
system("pkill -9 -f #{script}")
begin
File.unlink(script)
rescue StandardError
nil
end
end
def test_kill_default_command_on_accept
script = tempname + '.sh'
writelines(script,
['#!/usr/bin/env bash',
"echo 'Started'",
'while :; do sleep 1; done'])
system("chmod +x #{script}")
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', "FZF_DEFAULT_COMMAND=#{script}"), :Enter
tmux.until { |lines| assert_equal 1, lines.item_count }
tmux.send_keys :Enter
assert_equal 'Started', readonce.chomp
wait { refute system("pgrep -f #{script}") }
ensure
system("pkill -9 -f #{script}")
begin
File.unlink(script)
rescue StandardError
nil
end
end
def test_kill_reload_command_on_abort
script = tempname + '.sh'
writelines(script,
['#!/usr/bin/env bash',
"echo 'Started'",
'while :; do sleep 1; done'])
system("chmod +x #{script}")
tmux.send_keys "seq 1 3 | #{fzf("--bind 'ctrl-r:reload(#{script})'")}", :Enter
tmux.until { |lines| assert_equal 3, lines.item_count }
tmux.send_keys 'C-r'
tmux.until { |lines| assert_equal 1, lines.item_count }
tmux.send_keys 'C-c'
tmux.send_keys 'C-l', 'closed'
tmux.until { |lines| assert_includes lines[0], 'closed' }
wait { refute system("pgrep -f #{script}") }
ensure
system("pkill -9 -f #{script}")
begin
File.unlink(script)
rescue StandardError
nil
end
end
def test_kill_reload_command_on_accept
script = tempname + '.sh'
writelines(script,
['#!/usr/bin/env bash',
"echo 'Started'",
'while :; do sleep 1; done'])
system("chmod +x #{script}")
tmux.send_keys "seq 1 3 | #{fzf("--bind 'ctrl-r:reload(#{script})'")}", :Enter
tmux.until { |lines| assert_equal 3, lines.item_count }
tmux.send_keys 'C-r'
tmux.until { |lines| assert_equal 1, lines.item_count }
tmux.send_keys :Enter
assert_equal 'Started', readonce.chomp
wait { refute system("pgrep -f #{script}") }
ensure
system("pkill -9 -f #{script}")
begin
File.unlink(script)
rescue StandardError
nil
end
end
def test_preview_header
tmux.send_keys "seq 100 | #{FZF} --bind ctrl-k:preview-up+preview-up,ctrl-j:preview-down+preview-down+preview-down --preview 'seq 1000' --preview-window 'top:+{1}:~3'", :Enter
tmux.until { |lines| assert_equal 100, lines.item_count }
top5 = ->(lines) { lines.drop(1).take(5).map { |s| s[/[0-9]+/] } }
tmux.until do |lines|
assert_includes lines[1], '4/1000'
assert_equal(%w[1 2 3 4 5], top5[lines])
end
tmux.send_keys '55'
tmux.until do |lines|
assert_equal 1, lines.match_count
assert_equal(%w[1 2 3 55 56], top5[lines])
end
tmux.send_keys 'C-J'
tmux.until do |lines|
assert_equal(%w[1 2 3 58 59], top5[lines])
end
tmux.send_keys :BSpace
tmux.until do |lines|
assert_equal 19, lines.match_count
assert_equal(%w[1 2 3 5 6], top5[lines])
end
tmux.send_keys 'C-K'
tmux.until { |lines| assert_equal(%w[1 2 3 4 5], top5[lines]) }
end
end
module TestShell
@@ -1993,7 +2212,7 @@ module CompletionTest
end
# ~USERNAME**<TAB>
user = ENV['USER']
user = `whoami`.chomp
tmux.send_keys 'C-u'
tmux.send_keys "cat ~#{user}**", :Tab
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }