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

Compare commits

...

110 Commits

Author SHA1 Message Date
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
Junegunn Choi
30577b0c17 0.20.0 2019-12-18 01:07:25 +09:00
Junegunn Choi
212de25409 Fix incorrect header array mutation 2019-12-16 18:47:05 +09:00
Jan Edmund Lazo
5da8bbf45a [vim] Encode list source to codepage (#1794) 2019-12-16 14:41:03 +09:00
Jan Edmund Lazo
aa0e10ead7 [vim] Use cterm colors on Windows (#1793)
Truecolor does not work on default Windows terminal.
It is a problem in neovim GUIs.

https://github.com/sainnhe/edge/issues/5#issuecomment-565748240
2019-12-15 22:17:24 +09:00
msr1k
a9906c7c29 Add MSYS2 support as a vim plugin (#1677)
* Add MSYS2 support as a vim plugin

Add &shellcmdflag and TERM environment variable treatment.

- Make &shellcmdflag `/C` when &shell turns into `cmd.exe`
- Delete %TERM% environment variable before fzf execution

* Change shellescape default value depending on s:is_win flag

* Make TERM environment empty only when gui is running

* Stop checking &shell in fzf#shellescape function

This funcion's behavior is controlled by only if it is Windows or not.
So there is no need to check &shell.

* Take neovim into consideration when to set shellcmdflag

* Add &shellxquote control
2019-12-15 18:25:58 +09:00
Junegunn Choi
9fefe08b3f Revert README as preview-{fg,bg} are only available on master 2019-12-13 12:46:27 +09:00
Junegunn Choi
684bfff713 Update README/CHANGELOG 2019-12-13 12:43:34 +09:00
Junegunn Choi
3db6b88d82 Add preview-fg and preview-bg for --color
Close #1776
2019-12-12 23:03:17 +09:00
Junegunn Choi
8ae96774df Gutter color of 16-color theme should be undefined by default 2019-12-12 22:53:28 +09:00
Junegunn Choi
f68017d21e [windows/vim] Encode batchfile in current codepage
Backport https://github.com/junegunn/vim-plug/pull/913
2019-12-12 12:29:14 +09:00
Junegunn Choi
2b725a4db5 Defer resetting multi-selection on reload 2019-12-09 21:32:58 +09:00
Junegunn Choi
af1a5f130b Add clear-query and clear-selection
Close #1787
Related #1364
2019-12-07 14:44:24 +09:00
Junegunn Choi
86e3994e87 Properly clear list when --header-lines not filled on reload 2019-12-06 22:34:45 +09:00
Junegunn Choi
1e6ac5590e 'reload' action should be allowed even where there's no match
If the command template doesn't have any placeholder expressions.

    : | fzf --bind 'space:reload:seq 10'
2019-12-06 22:34:30 +09:00
Henré Botha
5e42b1c9f8 [ssh completion] Skip only aliases matching * (#1788)
This commit fixes a bug where lines that declare multiple hostnames get
omitted from completion entirely if one of the hostnames matches *. For
example:

	Host foo.com bar.dev baz.*
2019-12-06 17:58:53 +09:00
Junegunn Choi
9d842630c9 Mention _fzf_setup_completion helper function for bash 2019-12-06 12:03:34 +09:00
David Gray
77cb906dfe [completion] Add support for HostName lines in ~/.ssh/config (#1785)
Close #1783
2019-12-06 10:14:15 +09:00
Junegunn Choi
a59e846f74 Update installation instruction
Close #1707
Close #1779
2019-12-05 23:10:41 +09:00
infokiller
6e6340a0c9 Ignore zcompile output files (*.zwc files) (#1775) 2019-12-05 22:27:30 +09:00
John Purnell
357e82e51b [completion] Ignore hg repos (#1777)
* Update completion.bash
* Update completion.zsh
2019-12-05 22:27:04 +09:00
Junegunn Choi
394d8cfd18 Remove immediate flickering on reload action 2019-12-05 22:27:18 +09:00
Junegunn Choi
ef80bd401f Update installation instruction using Linux package managers
Added NixOS instruction. Close #1731
2019-12-01 23:48:48 +09:00
midchildan
f51d61d57a [zsh] Prevent the current directory from appearing as ~dir in prompts (#1774)
The zsh version of the cd widget sets the variable `dir` to the path of
the target directory before invoking `cd`. This causes zsh to treat the
target directory as a named directory, which has the effect of zsh
substituting '%~' with '~dir' instead of the proper path when it
performs prompt expansion.

This commit will cause the widget to unset `dir` before redrawing the
prompt to fix this issue.

Details of zsh prompt expansion can be found in:
http://zsh.sourceforge.net/Doc/Release/Prompt-Expansion.html
2019-12-01 23:20:26 +09:00
Junegunn Choi
1dd256a68a Update README-VIM 2019-11-29 18:32:49 +09:00
Junegunn Choi
85644aa3fb Revamp README-VIM.md 2019-11-23 01:54:52 +09:00
Junegunn Choi
effbc258bb Update CHANGELOG 2019-11-21 23:06:14 +09:00
Junegunn Choi
e615600ff1 Allow action composition over multiple --bind
# Note + prefix in the second bind expression
  fzf --bind u:up --bind u:+up
  fzf --bind u:up+up
2019-11-21 23:06:14 +09:00
Junegunn Choi
60465c4664 Fix parse error of --bind expression 2019-11-21 23:06:13 +09:00
Jan Edmund Lazo
c03c058bd5 [install] Support busybox uname on Windows (#1758) 2019-11-16 22:23:42 +09:00
Junegunn Choi
7238c8944d Update CHANGELOG 2019-11-16 01:23:01 +09:00
46 changed files with 2723 additions and 1149 deletions

2
.gitignore vendored
View File

@@ -7,3 +7,5 @@ Gemfile.lock
doc/tags doc/tags
vendor vendor
gopath gopath
*.zwc
fzf

View File

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

View File

@@ -1,9 +1,114 @@
CHANGELOG CHANGELOG
========= =========
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
------
- Customizable preview window color (`preview-fg` and `preview-bg` for `--color`)
```sh
fzf --preview 'cat {}' \
--color 'fg:#bbccdd,fg+:#ddeeff,bg:#334455,preview-bg:#223344,border:#778899' \
--border --height 20 --layout reverse --info inline
```
- Removed the immediate flicking of the screen on `reload` action.
```sh
: | fzf --bind 'change:reload:seq {q}' --phony
```
- Added `clear-query` and `clear-selection` actions for `--bind`
- It is now possible to split a composite bind action over multiple `--bind`
expressions by prefixing the later ones with `+`.
```sh
fzf --bind 'ctrl-a:up+up'
# Can be now written as
fzf --bind 'ctrl-a:up' --bind 'ctrl-a:+up'
# This is useful when you need to write special execute/reload form (i.e. `execute:...`)
# to avoid parse errors and add more actions to the same key
fzf --multi --bind 'ctrl-l:select-all+execute:less {+f}' --bind 'ctrl-l:+deselect-all'
```
- Fixed parse error of `--bind` expression where concatenated execute/reload
action contains `+` character.
```sh
fzf --multi --bind 'ctrl-l:select-all+execute(less {+f})+deselect-all'
```
- Fixed bugs of reload action
- Not triggered when there's no match even when the command doesn't have
any placeholder expressions
- Screen not properly cleared when `--header-lines` not filled on reload
0.19.0 0.19.0
------ ------
- Added `--phony` option which completely disables search functionality.
Useful when you want to use fzf only as a selector interface. See below.
- Added "reload" action for dynamically updating the input list without - Added "reload" action for dynamically updating the input list without
restarting fzf. See https://github.com/junegunn/fzf/issues/1750 to learn restarting fzf. See https://github.com/junegunn/fzf/issues/1750 to learn
more about it. more about it.
@@ -11,7 +116,7 @@ CHANGELOG
# Using fzf as the selector interface for ripgrep # Using fzf as the selector interface for ripgrep
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case " RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="foo" INITIAL_QUERY="foo"
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \ FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY' || true" \
fzf --bind "change:reload:$RG_PREFIX {q} || true" \ fzf --bind "change:reload:$RG_PREFIX {q} || true" \
--ansi --phony --query "$INITIAL_QUERY" --ansi --phony --query "$INITIAL_QUERY"
``` ```

View File

@@ -1,5 +1,5 @@
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 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

View File

@@ -1,16 +1,77 @@
FZF Vim integration FZF Vim integration
=================== ===================
This repository only enables basic integration with Vim. If you're looking for Installation
more, check out [fzf.vim](https://github.com/junegunn/fzf.vim) project. ------------
(Note: To use fzf in GVim, an external terminal emulator is required.) 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
-------
The Vim plugin of fzf provides two core functions, and `:FZF` command which is
the basic file selector command built on top of them.
1. **`fzf#run([spec dict])`**
- Starts fzf inside Vim with the given spec
- `:call fzf#run({'source': 'ls'})`
2. **`fzf#wrap([spec dict]) -> (dict)`**
- Takes a spec for `fzf#run` and returns an extended version of it with
additional options for addressing global preferences (`g:fzf_xxx`)
- `:echo fzf#wrap({'source': 'ls'})`
- We usually *wrap* a spec with `fzf#wrap` before passing it to `fzf#run`
- `:call fzf#run(fzf#wrap({'source': 'ls'}))`
3. **`:FZF [fzf_options string] [path string]`**
- Basic fuzzy file selector
- A reference implementation for those who don't want to write VimScript
to implement custom commands
- If you're looking for more such commands, check out [fzf.vim](https://github.com/junegunn/fzf.vim) project.
The most important of all is `fzf#run`, but it would be easier to understand
the whole if we start off with `:FZF` command.
`:FZF[!]` `:FZF[!]`
--------- ---------
If you have set up fzf for Vim, `:FZF` command will be added.
```vim ```vim
" Look for files under current directory " Look for files under current directory
:FZF :FZF
@@ -18,8 +79,8 @@ If you have set up fzf for Vim, `:FZF` command will be added.
" Look for files under your home directory " Look for files under your home directory
:FZF ~ :FZF ~
" With options " With fzf command-line options
:FZF --no-sort --reverse --inline-info /tmp :FZF --reverse --info=inline /tmp
" Bang version starts fzf in fullscreen mode " Bang version starts fzf in fullscreen mode
:FZF! :FZF!
@@ -75,6 +136,7 @@ let g:fzf_layout = { 'window': '-tabnew' }
let g:fzf_layout = { 'window': '10new' } let g:fzf_layout = { 'window': '10new' }
" Customize fzf colors to match your color scheme " Customize fzf colors to match your color scheme
" - fzf#wrap translates this to a set of `--color` options
let g:fzf_colors = let g:fzf_colors =
\ { 'fg': ['fg', 'Normal'], \ { 'fg': ['fg', 'Normal'],
\ 'bg': ['bg', 'Normal'], \ 'bg': ['bg', 'Normal'],
@@ -90,69 +152,109 @@ let g:fzf_colors =
\ 'spinner': ['fg', 'Label'], \ 'spinner': ['fg', 'Label'],
\ 'header': ['fg', 'Comment'] } \ 'header': ['fg', 'Comment'] }
" Enable per-command history. " Enable per-command history
" CTRL-N and CTRL-P will be automatically bound to next-history and " - History files will be stored in the specified directory
" previous-history instead of down and up. If you don't like the change, " - When set, CTRL-N and CTRL-P will be bound to 'next-history' and
" explicitly bind the keys to down and up in your $FZF_DEFAULT_OPTS. " 'previous-history' instead of 'down' and 'up'.
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`
--------- ---------
For more advanced uses, you can use `fzf#run([options])` function.
`fzf#run()` function is the core of Vim integration. It takes a single `fzf#run()` function is the core of Vim integration. It takes a single
dictionary argument. At the very least, specify `sink` option to tell what it dictionary argument, *a spec*, and starts fzf process accordingly. At the very
should do with the selected entry. least, specify `sink` option to tell what it should do with the selected
entry.
```vim ```vim
call fzf#run({'sink': 'e'}) call fzf#run({'sink': 'e'})
``` ```
Without `source`, fzf will use find command (or `$FZF_DEFAULT_COMMAND` if We haven't specified the `source`, so this is equivalent to starting fzf on
defined) to list the files under the current directory. When you select one, command line without standard input pipe; fzf will use find command (or
it will open it with `:e` command. If you want to open it in a new tab, you `$FZF_DEFAULT_COMMAND` if defined) to list the files under the current
can pass `:tabedit` command instead as the sink. directory. When you select one, it will open it with the sink, `:e` command.
If you want to open it in a new tab, you can pass `:tabedit` command instead
as the sink.
```vim ```vim
call fzf#run({'sink': 'tabedit'}) call fzf#run({'sink': 'tabedit'})
``` ```
fzf allows you to select multiple entries with `--multi` (or `-m`) option, and
you can change its bottom-up layout with `--reverse` option. Such options can
be specified as `options`.
```vim
call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})
```
Instead of using the default find command, you can use any shell command as Instead of using the default find command, you can use any shell command as
the source. This will list the files managed by git. the source. The following example will list the files managed by git. It's
equivalent to running `git ls-files | fzf` on shell.
```vim ```vim
call fzf#run({'source': 'git ls-files', 'sink': 'e'}) call fzf#run({'source': 'git ls-files', 'sink': 'e'})
``` ```
Pass a layout option if you don't want fzf window to take up the entire screen. fzf options can be specified as `options` entry in spec dictionary.
```vim
call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})
```
You can also pass a layout option if you don't want fzf window to take up the
entire screen.
```vim ```vim
" up / down / left / right / window are allowed " up / down / left / right / window are allowed
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'right': '40%'}) call fzf#run({'source': 'git ls-files', 'sink': 'e', 'left': '40%'})
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vnew'}) call fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vnew'})
``` ```
`source` doesn't have to be an external shell command, you can pass a Vim `source` doesn't have to be an external shell command, you can pass a Vim
array as the source. In the following example, we use the names of the open array as the source. In the next example, we pass the names of color
buffers as the source. schemes as the source to implement a color scheme selector.
```vim
call fzf#run({'source': map(filter(range(1, bufnr('$')), 'buflisted(v:val)'),
\ 'bufname(v:val)'),
\ 'sink': 'e', 'down': '30%'})
```
Or the names of color schemes.
```vim ```vim
call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')), call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
@@ -160,85 +262,156 @@ call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
\ 'sink': 'colo', 'left': '25%'}) \ 'sink': 'colo', 'left': '25%'})
``` ```
The following table shows the available options. The following table summarizes the available options.
| Option name | Type | Description | | Option name | Type | Description |
| -------------------------- | ------------- | ---------------------------------------------------------------- | | -------------------------- | ------------- | ---------------------------------------------------------------- |
| `source` | string | External command to generate input to fzf (e.g. `find .`) | | `source` | string | External command to generate input to fzf (e.g. `find .`) |
| `source` | list | Vim list as input to fzf | | `source` | list | Vim list as input to fzf |
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) | | `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
| `sink` | funcref | Reference to function to process each selected item | | `sink` | funcref | Reference to function to process each selected item |
| `sink*` | funcref | Similar to `sink`, but takes the list of output lines at once | | `sink*` | funcref | Similar to `sink`, but takes the list of output lines at once |
| `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 | Use tmux pane with the given size (e.g. `20`, `50%`) | | `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
| `window` (Vim 8 / Neovim) | string | 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`) |
| `launcher` | string | External terminal emulator to start fzf with (GVim only) | | `window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}`) |
| `launcher` | funcref | Function for generating `launcher` string (GVim only) |
`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 if you're concerned about escaping should suffice, but prefer to use list type to avoid escaping issues.
issues on different platforms.
```vim ```vim
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]]
- `height` [float range [0 ~ 1]]
- 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`
---------- ----------
`:FZF` command provided by default knows how to handle `CTRL-T`, `CTRL-X`, and We have seen that several aspects of `:FZF` command can be configured with
`CTRL-V` and opens the selected file in a new tab, in a horizontal split, or a set of global option variables; different ways to open files
in a vertical split respectively. And these key bindings can be configured via (`g:fzf_action`), window position and size (`g:fzf_layout`), color palette
`g:fzf_action`. This is implemented using `--expect` option of fzf and the (`g:fzf_colors`), etc.
smart sink function. It also understands `g:fzf_colors`, `g:fzf_layout` and
`g:fzf_history_dir`. However, `fzf#run` doesn't know about any of these
options.
By *"wrapping"* your options dictionary with `fzf#wrap` before passing it to So how can we make our custom `fzf#run` calls also respect those variables?
`fzf#run`, you can make your command also support the options. Simply by *"wrapping"* the spec dictionary with `fzf#wrap` before passing it
to `fzf#run`.
- **`fzf#wrap([name string], [spec dict], [fullscreen bool]) -> (dict)`**
- All arguments are optional. Usually we only need to pass a spec dictionary.
- `name` is for managing history files. It is ignored if
`g:fzf_history_dir` is not defined.
- `fullscreen` can be either `0` or `1` (default: 0).
`fzf#wrap` takes a spec and returns an extended version of it (also
a dictionary) with additional options for addressing global preferences. You
can examine the return value of it like so:
```vim ```vim
" Usage: echo fzf#wrap({'source': 'ls'})
" fzf#wrap([name string,] [opts dict,] [fullscreen boolean])
" This command now supports CTRL-T, CTRL-V, and CTRL-X key bindings
" and opens fzf according to g:fzf_layout setting.
command! Buffers call fzf#run(fzf#wrap(
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}))
" This extends the above example to open fzf in fullscreen
" when the command is run with ! suffix (Buffers!)
command! -bang Buffers call fzf#run(fzf#wrap(
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0))
" You can optionally pass the name of the command as the first argument to
" fzf#wrap to make it work with g:fzf_history_dir
command! -bang Buffers call fzf#run(fzf#wrap('buffers',
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0))
``` ```
fzf inside terminal buffer After we *"wrap"* our spec, we pass it to `fzf#run`.
--------------------------
```vim
call fzf#run(fzf#wrap({'source': 'ls'}))
```
Now it supports `CTRL-T`, `CTRL-V`, and `CTRL-X` key bindings and it opens fzf
window according to `g:fzf_layout` setting.
To make it easier to use, let's define `LS` command.
```vim
command! LS call fzf#run(fzf#wrap({'source': 'ls'}))
```
Type `:LS` and see how it works.
We would like to make `:LS!` (bang version) open fzf in fullscreen, just like
`:FZF!`. Add `-bang` to command definition, and use `<bang>` value to set
the last `fullscreen` argument of `fzf#wrap` (see `:help <bang>`).
```vim
" On :LS!, <bang> evaluates to '!', and '!0' becomes 1
command! -bang LS call fzf#run(fzf#wrap({'source': 'ls'}, <bang>0))
```
Our `:LS` command will be much more useful if we can pass a directory argument
to it, so that something like `:LS /tmp` is possible.
```vim
command! -bang -complete=dir -nargs=* LS
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
```
Lastly, if you have enabled `g:fzf_history_dir`, you might want to assign
a unique name to our command and pass it as the first argument to `fzf#wrap`.
```vim
" The query history for this command will be stored as 'ls' inside g:fzf_history_dir.
" The name is ignored if g:fzf_history_dir is not defined.
command! -bang -complete=dir -nargs=* LS
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
```
Tips
----
### fzf inside terminal buffer
The latest versions of Vim and Neovim include builtin terminal emulator The latest versions of Vim and Neovim include builtin terminal emulator
(`:terminal`) and fzf will start in a terminal buffer in the following cases: (`:terminal`) and fzf will start in a terminal buffer in the following cases:
- On Neovim - On Neovim
- On GVim - On GVim
- On Terminal Vim with the 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%'}`
### Hide statusline #### Starting fzf in a popup window
When fzf starts in a terminal buffer, you may want to hide the statusline of
the containing buffer.
```vim ```vim
autocmd! FileType fzf " Required:
autocmd FileType fzf set laststatus=0 noshowmode noruler " - width [float range [0 ~ 1]]
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler " - height [float range [0 ~ 1]]
"
" 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 } }
```
#### Hide statusline
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
the window.
For example, if you use the default layout (`{'down': '~40%'}`) on Neovim, you
might want to temporarily disable the statusline for a cleaner look.
```vim
if has('nvim') && !exists('g:fzf_layout')
autocmd! FileType fzf
autocmd FileType fzf set laststatus=0 noshowmode noruler
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
endif
``` ```
[License](LICENSE) [License](LICENSE)
@@ -246,4 +419,4 @@ autocmd FileType fzf set laststatus=0 noshowmode noruler
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2017 Junegunn Choi Copyright (c) 2013-2020 Junegunn Choi

