Compare commits

...

200 Commits

Author SHA1 Message Date
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
Junegunn Choi
d4c9db0a27 0.24.1 2020-10-29 01:45:55 +09:00
Junegunn Choi
b5e0e29ec6 Assign default number version (without patch version)
So that you can still build and use fzf even when the precise version
number is not injected via -ldflags.
2020-10-29 01:38:34 +09:00
Junegunn Choi
569be4c6c9 [vim] Allow 'border': 'no' to be consistent with --color=no 2020-10-29 01:32:41 +09:00
Junegunn Choi
e7ca237b07 Fix nil error on --color=bw
Fix #2229
2020-10-29 01:27:08 +09:00
Junegunn Choi
a7d3b72117 Make build flags consistent 2020-10-28 18:39:39 +09:00
Junegunn Choi
3ba7b5cf2d Make Makefile fail when git information is not available 2020-10-28 18:33:22 +09:00
Junegunn Choi
254e9765fe [install] Pass version number to go get command
Related: https://github.com/junegunn/fzf.vim/issues/1150#issuecomment-717735149
2020-10-28 15:56:05 +09:00
Junegunn Choi
3304f284a5 Panic when fzf was built without version information
So that the package maintainers would immediately know that the build is
incorrect. But is there a way to make build simply fail?

Related: https://github.com/junegunn/fzf.vim/issues/1150
2020-10-28 10:51:32 +09:00
Junegunn Choi
0d5f862daf 0.24.0 2020-10-27 23:57:18 +09:00
Junegunn Choi
51dfacd542 Merge branch 'devel' into master 2020-10-27 23:57:03 +09:00
Junegunn Choi
c691d52fa7 Fix: barbled multibyte text(exe. Japanese). (#2224)
* Fix: barbled multibyte text(exe. Japanese).

* fixup

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2020-10-27 23:55:10 +09:00
Junegunn Choi
de3d09fe79 fixup 2020-10-27 23:53:25 +09:00
Junegunn Choi
eaa413c566 Fix error when preview command failed to start 2020-10-27 21:36:38 +09:00
nekowasabi
407205e52b Fix: barbled multibyte text(exe. Japanese). 2020-10-27 17:16:47 +09:00
Junegunn Choi
552414978e 0.24.0-rc1 2020-10-27 11:07:27 +09:00
Junegunn Choi
607081bbaa [vim] Download latest binary to meet version requirement 2020-10-27 01:01:58 +09:00
Junegunn Choi
e73383fbbb [vim] Add 'none' option for popup border 2020-10-26 23:41:20 +09:00
Junegunn Choi
2e8e63fb0b Add more --border options
Instead of drawing the window border in Vim using an extra window,
extend the --border option so that we do can it natively.

Close #2223
Fix #2184
2020-10-26 22:51:22 +09:00
Junegunn Choi
874f7dd416 Update --color example in CHANGELOG
New color name: input
2020-10-26 00:22:06 +09:00
Junegunn Choi
8b0e3b1624 Update --color docs 2020-10-26 00:15:30 +09:00
Junegunn Choi
9b946f2b7a Fix preview window of tcell renderer 2020-10-25 21:43:53 +09:00
Junegunn Choi
11841f688b Add support for text styling using --color
Close #1663
2020-10-25 19:30:41 +09:00
Junegunn Choi
03c4f04246 Use 64-bit integer for preview version 2020-10-24 16:55:55 +09:00
Junegunn Choi
a1f06ae27f Fix regression where empty preview content is not displayed 2020-10-23 23:52:05 +09:00
Junegunn Choi
69dffd78a6 Do not assume that each character takes at least 1 column
Fixes #2163, though this is not a proper fix to the problem.
2020-10-23 23:32:10 +09:00
Junegunn Choi
2750e19657 Update go-runewidth 2020-10-23 23:31:46 +09:00
Junegunn Choi
b0987f727b Clarify that additional installation steps may be required
Close #2211
2020-10-23 22:52:46 +09:00
Junegunn Choi
a4d9b0b468 Support ANSI escape sequence for clearing display in preview window
fzf --preview 'for i in $(seq 100000); do
    (( i % 200 == 0 )) && printf "\033[2J"
    echo "$i"
    sleep 0.01
  done'
2020-10-23 21:37:20 +09:00
Junegunn Choi
e2b87e0d74 Fix Travis CI build 2020-10-23 20:44:04 +09:00
Junegunn Choi
2166b4ca17 Fix test cases
We were not properly waiting for truthy-ness in until blocks.

Need Minitest with 21d9e804b6
2020-10-23 19:54:45 +09:00
Junegunn Choi
d2d4d68585 Always show the number of selected entries to indicate if --multi is enabled
Close #2217

  seq 100 | fzf
    # 100/100
  seq 100 | fzf --multi
    # 100/100 (0)
  seq 100 | fzf --multi 5
    # 100/100 (0/5)
2020-10-20 20:04:06 +09:00
Junegunn Choi
faf68dbc5c Implement streaming preview window (#2215)
Fix #2212

    # Will start rendering after 200ms, update every 100ms
    fzf --preview 'for i in $(seq 100); do echo $i; sleep 0.01; done'

    # Should print "Loading .." message after 500ms
    fzf --preview 'sleep 1; for i in $(seq 100); do echo $i; sleep 0.01; done'

    # The first line should appear after 200ms
    fzf --preview 'date; sleep 2; date'

    # Should not render before enough lines for the scroll offset are ready
    rg --line-number --no-heading --color=always ^ |
      fzf --delimiter : --ansi --preview-window '+{2}-/2' \
          --preview 'sleep 1; bat --style=numbers --color=always --pager=never --highlight-line={2} {1}'
2020-10-18 17:03:33 +09:00
Junegunn Choi
305896fcb3 README-VIM: g:fzf_action doesn't work with custom sink
Fix https://github.com/junegunn/fzf.vim/issues/1131
2020-10-18 13:23:40 +09:00
Andrew Zhou
6c9adea0d3 [fish] Fix parser handling of option-like args (#2208)
Fixes error when option-like args are parsed (e.g. "-1").
2020-10-12 12:58:37 +09:00
Junegunn Choi
fc7630a66d 0.23.1 2020-10-11 02:04:07 +09:00
Junegunn Choi
3248153d9f Add --preview-window=default for resetting the options 2020-10-11 01:54:39 +09:00
Junegunn Choi
246b9f3130 Simplify fzf-tmux script
# Should properly escape arguments
    FZF_DEFAULT_OPTS='--prompt "\$a`b\"c"' fzf-tmux --header $'$a\nb"c`d'
2020-10-09 23:02:03 +09:00
Junegunn Choi
865144850d Add nowrap, nocycle, nohidden for --preview-window
Close #2203
2020-10-09 21:56:16 +09:00
Junegunn Choi
d9752a4c21 Reset preview window flags that are not style-related
Fix #2203
2020-10-09 19:53:51 +09:00
Junegunn Choi
dba14d2630 0.23.0 2020-10-07 19:18:50 +09:00
Elvan Owen
2986e64a49 [completion] Make host completion handle source files without EOL 2020-10-06 20:54:42 +09:00
Junegunn Choi
1d8bd11b67 Fix preview window size calculation 2020-10-06 19:37:33 +09:00
Junegunn Choi
bafb99d520 Allow splitting preview-window options
e.g. --preview-window sharp --preview-window cycle
2020-10-06 18:44:13 +09:00
Junegunn Choi
3cc8a74a91 Add --preview-window option for cyclic scrolling
Close #2182
2020-10-06 10:05:57 +09:00
Tinmarino
c0aa5a438f Add preview-half-page-down and preview-half-page-up (#2145) 2020-10-05 21:58:56 +09:00
Junegunn Choi
825d401403 Show how to use reload action 2020-10-05 19:17:31 +09:00
Junegunn Choi
9dfca77c36 [zsh] Keep current $BUFFER on ALT-C
Ideally, we could only use `print -sr` to update the command history.
However, the "cd" command by ALT-C is added to the history only after we
finalize the current command by pressing an additional enter key.

i.e. The cd command from ALT-C is not visible when you hit Up arrow. But
it appears once you hit enter key.

So when the current buffer is empty, we use `zle accept-line` so that
the command history is immediately updated.

Close #2200
2020-10-03 14:55:02 +09:00
octaltree
82c4af2902 [zsh] Record cd execution in history (#2193) 2020-10-02 22:14:09 +09:00
Junegunn Choi
736344e151 Remove deprecated item from man page 2020-09-29 11:34:57 +09:00
Junegunn Choi
6f9663da62 Always allow preview/execute commands with no placeholder expressions
Fix #2017
2020-09-29 11:32:56 +09:00
Wenxuan
f8ae1786dd Fix items width limit (#2190) 2020-09-24 11:06:20 +09:00
Junegunn Choi
c60ed17583 [vim] Change the default layout to use popup window
The new default is

  { 'window' : { 'width': 0.9, 'height': 0.6, 'highlight': 'Normal' } }

The default highlight group for the border of the popup window is
'Comment', but 'Normal' seems to be a safer choice.

If you prefer the previous default, add this to your Vim configuration file:

  let g:fzf_layout = { 'down': '40%' }

(fzf will fall back to this if popup window is not supported)
2020-09-12 21:14:32 +09:00
Junegunn Choi
e0f0b5bcf9 Update CHANGELOG 2020-09-09 00:06:53 +09:00
Junegunn Choi
9e96073128 [vim] Expose fzf#exec() function 2020-09-09 00:02:37 +09:00
Junegunn Choi
0db65c22d3 [vim] Allow specifying popup width and height in absolute integer value
Fix https://github.com/junegunn/fzf.vim/issues/1116
2020-09-06 22:15:44 +09:00
Yuji Nakao
d785135606 [zsh] Fix the regular expression (#2140)
Fix the regular expression to capture the command containing asterisk.
2020-09-02 17:27:56 +09:00
Michael Kelley
ae15eda546 Add truecolor support for Windows, if available (#2156)
- Update to latest tcell which has 24 bit Windows support
- light renderer under Windows defaults to Dark256, if possible
- Respect TCELL_TRUECOLOR
- Remove tcell 1.3 references
2020-09-02 13:47:13 +09:00
Junegunn Choi
f2d44ab5a7 Revert horizontal padding around preview window on "noborder"
Use 2-space horizontal padding so that the preview content is aligned
with the candidate list when the position of the preview window is `up`
or `down`.
2020-08-23 17:17:57 +09:00
Junegunn Choi
43798fc2e8 Revert 1ab4289: Preview window of size 0 is allowed 2020-08-23 17:12:37 +09:00
Junegunn Choi
9dc4b40d7a Add more preview window options and reduce vertical padding on noborder
Fix #2138
Fix #2029
2020-08-23 17:05:45 +09:00
Junegunn Choi
1cb19dbf65 Support preview scroll offset relative to window height
Related: https://github.com/junegunn/fzf.vim/issues/1092
2020-08-23 15:57:49 +09:00
Junegunn Choi
1ab4289ad6 Disallow preview-window size of zero 2020-08-21 11:49:01 +09:00
Junegunn Choi
e2ae1b249c 0.22.0 2020-08-02 15:56:02 +09:00
Junegunn Choi
92b7efafca Ignore punctuation characters before and after preview offset column
This is to allow line numbers in a ctags output (e.g. 123;")
2020-08-02 10:03:17 +09:00
Junegunn Choi
f092e4038f Smart match of accented characters
Fix #1618
2020-07-28 13:06:57 +09:00
Junegunn Choi
aa5dae391b Fix handling of unicode characters in query string 2020-07-28 12:58:37 +09:00
Junegunn Choi
08a6fd4ad4 Fix Travis CI build: Use Go 1.14 2020-07-27 09:01:28 +09:00
Junegunn Choi
a61150a96c Allow negative field index in preview-window scroll offset 2020-07-27 00:30:25 +09:00
Junegunn Choi
0f9cb5590e Add preview window option for setting the initial scroll offset
Close #1057
Close #2120

  # Initial scroll offset is set to the line number of each line of
  # git grep output *minus* 5 lines
  git grep --line-number '' |
    fzf --delimiter : --preview 'nl {1}' --preview-window +{2}-5
2020-07-27 00:27:03 +09:00
yuki yano
c0a83b27eb Fix failure of w:fzf_pushd unlet depending on timing (#2119) 2020-07-26 13:52:50 +09:00
Yanlin Sun
f79b1f71b8 [vim] Preserve current directory in case someone changes it (#2096)
Preserve current directory in case current directory is changed by others
after the call of s:open

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2020-07-15 13:20:56 +09:00
Junegunn Choi
8e027c445f Support ANSI colors in --prompt string
Close #2086
2020-07-05 16:16:46 +09:00
Atemu
dda3e3c39a README: Correct Nix distro support (#2051)
Nix can be installed on (almost) any Linux distro and on macOS.

See https://nixos.org/nix/ for more information.
2020-07-05 15:29:43 +09:00
anntnzrb
fd5157998c Void Linux installation instructions (#2100) 2020-07-03 23:14:12 +09:00
Junegunn Choi
e0217e8c79 Ignore cursor position report
Close #2081
2020-07-03 19:45:58 +09:00
Junegunn Choi
3ab1c42266 Use rune characters instaed of numbers in code 2020-07-03 19:41:19 +09:00
Junegunn Choi
d1676776aa Update CHANGELOG 2020-06-30 21:17:19 +09:00
Junegunn Choi
bdde69d011 [vim] Disable height calculation when 'preview' is found in the option string
Fix #2093

And we'll phase out height specification with `~` prefix.
2020-06-29 22:27:36 +09:00
Junegunn Choi
6dec42a33a Update version numbers in man pages 2020-06-29 22:07:48 +09:00
Junegunn Choi
199bc3f0ad Merge branch 'master' into devel 2020-06-21 22:43:03 +09:00
Junegunn Choi
17dd833925 Add preview action for --bind
Fix #2010
Fix #1638
2020-06-21 22:41:33 +09:00
Khon Trieu
4ec144c969 Accented character normalization for Vietnamese characters (#2090)
Fix #2088
2020-06-21 17:19:38 +09:00
Jan Edmund Lazo
3e36f2b0ac [nvim] Fix floating window requirements (#2089)
Vim 8.1.2371
05ad5ff0ab

Nvim 0.4.0
9a1675b065
2020-06-21 10:34:43 +09:00
Junegunn Choi
07a03b3e73 [vim] Make fzf#wrap support v:true and v:false as well
Fix #2087
2020-06-20 22:15:12 +09:00
Junegunn Choi
c33258832e Add refresh-preview action 2020-06-20 22:04:09 +09:00
Junegunn Choi
a7aa08ce07 Add backward-eof event for --bind 2020-06-07 23:07:03 +09:00
Ben
06d63a862e Fully qualify Expand-Archive (#2066)
If a user has the Powershell Community Extensions installed, it comes
with another command Expand-Archive that doesn't have a DestinationPath
argument, causing an error.
2020-06-07 10:57:23 +09:00
Janek
43d1c4c4b5 README: Use --line-range instead of head in bat example (#2064)
* Use --line-range instead of head in bat example

* README: extend preview section
2020-06-04 11:57:01 +09:00
Junegunn Choi
f81feb1e69 Revert file mode of key-bindings.zsh 2020-05-23 20:51:41 +09:00
karasu
01cf01e084 [fzf-tmux] Fix zoomed pane handling in popup mode (#2054)
When called with popup options, do not move to temp window.
2020-05-23 20:47:43 +09:00
Junegunn Choi
97a725fbd0 Do not disable mouse after execute(-silent) when --height option is used
The action takes place in the alternate screen so the offsets should
still be correct.
2020-05-18 02:43:58 +09:00
lacygoill
ace92ba281 [vim] Don't set wfw, wfh, bh options when opening popup (#2042)
* No need to restore &wfw and &wfh when using popup window

Co-authored-by: lacygoill <lacygoill@lacygoill.me>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2020-05-17 23:28:23 +09:00
ichizok
d631c76e8d [vim] Don't start extra process when opening popup (#2000)
Fix #2038
2020-05-15 15:25:33 +09:00
Slaven Rezić
e6d33f77da [zsh] Make CTRL-R work with older Perls (#2003)
s///r is only available since perl 5.14. The Perl oneliner
was changed to work with older Perls, possibly even with 5.000.

Fix #2001
2020-04-24 22:56:55 +09:00
Jack Bates
a6d3e3687b Improve error messages (#1962)
* Add RuboCop Minitest extension
* Improve error messages
* Use chomp option
2020-04-21 10:12:00 +09:00
Raffaele
08c2bcb952 Quote LDFLAGS (#1995)
Make sure that `extldflags` is quoted so that LDFLAGS containing spaces won't break the build command.

Close #1994
2020-04-21 10:07:39 +09:00
Junegunn Choi
98ca4bdede Add conda installation instruction
Close #1949
2020-04-18 13:00:38 +09:00
Janek
3f8e741562 Add more details on apt installation in README.md (#1977) 2020-04-18 12:59:32 +09:00
Junegunn Choi
6e464ebd9b Remove dead code 2020-04-18 02:51:02 +09:00
Junegunn Choi
c329279339 [completion] Make kill completion more consistent with the others
Support both ordinary completion trigger and empty trigger

    kill <tab>
    kill foo**<tab>

Close #1988
Close #385
2020-04-18 02:46:40 +09:00
Jack Bates
cf04753ad7 Make flaky tests reliable (#1978) 2020-04-18 02:34:38 +09:00
Junegunn Choi
69e7eab11f [install] Clarify that .bashrc should be loaded from .bash_profile on macOS
Close #1986
2020-04-17 21:51:16 +09:00
Junegunn Choi
dea206b023 [zsh-completion] Fix error with backslash-prefixed commands
Fix #1973
Fix #1974
Fix #1975
2020-04-13 00:30:43 +09:00
Jack Bates
5deaf58928 Run rubocop --auto-correct --disable-uncorrectable (#1967)
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2020-04-13 00:23:31 +09:00
Junegunn Choi
15e2952a2b [fzf-tmux] Allow positional flags
Since we don't know in advance which flags tmux will support, simply
allow a single uppercase character ([A-Z]) for now.

    fzf-tmux -xR -yS
    fzf-tmux -x R -y S

Fix #1956
2020-04-07 09:55:48 +09:00
Junegunn Choi
a9fba1c849 Fix typo 2020-04-05 18:36:31 +09:00
Junegunn Choi
71e573d082 [vim] Add 'tmux' layout option to use fzf-tmux
e.g.

  if exists('$TMUX')
    let g:fzf_layout = { 'tmux': '-p90%,60%' }
  else
    let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
  endif
2020-04-05 18:16:36 +09:00
Junegunn Choi
334a4fa159 0.21.1 2020-04-03 17:33:29 +09:00
Junegunn Choi
21f94ee800 [fzf-tmux] Split zsh variable expansion for old zsh
The following code works in zsh 5.8 but not in 5.4

  ${(Q)${(Z+n+)FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}}}
2020-04-03 13:23:15 +09:00
Junegunn Choi
540bfd7a72 [fzf-tmux] Fall back to plain fzf when split failed 2020-04-03 13:23:15 +09:00
Junegunn Choi
8fbed2b13a [fzf-tmux] Use $PWD instead of #{pane_current_path}
Related: https://github.com/tmux/tmux/issues/1282
2020-04-03 13:23:15 +09:00
Junegunn Choi
aa17510e0a [fzf-tmux] Set default horizontal margin 2020-04-03 13:23:15 +09:00
Junegunn Choi
bf65e8cd12 [fzf-tmux] Add option to start fzf in tmux popup window
Requires latest tmux built from source (e.g. brew install tmux --HEAD)

Examples:

  # 50%/50% width and height on the center of the screen
  fzf-tmux -p

  # 80%/80%
  fzf-tmux -p80%

  # 80%/40%
  fzf-tmux -p80%,40%

  # Separate -w and -h
  fzf-tmux -w80% -h40%

  # 80%/40% at position (0, 0)
  fzf-tmux -w80% -h40% -x0 -y0

You can configure key bindings and fuzzy completion to open in tmux
popup window like so:

  FZF_TMUX_OPTS='-p 80%'
2020-04-03 13:23:15 +09:00
lacygoill
0f5c6e8f04 [vim] Fix issue with multiple popups (#1927)
Invoking fzf from an existing Vim popup terminal is a special case.
It requires some new code to avoid E994 from being raised and the user
being stuck in a non-closable popup window.

Fix #1916
2020-03-30 02:06:06 +09:00
Roman Perepelitsa
b1b916ce15 [zsh] Ensure that fzf code always parses the same way (#1944)
At the top of each zsh file options are set to their
standard values (those marked with <Z> in `man zshoptions`)
and `aliases` option is disabled.

At the bottom of the file the original options are restored.

Fix #1938
2020-03-30 01:52:48 +09:00
Alexandr
a6a732e1fc Update AtomicBool to use atomic memory operation (#1939) 2020-03-30 01:42:58 +09:00
Junegunn Choi
a5c2f28539 Fix failing test case 2020-03-29 22:06:06 +09:00
Junegunn Choi
18261fe31c [shell] Update CTRL-R to remove duplicate commands
Close #1940
Related: #1363 #749 #270 #49 #88 #492 #600
2020-03-29 21:30:37 +09:00
Chitoku
079046863c [zsh-completion] Fix a bug where _fzf_complete did not iterate through args (#1936) 2020-03-24 08:58:22 +09:00
Junegunn Choi
07b965bba1 Fix ANSI color offsets when --keep-right is used 2020-03-23 19:05:06 +09:00
Junegunn Choi
c39113ee41 [windows] Do not include directories in the list
Fix #1926
2020-03-14 21:43:35 +09:00
Junegunn Choi
14f90502a4 [bash] Restore --nth option in CTRL-R 2020-03-13 09:13:38 +09:00
Junegunn Choi
b0673c3563 0.21.0 2020-03-12 13:15:45 +09:00
Junegunn Choi
373c6d8d55 Add --keep-right option to keep the right end of the line visible
Close #1652
2020-03-11 22:35:24 +09:00
Junegunn Choi
b8fc828955 Fix completion test 2020-03-11 19:50:04 +09:00
Jakub Łuczyński
b43b040512 Fuzzy completions: removed leftover debug echo (#1921) 2020-03-11 19:29:35 +09:00
Junegunn Choi
50b7608f9d Change custom fuzzy completion API
To make it easier to write more complex fzf options. Although this
does not break backward compatibility, users are encouraged to update
their code accordingly.

  # Before
  _fzf_complete "FZF_ARG1 FZF_ARG2..." "$@" < <(
    # Print candidates
  )

  # After
  _fzf_complete FZF_ARG1 FZF_ARG2... -- "$@" < <(
    # Print candidates
  )
2020-03-11 18:32:35 +09:00
Kahlil (Kal) Hodgson
7085e5b629 Add explanation for the g:fzf_colors setting (#1878)
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2020-03-11 09:58:59 +09:00
Michael Kelley
7d5985baf9 Make height option work under Windows (#1341)
Separate Unix & Windows code into platform specific files for light renderer
2020-03-10 00:03:34 +09:00
Junegunn Choi
7c40a424c0 Add retries to CTRL-R tests to avoid intermittent errors on Travis CI
- https://travis-ci.org/junegunn/fzf/jobs/659496745#L676

Related #1900
2020-03-07 19:56:06 +09:00
Junegunn Choi
baf882ace7 [completion] Use file redirection instead of pipe
This change allows the completion system of bash and zsh to return
before the input process completes.

Related #1887
2020-03-07 16:26:53 +09:00
Junegunn Choi
ba82f0bef9 Do not read more than 10K characters from /dev/tty
This might help with #1456 where fzf hangs consuming CPU resources.
2020-03-07 11:20:44 +09:00
Junegunn Choi
d9c6a0305b Draft CHANGELOG for the upcoming release 2020-03-05 23:12:27 +09:00
Junegunn Choi
d9b1211191 Add more --border options; default changed to "rounded"
--border option now takes an optional argument that defines the style

  - rounded (new default)
  - sharp
  - horizontal (previous default)
2020-03-05 20:56:15 +09:00
Junegunn Choi
99f1e02766 Fix flaky test case
Make sure that the shell is ready before hitting CTRL-R

      1) Error:
    TestFish#test_ctrl_r_multiline:
    RuntimeError: timeout
        test/test_go.rb:50:in `wait'
        test/test_go.rb:125:in `until'
        test/test_go.rb:1857:in `test_ctrl_r_multiline'
2020-03-04 08:37:45 +09:00
Junegunn Choi
242c0db26b [vim] Fix height calculation
Fix #1418

e.g.
  call fzf#run({'source': [1, 2, 3], 'down': '~50%', 'options': "--border --header $'1\n2'"})
2020-03-03 23:50:45 +09:00
Junegunn Choi
dd49e41c42 Ignore xterm OSC control sequences
- OSC Ps ; Pt BEL
- OSC Ps ; Pt ST

Fix #1415
2020-03-03 21:19:23 +09:00
Junegunn Choi
6db15e8693 [vim] Throw error when popup support is unavailable
https://github.com/junegunn/fzf.vim/issues/943
https://github.com/junegunn/fzf.vim/issues/959
2020-03-01 20:57:35 +09:00
Junegunn Choi
4c9cab3f8a Fix prefix/suffix/equal matcher to trim whitespaces
- Prefix matcher will trim leading whitespaces only when the pattern
  doesn't start with a whitespace
- Suffix matcher will trim trailing whitespaces only when the pattern
  doesn't end with a whitespace
- Equal matcher will trim leading whitespaces only when the pattern
  doesn't start with a whitespace, and trim trailing whitespaces only
  when the pattern doesn't end with a whitespace

Previously, only suffix matcher would trim whitespaces unconditionally.

Fix #1894
2020-03-01 12:36:02 +09:00
Junegunn Choi
b2c0413a98 [bash] Fix --query argument of CTRL-R
Fix #1898
2020-02-29 12:01:55 +09:00
Jack Bates
e34c7c00b1 Test multi-line C-r (#1892) 2020-02-28 20:06:38 +09:00
Jack Bates
7c447bbdc7 [bash] Start C-r search with current command line (#1886)
Restore the original line when search is aborted. Add --query
"$READLINE_LINE" and fall back to the current behavior pre Bash 4.

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2020-02-28 18:47:13 +09:00
Junegunn Choi
7bf1f2cc84 Clean up test shell initialization
- Fix 'make docker-test'
- Set fish_history to an empty string since 'fish --private' is not
  available prior to fish 3.0
2020-02-28 18:21:37 +09:00
Junegunn Choi
afa2c4e0af [fish] Ignore empty environment variables 2020-02-28 17:51:07 +09:00
Junegunn Choi
2ff7db1b36 Use a more robust way to check if the shell is ready 2020-02-28 14:46:08 +09:00
James Wright
9f0626da64 Add backward-delete-char/eof action (#1891)
'backward-delete-char/eof' will either abort if query is
empty or delete one character backwards.
2020-02-28 02:38:32 +09:00
Chris
d8cb5c1cf5 Update README.md: MacPorts upgrade instruction (#1893) 2020-02-26 17:58:54 +09:00
Junegunn Choi
dca56da0ef Add 'insert' key for --bind
Close #1744
2020-02-24 01:43:19 +09:00
Junegunn Choi
ec75d16ea8 Fix panic on unexpected escape sequences 2020-02-24 01:37:08 +09:00
Jack Bates
5cae8ea733 [bash] Multiline C-r without histexpand (#1837)
Close #1370 

Parses the history list, converts it to a NUL-delimited list of possibly
multiline entries. Adds the fzf --read0 option. Works with and without
histexpand enabled.

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2020-02-24 00:17:38 +09:00
Jack Bates
1ccd8f6a64 [bash] Restore insertion point pre Bash 4 (#1881)
Make C-t more consistent pre and post Bash 4. It already kills the
command line separately before and after the insertion point. Add
set-mark and exchange-point-and-mark to restore the insertion point
after yanking back and apply the same behavior to M-c.

* CTRL-T should put extra space after pasted items

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2020-02-23 23:24:50 +09:00
Jack Bates
9c293bb82b [bash] Put C-t items at point in vi mode (#1876)
Be consistent with emacs mode and put the items at the point vs. the end
of the command line.
2020-02-21 09:51:34 +09:00
Junegunn Choi
9897ee9591 [bash] Strip trailing whitespace on kill completion 2020-02-20 16:36:59 +09:00
Junegunn Choi
5215415315 [completion] Allow users to customize fzf options via _fzf_comprun
Related #1809 #1850
2020-02-20 00:28:16 +09:00
Junegunn Choi
54891d11e0 [bash-completion] Minor optimization 2020-02-19 11:59:18 +09:00
Junegunn Choi
567c8303bf Update ANSI processor to handle "rmso" and "rmul"
Fix #1877
2020-02-18 00:45:24 +09:00
Hiroki Konishi
2a60edcd52 Make pointer and multi-select marker customizable (#1844)
Add --pointer and --marker option which can provide additional context to the user
2020-02-17 10:19:03 +09:00
Hiroki Konishi
d61ac32d7b Fix bug of validation of jump-labels (#1875)
When jump-labels are specified with `--jump-labels=` way, validation was
not carried out.
2020-02-16 15:45:59 +09:00
Junegunn Choi
b57e6cff7e [vim] Pick up fzf-tmux on $PATH when bin/fzf-tmux is not found
Close #1874
2020-02-16 12:32:20 +09:00
Junegunn Choi
5b99f19dac [vim] Remove unnecessary statement 2020-02-14 15:51:22 +09:00
Junegunn Choi
6c03571887 [vim] Add fzf#install() for downloading fzf binary 2020-02-14 14:04:23 +09:00
Junegunn Choi
4fb410a93c [vim] More border styles
e.g.

  let g:fzf_layout = { 'window': { 'width': 0.4, 'height': 1, 'xoffset': 0, 'border': 'right' } }
  let g:fzf_layout = { 'window': { 'width': 0.4, 'height': 1, 'xoffset': 1, 'border': 'left' } }
  let g:fzf_layout = { 'window': { 'width': 1, 'height': 0.5, 'yoffset': 1, 'border': 'top' } }
  let g:fzf_layout = { 'window': { 'width': 1, 'height': 0.5, 'yoffset': 0, 'border': 'bottom' } }
2020-02-14 00:36:20 +09:00
Junegunn Choi
5e1db9fdd3 [vim] Do not pipe FZF_DEFAULT_COMMAND
Revert the change introduced in #552. It seems that the startup time
difference between bash and fish is not much of an issue now.

  > time bash -c 'date'
  Thu Feb 13 21:15:03 KST 2020

  real    0m0.008s
  user    0m0.003s
  sys     0m0.003s

  > time fish -c 'date'
  Thu Feb 13 21:15:05 KST 2020

  real    0m0.014s
  user    0m0.007s
  sys     0m0.006s

When we explicitly *pipe* $FZF_DEFAULT_COMMAND instead of making fzf
internally start the process ($FZF_DEFAULT_COMMAND | fzf), fzf may hang
if the input process doesn't quickly process SIGPIPE and abort.

Also, fzf#vim#grep temporarily swaps $FZF_DEFAULT_COMMAND instead of
setting 'sink' so fzf can kill the default command on 'reload'.

https://github.com/junegunn/fzf.vim/issues/927

However, because of the "pipe conversion", the trick wasn't working as
expected.

467c327788/autoload/fzf/vim.vim (L720-L726)

We can go even further and always set $FZF_DEFAULT_COMMAND instead of
piping source command.
2020-02-13 21:13:30 +09:00
Junegunn Choi
9d7480ae3c [vim] Use install.ps1 to download binary on Windows
Credits to @jiangjianshan
2020-02-12 18:01:41 +09:00
jiangjianshan
f39cf6d855 Add install.ps1 for downloading fzf.exe on Windows (#1845) 2020-02-12 17:56:53 +09:00
Kyoichiro Yamada
001d116884 [vim] Consider ambiwidth for border (#1861)
Close #1856
Close #1857
2020-02-10 17:52:15 +09:00
Junegunn Choi
02c5e62efe Fix documentation 2020-02-10 01:24:00 +09:00
Junegunn Choi
446df07b62 [vim] Border style for popup window (rounded | sharp | horizontal) 2020-02-06 12:27:48 +09:00
Junegunn Choi
8583b150c9 Fix inline info truncation 2020-02-06 12:01:51 +09:00
Junegunn Choi
a859aa72ee [vim] Add support for xoffset and yoffset options for popup
Close https://github.com/junegunn/fzf.vim/issues/942
2020-02-06 10:40:57 +09:00
Junegunn Choi
0896036266 [vim] Set &bufhidden=hide before starting terminal buffer 2020-02-05 10:09:15 +09:00
mattn
311b78ae82 [windows] Use native walker since output of DOS command is not UTF-8 encoded (#1847)
Makes scanning 300x faster on Windows
2020-02-04 12:31:00 +09:00
Sergey Bronnikov
f5cf4fc8fb README: OpenBSD package (#1848) 2020-02-04 01:16:42 +09:00
Junegunn Choi
7ceb58b2aa [vim] Popup window support for both Vim and Neovim
e.g.
  let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }

Based on the code from https://github.com/junegunn/fzf.vim/issues/821#issuecomment-581273191
by @lacygoill.
2020-02-04 00:35:57 +09:00
Junegunn Choi
293dd76af1 Update Dockerfile 2020-02-03 15:00:21 +09:00
Shun Sakai
3918c45ced Update copyright year (#1832)
Update copyright year to 2020 and change to multi-year format.
2020-01-25 01:41:55 +09:00
Junegunn Choi
4ec403347c Update Neovim floating window example to have border 2020-01-22 17:34:38 +09:00
Junegunn Choi
e01266ffcb Period. 2020-01-19 19:46:17 +09:00
Tony Metzidis
f246fb2fc2 Show error message when failed to start preview command (#1810)
Fix #1637
2020-01-19 19:42:10 +09:00
Chitoku
f7b26b34cb [zsh-completion] Fix quoting/splitting issues (#1820) 2020-01-19 14:19:05 +09:00
Aaron Bieber
a1bcdc225e Add pledge(2) support (OpenBSD only) via a 'protector' package. (#1297) 2020-01-19 14:13:32 +09:00
Junegunn Choi
7771241cc0 Fix F1, F2, F3, F4 on rxvt-unicode
Tested on urxvt.
Fix #1799.
2020-01-18 12:30:38 +09:00
Junegunn Choi
6e3af646b2 Draw spinner with Unicode characters 2020-01-15 10:43:09 +09:00
Jack Bates
82bf8c138d [bash] Populate emacs and vi keymaps (#1815)
Enables the right bindings when switching between editing modes.
2020-01-08 18:35:43 +09:00
Jan Edmund Lazo
e21b001116 [vim] Use iconv only if +iconv is enabled (#1813) 2020-01-07 16:11:47 +09:00
Junegunn Choi
577024f1e9 Use rounded corners 2019-12-31 19:27:32 +09:00
Junegunn Choi
d4ad4a25db [bash-completion] Fix default alias/variable completion
Fix #1795
2019-12-20 18:39:22 +09:00
54 changed files with 5027 additions and 2421 deletions

4
.gitignore vendored
View File

@@ -1,5 +1,6 @@
bin/fzf bin/fzf
bin/fzf.exe bin/fzf.exe
dist
target target
pkg pkg
Gemfile.lock Gemfile.lock
@@ -8,3 +9,6 @@ doc/tags
vendor vendor
gopath gopath
*.zwc *.zwc
fzf
tmp
*.patch

11
.gon.hcl Normal file
View File

@@ -0,0 +1,11 @@
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"
}

69
.goreleaser.yml Normal file
View File

@@ -0,0 +1,69 @@
---
project_name: fzf
before:
hooks:
- go mod download
builds:
- id: fzf-macos
binary: fzf
goos:
- darwin
goarch:
- amd64
ldflags:
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
hooks:
post: gon .gon.hcl
- goos:
- linux
- windows
- freebsd
- openbsd
goarch:
- amd64
- arm
- arm64
goarm:
- 5
- 6
- 7
ldflags:
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
ignore:
- goos: freebsd
goarch: arm
- goos: openbsd
goarch: arm
- goos: freebsd
goarch: arm64
- goos: openbsd
goarch: arm64
archives:
- name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
format: tar.gz
format_overrides:
- goos: windows
format: zip
files:
- non-existent*
release:
github:
owner: junegunn
name: fzf
prerelease: auto
name_template: '{{ .Tag }}'
snapshot:
name_template: "{{ .Tag }}-devel"
changelog:
sort: asc
filters:
exclude:
- README
- test

28
.rubocop.yml Normal file
View File

@@ -0,0 +1,28 @@
Layout/LineLength:
Enabled: false
Metrics:
Enabled: false
Lint/ShadowingOuterLocalVariable:
Enabled: false
Style/MethodCallWithArgsParentheses:
Enabled: true
IgnoredMethods:
- assert
- exit
- paste
- puts
- raise
- refute
- require
- send_keys
IgnoredPatterns:
- ^assert_
- ^refute_
Style/NumericPredicate:
Enabled: false
Style/StringConcatenation:
Enabled: false
Style/OptionalBooleanParameter:
Enabled: false
Style/WordArray:
MinSize: 1

View File

@@ -1,27 +1,28 @@
language: go language: go
dist: xenial go:
- "1.14"
env: GO111MODULE=on
os:
- linux
- osx
dist: bionic
addons: addons:
apt: apt:
sources:
- sourceline: "ppa:pi-rho/dev"
- sourceline: "ppa:fish-shell/release-2"
packages: packages:
- tmux - fish
- zsh - zsh
- fish sources:
sourceline: ppa:fish-shell/release-3
env: homebrew:
- GO111MODULE=on packages:
- fish
jobs: - tmux
include: update: true
- stage: unittest install: gem install --no-document minitest:5.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1
go: "1.13.x" script:
script: make && make test - make test
# LC_ALL=C to avoid escape codes in
- stage: cli # printf %q $'\355\205\214\354\212\244\355\212\270' on macOS. Bash on
go: "1.13.x" # macOS is built without HANDLE_MULTIBYTE?
rvm: "2.5" - make install && ./install --all && LC_ALL=C tmux new-session -d && ruby test/test_go.rb --verbose
script: | - rubocop --require rubocop-minitest --require rubocop-performance
make install && ./install --all && tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]

View File

@@ -17,21 +17,19 @@ make
# Build fzf binary and copy it to bin directory # Build fzf binary and copy it to bin directory
make install make install
# Build 32-bit and 64-bit executables and tarballs in target # Build fzf binaries and archives for all platforms using goreleaser
make build
# Publish GitHub release
make release make release
# Make release archives for all supported platforms in target
make release-all
``` ```
### Using `go get` > :warning: Makefile uses git commands to determine the version and the
> revision information for `fzf --version`. So if you're building fzf from an
Alternatively, you can build fzf directly with `go get` command without > environment where its git information is not available, you have to manually
manually cloning the repository. > set `$FZF_VERSION` and `$FZF_REVISION`.
>
```sh > e.g. `FZF_VERSION=0.24.0 FZF_REVISION=tarball make`
go get -u github.com/junegunn/fzf
```
Third-party libraries used Third-party libraries used
-------------------------- --------------------------

View File

@@ -1,6 +1,225 @@
CHANGELOG CHANGELOG
========= =========
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
0.24.0
------
- Real-time rendering of preview window
```sh
# fzf can render preview window before the command completes
fzf --preview 'sleep 1; for i in $(seq 100); do echo $i; sleep 0.01; done'
# Preview window can process ANSI escape sequence (CSI 2 J) for clearing the display
fzf --preview 'for i in $(seq 100000); do
(( i % 200 == 0 )) && printf "\033[2J"
echo "$i"
sleep 0.01
done'
```
- Updated `--color` option to support text styles
- `regular` / `bold` / `dim` / `underline` / `italic` / `reverse` / `blink`
```sh
# * Set -1 to keep the original color
# * Multiple style attributes can be combined
# * Italic style may not be supported by some terminals
rg --line-number --no-heading --color=always "" |
fzf --ansi --prompt "Rg: " \
--color fg+:italic,hl:underline:-1,hl+:italic:underline:reverse:-1 \
--color pointer:reverse,prompt:reverse,input:159 \
--pointer ' '
```
- More `--border` options
- `vertical`, `top`, `bottom`, `left`, `right`
- Updated Vim plugin to use these new `--border` options
```vim
" Floating popup window in the center of the screen
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
" Popup with 100% width
let g:fzf_layout = { 'window': { 'width': 1.0, 'height': 0.5, 'border': 'horizontal' } }
" Popup with 100% height
let g:fzf_layout = { 'window': { 'width': 0.5, 'height': 1.0, 'border': 'vertical' } }
" Similar to 'down' layout, but it uses a popup window and doesn't affect the window layout
let g:fzf_layout = { 'window': { 'width': 1.0, 'height': 0.5, 'yoffset': 1.0, 'border': 'top' } }
" Opens on the right;
" 'highlight' option is still supported but it will only take the foreground color of the group
let g:fzf_layout = { 'window': { 'width': 0.5, 'height': 1.0, 'xoffset': 1.0, 'border': 'left', 'highlight': 'Comment' } }
```
- To indicate if `--multi` mode is enabled, fzf will print the number of
selected items even when no item is selected
```sh
seq 100 | fzf
# 100/100
seq 100 | fzf --multi
# 100/100 (0)
seq 100 | fzf --multi 5
# 100/100 (0/5)
```
- Since 0.24.0, release binaries will be uploaded to https://github.com/junegunn/fzf/releases
0.23.1
------
- Added `--preview-window` options for disabling flags
- `nocycle`
- `nohidden`
- `nowrap`
- `default`
- Built with Go 1.14.9 due to performance regression
- https://github.com/golang/go/issues/40727
0.23.0
------
- Support preview scroll offset relative to window height
```sh
git grep --line-number '' |
fzf --delimiter : \
--preview 'bat --style=numbers --color=always --highlight-line {2} {1}' \
--preview-window +{2}-/2
```
- Added `--preview-window` option for sharp edges (`--preview-window sharp`)
- Added `--preview-window` option for cyclic scrolling (`--preview-window cycle`)
- Reduced vertical padding around the preview window when `--preview-window
noborder` is used
- Added actions for preview window
- `preview-half-page-up`
- `preview-half-page-down`
- Vim
- Popup width and height can be given in absolute integer values
- Added `fzf#exec()` function for getting the path of fzf executable
- It also downloads the latest binary if it's not available by running
`./install --bin`
- Built with Go 1.15.2
- We no longer provide 32-bit binaries
0.22.0
------
- Added more options for `--bind`
- `backward-eof` event
```sh
# Aborts when you delete backward when the query prompt is already empty
fzf --bind backward-eof:abort
```
- `refresh-preview` action
```sh
# Rerun preview command when you hit '?'
fzf --preview 'echo $RANDOM' --bind '?:refresh-preview'
```
- `preview` action
```sh
# Default preview command with an extra preview binding
fzf --preview 'file {}' --bind '?:preview:cat {}'
# A preview binding with no default preview command
# (Preview window is initially empty)
fzf --bind '?:preview:cat {}'
# Preview window hidden by default, it appears when you first hit '?'
fzf --bind '?:preview:cat {}' --preview-window hidden
```
- Added preview window option for setting the initial scroll offset
```sh
# Initial scroll offset is set to the line number of each line of
# git grep output *minus* 5 lines
git grep --line-number '' |
fzf --delimiter : --preview 'nl {1}' --preview-window +{2}-5
```
- Added support for ANSI colors in `--prompt` string
- Smart match of accented characters
- An unaccented character in the query string will match both accented and
unaccented characters, while an accented character will only match
accented characters. This is similar to how "smart-case" match works.
- Vim plugin
- `tmux` layout option for using fzf-tmux
```vim
let g:fzf_layout = { 'tmux': '-p90%,60%' }
```
0.21.1
------
- Shell extension
- CTRL-R will remove duplicate commands
- fzf-tmux
- Supports tmux popup window (require tmux 3.2 or above)
- ```sh
# 50% width and height
fzf-tmux -p
# 80% width and height
fzf-tmux -p 80%
# 80% width and 40% height
fzf-tmux -p 80%,40%
fzf-tmux -w 80% -h 40%
# Window position
fzf-tmux -w 80% -h 40% -x 0 -y 0
fzf-tmux -w 80% -h 40% -y 1000
# Write ordinary fzf options after --
fzf-tmux -p -- --reverse --info=inline --margin 2,4 --border
```
- On macOS, you can build the latest tmux from the source with
`brew install tmux --HEAD`
- Bug fixes
- Fixed Windows file traversal not to include directories
- Fixed ANSI colors with `--keep-right`
- Fixed _fzf_complete for zsh
- Built with Go 1.14.1
0.21.0
------
- `--height` option is now available on Windows as well (@kelleyma49)
- Added `--pointer` and `--marker` options
- Added `--keep-right` option that keeps the right end of the line visible
when it's too long
- Style changes
- `--border` will now print border with rounded corners around the
finder instead of printing horizontal lines above and below it.
The previous style is available via `--border=horizontal`
- Unicode spinner
- More keys and actions for `--bind`
- Added PowerShell script for downloading Windows binary
- Vim plugin: Built-in floating windows support
```vim
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
```
- bash: Various improvements in key bindings (CTRL-T, CTRL-R, ALT-C)
- CTRL-R will start with the current command-line as the initial query
- CTRL-R properly supports multi-line commands
- Fuzzy completion API changed
```sh
# Previous: fzf arguments given as a single string argument
# - This style is still supported, but it's deprecated
_fzf_complete "--multi --reverse --prompt=\"doge> \"" "$@" < <(
echo foo
)
# New API: multiple fzf arguments before "--"
# - Easier to write multiple options
_fzf_complete --multi --reverse --prompt="doge> " -- "$@" < <(
echo foo
)
```
- Bug fixes and improvements
0.20.0 0.20.0
------ ------
- Customizable preview window color (`preview-fg` and `preview-bg` for `--color`) - Customizable preview window color (`preview-fg` and `preview-bg` for `--color`)

View File

@@ -1,6 +1,6 @@
FROM archlinux/base:latest FROM archlinux/base:latest
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make gcc
RUN gem install --no-document minitest RUN gem install --no-document -v 5.14.2 minitest
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
RUN echo '. ~/.bashrc' >> ~/.bash_profile RUN echo '. ~/.bashrc' >> ~/.bash_profile

View File

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

114
Makefile
View File

@@ -5,24 +5,33 @@ MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
ROOT_DIR := $(shell dirname $(MAKEFILE)) ROOT_DIR := $(shell dirname $(MAKEFILE))
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(MAKEFILE) SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(MAKEFILE)
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES)) ifdef FZF_VERSION
BUILD_FLAGS := -a -ldflags "-X main.revision=$(REVISION) -w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" VERSION := $(FZF_VERSION)
else
VERSION := $(shell git describe --abbrev=0 2> /dev/null)
endif
ifeq ($(VERSION),)
$(error Not on git repository; cannot determine $$FZF_VERSION)
endif
VERSION_TRIM := $(shell sed "s/-.*//" <<< $(VERSION))
VERSION_REGEX := $(subst .,\.,$(VERSION_TRIM))
ifdef FZF_REVISION
REVISION := $(FZF_REVISION)
else
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES) 2> /dev/null)
endif
ifeq ($(REVISION),)
$(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 BINARY64 := fzf-$(GOOS)_amd64
BINARYARM5 := fzf-$(GOOS)_arm5 BINARYARM5 := fzf-$(GOOS)_arm5
BINARYARM6 := fzf-$(GOOS)_arm6 BINARYARM6 := fzf-$(GOOS)_arm6
BINARYARM7 := fzf-$(GOOS)_arm7 BINARYARM7 := fzf-$(GOOS)_arm7
BINARYARM8 := fzf-$(GOOS)_arm8 BINARYARM8 := fzf-$(GOOS)_arm8
BINARYPPC64LE := fzf-$(GOOS)_ppc64le BINARYPPC64LE := fzf-$(GOOS)_ppc64le
VERSION := $(shell awk -F= '/version =/ {print $$2}' src/constants.go | tr -d "\" ")
RELEASE32 := fzf-$(VERSION)-$(GOOS)_386
RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
RELEASEARM5 := fzf-$(VERSION)-$(GOOS)_arm5
RELEASEARM6 := fzf-$(VERSION)-$(GOOS)_arm6
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
RELEASEARM8 := fzf-$(VERSION)-$(GOOS)_arm8
RELEASEPPC64LE := fzf-$(VERSION)-$(GOOS)_ppc64le
# https://en.wikipedia.org/wiki/Uname # https://en.wikipedia.org/wiki/Uname
UNAME_M := $(shell uname -m) UNAME_M := $(shell uname -m)
@@ -30,10 +39,6 @@ ifeq ($(UNAME_M),x86_64)
BINARY := $(BINARY64) BINARY := $(BINARY64)
else ifeq ($(UNAME_M),amd64) else ifeq ($(UNAME_M),amd64)
BINARY := $(BINARY64) BINARY := $(BINARY64)
else ifeq ($(UNAME_M),i686)
BINARY := $(BINARY32)
else ifeq ($(UNAME_M),i386)
BINARY := $(BINARY32)
else ifeq ($(UNAME_M),armv5l) else ifeq ($(UNAME_M),armv5l)
BINARY := $(BINARYARM5) BINARY := $(BINARYARM5)
else ifeq ($(UNAME_M),armv6l) else ifeq ($(UNAME_M),armv6l)
@@ -47,43 +52,11 @@ else ifeq ($(UNAME_M),aarch64)
else ifeq ($(UNAME_M),ppc64le) else ifeq ($(UNAME_M),ppc64le)
BINARY := $(BINARYPPC64LE) BINARY := $(BINARYPPC64LE)
else else
$(error "Build on $(UNAME_M) is not supported, yet.") $(error Build on $(UNAME_M) is not supported, yet.)
endif endif
all: target/$(BINARY) all: target/$(BINARY)
target:
mkdir -p $@
ifeq ($(GOOS),windows)
release: target/$(BINARY32) target/$(BINARY64)
cd target && cp -f $(BINARY32) fzf.exe && zip $(RELEASE32).zip fzf.exe
cd target && cp -f $(BINARY64) fzf.exe && zip $(RELEASE64).zip fzf.exe
cd target && rm -f fzf.exe
else ifeq ($(GOOS),linux)
release: target/$(BINARY32) target/$(BINARY64) target/$(BINARYARM5) target/$(BINARYARM6) target/$(BINARYARM7) target/$(BINARYARM8) target/$(BINARYPPC64LE)
cd target && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf
cd target && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
cd target && cp -f $(BINARYARM5) fzf && tar -czf $(RELEASEARM5).tgz fzf
cd target && cp -f $(BINARYARM6) fzf && tar -czf $(RELEASEARM6).tgz fzf
cd target && cp -f $(BINARYARM7) fzf && tar -czf $(RELEASEARM7).tgz fzf
cd target && cp -f $(BINARYARM8) fzf && tar -czf $(RELEASEARM8).tgz fzf
cd target && cp -f $(BINARYPPC64LE) fzf && tar -czf $(RELEASEPPC64LE).tgz fzf
cd target && rm -f fzf
else
release: target/$(BINARY32) target/$(BINARY64)
cd target && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf
cd target && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
cd target && rm -f fzf
endif
release-all: clean test
GOOS=darwin make release
GOOS=linux make release
GOOS=freebsd make release
GOOS=openbsd make release
GOOS=windows make release
test: $(SOURCES) test: $(SOURCES)
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \ SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
github.com/junegunn/fzf/src \ github.com/junegunn/fzf/src \
@@ -93,11 +66,46 @@ test: $(SOURCES)
install: bin/fzf install: bin/fzf
clean: build:
$(RM) -r target goreleaser --rm-dist --snapshot
target/$(BINARY32): $(SOURCES) release:
GOARCH=386 $(GO) build $(BUILD_FLAGS) -o $@ ifndef GITHUB_TOKEN
$(error GITHUB_TOKEN is not defined)
endif
# Check if we are on master branch
ifneq ($(shell git symbolic-ref --short HEAD),master)
$(error Not on master branch)
endif
# Check if version numbers are properly updated
grep -q ^$(VERSION_REGEX)$$ CHANGELOG.md
grep -qF '"fzf $(VERSION_TRIM)"' man/man1/fzf.1
grep -qF '"fzf $(VERSION_TRIM)"' man/man1/fzf-tmux.1
grep -qF $(VERSION) install
grep -qF $(VERSION) install.ps1
# Make release note out of CHANGELOG.md
sed -n '/^$(VERSION_REGEX)$$/,/^[0-9]/p' CHANGELOG.md | tail -r | \
sed '1,/^ *$$/d' | tail -r | sed 1,2d | tee tmp/release-note
# Push to temp branch first so that install scripts always works on master branch
git checkout -B temp master
git push origin temp --follow-tags --force
# Make a GitHub release
goreleaser --rm-dist --release-notes tmp/release-note
# Push to master
git checkout master
git push origin master
# Delete temp branch
git push origin --delete temp
clean:
$(RM) -r dist target
target/$(BINARY64): $(SOURCES) target/$(BINARY64): $(SOURCES)
GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@ GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@
@@ -133,4 +141,4 @@ update:
$(GO) get -u $(GO) get -u
$(GO) mod tidy $(GO) mod tidy
.PHONY: all release release-all test install clean docker docker-test update .PHONY: all build release test install clean docker docker-test update

View File

@@ -1,6 +1,50 @@
FZF Vim integration FZF Vim integration
=================== ===================
Installation
------------
Once you have fzf installed, you can enable it inside Vim simply by adding the
directory to `&runtimepath` in your Vim configuration file. The path may
differ depending on the package manager.
```vim
" If installed using Homebrew
set rtp+=/usr/local/opt/fzf
" If installed using git
set rtp+=~/.fzf
```
If you use [vim-plug](https://github.com/junegunn/vim-plug), the same can be
written as:
```vim
" If installed using Homebrew
Plug '/usr/local/opt/fzf'
" If installed using git
Plug '~/.fzf'
```
But if you want the latest Vim plugin file from GitHub rather than the one
included in the package, write:
```vim
Plug 'junegunn/fzf'
```
The Vim plugin will pick up fzf binary available on the system. If fzf is not
found on `$PATH`, it will ask you if it should download the latest binary for
you.
To make sure that you have the latest version of the binary, set up
post-update hook like so:
```vim
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
```
Summary Summary
------- -------
@@ -83,10 +127,13 @@ let g:fzf_action = {
\ 'ctrl-v': 'vsplit' } \ 'ctrl-v': 'vsplit' }
" Default fzf layout " Default fzf layout
" - down / up / left / right " - Popup window
let g:fzf_layout = { 'down': '~40%' } let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
" You can set up fzf window using a Vim command (Neovim or latest Vim 8 required) " - down / up / left / right
let g:fzf_layout = { 'down': '40%' }
" - Window using a Vim command
let g:fzf_layout = { 'window': 'enew' } let g:fzf_layout = { 'window': 'enew' }
let g:fzf_layout = { 'window': '-tabnew' } let g:fzf_layout = { 'window': '-tabnew' }
let g:fzf_layout = { 'window': '10new' } let g:fzf_layout = { 'window': '10new' }
@@ -115,6 +162,53 @@ let g:fzf_colors =
let g:fzf_history_dir = '~/.local/share/fzf-history' let g:fzf_history_dir = '~/.local/share/fzf-history'
``` ```
##### Explanation of `g:fzf_colors`
`g:fzf_colors` is a dictionary mapping fzf elements to a color specification
list:
element: [ component, group1 [, group2, ...] ]
- `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 (`> `) |
- `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
order) for a matching color definition
For example, consider the following specification:
```vim
'prompt': ['fg', 'Conditional', 'Comment'],
```
This means we color the **prompt**
- using the `fg` attribute of the `Conditional` if it exists,
- otherwise use the `fg` attribute of the `Comment` highlight group if it exists,
- otherwise fall back to the default color settings for the **prompt**.
You can examine the color option generated according the setting by printing
the result of `fzf#wrap()` function like so:
```vim
:echo fzf#wrap()
```
`fzf#run` `fzf#run`
--------- ---------
@@ -183,7 +277,9 @@ The following table summarizes the available options.
| `options` | string/list | Options to fzf | | `options` | string/list | Options to fzf |
| `dir` | string | Working directory | | `dir` | string | Working directory |
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) | | `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
| `tmux` | string | (Layout) fzf-tmux options (e.g. `-p90%,60%`) |
| `window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new`) | | `window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new`) |
| `window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}`) |
`options` entry can be either a string or a list. For simple cases, string `options` entry can be either a string or a list. For simple cases, string
should suffice, but prefer to use list type to avoid escaping issues. should suffice, but prefer to use list type to avoid escaping issues.
@@ -193,6 +289,18 @@ call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'})
call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']}) call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
``` ```
When `window` entry is a dictionary, fzf will start in a popup window. The
following options are allowed:
- Required:
- `width` [float range [0 ~ 1]] or [integer range [8 ~ ]]
- `height` [float range [0 ~ 1]] or [integer range [4 ~ ]]
- Optional:
- `yoffset` [float default 0.5 range [0 ~ 1]]
- `xoffset` [float default 0.5 range [0 ~ 1]]
- `border` [string default `rounded`]: Border style
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`
`fzf#wrap` `fzf#wrap`
---------- ----------
@@ -225,8 +333,9 @@ After we *"wrap"* our spec, we pass it to `fzf#run`.
call fzf#run(fzf#wrap({'source': 'ls'})) call fzf#run(fzf#wrap({'source': 'ls'}))
``` ```
Now it supports `CTRL-T`, `CTRL-V`, and `CTRL-X` key bindings and it opens fzf Now it supports `CTRL-T`, `CTRL-V`, and `CTRL-X` key bindings (configurable
window according to `g:fzf_layout` setting. via `g:fzf_action`) and it opens fzf window according to `g:fzf_layout`
setting.
To make it easier to use, let's define `LS` command. To make it easier to use, let's define `LS` command.
@@ -263,6 +372,17 @@ command! -bang -complete=dir -nargs=* LS
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0)) \ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
``` ```
### Global options supported by `fzf#wrap`
- `g:fzf_layout`
- `g:fzf_action`
- **Works only when no custom `sink` (or `sink*`) is provided**
- Having custom sink usually means that each entry is not an ordinary
file path (e.g. name of color scheme), so we can't blindly apply the
same strategy (i.e. `tabedit some-color-scheme` doesn't make sense)
- `g:fzf_colors`
- `g:fzf_history_dir`
Tips Tips
---- ----
@@ -276,29 +396,32 @@ The latest versions of Vim and Neovim include builtin terminal emulator
- On Terminal Vim with a non-default layout - On Terminal Vim with a non-default layout
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}` - `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
#### Starting fzf in Neovim floating window #### Starting fzf in a popup window
```vim ```vim
" Using floating windows of Neovim to start fzf " Required:
if has('nvim') " - width [float range [0 ~ 1]] or [integer range [8 ~ ]]
let $FZF_DEFAULT_OPTS .= ' --border --margin=0,2' " - height [float range [0 ~ 1]] or [integer range [4 ~ ]]
"
" 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 } }
```
function! FloatingFZF() Alternatively, you can make fzf open in a tmux popup window (requires tmux 3.2
let width = float2nr(&columns * 0.9) or above) by putting fzf-tmux options in `tmux` key.
let height = float2nr(&lines * 0.6)
let opts = { 'relative': 'editor',
\ 'row': (&lines - height) / 2,
\ 'col': (&columns - width) / 2,
\ 'width': width,
\ 'height': height }
let win = nvim_open_win(nvim_create_buf(v:false, v:true), v:true, opts) ```vim
call setwinvar(win, '&winhighlight', 'NormalFloat:Normal') " See `man fzf-tmux` for available options
endfunction if exists('$TMUX')
let g:fzf_layout = { 'tmux': '-p90%,60%' }
let g:fzf_layout = { 'window': 'call FloatingFZF()' } else
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
endif endif
``` ```
#### Hide statusline #### Hide statusline
@@ -307,8 +430,8 @@ When fzf starts in a terminal buffer, the file type of the buffer is set to
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of `fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
the window. the window.
For example, if you use the default layout (`{'down': '~40%'}`) on Neovim, you For example, if you use a non-popup layout (e.g. `{'down': '40%'}`) on Neovim,
might want to temporarily disable the statusline for a cleaner look. you might want to temporarily disable the statusline for a cleaner look.
```vim ```vim
if has('nvim') && !exists('g:fzf_layout') if has('nvim') && !exists('g:fzf_layout')
@@ -323,4 +446,4 @@ endif
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2019 Junegunn Choi Copyright (c) 2013-2020 Junegunn Choi

301
README.md
View File

@@ -22,41 +22,50 @@ Pros
Table of Contents Table of Contents
----------------- -----------------
* [Installation](#installation) <!-- vim-markdown-toc GFM -->
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
* [Using git](#using-git) * [Installation](#installation)
* [Using Linux package managers](#using-linux-package-managers) * [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
* [Windows](#windows) * [Using git](#using-git)
* [As Vim plugin](#as-vim-plugin) * [Using Linux package managers](#using-linux-package-managers)
* [Upgrading fzf](#upgrading-fzf) * [Windows](#windows)
* [Building fzf](#building-fzf) * [As Vim plugin](#as-vim-plugin)
* [Usage](#usage) * [Upgrading fzf](#upgrading-fzf)
* [Using the finder](#using-the-finder) * [Building fzf](#building-fzf)
* [Layout](#layout) * [Usage](#usage)
* [Search syntax](#search-syntax) * [Using the finder](#using-the-finder)
* [Environment variables](#environment-variables) * [Layout](#layout)
* [Options](#options) * [Search syntax](#search-syntax)
* [Demo](#demo) * [Environment variables](#environment-variables)
* [Examples](#examples) * [Options](#options)
* [fzf-tmux script](#fzf-tmux-script) * [Demo](#demo)
* [Key bindings for command line](#key-bindings-for-command-line) * [Examples](#examples)
* [Fuzzy completion for bash and zsh](#fuzzy-completion-for-bash-and-zsh) * [`fzf-tmux` script](#fzf-tmux-script)
* [Files and directories](#files-and-directories) * [Key bindings for command-line](#key-bindings-for-command-line)
* [Process IDs](#process-ids) * [Fuzzy completion for bash and zsh](#fuzzy-completion-for-bash-and-zsh)
* [Host names](#host-names) * [Files and directories](#files-and-directories)
* [Environment variables / Aliases](#environment-variables--aliases) * [Process IDs](#process-ids)
* [Settings](#settings) * [Host names](#host-names)
* [Supported commands](#supported-commands) * [Environment variables / Aliases](#environment-variables--aliases)
* [Vim plugin](#vim-plugin) * [Settings](#settings)
* [Advanced topics](#advanced-topics) * [Supported commands](#supported-commands)
* [Performance](#performance) * [Custom fuzzy completion](#custom-fuzzy-completion)
* [Executing external programs](#executing-external-programs) * [Vim plugin](#vim-plugin)
* [Preview window](#preview-window) * [Advanced topics](#advanced-topics)
* [Tips](#tips) * [Performance](#performance)
* [Respecting .gitignore](#respecting-gitignore) * [Executing external programs](#executing-external-programs)
* [Fish shell](#fish-shell) * [Reloading the candidate list](#reloading-the-candidate-list)
* [Related projects](#related-projects) * [1. Update the list of processes by pressing CTRL-R](#1-update-the-list-of-processes-by-pressing-ctrl-r)
* [<a href="LICENSE">License</a>](#license) * [2. Switch between sources by pressing CTRL-D or CTRL-F](#2-switch-between-sources-by-pressing-ctrl-d-or-ctrl-f)
* [3. Interactive ripgrep integration](#3-interactive-ripgrep-integration)
* [Preview window](#preview-window)
* [Tips](#tips)
* [Respecting `.gitignore`](#respecting-gitignore)
* [Fish shell](#fish-shell)
* [Related projects](#related-projects)
* [License](#license)
<!-- vim-markdown-toc -->
Installation Installation
------------ ------------
@@ -73,7 +82,7 @@ fzf project consists of the following components:
You can [download fzf executable][bin] alone if you don't need the extra You can [download fzf executable][bin] alone if you don't need the extra
stuff. stuff.
[bin]: https://github.com/junegunn/fzf-bin/releases [bin]: https://github.com/junegunn/fzf/releases
### Using Homebrew or Linuxbrew ### Using Homebrew or Linuxbrew
@@ -103,19 +112,23 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
### Using Linux package managers ### Using Linux package managers
| Distro | Command | | Package Manager | Linux Distribution | Command |
| --- | --- | | --- | --- | --- |
| Alpine Linux | `sudo apk add fzf` | | APK | Alpine Linux | `sudo apk add fzf` |
| Arch Linux | `sudo pacman -S fzf` | | APT | Debian 9+/Ubuntu 19.10+ | `sudo apt-get install fzf` |
| Debian | `sudo apt-get install fzf` | | Conda | | `conda install -c conda-forge fzf` |
| Fedora | `sudo dnf install fzf` | | DNF | Fedora | `sudo dnf install fzf` |
| FreeBSD | `pkg install fzf` | | Nix | NixOS, etc. | `nix-env -iA nixpkgs.fzf` |
| NixOS | `nix-env -iA nixpkgs.fzf` | | Pacman | Arch Linux | `sudo pacman -S fzf` |
| openSUSE | `sudo zypper install fzf` | | pkg | FreeBSD | `pkg install fzf` |
| pkg_add | OpenBSD | `pkg_add fzf` |
| XBPS | Void Linux | `sudo xbps-install -S fzf` |
| Zypper | openSUSE | `sudo zypper install fzf` |
Shell extensions (key bindings and fuzzy auto-completion) and Vim/Neovim > :warning: **Key bindings (CTRL-T / CTRL-R / ALT-C) and fuzzy auto-completion
plugin may or may not be enabled by default depending on the package manager. > may not be enabled by default.**
Refer to the package documentation for more information. >
> Refer to the package documentation for more information. (e.g. `apt-cache show fzf`)
### Windows ### Windows
@@ -137,39 +150,18 @@ page][windows-wiki].
### As Vim plugin ### As Vim plugin
Once you have fzf installed, you can enable it inside Vim simply by adding the If you use
directory to `&runtimepath` in your Vim configuration file. The path may [vim-plug](https://github.com/junegunn/vim-plug), add this line to your Vim
differ depending on the package manager. configuration file:
```vim ```vim
" If installed using Homebrew Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
set rtp+=/usr/local/opt/fzf
" If installed using git
set rtp+=~/.fzf
``` ```
If you use [vim-plug](https://github.com/junegunn/vim-plug), the same can be `fzf#install()` makes sure that you have the latest binary, but it's optional,
written as: so you can omit it if you use a plugin manager that doesn't support hooks.
```vim For more installation options, see [README-VIM.md](README-VIM.md).
" If installed using Homebrew
Plug '/usr/local/opt/fzf'
" If installed using git
Plug '~/.fzf'
```
But instead of separately installing fzf on your system (using Homebrew or
"git clone") and enabling it on Vim (adding it to `&runtimepath`), you can use
vim-plug to do both.
```vim
" PlugInstall and PlugUpdate will clone fzf in ~/.fzf and run the install script
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
" Both options are optional. You don't have to install fzf in ~/.fzf
" and you don't have to run the install script if you use fzf only in Vim.
```
Upgrading fzf Upgrading fzf
------------- -------------
@@ -180,6 +172,7 @@ method used.
- git: `cd ~/.fzf && git pull && ./install` - git: `cd ~/.fzf && git pull && ./install`
- brew: `brew update; brew reinstall fzf` - brew: `brew update; brew reinstall fzf`
- macports: `sudo port upgrade fzf`
- chocolatey: `choco upgrade fzf` - chocolatey: `choco upgrade fzf`
- vim-plug: `:PlugUpdate fzf` - vim-plug: `:PlugUpdate fzf`
@@ -299,8 +292,10 @@ own as well.
[fzf-tmux](bin/fzf-tmux) is a bash script that opens fzf in a tmux pane. [fzf-tmux](bin/fzf-tmux) is a bash script that opens fzf in a tmux pane.
```sh ```sh
# usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS] # usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
# (-[udlr]: up/down/left/right)
# See available options
fzf-tmux --help
# select git branches in horizontal split below (15 lines) # select git branches in horizontal split below (15 lines)
git branch | fzf-tmux -d 15 git branch | fzf-tmux -d 15
@@ -309,7 +304,7 @@ git branch | fzf-tmux -d 15
cat /usr/share/dict/words | fzf-tmux -l 20% --multi --reverse cat /usr/share/dict/words | fzf-tmux -l 20% --multi --reverse
``` ```
It will still work even when you're not on tmux, silently ignoring `-[udlr]` It will still work even when you're not on tmux, silently ignoring `-[pudlr]`
options, so you can invariably use `fzf-tmux` in your scripts. options, so you can invariably use `fzf-tmux` in your scripts.
Alternatively, you can use `--height HEIGHT[%]` option not to start fzf in Alternatively, you can use `--height HEIGHT[%]` option not to start fzf in
@@ -336,13 +331,9 @@ fish.
- Set `FZF_ALT_C_COMMAND` to override the default command - Set `FZF_ALT_C_COMMAND` to override the default command
- Set `FZF_ALT_C_OPTS` to pass additional options - Set `FZF_ALT_C_OPTS` to pass additional options
If you're on a tmux session, you can start fzf in a split pane by setting If you're on a tmux session, you can start fzf in a tmux split pane or in
`FZF_TMUX` to 1, and change the height of the pane with `FZF_TMUX_HEIGHT` a tmux popup window by setting `FZF_TMUX_OPTS` (e.g. `-d 40%`).
(e.g. `20`, `50%`). See `fzf-tmux --help` for available options.
If you use vi mode on bash, you need to add `set -o vi` *before* `source
~/.fzf.bash` in your .bashrc, so that it correctly sets up key bindings for vi
mode.
More tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/Configuring-shell-key-bindings). More tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/Configuring-shell-key-bindings).
@@ -427,6 +418,21 @@ _fzf_compgen_path() {
_fzf_compgen_dir() { _fzf_compgen_dir() {
fd --type d --hidden --follow --exclude ".git" . "$1" fd --type d --hidden --follow --exclude ".git" . "$1"
} }
# (EXPERIMENTAL) Advanced customization of fzf options via _fzf_comprun function
# - The first argument to the function is the name of the command.
# - You should make sure to pass the rest of the arguments to fzf.
_fzf_comprun() {
local command=$1
shift
case "$command" in
cd) fzf "$@" --preview 'tree -C {} | head -200' ;;
export|unset) fzf "$@" --preview "eval 'echo \$'{}" ;;
ssh) fzf "$@" --preview 'dig {}' ;;
*) fzf "$@" ;;
esac
}
``` ```
#### Supported commands #### Supported commands
@@ -436,11 +442,61 @@ On bash, fuzzy completion is enabled only for a predefined set of commands
commands as well by using `_fzf_setup_completion` helper function. commands as well by using `_fzf_setup_completion` helper function.
```sh ```sh
# usage: _fzf_setup_completion path|dir COMMANDS... # usage: _fzf_setup_completion path|dir|var|alias|host COMMANDS...
_fzf_setup_completion path ag git kubectl _fzf_setup_completion path ag git kubectl
_fzf_setup_completion dir tree _fzf_setup_completion dir tree
``` ```
#### Custom fuzzy completion
_**(Custom completion API is experimental and subject to change)**_
For a command named _"COMMAND"_, define `_fzf_complete_COMMAND` function using
`_fzf_complete` helper.
```sh
# Custom fuzzy completion for "doge" command
# e.g. doge **<TAB>
_fzf_complete_doge() {
_fzf_complete --multi --reverse --prompt="doge> " -- "$@" < <(
echo very
echo wow
echo such
echo 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
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
`complete` command.
```sh
[ -n "$BASH" ] && complete -F _fzf_complete_doge -o default -o bashdefault doge
```
If you need to post-process the output from fzf, define
`_fzf_complete_COMMAND_post` as follows.
```sh
_fzf_complete_foo() {
_fzf_complete --multi --reverse --header-lines=3 -- "$@" < <(
ls -al
)
}
_fzf_complete_foo_post() {
awk '{print $NF}'
}
[ -n "$BASH" ] && complete -F _fzf_complete_foo -o default -o bashdefault foo
```
Vim plugin Vim plugin
---------- ----------
@@ -481,32 +537,69 @@ fzf --bind 'f1:execute(less -f {}),ctrl-y:execute-silent(echo {} | pbcopy)+abort
See *KEY BINDINGS* section of the man page for details. See *KEY BINDINGS* section of the man page for details.
### Reloading the candidate list
By binding `reload` action to a key or an event, you can make fzf dynamically
reload the candidate list. See https://github.com/junegunn/fzf/issues/1750 for
more details.
#### 1. Update the list of processes by pressing CTRL-R
```sh
FZF_DEFAULT_COMMAND='ps -ef' \
fzf --bind 'ctrl-r:reload($FZF_DEFAULT_COMMAND)' \
--header 'Press CTRL-R to reload' --header-lines=1 \
--height=50% --layout=reverse
```
#### 2. Switch between sources by pressing CTRL-D or CTRL-F
```sh
FZF_DEFAULT_COMMAND='find . -type f' \
fzf --bind 'ctrl-d:reload(find . -type d),ctrl-f:reload($FZF_DEFAULT_COMMAND)' \
--height=50% --layout=reverse
```
#### 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
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.
```sh
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" \
--height=50% --layout=reverse
```
If ripgrep doesn't find any matches, it will exit with a non-zero exit status,
and fzf will warn you about it. To suppress the warning message, we added
`|| true` to the command, so that it always exits with 0.
### Preview window ### Preview window
When `--preview` option is set, fzf automatically starts an external process with When the `--preview` option is set, fzf automatically starts an external process
the current line as the argument and shows the result in the split window. with the current line as the argument and shows the result in the split window.
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 ```bash
# {} is replaced to the single-quoted string of the focused line # {} is replaced to the single-quoted string of the focused line
fzf --preview 'cat {}' fzf --preview 'cat {}'
``` ```
Since the preview window is updated only after the process is complete, it's
important that the command finishes quickly.
```bash
# Use head instead of cat so that the command doesn't take too long to finish
fzf --preview 'head -100 {}'
```
Preview window supports ANSI colors, so you can use any program that Preview window supports ANSI colors, so you can use any program that
syntax-highlights the content of a file. syntax-highlights the content of a file, such as
[Bat](https://github.com/sharkdp/bat) or
- Bat: https://github.com/sharkdp/bat [Highlight](http://www.andre-simon.de/doku/highlight/en/highlight.php):
- Highlight: http://www.andre-simon.de/doku/highlight/en/highlight.php
```bash ```bash
fzf --preview 'bat --style=numbers --color=always {} | head -500' fzf --preview 'bat --style=numbers --color=always --line-range :500 {}'
``` ```
You can customize the size, position, and border of the preview window using You can customize the size, position, and border of the preview window using
@@ -535,7 +628,7 @@ not a good idea to add `--preview` option to your `$FZF_DEFAULT_OPTS`**.
# ********************* # *********************
# ** DO NOT DO THIS! ** # ** DO NOT DO THIS! **
# ********************* # *********************
export FZF_DEFAULT_OPTS='--preview "bat --style=numbers --color=always {} | head -500"' export FZF_DEFAULT_OPTS='--preview "bat --style=numbers --color=always --line-range :500 {}"'
# bat doesn't work with any input other than the list of files # bat doesn't work with any input other than the list of files
ps -ef | fzf ps -ef | fzf
@@ -605,4 +698,4 @@ https://github.com/junegunn/fzf/wiki/Related-projects
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2019 Junegunn Choi Copyright (c) 2013-2020 Junegunn Choi

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# fzf-tmux: starts fzf in a tmux pane # fzf-tmux: starts fzf in a tmux pane
# usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS] # usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
fail() { fail() {
>&2 echo "$1" >&2 echo "$1"
@@ -10,6 +10,7 @@ fail() {
fzf="$(command -v fzf 2> /dev/null)" || fzf="$(dirname "$0")/fzf" fzf="$(command -v fzf 2> /dev/null)" || fzf="$(dirname "$0")/fzf"
[[ -x "$fzf" ]] || fail 'fzf executable not found' [[ -x "$fzf" ]] || fail 'fzf executable not found'
tmux_args=()
args=() args=()
opt="" opt=""
skip="" skip=""
@@ -20,15 +21,23 @@ term=""
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}") [[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}")
help() { help() {
>&2 echo 'usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS] >&2 echo 'usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
Layout LAYOUT OPTIONS:
-u [HEIGHT[%]] Split above (up) (default layout: -d 50%)
-d [HEIGHT[%]] Split below (down)
-l [WIDTH[%]] Split left
-r [WIDTH[%]] Split right
(default: -d 50%) Popup window (requires tmux 3.2 or above):
-p [WIDTH[%][,HEIGHT[%]]] (default: 50%)
-w WIDTH[%]
-h HEIGHT[%]
-x COL
-y ROW
Split pane:
-u [HEIGHT[%]] Split above (up)
-d [HEIGHT[%]] Split below (down)
-l [WIDTH[%]] Split left
-r [WIDTH[%]] Split right
' '
exit exit
} }
@@ -47,8 +56,10 @@ while [[ $# -gt 0 ]]; do
echo "fzf-tmux (with fzf $("$fzf" --version))" echo "fzf-tmux (with fzf $("$fzf" --version))"
exit exit
;; ;;
-w*|-h*|-d*|-u*|-r*|-l*) -p*|-w*|-h*|-x*|-y*|-d*|-u*|-r*|-l*)
if [[ "$arg" =~ ^.[lrw] ]]; then if [[ "$arg" =~ ^-[pwhxy] ]]; then
[[ "$opt" =~ "-K -E" ]] || opt="-K -E"
elif [[ "$arg" =~ ^.[lr] ]]; then
opt="-h" opt="-h"
if [[ "$arg" =~ ^.l ]]; then if [[ "$arg" =~ ^.l ]]; then
opt="$opt -d" opt="$opt -d"
@@ -66,7 +77,7 @@ while [[ $# -gt 0 ]]; do
if [[ ${#arg} -gt 2 ]]; then if [[ ${#arg} -gt 2 ]]; then
size="${arg:2}" size="${arg:2}"
else else
if [[ "$1" =~ ^[0-9]+%?$ ]]; then if [[ "$1" =~ ^[0-9%,]+$ ]] || [[ "$1" =~ ^[A-Z]$ ]]; then
size="$1" size="$1"
shift shift
else else
@@ -74,7 +85,15 @@ while [[ $# -gt 0 ]]; do
fi fi
fi fi
if [[ "$size" =~ %$ ]]; then if [[ "$arg" =~ ^-p ]]; then
if [[ -n "$size" ]]; then
w=${size%%,*}
h=${size##*,}
opt="$opt -w$w -h$h"
fi
elif [[ "$arg" =~ ^-[whxy] ]]; then
opt="$opt ${arg:0:2}$size"
elif [[ "$size" =~ %$ ]]; then
size=${size:0:((${#size}-1))} size=${size:0:((${#size}-1))}
if [[ -n "$swap" ]]; then if [[ -n "$swap" ]]; then
opt="$opt -p $(( 100 - size ))" opt="$opt -p $(( 100 - size ))"
@@ -100,6 +119,8 @@ while [[ $# -gt 0 ]]; do
# "--" can be used to separate fzf-tmux options from fzf options to # "--" can be used to separate fzf-tmux options from fzf options to
# avoid conflicts # avoid conflicts
skip=1 skip=1
tmux_args=("${args[@]}")
args=()
continue continue
;; ;;
*) *)
@@ -109,17 +130,17 @@ while [[ $# -gt 0 ]]; do
[[ -n "$skip" ]] && args+=("$arg") [[ -n "$skip" ]] && args+=("$arg")
done done
if [[ -z "$TMUX" || "$opt" =~ ^-h && "$columns" -le 40 || ! "$opt" =~ ^-h && "$lines" -le 15 ]]; then if [[ -z "$TMUX" ]]; then
"$fzf" "${args[@]}" "$fzf" "${args[@]}"
exit $? exit $?
fi fi
# --height option is not allowed # --height option is not allowed
args+=("--no-height") args=("${args[@]}" "--no-height")
# Handle zoomed tmux pane by moving it to a temp window # Handle zoomed tmux pane without popup options by moving it to a temp window
if tmux list-panes -F '#F' | grep -q Z; then if [[ ! "$opt" =~ "-K -E" ]] && tmux list-panes -F '#F' | grep -q Z; then
zoomed=1 zoomed_without_popup=1
original_window=$(tmux display-message -p "#{window_id}") original_window=$(tmux display-message -p "#{window_id}")
tmp_window=$(tmux new-window -d -P -F "#{window_id}" "bash -c 'while :; do for c in \\| / - '\\;' do sleep 0.2; printf \"\\r\$c fzf-tmux is running\\r\"; done; done'") tmp_window=$(tmux new-window -d -P -F "#{window_id}" "bash -c 'while :; do for c in \\| / - '\\;' do sleep 0.2; printf \"\\r\$c fzf-tmux is running\\r\"; done; done'")
tmux swap-pane -t $tmp_window \; select-window -t $tmp_window tmux swap-pane -t $tmp_window \; select-window -t $tmp_window
@@ -133,16 +154,17 @@ argsf="${TMPDIR:-/tmp}/fzf-args-$id"
fifo1="${TMPDIR:-/tmp}/fzf-fifo1-$id" fifo1="${TMPDIR:-/tmp}/fzf-fifo1-$id"
fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id" fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id"
fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id" fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
cleanup() { cleanup() {
\rm -f $argsf $fifo1 $fifo2 $fifo3 \rm -f $argsf $fifo1 $fifo2 $fifo3
# Restore tmux window options # Restore tmux window options
if [[ "${#tmux_win_opts[@]}" -gt 0 ]]; then if [[ "${#tmux_win_opts[@]}" -gt 0 ]]; then
eval "tmux ${tmux_win_opts[@]}" eval "tmux ${tmux_win_opts[*]}"
fi fi
# Remove temp window if we were zoomed # Remove temp window if we were zoomed without popup options
if [[ -n "$zoomed" ]]; then if [[ -n "$zoomed_without_popup" ]]; then
tmux display-message -p "#{window_id}" > /dev/null tmux display-message -p "#{window_id}" > /dev/null
tmux swap-pane -t $original_window \; \ tmux swap-pane -t $original_window \; \
select-window -t $original_window \; \ select-window -t $original_window \; \
@@ -158,44 +180,45 @@ cleanup() {
trap 'cleanup 1' SIGUSR1 trap 'cleanup 1' SIGUSR1
trap 'cleanup' EXIT trap 'cleanup' EXIT
envs="env TERM=$TERM " envs="export TERM=$TERM "
[[ "$opt" =~ "-K -E" ]] && FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")" [[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")" [[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
echo "$envs;" > "$argsf"
mkfifo -m o+w $fifo2
mkfifo -m o+w $fifo3
# Build arguments to fzf # Build arguments to fzf
opts="" opts=$(printf "%q " "${args[@]}")
for arg in "${args[@]}"; do
arg="${arg//\\/\\\\}"
arg="${arg//\"/\\\"}"
arg="${arg//\`/\\\`}"
arg="${arg//$/\\$}"
opts="$opts \"$arg\""
done
pppid=$$ pppid=$$
echo -n "trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM;" > $argsf echo -n "trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM;" >> $argsf
close="; trap - EXIT SIGINT SIGTERM $close" close="; trap - EXIT SIGINT SIGTERM $close"
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') ) export TMUX=$(cut -d , -f 1,2 <<< "$TMUX")
mkfifo -m o+w $fifo2
if [[ "$opt" =~ "-K -E" ]]; then
cat $fifo2 &
if [[ -n "$term" ]] || [[ -t 0 ]]; then
cat <<< "\"$fzf\" $opts > $fifo2; out=\$? $close; exit \$out" >> $argsf
else
mkfifo $fifo1
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
exit $?
fi
mkfifo -m o+w $fifo3
if [[ -n "$term" ]] || [[ -t 0 ]]; then if [[ -n "$term" ]] || [[ -t 0 ]]; then
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
set-window-option remain-on-exit off \;\
split-window $opt "$envs bash -c 'cd $(printf %q "$PWD"); exec -a fzf bash $argsf'" $swap \
> /dev/null 2>&1
else else
mkfifo $fifo1 mkfifo $fifo1
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" >> $argsf cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" >> $argsf
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
set-window-option remain-on-exit off \;\
split-window $opt "$envs bash -c 'exec -a fzf bash $argsf'" $swap \
> /dev/null 2>&1
cat <&0 > $fifo1 & cat <&0 > $fifo1 &
fi fi
tmux set-window-option synchronize-panes off \;\
set-window-option remain-on-exit off \;\
split-window -c "$PWD" $opt "${tmux_args[@]}" "bash -c 'exec -a fzf bash $argsf'" $swap \
> /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
cat $fifo2 cat $fifo2
exit "$(cat $fifo3)" exit "$(cat $fifo3)"

View File

@@ -1,17 +1,20 @@
fzf.txt fzf Last change: November 23 2019 fzf.txt fzf Last change: October 18 2020
FZF - TABLE OF CONTENTS *fzf* *fzf-toc* FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
============================================================================== ==============================================================================
FZF Vim integration FZF Vim integration
Installation
Summary Summary
:FZF[!] :FZF[!]
Configuration Configuration
Examples Examples
Explanation of g:fzf_colors
fzf#run fzf#run
fzf#wrap fzf#wrap
Global options supported by fzf#wrap
Tips Tips
fzf inside terminal buffer fzf inside terminal buffer
Starting fzf in Neovim floating window Starting fzf in a popup window
Hide statusline Hide statusline
License License
@@ -19,6 +22,46 @@ FZF VIM INTEGRATION *fzf-vim-integration*
============================================================================== ==============================================================================
INSTALLATION *fzf-installation*
==============================================================================
Once you have fzf installed, you can enable it inside Vim simply by adding the
directory to 'runtimepath' in your Vim configuration file. The path may differ
depending on the package manager.
>
" If installed using Homebrew
set rtp+=/usr/local/opt/fzf
" If installed using git
set rtp+=~/.fzf
<
If you use {vim-plug}{1}, the same can be written as:
>
" If installed using Homebrew
Plug '/usr/local/opt/fzf'
" If installed using git
Plug '~/.fzf'
<
But if you want the latest Vim plugin file from GitHub rather than the one
included in the package, write:
>
Plug 'junegunn/fzf'
<
The Vim plugin will pick up fzf binary available on the system. If fzf is not
found on `$PATH`, it will ask you if it should download the latest binary for
you.
To make sure that you have the latest version of the binary, set up
post-update hook like so:
*fzf#install*
>
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
<
{1} https://github.com/junegunn/vim-plug
SUMMARY *fzf-summary* SUMMARY *fzf-summary*
============================================================================== ==============================================================================
@@ -38,12 +81,12 @@ the basic file selector command built on top of them.
- Basic fuzzy file selector - Basic fuzzy file selector
- A reference implementation for those who don't want to write VimScript to - A reference implementation for those who don't want to write VimScript to
implement custom commands implement custom commands
- If you're looking for more such commands, check out {fzf.vim}{1} project. - If you're looking for more such commands, check out {fzf.vim}{2} project.
The most important of all is `fzf#run`, but it would be easier to understand The most important of all is `fzf#run`, but it would be easier to understand
the whole if we start off with `:FZF` command. the whole if we start off with `:FZF` command.
{1} https://github.com/junegunn/fzf.vim {2} https://github.com/junegunn/fzf.vim
:FZF[!] :FZF[!]
@@ -63,14 +106,14 @@ the whole if we start off with `:FZF` command.
" Bang version starts fzf in fullscreen mode " Bang version starts fzf in fullscreen mode
:FZF! :FZF!
< <
Similarly to {ctrlp.vim}{2}, use enter key, CTRL-T, CTRL-X or CTRL-V to open Similarly to {ctrlp.vim}{3}, use enter key, CTRL-T, CTRL-X or CTRL-V to open
selected files in the current window, in new tabs, in horizontal splits, or in selected files in the current window, in new tabs, in horizontal splits, or in
vertical splits respectively. vertical splits respectively.
Note that the environment variables `FZF_DEFAULT_COMMAND` and Note that the environment variables `FZF_DEFAULT_COMMAND` and
`FZF_DEFAULT_OPTS` also apply here. `FZF_DEFAULT_OPTS` also apply here.
{2} https://github.com/kien/ctrlp.vim {3} https://github.com/kien/ctrlp.vim
< Configuration >_____________________________________________________________~ < Configuration >_____________________________________________________________~
@@ -112,10 +155,13 @@ Examples~
\ 'ctrl-v': 'vsplit' } \ 'ctrl-v': 'vsplit' }
" Default fzf layout " Default fzf layout
" - down / up / left / right " - Popup window
let g:fzf_layout = { 'down': '~40%' } let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
" You can set up fzf window using a Vim command (Neovim or latest Vim 8 required) " - down / up / left / right
let g:fzf_layout = { 'down': '40%' }
" - Window using a Vim command
let g:fzf_layout = { 'window': 'enew' } let g:fzf_layout = { 'window': 'enew' }
let g:fzf_layout = { 'window': '-tabnew' } let g:fzf_layout = { 'window': '-tabnew' }
let g:fzf_layout = { 'window': '10new' } let g:fzf_layout = { 'window': '10new' }
@@ -144,6 +190,51 @@ Examples~
let g:fzf_history_dir = '~/.local/share/fzf-history' let g:fzf_history_dir = '~/.local/share/fzf-history'
< <
Explanation of g:fzf_colors~
*fzf-explanation-of-gfzfcolors*
`g:fzf_colors` is a dictionary mapping fzf elements to a color specification
list:
>
element: [ component, group1 [, group2, ...] ]
<
- `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 ( `>` )
----------------------+------------------------------------------------------
- `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
order) for a matching color definition
For example, consider the following specification:
>
'prompt': ['fg', 'Conditional', 'Comment'],
<
This means we color the prompt - using the `fg` attribute of the `Conditional`
if it exists, - otherwise use the `fg` attribute of the `Comment` highlight
group if it exists, - otherwise fall back to the default color settings for
the prompt.
You can examine the color option generated according the setting by printing
the result of `fzf#wrap()` function like so:
>
:echo fzf#wrap()
<
FZF#RUN FZF#RUN
============================================================================== ==============================================================================
@@ -203,7 +294,9 @@ The following table summarizes the available options.
`options` | string/list | Options to fzf `options` | string/list | Options to fzf
`dir` | string | Working directory `dir` | string | Working directory
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` ) `up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
`tmux` | string | (Layout) fzf-tmux options (e.g. `-p90%,60%` )
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `verticalaboveleft30new` ) `window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `verticalaboveleft30new` )
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width':0.9,'height':0.6}` )
---------------------------+---------------+---------------------------------------------------------------------- ---------------------------+---------------+----------------------------------------------------------------------
`options` entry can be either a string or a list. For simple cases, string `options` entry can be either a string or a list. For simple cases, string
@@ -212,6 +305,19 @@ should suffice, but prefer to use list type to avoid escaping issues.
call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'}) call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'})
call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']}) call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
< <
When `window` entry is a dictionary, fzf will start in a popup window. The
following options are allowed:
- Required:
- `width` [float range [0 ~ 1]] or [integer range [8 ~ ]]
- `height` [float range [0 ~ 1]] or [integer range [4 ~ ]]
- 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`
FZF#WRAP FZF#WRAP
============================================================================== ==============================================================================
@@ -244,8 +350,8 @@ After we "wrap" our spec, we pass it to `fzf#run`.
> >
call fzf#run(fzf#wrap({'source': 'ls'})) call fzf#run(fzf#wrap({'source': 'ls'}))
< <
Now it supports CTRL-T, CTRL-V, and CTRL-X key bindings and it opens fzf Now it supports CTRL-T, CTRL-V, and CTRL-X key bindings (configurable via
window according to `g:fzf_layout` setting. `g:fzf_action`) and it opens fzf window according to `g:fzf_layout` setting.
To make it easier to use, let's define `LS` command. To make it easier to use, let's define `LS` command.
> >
@@ -275,6 +381,19 @@ unique name to our command and pass it as the first argument to `fzf#wrap`.
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0)) \ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
< <
< Global options supported by fzf#wrap >______________________________________~
*fzf-global-options-supported-by-fzf#wrap*
- `g:fzf_layout`
- `g:fzf_action`
- Works only when no custom `sink` (or `sink*`) is provided
- Having custom sink usually means that each entry is not an ordinary
file path (e.g. name of color scheme), so we can't blindly apply the
same strategy (i.e. `tabeditsome-color-scheme` doesn't make sense)
- `g:fzf_colors`
- `g:fzf_history_dir`
TIPS *fzf-tips* TIPS *fzf-tips*
============================================================================== ==============================================================================
@@ -291,29 +410,30 @@ The latest versions of Vim and Neovim include builtin terminal emulator
- `callfzf#run({'left':'30%'})` or `letg:fzf_layout={'left':'30%'}` - `callfzf#run({'left':'30%'})` or `letg:fzf_layout={'left':'30%'}`
Starting fzf in Neovim floating window~ Starting fzf in a popup window~
*fzf-starting-fzf-in-neovim-floating-window* *fzf-starting-fzf-in-a-popup-window*
> >
" Using floating windows of Neovim to start fzf " Required:
if has('nvim') " - width [float range [0 ~ 1]] or [integer range [8 ~ ]]
let $FZF_DEFAULT_OPTS .= ' --border --margin=0,2' " - height [float range [0 ~ 1]] or [integer range [4 ~ ]]
"
function! FloatingFZF() " Optional:
let width = float2nr(&columns * 0.9) " - xoffset [float default 0.5 range [0 ~ 1]]
let height = float2nr(&lines * 0.6) " - yoffset [float default 0.5 range [0 ~ 1]]
let opts = { 'relative': 'editor', " - highlight [string default 'Comment']: Highlight group for border
\ 'row': (&lines - height) / 2, " - border [string default 'rounded']: Border style
\ 'col': (&columns - width) / 2, " - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
\ 'width': width, let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
\ 'height': height } <
Alternatively, you can make fzf open in a tmux popup window (requires tmux 3.2
let win = nvim_open_win(nvim_create_buf(v:false, v:true), v:true, opts) or above) by putting fzf-tmux options in `tmux` key.
call setwinvar(win, '&winhighlight', 'NormalFloat:Normal') >
endfunction " See `man fzf-tmux` for available options
if exists('$TMUX')
let g:fzf_layout = { 'window': 'call FloatingFZF()' } let g:fzf_layout = { 'tmux': '-p90%,60%' }
else
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
endif endif
< <
Hide statusline~ Hide statusline~
@@ -323,8 +443,8 @@ When fzf starts in a terminal buffer, the file type of the buffer is set to
`fzf`. So you can set up `FileTypefzf` autocmd to customize the settings of `fzf`. So you can set up `FileTypefzf` autocmd to customize the settings of
the window. the window.
For example, if you use the default layout (`{'down':'~40%'}`) on Neovim, you For example, if you use a non-popup layout (e.g. `{'down':'40%'}`) on Neovim,
might want to temporarily disable the statusline for a cleaner look. you might want to temporarily disable the statusline for a cleaner look.
> >
if has('nvim') && !exists('g:fzf_layout') if has('nvim') && !exists('g:fzf_layout')
autocmd! FileType fzf autocmd! FileType fzf
@@ -338,7 +458,7 @@ LICENSE *fzf-license*
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2019 Junegunn Choi Copyright (c) 2013-2020 Junegunn Choi
============================================================================== ==============================================================================
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap: vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:

22
go.mod
View File

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

61
go.sum
View File

@@ -1,26 +1,35 @@
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635 h1:hheUEMzaOie/wKeIc1WPa7CDVuIO5hqQxjS+dwTQEnI= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635/go.mod h1:yrQYJKKDTrHmbYxI7CYi+/hbdiDT2m4Hj+t0ikCjsrQ= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df h1:tLS1QD2puA1USLvkEnGfOt+Zp2ijtNIK3z2YFaIZZR4= github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df/go.mod h1:tqyG50u7+Ctv1w5VX67kLzKcj9YXR/JSBZQq/+mLl1A= github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7 h1:G52I+Gk/wPD4HKvKT0Vxxp9OUPxqKs3OK6rffSPtNkA= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c h1:3nKFouDdpgGUV/uerJcYWH45ZbJzX0SiVWfTgmUeTzc= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw=
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c h1:eFzthqtg3W6Pihj3DMTXLAF4f+ge5r5Ie5g6HLIZAF0= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/saracen/walker v0.1.1 h1:Ou2QIKTWqo0QxhtuHVmtObbmhjMCEUyJ82xp0uV+MGI=
github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk= github.com/saracen/walker v0.1.1/go.mod h1:0oKYMsKVhSJ+ful4p/XbjvXbMgLEkLITZaxozsl4CGE=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74 h1:/0jf0Cx3u07Ma4EzUA6NIGuvk9hb3Br6i9V8atthkwk= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 h1:rM0ROo5vb9AdYJi1110yjWGMej9ITfKddS89P3Fkhug= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.0.0-20170530162606-4ee4af566555 h1:pjwO9HxObpgZBurDvTLFbSinaf3+cKpTAjVfiGaHwrI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170530162606-4ee4af566555/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1 h1:/DtoiOYKoQCcIFXQjz07RnWNPRCbqmSXSpgEzhC9ZHM=
golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

69
install
View File

@@ -2,11 +2,10 @@
set -u set -u
version=0.20.0 version=0.24.3
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2
binary_arch=
shells="bash zsh fish" shells="bash zsh fish"
prefix='~/.fzf' prefix='~/.fzf'
prefix_expand=~/.fzf prefix_expand=~/.fzf
@@ -28,9 +27,6 @@ usage: $0 [OPTIONS]
--no-bash Do not set up bash configuration --no-bash Do not set up bash configuration
--no-zsh Do not set up zsh configuration --no-zsh Do not set up zsh configuration
--no-fish Do not set up fish configuration --no-fish Do not set up fish configuration
--32 Download 32-bit binary
--64 Download 64-bit binary
EOF EOF
} }
@@ -56,8 +52,6 @@ for opt in "$@"; do
--no-completion) auto_completion=0 ;; --no-completion) auto_completion=0 ;;
--update-rc) update_config=1 ;; --update-rc) update_config=1 ;;
--no-update-rc) update_config=0 ;; --no-update-rc) update_config=0 ;;
--32) binary_arch=386 ;;
--64) binary_arch=amd64 ;;
--bin) ;; --bin) ;;
--no-bash) shells=${shells/bash/} ;; --no-bash) shells=${shells/bash/} ;;
--no-zsh) shells=${shells/zsh/} ;; --no-zsh) shells=${shells/zsh/} ;;
@@ -120,7 +114,7 @@ link_fzf_in_path() {
try_curl() { try_curl() {
command -v curl > /dev/null && command -v curl > /dev/null &&
if [[ $1 =~ tgz$ ]]; then if [[ $1 =~ tar.gz$ ]]; then
curl -fL $1 | tar -xzf - curl -fL $1 | tar -xzf -
else else
local temp=${TMPDIR:-/tmp}/fzf.zip local temp=${TMPDIR:-/tmp}/fzf.zip
@@ -130,7 +124,7 @@ try_curl() {
try_wget() { try_wget() {
command -v wget > /dev/null && command -v wget > /dev/null &&
if [[ $1 =~ tgz$ ]]; then if [[ $1 =~ tar.gz$ ]]; then
wget -O - $1 | tar -xzf - wget -O - $1 | tar -xzf -
else else
local temp=${TMPDIR:-/tmp}/fzf.zip local temp=${TMPDIR:-/tmp}/fzf.zip
@@ -140,13 +134,11 @@ try_wget() {
download() { download() {
echo "Downloading bin/fzf ..." echo "Downloading bin/fzf ..."
if [[ ! "$version" =~ alpha ]]; then if [ -x "$fzf_base"/bin/fzf ]; then
if [ -x "$fzf_base"/bin/fzf ]; then echo " - Already exists"
echo " - Already exists" check_binary && return
check_binary && return
fi
link_fzf_in_path && return
fi fi
link_fzf_in_path && return
mkdir -p "$fzf_base"/bin && cd "$fzf_base"/bin mkdir -p "$fzf_base"/bin && cd "$fzf_base"/bin
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
binary_error="Failed to create bin directory" binary_error="Failed to create bin directory"
@@ -154,9 +146,7 @@ download() {
fi fi
local url local url
[[ "$version" =~ alpha ]] && url=https://github.com/junegunn/fzf/releases/download/$version/${1}
url=https://github.com/junegunn/fzf-bin/releases/download/alpha/${1} ||
url=https://github.com/junegunn/fzf-bin/releases/download/$version/${1}
set -o pipefail set -o pipefail
if ! (try_curl $url || try_wget $url); then if ! (try_curl $url || try_wget $url); then
set +o pipefail set +o pipefail
@@ -178,27 +168,20 @@ archi=$(uname -sm)
binary_available=1 binary_available=1
binary_error="" binary_error=""
case "$archi" in case "$archi" in
Darwin\ *64) download fzf-$version-darwin_${binary_arch:-amd64}.tgz ;; Darwin\ *64) download fzf-$version-darwin_amd64.tar.gz ;;
Darwin\ *86) download fzf-$version-darwin_${binary_arch:-386}.tgz ;; Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
Linux\ armv5*) download fzf-$version-linux_${binary_arch:-arm5}.tgz ;; Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
Linux\ armv6*) download fzf-$version-linux_${binary_arch:-arm6}.tgz ;; Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
Linux\ armv7*) download fzf-$version-linux_${binary_arch:-arm7}.tgz ;; Linux\ armv8*) download fzf-$version-linux_arm64.tar.gz ;;
Linux\ armv8*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;; Linux\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;;
Linux\ aarch64*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;; Linux\ *64) download fzf-$version-linux_amd64.tar.gz ;;
Linux\ *64) download fzf-$version-linux_${binary_arch:-amd64}.tgz ;; FreeBSD\ *64) download fzf-$version-freebsd_amd64.tar.gz ;;
Linux\ *86) download fzf-$version-linux_${binary_arch:-386}.tgz ;; OpenBSD\ *64) download fzf-$version-openbsd_amd64.tar.gz ;;
FreeBSD\ *64) download fzf-$version-freebsd_${binary_arch:-amd64}.tgz ;; CYGWIN*\ *64) download fzf-$version-windows_amd64.zip ;;
FreeBSD\ *86) download fzf-$version-freebsd_${binary_arch:-386}.tgz ;; MINGW*\ *64) download fzf-$version-windows_amd64.zip ;;
OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64}.tgz ;; MSYS*\ *64) download fzf-$version-windows_amd64.zip ;;
OpenBSD\ *86) download fzf-$version-openbsd_${binary_arch:-386}.tgz ;; Windows*\ *64) download fzf-$version-windows_amd64.zip ;;
CYGWIN*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;; *) binary_available=0 binary_error=1 ;;
MINGW*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
MINGW*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
MSYS*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
MSYS*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
Windows*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
Windows*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
*) binary_available=0 binary_error=1 ;;
esac esac
cd "$fzf_base" cd "$fzf_base"
@@ -214,7 +197,7 @@ if [ -n "$binary_error" ]; then
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath" export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
mkdir -p "$GOPATH" mkdir -p "$GOPATH"
fi fi
if go get -u github.com/junegunn/fzf; then if go get -ldflags "-s -w -X main.version=$version -X main.revision=go-get" github.com/junegunn/fzf; then
echo "OK" echo "OK"
cp "$GOPATH/bin/fzf" "$fzf_base/bin/" cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
else else
@@ -384,7 +367,11 @@ fi
if [ $update_config -eq 1 ]; then if [ $update_config -eq 1 ]; then
echo 'Finished. Restart your shell or reload config file.' echo 'Finished. Restart your shell or reload config file.'
[[ "$shells" =~ bash ]] && echo ' source ~/.bashrc # bash' if [[ "$shells" =~ bash ]]; then
echo -n ' source ~/.bashrc # bash'
[[ "$archi" =~ Darwin ]] && echo -n ' (.bashrc should be loaded from .bash_profile)'
echo
fi
[[ "$shells" =~ zsh ]] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh" [[ "$shells" =~ zsh ]] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh"
[[ "$shells" =~ fish ]] && [ $key_bindings -eq 1 ] && echo ' fzf_key_bindings # fish' [[ "$shells" =~ fish ]] && [ $key_bindings -eq 1 ] && echo ' fzf_key_bindings # fish'
echo echo

61
install.ps1 Normal file
View File

@@ -0,0 +1,61 @@
$version="0.24.3"
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
function check_binary () {
Write-Host " - Checking fzf executable ... " -NoNewline
$output=cmd /c $fzf_base\bin\fzf.exe --version 2>&1
if (-not $?) {
Write-Host "Error: $output"
$binary_error="Invalid binary"
} else {
$output=(-Split $output)[0]
if ($version -ne $output) {
Write-Host "$output != $version"
$binary_error="Invalid version"
} else {
Write-Host "$output"
$binary_error=""
return 1
}
}
Remove-Item "$fzf_base\bin\fzf.exe"
return 0
}
function download {
param($file)
Write-Host "Downloading bin/fzf ..."
if (Test-Path "$fzf_base\bin\fzf.exe") {
Write-Host " - Already exists"
if (check_binary) {
return
}
}
if (-not (Test-Path "$fzf_base\bin")) {
md "$fzf_base\bin"
}
if (-not $?) {
$binary_error="Failed to create bin directory"
return
}
cd "$fzf_base\bin"
$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 ($?) {
(Microsoft.PowerShell.Archive\Expand-Archive -Path $temp -DestinationPath .); (Remove-Item $temp)
} else {
$binary_error="Failed to download with powershell"
}
if (-not (Test-Path fzf.exe)) {
$binary_error="Failed to download $file"
return
}
check_binary >$null
}
download "fzf-$version-windows_amd64.zip"
Write-Host 'For more information, see: https://github.com/junegunn/fzf'

11
main.go
View File

@@ -1,9 +1,14 @@
package main package main
import "github.com/junegunn/fzf/src" import (
"github.com/junegunn/fzf/src"
"github.com/junegunn/fzf/src/protector"
)
var revision string var version string = "0.24"
var revision string = "devel"
func main() { func main() {
fzf.Run(fzf.ParseOptions(), revision) protector.Protect()
fzf.Run(fzf.ParseOptions(), version, revision)
} }

View File

@@ -1,7 +1,7 @@
.ig .ig
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2019 Junegunn Choi Copyright (c) 2013-2020 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -21,25 +21,39 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf-tmux 1 "Dec 2019" "fzf 0.20.0" "fzf-tmux - open fzf in tmux split pane" .TH fzf-tmux 1 "Nov 2020" "fzf 0.24.3" "fzf-tmux - open fzf in tmux split pane"
.SH NAME .SH NAME
fzf-tmux - open fzf in tmux split pane fzf-tmux - open fzf in tmux split pane
.SH SYNOPSIS .SH SYNOPSIS
.B fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS] .B fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
.SH DESCRIPTION .SH DESCRIPTION
fzf-tmux is a wrapper script for fzf that opens fzf in a tmux split pane. It is fzf-tmux is a wrapper script for fzf that opens fzf in a tmux split pane or in
designed to work just like fzf except that it does not take up the whole a tmux popup window. It is designed to work just like fzf except that it does
screen. You can safely use fzf-tmux instead of fzf in your scripts as the extra not take up the whole screen. You can safely use fzf-tmux instead of fzf in
options will be silently ignored if you're not on tmux. your scripts as the extra options will be silently ignored if you're not on
tmux.
.SH OPTIONS .SH LAYOUT OPTIONS
.SS Layout
(default: \fB-d 50%\fR) (default layout: \fB-d 50%\fR)
.SS Popup window
(requires tmux 3.2 or above)
.TP
.B "-p [WIDTH[%][,HEIGHT[%]]]"
.TP
.B "-w WIDTH[%]"
.TP
.B "-h WIDTH[%]"
.TP
.B "-x COL"
.TP
.B "-y ROW"
.SS Split pane
.TP .TP
.B "-u [height[%]]" .B "-u [height[%]]"
Split above (up) Split above (up)

View File

@@ -1,7 +1,7 @@
.ig .ig
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2019 Junegunn Choi Copyright (c) 2013-2020 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Dec 2019" "fzf 0.20.0" "fzf - a command-line fuzzy finder" .TH fzf 1 "Nov 2020" "fzf 0.24.3" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -130,6 +130,10 @@ the details.
.B "--cycle" .B "--cycle"
Enable cyclic scroll Enable cyclic scroll
.TP .TP
.B "--keep-right"
Keep the right end of the line visible when it's too long. Effective only when
the query string is empty.
.TP
.B "--no-hscroll" .B "--no-hscroll"
Disable horizontal scroll Disable horizontal scroll
.TP .TP
@@ -178,8 +182,26 @@ Choose the layout (default: default)
A synonym for \fB--layout=reverse\fB A synonym for \fB--layout=reverse\fB
.TP .TP
.B "--border" .BI "--border" [=STYLE]
Draw border above and below the finder Draw border around the finder
.br
.BR rounded " Border with rounded corners (default)"
.br
.BR sharp " Border with sharp corners"
.br
.BR horizontal " Horizontal lines above and below the finder"
.br
.BR vertical " Vertical lines on each side of the finder"
.br
.BR top
.br
.BR bottom
.br
.BR left
.br
.BR right
.br
.TP .TP
.B "--no-unicode" .B "--no-unicode"
@@ -211,6 +233,29 @@ e.g.
\fBfzf --margin 10% \fBfzf --margin 10%
fzf --margin 1,5%\fR fzf --margin 1,5%\fR
.RE .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 .TP
.BI "--info=" "STYLE" .BI "--info=" "STYLE"
Determines the display style of finder info. Determines the display style of finder info.
@@ -231,6 +276,12 @@ A synonym for \fB--info=hidden\fB
.BI "--prompt=" "STR" .BI "--prompt=" "STR"
Input prompt (default: '> ') Input prompt (default: '> ')
.TP .TP
.BI "--pointer=" "STR"
Pointer to the current line (default: '>')
.TP
.BI "--marker=" "STR"
Multi-select marker (default: '>')
.TP
.BI "--header=" "STR" .BI "--header=" "STR"
The given string will be printed as the sticky header. The lines are displayed The given string will be printed as the sticky header. The lines are displayed
in the given order from top to bottom regardless of \fB--layout\fR option, and in the given order from top to bottom regardless of \fB--layout\fR option, and
@@ -249,11 +300,9 @@ Enable processing of ANSI color codes
.BI "--tabstop=" SPACES .BI "--tabstop=" SPACES
Number of spaces for a tab character (default: 8) Number of spaces for a tab character (default: 8)
.TP .TP
.BI "--color=" "[BASE_SCHEME][,COLOR:ANSI]" .BI "--color=" "[BASE_SCHEME][,COLOR_NAME[:ANSI_COLOR][:ANSI_ATTRIBUTES]]..."
Color configuration. The name of the base color scheme is followed by custom Color configuration. The name of the base color scheme is followed by custom
color mappings. Ansi color code of -1 denotes terminal default color mappings.
foreground/background color. You can also specify 24-bit color in \fB#rrggbb\fR
format.
.RS .RS
.B BASE SCHEME: .B BASE SCHEME:
@@ -264,7 +313,7 @@ format.
\fB16 \fRColor scheme for 16-color terminal \fB16 \fRColor scheme for 16-color terminal
\fBbw \fRNo colors (equivalent to \fB--no-color\fR) \fBbw \fRNo colors (equivalent to \fB--no-color\fR)
.B COLOR: .B COLOR NAMES:
\fBfg \fRText \fBfg \fRText
\fBbg \fRBackground \fBbg \fRBackground
\fBpreview-fg \fRPreview window text \fBpreview-fg \fRPreview window text
@@ -274,14 +323,29 @@ format.
\fBbg+ \fRBackground (current line) \fBbg+ \fRBackground (current line)
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR) \fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
\fBhl+ \fRHighlighted substrings (current line) \fBhl+ \fRHighlighted substrings (current line)
\fBinfo \fRInfo \fBinfo \fRInfo line (match counters)
\fBborder \fRBorder of the preview window and horizontal separators (\fB--border\fR) \fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
\fBprompt \fRPrompt \fBprompt \fRPrompt
\fBpointer \fRPointer to the current line \fBpointer \fRPointer to the current line
\fBmarker \fRMulti-select marker \fBmarker \fRMulti-select marker
\fBspinner \fRStreaming input indicator \fBspinner \fRStreaming input indicator
\fBheader \fRHeader \fBheader \fRHeader
.B ANSI COLORS:
\fB-1 \fRDefault terminal foreground/background color
\fB \fR(or the original color of the text)
\fB0 ~ 15 \fR16 base colors
\fB16 ~ 255 \fRANSI 256 colors
\fB#rrggbb \fR24-bit colors
.B ANSI ATTRIBUTES: (Only applies to foreground colors)
\fBregular \fRClears previously set attributes; should precede the other ones
\fBbold\fR
\fBunderline\fR
\fBreverse\fR
\fBdim\fR
\fBitalic\fR
.B EXAMPLES: .B EXAMPLES:
\fB# Seoul256 theme with 8-bit colors \fB# Seoul256 theme with 8-bit colors
@@ -361,16 +425,21 @@ Note that you can escape a placeholder pattern by prepending a backslash.
Preview window will be updated even when there is no match for the current Preview window will be updated even when there is no match for the current
query if any of the placeholder expressions evaluates to a non-empty string. query if any of the placeholder expressions evaluates to a non-empty string.
Since 0.24.0, fzf can render partial preview content before the preview command
completes. ANSI escape sequence for clearing the display (\fBCSI 2 J\fR) is
supported, so you can use it to implement preview window that is constantly
updating.
e.g.
\fBfzf --preview 'for i in $(seq 100000); do
(( i % 200 == 0 )) && printf "\\033[2J"
echo "$i"
sleep 0.01
done'\fR
.RE .RE
.TP .TP
.BI "--preview-window=" "[POSITION][:SIZE[%]][:noborder][:wrap][:hidden]" .BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]cycle][:[no]hidden][:+SCROLL[-OFFSET]][:default]"
Determines the layout of the preview window. If the argument contains
\fB:hidden\fR, the preview window will be hidden by default until
\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
Line wrap can be enabled with \fB:wrap\fR flag.
If size is given as 0, preview window will not be visible, but fzf will still
execute the command in the background.
.RS .RS
.B POSITION: (default: right) .B POSITION: (default: right)
@@ -378,13 +447,48 @@ execute the command in the background.
\fBdown \fBdown
\fBleft \fBleft
\fBright \fBright
.RE
\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.
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
\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.
\fBdefault\fR resets all options previously set to the default.
.RS .RS
e.g. e.g.
\fBfzf --preview="head {}" --preview-window=up:30% \fB# Non-default scroll window positions and sizes
fzf --preview="file {}" --preview-window=down:1\fR fzf --preview="head {}" --preview-window=up:30%
fzf --preview="file {}" --preview-window=down:1
# 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
# Preview with bat, matching line in the middle of the window (-/2)
git grep --line-number '' |
fzf --delimiter : \\
--preview 'bat --style=numbers --color=always --highlight-line {2} {1}' \\
--preview-window +{2}-/2\fR
.RE .RE
.SS Scripting .SS Scripting
.TP .TP
.BI "-q, --query=" "STR" .BI "-q, --query=" "STR"
@@ -594,6 +698,8 @@ e.g.
.br .br
\fIend\fR \fIend\fR
.br .br
\fIinsert\fR
.br
\fIpgup\fR (\fIpage-up\fR) \fIpgup\fR (\fIpage-up\fR)
.br .br
\fIpgdn\fR (\fIpage-down\fR) \fIpgdn\fR (\fIpage-down\fR)
@@ -615,72 +721,87 @@ e.g.
or any single character or any single character
.SS AVAILABLE EVENTS: .SS AVAILABLE EVENTS:
\fIchange\fR (triggered whenever the query string is changed) \fIchange\fR
.br .RS
Triggered whenever the query string is changed
e.g. e.g.
\fB# Moves cursor to the top (or bottom depending on --layout) whenever the query is changed \fB# Moves cursor to the top (or bottom depending on --layout) whenever the query is changed
fzf --bind change:top\fR fzf --bind change:top\fR
.RE
\fIbackward-eof\fR
.RS
Triggered when the query string is already empty and you try to delete it
backward.
e.g.
\fBfzf --bind backward-eof:abort\fR
.RE
.SS AVAILABLE ACTIONS: .SS AVAILABLE ACTIONS:
A key or an event can be bound to one or more of the following actions. A key or an event can be bound to one or more of the following actions.
\fBACTION: DEFAULT BINDINGS (NOTES): \fBACTION: DEFAULT BINDINGS (NOTES):
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR \fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
\fBaccept\fR \fIenter double-click\fR \fBaccept\fR \fIenter double-click\fR
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection) \fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
\fBbackward-char\fR \fIctrl-b left\fR \fBbackward-char\fR \fIctrl-b left\fR
\fBbackward-delete-char\fR \fIctrl-h bspace\fR \fBbackward-delete-char\fR \fIctrl-h bspace\fR
\fBbackward-kill-word\fR \fIalt-bs\fR \fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
\fBbackward-word\fR \fIalt-b shift-left\fR \fBbackward-kill-word\fR \fIalt-bs\fR
\fBbeginning-of-line\fR \fIctrl-a home\fR \fBbackward-word\fR \fIalt-b shift-left\fR
\fBcancel\fR (clear query string if not empty, abort fzf otherwise) \fBbeginning-of-line\fR \fIctrl-a home\fR
\fBclear-screen\fR \fIctrl-l\fR \fBcancel\fR (clear query string if not empty, abort fzf otherwise)
\fBclear-selection\fR (clear multi-selection) \fBclear-screen\fR \fIctrl-l\fR
\fBclear-query\fR (clear query string) \fBclear-selection\fR (clear multi-selection)
\fBdelete-char\fR \fIdel\fR \fBclear-query\fR (clear query string)
\fBdelete-char/eof\fR \fIctrl-d\fR \fBdelete-char\fR \fIdel\fR
\fBdeselect-all\fR (deselect all matches) \fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
\fBdown\fR \fIctrl-j ctrl-n down\fR \fBdeselect-all\fR (deselect all matches)
\fBend-of-line\fR \fIctrl-e end\fR \fBdown\fR \fIctrl-j ctrl-n down\fR
\fBexecute(...)\fR (see below for the details) \fBend-of-line\fR \fIctrl-e end\fR
\fBexecute-silent(...)\fR (see below for the details) \fBexecute(...)\fR (see below for the details)
\fRexecute-multi(...)\fR (deprecated in favor of \fB{+}\fR expression) \fBexecute-silent(...)\fR (see below for the details)
\fBforward-char\fR \fIctrl-f right\fR \fBforward-char\fR \fIctrl-f right\fR
\fBforward-word\fR \fIalt-f shift-right\fR \fBforward-word\fR \fIalt-f shift-right\fR
\fBignore\fR \fBignore\fR
\fBjump\fR (EasyMotion-like 2-keystroke movement) \fBjump\fR (EasyMotion-like 2-keystroke movement)
\fBjump-accept\fR (jump and accept) \fBjump-accept\fR (jump and accept)
\fBkill-line\fR \fBkill-line\fR
\fBkill-word\fR \fIalt-d\fR \fBkill-word\fR \fIalt-d\fR
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR) \fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
\fBpage-down\fR \fIpgdn\fR \fBpage-down\fR \fIpgdn\fR
\fBpage-up\fR \fIpgup\fR \fBpage-up\fR \fIpgup\fR
\fBhalf-page-down\fR \fBhalf-page-down\fR
\fBhalf-page-up\fR \fBhalf-page-up\fR
\fBpreview-down\fR \fIshift-down\fR \fBpreview(...)\fR (see below for the details)
\fBpreview-up\fR \fIshift-up\fR \fBpreview-down\fR \fIshift-down\fR
\fBpreview-up\fR \fIshift-up\fR
\fBpreview-page-down\fR \fBpreview-page-down\fR
\fBpreview-page-up\fR \fBpreview-page-up\fR
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR) \fBpreview-half-page-down\fR
\fBprint-query\fR (print query and exit) \fBpreview-half-page-up\fR
\fBreload(...)\fR (see below for the details) \fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
\fBreplace-query\fR (replace query string with the current selection) \fBprint-query\fR (print query and exit)
\fBselect-all\fR (select all matches) \fBrefresh-preview\fR
\fBtoggle\fR (\fIright-click\fR) \fBreload(...)\fR (see below for the details)
\fBtoggle-all\fR (toggle all matches) \fBreplace-query\fR (replace query string with the current selection)
\fBtoggle+down\fR \fIctrl-i (tab)\fR \fBselect-all\fR (select all matches)
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR) \fBtoggle\fR (\fIright-click\fR)
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR) \fBtoggle-all\fR (toggle all matches)
\fBtoggle+down\fR \fIctrl-i (tab)\fR
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
\fBtoggle-preview\fR \fBtoggle-preview\fR
\fBtoggle-preview-wrap\fR \fBtoggle-preview-wrap\fR
\fBtoggle-sort\fR \fBtoggle-sort\fR
\fBtoggle+up\fR \fIbtab (shift-tab)\fR \fBtoggle+up\fR \fIbtab (shift-tab)\fR
\fBtop\fR (move to the top result) \fBtop\fR (move to the top result)
\fBunix-line-discard\fR \fIctrl-u\fR \fBunix-line-discard\fR \fIctrl-u\fR
\fBunix-word-rubout\fR \fIctrl-w\fR \fBunix-word-rubout\fR \fIctrl-w\fR
\fBup\fR \fIctrl-k ctrl-p up\fR \fBup\fR \fIctrl-k ctrl-p up\fR
\fByank\fR \fIctrl-y\fR \fByank\fR \fIctrl-y\fR
.SS ACTION COMPOSITION .SS ACTION COMPOSITION
@@ -750,6 +871,24 @@ e.g.
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\ fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
--ansi --phony --query "$INITIAL_QUERY"\fR --ansi --phony --query "$INITIAL_QUERY"\fR
.SS PREVIEW BINDING
With \fBpreview(...)\fR action, you can specify multiple different preview
commands in addition to the default preview command given by \fB--preview\fR
option.
e.g.
# Default preview command with an extra preview binding
fzf --preview 'file {}' --bind '?:preview:cat {}'
# A preview binding with no default preview command
# (Preview window is initially empty)
fzf --bind '?:preview:cat {}'
# Preview window hidden by default, it appears when you first hit '?'
fzf --bind '?:preview:cat {}' --preview-window hidden
.SH AUTHOR .SH AUTHOR
Junegunn Choi (\fIjunegunn.c@gmail.com\fR) Junegunn Choi (\fIjunegunn.c@gmail.com\fR)