313
README.md
View File

@@ -25,13 +25,9 @@ Table of Contents
* [Installation](#installation) * [Installation](#installation)
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew) * [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
* [Using git](#using-git) * [Using git](#using-git)
* [As Vim plugin](#as-vim-plugin) * [Using Linux package managers](#using-linux-package-managers)
* [Arch Linux](#arch-linux)
* [Debian](#debian)
* [Fedora](#fedora)
* [openSUSE](#opensuse)
* [FreeBSD](#freebsd)
* [Windows](#windows) * [Windows](#windows)
* [As Vim plugin](#as-vim-plugin)
* [Upgrading fzf](#upgrading-fzf) * [Upgrading fzf](#upgrading-fzf)
* [Building fzf](#building-fzf) * [Building fzf](#building-fzf)
* [Usage](#usage) * [Usage](#usage)
@@ -51,6 +47,7 @@ Table of Contents
* [Environment variables / Aliases](#environment-variables--aliases) * [Environment variables / Aliases](#environment-variables--aliases)
* [Settings](#settings) * [Settings](#settings)
* [Supported commands](#supported-commands) * [Supported commands](#supported-commands)
* [Custom fuzzy completion](#custom-fuzzy-completion)
* [Vim plugin](#vim-plugin) * [Vim plugin](#vim-plugin)
* [Advanced topics](#advanced-topics) * [Advanced topics](#advanced-topics)
* [Performance](#performance) * [Performance](#performance)
@@ -58,7 +55,6 @@ Table of Contents
* [Preview window](#preview-window) * [Preview window](#preview-window)
* [Tips](#tips) * [Tips](#tips)
* [Respecting .gitignore](#respecting-gitignore) * [Respecting .gitignore](#respecting-gitignore)
* [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal)
* [Fish shell](#fish-shell) * [Fish shell](#fish-shell)
* [Related projects](#related-projects) * [Related projects](#related-projects)
* [<a href="LICENSE">License</a>](#license) * [<a href="LICENSE">License</a>](#license)
@@ -106,111 +102,55 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install ~/.fzf/install
``` ```
### As Vim plugin ### Using Linux package managers
Once you have fzf installed, you can enable it inside Vim simply by adding the | Distro | Command |
directory to `&runtimepath` in your Vim configuration file as follows: | --- | --- |
| Alpine Linux | `sudo apk add fzf` |
| Arch Linux | `sudo pacman -S fzf` |
| Debian | `sudo apt-get install fzf` |
| Fedora | `sudo dnf install fzf` |
| FreeBSD | `pkg install fzf` |
| NixOS | `nix-env -iA nixpkgs.fzf` |
| openSUSE | `sudo zypper install fzf` |
| OpenBSD | `pkg_add fzf` |
```vim Shell extensions (key bindings and fuzzy auto-completion) and Vim/Neovim
" If installed using Homebrew plugin may or may not be enabled by default depending on the package manager.
set rtp+=/usr/local/opt/fzf Refer to the package documentation for more information.
" 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 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.
```
### Arch Linux
```sh
sudo pacman -S fzf
```
### Debian
fzf is available in Debian Buster and above, and can be installed using the usual
method:
```sh
sudo apt-get install fzf
```
Read the documentation (/usr/share/doc/fzf/README.Debian) on how to enable it.
### Fedora
fzf is available in Fedora 26 and above, and can be installed using the usual
method:
```sh
sudo dnf install fzf
```
Shell completion and plugins for vim or neovim are enabled by default. Shell
key bindings are installed but not enabled by default. See Fedora's package
documentation (/usr/share/doc/fzf/README.Fedora) for more information.
### openSUSE
fzf is available in openSUSE Tumbleweed and can be installed via zypper:
```sh
sudo zypper install fzf
```
### FreeBSD
```sh
pkg install fzf
```
### Windows ### Windows
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
available as a [Chocolatey package][choco]: available via [Chocolatey][choco] and [Scoop][scoop]:
| Package manager | Command |
| --- | --- |
| Chocolatey | `choco install fzf` |
| Scoop | `scoop install fzf` |
[choco]: https://chocolatey.org/packages/fzf [choco]: https://chocolatey.org/packages/fzf
```sh
choco install fzf
```
or a [Scoop package][scoop]:
[scoop]: https://github.com/ScoopInstaller/Main/blob/master/bucket/fzf.json [scoop]: https://github.com/ScoopInstaller/Main/blob/master/bucket/fzf.json
```sh Known issues and limitations on Windows can be found on [the wiki
scoop install fzf page][windows-wiki].
```
However, other components of the project may not work on Windows. Known issues
and limitations can be found on [the wiki page][windows-wiki]. You might want
to consider installing fzf on [Windows Subsystem for Linux][wsl] where
everything runs flawlessly.
[windows-wiki]: https://github.com/junegunn/fzf/wiki/Windows [windows-wiki]: https://github.com/junegunn/fzf/wiki/Windows
[wsl]: https://blogs.msdn.microsoft.com/wsl/
### As Vim plugin
If you use
[vim-plug](https://github.com/junegunn/vim-plug), add this line to your Vim
configuration file:
```vim
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
```
`fzf#install()` makes sure that you have the latest binary, but it's optional,
so you can omit it if you use a plugin manager that doesn't support hooks.
For more installation options, see [README-VIM.md](README-VIM.md).
Upgrading fzf Upgrading fzf
------------- -------------
@@ -221,6 +161,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`
@@ -340,8 +281,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
@@ -350,7 +293,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
@@ -377,13 +320,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).
@@ -468,17 +407,83 @@ _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
On bash, fuzzy completion is enabled only for a predefined set of commands On bash, fuzzy completion is enabled only for a predefined set of commands
(`complete | grep _fzf` to see the list). But you can enable it for other (`complete | grep _fzf` to see the list). But you can enable it for other
commands as well as follows. commands as well by using `_fzf_setup_completion` helper function.
```sh ```sh
complete -F _fzf_path_completion -o default -o bashdefault ag # usage: _fzf_setup_completion path|dir|var|alias|host COMMANDS...
complete -F _fzf_dir_completion -o default -o bashdefault tree _fzf_setup_completion path ag git kubectl
_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
@@ -524,7 +529,8 @@ See *KEY BINDINGS* section of the man page for details.
### Preview window ### Preview window
When `--preview` option is set, fzf automatically starts an external process with When `--preview` option is set, fzf automatically starts an external process with
the current line as the argument and shows the result in the split window. 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`.
```bash ```bash
# {} is replaced to the single-quoted string of the focused line # {} is replaced to the single-quoted string of the focused line
@@ -539,37 +545,50 @@ important that the command finishes quickly.
fzf --preview 'head -100 {}' fzf --preview 'head -100 {}'
``` ```
Preview window supports ANSI colors, so you can use programs 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.
- Bat: https://github.com/sharkdp/bat - 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
- CodeRay: http://coderay.rubychan.de/
- Rouge: https://github.com/jneen/rouge
```bash ```bash
# Try bat, highlight, coderay, rougify in turn, then fall back to cat fzf --preview 'bat --style=numbers --color=always {} | head -500'
fzf --preview '[[ $(file --mime {}) =~ binary ]] &&
echo {} is a binary file ||
(bat --style=numbers --color=always {} ||
highlight -O ansi -l {} ||
coderay {} ||
rougify {} ||
cat {}) 2> /dev/null | head -500'
``` ```
You can customize the size and position of the preview window using You can customize the size, position, and border of the preview window using
`--preview-window` option. For example, `--preview-window` option, and the foreground and background color of it with
`--color` option. For example,
```bash ```bash
fzf --height 40% --reverse --preview 'file {}' --preview-window down:1 fzf --height 40% --layout reverse --info inline --border \
--preview 'file {}' --preview-window down:1:noborder \
--color 'fg:#bbccdd,fg+:#ddeeff,bg:#334455,preview-bg:#223344,border:#778899'
``` ```
See the man page (`man fzf`) for the full list of options.
For more advanced examples, see [Key bindings for git with fzf][fzf-git] For more advanced examples, see [Key bindings for git with fzf][fzf-git]
([code](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236)). ([code](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236)).
[fzf-git]: https://junegunn.kr/2016/07/fzf-git/ [fzf-git]: https://junegunn.kr/2016/07/fzf-git/
----
Since fzf is a general-purpose text filter rather than a file finder, **it is
not a good idea to add `--preview` option to your `$FZF_DEFAULT_OPTS`**.
```sh
# *********************
# ** DO NOT DO THIS! **
# *********************
export FZF_DEFAULT_OPTS='--preview "bat --style=numbers --color=always {} | head -500"'
# bat doesn't work with any input other than the list of files
ps -ef | fzf
seq 100 | fzf
history | fzf
```
Tips Tips
---- ----
@@ -602,45 +621,17 @@ hidden files, use the following command:
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git' export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
``` ```
#### `git ls-tree` for fast traversal
If you're running fzf in a large git repository, `git ls-tree` can boost up the
speed of the traversal.
```sh
export FZF_DEFAULT_COMMAND='
(git ls-tree -r --name-only HEAD ||
find . -path "*/\.*" -prune -o -type f -print -o -type l -print |
sed s/^..//) 2> /dev/null'
```
#### Fish shell #### Fish shell
Fish shell before version 2.6.0 [doesn't allow](https://github.com/fish-shell/fish-shell/issues/1362) `CTRL-T` key binding of fish, unlike those of bash and zsh, will use the last
reading from STDIN in command substitution, which means simple `vim (fzf)` token on the command-line as the root directory for the recursive search. For
doesn't work as expected. The workaround for fish 2.5.0 and earlier is to use instance, hitting `CTRL-T` at the end of the following command-line
the `read` fish command:
```sh
fzf | read -l result; and vim $result
```
or, for multiple results:
```sh
fzf -m | while read -l r; set result $result $r; end; and vim $result
```
The globbing system is different in fish and thus `**` completion will not work.
However, the `CTRL-T` command will use the last token on the command-line as the
root folder for the recursive search. For instance, hitting `CTRL-T` at the end
of the following command-line
```sh ```sh
ls /var/ ls /var/
``` ```
will list all files and folders under `/var/`. will list all files and directories under `/var/`.
When using a custom `FZF_CTRL_T_COMMAND`, use the unexpanded `$dir` variable to When using a custom `FZF_CTRL_T_COMMAND`, use the unexpanded `$dir` variable to
make use of this feature. `$dir` defaults to `.` when the last token is not a make use of this feature. `$dir` defaults to `.` when the last token is not a
@@ -660,4 +651,4 @@ https://github.com/junegunn/fzf/wiki/Related-projects
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2017 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%,C]+$ ]]; 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,13 +130,13 @@ 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=("--no-height" "${args[@]}")
# Handle zoomed tmux pane by moving it to a temp window # Handle zoomed tmux pane by moving it to a temp window
if tmux list-panes -F '#F' | grep -q Z; then if tmux list-panes -F '#F' | grep -q Z; then
@@ -159,6 +180,7 @@ trap 'cleanup 1' SIGUSR1
trap 'cleanup' EXIT trap 'cleanup' EXIT
envs="env TERM=$TERM " envs="env 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")"
@@ -181,21 +203,34 @@ 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/$/ \\;/') ) tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
if [[ "$opt" =~ "-K -E" ]]; then
cat $fifo2 &
if [[ -n "$term" ]] || [[ -t 0 ]]; then
cat <<< "\"$fzf\" $opts > $fifo2; out=\$? $close; exit \$out" >> $argsf
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux popup -d "$PWD" "${tmux_args[@]}" $opt -R "$envs bash $argsf" > /dev/null 2>&1
else
mkfifo $fifo1
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; out=\$? $close; exit \$out" >> $argsf
cat <&0 > $fifo1 &
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux popup -d "$PWD" "${tmux_args[@]}" $opt -R "$envs bash $argsf" > /dev/null 2>&1
fi
exit $?
fi
if [[ -n "$term" ]] || [[ -t 0 ]]; then if [[ -n "$term" ]] || [[ -t 0 ]]; then
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\ TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
set-window-option remain-on-exit off \;\ set-window-option remain-on-exit off \;\
split-window $opt "$envs bash -c 'cd $(printf %q "$PWD"); exec -a fzf bash $argsf'" $swap \ split-window $opt "${tmux_args[@]}" "$envs bash -c 'cd $(printf %q "$PWD"); exec -a fzf bash $argsf'" $swap \
> /dev/null 2>&1 > /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
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 \;\ TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
set-window-option remain-on-exit off \;\ set-window-option remain-on-exit off \;\
split-window $opt "$envs bash -c 'exec -a fzf bash $argsf'" $swap \ split-window $opt "${tmux_args[@]}" "$envs bash -c 'exec -a fzf bash $argsf'" $swap \
> /dev/null 2>&1 > /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
cat <&0 > $fifo1 & cat <&0 > $fifo1 &
fi fi
cat $fifo2 cat $fifo2
exit "$(cat $fifo3)" exit "$(cat $fifo3)"

View File

@@ -1,24 +1,47 @@
fzf.txt fzf Last change: June 8 2019 fzf.txt fzf Last change: February 14 2020
FZF - TABLE OF CONTENTS *fzf* *fzf-toc* FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
============================================================================== ==============================================================================
FZF Vim integration FZF Vim integration
Summary
:FZF[!] :FZF[!]
Configuration Configuration
Examples Examples
fzf#run fzf#run
fzf#wrap fzf#wrap
fzf inside terminal buffer Tips
Hide statusline fzf inside terminal buffer
Starting fzf in a popup window
Hide statusline
License License
FZF VIM INTEGRATION *fzf-vim-integration* FZF VIM INTEGRATION *fzf-vim-integration*
============================================================================== ==============================================================================
This repository only enables basic integration with Vim. If you're looking for
more, check out {fzf.vim}{1} project.
(Note: To use fzf in GVim, an external terminal emulator is required.) SUMMARY *fzf-summary*
==============================================================================
The Vim plugin of fzf provides two core functions, and `:FZF` command which is
the basic file selector command built on top of them.
1. `fzf#run([specdict])`
- Starts fzf inside Vim with the given spec
- `:callfzf#run({'source':'ls'})`
2. `fzf#wrap([specdict])->(dict)`
- Takes a spec for `fzf#run` and returns an extended version of it with
additional options for addressing global preferences (`g:fzf_xxx`)
- `:echofzf#wrap({'source':'ls'})`
- We usually wrap a spec with `fzf#wrap` before passing it to `fzf#run`
- `:callfzf#run(fzf#wrap({'source':'ls'}))`
3. `:FZF[fzf_optionsstring][pathstring]`
- Basic fuzzy file selector
- A reference implementation for those who don't want to write VimScript to
implement custom commands
- If you're looking for more such commands, check out {fzf.vim}{1} project.
The most important of all is `fzf#run`, but it would be easier to understand
the whole if we start off with `:FZF` command.
{1} https://github.com/junegunn/fzf.vim {1} https://github.com/junegunn/fzf.vim
@@ -27,8 +50,6 @@ more, check out {fzf.vim}{1} project.
============================================================================== ==============================================================================
*:FZF* *:FZF*
If you have set up fzf for Vim, `:FZF` command will be added.
> >
" Look for files under current directory " Look for files under current directory
:FZF :FZF
@@ -36,8 +57,8 @@ If you have set up fzf for Vim, `:FZF` command will be added.
" Look for files under your home directory " Look for files under your home directory
:FZF ~ :FZF ~
" With options " With fzf command-line options
:FZF --no-sort --reverse --inline-info /tmp :FZF --reverse --info=inline /tmp
" Bang version starts fzf in fullscreen mode " Bang version starts fzf in fullscreen mode
:FZF! :FZF!
@@ -100,6 +121,7 @@ Examples~
let g:fzf_layout = { 'window': '10new' } let g:fzf_layout = { 'window': '10new' }
" Customize fzf colors to match your color scheme " Customize fzf colors to match your color scheme
" - fzf#wrap translates this to a set of `--color` options
let g:fzf_colors = let g:fzf_colors =
\ { 'fg': ['fg', 'Normal'], \ { 'fg': ['fg', 'Normal'],
\ 'bg': ['bg', 'Normal'], \ 'bg': ['bg', 'Normal'],
@@ -115,68 +137,64 @@ Examples~
\ 'spinner': ['fg', 'Label'], \ 'spinner': ['fg', 'Label'],
\ 'header': ['fg', 'Comment'] } \ 'header': ['fg', 'Comment'] }
" Enable per-command history. " Enable per-command history
" CTRL-N and CTRL-P will be automatically bound to next-history and " - History files will be stored in the specified directory
" previous-history instead of down and up. If you don't like the change, " - When set, CTRL-N and CTRL-P will be bound to 'next-history' and
" explicitly bind the keys to down and up in your $FZF_DEFAULT_OPTS. " 'previous-history' instead of 'down' and 'up'.
let g:fzf_history_dir = '~/.local/share/fzf-history' let g:fzf_history_dir = '~/.local/share/fzf-history'
< <
FZF#RUN *fzf#run* FZF#RUN
============================================================================== ==============================================================================
For more advanced uses, you can use `fzf#run([options])` function. *fzf#run*
`fzf#run()` function is the core of Vim integration. It takes a single `fzf#run()` function is the core of Vim integration. It takes a single
dictionary argument. At the very least, specify `sink` option to tell what it dictionary argument, a spec, and starts fzf process accordingly. At the very
should do with the selected entry. least, specify `sink` option to tell what it should do with the selected
entry.
> >
call fzf#run({'sink': 'e'}) call fzf#run({'sink': 'e'})
< <
Without `source`, fzf will use find command (or `$FZF_DEFAULT_COMMAND` if We haven't specified the `source`, so this is equivalent to starting fzf on
defined) to list the files under the current directory. When you select one, command line without standard input pipe; fzf will use find command (or
it will open it with `:e` command. If you want to open it in a new tab, you `$FZF_DEFAULT_COMMAND` if defined) to list the files under the current
can pass `:tabedit` command instead as the sink. directory. When you select one, it will open it with the sink, `:e` command.
If you want to open it in a new tab, you can pass `:tabedit` command instead
as the sink.
> >
call fzf#run({'sink': 'tabedit'}) call fzf#run({'sink': 'tabedit'})
< <
fzf allows you to select multiple entries with `--multi` (or `-m`) option, and
you can change its bottom-up layout with `--reverse` option. Such options can
be specified as `options`.
>
call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})
<
Instead of using the default find command, you can use any shell command as Instead of using the default find command, you can use any shell command as
the source. This will list the files managed by git. the source. The following example will list the files managed by git. It's
equivalent to running `gitls-files|fzf` on shell.
> >
call fzf#run({'source': 'git ls-files', 'sink': 'e'}) call fzf#run({'source': 'git ls-files', 'sink': 'e'})
< <
Pass a layout option if you don't want fzf window to take up the entire fzf options can be specified as `options` entry in spec dictionary.
screen. >
call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})
<
You can also pass a layout option if you don't want fzf window to take up the
entire screen.
> >
" up / down / left / right / window are allowed " up / down / left / right / window are allowed
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'right': '40%'}) call fzf#run({'source': 'git ls-files', 'sink': 'e', 'left': '40%'})
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vnew'}) call fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vnew'})
< <
`source` doesn't have to be an external shell command, you can pass a Vim `source` doesn't have to be an external shell command, you can pass a Vim
array as the source. In the following example, we use the names of the open array as the source. In the next example, we pass the names of color schemes
buffers as the source. as the source to implement a color scheme selector.
>
call fzf#run({'source': map(filter(range(1, bufnr('$')), 'buflisted(v:val)'),
\ 'bufname(v:val)'),
\ 'sink': 'e', 'down': '30%'})
<
Or the names of color schemes.
> >
call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')), call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
\ 'fnamemodify(v:val, ":t:r")'), \ 'fnamemodify(v:val, ":t:r")'),
\ 'sink': 'colo', 'left': '25%'}) \ 'sink': 'colo', 'left': '25%'})
< <
The following table shows the available options. The following table summarizes the available options.
---------------------------+---------------+-------------------------------------------------------------- ---------------------------+---------------+----------------------------------------------------------------------
Option name | Type | Description ~ Option name | Type | Description ~
---------------------------+---------------+-------------------------------------------------------------- ---------------------------+---------------+----------------------------------------------------------------------
`source` | string | External command to generate input to fzf (e.g. `find.` ) `source` | string | External command to generate input to fzf (e.g. `find.` )
`source` | list | Vim list as input to fzf `source` | list | Vim list as input to fzf
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` ) `sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
@@ -184,74 +202,140 @@ The following table shows the available options.
`sink*` | funcref | Similar to `sink` , but takes the list of output lines at once `sink*` | funcref | Similar to `sink` , but takes the list of output lines at once
`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 | Use tmux pane with the given size (e.g. `20` , `50%` ) `up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
`window` (Vim 8 / Neovim) | string | Command to open fzf window (e.g. `verticalaboveleft30new` ) `window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `verticalaboveleft30new` )
`launcher` | string | External terminal emulator to start fzf with (GVim only) `window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width':0.9,'height':0.6}` )
`launcher` | funcref | Function for generating `launcher` string (GVim only) ---------------------------+---------------+----------------------------------------------------------------------
---------------------------+---------------+--------------------------------------------------------------
`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 if you're concerned about escaping should suffice, but prefer to use list type to avoid escaping issues.
issues on different platforms.
> >
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:
FZF#WRAP *fzf#wrap* - Required:
- `width` [float range [0 ~ 1]]
- `height` [float range [0 ~ 1]]
- 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` command provided by default knows how to handle CTRL-T, CTRL-X, and *fzf#wrap*
CTRL-V and opens the selected file in a new tab, in a horizontal split, or in
a vertical split respectively. And these key bindings can be configured via
`g:fzf_action`. This is implemented using `--expect` option of fzf and the
smart sink function. It also understands `g:fzf_colors`, `g:fzf_layout` and
`g:fzf_history_dir`. However, `fzf#run` doesn't know about any of these
options.
By "wrapping" your options dictionary with `fzf#wrap` before passing it to We have seen that several aspects of `:FZF` command can be configured with a
`fzf#run`, you can make your command also support the options. set of global option variables; different ways to open files (`g:fzf_action`),
window position and size (`g:fzf_layout`), color palette (`g:fzf_colors`),
etc.
So how can we make our custom `fzf#run` calls also respect those variables?
Simply by "wrapping" the spec dictionary with `fzf#wrap` before passing it to
`fzf#run`.
- `fzf#wrap([namestring],[specdict],[fullscreenbool])->(dict)`
- All arguments are optional. Usually we only need to pass a spec
dictionary.
- `name` is for managing history files. It is ignored if `g:fzf_history_dir`
is not defined.
- `fullscreen` can be either `0` or `1` (default: 0).
`fzf#wrap` takes a spec and returns an extended version of it (also a
dictionary) with additional options for addressing global preferences. You can
examine the return value of it like so:
> >
" Usage: echo fzf#wrap({'source': 'ls'})
" fzf#wrap([name string,] [opts dict,] [fullscreen boolean]) <
After we "wrap" our spec, we pass it to `fzf#run`.
>
call fzf#run(fzf#wrap({'source': 'ls'}))
<
Now it supports CTRL-T, CTRL-V, and CTRL-X key bindings and it opens fzf
window according to `g:fzf_layout` setting.
" This command now supports CTRL-T, CTRL-V, and CTRL-X key bindings To make it easier to use, let's define `LS` command.
" and opens fzf according to g:fzf_layout setting. >
command! Buffers call fzf#run(fzf#wrap( command! LS call fzf#run(fzf#wrap({'source': 'ls'}))
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')})) <
Type `:LS` and see how it works.
" This extends the above example to open fzf in fullscreen We would like to make `:LS!` (bang version) open fzf in fullscreen, just like
" when the command is run with ! suffix (Buffers!) `:FZF!`. Add `-bang` to command definition, and use <bang> value to set the
command! -bang Buffers call fzf#run(fzf#wrap( last `fullscreen` argument of `fzf#wrap` (see :help <bang>).
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0)) >
" On :LS!, <bang> evaluates to '!', and '!0' becomes 1
" You can optionally pass the name of the command as the first argument to command! -bang LS call fzf#run(fzf#wrap({'source': 'ls'}, <bang>0))
" fzf#wrap to make it work with g:fzf_history_dir <
command! -bang Buffers call fzf#run(fzf#wrap('buffers', Our `:LS` command will be much more useful if we can pass a directory argument
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0)) to it, so that something like `:LS/tmp` is possible.
>
command! -bang -complete=dir -nargs=* LS
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
<
Lastly, if you have enabled `g:fzf_history_dir`, you might want to assign a
unique name to our command and pass it as the first argument to `fzf#wrap`.
>
" The query history for this command will be stored as 'ls' inside g:fzf_history_dir.
" The name is ignored if g:fzf_history_dir is not defined.
command! -bang -complete=dir -nargs=* LS
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
< <
FZF INSIDE TERMINAL BUFFER *fzf-inside-terminal-buffer* TIPS *fzf-tips*
============================================================================== ==============================================================================
< fzf inside terminal buffer >________________________________________________~
*fzf-inside-terminal-buffer*
The latest versions of Vim and Neovim include builtin terminal emulator The latest versions of Vim and Neovim include builtin terminal emulator
(`:terminal`) and fzf will start in a terminal buffer in the following cases: (`:terminal`) and fzf will start in a terminal buffer in the following cases:
- On Neovim - On Neovim
- On GVim - On GVim
- On Terminal Vim with the non-default layout - On Terminal Vim with a non-default layout
- `callfzf#run({'left':'30%'})` or `letg:fzf_layout={'left':'30%'}` - `callfzf#run({'left':'30%'})` or `letg:fzf_layout={'left':'30%'}`
< Hide statusline >___________________________________________________________~ Starting fzf in a popup window~
*fzf-starting-fzf-in-a-popup-window*
>
" Required:
" - width [float range [0 ~ 1]]
" - height [float range [0 ~ 1]]
"
" 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 } }
<
Hide statusline~
*fzf-hide-statusline* *fzf-hide-statusline*
When fzf starts in a terminal buffer, you may want to hide the statusline of When fzf starts in a terminal buffer, the file type of the buffer is set to
the containing buffer. `fzf`. So you can set up `FileTypefzf` autocmd to customize the settings of
the window.
For example, if you use the default layout (`{'down':'~40%'}`) on Neovim, you
might want to temporarily disable the statusline for a cleaner look.
> >
autocmd! FileType fzf if has('nvim') && !exists('g:fzf_layout')
autocmd FileType fzf set laststatus=0 noshowmode noruler autocmd! FileType fzf
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler autocmd FileType fzf set laststatus=0 noshowmode noruler
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
endif
< <
LICENSE *fzf-license* LICENSE *fzf-license*
@@ -259,7 +343,7 @@ LICENSE *fzf-license*
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2017 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.3.0
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect github.com/mattn/go-isatty v0.0.12
github.com/jtolds/gls v4.2.1+incompatible // indirect github.com/mattn/go-runewidth v0.0.8
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7 // indirect github.com/mattn/go-shellwords v1.0.9
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d
github.com/mattn/go-shellwords v1.0.3 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect golang.org/x/text v0.3.2 // 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

70
go.sum
View File

@@ -1,26 +1,44 @@
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635 h1:hheUEMzaOie/wKeIc1WPa7CDVuIO5hqQxjS+dwTQEnI= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635/go.mod h1:yrQYJKKDTrHmbYxI7CYi+/hbdiDT2m4Hj+t0ikCjsrQ= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df h1:tLS1QD2puA1USLvkEnGfOt+Zp2ijtNIK3z2YFaIZZR4= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df/go.mod h1:tqyG50u7+Ctv1w5VX67kLzKcj9YXR/JSBZQq/+mLl1A= github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/lucasb-eyer/go-colorful v1.0.2 h1:mCMFu6PgSozg9tDNMMK3g18oJBX7oYGrC09mS6CXfO4=
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7 h1:G52I+Gk/wPD4HKvKT0Vxxp9OUPxqKs3OK6rffSPtNkA= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c h1:3nKFouDdpgGUV/uerJcYWH45ZbJzX0SiVWfTgmUeTzc= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c h1:eFzthqtg3W6Pihj3DMTXLAF4f+ge5r5Ie5g6HLIZAF0= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk= github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.9 h1:eaB5JspOwiKKcHdqcjbfe5lA9cNn/4NRRtddXJCimqk=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e h1:NO86zOn5ScSKW8wRbMaSIcjDZUFpWdCQQnexRqZ9h9A=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w= github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e/go.mod h1:G0Z6yVPru183i2MuRJx1DcR4dgIZtLcTdaaE/pC1BJU=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74 h1:/0jf0Cx3u07Ma4EzUA6NIGuvk9hb3Br6i9V8atthkwk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 h1:rM0ROo5vb9AdYJi1110yjWGMej9ITfKddS89P3Fkhug= golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/text v0.0.0-20170530162606-4ee4af566555 h1:pjwO9HxObpgZBurDvTLFbSinaf3+cKpTAjVfiGaHwrI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/text v0.0.0-20170530162606-4ee4af566555/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/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.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191011211836-4c025a95b26e h1:1o2bDs9pCd2xFhdwqJTrCIswAeEsn4h/PCNelWpfcsI=
golang.org/x/tools v0.0.0-20191011211836-4c025a95b26e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.19.0 version=0.21.1
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2
@@ -196,6 +196,8 @@ case "$archi" in
MINGW*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;; MINGW*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
MSYS*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;; MSYS*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
MSYS*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.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 ;; *) binary_available=0 binary_error=1 ;;
esac esac

73
install.ps1 Normal file
View File

@@ -0,0 +1,73 @@
$version="0.21.1"
if ([Environment]::Is64BitProcess) {
$binary_arch="amd64"
} else {
$binary_arch="386"
}
$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 ("$version" -ne "alpha") {
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"
if ("$version" -eq "alpha") {
$url="https://github.com/junegunn/fzf-bin/releases/download/alpha/$file"
} else {
$url="https://github.com/junegunn/fzf-bin/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 ($?) {
(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_$binary_arch.zip"
Write-Host 'For more information, see: https://github.com/junegunn/fzf'

View File

@@ -1,9 +1,13 @@
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 revision string
func main() { func main() {
protector.Protect()
fzf.Run(fzf.ParseOptions(), revision) fzf.Run(fzf.ParseOptions(), 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 "Nov 2019" "fzf 0.19.0" "fzf-tmux - open fzf in tmux split pane" .TH fzf-tmux 1 "Apr 2020" "fzf 0.21.1" "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 "Nov 2019" "fzf 0.19.0" "fzf - a command-line fuzzy finder" .TH fzf 1 "Apr 2020" "fzf 0.21.1" "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,16 @@ 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
.TP .TP
.B "--no-unicode" .B "--no-unicode"
@@ -231,6 +243,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
@@ -262,23 +280,25 @@ format.
\fBdark \fRColor scheme for dark 256-color terminal \fBdark \fRColor scheme for dark 256-color terminal
\fBlight \fRColor scheme for light 256-color terminal \fBlight \fRColor scheme for light 256-color terminal
\fB16 \fRColor scheme for 16-color terminal \fB16 \fRColor scheme for 16-color terminal
\fBbw \fRNo colors \fBbw \fRNo colors (equivalent to \fB--no-color\fR)
.B COLOR: .B COLOR:
\fBfg \fRText \fBfg \fRText
\fBbg \fRBackground \fBbg \fRBackground
\fBhl \fRHighlighted substrings \fBpreview-fg \fRPreview window text
\fBfg+ \fRText (current line) \fBpreview-bg \fRPreview window background
\fBbg+ \fRBackground (current line) \fBhl \fRHighlighted substrings
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR) \fBfg+ \fRText (current line)
\fBhl+ \fRHighlighted substrings (current line) \fBbg+ \fRBackground (current line)
\fBinfo \fRInfo \fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
\fBborder \fRBorder of the preview window and horizontal separators (\fB--border\fR) \fBhl+ \fRHighlighted substrings (current line)
\fBprompt \fRPrompt \fBinfo \fRInfo line (match counters)
\fBpointer \fRPointer to the current line \fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
\fBmarker \fRMulti-select marker \fBprompt \fRPrompt
\fBspinner \fRStreaming input indicator \fBpointer \fRPointer to the current line
\fBheader \fRHeader \fBmarker \fRMulti-select marker
\fBspinner \fRStreaming input indicator
\fBheader \fRHeader
.B EXAMPLES: .B EXAMPLES:
@@ -592,6 +612,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)
@@ -623,60 +645,63 @@ or any single character
.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 (clears query string if not empty, aborts 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)
\fBdelete-char\fR \fIdel\fR \fBclear-screen\fR \fIctrl-l\fR
\fBdelete-char/eof\fR \fIctrl-d\fR \fBclear-selection\fR (clear multi-selection)
\fBdeselect-all\fR \fBclear-query\fR (clear query string)
\fBdown\fR \fIctrl-j ctrl-n down\fR \fBdelete-char\fR \fIdel\fR
\fBend-of-line\fR \fIctrl-e end\fR \fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
\fBexecute(...)\fR (see below for the details) \fBdeselect-all\fR (deselect all matches)
\fBexecute-silent(...)\fR (see below for the details) \fBdown\fR \fIctrl-j ctrl-n down\fR
\fRexecute-multi(...)\fR (deprecated in favor of \fB{+}\fR expression) \fBend-of-line\fR \fIctrl-e end\fR
\fBforward-char\fR \fIctrl-f right\fR \fBexecute(...)\fR (see below for the details)
\fBforward-word\fR \fIalt-f shift-right\fR \fBexecute-silent(...)\fR (see below for the details)
\fRexecute-multi(...)\fR (deprecated in favor of \fB{+}\fR expression)
\fBforward-char\fR \fIctrl-f 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-down\fR \fIshift-down\fR
\fBpreview-up\fR \fIshift-up\fR \fBpreview-up\fR \fIshift-up\fR
\fBpreview-page-down\fR \fBpreview-page-down\fR
\fBpreview-page-up\fR \fBpreview-page-up\fR
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR) \fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
\fBprint-query\fR (print query and exit) \fBprint-query\fR (print query and exit)
\fBreload(...)\fR (see below for the details) \fBreload(...)\fR (see below for the details)
\fBreplace-query\fR (replace query string with the current selection) \fBreplace-query\fR (replace query string with the current selection)
\fBselect-all\fR \fBselect-all\fR (select all matches)
\fBtoggle\fR (\fIright-click\fR) \fBtoggle\fR (\fIright-click\fR)
\fBtoggle-all\fR \fBtoggle-all\fR (toggle all matches)
\fBtoggle+down\fR \fIctrl-i (tab)\fR \fBtoggle+down\fR \fIctrl-i (tab)\fR
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR) \fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR) \fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
\fBtoggle-preview\fR \fBtoggle-preview\fR
\fBtoggle-preview-wrap\fR \fBtoggle-preview-wrap\fR
\fBtoggle-sort\fR \fBtoggle-sort\fR
\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

View File

@@ -49,18 +49,23 @@ if s:is_win
" Use utf-8 for fzf.vim commands " Use utf-8 for fzf.vim commands
" Return array of shell commands for cmd.exe " Return array of shell commands for cmd.exe
function! s: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)
endfunction
function! s:wrap_cmds(cmds) function! s:wrap_cmds(cmds)
let use_chcp = executable('sed')
return map([ return map([
\ '@echo off', \ '@echo off',
\ 'setlocal enabledelayedexpansion'] \ 'setlocal enabledelayedexpansion']
\ + (use_chcp ? [ \ + (has('gui_running') ? ['set TERM= > nul'] : [])
\ 'for /f "usebackq" %%a in (`chcp ^| sed "s/[^0-9]//gp"`) do set origchcp=%%a',
\ 'chcp 65001 > nul'] : [])
\ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
\ + (use_chcp ? ['chcp !origchcp! > nul'] : [])
\ + ['endlocal'], \ + ['endlocal'],
\ 'v:val."\r"') \ '<SID>enc_to_cp(v:val."\r")')
endfunction endfunction
else else
let s:term_marker = ";#FZF" let s:term_marker = ";#FZF"
@@ -72,6 +77,10 @@ else
function! s:wrap_cmds(cmds) function! s:wrap_cmds(cmds)
return a:cmds return a:cmds
endfunction endfunction
function! s:enc_to_cp(str)
return a:str
endfunction
endif endif
function! s:shellesc_cmd(arg) function! s:shellesc_cmd(arg)
@@ -83,7 +92,7 @@ function! s:shellesc_cmd(arg)
endfunction endfunction
function! fzf#shellescape(arg, ...) function! fzf#shellescape(arg, ...)
let shell = get(a:000, 0, &shell) let shell = get(a:000, 0, s:is_win ? 'cmd.exe' : 'sh')
if shell =~# 'cmd.exe$' if shell =~# 'cmd.exe$'
return s:shellesc_cmd(a:arg) return s:shellesc_cmd(a:arg)
endif endif
@@ -110,30 +119,41 @@ let s:default_layout = { 'down': '~40%' }
let s:layout_keys = ['window', '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! 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:fzf_exec() function! s: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 ...')
let s:installed = 1
call system(s:install.' --bin')
return s:fzf_exec() return s:fzf_exec()
else else
redraw redraw
@@ -144,7 +164,7 @@ function! s:fzf_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
@@ -153,10 +173,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
@@ -247,7 +273,7 @@ function! s:common_sink(action, lines) abort
endfunction endfunction
function! s:get_color(attr, ...) function! s:get_color(attr, ...)
let gui = has('termguicolors') && &termguicolors let gui = !s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors
let fam = gui ? 'gui' : 'cterm' let fam = gui ? 'gui' : 'cterm'
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$' let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
for group in a:000 for group in a:000
@@ -342,19 +368,21 @@ function! fzf#wrap(...)
endfunction endfunction
function! s:use_sh() function! s:use_sh()
let [shell, shellslash] = [&shell, &shellslash] let [shell, shellslash, shellcmdflag, shellxquote] = [&shell, &shellslash, &shellcmdflag, &shellxquote]
if s:is_win if s:is_win
set shell=cmd.exe set shell=cmd.exe
set noshellslash set noshellslash
let &shellcmdflag = has('nvim') ? '/s /c' : '/c'
let &shellxquote = has('nvim') ? '"' : '('
else else
set shell=sh set shell=sh
endif endif
return [shell, shellslash] return [shell, shellslash, shellcmdflag, shellxquote]
endfunction endfunction
function! fzf#run(...) abort function! fzf#run(...) abort
try try
let [shell, shellslash] = 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') ? s:upgrade(a:1) : {}
let temps = { 'result': s:fzf_tempname() } let temps = { 'result': s:fzf_tempname() }
@@ -372,12 +400,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)
@@ -385,7 +407,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(source, temps.input) call writefile(map(source, '<SID>enc_to_cp(v:val)'), 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'
@@ -424,7 +446,7 @@ try
call s:callback(dict, lines) call s:callback(dict, lines)
return lines return lines
finally finally
let [&shell, &shellslash] = [shell, shellslash] let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote]
endtry endtry
endfunction endfunction
@@ -620,14 +642,16 @@ 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 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 endfunction
function! s:split(dict) function! s:split(dict)
@@ -639,7 +663,14 @@ function! s:split(dict)
let ppos = s:getpos() let ppos = s:getpos()
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 !has('nvim') && !has('patch-8.2.191')
throw 'Vim 8.2.191 or later is required for pop-up window'
end
call s:popup(a:dict.window)
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
@@ -696,8 +727,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)
@@ -731,6 +762,9 @@ function! s:execute_term(dict, command, temps) abort
if has('nvim') if has('nvim')
call termopen(command, fzf) call termopen(command, fzf)
else else
if !len(&bufhidden)
setlocal bufhidden=hide
endif
let fzf.buf = term_start([&shell, &shellcmdflag, command], {'curwin': 1, 'exit_cb': function(fzf.on_exit)}) let fzf.buf = term_start([&shell, &shellcmdflag, command], {'curwin': 1, 'exit_cb': function(fzf.on_exit)})
if !has('patch-8.0.1261') && !has('nvim') && !s:is_win if !has('patch-8.0.1261') && !has('nvim') && !s:is_win
call term_wait(fzf.buf, 20) call term_wait(fzf.buf, 20)
@@ -787,6 +821,100 @@ 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 is_frame = has_key(a:opts, 'border')
let buf = is_frame ? '' : term_start(&shell, #{hidden: 1, term_finish: 'close'})
let id = popup_create(buf, #{
\ line: a:opts.row,
\ col: a:opts.col,
\ minwidth: a:opts.width,
\ minheight: a:opts.height,
\ zindex: 50 - is_frame,
\ })
if is_frame
call setwinvar(id, '&wincolor', a:hl)
call setbufline(winbufnr(id), 1, a:opts.border)
execute 'autocmd BufWipeout * ++once call popup_close('..id..')'
else
execute 'autocmd BufWipeout * ++once call term_sendkeys('..buf..', "exit\<CR>")'
endif
return winbufnr(id)
endfunction
endif
function! s:popup(opts) abort
" Support ambiwidth == 'double'
let ambidouble = &ambiwidth == 'double' ? 2 : 1
" Size and position
let width = min([max([0, float2nr(&columns * a:opts.width)]), &columns])
let width += width % ambidouble
let height = min([max([0, 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')
" Border style
let style = tolower(get(a:opts, 'border', 'rounded'))
if !has_key(a:opts, 'border') && !get(a:opts, 'rounded', 1)
let style = 'sharp'
endif
if style =~ 'vertical\|left\|right'
let mid = style == 'vertical' ? '│' .. repeat(' ', width - 2 * ambidouble) .. '│' :
\ style == 'left' ? '│' .. repeat(' ', width - 1 * ambidouble)
\ : repeat(' ', width - 1 * ambidouble) .. '│'
let border = repeat([mid], height)
let shift = { 'row': 0, 'col': style == 'right' ? 0 : 2, 'width': style == 'vertical' ? -4 : -2, 'height': 0 }
elseif style =~ 'horizontal\|top\|bottom'
let hor = repeat('─', width / ambidouble)
let mid = repeat(' ', width)
let border = style == 'horizontal' ? [hor] + repeat([mid], height - 2) + [hor] :
\ style == 'top' ? [hor] + repeat([mid], height - 1)
\ : repeat([mid], height - 1) + [hor]
let shift = { 'row': style == 'bottom' ? 0 : 1, 'col': 0, 'width': 0, 'height': style == 'horizontal' ? -2 : -1 }
else
let edges = style == 'sharp' ? ['┌', '┐', '└', '┘'] : ['╭', '╮', '╰', '╯']
let bar = repeat('─', width / ambidouble - 2)
let top = edges[0] .. bar .. edges[1]
let mid = '│' .. repeat(' ', width - 2 * ambidouble) .. '│'
let bot = edges[2] .. bar .. edges[3]
let border = [top] + repeat([mid], height - 2) + [bot]
let shift = { 'row': 1, 'col': 2, 'width': -4, 'height': -2 }
endif
let highlight = get(a:opts, 'highlight', 'Comment')
let frame = s:create_popup(highlight, {
\ 'row': row, 'col': col, 'width': width, 'height': height, 'border': border
\ })
call s:create_popup('Normal', {
\ 'row': row + shift.row, 'col': col + shift.col, 'width': width + shift.width, 'height': height + shift.height
\ })
if has('nvim')
execute 'autocmd BufWipeout <buffer> bwipeout '..frame
endif
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)
@@ -16,7 +16,7 @@ if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() { _fzf_compgen_path() {
echo "$1" echo "$1"
command find -L "$1" \ command find -L "$1" \
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \ -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@' -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
} }
fi fi
@@ -24,7 +24,7 @@ fi
if ! declare -f _fzf_compgen_dir > /dev/null; then if ! declare -f _fzf_compgen_dir > /dev/null; then
_fzf_compgen_dir() { _fzf_compgen_dir() {
command find -L "$1" \ command find -L "$1" \
-name .git -prune -o -name .svn -prune -o -type d \ -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@' -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
} }
fi fi
@@ -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
} }
@@ -224,9 +251,9 @@ _fzf_dir_completion() {
_fzf_complete_kill() { _fzf_complete_kill() {
[ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1 [ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1
local selected fzf local selected
fzf="$(__fzfcmd_complete)" selected=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS --preview 'echo {}' --preview-window down:3:wrap" __fzf_comprun "kill" -m | awk '{print $2}' | tr '\n' ' ')
selected=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" $fzf -m | awk '{print $2}' | tr '\n' ' ') selected=${selected% }
printf '\e[5n' printf '\e[5n'
if [ -n "$selected" ]; then if [ -n "$selected" ]; then
@@ -235,36 +262,23 @@ _fzf_complete_kill() {
fi fi
} }
_fzf_complete_telnet() { _fzf_host_completion() {
_fzf_complete '+m' "$@" < <( _fzf_complete +m -- "$@" < <(
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' | 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 '[*?]') \
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
}
_fzf_complete_ssh() {
_fzf_complete '+m' "$@" < <(
cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host ' | command grep -v '[*?]' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \ <(command grep -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,7 +296,7 @@ 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" x_cmds="kill"
# Preserve existing completion # Preserve existing completion
eval "$(complete | eval "$(complete |
@@ -319,16 +333,7 @@ for cmd in $d_cmds; do
done done
# Kill completion # Kill completion
complete -F _fzf_complete_kill -o nospace -o default -o bashdefault kill complete -F _fzf_complete_kill -o default -o bashdefault kill
# Host completion
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 unset cmd d_cmds a_cmds x_cmds
@@ -337,17 +342,24 @@ _fzf_setup_completion() {
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 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,21 +2,86 @@
# / __/___ / __/ # / __/___ / __/
# / /_/_ / / /_ # / /_/_ / / /_
# / __/ / /_/ __/ # / __/ / /_/ __/
# /_/ /___/_/-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
_fzf_compgen_path() { _fzf_compgen_path() {
echo "$1" echo "$1"
command find -L "$1" \ command find -L "$1" \
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \ -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@' -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
} }
fi fi
@@ -24,27 +89,51 @@ fi
if ! declare -f _fzf_compgen_dir > /dev/null; then if ! declare -f _fzf_compgen_dir > /dev/null; then
_fzf_compgen_dir() { _fzf_compgen_dir() {
command find -L "$1" \ command find -L "$1" \
-name .git -prune -o -name .svn -prune -o -type d \ -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@' -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
} }
fi 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
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 +144,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 +176,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 +215,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 '^host ' | command grep -v '[*?]' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \ 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 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 +232,25 @@ _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-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 +261,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-'**'}
@@ -167,8 +276,7 @@ fzf-completion() {
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)" matches=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS --preview 'echo {}' --preview-window down:3:wrap" __fzf_comprun "$cmd" -m | awk '{print $2}' | tr '\n' ' ')
matches=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" ${=fzf} -m | awk '{print $2}' | tr '\n' ' ')
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
LBUFFER="$LBUFFER$matches" LBUFFER="$LBUFFER$matches"
fi fi
@@ -181,7 +289,7 @@ fzf-completion() {
[ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}} [ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER: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 +310,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

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() {
@@ -56,6 +88,7 @@ fzf-cd-widget() {
return 0 return 0
fi fi
cd "$dir" cd "$dir"
unset dir # ensure this doesn't end up appearing in prompt expansion
local ret=$? local ret=$?
zle fzf-redraw-prompt zle fzf-redraw-prompt
return $ret return $ret
@@ -67,7 +100,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/^\s*[0-9]+\s+//r)}++' |
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
@@ -82,4 +115,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

@@ -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

@@ -10,7 +10,7 @@ import (
const ( const (
// Current version // Current version
version = "0.19.0" version = "0.21.1"
// Core // Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond coordinatorDelayMax time.Duration = 100 * time.Millisecond
@@ -25,7 +25,7 @@ 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
maxPatternLength = 300 maxPatternLength = 300
maxMulti = math.MaxInt32 maxMulti = math.MaxInt32
@@ -62,8 +62,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

@@ -224,10 +224,14 @@ func Run(opts *Options, revision string) {
// Event coordination // Event coordination
reading := true reading := true
clearCache := util.Once(false)
clearSelection := util.Once(false)
ticks := 0 ticks := 0
var nextCommand *string var nextCommand *string
restart := func(command string) { restart := func(command string) {
reading = true reading = true
clearCache = util.Once(true)
clearSelection = util.Once(true)
chunkList.Clear() chunkList.Clear()
header = make([]string, 0, opts.HeaderLines) header = make([]string, 0, opts.HeaderLines)
go reader.restart(command) go reader.restart(command)
@@ -250,20 +254,20 @@ func Run(opts *Options, revision string) {
switch evt { switch evt {
case EvtReadNew, EvtReadFin: case EvtReadNew, EvtReadFin:
clearCache := false
if evt == EvtReadFin && nextCommand != nil { if evt == EvtReadFin && nextCommand != nil {
clearCache = true
restart(*nextCommand) restart(*nextCommand)
nextCommand = nil nextCommand = nil
break
} else { } else {
reading = reading && evt == EvtReadNew reading = reading && evt == EvtReadNew
} }
snapshot, count := chunkList.Snapshot() snapshot, count := chunkList.Snapshot()
terminal.UpdateCount(count, !reading, value.(*string)) terminal.UpdateCount(count, !reading, value.(*string))
if opts.Sync { if opts.Sync {
terminal.UpdateList(PassMerger(&snapshot, opts.Tac)) opts.Sync = false
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
} }
matcher.Reset(snapshot, input(), false, !reading, sort, clearCache) matcher.Reset(snapshot, input(), false, !reading, sort, clearCache())
case EvtSearchNew: case EvtSearchNew:
var command *string var command *string
@@ -279,9 +283,10 @@ func Run(opts *Options, revision string) {
} else { } else {
restart(*command) restart(*command)
} }
break
} }
snapshot, _ := chunkList.Snapshot() snapshot, _ := chunkList.Snapshot()
matcher.Reset(snapshot, input(), true, !reading, sort, command != nil) matcher.Reset(snapshot, input(), true, !reading, sort, clearCache())
delay = false delay = false
case EvtSearchProgress: case EvtSearchProgress:
@@ -291,7 +296,9 @@ func Run(opts *Options, revision string) {
} }
case EvtHeader: case EvtHeader:
terminal.UpdateHeader(value.([]string)) headerPadded := make([]string, opts.HeaderLines)
copy(headerPadded, value.([]string))
terminal.UpdateHeader(headerPadded)
case EvtSearchFin: case EvtSearchFin:
switch val := value.(type) { switch val := value.(type) {
@@ -321,7 +328,7 @@ func Run(opts *Options, revision string) {
terminal.startChan <- true terminal.startChan <- true
} }
} }
terminal.UpdateList(val) terminal.UpdateList(val, clearSelection())
} }
} }
} }

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,13 @@ 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
[rounded|sharp|horizontal] (default: rounded)
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L) --margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
--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
@@ -183,12 +188,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,7 +214,7 @@ type Options struct {
Header []string Header []string
HeaderLines int HeaderLines int
Margin [4]sizeSpec Margin [4]sizeSpec
Bordered bool BorderShape tui.BorderShape
Unicode bool Unicode bool
Tabstop int Tabstop int
ClearOnExit bool ClearOnExit bool
@@ -236,12 +244,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,
@@ -293,12 +304,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 +403,23 @@ 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
default:
if optional && str == "" {
return tui.BorderRounded
}
errorExit("invalid border style (expected: rounded|sharp|horizontal)")
}
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)
@@ -464,6 +492,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":
@@ -597,6 +627,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
theme.Fg = ansi theme.Fg = ansi
case "bg": case "bg":
theme.Bg = ansi theme.Bg = ansi
case "preview-fg":
theme.PreviewFg = ansi
case "preview-bg":
theme.PreviewBg = ansi
case "fg+": case "fg+":
theme.Current = ansi theme.Current = ansi
case "bg+": case "bg+":
@@ -648,14 +682,18 @@ 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):.+|[:+](execute(?:-multi|-silent)?|reload)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
} }
func parseKeymap(keymap map[int][]action, str string) { func parseKeymap(keymap map[int][]action, str string) {
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string { masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
prefix := ":execute" symbol := ":"
if strings.HasPrefix(src, ":reload") { if strings.HasPrefix(src, "+") {
prefix = ":reload" symbol = "+"
}
prefix := symbol + "execute"
if strings.HasPrefix(src[1:], "reload") {
prefix = symbol + "reload"
} 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' {
@@ -722,6 +760,8 @@ func parseKeymap(keymap map[int][]action, str string) {
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":
@@ -734,6 +774,10 @@ func parseKeymap(keymap map[int][]action, str string) {
appendAction(actEndOfLine) appendAction(actEndOfLine)
case "cancel": case "cancel":
appendAction(actCancel) appendAction(actCancel)
case "clear-query":
appendAction(actClearQuery)
case "clear-selection":
appendAction(actClearSelection)
case "forward-char": case "forward-char":
appendAction(actForwardChar) appendAction(actForwardChar)
case "forward-word": case "forward-word":
@@ -805,7 +849,11 @@ func parseKeymap(keymap map[int][]action, str string) {
default: default:
t := isExecuteAction(specLower) t := isExecuteAction(specLower)
if t == actIgnore { if t == actIgnore {
errorExit("unknown action: " + spec) if specIndex == 0 && specLower == "" {
actions = append(keymap[key], actions...)
} else {
errorExit("unknown action: " + spec)
}
} else { } else {
var offset int var offset int
switch t { switch t {
@@ -1025,6 +1073,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 {
@@ -1068,7 +1118,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 {
@@ -1127,6 +1177,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":
@@ -1173,6 +1227,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":
@@ -1210,9 +1270,10 @@ func parseOptions(opts *Options, allArgs []string) {
case "--no-margin": case "--no-margin":
opts.Margin = defaultMargin() opts.Margin = 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":
@@ -1237,8 +1298,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 {
@@ -1287,6 +1356,7 @@ func parseOptions(opts *Options, allArgs []string) {
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)
} }
@@ -1316,11 +1386,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 {

View File

@@ -243,9 +243,10 @@ func TestBind(t *testing.T) {
check(tui.CtrlA, "", actBeginningOfLine) check(tui.CtrlA, "", actBeginningOfLine)
parseKeymap(keymap, parseKeymap(keymap,
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+ "ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
"f1:execute(ls {})+abort,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+ "f1:execute(ls {+})+abort+execute(echo {+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+ "alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
"x:Execute(foo+bar),X:execute/bar+baz/"+ "x:Execute(foo+bar),X:execute/bar+baz/"+
",f1:+top,f1:+top"+
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up") ",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
check(tui.CtrlA, "", actKillLine) check(tui.CtrlA, "", actKillLine)
check(tui.CtrlB, "", actToggleSort, actUp, actDown) check(tui.CtrlB, "", actToggleSort, actUp, actDown)
@@ -253,7 +254,7 @@ func TestBind(t *testing.T) {
check(tui.AltZ+',', "", actAbort) check(tui.AltZ+',', "", actAbort)
check(tui.AltZ+':', "", actAccept) check(tui.AltZ+':', "", actAccept)
check(tui.AltZ, "", actPageDown) check(tui.AltZ, "", actPageDown)
check(tui.F1, "ls {}", actExecute, actAbort) check(tui.F1, "ls {+}", actExecute, actAbort, actExecute, actSelectAll, actTop, actTop)
check(tui.F2, "echo {}, {}, {}", actExecute) check(tui.F2, "echo {}, {}, {}", actExecute)
check(tui.F3, "echo '({})'", actExecute) check(tui.F3, "echo '({})'", actExecute)
check(tui.F4, "less {}", actExecute) check(tui.F4, "less {}", actExecute)
@@ -421,3 +422,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

@@ -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

@@ -25,6 +25,8 @@ import (
var placeholder *regexp.Regexp var placeholder *regexp.Regexp
var activeTempFiles []string var activeTempFiles []string
const ellipsis string = ".."
func init() { func init() {
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`) placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
activeTempFiles = []string{} activeTempFiles = []string{}
@@ -59,71 +61,79 @@ var emptyLine = itemLine{}
// Terminal represents terminal input/output // Terminal represents terminal input/output
type Terminal struct { type Terminal struct {
initDelay time.Duration initDelay time.Duration
infoStyle infoStyle infoStyle infoStyle
prompt string spinner []string
promptLen int prompt string
queryLen [2]int promptLen int
layout layoutType pointer string
fullscreen bool pointerLen int
hscroll bool pointerEmpty string
hscrollOff int marker string
wordRubout string markerLen int
wordNext string markerEmpty string
cx int queryLen [2]int
cy int layout layoutType
offset int fullscreen bool
xoffset int keepRight bool
yanked []rune hscroll bool
input []rune hscrollOff int
multi int wordRubout string
sort bool wordNext string
toggleSort bool cx int
delimiter Delimiter cy int
expect map[int]string offset int
keymap map[int][]action xoffset int
pressed string yanked []rune
printQuery bool input []rune
history *History multi int
cycle bool sort bool
header []string toggleSort bool
header0 []string delimiter Delimiter
ansi bool expect map[int]string
tabstop int keymap map[int][]action
margin [4]sizeSpec pressed string
strong tui.Attr printQuery bool
unicode bool history *History
bordered bool cycle bool
cleanExit bool header []string
border tui.Window header0 []string
window tui.Window ansi bool
pborder tui.Window tabstop int
pwindow tui.Window margin [4]sizeSpec
count int strong tui.Attr
progress int unicode bool
reading bool borderShape tui.BorderShape
failed *string cleanExit bool
jumping jumpMode border tui.Window
jumpLabels string window tui.Window
printer func(string) pborder tui.Window
printsep string pwindow tui.Window
merger *Merger count int
selected map[int32]selectedItem progress int
version int64 reading bool
reqBox *util.EventBox failed *string
preview previewOpts jumping jumpMode
previewer previewer jumpLabels string
previewBox *util.EventBox printer func(string)
eventBox *util.EventBox printsep string
mutex sync.Mutex merger *Merger
initFunc func() selected map[int32]selectedItem
prevLines []itemLine version int64
suppress bool reqBox *util.EventBox
startChan chan bool preview previewOpts
killChan chan int previewer previewer
slab *util.Slab previewBox *util.EventBox
theme *tui.ColorTheme eventBox *util.EventBox
tui tui.Renderer mutex sync.Mutex
initFunc func()
prevLines []itemLine
suppress bool
startChan chan bool
killChan chan int
slab *util.Slab
theme *tui.ColorTheme
tui tui.Renderer
} }
type selectedItem struct { type selectedItem struct {
@@ -145,8 +155,6 @@ func (a byTimeOrder) Less(i, j int) bool {
return a[i].at.Before(a[j].at) return a[i].at.Before(a[j].at)
} }
var _spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
const ( const (
reqPrompt util.EventType = iota reqPrompt util.EventType = iota
reqInfo reqInfo
@@ -182,9 +190,12 @@ const (
actAcceptNonEmpty actAcceptNonEmpty
actBackwardChar actBackwardChar
actBackwardDeleteChar actBackwardDeleteChar
actBackwardDeleteCharEOF
actBackwardWord actBackwardWord
actCancel actCancel
actClearScreen actClearScreen
actClearQuery
actClearSelection
actDeleteChar actDeleteChar
actDeleteCharEOF actDeleteCharEOF
actEndOfLine actEndOfLine
@@ -362,9 +373,9 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
effectiveMinHeight *= 2 effectiveMinHeight *= 2
} }
if opts.InfoStyle != infoDefault { if opts.InfoStyle != infoDefault {
effectiveMinHeight -= 1 effectiveMinHeight--
} }
if opts.Bordered { if opts.BorderShape != tui.BorderNone {
effectiveMinHeight += 2 effectiveMinHeight += 2
} }
return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight)) return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight))
@@ -378,63 +389,75 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
wordRubout = fmt.Sprintf("%s[^%s]", sep, sep) wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep) wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
} }
spinner := []string{``, ``, ``, ``, ``, ``, ``, ``, ``, ``}
if !opts.Unicode {
spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
}
t := Terminal{ t := Terminal{
initDelay: delay, initDelay: delay,
infoStyle: opts.InfoStyle, infoStyle: opts.InfoStyle,
queryLen: [2]int{0, 0}, spinner: spinner,
layout: opts.Layout, queryLen: [2]int{0, 0},
fullscreen: fullscreen, layout: opts.Layout,
hscroll: opts.Hscroll, fullscreen: fullscreen,
hscrollOff: opts.HscrollOff, keepRight: opts.KeepRight,
wordRubout: wordRubout, hscroll: opts.Hscroll,
wordNext: wordNext, hscrollOff: opts.HscrollOff,
cx: len(input), wordRubout: wordRubout,
cy: 0, wordNext: wordNext,
offset: 0, cx: len(input),
xoffset: 0, cy: 0,
yanked: []rune{}, offset: 0,
input: input, xoffset: 0,
multi: opts.Multi, yanked: []rune{},
sort: opts.Sort > 0, input: input,
toggleSort: opts.ToggleSort, multi: opts.Multi,
delimiter: opts.Delimiter, sort: opts.Sort > 0,
expect: opts.Expect, toggleSort: opts.ToggleSort,
keymap: opts.Keymap, delimiter: opts.Delimiter,
pressed: "", expect: opts.Expect,
printQuery: opts.PrintQuery, keymap: opts.Keymap,
history: opts.History, pressed: "",
margin: opts.Margin, printQuery: opts.PrintQuery,
unicode: opts.Unicode, history: opts.History,
bordered: opts.Bordered, margin: opts.Margin,
cleanExit: opts.ClearOnExit, unicode: opts.Unicode,
strong: strongAttr, borderShape: opts.BorderShape,
cycle: opts.Cycle, cleanExit: opts.ClearOnExit,
header: header, strong: strongAttr,
header0: header, cycle: opts.Cycle,
ansi: opts.Ansi, header: header,
tabstop: opts.Tabstop, header0: header,
reading: true, ansi: opts.Ansi,
failed: nil, tabstop: opts.Tabstop,
jumping: jumpDisabled, reading: true,
jumpLabels: opts.JumpLabels, failed: nil,
printer: opts.Printer, jumping: jumpDisabled,
printsep: opts.PrintSep, jumpLabels: opts.JumpLabels,
merger: EmptyMerger, printer: opts.Printer,
selected: make(map[int32]selectedItem), printsep: opts.PrintSep,
reqBox: util.NewEventBox(), merger: EmptyMerger,
preview: opts.Preview, selected: make(map[int32]selectedItem),
previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden, false}, reqBox: util.NewEventBox(),
previewBox: previewBox, preview: opts.Preview,
eventBox: eventBox, previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden, false},
mutex: sync.Mutex{}, previewBox: previewBox,
suppress: true, eventBox: eventBox,
slab: util.MakeSlab(slab16Size, slab32Size), mutex: sync.Mutex{},
theme: opts.Theme, suppress: true,
startChan: make(chan bool, 1), slab: util.MakeSlab(slab16Size, slab32Size),
killChan: make(chan int), theme: opts.Theme,
tui: renderer, startChan: make(chan bool, 1),
initFunc: func() { renderer.Init() }} killChan: make(chan int),
tui: renderer,
initFunc: func() { renderer.Init() }}
t.prompt, t.promptLen = t.processTabs([]rune(opts.Prompt), 0) t.prompt, t.promptLen = t.processTabs([]rune(opts.Prompt), 0)
t.pointer, t.pointerLen = t.processTabs([]rune(opts.Pointer), 0)
t.marker, t.markerLen = t.processTabs([]rune(opts.Marker), 0)
// Pre-calculated empty pointer and marker signs
t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
t.markerEmpty = strings.Repeat(" ", t.markerLen)
return &t return &t
} }
@@ -493,10 +516,13 @@ func (t *Terminal) UpdateProgress(progress float32) {
} }
// UpdateList updates Merger to display the list // UpdateList updates Merger to display the list
func (t *Terminal) UpdateList(merger *Merger) { func (t *Terminal) UpdateList(merger *Merger, reset bool) {
t.mutex.Lock() t.mutex.Lock()
t.progress = 100 t.progress = 100
t.merger = merger t.merger = merger
if reset {
t.selected = make(map[int32]selectedItem)
}
t.mutex.Unlock() t.mutex.Unlock()
t.reqBox.Set(reqInfo, nil) t.reqBox.Set(reqInfo, nil)
t.reqBox.Set(reqList, nil) t.reqBox.Set(reqList, nil)
@@ -573,8 +599,11 @@ func (t *Terminal) resizeWindows() {
} else { } else {
marginInt[idx] = int(sizeSpec.size) marginInt[idx] = int(sizeSpec.size)
} }
if t.bordered && idx%2 == 0 { switch t.borderShape {
marginInt[idx] += 1 case tui.BorderHorizontal:
marginInt[idx] += 1 - idx%2
case tui.BorderRounded, tui.BorderSharp:
marginInt[idx] += 1 + idx%2
} }
} }
adjust := func(idx1 int, idx2 int, max int, min int) { adjust := func(idx1 int, idx2 int, max int, min int) {
@@ -614,21 +643,30 @@ func (t *Terminal) resizeWindows() {
width := screenWidth - marginInt[1] - marginInt[3] width := screenWidth - marginInt[1] - marginInt[3]
height := screenHeight - marginInt[0] - marginInt[2] height := screenHeight - marginInt[0] - marginInt[2]
if t.bordered { switch t.borderShape {
case tui.BorderHorizontal:
t.border = t.tui.NewWindow( t.border = t.tui.NewWindow(
marginInt[0]-1, marginInt[0]-1,
marginInt[3], marginInt[3],
width, width,
height+2, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode)) height+2,
false, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode))
case tui.BorderRounded, tui.BorderSharp:
t.border = t.tui.NewWindow(
marginInt[0]-1,
marginInt[3]-2,
width+4,
height+2,
false, tui.MakeBorderStyle(t.borderShape, t.unicode))
} }
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode) noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
if previewVisible { if previewVisible {
createPreviewWindow := func(y int, x int, w int, h int) { createPreviewWindow := func(y int, x int, w int, h int) {
previewBorder := tui.MakeBorderStyle(tui.BorderAround, t.unicode) previewBorder := tui.MakeBorderStyle(tui.BorderRounded, t.unicode)
if !t.preview.border { if !t.preview.border {
previewBorder = tui.MakeTransparentBorder() previewBorder = tui.MakeTransparentBorder()
} }
t.pborder = t.tui.NewWindow(y, x, w, h, previewBorder) t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
pwidth := w - 4 pwidth := w - 4
// ncurses auto-wraps the line when the cursor reaches the right-end of // ncurses auto-wraps the line when the cursor reaches the right-end of
// the window. To prevent unintended line-wraps, we use the width one // the window. To prevent unintended line-wraps, we use the width one
@@ -636,28 +674,28 @@ func (t *Terminal) resizeWindows() {
if !t.preview.wrap && t.tui.DoesAutoWrap() { if !t.preview.wrap && t.tui.DoesAutoWrap() {
pwidth += 1 pwidth += 1
} }
t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, h-2, noBorder) t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, h-2, true, noBorder)
} }
switch t.preview.position { switch t.preview.position {
case posUp: case posUp:
pheight := calculateSize(height, t.preview.size, minHeight, 3) pheight := calculateSize(height, t.preview.size, minHeight, 3)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0]+pheight, marginInt[3], width, height-pheight, noBorder) marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder)
createPreviewWindow(marginInt[0], marginInt[3], width, pheight) createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
case posDown: case posDown:
pheight := calculateSize(height, t.preview.size, minHeight, 3) pheight := calculateSize(height, t.preview.size, minHeight, 3)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0], marginInt[3], width, height-pheight, noBorder) marginInt[0], marginInt[3], width, height-pheight, false, noBorder)
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight) createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
case posLeft: case posLeft:
pwidth := calculateSize(width, t.preview.size, minWidth, 5) pwidth := calculateSize(width, t.preview.size, minWidth, 5)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, noBorder) marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false, noBorder)
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height) createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
case posRight: case posRight:
pwidth := calculateSize(width, t.preview.size, minWidth, 5) pwidth := calculateSize(width, t.preview.size, minWidth, 5)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0], marginInt[3], width-pwidth, height, noBorder) marginInt[0], marginInt[3], width-pwidth, height, false, noBorder)
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height) createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
} }
} else { } else {
@@ -665,7 +703,7 @@ func (t *Terminal) resizeWindows() {
marginInt[0], marginInt[0],
marginInt[3], marginInt[3],
width, width,
height, noBorder) height, false, noBorder)
} }
for i := 0; i < t.window.Height(); i++ { for i := 0; i < t.window.Height(); i++ {
t.window.MoveAndClear(i, 0) t.window.MoveAndClear(i, 0)
@@ -738,8 +776,8 @@ func (t *Terminal) printInfo() {
t.move(1, 0, true) t.move(1, 0, true)
if t.reading { if t.reading {
duration := int64(spinnerDuration) duration := int64(spinnerDuration)
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration
t.window.CPrint(tui.ColSpinner, t.strong, _spinner[idx]) t.window.CPrint(tui.ColSpinner, t.strong, t.spinner[idx])
} }
t.move(1, 2, false) t.move(1, 2, false)
pos = 2 pos = 2
@@ -785,7 +823,7 @@ func (t *Terminal) printInfo() {
maxWidth := t.window.Width() - pos maxWidth := t.window.Width() - pos
if len(output) > maxWidth { if len(output) > maxWidth {
outputRunes, _ := t.trimRight([]rune(output), maxWidth-2) outputRunes, _ := t.trimRight([]rune(output), maxWidth-2)
output = string(outputRunes) + ".." output = string(outputRunes) + strings.Repeat(".", util.Constrain(maxWidth, 0, 2))
} }
t.window.CPrint(tui.ColInfo, 0, output) t.window.CPrint(tui.ColInfo, 0, output)
} }
@@ -842,15 +880,15 @@ func (t *Terminal) printList() {
func (t *Terminal) printItem(result Result, line int, i int, current bool) { func (t *Terminal) printItem(result Result, line int, i int, current bool) {
item := result.item item := result.item
_, selected := t.selected[item.Index()] _, selected := t.selected[item.Index()]
label := " " label := t.pointerEmpty
if t.jumping != jumpDisabled { if t.jumping != jumpDisabled {
if i < len(t.jumpLabels) { if i < len(t.jumpLabels) {
// Striped // Striped
current = i%2 == 0 current = i%2 == 0
label = t.jumpLabels[i : i+1] label = t.jumpLabels[i:i+1] + strings.Repeat(" ", t.pointerLen-1)
} }
} else if current { } else if current {
label = ">" label = t.pointer
} }
// Avoid unnecessary redraw // Avoid unnecessary redraw
@@ -869,17 +907,17 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
if current { if current {
t.window.CPrint(tui.ColCurrentCursor, t.strong, label) t.window.CPrint(tui.ColCurrentCursor, t.strong, label)
if selected { if selected {
t.window.CPrint(tui.ColCurrentSelected, t.strong, ">") t.window.CPrint(tui.ColCurrentSelected, t.strong, t.marker)
} else { } else {
t.window.CPrint(tui.ColCurrentSelected, t.strong, " ") t.window.CPrint(tui.ColCurrentSelected, t.strong, t.markerEmpty)
} }
newLine.width = t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true, true) newLine.width = t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true, true)
} else { } else {
t.window.CPrint(tui.ColCursor, t.strong, label) t.window.CPrint(tui.ColCursor, t.strong, label)
if selected { if selected {
t.window.CPrint(tui.ColSelected, t.strong, ">") t.window.CPrint(tui.ColSelected, t.strong, t.marker)
} else { } else {
t.window.Print(" ") t.window.Print(t.markerEmpty)
} }
newLine.width = t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false, true) newLine.width = t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false, true)
} }
@@ -965,34 +1003,41 @@ func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.Color
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text)) maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth) displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
if displayWidth > maxWidth { if displayWidth > maxWidth {
transformOffsets := func(diff int32) {
for idx, offset := range offsets {
b, e := offset.offset[0], offset.offset[1]
b += 2 - diff
e += 2 - diff
b = util.Max32(b, 2)
offsets[idx].offset[0] = b
offsets[idx].offset[1] = util.Max32(b, e)
}
}
if t.hscroll { if t.hscroll {
// Stri.. if t.keepRight && pos == nil {
if !t.overflow(text[:maxe], maxWidth-2) { trimmed, diff := t.trimLeft(text, maxWidth-2)
transformOffsets(diff)
text = append([]rune(ellipsis), trimmed...)
} else if !t.overflow(text[:maxe], maxWidth-2) {
// Stri..
text, _ = t.trimRight(text, maxWidth-2) text, _ = t.trimRight(text, maxWidth-2)
text = append(text, []rune("..")...) text = append(text, []rune(ellipsis)...)
} else { } else {
// Stri.. // Stri..
if t.overflow(text[maxe:], 2) { if t.overflow(text[maxe:], 2) {
text = append(text[:maxe], []rune("..")...) text = append(text[:maxe], []rune(ellipsis)...)
} }
// ..ri.. // ..ri..
var diff int32 var diff int32
text, diff = t.trimLeft(text, maxWidth-2) text, diff = t.trimLeft(text, maxWidth-2)
// Transform offsets // Transform offsets
for idx, offset := range offsets { transformOffsets(diff)
b, e := offset.offset[0], offset.offset[1] text = append([]rune(ellipsis), text...)
b += 2 - diff
e += 2 - diff
b = util.Max32(b, 2)
offsets[idx].offset[0] = b
offsets[idx].offset[1] = util.Max32(b, e)
}
text = append([]rune(".."), text...)
} }
} else { } else {
text, _ = t.trimRight(text, maxWidth-2) text, _ = t.trimRight(text, maxWidth-2)
text = append(text, []rune("..")...) text = append(text, []rune(ellipsis)...)
for idx, offset := range offsets { for idx, offset := range offsets {
offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-2)) offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-2))
@@ -1066,7 +1111,7 @@ func (t *Terminal) printPreview() {
if t.theme != nil && ansi != nil && ansi.colored() { if t.theme != nil && ansi != nil && ansi.colored() {
fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str) fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
} else { } else {
fillRet = t.pwindow.CFill(tui.ColNormal.Fg(), tui.ColNormal.Bg(), tui.AttrRegular, str) fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str)
} }
return fillRet == tui.FillContinue return fillRet == tui.FillContinue
}) })
@@ -1123,7 +1168,7 @@ func (t *Terminal) refresh() {
t.placeCursor() t.placeCursor()
if !t.suppress { if !t.suppress {
windows := make([]tui.Window, 0, 4) windows := make([]tui.Window, 0, 4)
if t.bordered { if t.borderShape != tui.BorderNone {
windows = append(windows, t.border) windows = append(windows, t.border)
} }
if t.hasPreviewWindow() { if t.hasPreviewWindow() {
@@ -1237,7 +1282,7 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
return false, matchWithoutFlags, flags return false, matchWithoutFlags, flags
} }
func hasPreviewFlags(template string) (plus bool, query bool) { func hasPreviewFlags(template string) (slot bool, plus bool, query bool) {
for _, match := range placeholder.FindAllString(template, -1) { for _, match := range placeholder.FindAllString(template, -1) {
_, _, flags := parsePlaceholder(match) _, _, flags := parsePlaceholder(match)
if flags.plus { if flags.plus {
@@ -1246,6 +1291,7 @@ func hasPreviewFlags(template string) (plus bool, query bool) {
if flags.query { if flags.query {
query = true query = true
} }
slot = true
} }
return return
} }
@@ -1409,7 +1455,7 @@ func (t *Terminal) currentItem() *Item {
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) { func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) {
current := t.currentItem() current := t.currentItem()
plus, query := hasPreviewFlags(template) _, plus, query := hasPreviewFlags(template)
if !(query && len(t.input) > 0 || (forcePlus || plus) && len(t.selected) > 0) { if !(query && len(t.input) > 0 || (forcePlus || plus) && len(t.selected) > 0) {
return current != nil, []*Item{current, current} return current != nil, []*Item{current, current}
} }
@@ -1565,7 +1611,10 @@ func (t *Terminal) Loop() {
var out bytes.Buffer var out bytes.Buffer
cmd.Stdout = &out cmd.Stdout = &out
cmd.Stderr = &out cmd.Stderr = &out
cmd.Start() err := cmd.Start()
if err != nil {
out.Write([]byte(err.Error()))
}
finishChan := make(chan bool, 1) finishChan := make(chan bool, 1)
updateChan := make(chan bool) updateChan := make(chan bool)
go func() { go func() {
@@ -1820,6 +1869,13 @@ func (t *Terminal) Loop() {
t.input = []rune{} t.input = []rune{}
t.cx = 0 t.cx = 0
} }
case actBackwardDeleteCharEOF:
if len(t.input) == 0 {
req(reqQuit)
} else if t.cx > 0 {
t.input = append(t.input[:t.cx-1], t.input[t.cx:]...)
t.cx--
}
case actForwardChar: case actForwardChar:
if t.cx < len(t.input) { if t.cx < len(t.input) {
t.cx++ t.cx++
@@ -1904,6 +1960,15 @@ func (t *Terminal) Loop() {
} }
case actClearScreen: case actClearScreen:
req(reqRedraw) req(reqRedraw)
case actClearQuery:
t.input = []rune{}
t.cx = 0
case actClearSelection:
if t.multi > 0 {
t.selected = make(map[int32]selectedItem)
t.version++
req(reqList, reqInfo)
}
case actTop: case actTop:
t.vset(0) t.vset(0)
req(reqList) req(reqList)
@@ -2045,17 +2110,17 @@ func (t *Terminal) Loop() {
t.failed = nil t.failed = nil
valid, list := t.buildPlusList(a.a, false) valid, list := t.buildPlusList(a.a, false)
// If the command template has {q}, we run the command even when the
// query string is empty.
if !valid { if !valid {
_, query := hasPreviewFlags(a.a) // We run the command even when there's no match
valid = query // 1. If the template doesn't have any slots
// 2. If the template has {q}
slot, _, query := hasPreviewFlags(a.a)
valid = !slot || query
} }
if valid { if valid {
command := replacePlaceholder(a.a, command := replacePlaceholder(a.a,
t.ansi, t.delimiter, t.printsep, false, string(t.input), list) t.ansi, t.delimiter, t.printsep, false, string(t.input), list)
newCommand = &command newCommand = &command
t.selected = make(map[int32]selectedItem)
} }
} }
return true return true
@@ -2095,7 +2160,7 @@ func (t *Terminal) Loop() {
if queryChanged { if queryChanged {
if t.isPreviewEnabled() { if t.isPreviewEnabled() {
_, q := hasPreviewFlags(t.preview.command) _, _, q := hasPreviewFlags(t.preview.command)
if q { if q {
t.version++ t.version++
} }

View File

@@ -39,6 +39,6 @@ func (r *FullscreenRenderer) MaxY() int { return 0 }
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {} func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window { func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
return nil return nil
} }

View File

@@ -7,7 +7,6 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"syscall"
"time" "time"
"unicode/utf8" "unicode/utf8"
@@ -23,27 +22,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")
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,10 +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 { func (r *LightRenderer) defaultTheme() *ColorTheme {
if strings.Contains(os.Getenv("TERM"), "256") { if strings.Contains(os.Getenv("TERM"), "256") {
return Dark256 return Dark256
@@ -147,22 +136,6 @@ func (r *LightRenderer) defaultTheme() *ColorTheme {
return Default16 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 +154,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 +229,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 +263,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
@@ -453,7 +407,10 @@ func (r *LightRenderer) escSequence(sz *int) Event {
*sz = 4 *sz = 4
switch r.buffer[2] { switch r.buffer[2] {
case 50: case 50:
if len(r.buffer) == 5 && r.buffer[4] == 126 { if r.buffer[3] == 126 {
return Event{Insert, 0, nil}
}
if len(r.buffer) > 4 && r.buffer[4] == 126 {
*sz = 5 *sz = 5
switch r.buffer[3] { switch r.buffer[3] {
case 48: case 48:
@@ -467,7 +424,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
} }
} }
// 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
@@ -486,10 +443,18 @@ func (r *LightRenderer) escSequence(sz *int) Event {
switch r.buffer[3] { switch r.buffer[3] {
case 126: case 126:
return Event{Home, 0, nil} return Event{Home, 0, nil}
case 53, 55, 56, 57: case 49, 50, 51, 52, 53, 55, 56, 57:
if len(r.buffer) == 5 && r.buffer[4] == 126 { if len(r.buffer) == 5 && r.buffer[4] == 126 {
*sz = 5 *sz = 5
switch r.buffer[3] { switch r.buffer[3] {
case 49:
return Event{F1, 0, nil}
case 50:
return Event{F2, 0, nil}
case 51:
return Event{F3, 0, nil}
case 52:
return Event{F4, 0, nil}
case 53: case 53:
return Event{F5, 0, nil} return Event{F5, 0, nil}
case 55: case 55:
@@ -584,7 +549,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()
@@ -597,7 +562,7 @@ func (r *LightRenderer) Pause(clear bool) {
} }
func (r *LightRenderer) Resume(clear bool) { func (r *LightRenderer) Resume(clear bool) {
terminal.MakeRaw(r.fd()) r.setupTerminal()
if clear { if clear {
if r.fullscreen { if r.fullscreen {
r.smcup() r.smcup()
@@ -651,7 +616,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 {
@@ -666,10 +632,11 @@ func (r *LightRenderer) DoesAutoWrap() bool {
return false return false
} }
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, 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 != nil,
preview: preview,
border: borderStyle, border: borderStyle,
top: top, top: top,
left: left, left: left,
@@ -679,8 +646,13 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
fg: colDefault, fg: colDefault,
bg: colDefault} bg: colDefault}
if r.theme != nil { if r.theme != nil {
w.fg = r.theme.Fg if preview {
w.bg = r.theme.Bg w.fg = r.theme.PreviewFg
w.bg = r.theme.PreviewBg
} else {
w.fg = r.theme.Fg
w.bg = r.theme.Bg
}
} }
w.drawBorder() w.drawBorder()
return w return w
@@ -688,7 +660,7 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
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()
@@ -704,16 +676,20 @@ func (w *LightWindow) drawBorderHorizontal() {
func (w *LightWindow) drawBorderAround() { func (w *LightWindow) drawBorderAround() {
w.Move(0, 0) w.Move(0, 0)
w.CPrint(ColBorder, AttrRegular, color := ColBorder
if w.preview {
color = ColPreviewBorder
}
w.CPrint(color, AttrRegular,
string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight)) 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(ColBorder, AttrRegular, string(w.border.vertical)) w.CPrint(color, AttrRegular, string(w.border.vertical))
w.cprint2(colDefault, w.bg, AttrRegular, repeat(' ', w.width-2)) w.CPrint(color, AttrRegular, repeat(' ', w.width-2))
w.CPrint(ColBorder, AttrRegular, string(w.border.vertical)) w.CPrint(color, AttrRegular, string(w.border.vertical))
} }
w.Move(w.height-1, 0) w.Move(w.height-1, 0)
w.CPrint(ColBorder, AttrRegular, w.CPrint(color, AttrRegular,
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))
} }

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

@@ -0,0 +1,97 @@
// +build !windows
package tui
import (
"fmt"
"os"
"syscall"
"github.com/junegunn/fzf/src/util"
"golang.org/x/crypto/ssh/terminal"
)
func IsLightRendererSupported() bool {
return true
}
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
}

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

@@ -0,0 +1,132 @@
//+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) 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,10 +28,12 @@ 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
height int height int
normal ColorPair
lastX int lastX int
lastY int lastY int
moveCursor bool moveCursor bool
@@ -317,6 +319,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:
@@ -408,14 +412,19 @@ func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
_screen.Show() _screen.Show()
} }
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window { func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
// TODO normal := ColNormal
if preview {
normal = ColPreview
}
return &TcellWindow{ return &TcellWindow{
color: r.theme != nil, color: r.theme != nil,
preview: preview,
top: top, top: top,
left: left, left: left,
width: width, width: width,
height: height, height: height,
normal: normal,
borderStyle: borderStyle} borderStyle: borderStyle}
} }
@@ -423,16 +432,16 @@ func (w *TcellWindow) Close() {
// TODO // TODO
} }
func fill(x, y, w, h int, r rune) { func fill(x, y, w, h int, n ColorPair, r rune) {
for ly := 0; ly <= h; ly++ { for ly := 0; ly <= h; ly++ {
for lx := 0; lx <= w; lx++ { for lx := 0; lx <= w; lx++ {
_screen.SetContent(x+lx, y+ly, r, nil, ColNormal.style()) _screen.SetContent(x+lx, y+ly, r, nil, n.style())
} }
} }
} }
func (w *TcellWindow) Erase() { func (w *TcellWindow) Erase() {
fill(w.left-1, w.top, w.width+1, w.height, ' ') fill(w.left-1, w.top, w.width+1, w.height, w.normal, ' ')
} }
func (w *TcellWindow) Enclose(y int, x int) bool { func (w *TcellWindow) Enclose(y int, x int) bool {
@@ -449,13 +458,13 @@ func (w *TcellWindow) Move(y int, x int) {
func (w *TcellWindow) MoveAndClear(y int, x int) { func (w *TcellWindow) MoveAndClear(y int, x int) {
w.Move(y, x) w.Move(y, x)
for i := w.lastX; i < w.width; i++ { for i := w.lastX; i < w.width; i++ {
_screen.SetContent(i+w.left, w.lastY+w.top, rune(' '), nil, ColNormal.style()) _screen.SetContent(i+w.left, w.lastY+w.top, rune(' '), nil, w.normal.style())
} }
w.lastX = x w.lastX = x
} }
func (w *TcellWindow) Print(text string) { func (w *TcellWindow) Print(text string) {
w.printString(text, ColNormal, 0) w.printString(text, w.normal, 0)
} }
func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) { func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
@@ -468,7 +477,7 @@ func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
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 { } else {
style = ColNormal.style(). style = w.normal.style().
Reverse(a&Attr(tcell.AttrReverse) != 0 || pair == ColCurrent || pair == ColCurrentMatch). Reverse(a&Attr(tcell.AttrReverse) != 0 || pair == ColCurrent || pair == ColCurrentMatch).
Underline(a&Attr(tcell.AttrUnderline) != 0 || pair == ColMatch || pair == ColCurrentMatch) Underline(a&Attr(tcell.AttrUnderline) != 0 || pair == ColMatch || pair == ColCurrentMatch)
} }
@@ -519,7 +528,7 @@ func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn
if w.color { if w.color {
style = pair.style() style = pair.style()
} else { } else {
style = ColNormal.style() style = w.normal.style()
} }
style = style. style = style.
Blink(a&Attr(tcell.AttrBlink) != 0). Blink(a&Attr(tcell.AttrBlink) != 0).
@@ -559,15 +568,15 @@ func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn
} }
func (w *TcellWindow) Fill(str string) FillReturn { func (w *TcellWindow) Fill(str string) FillReturn {
return w.fillString(str, ColNormal, 0) return w.fillString(str, w.normal, 0)
} }
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 {
if fg == colDefault { if fg == colDefault {
fg = ColNormal.Fg() fg = w.normal.Fg()
} }
if bg == colDefault { if bg == colDefault {
bg = ColNormal.Bg() bg = w.normal.Bg()
} }
return w.fillString(str, NewColorPair(fg, bg), a) return w.fillString(str, NewColorPair(fg, bg), a)
} }
@@ -584,9 +593,13 @@ func (w *TcellWindow) drawBorder() {
var style tcell.Style var style tcell.Style
if w.color { if w.color {
style = ColBorder.style() if w.preview {
style = ColPreviewBorder.style()
} else {
style = ColBorder.style()
}
} else { } else {
style = ColNormal.style() style = w.normal.style()
} }
for x := left; x < right; x++ { for x := left; x < right; x++ {
@@ -594,7 +607,7 @@ func (w *TcellWindow) drawBorder() {
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style) _screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
} }
if w.borderStyle.shape == BorderAround { if w.borderStyle.shape != BorderHorizontal {
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)
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style) _screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)

View File

@@ -66,6 +66,7 @@ const (
Right Right
Home Home
End End
Insert
SUp SUp
SDown SDown
@@ -173,6 +174,8 @@ func (p ColorPair) Bg() Color {
type ColorTheme struct { type ColorTheme struct {
Fg Color Fg Color
Bg Color Bg Color
PreviewFg Color
PreviewBg Color
DarkBg Color DarkBg Color
Gutter Color Gutter Color
Prompt Color Prompt Color
@@ -207,7 +210,8 @@ type BorderShape int
const ( const (
BorderNone BorderShape = iota BorderNone BorderShape = iota
BorderAround BorderRounded
BorderSharp
BorderHorizontal BorderHorizontal
) )
@@ -225,6 +229,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: '─',
@@ -248,7 +263,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: ' ',
@@ -272,7 +287,7 @@ type Renderer interface {
MaxY() int MaxY() int
DoesAutoWrap() bool DoesAutoWrap() bool
NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window
} }
type Window interface { type Window interface {
@@ -334,12 +349,16 @@ var (
ColInfo ColorPair ColInfo ColorPair
ColHeader ColorPair ColHeader ColorPair
ColBorder ColorPair ColBorder ColorPair
ColPreview ColorPair
ColPreviewBorder ColorPair
) )
func EmptyTheme() *ColorTheme { func EmptyTheme() *ColorTheme {
return &ColorTheme{ return &ColorTheme{
Fg: colUndefined, Fg: colUndefined,
Bg: colUndefined, Bg: colUndefined,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: colUndefined, DarkBg: colUndefined,
Gutter: colUndefined, Gutter: colUndefined,
Prompt: colUndefined, Prompt: colUndefined,
@@ -363,8 +382,10 @@ func init() {
Default16 = &ColorTheme{ Default16 = &ColorTheme{
Fg: colDefault, Fg: colDefault,
Bg: colDefault, Bg: colDefault,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: colBlack, DarkBg: colBlack,
Gutter: colBlack, Gutter: colUndefined,
Prompt: colBlue, Prompt: colBlue,
Match: colGreen, Match: colGreen,
Current: colYellow, Current: colYellow,
@@ -378,6 +399,8 @@ func init() {
Dark256 = &ColorTheme{ Dark256 = &ColorTheme{
Fg: colDefault, Fg: colDefault,
Bg: colDefault, Bg: colDefault,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: 236, DarkBg: 236,
Gutter: colUndefined, Gutter: colUndefined,
Prompt: 110, Prompt: 110,
@@ -393,6 +416,8 @@ func init() {
Light256 = &ColorTheme{ Light256 = &ColorTheme{
Fg: colDefault, Fg: colDefault,
Bg: colDefault, Bg: colDefault,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: 251, DarkBg: 251,
Gutter: colUndefined, Gutter: colUndefined,
Prompt: 25, Prompt: 25,
@@ -425,6 +450,8 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
} }
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.PreviewBg = o(theme.Bg, o(baseTheme.PreviewBg, theme.PreviewBg))
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg) theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
theme.Gutter = o(theme.DarkBg, o(baseTheme.Gutter, theme.Gutter)) theme.Gutter = o(theme.DarkBg, o(baseTheme.Gutter, theme.Gutter))
theme.Prompt = o(baseTheme.Prompt, theme.Prompt) theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
@@ -461,6 +488,8 @@ func initPalette(theme *ColorTheme) {
ColInfo = pair(theme.Info, theme.Bg) ColInfo = pair(theme.Info, theme.Bg)
ColHeader = pair(theme.Header, theme.Bg) ColHeader = pair(theme.Header, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg) ColBorder = pair(theme.Border, theme.Bg)
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
} else { } else {
ColPrompt = pair(colDefault, colDefault) ColPrompt = pair(colDefault, colDefault)
ColNormal = pair(colDefault, colDefault) ColNormal = pair(colDefault, colDefault)
@@ -475,6 +504,8 @@ func initPalette(theme *ColorTheme) {
ColInfo = pair(colDefault, colDefault) ColInfo = pair(colDefault, colDefault)
ColHeader = pair(colDefault, colDefault) ColHeader = pair(colDefault, colDefault)
ColBorder = pair(colDefault, colDefault) ColBorder = pair(colDefault, colDefault)
ColPreview = pair(colDefault, colDefault)
ColPreviewBorder = pair(colDefault, colDefault)
} }
} }

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-- {

View File

@@ -112,3 +112,13 @@ func DurWithin(
func IsTty() bool { func IsTty() bool {
return isatty.IsTerminal(os.Stdin.Fd()) return isatty.IsTerminal(os.Stdin.Fd())
} }
// Once returns a function that returns the specified boolean value only once
func Once(nextResponse bool) func() bool {
state := nextResponse
return func() bool {
prevState := state
state = false
return prevState
}
}

View File

@@ -20,3 +20,21 @@ func TestContrain(t *testing.T) {
t.Error("Expected", 3) t.Error("Expected", 3)
} }
} }
func TestOnce(t *testing.T) {
o := Once(false)
if o() {
t.Error("Expected: false")
}
if o() {
t.Error("Expected: false")
}
o = Once(true)
if !o() {
t.Error("Expected: true")
}
if o() {
t.Error("Expected: false")
}
}

View File

@@ -9,13 +9,23 @@ require 'minitest/autorun'
require 'fileutils' require 'fileutils'
require 'English' require 'English'
require 'shellwords' require 'shellwords'
require 'erb'
require 'tempfile'
TEMPLATE = DATA.read
UNSETS = %w[
FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS
FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS
FZF_ALT_C_COMMAND
FZF_ALT_C_OPTS FZF_CTRL_R_OPTS
fish_history
].freeze
DEFAULT_TIMEOUT = 20 DEFAULT_TIMEOUT = 20
FILE = File.expand_path(__FILE__) FILE = File.expand_path(__FILE__)
base = File.expand_path('../../', __FILE__) BASE = File.expand_path('..', __dir__)
Dir.chdir base Dir.chdir(BASE)
FZF = "FZF_DEFAULT_OPTS= FZF_DEFAULT_COMMAND= #{base}/bin/fzf" FZF = "FZF_DEFAULT_OPTS= FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf"
class NilClass class NilClass
def include?(_str) def include?(_str)
@@ -42,96 +52,74 @@ end
class Shell class Shell
class << self class << self
def unsets
'unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS FZF_ALT_C_COMMAND FZF_ALT_C_OPTS FZF_CTRL_R_OPTS;'
end
def bash def bash
'PS1= PROMPT_COMMAND= bash --rcfile ~/.fzf.bash' @bash ||=
begin
bashrc = '/tmp/fzf.bash'
File.open(bashrc, 'w') do |f|
f.puts(ERB.new(TEMPLATE).result(binding))
end
"bash --rcfile #{bashrc}"
end
end end
def zsh def zsh
FileUtils.mkdir_p '/tmp/fzf-zsh' @zsh ||=
FileUtils.cp File.expand_path('~/.fzf.zsh'), '/tmp/fzf-zsh/.zshrc' begin
'PS1= PROMPT_COMMAND= HISTSIZE=100 ZDOTDIR=/tmp/fzf-zsh zsh' zdotdir = '/tmp/fzf-zsh'
FileUtils.rm_rf(zdotdir)
FileUtils.mkdir_p(zdotdir)
File.open("#{zdotdir}/.zshrc", 'w') do |f|
f.puts(ERB.new(TEMPLATE).result(binding))
end
"ZDOTDIR=#{zdotdir} zsh"
end
end end
def fish def fish
'fish' UNSETS.map { |v| v + '= ' }.join + 'fish'
end end
end end
end end
class Tmux class Tmux
TEMPNAME = '/tmp/fzf-test.txt'
attr_reader :win attr_reader :win
def initialize(shell = :bash) def initialize(shell = :bash)
@win = @win = go(%W[new-window -d -P -F #I #{Shell.send(shell)}]).first
case shell go(%W[set-window-option -t #{@win} pane-base-index 0])
when :bash
go("new-window -d -P -F '#I' '#{Shell.unsets + Shell.bash}'").first
when :zsh
go("new-window -d -P -F '#I' '#{Shell.unsets + Shell.zsh}'").first
when :fish
go("new-window -d -P -F '#I' '#{Shell.unsets + Shell.fish}'").first
else
raise "Unknown shell: #{shell}"
end
go("set-window-option -t #{@win} pane-base-index 0")
@lines = `tput lines`.chomp.to_i
return unless shell == :fish return unless shell == :fish
send_keys('function fish_prompt; end; clear', :Enter) send_keys('function fish_prompt; end; clear', :Enter)
self.until(&:empty?) self.until(&:empty?)
end end
def kill def kill
go("kill-window -t #{win} 2> /dev/null") go(%W[kill-window -t #{win}])
end
def focus
go(%W[select-window -t #{win}])
end end
def send_keys(*args) def send_keys(*args)
target = go(%W[send-keys -t #{win}] + args.map(&:to_s))
if args.last.is_a?(Hash)
hash = args.pop
go("select-window -t #{win}")
"#{win}.#{hash[:pane]}"
else
win
end
enum = (args + [nil]).each_cons(2)
loop do
pair = enum.next
if pair.first == :Escape
arg = pair.compact.map { |key| %("#{key}") }.join(' ')
go(%(send-keys -t #{target} #{arg}))
enum.next if pair.last
else
go(%(send-keys -t #{target} "#{pair.first}"))
end
break unless pair.last
end
end end
def paste(str) def paste(str)
`tmux setb '#{str.gsub("'", "'\\''")}' \\; pasteb -t #{win} \\; send-keys -t #{win} Enter` system('tmux', 'setb', str, ';', 'pasteb', '-t', win, ';', 'send-keys', '-t', win, 'Enter')
end end
def capture(pane = 0) def capture
File.unlink TEMPNAME while File.exist? TEMPNAME go(%W[capture-pane -p -t #{win}]).reverse.drop_while(&:empty?).reverse
wait do
go("capture-pane -t #{win}.#{pane} \\; save-buffer #{TEMPNAME} 2> /dev/null")
$CHILD_STATUS.exitstatus.zero?
end
File.read(TEMPNAME).split($INPUT_RECORD_SEPARATOR)[0, @lines].reverse.drop_while(&:empty?).reverse
end end
def until(refresh = false, pane = 0) def until(refresh = false)
lines = nil lines = nil
begin begin
wait do wait do
lines = capture(pane) lines = capture
class << lines class << lines
def counts def counts
lazy lazy
@@ -175,7 +163,7 @@ class Tmux
tries = 0 tries = 0
begin begin
self.until do |lines| self.until do |lines|
send_keys 'C-u', 'hello' send_keys ' ', 'C-u', 'hello', :Left, :Right
lines[-1].end_with?('hello') lines[-1].end_with?('hello')
end end
rescue StandardError rescue StandardError
@@ -186,8 +174,8 @@ class Tmux
private private
def go(*args) def go(args)
`tmux #{args.join ' '}`.split($INPUT_RECORD_SEPARATOR) IO.popen(['tmux'] + args) { |io| io.readlines(chomp: true) }
end end
end end
@@ -491,14 +479,14 @@ class TestGoFZF < TestBase
end end
def test_query_unicode def test_query_unicode
tmux.paste "(echo abc; echo 가나다) | #{fzf :query, '가다'}" tmux.paste "(echo abc; echo $'\\352\\260\\200\\353\\202\\230\\353\\213\\244') | #{fzf :query, "$'\\352\\260\\200\\353\\213\\244'"}"
tmux.until { |lines| lines[-2].include? '1/2' } tmux.until { |lines| lines[-2].include? '1/2' }
tmux.send_keys :Enter tmux.send_keys :Enter
assert_equal ['가나다'], readonce.split($INPUT_RECORD_SEPARATOR) assert_equal ['가나다'], readonce.split($INPUT_RECORD_SEPARATOR)
end end
def test_sync def test_sync
tmux.send_keys "seq 1 100 | #{fzf! :multi} | awk '{print \\$1 \\$1}' | #{fzf :sync}", :Enter tmux.send_keys "seq 1 100 | #{fzf! :multi} | awk '{print $1 $1}' | #{fzf :sync}", :Enter
tmux.until { |lines| lines[-1] == '>' } tmux.until { |lines| lines[-1] == '>' }
tmux.send_keys 9 tmux.send_keys 9
tmux.until { |lines| lines[-2] == ' 19/100' } tmux.until { |lines| lines[-2] == ' 19/100' }
@@ -779,7 +767,7 @@ class TestGoFZF < TestBase
end end
def test_invalid_cache_query_type def test_invalid_cache_query_type
command = %[(echo 'foo\\$bar'; echo 'barfoo'; echo 'foo^bar'; echo \\"foo'1-2\\"; seq 100) | #{fzf}] command = %[(echo 'foo$bar'; echo 'barfoo'; echo 'foo^bar'; echo "foo'1-2"; seq 100) | #{fzf}]
# Suffix match # Suffix match
tmux.send_keys command, :Enter tmux.send_keys command, :Enter
@@ -938,7 +926,7 @@ class TestGoFZF < TestBase
def test_execute def test_execute
output = '/tmp/fzf-test-execute' output = '/tmp/fzf-test-execute'
opts = %[--bind \\"alt-a:execute(echo /{}/ >> #{output}),alt-b:execute[echo /{}{}/ >> #{output}],C:execute:echo /{}{}{}/ >> #{output}\\"] opts = %[--bind "alt-a:execute(echo /{}/ >> #{output}),alt-b:execute[echo /{}{}/ >> #{output}],C:execute:echo /{}{}{}/ >> #{output}"]
wait = ->(exp) { tmux.until { |lines| lines[-2].include? exp } } wait = ->(exp) { tmux.until { |lines| lines[-2].include? exp } }
writelines tempname, %w[foo'bar foo"bar foo$bar] writelines tempname, %w[foo'bar foo"bar foo$bar]
tmux.send_keys "cat #{tempname} | #{fzf opts}; sync", :Enter tmux.send_keys "cat #{tempname} | #{fzf opts}; sync", :Enter
@@ -977,7 +965,7 @@ class TestGoFZF < TestBase
def test_execute_multi def test_execute_multi
output = '/tmp/fzf-test-execute-multi' output = '/tmp/fzf-test-execute-multi'
opts = %[--multi --bind \\"alt-a:execute-multi(echo {}/{+} >> #{output}; sync)\\"] opts = %[--multi --bind "alt-a:execute-multi(echo {}/{+} >> #{output}; sync)"]
writelines tempname, %w[foo'bar foo"bar foo$bar foobar] writelines tempname, %w[foo'bar foo"bar foo$bar foobar]
tmux.send_keys "cat #{tempname} | #{fzf opts}", :Enter tmux.send_keys "cat #{tempname} | #{fzf opts}", :Enter
tmux.until { |lines| lines[-2].include? '4/4' } tmux.until { |lines| lines[-2].include? '4/4' }
@@ -1163,7 +1151,7 @@ class TestGoFZF < TestBase
end end
def test_header def test_header
tmux.send_keys "seq 100 | #{fzf "--header \\\"\\$(head -5 #{FILE})\\\""}", :Enter tmux.send_keys "seq 100 | #{fzf "--header \"$(head -5 #{FILE})\""}", :Enter
header = File.readlines(FILE).take(5).map(&:strip) header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines| tmux.until do |lines|
lines[-2].include?('100/100') && lines[-2].include?('100/100') &&
@@ -1173,7 +1161,7 @@ class TestGoFZF < TestBase
end end
def test_header_reverse def test_header_reverse
tmux.send_keys "seq 100 | #{fzf "--header=\\\"\\$(head -5 #{FILE})\\\" --reverse"}", :Enter tmux.send_keys "seq 100 | #{fzf "--header \"$(head -5 #{FILE})\" --reverse"}", :Enter
header = File.readlines(FILE).take(5).map(&:strip) header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines| tmux.until do |lines|
lines[1].include?('100/100') && lines[1].include?('100/100') &&
@@ -1183,7 +1171,7 @@ class TestGoFZF < TestBase
end end
def test_header_reverse_list def test_header_reverse_list
tmux.send_keys "seq 100 | #{fzf "--header=\\\"\\$(head -5 #{FILE})\\\" --layout=reverse-list"}", :Enter tmux.send_keys "seq 100 | #{fzf "--header \"$(head -5 #{FILE})\" --layout=reverse-list"}", :Enter
header = File.readlines(FILE).take(5).map(&:strip) header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines| tmux.until do |lines|
lines[-2].include?('100/100') && lines[-2].include?('100/100') &&
@@ -1193,7 +1181,7 @@ class TestGoFZF < TestBase
end end
def test_header_and_header_lines def test_header_and_header_lines
tmux.send_keys "seq 100 | #{fzf "--header-lines 10 --header \\\"\\$(head -5 #{FILE})\\\""}", :Enter tmux.send_keys "seq 100 | #{fzf "--header-lines 10 --header \"$(head -5 #{FILE})\""}", :Enter
header = File.readlines(FILE).take(5).map(&:strip) header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines| tmux.until do |lines|
lines[-2].include?('90/90') && lines[-2].include?('90/90') &&
@@ -1203,7 +1191,7 @@ class TestGoFZF < TestBase
end end
def test_header_and_header_lines_reverse def test_header_and_header_lines_reverse
tmux.send_keys "seq 100 | #{fzf "--reverse --header-lines 10 --header \\\"\\$(head -5 #{FILE})\\\""}", :Enter tmux.send_keys "seq 100 | #{fzf "--reverse --header-lines 10 --header \"$(head -5 #{FILE})\""}", :Enter
header = File.readlines(FILE).take(5).map(&:strip) header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines| tmux.until do |lines|
lines[1].include?('90/90') && lines[1].include?('90/90') &&
@@ -1213,7 +1201,7 @@ class TestGoFZF < TestBase
end end
def test_header_and_header_lines_reverse_list def test_header_and_header_lines_reverse_list
tmux.send_keys "seq 100 | #{fzf "--layout=reverse-list --header-lines 10 --header \\\"\\$(head -5 #{FILE})\\\""}", :Enter tmux.send_keys "seq 100 | #{fzf "--layout=reverse-list --header-lines 10 --header \"$(head -5 #{FILE})\""}", :Enter
header = File.readlines(FILE).take(5).map(&:strip) header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines| tmux.until do |lines|
lines[-2].include?('90/90') && lines[-2].include?('90/90') &&
@@ -1324,7 +1312,7 @@ class TestGoFZF < TestBase
def test_exitstatus_empty def test_exitstatus_empty
{ '99' => '0', '999' => '1' }.each do |query, status| { '99' => '0', '999' => '1' }.each do |query, status|
tmux.send_keys "seq 100 | #{FZF} -q #{query}; echo --\\$?--", :Enter tmux.send_keys "seq 100 | #{FZF} -q #{query}; echo --$?--", :Enter
tmux.until { |lines| lines[-2] =~ %r{ [10]/100} } tmux.until { |lines| lines[-2] =~ %r{ [10]/100} }
tmux.send_keys :Enter tmux.send_keys :Enter
tmux.until { |lines| lines.last.include? "--#{status}--" } tmux.until { |lines| lines.last.include? "--#{status}--" }
@@ -1407,6 +1395,39 @@ class TestGoFZF < TestBase
assert_equal '3', readonce.chomp assert_equal '3', readonce.chomp
end end
def test_pointer
pointer = '>>'
tmux.send_keys "seq 10 | #{fzf "--pointer '#{pointer}'"}", :Enter
tmux.until { |lines| lines[-2] == ' 10/10' }
lines = tmux.capture
# Assert that specified pointer is displayed
assert_equal "#{pointer} 1", lines[-3]
end
def test_pointer_with_jump
pointer = '>>'
tmux.send_keys "seq 10 | #{fzf "--multi --jump-labels 12345 --bind 'ctrl-j:jump' --pointer '#{pointer}'"}", :Enter
tmux.until { |lines| lines[-2] == ' 10/10' }
tmux.send_keys 'C-j'
# Correctly padded jump label should appear
tmux.until { |lines| lines[-7] == '5 5' }
tmux.until { |lines| lines[-8] == ' 6' }
tmux.send_keys '5'
lines = tmux.capture
# Assert that specified pointer is displayed
assert_equal "#{pointer} 5", lines[-7]
end
def test_marker
marker = '>>'
tmux.send_keys "seq 10 | #{fzf "--multi --marker '#{marker}'"}", :Enter
tmux.until { |lines| lines[-2] == ' 10/10' }
tmux.send_keys :BTab
lines = tmux.capture
# Assert that specified marker is displayed
assert_equal " #{marker}1", lines[-3]
end
def test_preview def test_preview
tmux.send_keys %(seq 1000 | sed s/^2$// | #{FZF} -m --preview 'sleep 0.2; echo {{}-{+}}' --bind ?:toggle-preview), :Enter tmux.send_keys %(seq 1000 | sed s/^2$// | #{FZF} -m --preview 'sleep 0.2; echo {{}-{+}}' --bind ?:toggle-preview), :Enter
tmux.until { |lines| lines[1].include?(' {1-1}') } tmux.until { |lines| lines[1].include?(' {1-1}') }
@@ -1436,7 +1457,7 @@ class TestGoFZF < TestBase
end end
def test_preview_hidden def test_preview_hidden
tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-\\$FZF_PREVIEW_LINES-\\$FZF_PREVIEW_COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-$FZF_PREVIEW_LINES-$FZF_PREVIEW_COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter
tmux.until { |lines| lines[-1] == '>' } tmux.until { |lines| lines[-1] == '>' }
tmux.send_keys '?' tmux.send_keys '?'
tmux.until { |lines| lines[-2] =~ / {1-1-1-[0-9]+}/ } tmux.until { |lines| lines[-2] =~ / {1-1-1-[0-9]+}/ }
@@ -1640,11 +1661,80 @@ class TestGoFZF < TestBase
tmux.until { |lines| lines.item_count == 553 && lines.match_count == 1 } tmux.until { |lines| lines.item_count == 553 && lines.match_count == 1 }
tmux.until { |lines| !lines[-2].include?('(1/2)') } tmux.until { |lines| !lines[-2].include?('(1/2)') }
end end
def test_reload_even_when_theres_no_match
tmux.send_keys %(: | #{FZF} --bind 'space:reload(seq 10)'), :Enter
tmux.until { |lines| lines.item_count.zero? }
tmux.send_keys :Space
tmux.until { |lines| lines.item_count == 10 }
end
def test_clear_list_when_header_lines_changed_due_to_reload
tmux.send_keys %(seq 10 | #{FZF} --header 0 --header-lines 3 --bind 'space:reload(seq 1)'), :Enter
tmux.until { |lines| lines.any? { |line| line.include?('9') } }
tmux.send_keys :Space
tmux.until { |lines| lines.none? { |line| line.include?('9') } }
end
def test_clear_query
tmux.send_keys %(: | #{FZF} --query foo --bind space:clear-query), :Enter
tmux.until { |lines| lines.item_count.zero? }
tmux.until { |lines| lines.last.include?('> foo') }
tmux.send_keys 'C-a', 'bar'
tmux.until { |lines| lines.last.include?('> barfoo') }
tmux.send_keys :Space
tmux.until { |lines| lines.last == '>' }
end
def test_clear_selection
tmux.send_keys %(seq 100 | #{FZF} --multi --bind space:clear-selection), :Enter
tmux.until { |lines| lines.match_count == 100 }
tmux.send_keys :Tab
tmux.until { |lines| lines[-2].include?('(1)') }
tmux.send_keys 'foo'
tmux.until { |lines| lines.match_count.zero? }
tmux.until { |lines| lines[-2].include?('(1)') }
tmux.send_keys :Space
tmux.until { |lines| lines.match_count.zero? }
tmux.until { |lines| !lines[-2].include?('(1)') }
end
def test_backward_delete_char_eof
tmux.send_keys "seq 1000 | #{fzf "--bind 'bs:backward-delete-char/eof'"}", :Enter
tmux.until { |lines| lines[-2] == ' 1000/1000' }
tmux.send_keys '11'
tmux.until { |lines| lines[-1] == '> 11' }
tmux.send_keys :BSpace
tmux.until { |lines| lines[-1] == '> 1' }
tmux.send_keys :BSpace
tmux.until { |lines| lines[-1] == '>' }
tmux.send_keys :BSpace
tmux.prepare
end
def test_strip_xterm_osc_sequence
%W[\x07 \x1b\\].each do |esc|
writelines tempname, [%(printf $1"\e]4;3;rgb:aa/bb/cc#{esc} "$2)]
File.chmod(0o755, tempname)
tmux.prepare
tmux.send_keys(
%(echo foo bar | #{FZF} --preview '#{tempname} {2} {1}'), :Enter
)
tmux.until { |lines| lines.any_include?('bar foo') }
tmux.send_keys :Enter
end
end
def test_keep_right
tmux.send_keys("seq 10000 | #{FZF} --read0 --keep-right", :Enter)
tmux.until { |lines| lines.any_include?('9999 10000') }
end
end end
module TestShell module TestShell
def setup def setup
super @tmux = Tmux.new shell
tmux.prepare
end end
def teardown def teardown
@@ -1704,7 +1794,7 @@ module TestShell
tmux.until { |lines| lines.select_count == 2 } tmux.until { |lines| lines.select_count == 2 }
tmux.send_keys :Enter tmux.send_keys :Enter
tmux.until { |lines| lines.any_include?(/echo.*fzf-unicode.*1.*fzf-unicode.*2/) } tmux.until { |lines| lines.join.match(/echo.*fzf-unicode.*1.*fzf-unicode.*2/) }
tmux.send_keys :Enter tmux.send_keys :Enter
tmux.until { |lines| lines.any_include?(/^fzf-unicode.*1.*fzf-unicode.*2/) } tmux.until { |lines| lines.any_include?(/^fzf-unicode.*1.*fzf-unicode.*2/) }
end end
@@ -1745,15 +1835,18 @@ module TestShell
tmux.send_keys 'echo 1st', :Enter; tmux.prepare tmux.send_keys 'echo 1st', :Enter; tmux.prepare
tmux.send_keys 'echo 2nd', :Enter; tmux.prepare tmux.send_keys 'echo 2nd', :Enter; tmux.prepare
tmux.send_keys 'echo 3d', :Enter; tmux.prepare tmux.send_keys 'echo 3d', :Enter; tmux.prepare
tmux.send_keys 'echo 3rd', :Enter; tmux.prepare 3.times { tmux.send_keys 'echo 3rd', :Enter; tmux.prepare }
tmux.send_keys 'echo 4th', :Enter tmux.send_keys 'echo 4th', :Enter
retries do retries do
tmux.prepare tmux.prepare
tmux.send_keys 'C-r' tmux.send_keys 'C-r'
tmux.until { |lines| lines.match_count.positive? } tmux.until { |lines| lines.match_count.positive? }
end end
tmux.send_keys 'e3d'
# Duplicates removed: 3d (1) + 3rd (1) => 2 matches
tmux.until { |lines| lines.match_count == 2 }
tmux.until { |lines| lines[-3].end_with? 'echo 3d' }
tmux.send_keys 'C-r' tmux.send_keys 'C-r'
tmux.send_keys '3d'
tmux.until { |lines| lines[-3].end_with? 'echo 3rd' } tmux.until { |lines| lines[-3].end_with? 'echo 3rd' }
tmux.send_keys :Enter tmux.send_keys :Enter
tmux.until { |lines| lines[-1] == 'echo 3rd' } tmux.until { |lines| lines[-1] == 'echo 3rd' }
@@ -1761,6 +1854,37 @@ module TestShell
tmux.until { |lines| lines[-1] == '3rd' } tmux.until { |lines| lines[-1] == '3rd' }
end end
def test_ctrl_r_multiline
tmux.send_keys 'echo "foo', :Enter, 'bar"', :Enter
tmux.until { |lines| lines[-2..-1] == ['foo', 'bar'] }
retries do
tmux.prepare
tmux.send_keys 'C-r'
tmux.until { |lines| lines[-1] == '>' }
end
tmux.send_keys 'foo bar'
tmux.until { |lines| lines[-3].end_with? 'bar"' }
tmux.send_keys :Enter
tmux.until { |lines| lines[-1].end_with? 'bar"' }
tmux.send_keys :Enter
tmux.until { |lines| lines[-2..-1] == ['foo', 'bar'] }
end
def test_ctrl_r_abort
skip "doesn't restore the original line when search is aborted pre Bash 4" if shell == :bash && /(?<= version )\d+/.match(`#{Shell.bash} --version`).to_s.to_i < 4
%w[foo ' "].each do |query|
retries do
tmux.prepare
tmux.send_keys(:Space, 'C-e', 'C-u', query)
tmux.until { |lines| lines[-1].start_with? query }
tmux.send_keys 'C-r'
tmux.until { |lines| lines[-1] == "> #{query}" }
end
tmux.send_keys 'C-g'
tmux.until { |lines| lines[-1].start_with? query }
end
end
def retries(times = 3) def retries(times = 3)
(times - 1).times do (times - 1).times do
begin begin
@@ -1890,7 +2014,7 @@ module CompletionTest
end end
def test_custom_completion def test_custom_completion
tmux.send_keys '_fzf_compgen_path() { echo "\$1"; seq 10; }', :Enter tmux.send_keys '_fzf_compgen_path() { echo "$1"; seq 10; }', :Enter
tmux.prepare tmux.prepare
tmux.send_keys 'ls /tmp/**', :Tab tmux.send_keys 'ls /tmp/**', :Tab
tmux.until { |lines| lines.match_count == 11 } tmux.until { |lines| lines.match_count == 11 }
@@ -1913,15 +2037,16 @@ module CompletionTest
# FZF_TMUX=1 # FZF_TMUX=1
new_shell new_shell
tmux.send_keys 'unset FZFFOOBR**', :Tab, pane: 0 tmux.focus
tmux.until(false, 1) { |lines| lines.match_count == 1 } tmux.send_keys 'unset FZFFOOBR**', :Tab
tmux.until { |lines| lines.match_count == 1 }
tmux.send_keys :Enter tmux.send_keys :Enter
tmux.until { |lines| lines[-1].include? 'unset FZFFOOBAR' } tmux.until { |lines| lines[-1].include? 'unset FZFFOOBAR' }
end end
def test_file_completion_unicode def test_file_completion_unicode
FileUtils.mkdir_p '/tmp/fzf-test' FileUtils.mkdir_p '/tmp/fzf-test'
tmux.paste 'cd /tmp/fzf-test; echo -n test3 > "fzf-unicode 테스트1"; echo -n test4 > "fzf-unicode 테스트2"' tmux.paste "cd /tmp/fzf-test; echo -n test3 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2701'; echo -n test4 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2702'"
tmux.prepare tmux.prepare
tmux.send_keys 'cat fzf-unicode**', :Tab tmux.send_keys 'cat fzf-unicode**', :Tab
tmux.until { |lines| lines.match_count == 2 } tmux.until { |lines| lines.match_count == 2 }
@@ -1944,23 +2069,41 @@ module CompletionTest
tmux.send_keys :Enter tmux.send_keys :Enter
tmux.until { |lines| lines[-1].include? 'test3test4' } tmux.until { |lines| lines[-1].include? 'test3test4' }
end end
def test_custom_completion_api
tmux.send_keys 'eval "_fzf$(declare -f _comprun)"', :Enter
%w[f g].each do |command|
tmux.prepare
tmux.send_keys "#{command} b**", :Tab
tmux.until do |lines|
lines.item_count == 2 && lines.match_count == 1 &&
lines.any_include?("prompt-#{command}") &&
lines.any_include?("preview-#{command}-bar")
end
tmux.send_keys :Enter
tmux.until { |lines| lines[-1].include?("#{command} #{command}barbar") }
tmux.send_keys 'C-u'
end
ensure
tmux.prepare
tmux.send_keys 'unset -f _fzf_comprun', :Enter
end
end end
class TestBash < TestBase class TestBash < TestBase
include TestShell include TestShell
include CompletionTest include CompletionTest
def shell
:bash
end
def new_shell def new_shell
tmux.prepare tmux.prepare
tmux.send_keys "FZF_TMUX=1 #{Shell.bash}", :Enter tmux.send_keys "FZF_TMUX=1 #{Shell.bash}", :Enter
tmux.prepare tmux.prepare
end end
def setup
super
@tmux = Tmux.new :bash
end
def test_dynamic_completion_loader def test_dynamic_completion_loader
tmux.paste 'touch /tmp/foo; _fzf_completion_loader=1' tmux.paste 'touch /tmp/foo; _fzf_completion_loader=1'
tmux.paste '_completion_loader() { complete -o default fake; }' tmux.paste '_completion_loader() { complete -o default fake; }'
@@ -1983,20 +2126,23 @@ class TestZsh < TestBase
include TestShell include TestShell
include CompletionTest include CompletionTest
def shell
:zsh
end
def new_shell def new_shell
tmux.send_keys "FZF_TMUX=1 #{Shell.zsh}", :Enter tmux.send_keys "FZF_TMUX=1 #{Shell.zsh}", :Enter
tmux.prepare tmux.prepare
end end
def setup
super
@tmux = Tmux.new :zsh
end
end end
class TestFish < TestBase class TestFish < TestBase
include TestShell include TestShell
def shell
:fish
end
def new_shell def new_shell
tmux.send_keys 'env FZF_TMUX=1 fish', :Enter tmux.send_keys 'env FZF_TMUX=1 fish', :Enter
tmux.send_keys 'function fish_prompt; end; clear', :Enter tmux.send_keys 'function fish_prompt; end; clear', :Enter
@@ -2008,9 +2154,60 @@ class TestFish < TestBase
tmux.send_keys "set -g #{name} '#{val}'", :Enter tmux.send_keys "set -g #{name} '#{val}'", :Enter
tmux.prepare tmux.prepare
end end
def setup
super
@tmux = Tmux.new :fish
end
end end
__END__
# Setup fzf
# ---------
if [[ ! "$PATH" == *<%= BASE %>/bin* ]]; then
export PATH="${PATH:+${PATH}:}<%= BASE %>/bin"
fi
# Auto-completion
# ---------------
[[ $- == *i* ]] && source "<%= BASE %>/shell/completion.<%= __method__ %>" 2> /dev/null
# Key bindings
# ------------
source "<%= BASE %>/shell/key-bindings.<%= __method__ %>"
PS1= PROMPT_COMMAND= HISTFILE= HISTSIZE=100
unset <%= UNSETS.join(' ') %>
# Old API
_fzf_complete_f() {
_fzf_complete "+m --multi --prompt \"prompt-f> \"" "$@" < <(
echo foo
echo bar
)
}
# New API
_fzf_complete_g() {
_fzf_complete +m --multi --prompt "prompt-g> " -- "$@" < <(
echo foo
echo bar
)
}
_fzf_complete_f_post() {
awk '{print "f" $0 $0}'
}
_fzf_complete_g_post() {
awk '{print "g" $0 $0}'
}
[ -n "$BASH" ] && complete -F _fzf_complete_f -o default -o bashdefault f
[ -n "$BASH" ] && complete -F _fzf_complete_g -o default -o bashdefault g
_comprun() {
local command=$1
shift
case "$command" in
f) fzf "$@" --preview 'echo preview-f-{}' ;;
g) fzf "$@" --preview 'echo preview-g-{}' ;;
*) fzf "$@" ;;
esac
}