View File

@@ -49,8 +49,13 @@ if s:is_win
" Use utf-8 for fzf.vim commands " Use utf-8 for fzf.vim commands
" Return array of shell commands for cmd.exe " Return array of shell commands for cmd.exe
let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
function! s:enc_to_cp(str) function! s:enc_to_cp(str)
if !has('iconv')
return a:str
endif
if !exists('s:codepage')
let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
endif
return iconv(a:str, &encoding, 'cp'.s:codepage) return iconv(a:str, &encoding, 'cp'.s:codepage)
endfunction endfunction
function! s:wrap_cmds(cmds) function! s:wrap_cmds(cmds)
@@ -110,45 +115,98 @@ function! s:fzf_tempname()
return s:fzf_call('tempname') return s:fzf_call('tempname')
endfunction endfunction
let s:default_layout = { 'down': '~40%' } let s:layout_keys = ['window', 'tmux', 'up', 'down', 'left', 'right']
let s:layout_keys = ['window', 'up', 'down', 'left', 'right']
let s:fzf_go = s:base_dir.'/bin/fzf' let s:fzf_go = s:base_dir.'/bin/fzf'
let s:fzf_tmux = s:base_dir.'/bin/fzf-tmux' let s:fzf_tmux = s:base_dir.'/bin/fzf-tmux'
let s:install = s:base_dir.'/install'
let s:installed = 0
let s:cpo_save = &cpo let s:cpo_save = &cpo
set cpo&vim set cpo&vim
function! s:fzf_exec() function! s:popup_support()
return has('nvim') ? has('nvim-0.4') : has('popupwin') && has('patch-8.2.191')
endfunction
function! s:default_layout()
return s:popup_support()
\ ? { 'window' : { 'width': 0.9, 'height': 0.6 } }
\ : { 'down': '~40%' }
endfunction
function! fzf#install()
if s:is_win && !has('win32unix')
let script = s:base_dir.'/install.ps1'
if !filereadable(script)
throw script.' not found'
endif
let script = 'powershell -ExecutionPolicy Bypass -file ' . script
else
let script = s:base_dir.'/install'
if !executable(script)
throw script.' not found'
endif
let script .= ' --bin'
endif
call s:warn('Running fzf installer ...')
call system(script)
if v:shell_error
throw 'Failed to download fzf: '.script
endif
endfunction
function! s:version_requirement(val, min)
let val = split(a:val, '\.')
let min = split(a:min, '\.')
for idx in range(0, len(min) - 1)
let v = get(val, idx, 0)
if v < min[idx] | return 0
elseif v > min[idx] | return 1
endif
endfor
return 1
endfunction
let s:checked = {}
function! fzf#exec(...)
if !exists('s:exec') if !exists('s:exec')
if executable(s:fzf_go) if executable(s:fzf_go)
let s:exec = s:fzf_go let s:exec = s:fzf_go
elseif executable('fzf') elseif executable('fzf')
let s:exec = 'fzf' let s:exec = 'fzf'
elseif s:is_win && !has('win32unix') elseif input('fzf executable not found. Download binary? (y/n) ') =~? '^y'
call s:warn('fzf executable not found.')
call s:warn('Download fzf binary for Windows from https://github.com/junegunn/fzf-bin/releases/')
call s:warn('and place it as '.s:base_dir.'\bin\fzf.exe')
throw 'fzf executable not found'
elseif !s:installed && executable(s:install) &&
\ input('fzf executable not found. Download binary? (y/n) ') =~? '^y'
redraw redraw
echo call fzf#install()
call s:warn('Downloading fzf binary. Please wait ...') return fzf#exec()
let s:installed = 1
call system(s:install.' --bin')
return s:fzf_exec()
else else
redraw redraw
throw 'fzf executable not found' throw 'fzf executable not found'
endif endif
endif endif
return fzf#shellescape(s:exec)
if a:0 && !has_key(s:checked, a:1)
let command = s:exec . ' --version'
let output = systemlist(command)
if v:shell_error || empty(output)
throw printf('Failed to run "%s": %s', command, output)
endif
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
elseif a:0 < 2 && input(printf('You need fzf %s or above. Found: %s. Download binary? (y/n) ', a:1, fzf_version)) =~? '^y'
redraw
call fzf#install()
return fzf#exec(a:1, 1)
else
throw printf('You need to upgrade fzf (required: %s or above)', a:1)
endif
endif
return s:exec
endfunction endfunction
function! s:tmux_enabled() function! s:tmux_enabled()
if has('gui_running') if has('gui_running') || !exists('$TMUX')
return 0 return 0
endif endif
@@ -157,10 +215,16 @@ function! s:tmux_enabled()
endif endif
let s:tmux = 0 let s:tmux = 0
if exists('$TMUX') && executable(s:fzf_tmux) if !executable(s:fzf_tmux)
let output = system('tmux -V') if executable('fzf-tmux')
let s:tmux = !v:shell_error && output >= 'tmux 1.7' let s:fzf_tmux = 'fzf-tmux'
else
return 0
endif
endif endif
let output = system('tmux -V')
let s:tmux = !v:shell_error && output >= 'tmux 1.7'
return s:tmux return s:tmux
endfunction endfunction
@@ -169,21 +233,6 @@ function! s:escape(path)
return s:is_win ? escape(path, '$') : path return s:is_win ? escape(path, '$') : path
endfunction endfunction
" Upgrade legacy options
function! s:upgrade(dict)
let copy = copy(a:dict)
if has_key(copy, 'tmux')
let copy.down = remove(copy, 'tmux')
endif
if has_key(copy, 'tmux_height')
let copy.down = remove(copy, 'tmux_height')
endif
if has_key(copy, 'tmux_width')
let copy.right = remove(copy, 'tmux_width')
endif
return copy
endfunction
function! s:error(msg) function! s:error(msg)
echohl ErrorMsg echohl ErrorMsg
echom a:msg echom a:msg
@@ -229,9 +278,14 @@ function! s:common_sink(action, lines) abort
endif endif
try try
let empty = empty(s:fzf_expand('%')) && line('$') == 1 && empty(getline(1)) && !&modified let empty = empty(s:fzf_expand('%')) && line('$') == 1 && empty(getline(1)) && !&modified
let autochdir = &autochdir " Preserve the current working directory in case it's changed during
set noautochdir " the execution (e.g. `set autochdir` or `autocmd BufEnter * lcd ...`)
let cwd = exists('w:fzf_pushd') ? w:fzf_pushd.dir : expand('%:p:h')
for item in a:lines for item in a:lines
if item[0] != '~' && item !~ (s:is_win ? '^[A-Z]:\' : '^/')
let sep = s:is_win ? '\' : '/'
let item = join([cwd, item], cwd[len(cwd)-1] == sep ? '' : sep)
endif
if empty if empty
execute 'e' s:escape(item) execute 'e' s:escape(item)
let empty = 0 let empty = 0
@@ -245,7 +299,6 @@ function! s:common_sink(action, lines) abort
endfor endfor
catch /^Vim:Interrupt$/ catch /^Vim:Interrupt$/
finally finally
let &autochdir = autochdir
silent! autocmd! fzf_swap silent! autocmd! fzf_swap
endtry endtry
endfunction endfunction
@@ -290,7 +343,7 @@ function! fzf#wrap(...)
let expects = map(copy(args), 'type(v:val)') let expects = map(copy(args), 'type(v:val)')
let tidx = 0 let tidx = 0
for arg in copy(a:000) for arg in copy(a:000)
let tidx = index(expects, type(arg), tidx) let tidx = index(expects, type(arg) == 6 ? type(0) : type(arg), tidx)
if tidx < 0 if tidx < 0
throw 'Invalid arguments (expected: [name string] [opts dict] [fullscreen boolean])' throw 'Invalid arguments (expected: [name string] [opts dict] [fullscreen boolean])'
endif endif
@@ -315,7 +368,7 @@ function! fzf#wrap(...)
if !exists('g:fzf_layout') && exists('g:fzf_height') if !exists('g:fzf_layout') && exists('g:fzf_height')
let opts.down = g:fzf_height let opts.down = g:fzf_height
else else
let opts = extend(opts, s:validate_layout(get(g:, 'fzf_layout', s:default_layout))) let opts = extend(opts, s:validate_layout(get(g:, 'fzf_layout', s:default_layout())))
endif endif
endif endif
@@ -362,11 +415,11 @@ function! fzf#run(...) abort
try try
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh() let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
let dict = exists('a:1') ? s:upgrade(a:1) : {} let dict = exists('a:1') ? copy(a:1) : {}
let temps = { 'result': s:fzf_tempname() } let temps = { 'result': s:fzf_tempname() }
let optstr = s:evaluate_opts(get(dict, 'options', '')) let optstr = s:evaluate_opts(get(dict, 'options', ''))
try try
let fzf_exec = s:fzf_exec() let fzf_exec = fzf#shellescape(fzf#exec())
catch catch
throw v:exception throw v:exception
endtry endtry
@@ -378,12 +431,6 @@ try
let dict.dir = fnamemodify(dict.dir, ':p') let dict.dir = fnamemodify(dict.dir, ':p')
endif endif
if !has_key(dict, 'source') && !empty($FZF_DEFAULT_COMMAND) && !s:is_win
let temps.source = s:fzf_tempname()
call writefile(s:wrap_cmds(split($FZF_DEFAULT_COMMAND, "\n")), temps.source)
let dict.source = (empty($SHELL) ? &shell : $SHELL).' '.fzf#shellescape(temps.source)
endif
if has_key(dict, 'source') if has_key(dict, 'source')
let source = dict.source let source = dict.source
let type = type(source) let type = type(source)
@@ -391,7 +438,7 @@ try
let prefix = '( '.source.' )|' let prefix = '( '.source.' )|'
elseif type == 3 elseif type == 3
let temps.input = s:fzf_tempname() let temps.input = s:fzf_tempname()
call writefile(map(source, '<SID>enc_to_cp(v:val)'), temps.input) call writefile(source, temps.input)
let prefix = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input).'|' let prefix = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input).'|'
else else
throw 'Invalid source type' throw 'Invalid source type'
@@ -400,7 +447,7 @@ try
let prefix = '' let prefix = ''
endif endif
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0) let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0) || has_key(dict, 'tmux')
let use_height = has_key(dict, 'down') && !has('gui_running') && let use_height = has_key(dict, 'down') && !has('gui_running') &&
\ !(has('nvim') || s:is_win || has('win32unix') || s:present(dict, 'up', 'left', 'right', 'window')) && \ !(has('nvim') || s:is_win || has('win32unix') || s:present(dict, 'up', 'left', 'right', 'window')) &&
\ executable('tput') && filereadable('/dev/tty') \ executable('tput') && filereadable('/dev/tty')
@@ -408,7 +455,7 @@ try
let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win
let use_term = has_nvim_term || let use_term = has_nvim_term ||
\ has_vim8_term && !has('win32unix') && (has('gui_running') || s:is_win || !use_height && s:present(dict, 'down', 'up', 'left', 'right', 'window')) \ has_vim8_term && !has('win32unix') && (has('gui_running') || s:is_win || !use_height && s:present(dict, 'down', 'up', 'left', 'right', 'window'))
let use_tmux = (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:tmux_enabled() && s:splittable(dict) let use_tmux = (has_key(dict, 'tmux') || (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:splittable(dict)) && s:tmux_enabled()
if prefer_tmux && use_tmux if prefer_tmux && use_tmux
let use_height = 0 let use_height = 0
let use_term = 0 let use_term = 0
@@ -419,6 +466,7 @@ try
elseif use_term elseif use_term
let optstr .= ' --no-height' let optstr .= ' --no-height'
endif endif
let optstr .= s:border_opt(get(dict, 'window', 0))
let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
if use_term if use_term
@@ -444,19 +492,21 @@ function! s:present(dict, ...)
endfunction endfunction
function! s:fzf_tmux(dict) function! s:fzf_tmux(dict)
let size = '' let size = get(a:dict, 'tmux', '')
for o in ['up', 'down', 'left', 'right'] if empty(size)
if s:present(a:dict, o) for o in ['up', 'down', 'left', 'right']
let spec = a:dict[o] if s:present(a:dict, o)
if (o == 'up' || o == 'down') && spec[0] == '~' let spec = a:dict[o]
let size = '-'.o[0].s:calc_size(&lines, spec, a:dict) if (o == 'up' || o == 'down') && spec[0] == '~'
else let size = '-'.o[0].s:calc_size(&lines, spec, a:dict)
" Legacy boolean option else
let size = '-'.o[0].(spec == 1 ? '' : substitute(spec, '^\~', '', '')) " Legacy boolean option
let size = '-'.o[0].(spec == 1 ? '' : substitute(spec, '^\~', '', ''))
endif
break
endif endif
break endfor
endif endif
endfor
return printf('LINES=%d COLUMNS=%d %s %s %s --', return printf('LINES=%d COLUMNS=%d %s %s %s --',
\ &lines, &columns, fzf#shellescape(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-')) \ &lines, &columns, fzf#shellescape(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-'))
endfunction endfunction
@@ -511,7 +561,7 @@ function! s:dopopd()
if s:fzf_getcwd() ==# w:fzf_pushd.dir && (!&autochdir || w:fzf_pushd.bufname ==# bufname('')) if s:fzf_getcwd() ==# w:fzf_pushd.dir && (!&autochdir || w:fzf_pushd.bufname ==# bufname(''))
execute w:fzf_pushd.command s:escape(w:fzf_pushd.origin) execute w:fzf_pushd.command s:escape(w:fzf_pushd.origin)
endif endif
unlet w:fzf_pushd unlet! w:fzf_pushd
endfunction endfunction
function! s:xterm_launcher() function! s:xterm_launcher()
@@ -626,14 +676,45 @@ function! s:calc_size(max, val, dict)
endif endif
let opts = $FZF_DEFAULT_OPTS.' '.s:evaluate_opts(get(a:dict, 'options', '')) let opts = $FZF_DEFAULT_OPTS.' '.s:evaluate_opts(get(a:dict, 'options', ''))
let margin = stridx(opts, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2 if opts =~ 'preview'
return size
endif
let margin = match(opts, '--inline-info\|--info[^-]\{-}inline') > match(opts, '--no-inline-info\|--info[^-]\{-}\(default\|hidden\)') ? 1 : 2
let margin += stridx(opts, '--border') > stridx(opts, '--no-border') ? 2 : 0 let margin += stridx(opts, '--border') > stridx(opts, '--no-border') ? 2 : 0
let margin += stridx(opts, '--header') > stridx(opts, '--no-header') if stridx(opts, '--header') > stridx(opts, '--no-header')
let margin += len(split(opts, "\n"))
endif
return srcsz >= 0 ? min([srcsz + margin, size]) : size return srcsz >= 0 ? min([srcsz + margin, size]) : size
endfunction endfunction
function! s:getpos() function! s:getpos()
return {'tab': tabpagenr(), 'win': winnr(), 'cnt': winnr('$'), 'tcnt': tabpagenr('$')} return {'tab': tabpagenr(), 'win': winnr(), 'winid': win_getid(), 'cnt': winnr('$'), 'tcnt': tabpagenr('$')}
endfunction
function! s:border_opt(window)
if type(a:window) != type({})
return ''
endif
" Border style
let style = tolower(get(a:window, 'border', 'rounded'))
if !has_key(a:window, 'border') && !get(a:window, 'rounded', 1)
let style = 'sharp'
endif
if style == 'none' || style == 'no'
return ''
endif
" For --border styles, we need fzf 0.24.0 or above
call fzf#exec('0.24.0')
let opt = ' --border=' . style
if has_key(a:window, 'highlight')
let color = s:get_color('fg', a:window.highlight)
if len(color)
let opt .= ' --color=border:' . color
endif
endif
return opt
endfunction endfunction
function! s:split(dict) function! s:split(dict)
@@ -643,9 +724,18 @@ function! s:split(dict)
\ 'left': ['vertical topleft', 'vertical resize', &columns], \ 'left': ['vertical topleft', 'vertical resize', &columns],
\ 'right': ['vertical botright', 'vertical resize', &columns] } \ 'right': ['vertical botright', 'vertical resize', &columns] }
let ppos = s:getpos() let ppos = s:getpos()
let is_popup = 0
try try
if s:present(a:dict, 'window') if s:present(a:dict, 'window')
execute 'keepalt' a:dict.window if type(a:dict.window) == type({})
if !s:popup_support()
throw 'Nvim 0.4+ or Vim 8.2.191+ with popupwin feature is required for pop-up window'
end
call s:popup(a:dict.window)
let is_popup = 1
else
execute 'keepalt' a:dict.window
endif
elseif !s:splittable(a:dict) elseif !s:splittable(a:dict)
execute (tabpagenr()-1).'tabnew' execute (tabpagenr()-1).'tabnew'
else else
@@ -660,20 +750,22 @@ function! s:split(dict)
endif endif
execute cmd sz.'new' execute cmd sz.'new'
execute resz sz execute resz sz
return [ppos, {}] return [ppos, {}, is_popup]
endif endif
endfor endfor
endif endif
return [ppos, { '&l:wfw': &l:wfw, '&l:wfh': &l:wfh }] return [ppos, is_popup ? {} : { '&l:wfw': &l:wfw, '&l:wfh': &l:wfh }, is_popup]
finally finally
setlocal winfixwidth winfixheight if !is_popup
setlocal winfixwidth winfixheight
endif
endtry endtry
endfunction endfunction
function! s:execute_term(dict, command, temps) abort function! s:execute_term(dict, command, temps) abort
let winrest = winrestcmd() let winrest = winrestcmd()
let pbuf = bufnr('') let pbuf = bufnr('')
let [ppos, winopts] = s:split(a:dict) let [ppos, winopts, is_popup] = s:split(a:dict)
call s:use_sh() call s:use_sh()
let b:fzf = a:dict let b:fzf = a:dict
let fzf = { 'buf': bufnr(''), 'pbuf': pbuf, 'ppos': ppos, 'dict': a:dict, 'temps': a:temps, let fzf = { 'buf': bufnr(''), 'pbuf': pbuf, 'ppos': ppos, 'dict': a:dict, 'temps': a:temps,
@@ -702,8 +794,8 @@ function! s:execute_term(dict, command, temps) abort
" there's no other listed buffer (nvim +'set nobuflisted') " there's no other listed buffer (nvim +'set nobuflisted')
close close
endif endif
execute 'tabnext' self.ppos.tab silent! execute 'tabnext' self.ppos.tab
execute self.ppos.win.'wincmd w' silent! execute self.ppos.win.'wincmd w'
endif endif
if bufexists(self.buf) if bufexists(self.buf)
@@ -737,8 +829,17 @@ function! s:execute_term(dict, command, temps) abort
if has('nvim') if has('nvim')
call termopen(command, fzf) call termopen(command, fzf)
else else
let fzf.buf = term_start([&shell, &shellcmdflag, command], {'curwin': 1, 'exit_cb': function(fzf.on_exit)}) let term_opts = {'exit_cb': function(fzf.on_exit)}
if !has('patch-8.0.1261') && !has('nvim') && !s:is_win 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 is_popup && exists('#TerminalWinOpen')
doautocmd <nomodeline> TerminalWinOpen
endif
if !has('patch-8.0.1261') && !s:is_win
call term_wait(fzf.buf, 20) call term_wait(fzf.buf, 20)
endif endif
endif endif
@@ -793,6 +894,52 @@ function! s:callback(dict, lines) abort
endif endif
endfunction endfunction
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 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,
\ maxheight: a:opts.height,
\ zindex: 1000,
\ })}
autocmd TerminalOpen * ++once call s:popup_create(str2nr(expand('<abuf>')))
endfunction
endif
function! s:popup(opts) abort
" Size and position
let width = min([max([8, a:opts.width > 1 ? a:opts.width : float2nr(&columns * a:opts.width)]), &columns])
let height = min([max([4, a:opts.height > 1 ? a:opts.height : float2nr(&lines * a:opts.height)]), &lines - has('nvim')])
let row = float2nr(get(a:opts, 'yoffset', 0.5) * (&lines - height))
let col = float2nr(get(a:opts, 'xoffset', 0.5) * (&columns - width))
" Managing the differences
let row = min([max([0, row]), &lines - has('nvim') - height])
let col = min([max([0, col]), &columns - width])
let row += !has('nvim')
let col += !has('nvim')
call s:create_popup('Normal', {
\ 'row': row, 'col': col, 'width': width, 'height': height
\ })
endfunction
let s:default_action = { let s:default_action = {
\ 'ctrl-t': 'tab split', \ 'ctrl-t': 'tab split',
\ 'ctrl-x': 'split', \ 'ctrl-x': 'split',

View File

@@ -2,10 +2,10 @@
# / __/___ / __/ # / __/___ / __/
# / /_/_ / / /_ # / /_/_ / / /_
# / __/ / /_/ __/ # / __/ / /_/ __/
# /_/ /___/_/-completion.bash # /_/ /___/_/ completion.bash
# #
# - $FZF_TMUX (default: 0) # - $FZF_TMUX (default: 0)
# - $FZF_TMUX_HEIGHT (default: '40%') # - $FZF_TMUX_OPTS (default: empty)
# - $FZF_COMPLETION_TRIGGER (default: '**') # - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty) # - $FZF_COMPLETION_OPTS (default: empty)
@@ -34,9 +34,16 @@ fi
# To redraw line after fzf closes (printf '\e[5n') # To redraw line after fzf closes (printf '\e[5n')
bind '"\e[0n": redraw-current-line' bind '"\e[0n": redraw-current-line'
__fzfcmd_complete() { __fzf_comprun() {
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ] && if [ "$(type -t _fzf_comprun 2>&1)" = function ]; then
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf" _fzf_comprun "$@"
elif [ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; }; then
shift
fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- "$@"
else
shift
fzf "$@"
fi
} }
__fzf_orig_completion_filter() { __fzf_orig_completion_filter() {
@@ -72,6 +79,8 @@ _fzf_opts_completion() {
--margin --margin
--inline-info --inline-info
--prompt --prompt
--pointer
--marker
--header --header
--header-lines --header-lines
--ansi --ansi
@@ -140,8 +149,7 @@ _fzf_handle_dynamic_completion() {
} }
__fzf_generic_path_completion() { __fzf_generic_path_completion() {
local cur base dir leftover matches trigger cmd fzf local cur base dir leftover matches trigger cmd
fzf="$(__fzfcmd_complete)"
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}" cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
COMPREPLY=() COMPREPLY=()
trigger=${FZF_COMPLETION_TRIGGER-'**'} trigger=${FZF_COMPLETION_TRIGGER-'**'}
@@ -157,7 +165,7 @@ __fzf_generic_path_completion() {
leftover=${leftover/#\/} leftover=${leftover/#\/}
[ -z "$dir" ] && dir='.' [ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && 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" $fzf $2 -q "$leftover" | while read -r item; do 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
printf "%q$3 " "$item" printf "%q$3 " "$item"
done) done)
matches=${matches% } matches=${matches% }
@@ -182,10 +190,30 @@ __fzf_generic_path_completion() {
} }
_fzf_complete() { _fzf_complete() {
local cur selected trigger cmd fzf post # Split arguments around --
local args rest str_arg i sep
args=("$@")
sep=
for i in "${!args[@]}"; do
if [[ "${args[$i]}" = -- ]]; then
sep=$i
break
fi
done
if [[ -n "$sep" ]]; then
str_arg=
rest=("${args[@]:$((sep + 1)):${#args[@]}}")
args=("${args[@]:0:$sep}")
else
str_arg=$1
args=()
shift
rest=("$@")
fi
local cur selected trigger cmd post
post="$(caller 0 | awk '{print $2}')_post" post="$(caller 0 | awk '{print $2}')_post"
type -t "$post" > /dev/null 2>&1 || post=cat type -t "$post" > /dev/null 2>&1 || post=cat
fzf="$(__fzfcmd_complete)"
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}" cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
trigger=${FZF_COMPLETION_TRIGGER-'**'} trigger=${FZF_COMPLETION_TRIGGER-'**'}
@@ -193,7 +221,7 @@ _fzf_complete() {
if [[ "$cur" == *"$trigger" ]]; then if [[ "$cur" == *"$trigger" ]]; then
cur=${cur:0:${#cur}-${#trigger}} cur=${cur:0:${#cur}-${#trigger}}
selected=$(cat | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" $fzf $1 -q "$cur" | $post | tr '\n' ' ') selected=$(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=${selected% } # Strip trailing space not to repeat "-o nospace" selected=${selected% } # Strip trailing space not to repeat "-o nospace"
if [ -n "$selected" ]; then if [ -n "$selected" ]; then
COMPREPLY=("$selected") COMPREPLY=("$selected")
@@ -203,8 +231,7 @@ _fzf_complete() {
printf '\e[5n' printf '\e[5n'
return 0 return 0
else else
shift _fzf_handle_dynamic_completion "$cmd" "${rest[@]}"
_fzf_handle_dynamic_completion "$cmd" "$@"
fi fi
} }
@@ -222,49 +249,44 @@ _fzf_dir_completion() {
} }
_fzf_complete_kill() { _fzf_complete_kill() {
[ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1 local trigger=${FZF_COMPLETION_TRIGGER-'**'}
local cur="${COMP_WORDS[COMP_CWORD]}"
local selected fzf if [[ -z "$cur" ]]; then
fzf="$(__fzfcmd_complete)" COMP_WORDS[$COMP_CWORD]=$trigger
selected=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" $fzf -m | awk '{print $2}' | tr '\n' ' ') elif [[ "$cur" != *"$trigger" ]]; then
printf '\e[5n' return 1
if [ -n "$selected" ]; then
COMPREPLY=( "$selected" )
return 0
fi fi
_fzf_proc_completion "$@"
} }
_fzf_complete_telnet() { _fzf_proc_completion() {
_fzf_complete '+m' "$@" < <( _fzf_complete -m --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <(
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' | command ps -ef | sed 1d
awk '{if (length($2) > 0) {print $2}}' | sort -u
) )
} }
_fzf_complete_ssh() { _fzf_proc_completion_post() {
_fzf_complete '+m' "$@" < <( awk '{print $2}'
cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \ }
_fzf_host_completion() {
_fzf_complete +m -- "$@" < <(
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \ <(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') | <(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u awk '{if (length($2) > 0) {print $2}}' | sort -u
) )
} }
_fzf_complete_unset() { _fzf_var_completion() {
_fzf_complete '-m' "$@" < <( _fzf_complete -m -- "$@" < <(
declare -xp | sed 's/=.*//' | sed 's/.* //' declare -xp | sed 's/=.*//' | sed 's/.* //'
) )
} }
_fzf_complete_export() { _fzf_alias_completion() {
_fzf_complete '-m' "$@" < <( _fzf_complete -m -- "$@" < <(
declare -xp | sed 's/=.*//' | sed 's/.* //'
)
}
_fzf_complete_unalias() {
_fzf_complete '-m' "$@" < <(
alias | sed 's/=.*//' | sed 's/.* //' alias | sed 's/=.*//' | sed 's/.* //'
) )
} }
@@ -282,11 +304,10 @@ a_cmds="
find git grep gunzip gzip hg jar find git grep gunzip gzip hg jar
ln ls mv open rm rsync scp ln ls mv open rm rsync scp
svn tar unzip zip" svn tar unzip zip"
x_cmds="kill ssh telnet unset unalias export"
# Preserve existing completion # Preserve existing completion
eval "$(complete | eval "$(complete |
sed -E '/-F/!d; / _fzf/d; '"/ ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g; s/+/\\+/g'))$/"'!d' | sed -E '/-F/!d; / _fzf/d; '"/ ($(echo $d_cmds $a_cmds | sed 's/ /|/g; s/+/\\+/g'))$/"'!d' |
__fzf_orig_completion_filter)" __fzf_orig_completion_filter)"
if type _completion_loader > /dev/null 2>&1; then if type _completion_loader > /dev/null 2>&1; then
@@ -318,36 +339,34 @@ for cmd in $d_cmds; do
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames" __fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
done done
# Kill completion # Kill completion (supports empty completion trigger)
complete -F _fzf_complete_kill -o nospace -o default -o bashdefault kill complete -F _fzf_complete_kill -o default -o bashdefault kill
# Host completion unset cmd d_cmds a_cmds
complete -F _fzf_complete_ssh -o default -o bashdefault ssh
complete -F _fzf_complete_telnet -o default -o bashdefault telnet
# Environment variables / Aliases
complete -F _fzf_complete_unset -o default -o bashdefault unset
complete -F _fzf_complete_export -o default -o bashdefault export
complete -F _fzf_complete_unalias -o default -o bashdefault unalias
unset cmd d_cmds a_cmds x_cmds
_fzf_setup_completion() { _fzf_setup_completion() {
local kind fn cmd local kind fn cmd
kind=$1 kind=$1
fn=_fzf_${1}_completion fn=_fzf_${1}_completion
if [[ $# -lt 2 ]] || ! type -t "$fn" > /dev/null; then if [[ $# -lt 2 ]] || ! type -t "$fn" > /dev/null; then
echo "usage: ${FUNCNAME[0]} path|dir COMMANDS..." echo "usage: ${FUNCNAME[0]} path|dir|var|alias|host|proc COMMANDS..."
return 1 return 1
fi fi
shift shift
eval "$(complete -p "$@" 2> /dev/null | grep -v "$fn" | __fzf_orig_completion_filter)"
for cmd in "$@"; do for cmd in "$@"; do
eval "$(complete -p "$cmd" 2> /dev/null | grep -v "$fn" | __fzf_orig_completion_filter)"
case "$kind" in case "$kind" in
dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;; dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;;
*) __fzf_defc "$cmd" "$fn" "-o default -o bashdefault" ;; var) __fzf_defc "$cmd" "$fn" "-o default -o nospace -v" ;;
alias) __fzf_defc "$cmd" "$fn" "-a" ;;
*) __fzf_defc "$cmd" "$fn" "-o default -o bashdefault" ;;
esac esac
done done
} }
# Environment variables / Aliases / Hosts
_fzf_setup_completion 'var' export unset
_fzf_setup_completion 'alias' unalias
_fzf_setup_completion 'host' ssh telnet
fi fi

View File

@@ -2,14 +2,79 @@
# / __/___ / __/ # / __/___ / __/
# / /_/_ / / /_ # / /_/_ / / /_
# / __/ / /_/ __/ # / __/ / /_/ __/
# /_/ /___/_/-completion.zsh # /_/ /___/_/ completion.zsh
# #
# - $FZF_TMUX (default: 0) # - $FZF_TMUX (default: 0)
# - $FZF_TMUX_HEIGHT (default: '40%') # - $FZF_TMUX_OPTS (default: '-d 40%')
# - $FZF_COMPLETION_TRIGGER (default: '**') # - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty) # - $FZF_COMPLETION_OPTS (default: empty)
if [[ $- =~ i ]]; then # Both branches of the following `if` do the same thing -- define
# __fzf_completion_options such that `eval $__fzf_completion_options` sets
# all options to the same values they currently have. We'll do just that at
# the bottom of the file after changing options to what we prefer.
#
# IMPORTANT: Until we get to the `emulate` line, all words that *can* be quoted
# *must* be quoted in order to prevent alias expansion. In addition, code must
# be written in a way works with any set of zsh options. This is very tricky, so
# careful when you change it.
#
# Start by loading the builtin zsh/parameter module. It provides `options`
# associative array that stores current shell options.
if 'zmodload' 'zsh/parameter' 2>'/dev/null' && (( ${+options} )); then
# This is the fast branch and it gets taken on virtually all Zsh installations.
#
# ${(kv)options[@]} expands to array of keys (option names) and values ("on"
# or "off"). The subsequent expansion# with (j: :) flag joins all elements
# together separated by spaces. __fzf_completion_options ends up with a value
# like this: "options=(shwordsplit off aliases on ...)".
__fzf_completion_options="options=(${(j: :)${(kv)options[@]}})"
else
# This branch is much slower because it forks to get the names of all
# zsh options. It's possible to eliminate this fork but it's not worth the
# trouble because this branch gets taken only on very ancient or broken
# zsh installations.
() {
# That `()` above defines an anonymous function. This is essentially a scope
# for local parameters. We use it to avoid polluting global scope.
'local' '__fzf_opt'
__fzf_completion_options="setopt"
# `set -o` prints one line for every zsh option. Each line contains option
# name, some spaces, and then either "on" or "off". We just want option names.
# Expansion with (@f) flag splits a string into lines. The outer expansion
# removes spaces and everything that follow them on every line. __fzf_opt
# ends up iterating over option names: shwordsplit, aliases, etc.
for __fzf_opt in "${(@)${(@f)$(set -o)}%% *}"; do
if [[ -o "$__fzf_opt" ]]; then
# Option $__fzf_opt is currently on, so remember to set it back on.
__fzf_completion_options+=" -o $__fzf_opt"
else
# Option $__fzf_opt is currently off, so remember to set it back off.
__fzf_completion_options+=" +o $__fzf_opt"
fi
done
# The value of __fzf_completion_options here looks like this:
# "setopt +o shwordsplit -o aliases ..."
}
fi
# Enable the default zsh options (those marked with <Z> in `man zshoptions`)
# but without `aliases`. Aliases in functions are expanded when functions are
# defined, so if we disable aliases here, we'll be sure to have no pesky
# aliases in any of our functions. This way we won't need prefix every
# command with `command` or to quote every word to defend against global
# aliases. Note that `aliases` is not the only option that's important to
# control. There are several others that could wreck havoc if they are set
# to values we don't expect. With the following `emulate` command we
# sidestep this issue entirely.
'emulate' 'zsh' '-o' 'no_aliases'
# This brace is the start of try-always block. The `always` part is like
# `finally` in lesser languages. We use it to *always* restore user options.
{
# Bail out if not interactive shell.
[[ -o interactive ]] || return 0
# To use custom commands instead of find, override _fzf_compgen_{path,dir} # To use custom commands instead of find, override _fzf_compgen_{path,dir}
if ! declare -f _fzf_compgen_path > /dev/null; then if ! declare -f _fzf_compgen_path > /dev/null; then
@@ -31,20 +96,45 @@ fi
########################################################### ###########################################################
__fzfcmd_complete() { __fzf_comprun() {
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ] && if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf" _fzf_comprun "$@"
elif [ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; }; then
shift
if [ -n "$FZF_TMUX_OPTS" ]; then
fzf-tmux ${(Q)${(Z+n+)FZF_TMUX_OPTS}} -- "$@"
else
fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%} -- "$@"
fi
else
shift
fzf "$@"
fi
}
# Extract the name of the command. e.g. foo=1 bar baz**<tab>
__fzf_extract_command() {
local token tokens
tokens=(${(z)1})
for token in $tokens; do
token=${(Q)token}
if [[ "$token" =~ [[:alnum:]] && ! "$token" =~ "=" ]]; then
echo "$token"
return
fi
done
echo "${tokens[1]}"
} }
__fzf_generic_path_completion() { __fzf_generic_path_completion() {
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches local base lbuf cmd compgen fzf_opts suffix tail dir leftover matches
base=$1 base=$1
lbuf=$2 lbuf=$2
cmd=$(__fzf_extract_command "$lbuf")
compgen=$3 compgen=$3
fzf_opts=$4 fzf_opts=$4
suffix=$5 suffix=$5
tail=$6 tail=$6
fzf="$(__fzfcmd_complete)"
setopt localoptions nonomatch setopt localoptions nonomatch
eval "base=$base" eval "base=$base"
@@ -55,7 +145,7 @@ __fzf_generic_path_completion() {
leftover=${leftover/#\/} leftover=${leftover/#\/}
[ -z "$dir" ] && dir='.' [ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}" [ "$dir" != "/" ] && dir="${dir/%\//}"
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" ${=fzf} ${=fzf_opts} -q "$leftover" | while read item; do matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
echo -n "${(q)item}$suffix " echo -n "${(q)item}$suffix "
done) done)
matches=${matches% } matches=${matches% }
@@ -87,17 +177,37 @@ _fzf_feed_fifo() (
) )
_fzf_complete() { _fzf_complete() {
local fifo fzf_opts lbuf fzf matches post setopt localoptions ksh_arrays
# Split arguments around --
local args rest str_arg i sep
args=("$@")
sep=
for i in {0..${#args[@]}}; do
if [[ "${args[$i]}" = -- ]]; then
sep=$i
break
fi
done
if [[ -n "$sep" ]]; then
str_arg=
rest=("${args[@]:$((sep + 1)):${#args[@]}}")
args=("${args[@]:0:$sep}")
else
str_arg=$1
args=()
shift
rest=("$@")
fi
local fifo lbuf cmd matches post
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$" fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
fzf_opts=$1 lbuf=${rest[0]}
lbuf=$2 cmd=$(__fzf_extract_command "$lbuf")
post="${funcstack[2]}_post" post="${funcstack[1]}_post"
type $post > /dev/null 2>&1 || post=cat type $post > /dev/null 2>&1 || post=cat
fzf="$(__fzfcmd_complete)"
_fzf_feed_fifo "$fifo" _fzf_feed_fifo "$fifo"
matches=$(cat "$fifo" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" ${=fzf} ${=fzf_opts} -q "${(Q)prefix}" | $post | tr '\n' ' ') 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' ' ')
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches" LBUFFER="$lbuf$matches"
fi fi
@@ -106,16 +216,16 @@ _fzf_complete() {
} }
_fzf_complete_telnet() { _fzf_complete_telnet() {
_fzf_complete '+m' "$@" < <( _fzf_complete +m -- "$@" < <(
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' | command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
awk '{if (length($2) > 0) {print $2}}' | sort -u awk '{if (length($2) > 0) {print $2}}' | sort -u
) )
} }
_fzf_complete_ssh() { _fzf_complete_ssh() {
_fzf_complete '+m' "$@" < <( _fzf_complete +m -- "$@" < <(
setopt localoptions nonomatch setopt localoptions nonomatch
command cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \ command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \ <(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') | <(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u awk '{if (length($2) > 0) {print $2}}' | sort -u
@@ -123,25 +233,35 @@ _fzf_complete_ssh() {
} }
_fzf_complete_export() { _fzf_complete_export() {
_fzf_complete '-m' "$@" < <( _fzf_complete -m -- "$@" < <(
declare -xp | sed 's/=.*//' | sed 's/.* //' declare -xp | sed 's/=.*//' | sed 's/.* //'
) )
} }
_fzf_complete_unset() { _fzf_complete_unset() {
_fzf_complete '-m' "$@" < <( _fzf_complete -m -- "$@" < <(
declare -xp | sed 's/=.*//' | sed 's/.* //' declare -xp | sed 's/=.*//' | sed 's/.* //'
) )
} }
_fzf_complete_unalias() { _fzf_complete_unalias() {
_fzf_complete '+m' "$@" < <( _fzf_complete +m -- "$@" < <(
alias | sed 's/=.*//' alias | sed 's/=.*//'
) )
} }
_fzf_complete_kill() {
_fzf_complete -m --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <(
command ps -ef | sed 1d
)
}
_fzf_complete_kill_post() {
awk '{print $2}'
}
fzf-completion() { fzf-completion() {
local tokens cmd prefix trigger tail fzf matches lbuf d_cmds local tokens cmd prefix trigger tail matches lbuf d_cmds
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
# http://zsh.sourceforge.net/FAQ/zshfaq03.html # http://zsh.sourceforge.net/FAQ/zshfaq03.html
@@ -152,7 +272,7 @@ fzf-completion() {
return return
fi fi
cmd=${tokens[1]} cmd=$(__fzf_extract_command "$LBUFFER")
# Explicitly allow for empty trigger. # Explicitly allow for empty trigger.
trigger=${FZF_COMPLETION_TRIGGER-'**'} trigger=${FZF_COMPLETION_TRIGGER-'**'}
@@ -164,24 +284,24 @@ fzf-completion() {
tokens=(${tokens[0,-2]}) tokens=(${tokens[0,-2]})
fi fi
lbuf=$LBUFFER
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))} tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
# Kill completion (do not require trigger sequence) # Kill completion (do not require trigger sequence)
if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then if [ "$cmd" = kill -a ${LBUFFER[-1]} = ' ' ]; then
fzf="$(__fzfcmd_complete)" tail=$trigger
matches=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" ${=fzf} -m | awk '{print $2}' | tr '\n' ' ') tokens+=$trigger
if [ -n "$matches" ]; then lbuf="$lbuf$trigger"
LBUFFER="$LBUFFER$matches" fi
fi
zle reset-prompt
# Trigger sequence given # Trigger sequence given
elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}) d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}} [ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
[ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}} [ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
if eval "type _fzf_complete_${cmd} > /dev/null"; then if eval "type _fzf_complete_${cmd} > /dev/null"; then
eval "prefix=\"$prefix\" _fzf_complete_${cmd} \"$lbuf\"" prefix="$prefix" eval _fzf_complete_${cmd} ${(q)lbuf}
elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then
_fzf_dir_completion "$prefix" "$lbuf" _fzf_dir_completion "$prefix" "$lbuf"
else else
@@ -202,4 +322,8 @@ fzf-completion() {
zle -N fzf-completion zle -N fzf-completion
bindkey '^I' fzf-completion bindkey '^I' fzf-completion
fi } always {
# Restore the original options.
eval $__fzf_completion_options
'unset' '__fzf_completion_options'
}

View File

@@ -1,3 +1,16 @@
# ____ ____
# / __/___ / __/
# / /_/_ / / /_
# / __/ / /_/ __/
# /_/ /___/_/ key-bindings.bash
#
# - $FZF_TMUX_OPTS
# - $FZF_CTRL_T_COMMAND
# - $FZF_CTRL_T_OPTS
# - $FZF_CTRL_R_OPTS
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
# Key bindings # Key bindings
# ------------ # ------------
__fzf_select__() { __fzf_select__() {
@@ -5,7 +18,7 @@ __fzf_select__() {
-o -type f -print \ -o -type f -print \
-o -type d -print \ -o -type d -print \
-o -type l -print 2> /dev/null | cut -b3-"}" -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" fzf -m "$@" | while read -r item; do 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
printf '%q ' "$item" printf '%q ' "$item"
done done
echo echo
@@ -13,35 +26,15 @@ __fzf_select__() {
if [[ $- =~ i ]]; then if [[ $- =~ i ]]; then
__fzf_use_tmux__() {
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ]
}
__fzfcmd() { __fzfcmd() {
__fzf_use_tmux__ && [ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; } &&
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf" echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
}
__fzf_select_tmux__() {
local height
height=${FZF_TMUX_HEIGHT:-40%}
if [[ $height =~ %$ ]]; then
height="-p ${height%\%}"
else
height="-l $height"
fi
tmux split-window $height "cd $(printf %q "$PWD"); FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS") PATH=$(printf %q "$PATH") FZF_CTRL_T_COMMAND=$(printf %q "$FZF_CTRL_T_COMMAND") FZF_CTRL_T_OPTS=$(printf %q "$FZF_CTRL_T_OPTS") bash -c 'source \"${BASH_SOURCE[0]}\"; RESULT=\"\$(__fzf_select__ --no-height)\"; tmux setb -b fzf \"\$RESULT\" \\; pasteb -b fzf -t $TMUX_PANE \\; deleteb -b fzf || tmux send-keys -t $TMUX_PANE \"\$RESULT\"'"
} }
fzf-file-widget() { fzf-file-widget() {
if __fzf_use_tmux__; then local selected="$(__fzf_select__)"
__fzf_select_tmux__ READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
else READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
local selected="$(__fzf_select__)"
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
fi
} }
__fzf_cd__() { __fzf_cd__() {
@@ -51,71 +44,53 @@ __fzf_cd__() {
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 $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
} }
__fzf_history__() ( __fzf_history__() {
local line local output
shopt -u nocaseglob nocasematch output=$(
line=$( builtin fc -lnr -2147483648 |
HISTTIMEFORMAT= builtin history | 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 --tac --sync -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" $(__fzfcmd) | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m --read0" $(__fzfcmd) --query "$READLINE_LINE"
command grep '^ *[0-9]') && ) || return
if [[ $- =~ H ]]; then READLINE_LINE=${output#*$'\t'}
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line" if [ -z "$READLINE_POINT" ]; then
else echo "$READLINE_LINE"
sed 's/^ *\([0-9]*\)\** *//' <<< "$line"
fi
)
if [[ ! -o vi ]]; then
# Required to refresh the prompt after fzf
bind '"\er": redraw-current-line'
bind '"\e^": history-expand-line'
# CTRL-T - Paste the selected file path into the command line
if [ $BASH_VERSINFO -gt 3 ]; then
bind -x '"\C-t": "fzf-file-widget"'
elif __fzf_use_tmux__; then
bind '"\C-t": " \C-u \C-a\C-k`__fzf_select_tmux__`\e\C-e\C-y\C-a\C-d\C-y\ey\C-h"'
else else
bind '"\C-t": " \C-u \C-a\C-k`__fzf_select__`\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er \C-h"' READLINE_POINT=0x7fffffff
fi fi
}
# Required to refresh the prompt after fzf
bind -m emacs-standard '"\er": redraw-current-line'
bind -m vi-command '"\C-z": emacs-editing-mode'
bind -m vi-insert '"\C-z": emacs-editing-mode'
bind -m emacs-standard '"\C-z": vi-editing-mode'
if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then
# CTRL-T - Paste the selected file path into the command line
bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__fzf_select__`\e\C-e\er\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"'
bind -m vi-command '"\C-t": "\C-z\C-t\C-z"'
bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"'
# CTRL-R - Paste the selected command from history into the command line # CTRL-R - Paste the selected command from history into the command line
bind '"\C-r": " \C-e\C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er\e^"' bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u"$(__fzf_history__)"\e\C-e\er"'
bind -m vi-command '"\C-r": "\C-z\C-r\C-z"'
# ALT-C - cd into the selected directory bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
bind '"\ec": " \C-e\C-u`__fzf_cd__`\e\C-e\er\C-m"'
else else
# We'd usually use "\e" to enter vi-movement-mode so we can do our magic,
# but this incurs a very noticeable delay of a half second or so,
# because many other commands start with "\e".
# Instead, we bind an unused key, "\C-x\C-a",
# to also enter vi-movement-mode,
# and then use that thereafter.
# (We imagine that "\C-x\C-a" is relatively unlikely to be in use.)
bind '"\C-x\C-a": vi-movement-mode'
bind '"\C-x\C-e": shell-expand-line'
bind '"\C-x\C-r": redraw-current-line'
bind '"\C-x^": history-expand-line'
# CTRL-T - Paste the selected file path into the command line # CTRL-T - Paste the selected file path into the command line
# - FIXME: Selected items are attached to the end regardless of cursor position bind -m emacs-standard -x '"\C-t": fzf-file-widget'
if [ $BASH_VERSINFO -gt 3 ]; then bind -m vi-command -x '"\C-t": fzf-file-widget'
bind -x '"\C-t": "fzf-file-widget"' bind -m vi-insert -x '"\C-t": fzf-file-widget'
elif __fzf_use_tmux__; then
bind '"\C-t": "\C-x\C-a$a \C-x\C-addi`__fzf_select_tmux__`\C-x\C-e\C-x\C-a0P$xa"'
else
bind '"\C-t": "\C-x\C-a$a \C-x\C-addi`__fzf_select__`\C-x\C-e\C-x\C-a0Px$a \C-x\C-r\C-x\C-axa "'
fi
bind -m vi-command '"\C-t": "i\C-t"'
# CTRL-R - Paste the selected command from history into the command line # CTRL-R - Paste the selected command from history into the command line
bind '"\C-r": "\C-x\C-addi`__fzf_history__`\C-x\C-e\C-x\C-r\C-x^\C-x\C-a$a"' bind -m emacs-standard -x '"\C-r": __fzf_history__'
bind -m vi-command '"\C-r": "i\C-r"' bind -m vi-command -x '"\C-r": __fzf_history__'
bind -m vi-insert -x '"\C-r": __fzf_history__'
# ALT-C - cd into the selected directory
bind '"\ec": "\C-x\C-addi`__fzf_cd__`\C-x\C-e\C-x\C-r\C-m"'
bind -m vi-command '"\ec": "ddi`__fzf_cd__`\C-x\C-e\C-x\C-r\C-m"'
fi fi
# ALT-C - cd into the selected directory
bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"'
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
fi fi

View File

@@ -1,3 +1,16 @@
# ____ ____
# / __/___ / __/
# / /_/_ / / /_
# / __/ / /_/ __/
# /_/ /___/_/ key-bindings.fish
#
# - $FZF_TMUX_OPTS
# - $FZF_CTRL_T_COMMAND
# - $FZF_CTRL_T_OPTS
# - $FZF_CTRL_R_OPTS
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
# Key bindings # Key bindings
# ------------ # ------------
function fzf_key_bindings function fzf_key_bindings
@@ -10,13 +23,13 @@ function fzf_key_bindings
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not # "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
# $dir itself, even if hidden. # $dir itself, even if hidden.
set -q FZF_CTRL_T_COMMAND; or set -l FZF_CTRL_T_COMMAND " test -n "$FZF_CTRL_T_COMMAND"; or set -l FZF_CTRL_T_COMMAND "
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \ command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
-o -type f -print \ -o -type f -print \
-o -type d -print \ -o -type d -print \
-o -type l -print 2> /dev/null | sed 's@^\./@@'" -o -type l -print 2> /dev/null | sed 's@^\./@@'"
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin 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 $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 eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
@@ -36,7 +49,7 @@ function fzf_key_bindings
end end
function fzf-history-widget -d "Show command history" function fzf-history-widget -d "Show command history"
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
@@ -62,10 +75,10 @@ function fzf_key_bindings
set -l dir $commandline[1] set -l dir $commandline[1]
set -l fzf_query $commandline[2] set -l fzf_query $commandline[2]
set -q FZF_ALT_C_COMMAND; or set -l FZF_ALT_C_COMMAND " 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 \ 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@^\./@@'" -o -type d -print 2> /dev/null | sed 's@^\./@@'"
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin 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 $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
@@ -82,10 +95,12 @@ function fzf_key_bindings
end end
function __fzfcmd function __fzfcmd
set -q FZF_TMUX; or set FZF_TMUX 0 test -n "$FZF_TMUX"; or set FZF_TMUX 0
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
if [ $FZF_TMUX -eq 1 ] if [ -n "$FZF_TMUX_OPTS" ]
echo "fzf-tmux -d$FZF_TMUX_HEIGHT" echo "fzf-tmux $FZF_TMUX_OPTS -- "
else if [ $FZF_TMUX -eq 1 ]
echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
else else
echo "fzf" echo "fzf"
end end
@@ -112,12 +127,12 @@ function fzf_key_bindings
else else
set dir (__fzf_get_dir $commandline) set dir (__fzf_get_dir $commandline)
if [ "$dir" = "." -a (string sub -l 1 $commandline) != '.' ] if [ "$dir" = "." -a (string sub -l 1 -- $commandline) != '.' ]
# if $dir is "." but commandline is not a relative path, this means no file path found # if $dir is "." but commandline is not a relative path, this means no file path found
set fzf_query $commandline set fzf_query $commandline
else else
# Also remove trailing slash after dir, to "split" input properly # Also remove trailing slash after dir, to "split" input properly
set fzf_query (string replace -r "^$dir/?" '' "$commandline") set fzf_query (string replace -r "^$dir/?" -- '' "$commandline")
end end
end end
@@ -129,15 +144,15 @@ function fzf_key_bindings
set dir $argv set dir $argv
# Strip all trailing slashes. Ignore if $dir is root dir (/) # Strip all trailing slashes. Ignore if $dir is root dir (/)
if [ (string length $dir) -gt 1 ] if [ (string length -- $dir) -gt 1 ]
set dir (string replace -r '/*$' '' $dir) set dir (string replace -r '/*$' -- '' $dir)
end end
# Iteratively check if dir exists and strip tail end of path # Iteratively check if dir exists and strip tail end of path
while [ ! -d "$dir" ] while [ ! -d "$dir" ]
# If path is absolute, this can keep going until ends up at / # If path is absolute, this can keep going until ends up at /
# If path is relative, this can keep going until entire input is consumed, dirname returns "." # If path is relative, this can keep going until entire input is consumed, dirname returns "."
set dir (dirname "$dir") set dir (dirname -- "$dir")
end end
echo $dir echo $dir

View File

@@ -1,6 +1,42 @@
# ____ ____
# / __/___ / __/
# / /_/_ / / /_
# / __/ / /_/ __/
# /_/ /___/_/ key-bindings.zsh
#
# - $FZF_TMUX_OPTS
# - $FZF_CTRL_T_COMMAND
# - $FZF_CTRL_T_OPTS
# - $FZF_CTRL_R_OPTS
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
# Key bindings # Key bindings
# ------------ # ------------
if [[ $- == *i* ]]; then
# The code at the top and the bottom of this file is the same as in completion.zsh.
# Refer to that file for explanation.
if 'zmodload' 'zsh/parameter' 2>'/dev/null' && (( ${+options} )); then
__fzf_key_bindings_options="options=(${(j: :)${(kv)options[@]}})"
else
() {
__fzf_key_bindings_options="setopt"
'local' '__fzf_opt'
for __fzf_opt in "${(@)${(@f)$(set -o)}%% *}"; do
if [[ -o "$__fzf_opt" ]]; then
__fzf_key_bindings_options+=" -o $__fzf_opt"
else
__fzf_key_bindings_options+=" +o $__fzf_opt"
fi
done
}
fi
'emulate' 'zsh' '-o' 'no_aliases'
{
[[ -o interactive ]] || return 0
# CTRL-T - Paste the selected file path(s) into the command line # CTRL-T - Paste the selected file path(s) into the command line
__fsel() { __fsel() {
@@ -17,13 +53,9 @@ __fsel() {
return $ret return $ret
} }
__fzf_use_tmux__() {
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ]
}
__fzfcmd() { __fzfcmd() {
__fzf_use_tmux__ && [ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; } &&
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf" echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
} }
fzf-file-widget() { fzf-file-widget() {
@@ -55,9 +87,15 @@ fzf-cd-widget() {
zle redisplay zle redisplay
return 0 return 0
fi fi
cd "$dir" if [ -z "$BUFFER" ]; then
unset dir # ensure this doesn't end up appearing in prompt expansion BUFFER="cd ${(q)dir}"
zle accept-line
else
print -sr "cd ${(q)dir}"
cd "$dir"
fi
local ret=$? local ret=$?
unset dir # ensure this doesn't end up appearing in prompt expansion
zle fzf-redraw-prompt zle fzf-redraw-prompt
return $ret return $ret
} }
@@ -68,7 +106,7 @@ bindkey '\ec' fzf-cd-widget
fzf-history-widget() { fzf-history-widget() {
local selected num local selected num
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
selected=( $(fc -rl 1 | 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 $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
local ret=$? local ret=$?
if [ -n "$selected" ]; then if [ -n "$selected" ]; then
@@ -83,4 +121,7 @@ fzf-history-widget() {
zle -N fzf-history-widget zle -N fzf-history-widget
bindkey '^R' fzf-history-widget bindkey '^R' fzf-history-widget
fi } always {
eval $__fzf_key_bindings_options
'unset' '__fzf_key_bindings_options'
}

View File

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

View File

@@ -773,12 +773,17 @@ func PrefixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Ch
return Result{0, 0, 0}, nil return Result{0, 0, 0}, nil
} }
if text.Length() < len(pattern) { trimmedLen := 0
if !unicode.IsSpace(pattern[0]) {
trimmedLen = text.LeadingWhitespaces()
}
if text.Length()-trimmedLen < len(pattern) {
return Result{-1, -1, 0}, nil return Result{-1, -1, 0}, nil
} }
for index, r := range pattern { for index, r := range pattern {
char := text.Get(index) char := text.Get(trimmedLen + index)
if !caseSensitive { if !caseSensitive {
char = unicode.ToLower(char) char = unicode.ToLower(char)
} }
@@ -790,14 +795,17 @@ func PrefixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Ch
} }
} }
lenPattern := len(pattern) lenPattern := len(pattern)
score, _ := calculateScore(caseSensitive, normalize, text, pattern, 0, lenPattern, false) score, _ := calculateScore(caseSensitive, normalize, text, pattern, trimmedLen, trimmedLen+lenPattern, false)
return Result{0, lenPattern, score}, nil return Result{trimmedLen, trimmedLen + lenPattern, score}, nil
} }
// SuffixMatch performs suffix-match // SuffixMatch performs suffix-match
func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) { func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
lenRunes := text.Length() lenRunes := text.Length()
trimmedLen := lenRunes - text.TrailingWhitespaces() trimmedLen := lenRunes
if len(pattern) == 0 || !unicode.IsSpace(pattern[len(pattern)-1]) {
trimmedLen -= text.TrailingWhitespaces()
}
if len(pattern) == 0 { if len(pattern) == 0 {
return Result{trimmedLen, trimmedLen, 0}, nil return Result{trimmedLen, trimmedLen, 0}, nil
} }
@@ -828,14 +836,30 @@ func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Ch
// EqualMatch performs equal-match // EqualMatch performs equal-match
func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) { func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
lenPattern := len(pattern) lenPattern := len(pattern)
if text.Length() != lenPattern { if lenPattern == 0 {
return Result{-1, -1, 0}, nil
}
// Strip leading whitespaces
trimmedLen := 0
if !unicode.IsSpace(pattern[0]) {
trimmedLen = text.LeadingWhitespaces()
}
// Strip trailing whitespaces
trimmedEndLen := 0
if !unicode.IsSpace(pattern[lenPattern-1]) {
trimmedEndLen = text.TrailingWhitespaces()
}
if text.Length()-trimmedLen-trimmedEndLen != lenPattern {
return Result{-1, -1, 0}, nil return Result{-1, -1, 0}, nil
} }
match := true match := true
if normalize { if normalize {
runes := text.ToRunes() runes := text.ToRunes()
for idx, pchar := range pattern { for idx, pchar := range pattern {
char := runes[idx] char := runes[trimmedLen+idx]
if !caseSensitive { if !caseSensitive {
char = unicode.To(unicode.LowerCase, char) char = unicode.To(unicode.LowerCase, char)
} }
@@ -845,14 +869,15 @@ func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Cha
} }
} }
} else { } else {
runesStr := text.ToString() runes := text.ToRunes()
runesStr := string(runes[trimmedLen : len(runes)-trimmedEndLen])
if !caseSensitive { if !caseSensitive {
runesStr = strings.ToLower(runesStr) runesStr = strings.ToLower(runesStr)
} }
match = runesStr == string(pattern) match = runesStr == string(pattern)
} }
if match { if match {
return Result{0, lenPattern, (scoreMatch+bonusBoundary)*lenPattern + return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+bonusBoundary)*lenPattern +
(bonusFirstCharMultiplier-1)*bonusBoundary}, nil (bonusFirstCharMultiplier-1)*bonusBoundary}, nil
} }
return Result{-1, -1, 0}, nil return Result{-1, -1, 0}, nil

View File

@@ -136,6 +136,10 @@ func TestPrefixMatch(t *testing.T) {
assertMatch(t, PrefixMatch, false, dir, "fooBarbaz", "Foo", 0, 3, score) assertMatch(t, PrefixMatch, false, dir, "fooBarbaz", "Foo", 0, 3, score)
assertMatch(t, PrefixMatch, false, dir, "foOBarBaZ", "foo", 0, 3, score) assertMatch(t, PrefixMatch, false, dir, "foOBarBaZ", "foo", 0, 3, score)
assertMatch(t, PrefixMatch, false, dir, "f-oBarbaz", "f-o", 0, 3, score) assertMatch(t, PrefixMatch, false, dir, "f-oBarbaz", "f-o", 0, 3, score)
assertMatch(t, PrefixMatch, false, dir, " fooBar", "foo", 1, 4, score)
assertMatch(t, PrefixMatch, false, dir, " fooBar", " fo", 0, 3, score)
assertMatch(t, PrefixMatch, false, dir, " fo", "foo", -1, -1, 0)
} }
} }
@@ -148,6 +152,13 @@ func TestSuffixMatch(t *testing.T) {
scoreMatch*3+bonusConsecutive*2) scoreMatch*3+bonusConsecutive*2)
assertMatch(t, SuffixMatch, false, dir, "fooBarBaZ", "baz", 6, 9, assertMatch(t, SuffixMatch, false, dir, "fooBarBaZ", "baz", 6, 9,
(scoreMatch+bonusCamel123)*3+bonusCamel123*(bonusFirstCharMultiplier-1)) (scoreMatch+bonusCamel123)*3+bonusCamel123*(bonusFirstCharMultiplier-1))
// Strip trailing white space from the string
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz", 6, 9,
scoreMatch*3+bonusConsecutive*2)
// Only when the pattern doesn't end with a space
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz ", 6, 10,
scoreMatch*4+bonusConsecutive*2+bonusNonWord)
} }
} }

View File

@@ -405,6 +405,74 @@ var normalized map[rune]rune = map[rune]rune{
0x024E: 'Y', // WITH STROKE, LATIN CAPITAL LETTER 0x024E: 'Y', // WITH STROKE, LATIN CAPITAL LETTER
0x028F: 'Y', // , LATIN LETTER SMALL CAPITAL 0x028F: 'Y', // , LATIN LETTER SMALL CAPITAL
0x1D22: 'Z', // , LATIN LETTER SMALL CAPITAL 0x1D22: 'Z', // , LATIN LETTER SMALL CAPITAL
'Ắ': 'A',
'Ấ': 'A',
'Ằ': 'A',
'Ầ': 'A',
'Ẳ': 'A',
'Ẩ': 'A',
'Ẵ': 'A',
'Ẫ': 'A',
'Ặ': 'A',
'Ậ': 'A',
'ắ': 'a',
'ấ': 'a',
'ằ': 'a',
'ầ': 'a',
'ẳ': 'a',
'ẩ': 'a',
'ẵ': 'a',
'ẫ': 'a',
'ặ': 'a',
'ậ': 'a',
'Ế': 'E',
'Ề': 'E',
'Ể': 'E',
'Ễ': 'E',
'Ệ': 'E',
'ế': 'e',
'ề': 'e',
'ể': 'e',
'ễ': 'e',
'ệ': 'e',
'Ố': 'O',
'Ớ': 'O',
'Ồ': 'O',
'Ờ': 'O',
'Ổ': 'O',
'Ở': 'O',
'Ỗ': 'O',
'Ỡ': 'O',
'Ộ': 'O',
'Ợ': 'O',
'ố': 'o',
'ớ': 'o',
'ồ': 'o',
'ờ': 'o',
'ổ': 'o',
'ở': 'o',
'ỗ': 'o',
'ỡ': 'o',
'ộ': 'o',
'ợ': 'o',
'Ứ': 'U',
'Ừ': 'U',
'Ử': 'U',
'Ữ': 'U',
'Ự': 'U',
'ứ': 'u',
'ừ': 'u',
'ử': 'u',
'ữ': 'u',
'ự': 'u',
} }
// NormalizeRunes normalizes latin script letters // NormalizeRunes normalizes latin script letters

View File

@@ -90,10 +90,11 @@ func init() {
- http://ascii-table.com/ansi-escape-sequences.php - http://ascii-table.com/ansi-escape-sequences.php
- http://ascii-table.com/ansi-escape-sequences-vt-100.php - http://ascii-table.com/ansi-escape-sequences-vt-100.php
- http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html - 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 // The following regular expression will include not all but most of the
// frequently used ANSI sequences // frequently used ANSI sequences
ansiRegex = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b.|[\x0e\x0f]|.\x08)") ansiRegex = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
} }
func findAnsiStart(str string) int { func findAnsiStart(str string) int {
@@ -243,6 +244,10 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
state.attr = state.attr | tui.Blink state.attr = state.attr | tui.Blink
case 7: case 7:
state.attr = state.attr | tui.Reverse state.attr = state.attr | tui.Reverse
case 23: // tput rmso
state.attr = state.attr &^ tui.Italic
case 24: // tput rmul
state.attr = state.attr &^ tui.Underline
case 0: case 0:
init() init()
default: default:

View File

@@ -9,9 +9,6 @@ import (
) )
const ( const (
// Current version
version = "0.20.0"
// Core // Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond coordinatorDelayMax time.Duration = 100 * time.Millisecond
coordinatorDelayStep time.Duration = 10 * time.Millisecond coordinatorDelayStep time.Duration = 10 * time.Millisecond
@@ -25,8 +22,10 @@ const (
// Terminal // Terminal
initialDelay = 20 * time.Millisecond initialDelay = 20 * time.Millisecond
initialDelayTac = 100 * time.Millisecond initialDelayTac = 100 * time.Millisecond
spinnerDuration = 200 * time.Millisecond spinnerDuration = 100 * time.Millisecond
previewCancelWait = 500 * time.Millisecond previewCancelWait = 500 * time.Millisecond
previewChunkDelay = 100 * time.Millisecond
previewDelayed = 500 * time.Millisecond
maxPatternLength = 300 maxPatternLength = 300
maxMulti = math.MaxInt32 maxMulti = math.MaxInt32
@@ -62,8 +61,6 @@ func init() {
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-` defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
} else if os.Getenv("TERM") == "cygwin" { } else if os.Getenv("TERM") == "cygwin" {
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"` defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
} else {
defaultCommand = `for /r %P in (*) do @(set "_curfile=%P" & set "_curfile=!_curfile:%__CD__%=!" & echo !_curfile!)`
} }
} }

View File

@@ -43,7 +43,7 @@ Matcher -> EvtHeader -> Terminal (update header)
*/ */
// Run starts fzf // Run starts fzf
func Run(opts *Options, revision string) { func Run(opts *Options, version string, revision string) {
sort := opts.Sort > 0 sort := opts.Sort > 0
sortCriteria = opts.Criteria sortCriteria = opts.Criteria
@@ -66,7 +66,7 @@ func Run(opts *Options, revision string) {
var lineAnsiState, prevLineAnsiState *ansiState var lineAnsiState, prevLineAnsiState *ansiState
if opts.Ansi { if opts.Ansi {
if opts.Theme != nil { if opts.Theme.Colored {
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) { ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
prevLineAnsiState = lineAnsiState prevLineAnsiState = lineAnsiState
trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil) trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
@@ -102,7 +102,7 @@ func Run(opts *Options, revision string) {
} else { } else {
chunkList = NewChunkList(func(item *Item, data []byte) bool { chunkList = NewChunkList(func(item *Item, data []byte) bool {
tokens := Tokenize(string(data), opts.Delimiter) tokens := Tokenize(string(data), opts.Delimiter)
if opts.Ansi && opts.Theme != nil && len(tokens) > 1 { if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
var ansiState *ansiState var ansiState *ansiState
if prevLineAnsiState != nil { if prevLineAnsiState != nil {
ansiStateDup := *prevLineAnsiState ansiStateDup := *prevLineAnsiState

View File

@@ -6,12 +6,13 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"unicode"
"unicode/utf8" "unicode/utf8"
"github.com/junegunn/fzf/src/algo" "github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/tui" "github.com/junegunn/fzf/src/tui"
"github.com/junegunn/fzf/src/util"
"github.com/mattn/go-runewidth"
"github.com/mattn/go-shellwords" "github.com/mattn/go-shellwords"
) )
@@ -43,6 +44,7 @@ const usage = `usage: fzf [options]
--no-mouse Disable mouse --no-mouse Disable mouse
--bind=KEYBINDS Custom key bindings. Refer to the man page. --bind=KEYBINDS Custom key bindings. Refer to the man page.
--cycle Enable cyclic scroll --cycle Enable cyclic scroll
--keep-right Keep the right end of the line visible on overflow
--no-hscroll Disable horizontal scroll --no-hscroll Disable horizontal scroll
--hscroll-off=COL Number of screen columns to keep to the right of the --hscroll-off=COL Number of screen columns to keep to the right of the
highlighted substring (default: 10) highlighted substring (default: 10)
@@ -55,10 +57,15 @@ const usage = `usage: fzf [options]
--min-height=HEIGHT Minimum height when --height is given in percent --min-height=HEIGHT Minimum height when --height is given in percent
(default: 10) (default: 10)
--layout=LAYOUT Choose layout: [default|reverse|reverse-list] --layout=LAYOUT Choose layout: [default|reverse|reverse-list]
--border Draw border above and below the finder --border[=STYLE] Draw border around the finder
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L) [rounded|sharp|horizontal|vertical|
top|bottom|left|right] (default: rounded)
--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] --info=STYLE Finder info style [default|inline|hidden]
--prompt=STR Input prompt (default: '> ') --prompt=STR Input prompt (default: '> ')
--pointer=STR Pointer to the current line (default: '>')
--marker=STR Multi-select marker (default: '>')
--header=STR String to print as header --header=STR String to print as header
--header-lines=N The first N lines of the input are treated as header --header-lines=N The first N lines of the input are treated as header
@@ -75,7 +82,11 @@ const usage = `usage: fzf [options]
Preview Preview
--preview=COMMAND Command to preview highlighted line ({}) --preview=COMMAND Command to preview highlighted line ({})
--preview-window=OPT Preview window layout (default: right:50%) --preview-window=OPT Preview window layout (default: right:50%)
[up|down|left|right][:SIZE[%]][:wrap][:hidden] [up|down|left|right][:SIZE[%]]
[:[no]wrap][:[no]cycle][:[no]hidden]
[:rounded|sharp|noborder]
[:+SCROLL[-OFFSET]]
[:default]
Scripting Scripting
-q, --query=STR Start the finder with the given query -q, --query=STR Start the finder with the given query
@@ -154,9 +165,11 @@ type previewOpts struct {
command string command string
position windowPosition position windowPosition
size sizeSpec size sizeSpec
scroll string
hidden bool hidden bool
wrap bool wrap bool
border bool cycle bool
border tui.BorderShape
} }
// Options stores the values of command-line options // Options stores the values of command-line options
@@ -183,12 +196,15 @@ type Options struct {
MinHeight int MinHeight int
Layout layoutType Layout layoutType
Cycle bool Cycle bool
KeepRight bool
Hscroll bool Hscroll bool
HscrollOff int HscrollOff int
FileWord bool FileWord bool
InfoStyle infoStyle InfoStyle infoStyle
JumpLabels string JumpLabels string
Prompt string Prompt string
Pointer string
Marker string
Query string Query string
Select1 bool Select1 bool
Exit0 bool Exit0 bool
@@ -206,13 +222,18 @@ type Options struct {
Header []string Header []string
HeaderLines int HeaderLines int
Margin [4]sizeSpec Margin [4]sizeSpec
Bordered bool Padding [4]sizeSpec
BorderShape tui.BorderShape
Unicode bool Unicode bool
Tabstop int Tabstop int
ClearOnExit bool ClearOnExit bool
Version bool Version bool
} }
func defaultPreviewOpts(command string) previewOpts {
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, tui.BorderRounded}
}
func defaultOptions() *Options { func defaultOptions() *Options {
return &Options{ return &Options{
Fuzzy: true, Fuzzy: true,
@@ -236,12 +257,15 @@ func defaultOptions() *Options {
MinHeight: 10, MinHeight: 10,
Layout: layoutDefault, Layout: layoutDefault,
Cycle: false, Cycle: false,
KeepRight: false,
Hscroll: true, Hscroll: true,
HscrollOff: 10, HscrollOff: 10,
FileWord: false, FileWord: false,
InfoStyle: infoDefault, InfoStyle: infoDefault,
JumpLabels: defaultJumpLabels, JumpLabels: defaultJumpLabels,
Prompt: "> ", Prompt: "> ",
Pointer: ">",
Marker: ">",
Query: "", Query: "",
Select1: false, Select1: false,
Exit0: false, Exit0: false,
@@ -249,7 +273,7 @@ func defaultOptions() *Options {
ToggleSort: false, ToggleSort: false,
Expect: make(map[int]string), Expect: make(map[int]string),
Keymap: make(map[int][]action), Keymap: make(map[int][]action),
Preview: previewOpts{"", posRight, sizeSpec{50, true}, false, false, true}, Preview: defaultPreviewOpts(""),
PrintQuery: false, PrintQuery: false,
ReadZero: false, ReadZero: false,
Printer: func(str string) { fmt.Println(str) }, Printer: func(str string) { fmt.Println(str) },
@@ -259,6 +283,7 @@ func defaultOptions() *Options {
Header: make([]string, 0), Header: make([]string, 0),
HeaderLines: 0, HeaderLines: 0,
Margin: defaultMargin(), Margin: defaultMargin(),
Padding: defaultMargin(),
Unicode: true, Unicode: true,
Tabstop: 8, Tabstop: 8,
ClearOnExit: true, ClearOnExit: true,
@@ -293,12 +318,12 @@ func nextString(args []string, i *int, message string) string {
return args[*i] return args[*i]
} }
func optionalNextString(args []string, i *int) string { func optionalNextString(args []string, i *int) (bool, string) {
if len(args) > *i+1 && !strings.HasPrefix(args[*i+1], "-") { if len(args) > *i+1 && !strings.HasPrefix(args[*i+1], "-") && !strings.HasPrefix(args[*i+1], "+") {
*i++ *i++
return args[*i] return true, args[*i]
} }
return "" return false, ""
} }
func atoi(str string) int { func atoi(str string) int {
@@ -392,6 +417,33 @@ func parseAlgo(str string) algo.Algo {
return algo.FuzzyMatchV2 return algo.FuzzyMatchV2
} }
func parseBorder(str string, optional bool) tui.BorderShape {
switch str {
case "rounded":
return tui.BorderRounded
case "sharp":
return tui.BorderSharp
case "horizontal":
return tui.BorderHorizontal
case "vertical":
return tui.BorderVertical
case "top":
return tui.BorderTop
case "bottom":
return tui.BorderBottom
case "left":
return tui.BorderLeft
case "right":
return tui.BorderRight
default:
if optional && str == "" {
return tui.BorderRounded
}
errorExit("invalid border style (expected: rounded|sharp|horizontal|vertical|top|bottom|left|right)")
}
return tui.BorderNone
}
func parseKeyChords(str string, message string) map[int]string { func parseKeyChords(str string, message string) map[int]string {
if len(str) == 0 { if len(str) == 0 {
errorExit(message) errorExit(message)
@@ -436,6 +488,8 @@ func parseKeyChords(str string, message string) map[int]string {
chord = tui.CtrlRightBracket chord = tui.CtrlRightBracket
case "change": case "change":
chord = tui.Change chord = tui.Change
case "backward-eof":
chord = tui.BackwardEOF
case "alt-enter", "alt-return": case "alt-enter", "alt-return":
chord = tui.CtrlAltM chord = tui.CtrlAltM
case "alt-space": case "alt-space":
@@ -464,6 +518,8 @@ func parseKeyChords(str string, message string) map[int]string {
chord = tui.Home chord = tui.Home
case "end": case "end":
chord = tui.End chord = tui.End
case "insert":
chord = tui.Insert
case "pgup", "page-up": case "pgup", "page-up":
chord = tui.PgUp chord = tui.PgUp
case "pgdn", "page-down": case "pgdn", "page-down":
@@ -548,11 +604,8 @@ func parseTiebreak(str string) []criterion {
} }
func dupeTheme(theme *tui.ColorTheme) *tui.ColorTheme { func dupeTheme(theme *tui.ColorTheme) *tui.ColorTheme {
if theme != nil { dupe := *theme
dupe := *theme return &dupe
return &dupe
}
return nil
} }
func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme { func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
@@ -567,7 +620,7 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
case "16": case "16":
theme = dupeTheme(tui.Default16) theme = dupeTheme(tui.Default16)
case "bw", "no": case "bw", "no":
theme = nil theme = tui.NoColorTheme()
default: default:
fail := func() { fail := func() {
errorExit("invalid color specification: " + str) errorExit("invalid color specification: " + str)
@@ -577,54 +630,76 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
continue continue
} }
pair := strings.Split(str, ":") components := strings.Split(str, ":")
if len(pair) != 2 { if len(components) < 2 {
fail() fail()
} }
var ansi tui.Color cattr := tui.NewColorAttr()
if rrggbb.MatchString(pair[1]) { for _, component := range components[1:] {
ansi = tui.HexToColor(pair[1]) switch component {
} else { case "regular":
ansi32, err := strconv.Atoi(pair[1]) cattr.Attr = tui.AttrRegular
if err != nil || ansi32 < -1 || ansi32 > 255 { case "bold", "strong":
fail() 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)
}
} }
ansi = tui.Color(ansi32)
} }
switch pair[0] { switch components[0] {
case "input":
theme.Input = cattr
case "fg": case "fg":
theme.Fg = ansi theme.Fg = cattr
case "bg": case "bg":
theme.Bg = ansi theme.Bg = cattr
case "preview-fg": case "preview-fg":
theme.PreviewFg = ansi theme.PreviewFg = cattr
case "preview-bg": case "preview-bg":
theme.PreviewBg = ansi theme.PreviewBg = cattr
case "fg+": case "fg+":
theme.Current = ansi theme.Current = cattr
case "bg+": case "bg+":
theme.DarkBg = ansi theme.DarkBg = cattr
case "gutter": case "gutter":
theme.Gutter = ansi theme.Gutter = cattr
case "hl": case "hl":
theme.Match = ansi theme.Match = cattr
case "hl+": case "hl+":
theme.CurrentMatch = ansi theme.CurrentMatch = cattr
case "border": case "border":
theme.Border = ansi theme.Border = cattr
case "prompt": case "prompt":
theme.Prompt = ansi theme.Prompt = cattr
case "spinner": case "spinner":
theme.Spinner = ansi theme.Spinner = cattr
case "info": case "info":
theme.Info = ansi theme.Info = cattr
case "pointer": case "pointer":
theme.Cursor = ansi theme.Cursor = cattr
case "marker": case "marker":
theme.Selected = ansi theme.Selected = cattr
case "header": case "header":
theme.Header = ansi theme.Header = cattr
default: default:
fail() fail()
} }
@@ -652,7 +727,7 @@ func init() {
// Backreferences are not supported. // Backreferences are not supported.
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|') // "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
executeRegexp = regexp.MustCompile( executeRegexp = regexp.MustCompile(
`(?si)[:+](execute(?:-multi|-silent)?|reload):.+|[:+](execute(?:-multi|-silent)?|reload)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`) `(?si)[:+](execute(?:-multi|-silent)?|reload|preview):.+|[:+](execute(?:-multi|-silent)?|reload|preview)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
} }
func parseKeymap(keymap map[int][]action, str string) { func parseKeymap(keymap map[int][]action, str string) {
@@ -664,6 +739,8 @@ func parseKeymap(keymap map[int][]action, str string) {
prefix := symbol + "execute" prefix := symbol + "execute"
if strings.HasPrefix(src[1:], "reload") { if strings.HasPrefix(src[1:], "reload") {
prefix = symbol + "reload" prefix = symbol + "reload"
} else if strings.HasPrefix(src[1:], "preview") {
prefix = symbol + "preview"
} else if src[len(prefix)] == '-' { } else if src[len(prefix)] == '-' {
c := src[len(prefix)+1] c := src[len(prefix)+1]
if c == 's' || c == 'S' { if c == 's' || c == 'S' {
@@ -724,12 +801,16 @@ func parseKeymap(keymap map[int][]action, str string) {
appendAction(actAcceptNonEmpty) appendAction(actAcceptNonEmpty)
case "print-query": case "print-query":
appendAction(actPrintQuery) appendAction(actPrintQuery)
case "refresh-preview":
appendAction(actRefreshPreview)
case "replace-query": case "replace-query":
appendAction(actReplaceQuery) appendAction(actReplaceQuery)
case "backward-char": case "backward-char":
appendAction(actBackwardChar) appendAction(actBackwardChar)
case "backward-delete-char": case "backward-delete-char":
appendAction(actBackwardDeleteChar) appendAction(actBackwardDeleteChar)
case "backward-delete-char/eof":
appendAction(actBackwardDeleteCharEOF)
case "backward-word": case "backward-word":
appendAction(actBackwardWord) appendAction(actBackwardWord)
case "clear-screen": case "clear-screen":
@@ -814,6 +895,10 @@ func parseKeymap(keymap map[int][]action, str string) {
appendAction(actPreviewPageUp) appendAction(actPreviewPageUp)
case "preview-page-down": case "preview-page-down":
appendAction(actPreviewPageDown) appendAction(actPreviewPageDown)
case "preview-half-page-up":
appendAction(actPreviewHalfPageUp)
case "preview-half-page-down":
appendAction(actPreviewHalfPageDown)
default: default:
t := isExecuteAction(specLower) t := isExecuteAction(specLower)
if t == actIgnore { if t == actIgnore {
@@ -827,6 +912,8 @@ func parseKeymap(keymap map[int][]action, str string) {
switch t { switch t {
case actReload: case actReload:
offset = len("reload") offset = len("reload")
case actPreview:
offset = len("preview")
case actExecuteSilent: case actExecuteSilent:
offset = len("execute-silent") offset = len("execute-silent")
case actExecuteMulti: case actExecuteMulti:
@@ -864,6 +951,8 @@ func isExecuteAction(str string) actionType {
switch prefix { switch prefix {
case "reload": case "reload":
return actReload return actReload
case "preview":
return actPreview
case "execute": case "execute":
return actExecute return actExecute
case "execute-silent": case "execute-silent":
@@ -944,21 +1033,26 @@ func parseInfoStyle(str string) infoStyle {
} }
func parsePreviewWindow(opts *previewOpts, input string) { func parsePreviewWindow(opts *previewOpts, input string) {
// Default
opts.position = posRight
opts.size = sizeSpec{50, true}
opts.hidden = false
opts.wrap = false
tokens := strings.Split(input, ":") tokens := strings.Split(input, ":")
sizeRegex := regexp.MustCompile("^[0-9]+%?$") sizeRegex := regexp.MustCompile("^[0-9]+%?$")
offsetRegex := regexp.MustCompile("^\\+([0-9]+|{-?[0-9]+})(-[0-9]+|-/[1-9][0-9]*)?$")
for _, token := range tokens { for _, token := range tokens {
switch token { switch token {
case "": case "":
case "default":
*opts = defaultPreviewOpts(opts.command)
case "hidden": case "hidden":
opts.hidden = true opts.hidden = true
case "nohidden":
opts.hidden = false
case "wrap": case "wrap":
opts.wrap = true opts.wrap = true
case "nowrap":
opts.wrap = false
case "cycle":
opts.cycle = true
case "nocycle":
opts.cycle = false
case "up", "top": case "up", "top":
opts.position = posUp opts.position = posUp
case "down", "bottom": case "down", "bottom":
@@ -967,32 +1061,28 @@ func parsePreviewWindow(opts *previewOpts, input string) {
opts.position = posLeft opts.position = posLeft
case "right": case "right":
opts.position = posRight opts.position = posRight
case "border": case "rounded", "border":
opts.border = true opts.border = tui.BorderRounded
case "sharp":
opts.border = tui.BorderSharp
case "noborder": case "noborder":
opts.border = false opts.border = tui.BorderNone
default: default:
if sizeRegex.MatchString(token) { if sizeRegex.MatchString(token) {
opts.size = parseSize(token, 99, "window size") opts.size = parseSize(token, 99, "window size")
} else if offsetRegex.MatchString(token) {
opts.scroll = token[1:]
} else { } else {
errorExit("invalid preview window layout: " + input) errorExit("invalid preview window option: " + token)
} }
} }
} }
if !opts.size.percent && opts.size.size > 0 {
// Adjust size for border
opts.size.size += 2
// And padding
if opts.position == posLeft || opts.position == posRight {
opts.size.size += 2
}
}
} }
func parseMargin(margin string) [4]sizeSpec { func parseMargin(opt string, margin string) [4]sizeSpec {
margins := strings.Split(margin, ",") margins := strings.Split(margin, ",")
checked := func(str string) sizeSpec { checked := func(str string) sizeSpec {
return parseSize(str, 49, "margin") return parseSize(str, 49, opt)
} }
switch len(margins) { switch len(margins) {
case 1: case 1:
@@ -1012,7 +1102,7 @@ func parseMargin(margin string) [4]sizeSpec {
checked(margins[0]), checked(margins[1]), checked(margins[0]), checked(margins[1]),
checked(margins[2]), checked(margins[3])} checked(margins[2]), checked(margins[3])}
default: default:
errorExit("invalid margin: " + margin) errorExit("invalid " + opt + ": " + margin)
} }
return defaultMargin() return defaultMargin()
} }
@@ -1041,6 +1131,8 @@ func parseOptions(opts *Options, allArgs []string) {
} }
} }
validateJumpLabels := false validateJumpLabels := false
validatePointer := false
validateMarker := false
for i := 0; i < len(allArgs); i++ { for i := 0; i < len(allArgs); i++ {
arg := allArgs[i] arg := allArgs[i]
switch arg { switch arg {
@@ -1084,7 +1176,7 @@ func parseOptions(opts *Options, allArgs []string) {
case "--bind": case "--bind":
parseKeymap(opts.Keymap, nextString(allArgs, &i, "bind expression required")) parseKeymap(opts.Keymap, nextString(allArgs, &i, "bind expression required"))
case "--color": case "--color":
spec := optionalNextString(allArgs, &i) _, spec := optionalNextString(allArgs, &i)
if len(spec) == 0 { if len(spec) == 0 {
opts.Theme = tui.EmptyTheme() opts.Theme = tui.EmptyTheme()
} else { } else {
@@ -1121,7 +1213,7 @@ func parseOptions(opts *Options, allArgs []string) {
case "--no-mouse": case "--no-mouse":
opts.Mouse = false opts.Mouse = false
case "+c", "--no-color": case "+c", "--no-color":
opts.Theme = nil opts.Theme = tui.NoColorTheme()
case "+2", "--no-256": case "+2", "--no-256":
opts.Theme = tui.Default16 opts.Theme = tui.Default16
case "--black": case "--black":
@@ -1143,6 +1235,10 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Cycle = true opts.Cycle = true
case "--no-cycle": case "--no-cycle":
opts.Cycle = false opts.Cycle = false
case "--keep-right":
opts.KeepRight = true
case "--no-keep-right":
opts.KeepRight = false
case "--hscroll": case "--hscroll":
opts.Hscroll = true opts.Hscroll = true
case "--no-hscroll": case "--no-hscroll":
@@ -1189,6 +1285,12 @@ func parseOptions(opts *Options, allArgs []string) {
opts.PrintQuery = false opts.PrintQuery = false
case "--prompt": case "--prompt":
opts.Prompt = nextString(allArgs, &i, "prompt string required") opts.Prompt = nextString(allArgs, &i, "prompt string required")
case "--pointer":
opts.Pointer = nextString(allArgs, &i, "pointer sign string required")
validatePointer = true
case "--marker":
opts.Marker = nextString(allArgs, &i, "selected sign string required")
validateMarker = true
case "--sync": case "--sync":
opts.Sync = true opts.Sync = true
case "--no-sync": case "--no-sync":
@@ -1216,7 +1318,7 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Preview.command = "" opts.Preview.command = ""
case "--preview-window": case "--preview-window":
parsePreviewWindow(&opts.Preview, parsePreviewWindow(&opts.Preview,
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:noborder][:wrap][:hidden]")) nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:rounded|sharp|noborder][:wrap][:cycle][:hidden][:+SCROLL[-OFFSET]][:default]"))
case "--height": case "--height":
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]")) opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
case "--min-height": case "--min-height":
@@ -1225,17 +1327,25 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Height = sizeSpec{} opts.Height = sizeSpec{}
case "--no-margin": case "--no-margin":
opts.Margin = defaultMargin() opts.Margin = defaultMargin()
case "--no-padding":
opts.Padding = defaultMargin()
case "--no-border": case "--no-border":
opts.Bordered = false opts.BorderShape = tui.BorderNone
case "--border": case "--border":
opts.Bordered = true hasArg, arg := optionalNextString(allArgs, &i)
opts.BorderShape = parseBorder(arg, !hasArg)
case "--no-unicode": case "--no-unicode":
opts.Unicode = false opts.Unicode = false
case "--unicode": case "--unicode":
opts.Unicode = true opts.Unicode = true
case "--margin": case "--margin":
opts.Margin = parseMargin( opts.Margin = parseMargin(
"margin",
nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)")) 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": case "--tabstop":
opts.Tabstop = nextInt(allArgs, &i, "tab stop required") opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
case "--clear": case "--clear":
@@ -1253,8 +1363,16 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Filter = &value opts.Filter = &value
} else if match, value := optString(arg, "-d", "--delimiter="); match { } else if match, value := optString(arg, "-d", "--delimiter="); match {
opts.Delimiter = delimiterRegexp(value) opts.Delimiter = delimiterRegexp(value)
} else if match, value := optString(arg, "--border="); match {
opts.BorderShape = parseBorder(value, false)
} else if match, value := optString(arg, "--prompt="); match { } else if match, value := optString(arg, "--prompt="); match {
opts.Prompt = value opts.Prompt = value
} else if match, value := optString(arg, "--pointer="); match {
opts.Pointer = value
validatePointer = true
} else if match, value := optString(arg, "--marker="); match {
opts.Marker = value
validateMarker = true
} else if match, value := optString(arg, "-n", "--nth="); match { } else if match, value := optString(arg, "-n", "--nth="); match {
opts.Nth = splitNth(value) opts.Nth = splitNth(value)
} else if match, value := optString(arg, "--with-nth="); match { } else if match, value := optString(arg, "--with-nth="); match {
@@ -1296,13 +1414,16 @@ func parseOptions(opts *Options, allArgs []string) {
} else if match, value := optString(arg, "--preview-window="); match { } else if match, value := optString(arg, "--preview-window="); match {
parsePreviewWindow(&opts.Preview, value) parsePreviewWindow(&opts.Preview, value)
} else if match, value := optString(arg, "--margin="); match { } 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 { } else if match, value := optString(arg, "--tabstop="); match {
opts.Tabstop = atoi(value) opts.Tabstop = atoi(value)
} else if match, value := optString(arg, "--hscroll-off="); match { } else if match, value := optString(arg, "--hscroll-off="); match {
opts.HscrollOff = atoi(value) opts.HscrollOff = atoi(value)
} else if match, value := optString(arg, "--jump-labels="); match { } else if match, value := optString(arg, "--jump-labels="); match {
opts.JumpLabels = value opts.JumpLabels = value
validateJumpLabels = true
} else { } else {
errorExit("unknown option: " + arg) errorExit("unknown option: " + arg)
} }
@@ -1332,11 +1453,40 @@ func parseOptions(opts *Options, allArgs []string) {
} }
} }
} }
if validatePointer {
if err := validateSign(opts.Pointer, "pointer"); err != nil {
errorExit(err.Error())
}
}
if validateMarker {
if err := validateSign(opts.Marker, "marker"); err != nil {
errorExit(err.Error())
}
}
}
func validateSign(sign string, signOptName string) error {
if sign == "" {
return fmt.Errorf("%v cannot be empty", signOptName)
}
widthSum := 0
for _, r := range sign {
if !unicode.IsGraphic(r) {
return fmt.Errorf("invalid character in %v", signOptName)
}
widthSum += runewidth.RuneWidth(r)
if widthSum > 2 {
return fmt.Errorf("%v display width should be up to 2", signOptName)
}
}
return nil
} }
func postProcessOptions(opts *Options) { func postProcessOptions(opts *Options) {
if util.IsWindows() && opts.Height.size > 0 { if !tui.IsLightRendererSupported() && opts.Height.size > 0 {
errorExit("--height option is currently not supported on Windows") errorExit("--height option is currently not supported on this platform")
} }
// Default actions for CTRL-N / CTRL-P when --history is set // Default actions for CTRL-N / CTRL-P when --history is set
if opts.History != nil { if opts.History != nil {
@@ -1370,6 +1520,25 @@ func postProcessOptions(opts *Options) {
} }
} }
} }
if opts.Bold {
theme := opts.Theme
boldify := func(c tui.ColorAttr) tui.ColorAttr {
dup := c
if !theme.Colored {
dup.Attr |= tui.Bold
} else if (c.Attr & tui.AttrRegular) == 0 {
dup.Attr |= tui.Bold
}
return dup
}
theme.Current = boldify(theme.Current)
theme.CurrentMatch = boldify(theme.CurrentMatch)
theme.Prompt = boldify(theme.Prompt)
theme.Input = boldify(theme.Input)
theme.Cursor = boldify(theme.Cursor)
theme.Spinner = boldify(theme.Spinner)
}
} }
// ParseOptions parses command-line options // ParseOptions parses command-line options

View File

@@ -295,7 +295,7 @@ func TestColorSpec(t *testing.T) {
} }
customized := parseTheme(theme, "fg:231,bg:232") customized := parseTheme(theme, "fg:231,bg:232")
if customized.Fg != 231 || customized.Bg != 232 { if customized.Fg.Color != 231 || customized.Bg.Color != 232 {
t.Errorf("color not customized") t.Errorf("color not customized")
} }
if *tui.Dark256 == *customized { if *tui.Dark256 == *customized {
@@ -313,18 +313,6 @@ func TestColorSpec(t *testing.T) {
} }
} }
func TestParseNilTheme(t *testing.T) {
var theme *tui.ColorTheme
newTheme := parseTheme(theme, "prompt:12")
if newTheme != nil {
t.Errorf("color is disabled. keep it that way.")
}
newTheme = parseTheme(theme, "prompt:12,dark,prompt:13")
if newTheme.Prompt != 13 {
t.Errorf("color should now be enabled and customized")
}
}
func TestDefaultCtrlNP(t *testing.T) { func TestDefaultCtrlNP(t *testing.T) {
check := func(words []string, key int, expected actionType) { check := func(words []string, key int, expected actionType) {
opts := defaultOptions() opts := defaultOptions()
@@ -387,23 +375,26 @@ func TestPreviewOpts(t *testing.T) {
opts.Preview.size.size == 50) { opts.Preview.size.size == 50) {
t.Error() t.Error()
} }
opts = optsFor("--preview", "cat {}", "--preview-window=left:15:hidden:wrap") opts = optsFor("--preview", "cat {}", "--preview-window=left:15:hidden:wrap:+{1}-/2")
if !(opts.Preview.command == "cat {}" && if !(opts.Preview.command == "cat {}" &&
opts.Preview.hidden == true && opts.Preview.hidden == true &&
opts.Preview.wrap == true && opts.Preview.wrap == true &&
opts.Preview.position == posLeft && opts.Preview.position == posLeft &&
opts.Preview.scroll == "{1}-/2" &&
opts.Preview.size.percent == false && opts.Preview.size.percent == false &&
opts.Preview.size.size == 15+2+2) { opts.Preview.size.size == 15) {
t.Error(opts.Preview) t.Error(opts.Preview)
} }
opts = optsFor("--preview-window=up:15:wrap:hidden", "--preview-window=down") opts = optsFor("--preview-window=up:15:wrap:hidden:+{1}-/2", "--preview-window=down", "--preview-window=cycle")
if !(opts.Preview.command == "" && if !(opts.Preview.command == "" &&
opts.Preview.hidden == false && opts.Preview.hidden == true &&
opts.Preview.wrap == false && opts.Preview.wrap == true &&
opts.Preview.cycle == true &&
opts.Preview.position == posDown && opts.Preview.position == posDown &&
opts.Preview.size.percent == true && opts.Preview.scroll == "{1}-/2" &&
opts.Preview.size.size == 50) { opts.Preview.size.percent == false &&
t.Error(opts.Preview) opts.Preview.size.size == 15) {
t.Error(opts.Preview.size.size)
} }
opts = optsFor("--preview-window=up:15:wrap:hidden") opts = optsFor("--preview-window=up:15:wrap:hidden")
if !(opts.Preview.command == "" && if !(opts.Preview.command == "" &&
@@ -411,7 +402,14 @@ func TestPreviewOpts(t *testing.T) {
opts.Preview.wrap == true && opts.Preview.wrap == true &&
opts.Preview.position == posUp && opts.Preview.position == posUp &&
opts.Preview.size.percent == false && opts.Preview.size.percent == false &&
opts.Preview.size.size == 15+2) { opts.Preview.size.size == 15) {
t.Error(opts.Preview)
}
opts = optsFor("--preview=foo", "--preview-window=up", "--preview-window=default:70%")
if !(opts.Preview.command == "foo" &&
opts.Preview.position == posRight &&
opts.Preview.size.percent == true &&
opts.Preview.size.size == 70) {
t.Error(opts.Preview) t.Error(opts.Preview)
} }
} }
@@ -422,3 +420,29 @@ func TestAdditiveExpect(t *testing.T) {
t.Error(opts.Expect) t.Error(opts.Expect)
} }
} }
func TestValidateSign(t *testing.T) {
testCases := []struct {
inputSign string
isValid bool
}{
{"> ", true},
{"아", true},
{"😀", true},
{"", false},
{">>>", false},
{"\n", false},
{"\t", false},
}
for _, testCase := range testCases {
err := validateSign(testCase.inputSign, "")
if testCase.isValid && err != nil {
t.Errorf("Input sign `%s` caused error", testCase.inputSign)
}
if !testCase.isValid && err == nil {
t.Errorf("Input sign `%s` did not cause error", testCase.inputSign)
}
}
}

View File

@@ -33,6 +33,7 @@ type term struct {
inv bool inv bool
text []rune text []rune
caseSensitive bool caseSensitive bool
normalize bool
} }
// String returns the string representation of a term. // String returns the string representation of a term.
@@ -128,6 +129,8 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
} }
} else { } else {
lowerString := strings.ToLower(asString) lowerString := strings.ToLower(asString)
normalize = normalize &&
lowerString == string(algo.NormalizeRunes([]rune(lowerString)))
caseSensitive = caseMode == CaseRespect || caseSensitive = caseMode == CaseRespect ||
caseMode == CaseSmart && lowerString != asString caseMode == CaseSmart && lowerString != asString
if !caseSensitive { if !caseSensitive {
@@ -173,6 +176,8 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
lowerText := strings.ToLower(text) lowerText := strings.ToLower(text)
caseSensitive := caseMode == CaseRespect || caseSensitive := caseMode == CaseRespect ||
caseMode == CaseSmart && text != lowerText caseMode == CaseSmart && text != lowerText
normalizeTerm := normalize &&
lowerText == string(algo.NormalizeRunes([]rune(lowerText)))
if !caseSensitive { if !caseSensitive {
text = lowerText text = lowerText
} }
@@ -222,14 +227,15 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
set = termSet{} set = termSet{}
} }
textRunes := []rune(text) textRunes := []rune(text)
if normalize { if normalizeTerm {
textRunes = algo.NormalizeRunes(textRunes) textRunes = algo.NormalizeRunes(textRunes)
} }
set = append(set, term{ set = append(set, term{
typ: typ, typ: typ,
inv: inv, inv: inv,
text: textRunes, text: textRunes,
caseSensitive: caseSensitive}) caseSensitive: caseSensitive,
normalize: normalizeTerm})
switchSet = true switchSet = true
} }
} }
@@ -360,7 +366,7 @@ func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Of
matched := false matched := false
for _, term := range termSet { for _, term := range termSet {
pfun := p.procFun[term.typ] pfun := p.procFun[term.typ]
off, score, pos := p.iter(pfun, input, term.caseSensitive, p.normalize, p.forward, term.text, withPos, slab) off, score, pos := p.iter(pfun, input, term.caseSensitive, term.normalize, p.forward, term.text, withPos, slab)
if sidx := off[0]; sidx >= 0 { if sidx := off[0]; sidx >= 0 {
if term.inv { if term.inv {
continue continue

View File

@@ -98,6 +98,9 @@ func TestEqual(t *testing.T) {
} }
match("ABC", -1, -1) match("ABC", -1, -1)
match("AbC", 0, 3) match("AbC", 0, 3)
match("AbC ", 0, 3)
match(" AbC ", 1, 4)
match(" AbC", 2, 5)
} }
func TestCaseSensitivity(t *testing.T) { func TestCaseSensitivity(t *testing.T) {

View File

@@ -0,0 +1,8 @@
// +build !openbsd
package protector
// Protect calls OS specific protections like pledge on OpenBSD
func Protect() {
return
}

View File

@@ -0,0 +1,10 @@
// +build openbsd
package protector
import "golang.org/x/sys/unix"
// Protect calls OS specific protections like pledge on OpenBSD
func Protect() {
unix.PledgePromises("stdio rpath tty proc exec")
}

View File

@@ -2,14 +2,17 @@ package fzf
import ( import (
"bufio" "bufio"
"context"
"io" "io"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
"github.com/saracen/walker"
) )
// Reader reads from command or standard input // Reader reads from command or standard input
@@ -78,7 +81,7 @@ func (r *Reader) terminate() {
r.killed = true r.killed = true
if r.exec != nil && r.exec.Process != nil { if r.exec != nil && r.exec.Process != nil {
util.KillCommand(r.exec) util.KillCommand(r.exec)
} else { } else if defaultCommand != "" {
os.Stdin.Close() os.Stdin.Close()
} }
} }
@@ -99,7 +102,11 @@ func (r *Reader) ReadSource() {
shell := "bash" shell := "bash"
cmd := os.Getenv("FZF_DEFAULT_COMMAND") cmd := os.Getenv("FZF_DEFAULT_COMMAND")
if len(cmd) == 0 { if len(cmd) == 0 {
success = r.readFromCommand(&shell, defaultCommand) if defaultCommand != "" {
success = r.readFromCommand(&shell, defaultCommand)
} else {
success = r.readFiles()
}
} else { } else {
success = r.readFromCommand(nil, cmd) success = r.readFromCommand(nil, cmd)
} }
@@ -144,6 +151,32 @@ func (r *Reader) readFromStdin() bool {
return true return true
} }
func (r *Reader) readFiles() bool {
r.killed = false
fn := func(path string, mode os.FileInfo) error {
path = filepath.Clean(path)
if path != "." {
isDir := mode.Mode().IsDir()
if isDir && filepath.Base(path)[0] == '.' {
return filepath.SkipDir
}
if !isDir && r.pusher([]byte(path)) {
atomic.StoreInt32(&r.event, int32(EvtReadNew))
}
}
r.mutex.Lock()
defer r.mutex.Unlock()
if r.killed {
return context.Canceled
}
return nil
}
cb := walker.WithErrorCallback(func(pathname string, err error) error {
return nil
})
return walker.Walk(".", fn, cb) == nil
}
func (r *Reader) readFromCommand(shell *string, command string) bool { func (r *Reader) readFromCommand(shell *string, command string) bool {
r.mutex.Lock() r.mutex.Lock()
r.killed = false r.killed = false

View File

@@ -15,7 +15,6 @@ type Offset [2]int32
type colorOffset struct { type colorOffset struct {
offset [2]int32 offset [2]int32
color tui.ColorPair color tui.ColorPair
attr tui.Attr
} }
type Result struct { type Result struct {
@@ -87,14 +86,14 @@ func minRank() Result {
return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}} return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
} }
func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, color tui.ColorPair, attr tui.Attr, current bool) []colorOffset { func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, current bool) []colorOffset {
itemColors := result.item.Colors() itemColors := result.item.Colors()
// No ANSI code, or --color=no // No ANSI codes
if len(itemColors) == 0 { if len(itemColors) == 0 {
var offsets []colorOffset var offsets []colorOffset
for _, off := range matchOffsets { for _, off := range matchOffsets {
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: color, attr: attr}) offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: colMatch})
} }
return offsets return offsets
} }
@@ -111,17 +110,19 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
maxCol = ansi.offset[1] maxCol = ansi.offset[1]
} }
} }
cols := make([]int, maxCol)
cols := make([]int, maxCol)
for colorIndex, ansi := range itemColors { for colorIndex, ansi := range itemColors {
for i := ansi.offset[0]; i < ansi.offset[1]; i++ { for i := ansi.offset[0]; i < ansi.offset[1]; i++ {
cols[i] = colorIndex + 1 // XXX cols[i] = colorIndex + 1 // 1-based index of itemColors
} }
} }
for _, off := range matchOffsets { for _, off := range matchOffsets {
for i := off[0]; i < off[1]; i++ { for i := off[0]; i < off[1]; i++ {
cols[i] = -1 // Negative of 1-based index of itemColors
// - The extra -1 means highlighted
cols[i] = cols[i]*-1 - 1
} }
} }
@@ -133,36 +134,41 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
// --++++++++-- --++++++++++--- // --++++++++-- --++++++++++---
curr := 0 curr := 0
start := 0 start := 0
ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair {
fg := ansi.color.fg
bg := ansi.color.bg
if fg == -1 {
if current {
fg = theme.Current.Color
} else {
fg = theme.Fg.Color
}
}
if bg == -1 {
if current {
bg = theme.DarkBg.Color
} else {
bg = theme.Bg.Color
}
}
return tui.NewColorPair(fg, bg, ansi.color.attr).MergeAttr(base)
}
var colors []colorOffset var colors []colorOffset
add := func(idx int) { add := func(idx int) {
if curr != 0 && idx > start { if curr != 0 && idx > start {
if curr == -1 { if curr < 0 {
colors = append(colors, colorOffset{ color := colMatch
offset: [2]int32{int32(start), int32(idx)}, color: color, attr: attr}) if curr < -1 && theme.Colored {
} else { origColor := ansiToColorPair(itemColors[-curr-2], colMatch)
ansi := itemColors[curr-1] color = origColor.MergeNonDefault(color)
fg := ansi.color.fg
bg := ansi.color.bg
if theme != nil {
if fg == -1 {
if current {
fg = theme.Current
} else {
fg = theme.Fg
}
}
if bg == -1 {
if current {
bg = theme.DarkBg
} else {
bg = theme.Bg
}
}
} }
colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, color: color})
} else {
ansi := itemColors[curr-1]
colors = append(colors, colorOffset{ colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, offset: [2]int32{int32(start), int32(idx)},
color: tui.NewColorPair(fg, bg), color: ansiToColorPair(ansi, colBase)})
attr: ansi.color.attr.Merge(attr)})
} }
} }
} }

View File

@@ -105,32 +105,55 @@ func TestColorOffset(t *testing.T) {
// ++++++++ ++++++++++ // ++++++++ ++++++++++
// --++++++++-- --++++++++++--- // --++++++++-- --++++++++++---
offsets := []Offset{Offset{5, 15}, Offset{25, 35}} offsets := []Offset{{5, 15}, {25, 35}}
item := Result{ item := Result{
item: &Item{ item: &Item{
colors: &[]ansiOffset{ colors: &[]ansiOffset{
ansiOffset{[2]int32{0, 20}, ansiState{1, 5, 0}}, {[2]int32{0, 20}, ansiState{1, 5, 0}},
ansiOffset{[2]int32{22, 27}, ansiState{2, 6, tui.Bold}}, {[2]int32{22, 27}, ansiState{2, 6, tui.Bold}},
ansiOffset{[2]int32{30, 32}, ansiState{3, 7, 0}}, {[2]int32{30, 32}, ansiState{3, 7, 0}},
ansiOffset{[2]int32{33, 40}, ansiState{4, 8, tui.Bold}}}}} {[2]int32{33, 40}, ansiState{4, 8, tui.Bold}}}}}
// [{[0 5] 9 false} {[5 15] 99 false} {[15 20] 9 false} {[22 25] 10 true} {[25 35] 99 false} {[35 40] 11 true}]
pair := tui.NewColorPair(99, 199) colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
colors := item.colorOffsets(offsets, tui.Dark256, pair, tui.AttrRegular, true) colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
assert := func(idx int, b int32, e int32, c tui.ColorPair, bold bool) { colors := item.colorOffsets(offsets, tui.Dark256, colBase, colMatch, true)
var attr tui.Attr assert := func(idx int, b int32, e int32, c tui.ColorPair) {
if bold {
attr = tui.Bold
}
o := colors[idx] o := colors[idx]
if o.offset[0] != b || o.offset[1] != e || o.color != c || o.attr != attr { if o.offset[0] != b || o.offset[1] != e || o.color != c {
t.Error(o) t.Error(o, b, e, c)
} }
} }
assert(0, 0, 5, tui.NewColorPair(1, 5), false) // [{[0 5] {1 5 0}} {[5 15] {99 199 0}} {[15 20] {1 5 0}}
assert(1, 5, 15, pair, false) // {[22 25] {2 6 1}} {[25 27] {99 199 1}} {[27 30] {99 199 0}}
assert(2, 15, 20, tui.NewColorPair(1, 5), false) // {[30 32] {99 199 0}} {[32 33] {99 199 0}} {[33 35] {99 199 1}}
assert(3, 22, 25, tui.NewColorPair(2, 6), true) // {[35 40] {4 8 1}}]
assert(4, 25, 35, pair, false) assert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))
assert(5, 35, 40, tui.NewColorPair(4, 8), true) assert(1, 5, 15, colMatch)
assert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined))
assert(3, 22, 25, tui.NewColorPair(2, 6, tui.Bold))
assert(4, 25, 27, colMatch.WithAttr(tui.Bold))
assert(5, 27, 30, colMatch)
assert(6, 30, 32, colMatch)
assert(7, 32, 33, colMatch) // TODO: Should we merge consecutive blocks?
assert(8, 33, 35, colMatch.WithAttr(tui.Bold))
assert(9, 35, 40, tui.NewColorPair(4, 8, tui.Bold))
colRegular := tui.NewColorPair(-1, -1, tui.AttrUndefined)
colUnderline := tui.NewColorPair(-1, -1, tui.Underline)
colors = item.colorOffsets(offsets, tui.Dark256, colRegular, colUnderline, true)
// [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}}
// {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}}
// {[30 32] {3 7 8}} {[32 33] {-1 -1 8}} {[33 35] {4 8 9}}
// {[35 40] {4 8 1}}]
assert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))
assert(1, 5, 15, tui.NewColorPair(1, 5, tui.Underline))
assert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined))
assert(3, 22, 25, tui.NewColorPair(2, 6, tui.Bold))
assert(4, 25, 27, tui.NewColorPair(2, 6, tui.Bold|tui.Underline))
assert(5, 27, 30, colUnderline)
assert(6, 30, 32, tui.NewColorPair(3, 7, tui.Underline))
assert(7, 32, 33, colUnderline)
assert(8, 33, 35, tui.NewColorPair(4, 8, tui.Bold|tui.Underline))
assert(9, 35, 40, tui.NewColorPair(4, 8, tui.Bold))
} }

File diff suppressed because it is too large Load Diff

View File

@@ -15,27 +15,29 @@ func (a Attr) Merge(b Attr) Attr {
} }
const ( const (
AttrRegular Attr = Attr(0) AttrUndefined = Attr(0)
Bold = Attr(1) AttrRegular = Attr(1 << 7)
Dim = Attr(1 << 1) AttrClear = Attr(1 << 8)
Italic = Attr(1 << 2)
Underline = Attr(1 << 3) Bold = Attr(1)
Blink = Attr(1 << 4) Dim = Attr(1 << 1)
Blink2 = Attr(1 << 5) Italic = Attr(1 << 2)
Reverse = Attr(1 << 6) Underline = Attr(1 << 3)
Blink = Attr(1 << 4)
Blink2 = Attr(1 << 5)
Reverse = Attr(1 << 6)
) )
func (r *FullscreenRenderer) Init() {} func (r *FullscreenRenderer) Init() {}
func (r *FullscreenRenderer) Pause(bool) {} func (r *FullscreenRenderer) Pause(bool) {}
func (r *FullscreenRenderer) Resume(bool) {} func (r *FullscreenRenderer) Resume(bool, bool) {}
func (r *FullscreenRenderer) Clear() {} func (r *FullscreenRenderer) Clear() {}
func (r *FullscreenRenderer) Refresh() {} func (r *FullscreenRenderer) Refresh() {}
func (r *FullscreenRenderer) Close() {} func (r *FullscreenRenderer) Close() {}
func (r *FullscreenRenderer) DoesAutoWrap() bool { return false } func (r *FullscreenRenderer) GetChar() Event { return Event{} }
func (r *FullscreenRenderer) GetChar() Event { return Event{} } func (r *FullscreenRenderer) MaxX() int { return 0 }
func (r *FullscreenRenderer) MaxX() int { return 0 } func (r *FullscreenRenderer) MaxY() int { return 0 }
func (r *FullscreenRenderer) MaxY() int { return 0 }
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {} func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}

View File

@@ -3,11 +3,9 @@ package tui
import ( import (
"fmt" "fmt"
"os" "os"
"os/exec"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"syscall"
"time" "time"
"unicode/utf8" "unicode/utf8"
@@ -23,26 +21,13 @@ const (
defaultEscDelay = 100 defaultEscDelay = 100
escPollInterval = 5 escPollInterval = 5
offsetPollTries = 10 offsetPollTries = 10
maxInputBuffer = 10 * 1024
) )
const consoleDevice string = "/dev/tty" const consoleDevice string = "/dev/tty"
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R") var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
func openTtyIn() *os.File {
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
if err != nil {
tty := ttyname()
if len(tty) > 0 {
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
return in
}
}
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
os.Exit(2)
}
return in
}
func (r *LightRenderer) stderr(str string) { func (r *LightRenderer) stderr(str string) {
r.stderrInternal(str, true) r.stderrInternal(str, true)
@@ -100,11 +85,19 @@ type LightRenderer struct {
y int y int
x int x int
maxHeightFunc func(int) int maxHeightFunc func(int) int
// Windows only
ttyinChannel chan byte
inHandle uintptr
outHandle uintptr
origStateInput uint32
origStateOutput uint32
} }
type LightWindow struct { type LightWindow struct {
renderer *LightRenderer renderer *LightRenderer
colored bool colored bool
preview bool
border BorderStyle border BorderStyle
top int top int
left int left int
@@ -132,37 +125,6 @@ func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop in
return &r return &r
} }
func (r *LightRenderer) fd() int {
return int(r.ttyin.Fd())
}
func (r *LightRenderer) defaultTheme() *ColorTheme {
if strings.Contains(os.Getenv("TERM"), "256") {
return Dark256
}
colors, err := exec.Command("tput", "colors").Output()
if err == nil && atoi(strings.TrimSpace(string(colors)), 16) > 16 {
return Dark256
}
return Default16
}
func (r *LightRenderer) findOffset() (row int, col int) {
r.csi("6n")
r.flush()
bytes := []byte{}
for tries := 0; tries < offsetPollTries; tries++ {
bytes = r.getBytesInternal(bytes, tries > 0)
offsets := offsetRegexp.FindSubmatch(bytes)
if len(offsets) > 3 {
// add anything we skipped over to the input buffer
r.buffer = append(r.buffer, offsets[1]...)
return atoi(string(offsets[2]), 0) - 1, atoi(string(offsets[3]), 0) - 1
}
}
return -1, -1
}
func repeat(r rune, times int) string { func repeat(r rune, times int) string {
if times > 0 { if times > 0 {
return strings.Repeat(string(r), times) return strings.Repeat(string(r), times)
@@ -181,13 +143,9 @@ func atoi(s string, defaultValue int) int {
func (r *LightRenderer) Init() { func (r *LightRenderer) Init() {
r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay) r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay)
fd := r.fd() if err := r.initPlatform(); err != nil {
origState, err := terminal.GetState(fd)
if err != nil {
errorExit(err.Error()) errorExit(err.Error())
} }
r.origState = origState
terminal.MakeRaw(fd)
r.updateTerminalSize() r.updateTerminalSize()
initTheme(r.theme, r.defaultTheme(), r.forceBlack) initTheme(r.theme, r.defaultTheme(), r.forceBlack)
@@ -260,28 +218,6 @@ func getEnv(name string, defaultValue int) int {
return atoi(env, defaultValue) return atoi(env, defaultValue)
} }
func (r *LightRenderer) updateTerminalSize() {
width, height, err := terminal.GetSize(r.fd())
if err == nil {
r.width = width
r.height = r.maxHeightFunc(height)
} else {
r.width = getEnv("COLUMNS", defaultWidth)
r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
}
}
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
b := make([]byte, 1)
fd := r.fd()
util.SetNonblock(r.ttyin, nonblock)
_, err := util.Read(fd, b)
if err != nil {
return 0, false
}
return int(b[0]), true
}
func (r *LightRenderer) getBytes() []byte { func (r *LightRenderer) getBytes() []byte {
return r.getBytesInternal(r.buffer, false) return r.getBytesInternal(r.buffer, false)
} }
@@ -316,6 +252,13 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
} }
buffer = append(buffer, byte(c)) buffer = append(buffer, byte(c))
pc = c pc = c
// This should never happen under normal conditions,
// so terminate fzf immediately.
if len(buffer) > maxInputBuffer {
r.Close()
panic(fmt.Sprintf("Input buffer overflow (%d): %v", len(buffer), buffer))
}
} }
return buffer return buffer
@@ -379,6 +322,13 @@ func (r *LightRenderer) escSequence(sz *int) Event {
if len(r.buffer) < 2 { if len(r.buffer) < 2 {
return Event{ESC, 0, nil} return Event{ESC, 0, nil}
} }
loc := offsetRegexpBegin.FindIndex(r.buffer)
if loc != nil && loc[0] == 0 {
*sz = loc[1]
return Event{Invalid, 0, nil}
}
*sz = 2 *sz = 2
if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 { if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {
return Event{int(CtrlAltA + r.buffer[1] - 1), 0, nil} return Event{int(CtrlAltA + r.buffer[1] - 1), 0, nil}
@@ -391,112 +341,123 @@ func (r *LightRenderer) escSequence(sz *int) Event {
switch r.buffer[1] { switch r.buffer[1] {
case ESC: case ESC:
return Event{ESC, 0, nil} return Event{ESC, 0, nil}
case 32: case ' ':
return Event{AltSpace, 0, nil} return Event{AltSpace, 0, nil}
case 47: case '/':
return Event{AltSlash, 0, nil} return Event{AltSlash, 0, nil}
case 98: case 'b':
return Event{AltB, 0, nil} return Event{AltB, 0, nil}
case 100: case 'd':
return Event{AltD, 0, nil} return Event{AltD, 0, nil}
case 102: case 'f':
return Event{AltF, 0, nil} return Event{AltF, 0, nil}
case 127: case 127:
return Event{AltBS, 0, nil} return Event{AltBS, 0, nil}
case 91, 79: case '[', 'O':
if len(r.buffer) < 3 { if len(r.buffer) < 3 {
return Event{Invalid, 0, nil} return Event{Invalid, 0, nil}
} }
*sz = 3 *sz = 3
switch r.buffer[2] { switch r.buffer[2] {
case 68: case 'D':
if alt { if alt {
return Event{AltLeft, 0, nil} return Event{AltLeft, 0, nil}
} }
return Event{Left, 0, nil} return Event{Left, 0, nil}
case 67: case 'C':
if alt { if alt {
// Ugh.. // Ugh..
return Event{AltRight, 0, nil} return Event{AltRight, 0, nil}
} }
return Event{Right, 0, nil} return Event{Right, 0, nil}
case 66: case 'B':
if alt { if alt {
return Event{AltDown, 0, nil} return Event{AltDown, 0, nil}
} }
return Event{Down, 0, nil} return Event{Down, 0, nil}
case 65: case 'A':
if alt { if alt {
return Event{AltUp, 0, nil} return Event{AltUp, 0, nil}
} }
return Event{Up, 0, nil} return Event{Up, 0, nil}
case 90: case 'Z':
return Event{BTab, 0, nil} return Event{BTab, 0, nil}
case 72: case 'H':
return Event{Home, 0, nil} return Event{Home, 0, nil}
case 70: case 'F':
return Event{End, 0, nil} return Event{End, 0, nil}
case 77: case 'M':
return r.mouseSequence(sz) return r.mouseSequence(sz)
case 80: case 'P':
return Event{F1, 0, nil} return Event{F1, 0, nil}
case 81: case 'Q':
return Event{F2, 0, nil} return Event{F2, 0, nil}
case 82: case 'R':
return Event{F3, 0, nil} return Event{F3, 0, nil}
case 83: case 'S':
return Event{F4, 0, nil} return Event{F4, 0, nil}
case 49, 50, 51, 52, 53, 54: case '1', '2', '3', '4', '5', '6':
if len(r.buffer) < 4 { if len(r.buffer) < 4 {
return Event{Invalid, 0, nil} return Event{Invalid, 0, nil}
} }
*sz = 4 *sz = 4
switch r.buffer[2] { switch r.buffer[2] {
case 50: case '2':
if len(r.buffer) == 5 && r.buffer[4] == 126 { if r.buffer[3] == '~' {
return Event{Insert, 0, nil}
}
if len(r.buffer) > 4 && r.buffer[4] == '~' {
*sz = 5 *sz = 5
switch r.buffer[3] { switch r.buffer[3] {
case 48: case '0':
return Event{F9, 0, nil} return Event{F9, 0, nil}
case 49: case '1':
return Event{F10, 0, nil} return Event{F10, 0, nil}
case 51: case '3':
return Event{F11, 0, nil} return Event{F11, 0, nil}
case 52: case '4':
return Event{F12, 0, nil} return Event{F12, 0, nil}
} }
} }
// Bracketed paste mode: \e[200~ ... \e[201~ // Bracketed paste mode: \e[200~ ... \e[201~
if r.buffer[3] == '0' && (r.buffer[4] == '0' || r.buffer[4] == '1') && r.buffer[5] == '~' { if len(r.buffer) > 5 && r.buffer[3] == '0' && (r.buffer[4] == '0' || r.buffer[4] == '1') && r.buffer[5] == '~' {
// Immediately discard the sequence from the buffer and reread input // Immediately discard the sequence from the buffer and reread input
r.buffer = r.buffer[6:] r.buffer = r.buffer[6:]
*sz = 0 *sz = 0
return r.GetChar() return r.GetChar()
} }
return Event{Invalid, 0, nil} // INS return Event{Invalid, 0, nil} // INS
case 51: case '3':
return Event{Del, 0, nil} return Event{Del, 0, nil}
case 52: case '4':
return Event{End, 0, nil} return Event{End, 0, nil}
case 53: case '5':
return Event{PgUp, 0, nil} return Event{PgUp, 0, nil}
case 54: case '6':
return Event{PgDn, 0, nil} return Event{PgDn, 0, nil}
case 49: case '1':
switch r.buffer[3] { switch r.buffer[3] {
case 126: case '~':
return Event{Home, 0, nil} return Event{Home, 0, nil}
case 53, 55, 56, 57: case '1', '2', '3', '4', '5', '7', '8', '9':
if len(r.buffer) == 5 && r.buffer[4] == 126 { if len(r.buffer) == 5 && r.buffer[4] == '~' {
*sz = 5 *sz = 5
switch r.buffer[3] { switch r.buffer[3] {
case 53: case '1':
return Event{F1, 0, nil}
case '2':
return Event{F2, 0, nil}
case '3':
return Event{F3, 0, nil}
case '4':
return Event{F4, 0, nil}
case '5':
return Event{F5, 0, nil} return Event{F5, 0, nil}
case 55: case '7':
return Event{F6, 0, nil} return Event{F6, 0, nil}
case 56: case '8':
return Event{F7, 0, nil} return Event{F7, 0, nil}
case 57: case '9':
return Event{F8, 0, nil} return Event{F8, 0, nil}
} }
} }
@@ -584,7 +545,7 @@ func (r *LightRenderer) rmcup() {
} }
func (r *LightRenderer) Pause(clear bool) { func (r *LightRenderer) Pause(clear bool) {
terminal.Restore(r.fd(), r.origState) r.restoreTerminal()
if clear { if clear {
if r.fullscreen { if r.fullscreen {
r.rmcup() r.rmcup()
@@ -596,8 +557,8 @@ func (r *LightRenderer) Pause(clear bool) {
} }
} }
func (r *LightRenderer) Resume(clear bool) { func (r *LightRenderer) Resume(clear bool, sigcont bool) {
terminal.MakeRaw(r.fd()) r.setupTerminal()
if clear { if clear {
if r.fullscreen { if r.fullscreen {
r.smcup() r.smcup()
@@ -605,10 +566,10 @@ func (r *LightRenderer) Resume(clear bool) {
r.rmcup() r.rmcup()
} }
r.flush() r.flush()
} else if !r.fullscreen && r.mouse { } else if sigcont && !r.fullscreen && r.mouse {
// NOTE: Resume(false) is only called on SIGCONT after SIGSTOP. // NOTE: SIGCONT (Coming back from CTRL-Z):
// And It's highly likely that the offset we obtained at the beginning will // It's highly likely that the offset we obtained at the beginning is
// no longer be correct, so we simply disable mouse input. // no longer correct, so we simply disable mouse input.
r.csi("?1000l") r.csi("?1000l")
r.mouse = false r.mouse = false
} }
@@ -651,7 +612,8 @@ func (r *LightRenderer) Close() {
r.csi("?1000l") r.csi("?1000l")
} }
r.flush() r.flush()
terminal.Restore(r.fd(), r.origState) r.closePlatform()
r.restoreTerminal()
} }
func (r *LightRenderer) MaxX() int { func (r *LightRenderer) MaxX() int {
@@ -662,14 +624,11 @@ func (r *LightRenderer) MaxY() int {
return r.height return r.height
} }
func (r *LightRenderer) DoesAutoWrap() bool {
return false
}
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window { func (r *LightRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
w := &LightWindow{ w := &LightWindow{
renderer: r, renderer: r,
colored: r.theme != nil, colored: r.theme.Colored,
preview: preview,
border: borderStyle, border: borderStyle,
top: top, top: top,
left: left, left: left,
@@ -678,14 +637,12 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev
tabstop: r.tabstop, tabstop: r.tabstop,
fg: colDefault, fg: colDefault,
bg: colDefault} bg: colDefault}
if r.theme != nil { if preview {
if preview { w.fg = r.theme.PreviewFg.Color
w.fg = r.theme.PreviewFg w.bg = r.theme.PreviewBg.Color
w.bg = r.theme.PreviewBg } else {
} else { w.fg = r.theme.Fg.Color
w.fg = r.theme.Fg w.bg = r.theme.Bg.Color
w.bg = r.theme.Bg
}
} }
w.drawBorder() w.drawBorder()
return w return w
@@ -693,33 +650,66 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev
func (w *LightWindow) drawBorder() { func (w *LightWindow) drawBorder() {
switch w.border.shape { switch w.border.shape {
case BorderAround: case BorderRounded, BorderSharp:
w.drawBorderAround() w.drawBorderAround()
case BorderHorizontal: case BorderHorizontal:
w.drawBorderHorizontal() w.drawBorderHorizontal(true, true)
case BorderVertical:
w.drawBorderVertical(true, true)
case BorderTop:
w.drawBorderHorizontal(true, false)
case BorderBottom:
w.drawBorderHorizontal(false, true)
case BorderLeft:
w.drawBorderVertical(true, false)
case BorderRight:
w.drawBorderVertical(false, true)
} }
} }
func (w *LightWindow) drawBorderHorizontal() { func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
w.Move(0, 0) if top {
w.CPrint(ColBorder, AttrRegular, repeat(w.border.horizontal, w.width)) w.Move(0, 0)
w.Move(w.height-1, 0) w.CPrint(ColBorder, repeat(w.border.horizontal, w.width))
w.CPrint(ColBorder, AttrRegular, repeat(w.border.horizontal, w.width)) }
if bottom {
w.Move(w.height-1, 0)
w.CPrint(ColBorder, repeat(w.border.horizontal, w.width))
}
}
func (w *LightWindow) drawBorderVertical(left, right bool) {
width := w.width - 2
if !left || !right {
width++
}
for y := 0; y < w.height; y++ {
w.Move(y, 0)
if left {
w.CPrint(ColBorder, string(w.border.vertical))
}
w.CPrint(ColBorder, repeat(' ', width))
if right {
w.CPrint(ColBorder, string(w.border.vertical))
}
}
} }
func (w *LightWindow) drawBorderAround() { func (w *LightWindow) drawBorderAround() {
w.Move(0, 0) w.Move(0, 0)
w.CPrint(ColPreviewBorder, AttrRegular, color := ColBorder
string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight)) if w.preview {
color = ColPreviewBorder
}
w.CPrint(color, string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight))
for y := 1; y < w.height-1; y++ { for y := 1; y < w.height-1; y++ {
w.Move(y, 0) w.Move(y, 0)
w.CPrint(ColPreviewBorder, AttrRegular, string(w.border.vertical)) w.CPrint(color, string(w.border.vertical))
w.CPrint(ColPreviewBorder, AttrRegular, repeat(' ', w.width-2)) w.CPrint(color, repeat(' ', w.width-2))
w.CPrint(ColPreviewBorder, AttrRegular, string(w.border.vertical)) w.CPrint(color, string(w.border.vertical))
} }
w.Move(w.height-1, 0) w.Move(w.height-1, 0)
w.CPrint(ColPreviewBorder, AttrRegular, w.CPrint(color, string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight))
string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight))
} }
func (w *LightWindow) csi(code string) { func (w *LightWindow) csi(code string) {
@@ -782,6 +772,9 @@ func (w *LightWindow) MoveAndClear(y int, x int) {
func attrCodes(attr Attr) []string { func attrCodes(attr Attr) []string {
codes := []string{} codes := []string{}
if (attr & AttrClear) > 0 {
return codes
}
if (attr & Bold) > 0 { if (attr & Bold) > 0 {
codes = append(codes, "1") codes = append(codes, "1")
} }
@@ -841,12 +834,8 @@ func cleanse(str string) string {
return strings.Replace(str, "\x1b", "", -1) return strings.Replace(str, "\x1b", "", -1)
} }
func (w *LightWindow) CPrint(pair ColorPair, attr Attr, text string) { func (w *LightWindow) CPrint(pair ColorPair, text string) {
if !w.colored { w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
w.csiColor(colDefault, colDefault, attrFor(pair, attr))
} else {
w.csiColor(pair.Fg(), pair.Bg(), attr)
}
w.stderrInternal(cleanse(text), false) w.stderrInternal(cleanse(text), false)
w.csi("m") w.csi("m")
} }
@@ -868,7 +857,7 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
width := 0 width := 0
line := "" line := ""
for _, r := range input { for _, r := range input {
w := util.Max(util.RuneWidth(r, prefixLength+width, 8), 1) w := util.RuneWidth(r, prefixLength+width, 8)
width += w width += w
str := string(r) str := string(r)
if r == '\t' { if r == '\t' {

110
src/tui/light_unix.go Normal file
View File

@@ -0,0 +1,110 @@
// +build !windows
package tui
import (
"fmt"
"os"
"os/exec"
"strings"
"syscall"
"github.com/junegunn/fzf/src/util"
"golang.org/x/crypto/ssh/terminal"
)
func IsLightRendererSupported() bool {
return true
}
func (r *LightRenderer) defaultTheme() *ColorTheme {
if strings.Contains(os.Getenv("TERM"), "256") {
return Dark256
}
colors, err := exec.Command("tput", "colors").Output()
if err == nil && atoi(strings.TrimSpace(string(colors)), 16) > 16 {
return Dark256
}
return Default16
}
func (r *LightRenderer) fd() int {
return int(r.ttyin.Fd())
}
func (r *LightRenderer) initPlatform() error {
fd := r.fd()
origState, err := terminal.GetState(fd)
if err != nil {
return err
}
r.origState = origState
terminal.MakeRaw(fd)
return nil
}
func (r *LightRenderer) closePlatform() {
// NOOP
}
func openTtyIn() *os.File {
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
if err != nil {
tty := ttyname()
if len(tty) > 0 {
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
return in
}
}
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
os.Exit(2)
}
return in
}
func (r *LightRenderer) setupTerminal() {
terminal.MakeRaw(r.fd())
}
func (r *LightRenderer) restoreTerminal() {
terminal.Restore(r.fd(), r.origState)
}
func (r *LightRenderer) updateTerminalSize() {
width, height, err := terminal.GetSize(r.fd())
if err == nil {
r.width = width
r.height = r.maxHeightFunc(height)
} else {
r.width = getEnv("COLUMNS", defaultWidth)
r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
}
}
func (r *LightRenderer) findOffset() (row int, col int) {
r.csi("6n")
r.flush()
bytes := []byte{}
for tries := 0; tries < offsetPollTries; tries++ {
bytes = r.getBytesInternal(bytes, tries > 0)
offsets := offsetRegexp.FindSubmatch(bytes)
if len(offsets) > 3 {
// Add anything we skipped over to the input buffer
r.buffer = append(r.buffer, offsets[1]...)
return atoi(string(offsets[2]), 0) - 1, atoi(string(offsets[3]), 0) - 1
}
}
return -1, -1
}
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
b := make([]byte, 1)
fd := r.fd()
util.SetNonblock(r.ttyin, nonblock)
_, err := util.Read(fd, b)
if err != nil {
return 0, false
}
return int(b[0]), true
}

140
src/tui/light_windows.go Normal file
View File

@@ -0,0 +1,140 @@
//+build windows
package tui
import (
"os"
"syscall"
"github.com/junegunn/fzf/src/util"
"golang.org/x/sys/windows"
)
var (
consoleFlagsInput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_EXTENDED_FLAGS)
consoleFlagsOutput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | windows.ENABLE_PROCESSED_OUTPUT | windows.DISABLE_NEWLINE_AUTO_RETURN)
)
// IsLightRendererSupported checks to see if the Light renderer is supported
func IsLightRendererSupported() bool {
var oldState uint32
// enable vt100 emulation (https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences)
if windows.GetConsoleMode(windows.Stderr, &oldState) != nil {
return false
}
// attempt to set mode to determine if we support VT 100 codes. This will work on newer Windows 10
// version:
canSetVt100 := windows.SetConsoleMode(windows.Stderr, oldState|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) == nil
var checkState uint32
if windows.GetConsoleMode(windows.Stderr, &checkState) != nil ||
(checkState&windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) != windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING {
return false
}
windows.SetConsoleMode(windows.Stderr, oldState)
return canSetVt100
}
func (r *LightRenderer) defaultTheme() *ColorTheme {
// the getenv check is borrowed from here: https://github.com/gdamore/tcell/commit/0c473b86d82f68226a142e96cc5a34c5a29b3690#diff-b008fcd5e6934bf31bc3d33bf49f47d8R178:
if !IsLightRendererSupported() || os.Getenv("ConEmuPID") != "" || os.Getenv("TCELL_TRUECOLOR") == "disable" {
return Default16
}
return Dark256
}
func (r *LightRenderer) initPlatform() error {
//outHandle := windows.Stdout
outHandle, _ := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
// enable vt100 emulation (https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences)
if err := windows.GetConsoleMode(windows.Handle(outHandle), &r.origStateOutput); err != nil {
return err
}
r.outHandle = uintptr(outHandle)
inHandle, _ := syscall.Open("CONIN$", syscall.O_RDWR, 0)
if err := windows.GetConsoleMode(windows.Handle(inHandle), &r.origStateInput); err != nil {
return err
}
r.inHandle = uintptr(inHandle)
r.setupTerminal()
// channel for non-blocking reads. Buffer to make sure
// we get the ESC sets:
r.ttyinChannel = make(chan byte, 12)
// the following allows for non-blocking IO.
// syscall.SetNonblock() is a NOOP under Windows.
go func() {
fd := int(r.inHandle)
b := make([]byte, 1)
for {
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
_ = windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
_, err := util.Read(fd, b)
if err == nil {
r.ttyinChannel <- b[0]
}
}
}()
return nil
}
func (r *LightRenderer) closePlatform() {
windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput)
}
func openTtyIn() *os.File {
// not used
return nil
}
func (r *LightRenderer) setupTerminal() error {
if err := windows.SetConsoleMode(windows.Handle(r.outHandle), consoleFlagsOutput); err != nil {
return err
}
return windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
}
func (r *LightRenderer) restoreTerminal() error {
if err := windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput); err != nil {
return err
}
return windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
}
func (r *LightRenderer) updateTerminalSize() {
var bufferInfo windows.ConsoleScreenBufferInfo
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {
r.width = getEnv("COLUMNS", defaultWidth)
r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
} else {
r.width = int(bufferInfo.Window.Right - bufferInfo.Window.Left)
r.height = r.maxHeightFunc(int(bufferInfo.Window.Bottom - bufferInfo.Window.Top))
}
}
func (r *LightRenderer) findOffset() (row int, col int) {
var bufferInfo windows.ConsoleScreenBufferInfo
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {
return -1, -1
}
return int(bufferInfo.CursorPosition.X), int(bufferInfo.CursorPosition.Y)
}
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
if nonblock {
select {
case bc := <-r.ttyinChannel:
return int(bc), true
default:
return 0, false
}
} else {
bc := <-r.ttyinChannel
return int(bc), true
}
}

View File

@@ -28,6 +28,7 @@ type Attr tcell.Style
type TcellWindow struct { type TcellWindow struct {
color bool color bool
preview bool
top int top int
left int left int
width int width int
@@ -76,11 +77,13 @@ const (
Blink = Attr(tcell.AttrBlink) Blink = Attr(tcell.AttrBlink)
Reverse = Attr(tcell.AttrReverse) Reverse = Attr(tcell.AttrReverse)
Underline = Attr(tcell.AttrUnderline) Underline = Attr(tcell.AttrUnderline)
Italic = Attr(tcell.AttrNone) // Not supported Italic = Attr(tcell.AttrItalic)
) )
const ( const (
AttrRegular Attr = 0 AttrUndefined = Attr(0)
AttrRegular = Attr(1 << 7)
AttrClear = Attr(1 << 8)
) )
func (r *FullscreenRenderer) defaultTheme() *ColorTheme { func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
@@ -165,10 +168,6 @@ func (w *TcellWindow) Y() int {
return w.lastY return w.lastY
} }
func (r *FullscreenRenderer) DoesAutoWrap() bool {
return false
}
func (r *FullscreenRenderer) Clear() { func (r *FullscreenRenderer) Clear() {
_screen.Sync() _screen.Sync()
_screen.Clear() _screen.Clear()
@@ -318,6 +317,8 @@ func (r *FullscreenRenderer) GetChar() Event {
} }
return Event{Right, 0, nil} return Event{Right, 0, nil}
case tcell.KeyInsert:
return Event{Insert, 0, nil}
case tcell.KeyHome: case tcell.KeyHome:
return Event{Home, 0, nil} return Event{Home, 0, nil}
case tcell.KeyDelete: case tcell.KeyDelete:
@@ -391,7 +392,7 @@ func (r *FullscreenRenderer) Pause(clear bool) {
} }
} }
func (r *FullscreenRenderer) Resume(clear bool) { func (r *FullscreenRenderer) Resume(clear bool, sigcont bool) {
if clear { if clear {
r.initScreen() r.initScreen()
} }
@@ -415,7 +416,8 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
normal = ColPreview normal = ColPreview
} }
return &TcellWindow{ return &TcellWindow{
color: r.theme != nil, color: r.theme.Colored,
preview: preview,
top: top, top: top,
left: left, left: left,
width: width, width: width,
@@ -460,27 +462,23 @@ func (w *TcellWindow) MoveAndClear(y int, x int) {
} }
func (w *TcellWindow) Print(text string) { func (w *TcellWindow) Print(text string) {
w.printString(text, w.normal, 0) w.printString(text, w.normal)
} }
func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) { func (w *TcellWindow) printString(text string, pair ColorPair) {
t := text t := text
lx := 0 lx := 0
a := pair.Attr()
var style tcell.Style style := pair.style()
if w.color { if a&AttrClear == 0 {
style = pair.style(). style = style.
Reverse(a&Attr(tcell.AttrReverse) != 0). Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0) Underline(a&Attr(tcell.AttrUnderline) != 0).
} else { Italic(a&Attr(tcell.AttrItalic) != 0).
style = w.normal.style(). Blink(a&Attr(tcell.AttrBlink) != 0).
Reverse(a&Attr(tcell.AttrReverse) != 0 || pair == ColCurrent || pair == ColCurrentMatch). Dim(a&Attr(tcell.AttrDim) != 0)
Underline(a&Attr(tcell.AttrUnderline) != 0 || pair == ColMatch || pair == ColCurrentMatch)
} }
style = style.
Blink(a&Attr(tcell.AttrBlink) != 0).
Bold(a&Attr(tcell.AttrBold) != 0).
Dim(a&Attr(tcell.AttrDim) != 0)
for { for {
if len(t) == 0 { if len(t) == 0 {
@@ -513,12 +511,13 @@ func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
w.lastX += lx w.lastX += lx
} }
func (w *TcellWindow) CPrint(pair ColorPair, attr Attr, text string) { func (w *TcellWindow) CPrint(pair ColorPair, text string) {
w.printString(text, pair, attr) w.printString(text, pair)
} }
func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn { func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
lx := 0 lx := 0
a := pair.Attr()
var style tcell.Style var style tcell.Style
if w.color { if w.color {
@@ -531,7 +530,8 @@ func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn
Bold(a&Attr(tcell.AttrBold) != 0). Bold(a&Attr(tcell.AttrBold) != 0).
Dim(a&Attr(tcell.AttrDim) != 0). Dim(a&Attr(tcell.AttrDim) != 0).
Reverse(a&Attr(tcell.AttrReverse) != 0). Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0) Underline(a&Attr(tcell.AttrUnderline) != 0).
Italic(a&Attr(tcell.AttrItalic) != 0)
for _, r := range text { for _, r := range text {
if r == '\n' { if r == '\n' {
@@ -559,12 +559,17 @@ func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn
} }
} }
w.lastX += lx w.lastX += lx
if w.lastX == w.width {
w.lastY++
w.lastX = 0
return FillNextLine
}
return FillContinue return FillContinue
} }
func (w *TcellWindow) Fill(str string) FillReturn { func (w *TcellWindow) Fill(str string) FillReturn {
return w.fillString(str, w.normal, 0) return w.fillString(str, w.normal)
} }
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn { func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
@@ -574,11 +579,12 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
if bg == colDefault { if bg == colDefault {
bg = w.normal.Bg() bg = w.normal.Bg()
} }
return w.fillString(str, NewColorPair(fg, bg), a) return w.fillString(str, NewColorPair(fg, bg, a))
} }
func (w *TcellWindow) drawBorder() { func (w *TcellWindow) drawBorder() {
if w.borderStyle.shape == BorderNone { shape := w.borderStyle.shape
if shape == BorderNone {
return return
} }
@@ -589,7 +595,7 @@ func (w *TcellWindow) drawBorder() {
var style tcell.Style var style tcell.Style
if w.color { if w.color {
if w.borderStyle.shape == BorderAround { if w.preview {
style = ColPreviewBorder.style() style = ColPreviewBorder.style()
} else { } else {
style = ColBorder.style() style = ColBorder.style()
@@ -598,17 +604,32 @@ func (w *TcellWindow) drawBorder() {
style = w.normal.style() style = w.normal.style()
} }
for x := left; x < right; x++ { switch shape {
_screen.SetContent(x, top, w.borderStyle.horizontal, nil, style) case BorderRounded, BorderSharp, BorderHorizontal, BorderTop:
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style) for x := left; x < right; x++ {
_screen.SetContent(x, top, w.borderStyle.horizontal, nil, style)
}
} }
switch shape {
if w.borderStyle.shape == BorderAround { case BorderRounded, BorderSharp, BorderHorizontal, BorderBottom:
for x := left; x < right; x++ {
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
}
}
switch shape {
case BorderRounded, BorderSharp, BorderVertical, BorderLeft:
for y := top; y < bot; y++ { for y := top; y < bot; y++ {
_screen.SetContent(left, y, w.borderStyle.vertical, nil, style) _screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
}
}
switch shape {
case BorderRounded, BorderSharp, BorderVertical, BorderRight:
for y := top; y < bot; y++ {
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style) _screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
} }
}
switch shape {
case BorderRounded, BorderSharp:
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style) _screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
_screen.SetContent(right-1, top, w.borderStyle.topRight, nil, style) _screen.SetContent(right-1, top, w.borderStyle.topRight, nil, style)
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style) _screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)

View File

@@ -66,6 +66,7 @@ const (
Right Right
Home Home
End End
Insert
SUp SUp
SDown SDown
@@ -86,6 +87,7 @@ const (
F12 F12
Change Change
BackwardEOF
AltSpace AltSpace
AltSlash AltSlash
@@ -121,6 +123,15 @@ func (c Color) is24() bool {
return c > 0 && (c&(1<<24)) > 0 return c > 0 && (c&(1<<24)) > 0
} }
type ColorAttr struct {
Color Color
Attr Attr
}
func NewColorAttr() ColorAttr {
return ColorAttr{Color: colUndefined, Attr: AttrUndefined}
}
const ( const (
colUndefined Color = -2 colUndefined Color = -2
colDefault Color = -1 colDefault Color = -1
@@ -146,9 +157,9 @@ const (
) )
type ColorPair struct { type ColorPair struct {
fg Color fg Color
bg Color bg Color
id int attr Attr
} }
func HexToColor(rrggbb string) Color { func HexToColor(rrggbb string) Color {
@@ -158,8 +169,8 @@ func HexToColor(rrggbb string) Color {
return Color((1 << 24) + (r << 16) + (g << 8) + b) return Color((1 << 24) + (r << 16) + (g << 8) + b)
} }
func NewColorPair(fg Color, bg Color) ColorPair { func NewColorPair(fg Color, bg Color, attr Attr) ColorPair {
return ColorPair{fg, bg, -1} return ColorPair{fg, bg, attr}
} }
func (p ColorPair) Fg() Color { func (p ColorPair) Fg() Color {
@@ -170,23 +181,59 @@ func (p ColorPair) Bg() Color {
return p.bg return p.bg
} }
func (p ColorPair) Attr() Attr {
return p.attr
}
func (p ColorPair) merge(other ColorPair, except Color) ColorPair {
dup := p
dup.attr = dup.attr.Merge(other.attr)
if other.fg != except {
dup.fg = other.fg
}
if other.bg != except {
dup.bg = other.bg
}
return dup
}
func (p ColorPair) WithAttr(attr Attr) ColorPair {
dup := p
dup.attr = dup.attr.Merge(attr)
return dup
}
func (p ColorPair) MergeAttr(other ColorPair) ColorPair {
return p.WithAttr(other.attr)
}
func (p ColorPair) Merge(other ColorPair) ColorPair {
return p.merge(other, colUndefined)
}
func (p ColorPair) MergeNonDefault(other ColorPair) ColorPair {
return p.merge(other, colDefault)
}
type ColorTheme struct { type ColorTheme struct {
Fg Color Colored bool
Bg Color Input ColorAttr
PreviewFg Color Fg ColorAttr
PreviewBg Color Bg ColorAttr
DarkBg Color PreviewFg ColorAttr
Gutter Color PreviewBg ColorAttr
Prompt Color DarkBg ColorAttr
Match Color Gutter ColorAttr
Current Color Prompt ColorAttr
CurrentMatch Color Match ColorAttr
Spinner Color Current ColorAttr
Info Color CurrentMatch ColorAttr
Cursor Color Spinner ColorAttr
Selected Color Info ColorAttr
Header Color Cursor ColorAttr
Border Color Selected ColorAttr
Header ColorAttr
Border ColorAttr
} }
type Event struct { type Event struct {
@@ -209,8 +256,14 @@ type BorderShape int
const ( const (
BorderNone BorderShape = iota BorderNone BorderShape = iota
BorderAround BorderRounded
BorderSharp
BorderHorizontal BorderHorizontal
BorderVertical
BorderTop
BorderBottom
BorderLeft
BorderRight
) )
type BorderStyle struct { type BorderStyle struct {
@@ -227,6 +280,17 @@ type BorderCharacter int
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle { func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
if unicode { if unicode {
if shape == BorderRounded {
return BorderStyle{
shape: shape,
horizontal: '─',
vertical: '│',
topLeft: '╭',
topRight: '╮',
bottomLeft: '╰',
bottomRight: '╯',
}
}
return BorderStyle{ return BorderStyle{
shape: shape, shape: shape,
horizontal: '─', horizontal: '─',
@@ -250,7 +314,7 @@ func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
func MakeTransparentBorder() BorderStyle { func MakeTransparentBorder() BorderStyle {
return BorderStyle{ return BorderStyle{
shape: BorderAround, shape: BorderRounded,
horizontal: ' ', horizontal: ' ',
vertical: ' ', vertical: ' ',
topLeft: ' ', topLeft: ' ',
@@ -262,7 +326,7 @@ func MakeTransparentBorder() BorderStyle {
type Renderer interface { type Renderer interface {
Init() Init()
Pause(clear bool) Pause(clear bool)
Resume(clear bool) Resume(clear bool, sigcont bool)
Clear() Clear()
RefreshWindows(windows []Window) RefreshWindows(windows []Window)
Refresh() Refresh()
@@ -272,7 +336,6 @@ type Renderer interface {
MaxX() int MaxX() int
MaxY() int MaxY() int
DoesAutoWrap() bool
NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window
} }
@@ -294,7 +357,7 @@ type Window interface {
Move(y int, x int) Move(y int, x int)
MoveAndClear(y int, x int) MoveAndClear(y int, x int)
Print(text string) Print(text string)
CPrint(color ColorPair, attr Attr, text string) CPrint(color ColorPair, text string)
Fill(text string) FillReturn Fill(text string) FillReturn
CFill(fg Color, bg Color, attr Attr, text string) FillReturn CFill(fg Color, bg Color, attr Attr, text string) FillReturn
Erase() Erase()
@@ -323,41 +386,69 @@ var (
Dark256 *ColorTheme Dark256 *ColorTheme
Light256 *ColorTheme Light256 *ColorTheme
ColPrompt ColorPair ColPrompt ColorPair
ColNormal ColorPair ColNormal ColorPair
ColMatch ColorPair ColInput ColorPair
ColCursor ColorPair ColMatch ColorPair
ColSelected ColorPair ColCursor ColorPair
ColCurrent ColorPair ColCursorEmpty ColorPair
ColCurrentMatch ColorPair ColSelected ColorPair
ColCurrentCursor ColorPair ColCurrent ColorPair
ColCurrentSelected ColorPair ColCurrentMatch ColorPair
ColSpinner ColorPair ColCurrentCursor ColorPair
ColInfo ColorPair ColCurrentCursorEmpty ColorPair
ColHeader ColorPair ColCurrentSelected ColorPair
ColBorder ColorPair ColCurrentSelectedEmpty ColorPair
ColPreview ColorPair ColSpinner ColorPair
ColPreviewBorder ColorPair ColInfo ColorPair
ColHeader ColorPair
ColBorder ColorPair
ColPreview ColorPair
ColPreviewBorder ColorPair
) )
func EmptyTheme() *ColorTheme { func EmptyTheme() *ColorTheme {
return &ColorTheme{ return &ColorTheme{
Fg: colUndefined, Colored: true,
Bg: colUndefined, Input: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: colUndefined, Fg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: colUndefined, Bg: ColorAttr{colUndefined, AttrUndefined},
DarkBg: colUndefined, PreviewFg: ColorAttr{colUndefined, AttrUndefined},
Gutter: colUndefined, PreviewBg: ColorAttr{colUndefined, AttrUndefined},
Prompt: colUndefined, DarkBg: ColorAttr{colUndefined, AttrUndefined},
Match: colUndefined, Gutter: ColorAttr{colUndefined, AttrUndefined},
Current: colUndefined, Prompt: ColorAttr{colUndefined, AttrUndefined},
CurrentMatch: colUndefined, Match: ColorAttr{colUndefined, AttrUndefined},
Spinner: colUndefined, Current: ColorAttr{colUndefined, AttrUndefined},
Info: colUndefined, CurrentMatch: ColorAttr{colUndefined, AttrUndefined},
Cursor: colUndefined, Spinner: ColorAttr{colUndefined, AttrUndefined},
Selected: colUndefined, Info: ColorAttr{colUndefined, AttrUndefined},
Header: colUndefined, Cursor: ColorAttr{colUndefined, AttrUndefined},
Border: colUndefined} Selected: ColorAttr{colUndefined, AttrUndefined},
Header: ColorAttr{colUndefined, AttrUndefined},
Border: ColorAttr{colUndefined, AttrUndefined}}
}
func NoColorTheme() *ColorTheme {
return &ColorTheme{
Colored: false,
Input: ColorAttr{colDefault, AttrRegular},
Fg: ColorAttr{colDefault, AttrRegular},
Bg: ColorAttr{colDefault, AttrRegular},
PreviewFg: ColorAttr{colDefault, AttrRegular},
PreviewBg: ColorAttr{colDefault, AttrRegular},
DarkBg: ColorAttr{colDefault, AttrRegular},
Gutter: ColorAttr{colDefault, AttrRegular},
Prompt: ColorAttr{colDefault, AttrRegular},
Match: ColorAttr{colDefault, Underline},
Current: ColorAttr{colDefault, Reverse},
CurrentMatch: ColorAttr{colDefault, Reverse | Underline},
Spinner: ColorAttr{colDefault, AttrRegular},
Info: ColorAttr{colDefault, AttrRegular},
Cursor: ColorAttr{colDefault, AttrRegular},
Selected: ColorAttr{colDefault, AttrRegular},
Header: ColorAttr{colDefault, AttrRegular},
Border: ColorAttr{colDefault, AttrRegular}}
} }
func errorExit(message string) { func errorExit(message string) {
@@ -367,74 +458,80 @@ func errorExit(message string) {
func init() { func init() {
Default16 = &ColorTheme{ Default16 = &ColorTheme{
Fg: colDefault, Colored: true,
Bg: colDefault, Input: ColorAttr{colDefault, AttrUndefined},
PreviewFg: colUndefined, Fg: ColorAttr{colDefault, AttrUndefined},
PreviewBg: colUndefined, Bg: ColorAttr{colDefault, AttrUndefined},
DarkBg: colBlack, PreviewFg: ColorAttr{colUndefined, AttrUndefined},
Gutter: colUndefined, PreviewBg: ColorAttr{colUndefined, AttrUndefined},
Prompt: colBlue, DarkBg: ColorAttr{colBlack, AttrUndefined},
Match: colGreen, Gutter: ColorAttr{colUndefined, AttrUndefined},
Current: colYellow, Prompt: ColorAttr{colBlue, AttrUndefined},
CurrentMatch: colGreen, Match: ColorAttr{colGreen, AttrUndefined},
Spinner: colGreen, Current: ColorAttr{colYellow, AttrUndefined},
Info: colWhite, CurrentMatch: ColorAttr{colGreen, AttrUndefined},
Cursor: colRed, Spinner: ColorAttr{colGreen, AttrUndefined},
Selected: colMagenta, Info: ColorAttr{colWhite, AttrUndefined},
Header: colCyan, Cursor: ColorAttr{colRed, AttrUndefined},
Border: colBlack} Selected: ColorAttr{colMagenta, AttrUndefined},
Header: ColorAttr{colCyan, AttrUndefined},
Border: ColorAttr{colBlack, AttrUndefined}}
Dark256 = &ColorTheme{ Dark256 = &ColorTheme{
Fg: colDefault, Colored: true,
Bg: colDefault, Input: ColorAttr{colDefault, AttrUndefined},
PreviewFg: colUndefined, Fg: ColorAttr{colDefault, AttrUndefined},
PreviewBg: colUndefined, Bg: ColorAttr{colDefault, AttrUndefined},
DarkBg: 236, PreviewFg: ColorAttr{colUndefined, AttrUndefined},
Gutter: colUndefined, PreviewBg: ColorAttr{colUndefined, AttrUndefined},
Prompt: 110, DarkBg: ColorAttr{236, AttrUndefined},
Match: 108, Gutter: ColorAttr{colUndefined, AttrUndefined},
Current: 254, Prompt: ColorAttr{110, AttrUndefined},
CurrentMatch: 151, Match: ColorAttr{108, AttrUndefined},
Spinner: 148, Current: ColorAttr{254, AttrUndefined},
Info: 144, CurrentMatch: ColorAttr{151, AttrUndefined},
Cursor: 161, Spinner: ColorAttr{148, AttrUndefined},
Selected: 168, Info: ColorAttr{144, AttrUndefined},
Header: 109, Cursor: ColorAttr{161, AttrUndefined},
Border: 59} Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{109, AttrUndefined},
Border: ColorAttr{59, AttrUndefined}}
Light256 = &ColorTheme{ Light256 = &ColorTheme{
Fg: colDefault, Colored: true,
Bg: colDefault, Input: ColorAttr{colDefault, AttrUndefined},
PreviewFg: colUndefined, Fg: ColorAttr{colDefault, AttrUndefined},
PreviewBg: colUndefined, Bg: ColorAttr{colDefault, AttrUndefined},
DarkBg: 251, PreviewFg: ColorAttr{colUndefined, AttrUndefined},
Gutter: colUndefined, PreviewBg: ColorAttr{colUndefined, AttrUndefined},
Prompt: 25, DarkBg: ColorAttr{251, AttrUndefined},
Match: 66, Gutter: ColorAttr{colUndefined, AttrUndefined},
Current: 237, Prompt: ColorAttr{25, AttrUndefined},
CurrentMatch: 23, Match: ColorAttr{66, AttrUndefined},
Spinner: 65, Current: ColorAttr{237, AttrUndefined},
Info: 101, CurrentMatch: ColorAttr{23, AttrUndefined},
Cursor: 161, Spinner: ColorAttr{65, AttrUndefined},
Selected: 168, Info: ColorAttr{101, AttrUndefined},
Header: 31, Cursor: ColorAttr{161, AttrUndefined},
Border: 145} Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{31, AttrUndefined},
Border: ColorAttr{145, AttrUndefined}}
} }
func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) { func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
if theme == nil {
initPalette(theme)
return
}
if forceBlack { if forceBlack {
theme.Bg = colBlack theme.Bg = ColorAttr{colBlack, AttrUndefined}
} }
o := func(a Color, b Color) Color { o := func(a ColorAttr, b ColorAttr) ColorAttr {
if b == colUndefined { c := a
return a if b.Color != colUndefined {
c.Color = b.Color
} }
return b if b.Attr != AttrUndefined {
c.Attr = b.Attr
}
return c
} }
theme.Input = o(baseTheme.Input, theme.Input)
theme.Fg = o(baseTheme.Fg, theme.Fg) theme.Fg = o(baseTheme.Fg, theme.Fg)
theme.Bg = o(baseTheme.Bg, theme.Bg) theme.Bg = o(baseTheme.Bg, theme.Bg)
theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg)) theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg))
@@ -456,54 +553,32 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
} }
func initPalette(theme *ColorTheme) { func initPalette(theme *ColorTheme) {
idx := 0 pair := func(fg, bg ColorAttr) ColorPair {
pair := func(fg, bg Color) ColorPair { if fg.Color == colDefault && (fg.Attr&Reverse) > 0 {
idx++ bg.Color = colDefault
return ColorPair{fg, bg, idx} }
return ColorPair{fg.Color, bg.Color, fg.Attr}
} }
if theme != nil { blank := theme.Fg
ColPrompt = pair(theme.Prompt, theme.Bg) blank.Attr = AttrRegular
ColNormal = pair(theme.Fg, theme.Bg)
ColMatch = pair(theme.Match, theme.Bg)
ColCursor = pair(theme.Cursor, theme.Gutter)
ColSelected = pair(theme.Selected, theme.Gutter)
ColCurrent = pair(theme.Current, theme.DarkBg)
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
ColCurrentSelected = pair(theme.Selected, theme.DarkBg)
ColSpinner = pair(theme.Spinner, theme.Bg)
ColInfo = pair(theme.Info, theme.Bg)
ColHeader = pair(theme.Header, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg)
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
} else {
ColPrompt = pair(colDefault, colDefault)
ColNormal = pair(colDefault, colDefault)
ColMatch = pair(colDefault, colDefault)
ColCursor = pair(colDefault, colDefault)
ColSelected = pair(colDefault, colDefault)
ColCurrent = pair(colDefault, colDefault)
ColCurrentMatch = pair(colDefault, colDefault)
ColCurrentCursor = pair(colDefault, colDefault)
ColCurrentSelected = pair(colDefault, colDefault)
ColSpinner = pair(colDefault, colDefault)
ColInfo = pair(colDefault, colDefault)
ColHeader = pair(colDefault, colDefault)
ColBorder = pair(colDefault, colDefault)
ColPreview = pair(colDefault, colDefault)
ColPreviewBorder = pair(colDefault, colDefault)
}
}
func attrFor(color ColorPair, attr Attr) Attr { ColPrompt = pair(theme.Prompt, theme.Bg)
switch color { ColNormal = pair(theme.Fg, theme.Bg)
case ColCurrent: ColInput = pair(theme.Input, theme.Bg)
return attr | Reverse ColMatch = pair(theme.Match, theme.Bg)
case ColMatch: ColCursor = pair(theme.Cursor, theme.Gutter)
return attr | Underline ColCursorEmpty = pair(blank, theme.Gutter)
case ColCurrentMatch: ColSelected = pair(theme.Selected, theme.Gutter)
return attr | Underline | Reverse ColCurrent = pair(theme.Current, theme.DarkBg)
} ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
return attr ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
ColCurrentCursorEmpty = pair(blank, theme.DarkBg)
ColCurrentSelected = pair(theme.Selected, theme.DarkBg)
ColCurrentSelectedEmpty = pair(blank, theme.DarkBg)
ColSpinner = pair(theme.Spinner, theme.Bg)
ColInfo = pair(theme.Info, theme.Bg)
ColHeader = pair(theme.Header, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg)
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
} }

View File

@@ -1,45 +0,0 @@
#!/usr/bin/env ruby
# http://www.rubydoc.info/github/rest-client/rest-client/RestClient
require 'rest_client'
require 'json'
if ARGV.length < 3
puts "usage: #$0 <token> <version> <files...>"
exit 1
end
token, version, *files = ARGV
base = "https://api.github.com/repos/junegunn/fzf-bin/releases"
# List releases
rels = JSON.parse(RestClient.get(base, :authorization => "token #{token}"))
rel = rels.find { |r| r['tag_name'] == version }
unless rel
puts "#{version} not found"
exit 1
end
# List assets
assets = Hash[rel['assets'].map { |a| a.values_at *%w[name id] }]
files.select { |f| File.exists? f }.map do |file|
Thread.new do
name = File.basename file
if asset_id = assets[name]
puts "#{name} found. Deleting asset id #{asset_id}."
RestClient.delete "#{base}/assets/#{asset_id}",
:authorization => "token #{token}"
else
puts "#{name} not found"
end
puts "Uploading #{name}"
RestClient.post(
"#{base.sub 'api', 'uploads'}/#{rel['id']}/assets?name=#{name}",
File.read(file),
:authorization => "token #{token}",
:content_type => "application/octet-stream")
end
end.each(&:join)

View File

@@ -1,32 +1,34 @@
package util package util
import "sync" import (
"sync/atomic"
)
func convertBoolToInt32(b bool) int32 {
if b {
return 1
}
return 0
}
// AtomicBool is a boxed-class that provides synchronized access to the // AtomicBool is a boxed-class that provides synchronized access to the
// underlying boolean value // underlying boolean value
type AtomicBool struct { type AtomicBool struct {
mutex sync.Mutex state int32 // "1" is true, "0" is false
state bool
} }
// NewAtomicBool returns a new AtomicBool // NewAtomicBool returns a new AtomicBool
func NewAtomicBool(initialState bool) *AtomicBool { func NewAtomicBool(initialState bool) *AtomicBool {
return &AtomicBool{ return &AtomicBool{state: convertBoolToInt32(initialState)}
mutex: sync.Mutex{},
state: initialState}
} }
// Get returns the current boolean value synchronously // Get returns the current boolean value synchronously
func (a *AtomicBool) Get() bool { func (a *AtomicBool) Get() bool {
a.mutex.Lock() return atomic.LoadInt32(&a.state) == 1
defer a.mutex.Unlock()
return a.state
} }
// Set updates the boolean value synchronously // Set updates the boolean value synchronously
func (a *AtomicBool) Set(newState bool) bool { func (a *AtomicBool) Set(newState bool) bool {
a.mutex.Lock() atomic.StoreInt32(&a.state, convertBoolToInt32(newState))
defer a.mutex.Unlock() return newState
a.state = newState
return a.state
} }

View File

@@ -130,6 +130,18 @@ func (chars *Chars) TrimLength() uint16 {
return chars.trimLength return chars.trimLength
} }
func (chars *Chars) LeadingWhitespaces() int {
whitespaces := 0
for i := 0; i < chars.Length(); i++ {
char := chars.Get(i)
if !unicode.IsSpace(char) {
break
}
whitespaces++
}
return whitespaces
}
func (chars *Chars) TrailingWhitespaces() int { func (chars *Chars) TrailingWhitespaces() int {
whitespaces := 0 whitespaces := 0
for i := chars.Length() - 1; i >= 0; i-- { for i := chars.Length() - 1; i >= 0; i-- {

File diff suppressed because it is too large Load Diff